Console support

This commit is contained in:
yangzhi 2019-05-06 09:49:38 +08:00
parent 0a17a0e6fd
commit c14ceff074
14 changed files with 1401 additions and 1084 deletions

File diff suppressed because it is too large Load Diff

View File

@ -14,20 +14,9 @@ namespace DllRewrite
{
MethodPatcher mp = new MethodPatcher();
AssemblyDefinition StardewValley = mp.InsertModHooks();
TypeDefinition typeModHooksObject = StardewValley.MainModule.GetType("StardewValley.ModHooks");
TypeDefinition typeObject = StardewValley.MainModule.GetType("StardewValley.Object");
//foreach (MethodDefinition method in typeObject.Methods) {
// if(!method.IsConstructor && method.HasBody)
// {
// var processor = method.Body.GetILProcessor();
// var hook = typeModHooksObject.Methods.FirstOrDefault(m => m.Name == "OnObject_xxx");
// var newInstruction = processor.Create(OpCodes.Callvirt, hook);
// var firstInstruction = method.Body.Instructions[0];
// processor.InsertBefore(firstInstruction, newInstruction);
// }
//}
StardewValley.Write("./StardewValley.dll");
//AssemblyDefinition MonoFramework = mp.InsertMonoHooks();
//MonoFramework.Write("./MonoGame.Framework.dll");
}
}
}

118
src/GameConsole.cs Normal file
View File

@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Internal.ConsoleWriting;
using StardewValley;
using StardewValley.Menus;
namespace StardewModdingAPI
{
class GameConsole : IClickableMenu
{
public static GameConsole Instance;
public bool IsVisible;
private readonly LinkedList<KeyValuePair<ConsoleLogLevel, string>> _consoleMessageQueue = new LinkedList<KeyValuePair<ConsoleLogLevel, string>>();
private readonly TextBox Textbox;
private Rectangle TextboxBounds;
private SpriteFont _smallFont;
internal GameConsole()
{
Instance = this;
this.IsVisible = true;
this.Textbox = new TextBox(null, null, Game1.dialogueFont, Game1.textColor)
{
X = 0,
Y = 0,
Width = 1280,
Height = 320
};
this.TextboxBounds = new Rectangle(this.Textbox.X, this.Textbox.Y, this.Textbox.Width, this.Textbox.Height);
}
internal void InitContent(LocalizedContentManager content)
{
this._smallFont = content.Load<SpriteFont>(@"Fonts\SmallFont");
}
public void Show()
{
Game1.activeClickableMenu = this;
this.IsVisible = true;
}
public override void receiveLeftClick(int x, int y, bool playSound = true)
{
if (this.TextboxBounds.Contains(x, y))
{
this.Textbox.OnEnterPressed += sender => { SGame.instance.CommandQueue.Enqueue(sender.Text); this.Textbox.Text = ""; };
Game1.keyboardDispatcher.Subscriber = this.Textbox;
typeof(TextBox).GetMethod("ShowAndroidKeyboard", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(this.Textbox, new object[] { });
}
else
{
Game1.activeClickableMenu = null;
this.IsVisible = false;
}
}
public void WriteLine(string consoleMessage, ConsoleLogLevel level)
{
lock (this._consoleMessageQueue)
{
this._consoleMessageQueue.AddFirst(new KeyValuePair<ConsoleLogLevel, string>(level, consoleMessage));
if (this._consoleMessageQueue.Count > 2000)
{
this._consoleMessageQueue.RemoveLast();
}
}
}
public override void draw(SpriteBatch spriteBatch)
{
Vector2 size = this._smallFont.MeasureString("aA");
float y = Game1.game1.screen.Height - size.Y * 2;
lock (this._consoleMessageQueue)
{
foreach (var log in this._consoleMessageQueue)
{
string text = log.Value;
switch (log.Key)
{
case ConsoleLogLevel.Critical:
case ConsoleLogLevel.Error:
spriteBatch.DrawString(this._smallFont, text, new Vector2(16, y), Color.Red);
break;
case ConsoleLogLevel.Alert:
case ConsoleLogLevel.Warn:
spriteBatch.DrawString(this._smallFont, text, new Vector2(16, y), Color.Orange);
break;
case ConsoleLogLevel.Info:
case ConsoleLogLevel.Success:
spriteBatch.DrawString(this._smallFont, text, new Vector2(16, y), Color.AntiqueWhite);
break;
case ConsoleLogLevel.Debug:
case ConsoleLogLevel.Trace:
spriteBatch.DrawString(this._smallFont, text, new Vector2(16, y), Color.LightGray);
break;
default:
spriteBatch.DrawString(this._smallFont, text, new Vector2(16, y), Color.LightGray);
break;
}
size = this._smallFont.MeasureString(text);
if (y < 0)
{
break;
}
y -= size.Y;
}
}
}
}
}

View File

@ -426,6 +426,7 @@
<Compile Include="Newtonsoft.Json\Utilities\TypeExtensions.cs" />
<Compile Include="Newtonsoft.Json\Utilities\ValidationUtils.cs" />
<Compile Include="Newtonsoft.Json\WriteState.cs" />
<Compile Include="GameConsole.cs" />
<Compile Include="Options\ModOptionsCheckbox.cs" />
<Compile Include="Options\ModOptionsSlider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -5,6 +5,7 @@ using StardewModdingAPI.Framework;
using System.Threading;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using StardewModdingAPI;
using StardewValley.Menus;
using StardewValley.Buildings;
using StardewValley.Objects;
@ -21,9 +22,16 @@ namespace SMDroid
/// <summary>SMAPI's content manager.</summary>
private ContentCoordinator ContentCore { get; set; }
public static bool ContextInitialize = true;
public static ModEntry Instance;
public static bool IsHalt = false;
public ModEntry()
{
Instance = this;
new GameConsole();
this.core = new SCore(Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid/Mods"), false);
}
public override bool OnGame1_CreateContentManager_Prefix(Game1 game1, IServiceProvider serviceProvider, string rootDirectory, ref LocalizedContentManager __result)
@ -35,7 +43,8 @@ namespace SMDroid
this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, SGame.ConstructorHack.Monitor, SGame.ConstructorHack.Reflection, SGame.ConstructorHack.JsonHelper, SGame.OnLoadingFirstAsset ?? SGame.ConstructorHack?.OnLoadingFirstAsset);
this.NextContentManagerIsMain = true;
__result = this.ContentCore.CreateGameContentManager("Game1._temporaryContent");
this.core.RunInteractively(this.ContentCore);
ContextInitialize = true;
this.core.RunInteractively(this.ContentCore, __result);
return false;
}
// Game1.content initialising from LoadContent
@ -43,6 +52,8 @@ namespace SMDroid
{
this.NextContentManagerIsMain = false;
__result = this.ContentCore.MainContentManager;
GameConsole.Instance.InitContent(__result);
ContextInitialize = false;
return false;
}

View File

@ -62,7 +62,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
public override T Load<T>(string assetName, LanguageCode language)
{
// raise first-load callback
if (GameContentManager.IsFirstLoad)
if (!SMDroid.ModEntry.ContextInitialize && GameContentManager.IsFirstLoad)
{
GameContentManager.IsFirstLoad = false;
this.OnLoadingFirstAsset();

View File

@ -146,6 +146,10 @@ namespace StardewModdingAPI.Framework
this.ConsoleWriter.WriteLine(consoleMessage, level);
});
}
else if (this.ShowTraceInConsole || level != ConsoleLogLevel.Trace)
{
GameConsole.Instance.WriteLine(consoleMessage, level);
}
// write to log file
this.LogFile.WriteLine(fullMessage);

View File

@ -31,11 +31,5 @@ namespace StardewModdingAPI.Framework.RewriteFacades
{
warpFarmer(locationName, tileX, tileY, facingDirectionAfterWarp, false, true, false);
}
[SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")]
public static new void warpFarmer(string locationName, int tileX, int tileY, int facingDirectionAfterWarp, bool isStructure)
{
warpFarmer(locationName, tileX, tileY, facingDirectionAfterWarp, isStructure, true, false);
}
}
}

View File

@ -11,10 +11,12 @@ using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.Xna.Framework.Graphics;
#if SMAPI_FOR_WINDOWS
using System.Windows.Forms;
#endif
using Newtonsoft.Json;
using SMDroid;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Exceptions;
@ -159,6 +161,7 @@ namespace StardewModdingAPI.Framework
// init logging
this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info);
this.Monitor.Log($"SMDroid 1.4.0 for Stardew Valley Android release {MainActivity.instance.GetBuild()}", LogLevel.Info);
this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info);
if (modsPath != Constants.DefaultModsPath)
this.Monitor.Log("(Using custom --mods-path argument.)", LogLevel.Trace);
@ -181,7 +184,6 @@ namespace StardewModdingAPI.Framework
// add more leniant assembly resolvers
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => AssemblyLoader.ResolveAssembly(e.Name);
SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper, this.InitialiseBeforeFirstAssetLoaded);
// validate platform
//#if SMAPI_FOR_WINDOWS
// if (Constants.Platform != Platform.Windows)
@ -202,7 +204,7 @@ namespace StardewModdingAPI.Framework
/// <summary>Launch SMAPI.</summary>
[HandleProcessCorruptedStateExceptions, SecurityCritical] // let try..catch handle corrupted state exceptions
public void RunInteractively(ContentCoordinator contentCore)
public void RunInteractively(ContentCoordinator contentCore, LocalizedContentManager contentManager)
{
// initialise SMAPI
try
@ -244,7 +246,6 @@ namespace StardewModdingAPI.Framework
new ObjectErrorPatch(),
new LoadForNewGamePatch(this.Reflection, this.GameInstance.OnLoadStageChanged)
);
//// add exit handler
//new Thread(() =>
//{
@ -309,8 +310,8 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"You have SMAPI for developers, so the console will be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Info);
if (!this.Settings.CheckForUpdates)
this.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Warn);
if (!this.Monitor.WriteToConsole)
this.Monitor.Log("Writing to the terminal is disabled because the --no-terminal argument was received. This usually means launching the terminal failed.", LogLevel.Warn);
//if (!this.Monitor.WriteToConsole)
// this.Monitor.Log("Writing to the terminal is disabled because the --no-terminal argument was received. This usually means launching the terminal failed.", LogLevel.Warn);
this.Monitor.VerboseLog("Verbose logging enabled.");
// update window titles
@ -365,46 +366,51 @@ namespace StardewModdingAPI.Framework
return;
}
// load mod data
ModToolkit toolkit = new ModToolkit();
ModDatabase modDatabase = toolkit.GetModDatabase(Constants.ApiMetadataPath);
// load mods
this.GameInstance.IsSuspended = true;
new Thread(() =>
{
this.Monitor.Log("Loading mod metadata...", LogLevel.Trace);
ModResolver resolver = new ModResolver();
// load manifests
IModMetadata[] mods = resolver.ReadManifests(toolkit, this.ModsPath, modDatabase).ToArray();
// filter out ignored mods
foreach (IModMetadata mod in mods.Where(p => p.IsIgnored))
this.Monitor.Log($" Skipped {mod.RelativeDirectoryPath} (folder name starts with a dot).", LogLevel.Trace);
mods = mods.Where(p => !p.IsIgnored).ToArray();
// load mod data
ModToolkit toolkit = new ModToolkit();
ModDatabase modDatabase = toolkit.GetModDatabase(Constants.ApiMetadataPath);
// load mods
resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl);
mods = resolver.ProcessDependencies(mods, modDatabase).ToArray();
this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase);
// write metadata file
if (this.Settings.DumpMetadata)
{
ModFolderExport export = new ModFolderExport
this.Monitor.Log("Loading mod metadata...", LogLevel.Trace);
ModResolver resolver = new ModResolver();
// load manifests
IModMetadata[] mods = resolver.ReadManifests(toolkit, this.ModsPath, modDatabase).ToArray();
// filter out ignored mods
foreach (IModMetadata mod in mods.Where(p => p.IsIgnored))
this.Monitor.Log($" Skipped {mod.RelativeDirectoryPath} (folder name starts with a dot).", LogLevel.Trace);
mods = mods.Where(p => !p.IsIgnored).ToArray();
// load mods
resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl);
mods = resolver.ProcessDependencies(mods, modDatabase).ToArray();
this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase);
// write metadata file
if (this.Settings.DumpMetadata)
{
Exported = DateTime.UtcNow.ToString("O"),
ApiVersion = Constants.ApiVersion.ToString(),
GameVersion = Constants.GameVersion.ToString(),
ModFolderPath = this.ModsPath,
Mods = mods
};
this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}metadata-dump.json"), export);
ModFolderExport export = new ModFolderExport
{
Exported = DateTime.UtcNow.ToString("O"),
ApiVersion = Constants.ApiVersion.ToString(),
GameVersion = Constants.GameVersion.ToString(),
ModFolderPath = this.ModsPath,
Mods = mods
};
this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}metadata-dump.json"), export);
}
// check for updates
//this.CheckForUpdatesAsync(mods);
}
// check for updates
//this.CheckForUpdatesAsync(mods);
}
GameConsole.Instance.IsVisible = false;
this.GameInstance.IsSuspended = false;
}).Start();
// update window titles
//int modsLoaded = this.ModRegistry.GetAll().Count();
//Game1.game1.Window.Title = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion} with {modsLoaded} mods";

File diff suppressed because it is too large Load Diff

View File

@ -53,9 +53,9 @@ namespace StardewModdingAPI.Framework.StateTracking
/// <summary>Construct an instance.</summary>
/// <param name="locations">The game's list of locations.</param>
/// <param name="activeMineLocations">The game's list of active mine locations.</param>
public WorldLocationsTracker(List<GameLocation> locations, IList<MineShaft> activeMineLocations)
public WorldLocationsTracker(ObservableCollection<GameLocation> locations, IList<MineShaft> activeMineLocations)
{
this.LocationListWatcher = WatcherFactory.ForReferenceList(locations);
this.LocationListWatcher = WatcherFactory.ForObservableCollection(locations);
this.MineLocationListWatcher = WatcherFactory.ForReferenceList(activeMineLocations);
}

View File

@ -65,7 +65,7 @@ namespace StardewModdingAPI.Framework
this.WindowSizeWatcher = WatcherFactory.ForEquatable(() => new Point(Game1.viewport.Width, Game1.viewport.Height));
this.TimeWatcher = WatcherFactory.ForEquatable(() => Game1.timeOfDay);
this.ActiveMenuWatcher = WatcherFactory.ForReference(() => Game1.activeClickableMenu);
this.LocationsWatcher = new WorldLocationsTracker(Game1.locations, MineShaft.activeMines);
this.LocationsWatcher = new WorldLocationsTracker((ObservableCollection<GameLocation>) Game1.locations, MineShaft.activeMines);
this.LocaleWatcher = WatcherFactory.ForGenericEquality(() => LocalizedContentManager.CurrentLanguageCode);
this.Watchers.AddRange(new IWatcher[]
{

View File

@ -37,8 +37,6 @@ namespace StardewModdingAPI.Metadata
// rewrite for Stardew Valley 1.3
yield return new StaticFieldToConstantRewriter<int>(typeof(Game1), "tileSize", Game1.tileSize);
yield return new TypeReferenceRewriter("System.Collections.Generic.IList`1<StardewValley.GameLocation>", typeof(List<GameLocation>));
yield return new TypeReferenceRewriter("System.Collections.Generic.IList`1<StardewValley.Menus.IClickableMenu>", typeof(List<IClickableMenu>));
yield return new FieldToPropertyRewriter(typeof(Game1), "player");
@ -61,11 +59,11 @@ namespace StardewModdingAPI.Metadata
yield return new FieldToPropertyRewriter(typeof(Game1), "isDebrisWeather");
yield return new MethodParentRewriter(typeof(IClickableMenu), typeof(IClickableMenuMethods), onlyIfPlatformChanged: true);
yield return new MethodParentRewriter(typeof(IClickableMenu), typeof(IClickableMenuMethods));
yield return new MethodParentRewriter(typeof(Game1), typeof(Game1Methods), onlyIfPlatformChanged: true);
yield return new MethodParentRewriter(typeof(Game1), typeof(Game1Methods));
yield return new MethodParentRewriter(typeof(Farmer), typeof(FarmerMethods), onlyIfPlatformChanged: true);
yield return new MethodParentRewriter(typeof(Farmer), typeof(FarmerMethods));
/****
** detect mod issues

View File

@ -76,7 +76,7 @@ namespace StardewModdingAPI.Patches
if (LoadForNewGamePatch.IsCreating)
{
// raise CreatedBasicInfo after locations are cleared twice
List<GameLocation> locations = Game1.locations;
IList<GameLocation> locations = Game1.locations;
//locations.CollectionChanged += LoadForNewGamePatch.OnLocationListChanged;
}
@ -90,7 +90,7 @@ namespace StardewModdingAPI.Patches
if (LoadForNewGamePatch.IsCreating)
{
// clean up
List<GameLocation> locations = Game1.locations;
IList<GameLocation> locations = Game1.locations;
//locations.CollectionChanged -= LoadForNewGamePatch.OnLocationListChanged;
// raise stage changed