diff --git a/DllRewrite/DllRewrite/MethodPatcher.cs b/DllRewrite/DllRewrite/MethodPatcher.cs index 6a229f73..30645360 100644 --- a/DllRewrite/DllRewrite/MethodPatcher.cs +++ b/DllRewrite/DllRewrite/MethodPatcher.cs @@ -27,13 +27,14 @@ namespace DllRewrite this.StardewValley = this.resolver.Resolve(new AssemblyNameReference("StardewValley", new Version("1.3.0.0"))); this.StardewModdingAPI = this.resolver.Resolve(new AssemblyNameReference("StardewModdingAPI", new Version("0.0.0.0"))); } - public void InsertModHook(string name, TypeReference[] paraTypes, TypeReference returnType) + public void InsertModHook(string name, TypeReference[] paraTypes, string[] paraNames, TypeReference returnType) { TypeDefinition typeModHooksObject = this.StardewValley.MainModule.GetType("StardewValley.ModHooks"); var hook = new MethodDefinition(name, MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.HideBySig, returnType); - foreach (TypeReference typeReference in paraTypes) + for(int i = 0; i< paraTypes.Length; i++) { - hook.Parameters.Add(new ParameterDefinition(typeReference)); + ParameterDefinition define = new ParameterDefinition(paraNames[i], ParameterAttributes.None, paraTypes[i]); + hook.Parameters.Add(define); } switch (returnType.FullName) { @@ -111,6 +112,56 @@ namespace DllRewrite instructions.Add(processor.Create(OpCodes.Stsfld, this.GetFieldReference("hooks", "StardewValley.Game1", this.StardewValley))); this.InsertInstructions(processor, jointPoint, instructions); + // isRaining and isDebrisWeather + PropertyDefinition propertyDefinition = new PropertyDefinition("isRaining", PropertyAttributes.None, this.GetTypeReference("System.Boolean")); + propertyDefinition.GetMethod = new MethodDefinition("get_isRaining", MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.SpecialName | MethodAttributes.Static | MethodAttributes.HideBySig, this.GetTypeReference("System.Boolean")); + propertyDefinition.GetMethod.SemanticsAttributes = MethodSemanticsAttributes.Getter; + processor = propertyDefinition.GetMethod.Body.GetILProcessor(); + TypeDefinition typeRainManager = this.StardewValley.MainModule.GetType("StardewValley.RainManager"); + MethodDefinition getMethod = typeRainManager.Methods.FirstOrDefault(m => m.Name == "get_Instance"); + processor.Emit(OpCodes.Callvirt, getMethod); + FieldReference isRainingField = this.GetFieldReference("isRaining", "StardewValley.RainManager", this.StardewValley); + processor.Emit(OpCodes.Ldfld, isRainingField); + processor.Emit(OpCodes.Ret); + typeGame1.Methods.Add(propertyDefinition.GetMethod); + typeGame1.Properties.Add(propertyDefinition); + + propertyDefinition = new PropertyDefinition("isDebrisWeather", PropertyAttributes.None, this.GetTypeReference("System.Boolean")); + propertyDefinition.GetMethod = new MethodDefinition("get_isDebrisWeather", MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.SpecialName | MethodAttributes.Static | MethodAttributes.HideBySig, this.GetTypeReference("System.Boolean")); + propertyDefinition.GetMethod.SemanticsAttributes = MethodSemanticsAttributes.Getter; + processor = propertyDefinition.GetMethod.Body.GetILProcessor(); + TypeDefinition typeWeatherDebrisManager = this.StardewValley.MainModule.GetType("StardewValley.WeatherDebrisManager"); + getMethod = typeWeatherDebrisManager.Methods.FirstOrDefault(m => m.Name == "get_Instance"); + processor.Emit(OpCodes.Callvirt, getMethod); + FieldReference isDebrisWeatherField = this.GetFieldReference("isDebrisWeather", "StardewValley.WeatherDebrisManager", this.StardewValley); + processor.Emit(OpCodes.Ldfld, isDebrisWeatherField); + processor.Emit(OpCodes.Ret); + typeGame1.Methods.Add(propertyDefinition.GetMethod); + typeGame1.Properties.Add(propertyDefinition); + + //HUDMessage..ctor + TypeDefinition typeHUDMessage = this.StardewValley.MainModule.GetType("StardewValley.HUDMessage"); + MethodDefinition hudConstructor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.HideBySig, this.GetTypeReference("System.Void")); + hudConstructor.Parameters.Add(new ParameterDefinition(this.GetTypeReference("System.String"))); + hudConstructor.Parameters.Add(new ParameterDefinition(this.GetTypeReference("System.Int32"))); + processor = hudConstructor.Body.GetILProcessor(); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldarg_1); + processor.Emit(OpCodes.Ldarg_2); + processor.Emit(OpCodes.Ldc_I4_M1); + MethodDefinition targetConstructor = typeHUDMessage.Methods.FirstOrDefault(item => { + if(item.Parameters.Count == 3 && item.Parameters[0].ParameterType.FullName == "System.String" + && item.Parameters[1].ParameterType.FullName == "System.Int32" && item.Parameters[1].ParameterType.FullName == "System.Int32") + { + return true; + } + return false; + }); + processor.Emit(OpCodes.Call, targetConstructor); + processor.Emit(OpCodes.Ret); + typeHUDMessage.Methods.Add(hudConstructor); + + // Back Button Fix MethodDefinition method = typeGame1.Methods.FirstOrDefault(m => m.Name == "_updateAndroidMenus"); processor = method.Body.GetILProcessor(); @@ -120,6 +171,153 @@ namespace DllRewrite var GetGamePadState = typeInputState.Methods.FirstOrDefault(m => m.Name == "GetGamePadState"); processor.Replace(method.Body.Instructions[1], processor.Create(OpCodes.Callvirt, GetGamePadState)); } + public void ApplyCommonHookEntry(TypeDefinition targetType, string methodname, string hookname, bool patchPrefix = true, bool patchPostfix = true, Func methodFilter = null) + { + string qualifyName = $"{targetType.FullName}.{methodname}"; + TypeDefinition typeModHooksObject = this.StardewValley.MainModule.GetType("StardewValley.ModHooks"); + var targetMethod = targetType.Methods.FirstOrDefault(method => (!method.IsConstructor && method.HasBody && method.Name == methodname && (methodFilter == null || methodFilter(method)))); + var prefixHook = typeModHooksObject.Methods.FirstOrDefault(m => m.Name == hookname + "_Prefix"); + var postfixHook = typeModHooksObject.Methods.FirstOrDefault(m => m.Name == hookname + "_Postfix"); + var processor = targetMethod.Body.GetILProcessor(); + FieldReference hooksField = this.GetFieldReference("hooks", "StardewValley.Game1", this.StardewValley); + Instruction jointPoint; + List instructions; + byte i, j; + // state + byte returnIndex = 0; + byte parameterIndexBegin = 0, parameterIndexEnd = 0; + byte parameterOffset = (targetMethod.IsStatic ? (byte)0 : (byte)1); + byte stateIndex = (byte)targetMethod.Body.Variables.Count; + targetMethod.Body.Variables.Add(new VariableDefinition(this.GetTypeReference("System.Boolean"))); + if (targetMethod.ReturnType.FullName != "System.Void") + { + // return + returnIndex = (byte)targetMethod.Body.Variables.Count; + targetMethod.Body.Variables.Add(new VariableDefinition(this.GetTypeReference("System.Object"))); + } + parameterIndexBegin = (byte)targetMethod.Body.Variables.Count; + for (i = 0; i < targetMethod.Parameters.Count; i++) + { + targetMethod.Body.Variables.Add(new VariableDefinition(this.GetTypeReference("System.Object"))); + parameterIndexEnd = (byte)targetMethod.Body.Variables.Count; + } + if (patchPrefix && prefixHook != null) + { + instructions = new List(); + jointPoint = targetMethod.Body.Instructions[0]; + for (i = parameterOffset, j = parameterIndexBegin; i < targetMethod.Parameters.Count + parameterOffset; i++, j++) + { + instructions.Add(this._createLdargsInstruction(processor, i)); + if(targetMethod.Parameters[i - parameterOffset].ParameterType.IsValueType) + { + instructions.Add(processor.Create(OpCodes.Box, targetMethod.Parameters[i - parameterOffset].ParameterType)); + } + instructions.Add(this._createStlocInstruction(processor, j)); + } + instructions.Add(processor.Create(OpCodes.Ldsfld, hooksField)); + instructions.Add(processor.Create(OpCodes.Ldstr, qualifyName)); + if (!targetMethod.IsStatic) + { + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + if (targetType.IsValueType) + instructions.Add(processor.Create(OpCodes.Box, targetType)); + } + for (i = parameterOffset, j = parameterIndexBegin; i < targetMethod.Parameters.Count + parameterOffset; i++, j++) + { + instructions.Add(processor.Create(OpCodes.Ldloca_S, j)); + } + for (; i < prefixHook.Parameters.Count - 2; i++) + { + instructions.Add(processor.Create(OpCodes.Ldloca_S, (byte)0)); + } + instructions.Add(processor.Create(OpCodes.Ldloca_S, returnIndex)); + instructions.Add(processor.Create(OpCodes.Callvirt, prefixHook)); + instructions.Add(this._createStlocInstruction(processor, stateIndex)); + for (i = parameterOffset, j = parameterIndexBegin; i < targetMethod.Parameters.Count + parameterOffset; i++, j++) + { + instructions.Add(this._createLdlocInstruction(processor, j)); + if (targetMethod.Parameters[i - parameterOffset].ParameterType.IsValueType) + { + instructions.Add(processor.Create(OpCodes.Unbox_Any, targetMethod.Parameters[i - parameterOffset].ParameterType)); + } + else + { + instructions.Add(processor.Create(OpCodes.Castclass, targetMethod.Parameters[i - parameterOffset].ParameterType)); + } + instructions.Add(processor.Create(OpCodes.Starg_S, i)); + } + instructions.Add(this._createLdlocInstruction(processor, stateIndex)); + instructions.Add(processor.Create(OpCodes.Brtrue, jointPoint)); + if (targetMethod.ReturnType.FullName != "System.Void") { + instructions.Add(this._createLdlocInstruction(processor, returnIndex)); + if (targetMethod.ReturnType.IsValueType) + instructions.Add(processor.Create(OpCodes.Unbox_Any, targetMethod.ReturnType)); + else + instructions.Add(processor.Create(OpCodes.Castclass, targetMethod.ReturnType)); + } + instructions.Add(processor.Create(OpCodes.Ret)); + this.InsertInstructions(processor, jointPoint, instructions); + } + if (patchPostfix && postfixHook != null) + { + instructions = new List(); + jointPoint = targetMethod.Body.Instructions[targetMethod.Body.Instructions.Count - 1]; + if (targetMethod.ReturnType.FullName != "System.Void") + { + instructions.Add(processor.Create(OpCodes.Box, targetMethod.ReturnType)); + instructions.Add(this._createStlocInstruction(processor, returnIndex)); + } + for (i = parameterOffset, j = parameterIndexBegin; i < targetMethod.Parameters.Count + parameterOffset; i++, j++) + { + instructions.Add(this._createLdargsInstruction(processor, i)); + if (targetMethod.Parameters[i - parameterOffset].ParameterType.IsValueType) + { + instructions.Add(processor.Create(OpCodes.Box, targetMethod.Parameters[i - parameterOffset].ParameterType)); + } + instructions.Add(this._createStlocInstruction(processor, j)); + } + instructions.Add(processor.Create(OpCodes.Ldsfld, hooksField)); + instructions.Add(processor.Create(OpCodes.Ldstr, qualifyName)); + if (!targetMethod.IsStatic) + { + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + if (targetType.IsValueType) + instructions.Add(processor.Create(OpCodes.Box, targetType)); + } + for (i = parameterOffset, j = parameterIndexBegin; i < targetMethod.Parameters.Count + parameterOffset; i++, j++) + { + instructions.Add(processor.Create(OpCodes.Ldloca_S, j)); + } + for (; i < postfixHook.Parameters.Count - 3; i++) + { + instructions.Add(processor.Create(OpCodes.Ldloca_S, (byte)0)); + } + if (targetMethod.ReturnType.FullName != "System.Void") + { + instructions.Add(processor.Create(OpCodes.Ldloca_S, stateIndex)); + instructions.Add(processor.Create(OpCodes.Ldloca_S, returnIndex)); + instructions.Add(processor.Create(OpCodes.Callvirt, postfixHook)); + instructions.Add(this._createLdlocInstruction(processor, returnIndex)); + if (targetMethod.ReturnType.IsValueType) + instructions.Add(processor.Create(OpCodes.Unbox_Any, targetMethod.ReturnType)); + else + instructions.Add(processor.Create(OpCodes.Castclass, targetMethod.ReturnType)); + } + else + { + instructions.Add(processor.Create(OpCodes.Ldloca_S, stateIndex)); + instructions.Add(processor.Create(OpCodes.Ldloca_S, returnIndex)); + instructions.Add(processor.Create(OpCodes.Callvirt, postfixHook)); + } + this.InsertInstructions(processor, jointPoint, instructions); + for (int x = 0; x < targetMethod.Body.Instructions.Count - 1; x++) + { + Instruction origin = targetMethod.Body.Instructions[x]; + _patchPostfixReturn(processor, instructions, origin); + } + } + } + public void ApplyHookEntry(TypeDefinition targetType, string methodname, string hookname, bool isPrefix) { TypeDefinition typeModHooksObject = this.StardewValley.MainModule.GetType("StardewValley.ModHooks"); @@ -162,10 +360,7 @@ namespace DllRewrite for(int i = 0; i < method.Body.Instructions.Count - 1; i++) { Instruction origin = method.Body.Instructions[i]; - if (origin.OpCode == OpCodes.Ret) - { - processor.Replace(origin, processor.Create(OpCodes.Br, instructions[0])); - } + _patchPostfixReturn(processor, instructions, origin); } } else @@ -204,6 +399,102 @@ namespace DllRewrite } } + + private static void _patchPostfixReturn(ILProcessor processor, List instructions, Instruction origin) + { + if (origin.OpCode == OpCodes.Ret) + { + processor.Replace(origin, processor.Create(OpCodes.Br, instructions[0])); + } + else + { + if ((origin.OpCode == OpCodes.Br && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Br_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Br; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Brfalse && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Brfalse_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Brfalse; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Brtrue && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Brtrue_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Brtrue; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Beq && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Beq_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Beq; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Bge && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Bge_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Bge; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Bge_Un && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Bge_Un_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Bge_Un; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Bgt && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Bgt_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Bgt; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Bgt_Un && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Bgt_Un_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Bgt_Un; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Ble && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Ble_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Ble; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Ble_Un && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Ble_Un_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Ble_Un; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Blt && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Blt_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Blt; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Blt_Un && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Blt_Un_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Blt_Un; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Bne_Un && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Bne_Un_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Bne_Un; + origin.Operand = instructions[0]; + } + else if ((origin.OpCode == OpCodes.Leave && ((Instruction)origin.Operand).OpCode == OpCodes.Ret) + || (origin.OpCode == OpCodes.Leave_S && ((Instruction)origin.Operand).OpCode == OpCodes.Ret)) + { + origin.OpCode = OpCodes.Leave; + origin.Operand = instructions[0]; + } + } + } + private Instruction _createStlocInstruction(ILProcessor processor, byte index) { switch (index) @@ -262,55 +553,340 @@ namespace DllRewrite public AssemblyDefinition InsertModHooks() { this.ApplyGamePatch(); - TypeDefinition typeGame1 = this.StardewValley.MainModule.GetType("StardewValley.Game1"); - this.InsertModHook("OnGame1_Update_Prefix", new TypeReference[] { - this.GetTypeReference("StardewValley.Game1", this.StardewValley), - this.GetTypeReference("Microsoft.Xna.Framework.GameTime", this.MonoGame_Framework)}, + + this.InsertModHook("OnCommonHook_Prefix", new TypeReference[] { + this.GetTypeReference("System.String"), + this.GetTypeReference("System.Object"), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object"))}, + new string[] { + "hookName", "__instance", + "param1", "param2", "param3", + "param4", "__result"}, this.GetTypeReference("System.Boolean")); - this.ApplyHookEntry(typeGame1, "Update", "OnGame1_Update_Prefix", true); - this.InsertModHook("OnGame1_Update_Postfix", new TypeReference[] { - this.GetTypeReference("StardewValley.Game1", this.StardewValley), - this.GetTypeReference("Microsoft.Xna.Framework.GameTime", this.MonoGame_Framework)}, + this.InsertModHook("OnCommonStaticHook_Prefix", new TypeReference[] { + this.GetTypeReference("System.String"), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object"))}, + new string[] { + "hookName", "param1", + "param2", "param3", "param4", + "param5", "__result"}, + this.GetTypeReference("System.Boolean")); + this.InsertModHook("OnCommonHook_Postfix", new TypeReference[] { + this.GetTypeReference("System.String"), + this.GetTypeReference("System.Object"), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Boolean")), + new ByReferenceType(this.GetTypeReference("System.Object"))}, + new string[] { + "hookName", "__instance", + "param1", "param2", "param3", + "param4", "__state", "__result"}, this.GetTypeReference("System.Void")); - this.ApplyHookEntry(typeGame1, "Update", "OnGame1_Update_Postfix", false); + this.InsertModHook("OnCommonStaticHook_Postfix", new TypeReference[] { + this.GetTypeReference("System.String"), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Boolean")), + new ByReferenceType(this.GetTypeReference("System.Object"))}, + new string[] { + "hookName", "param1", + "param2", "param3", "param4", + "param5", "__state", "__result"}, + this.GetTypeReference("System.Void")); + + this.InsertModHook("OnCommonHook10_Prefix", new TypeReference[] { + this.GetTypeReference("System.String"), + this.GetTypeReference("System.Object"), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object"))}, + new string[] { + "hookName", "__instance", + "param1", "param2", "param3", + "param4", "param5", "param6", + "param7", "param8", "param9", + "__result"}, + this.GetTypeReference("System.Boolean")); + this.InsertModHook("OnCommonStaticHook10_Prefix", new TypeReference[] { + this.GetTypeReference("System.String"), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object"))}, + new string[] { + "hookName", "param1", + "param2", "param3", "param4", + "param5", "param6", "param7", + "param8", "param9", "param10", + "__result"}, + this.GetTypeReference("System.Boolean")); + this.InsertModHook("OnCommonHook10_Postfix", new TypeReference[] { + this.GetTypeReference("System.String"), + this.GetTypeReference("System.Object"), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Boolean")), + new ByReferenceType(this.GetTypeReference("System.Object"))}, + new string[] { + "hookName", "__instance", + "param1", "param2", "param3", + "param4", "param5", "param6", + "param7", "param8", "param9", + "__state", "__result"}, + this.GetTypeReference("System.Void")); + this.InsertModHook("OnCommonStaticHook10_Postfix", new TypeReference[] { + this.GetTypeReference("System.String"), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Object")), + new ByReferenceType(this.GetTypeReference("System.Boolean")), + new ByReferenceType(this.GetTypeReference("System.Object"))}, + new string[] { + "hookName", "param1", + "param2", "param3", "param4", + "param5", "param6", "param7", + "param8", "param9", "param10", + "__state", "__result"}, + this.GetTypeReference("System.Void")); + // On Game1 hooks + TypeDefinition typeGame1 = this.StardewValley.MainModule.GetType("StardewValley.Game1"); + this.ApplyCommonHookEntry(typeGame1, "Update", "OnCommonHook"); + this.ApplyCommonHookEntry(typeGame1, "_draw", "OnCommonHook"); + this.ApplyCommonHookEntry(typeGame1, "getSourceRectForStandardTileSheet", "OnCommonStaticHook"); + this.ApplyCommonHookEntry(typeGame1, "tryToCheckAt", "OnCommonStaticHook"); + this.ApplyCommonHookEntry(typeGame1, "getLocationRequest", "OnCommonStaticHook"); + + // On Object hooks + TypeDefinition typeObject = this.StardewValley.MainModule.GetType("StardewValley.Object"); + this.ApplyCommonHookEntry(typeObject, "canBePlacedHere", "OnCommonHook", true, false); + this.ApplyCommonHookEntry(typeObject, "checkForAction", "OnCommonHook", true, false); + this.ApplyCommonHookEntry(typeObject, "isIndexOkForBasicShippedCategory", "OnCommonStaticHook"); + + // On ReadyCheckDialog hooks + TypeDefinition typeReadyCheckDialog = this.StardewValley.MainModule.GetType("StardewValley.Menus.ReadyCheckDialog"); + this.ApplyCommonHookEntry(typeReadyCheckDialog, "update", "OnCommonHook"); + + // On Building hooks + TypeDefinition typeBuilding = this.StardewValley.MainModule.GetType("StardewValley.Buildings.Building"); + this.ApplyCommonHookEntry(typeBuilding, "load", "OnCommonHook"); + + // On GameLocation hooks + TypeDefinition typeGameLocation = this.StardewValley.MainModule.GetType("StardewValley.GameLocation"); + this.ApplyCommonHookEntry(typeGameLocation, "performTouchAction", "OnCommonHook"); + this.ApplyCommonHookEntry(typeGameLocation, "isActionableTile", "OnCommonHook"); + this.ApplyCommonHookEntry(typeGameLocation, "tryToAddCritters", "OnCommonHook"); + this.ApplyCommonHookEntry(typeGameLocation, "getSourceRectForObject", "OnCommonStaticHook"); + this.ApplyCommonHookEntry(typeGameLocation, "answerDialogue", "OnCommonHook"); + this.ApplyCommonHookEntry(typeGameLocation, "Equals", "OnCommonHook", true, false, (method)=> method.Parameters[0].ParameterType == this.GetTypeReference("StardewValley.GameLocation", this.StardewValley)); + + + // On Objects.TV hooks + TypeDefinition typeObjectsTV = this.StardewValley.MainModule.GetType("StardewValley.Objects.TV"); + this.ApplyCommonHookEntry(typeObjectsTV, "checkForAction", "OnCommonHook"); + + + //this.InsertModHook("OnGame1_Update_Prefix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Game1", this.StardewValley), + // this.GetTypeReference("Microsoft.Xna.Framework.GameTime", this.MonoGame_Framework)}, + // this.GetTypeReference("System.Boolean")); + //this.ApplyHookEntry(typeGame1, "Update", "OnGame1_Update_Prefix", true); + //this.InsertModHook("OnGame1_Update_Postfix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Game1", this.StardewValley), + // this.GetTypeReference("Microsoft.Xna.Framework.GameTime", this.MonoGame_Framework)}, + // this.GetTypeReference("System.Void")); + //this.ApplyHookEntry(typeGame1, "Update", "OnGame1_Update_Postfix", false); this.InsertModHook("OnGame1_CreateContentManager_Prefix", new TypeReference[] { this.GetTypeReference("StardewValley.Game1", this.StardewValley), this.GetTypeReference("System.IServiceProvider"), this.GetTypeReference("System.String"), new ByReferenceType(this.GetTypeReference("StardewValley.LocalizedContentManager", this.StardewValley)) }, + new string[] { "game1", "serviceProvider", "rootDirectory", "__result"}, this.GetTypeReference("System.Boolean")); this.ApplyHookEntry(typeGame1, "CreateContentManager", "OnGame1_CreateContentManager_Prefix", true); - this.InsertModHook("OnGame1_Draw_Prefix", new TypeReference[] { - this.GetTypeReference("StardewValley.Game1", this.StardewValley), - this.GetTypeReference("Microsoft.Xna.Framework.GameTime", this.MonoGame_Framework)}, - this.GetTypeReference("System.Boolean")); - this.ApplyHookEntry(typeGame1, "Draw", "OnGame1_Draw_Prefix", true); + //this.InsertModHook("OnGame1__draw_Prefix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Game1", this.StardewValley), + // this.GetTypeReference("Microsoft.Xna.Framework.GameTime", this.MonoGame_Framework), + // this.GetTypeReference("Microsoft.Xna.Framework.Graphics.RenderTarget2D", this.MonoGame_Framework)}, + // this.GetTypeReference("System.Boolean")); + //this.ApplyHookEntry(typeGame1, "_draw", "OnGame1__draw_Prefix", true); + //this.InsertModHook("OnGame1_getSourceRectForStandardTileSheet_Prefix", new TypeReference[] { + // this.GetTypeReference("Microsoft.Xna.Framework.Graphics.Texture2D", this.MonoGame_Framework), + // this.GetTypeReference("System.Int32"), + // this.GetTypeReference("System.Int32"), + // this.GetTypeReference("System.Int32"), + // new ByReferenceType(this.GetTypeReference("Microsoft.Xna.Framework.Rectangle", this.MonoGame_Framework)) }, + // this.GetTypeReference("System.Boolean")); + //this.ApplyHookEntry(typeGame1, "getSourceRectForStandardTileSheet", "OnGame1_getSourceRectForStandardTileSheet_Prefix", true); - this.InsertModHook("OnObject_canBePlacedHere_Prefix", new TypeReference[] { - this.GetTypeReference("StardewValley.Object", this.StardewValley), - this.GetTypeReference("StardewValley.GameLocation", this.StardewValley), - this.GetTypeReference("Microsoft.Xna.Framework.Vector2", this.MonoGame_Framework), - new ByReferenceType(this.GetTypeReference("System.Boolean")) }, - this.GetTypeReference("System.Boolean")); - TypeDefinition typeObject = this.StardewValley.MainModule.GetType("StardewValley.Object"); - this.ApplyHookEntry(typeObject, "canBePlacedHere", "OnObject_canBePlacedHere_Prefix", true); + //this.InsertModHook("OnGame1_getSourceRectForStandardTileSheet_Postfix", new TypeReference[] { + // this.GetTypeReference("Microsoft.Xna.Framework.Graphics.Texture2D", this.MonoGame_Framework), + // this.GetTypeReference("System.Int32"), + // this.GetTypeReference("System.Int32"), + // this.GetTypeReference("System.Int32"), + // new ByReferenceType(this.GetTypeReference("Microsoft.Xna.Framework.Rectangle", this.MonoGame_Framework)) }, + // this.GetTypeReference("System.Void")); + //this.ApplyHookEntry(typeGame1, "getSourceRectForStandardTileSheet", "OnGame1_getSourceRectForStandardTileSheet_Postfix", false); - this.InsertModHook("OnObject_checkForAction_Prefix", new TypeReference[] { - this.GetTypeReference("StardewValley.Object", this.StardewValley), - this.GetTypeReference("StardewValley.Farmer", this.StardewValley), - this.GetTypeReference("System.Boolean"), - new ByReferenceType(this.GetTypeReference("System.Boolean"))}, - this.GetTypeReference("System.Boolean")); - this.ApplyHookEntry(typeObject, "checkForAction", "OnObject_checkForAction_Prefix", true); + //this.InsertModHook("OnGame1_tryToCheckAt_Postfix", new TypeReference[] { + // this.GetTypeReference("Microsoft.Xna.Framework.Vector2", this.MonoGame_Framework), + // this.GetTypeReference("StardewValley.Farmer", this.StardewValley), + // new ByReferenceType(this.GetTypeReference("System.Boolean")) }, + // this.GetTypeReference("System.Void")); + //this.ApplyHookEntry(typeGame1, "tryToCheckAt", "OnGame1_tryToCheckAt_Postfix", false); + + //this.InsertModHook("OnGame1_getLocationRequest_Prefix", new TypeReference[] { + // this.GetTypeReference("System.String"), + // this.GetTypeReference("System.Boolean"), + // new ByReferenceType(this.GetTypeReference("StardewValley.LocationRequest", this.StardewValley)) }, + // this.GetTypeReference("System.Boolean")); + //this.ApplyHookEntry(typeGame1, "getLocationRequest", "OnGame1_getLocationRequest_Prefix", true); + + //// On Object hooks + //this.InsertModHook("OnObject_canBePlacedHere_Prefix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Object", this.StardewValley), + // this.GetTypeReference("StardewValley.GameLocation", this.StardewValley), + // this.GetTypeReference("Microsoft.Xna.Framework.Vector2", this.MonoGame_Framework), + // new ByReferenceType(this.GetTypeReference("System.Boolean")) }, + // this.GetTypeReference("System.Boolean")); + //TypeDefinition typeObject = this.StardewValley.MainModule.GetType("StardewValley.Object"); + //this.ApplyHookEntry(typeObject, "canBePlacedHere", "OnObject_canBePlacedHere_Prefix", true); + + //this.InsertModHook("OnObject_checkForAction_Prefix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Object", this.StardewValley), + // this.GetTypeReference("StardewValley.Farmer", this.StardewValley), + // this.GetTypeReference("System.Boolean"), + // new ByReferenceType(this.GetTypeReference("System.Boolean"))}, + // this.GetTypeReference("System.Boolean")); + //this.ApplyHookEntry(typeObject, "checkForAction", "OnObject_checkForAction_Prefix", true); + + //this.InsertModHook("OnObject_isIndexOkForBasicShippedCategory_Postfix", new TypeReference[] { + // this.GetTypeReference("System.Int32"), + // new ByReferenceType(this.GetTypeReference("System.Boolean")) }, + // this.GetTypeReference("System.Void")); + //this.ApplyHookEntry(typeObject, "isIndexOkForBasicShippedCategory", "OnObject_isIndexOkForBasicShippedCategory_Postfix", false); + + //// On ReadyCheckDialog hooks + //this.InsertModHook("OnReadyCheckDialog_update_Postfix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Menus.ReadyCheckDialog", this.StardewValley), + // this.GetTypeReference("Microsoft.Xna.Framework.GameTime", this.MonoGame_Framework)}, + // this.GetTypeReference("System.Void")); + //TypeDefinition typeReadyCheckDialog = this.StardewValley.MainModule.GetType("StardewValley.Menus.ReadyCheckDialog"); + //this.ApplyHookEntry(typeReadyCheckDialog, "update", "OnReadyCheckDialog_update_Postfix", false); + + //// On Building hooks + //this.InsertModHook("OnBuilding_load_Prefix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Buildings.Building", this.StardewValley)}, + // this.GetTypeReference("System.Boolean")); + //TypeDefinition typeBuilding = this.StardewValley.MainModule.GetType("StardewValley.Buildings.Building"); + //this.ApplyHookEntry(typeBuilding, "load", "OnBuilding_load_Prefix", true); + + //// On GameLocation hooks + //this.InsertModHook("OnGameLocation_performTouchAction_Postfix", new TypeReference[] { + // this.GetTypeReference("StardewValley.GameLocation", this.StardewValley), + // this.GetTypeReference("System.String"), + // this.GetTypeReference("Microsoft.Xna.Framework.Vector2", this.MonoGame_Framework)}, + // this.GetTypeReference("System.Void")); + //TypeDefinition typeGameLocation = this.StardewValley.MainModule.GetType("StardewValley.GameLocation"); + //this.ApplyHookEntry(typeGameLocation, "performTouchAction", "OnGameLocation_performTouchAction_Postfix", false); + + //this.InsertModHook("OnGameLocation_isActionableTile_Postfix", new TypeReference[] { + // this.GetTypeReference("StardewValley.GameLocation", this.StardewValley), + // this.GetTypeReference("System.Int32"), + // this.GetTypeReference("System.Int32"), + // this.GetTypeReference("StardewValley.Farmer", this.StardewValley), + // new ByReferenceType(this.GetTypeReference("System.Boolean"))}, + // this.GetTypeReference("System.Void")); + //this.ApplyHookEntry(typeGameLocation, "isActionableTile", "OnGameLocation_isActionableTile_Postfix", false); + + //this.InsertModHook("OnGameLocation_tryToAddCritters_Prefix", new TypeReference[] { + // this.GetTypeReference("StardewValley.GameLocation", this.StardewValley), + // this.GetTypeReference("System.Boolean")}, + // this.GetTypeReference("System.Boolean")); + //this.ApplyHookEntry(typeGameLocation, "tryToAddCritters", "OnGameLocation_tryToAddCritters_Prefix", true); + + //this.InsertModHook("OnGameLocation_getSourceRectForObject_Prefix", new TypeReference[] { + // this.GetTypeReference("System.Int32"), + // new ByReferenceType(this.GetTypeReference("Microsoft.Xna.Framework.Rectangle", this.MonoGame_Framework)) }, + // this.GetTypeReference("System.Boolean")); + //this.ApplyHookEntry(typeGameLocation, "getSourceRectForObject", "OnGameLocation_getSourceRectForObject_Prefix", true); + + //this.InsertModHook("OnGameLocation_getSourceRectForObject_Postfix", new TypeReference[] { + // this.GetTypeReference("System.Int32"), + // new ByReferenceType(this.GetTypeReference("Microsoft.Xna.Framework.Rectangle", this.MonoGame_Framework)) }, + // this.GetTypeReference("System.Void")); + //this.ApplyHookEntry(typeGameLocation, "getSourceRectForObject", "OnGameLocation_getSourceRectForObject_Postfix", false); + + //this.InsertModHook("OnGameLocation_answerDialogue_Prefix", new TypeReference[] { + // this.GetTypeReference("StardewValley.GameLocation", this.StardewValley), + // this.GetTypeReference("StardewValley.Response", this.StardewValley), + // new ByReferenceType(this.GetTypeReference("System.Boolean"))}, + // this.GetTypeReference("System.Boolean")); + //this.ApplyHookEntry(typeGameLocation, "answerDialogue", "OnGameLocation_answerDialogue_Prefix", true); + + //// On Objects.TV hooks + //this.InsertModHook("OnObjectsTV_checkForAction_Prefix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Objects.TV", this.StardewValley), + // this.GetTypeReference("StardewValley.Farmer", this.StardewValley), + // this.GetTypeReference("System.Boolean"), + // new ByReferenceType(this.GetTypeReference("System.Boolean"))}, + // this.GetTypeReference("System.Boolean")); + //TypeDefinition typeObjectsTV = this.StardewValley.MainModule.GetType("StardewValley.Objects.TV"); + //this.ApplyHookEntry(typeObjectsTV, "checkForAction", "OnObjectsTV_checkForAction_Prefix", true); + + //this.InsertModHook("OnObjectsTV_checkForAction_Postfix", new TypeReference[] { + // this.GetTypeReference("StardewValley.Objects.TV", this.StardewValley), + // this.GetTypeReference("StardewValley.Farmer", this.StardewValley), + // this.GetTypeReference("System.Boolean"), + // new ByReferenceType(this.GetTypeReference("System.Boolean"))}, + // this.GetTypeReference("System.Void")); + //this.ApplyHookEntry(typeObjectsTV, "checkForAction", "OnObjectsTV_checkForAction_Postfix", false); - this.InsertModHook("OnObject_isIndexOkForBasicShippedCategory_Postfix", new TypeReference[] { - this.GetTypeReference("System.Int32"), - new ByReferenceType(this.GetTypeReference("System.Boolean")) }, - this.GetTypeReference("System.Void")); - this.ApplyHookEntry(typeObject, "isIndexOkForBasicShippedCategory", "OnObject_isIndexOkForBasicShippedCategory_Postfix", false); return this.StardewValley; } diff --git a/Mods/VirtualKeyboad/KeyButton.cs b/Mods/VirtualKeyboad/KeyButton.cs new file mode 100644 index 00000000..d509da00 --- /dev/null +++ b/Mods/VirtualKeyboad/KeyButton.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using StardewModdingAPI; +using StardewModdingAPI.Events; +using StardewValley; +using StardewValley.Menus; +using static VirtualKeyboad.ModConfig; + +namespace VirtualKeyboad +{ + class KeyButton + { + private readonly IModHelper helper; + private readonly Rectangle buttonRectangle; + private readonly int padding; + private readonly IReflectedMethod RaiseButtonPressed; + private readonly IReflectedMethod RaiseButtonReleased; + private readonly IReflectedMethod Legacy_KeyPressed; + private readonly IReflectedMethod Legacy_KeyReleased; + + private readonly SButton button; + private readonly float transparency; + private readonly bool autoHidden; + private bool raisingPressed = false; + private bool raisingReleased = false; + public KeyButton(IModHelper helper, Button buttonDefine) + { + this.helper = helper; + this.buttonRectangle = new Rectangle(buttonDefine.rectangle.X, buttonDefine.rectangle.Y, buttonDefine.rectangle.Width, buttonDefine.rectangle.Height); + this.padding = buttonDefine.rectangle.Padding; + this.button = buttonDefine.key; + this.autoHidden = buttonDefine.autoHidden; + if (buttonDefine.transparency <= 0.01f || buttonDefine.transparency > 1f) + { + buttonDefine.transparency = 0.5f; + } + this.transparency = buttonDefine.transparency; + + helper.Events.Display.RenderingHud += this.OnRenderingHud; + helper.Events.Input.ButtonReleased += this.Input_ButtonReleased; + helper.Events.Input.ButtonPressed += this.Input_ButtonPressed; + SMDroid.ModEntry entry = helper.Reflection.GetField(typeof(Game1), "hooks").GetValue(); + object score = helper.Reflection.GetField(entry, "core").GetValue(); + object eventManager = helper.Reflection.GetField(score, "EventManager").GetValue(); + + object buttonPressed = helper.Reflection.GetField(eventManager, "ButtonPressed").GetValue(); + object buttonReleased = helper.Reflection.GetField(eventManager, "ButtonReleased").GetValue(); + this.RaiseButtonPressed = helper.Reflection.GetMethod(buttonPressed, "Raise"); + this.RaiseButtonReleased = helper.Reflection.GetMethod(buttonReleased, "Raise"); + + object legacyButtonPressed = helper.Reflection.GetField(eventManager, "Legacy_KeyPressed").GetValue(); + object legacyButtonReleased = helper.Reflection.GetField(eventManager, "Legacy_KeyReleased").GetValue(); + this.Legacy_KeyPressed = helper.Reflection.GetMethod(legacyButtonPressed, "Raise"); + this.Legacy_KeyReleased = helper.Reflection.GetMethod(legacyButtonReleased, "Raise"); + } + + private bool shouldTrigger(Vector2 point) + { + if (this.autoHidden && Game1.activeClickableMenu != null) + { + return false; + } + if (!this.buttonRectangle.Contains(point.X * Game1.options.zoomLevel, point.Y * Game1.options.zoomLevel)) + { + return false; + } + //if (Game1.activeClickableMenu != null && !this.buttonRectangle.Contains(point.X, point.Y)) + //{ + // return false; + //} + return true; + } + + private void Input_ButtonPressed(object sender, ButtonPressedEventArgs e) + { + if (this.raisingPressed) + { + return; + } + Vector2 point = e.Cursor.ScreenPixels; + if (this.shouldTrigger(point)){ + object inputState = this.helper.Reflection.GetField(e, "InputState").GetValue(); + object buttonPressedEventArgs = Activator.CreateInstance(typeof(ButtonPressedEventArgs), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.button, e.Cursor, inputState }, null); + EventArgsKeyPressed eventArgsKeyPressed = new EventArgsKeyPressed((Keys)this.button); + try + { + this.raisingPressed = true; + this.RaiseButtonPressed.Invoke(new object[] { buttonPressedEventArgs }); + this.Legacy_KeyPressed.Invoke(new object[] { eventArgsKeyPressed }); + } + finally + { + this.raisingPressed = false; + } + } + } + + private void Input_ButtonReleased(object sender, ButtonReleasedEventArgs e) + { + if (this.raisingReleased) + { + return; + } + Vector2 point = e.Cursor.ScreenPixels; + if (this.shouldTrigger(point)) + { + object inputState = this.helper.Reflection.GetField(e, "InputState").GetValue(); + object buttonReleasedEventArgs = Activator.CreateInstance(typeof(ButtonReleasedEventArgs), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.button, e.Cursor, inputState }, null); + EventArgsKeyPressed eventArgsKeyReleased = new EventArgsKeyPressed((Keys)this.button); + try + { + this.raisingReleased = true; + this.RaiseButtonReleased.Invoke(new object[] { buttonReleasedEventArgs }); + this.Legacy_KeyReleased.Invoke(new object[] { eventArgsKeyReleased }); + } + finally + { + this.raisingReleased = false; + } + } + } + + /// Raised before drawing the HUD (item toolbar, clock, etc) to the screen. + /// The event sender. + /// The event arguments. + private void OnRenderingHud(object sender, EventArgs e) + { + if (!Game1.eventUp && (!this.autoHidden || (this.autoHidden && Game1.activeClickableMenu == null))) + { + Game1.spriteBatch.Draw(Game1.staminaRect, this.buttonRectangle, Color.LightGray * this.transparency); + Rectangle shrinkRectangle = new Rectangle(this.buttonRectangle.X + this.padding, this.buttonRectangle.Y + this.padding, this.buttonRectangle.Width - 2 * this.padding, this.buttonRectangle.Height - 2 * this.padding); + Game1.spriteBatch.Draw(Game1.staminaRect, shrinkRectangle, Color.DarkGray * this.transparency * 0.5f); + Game1.spriteBatch.DrawString(Game1.dialogueFont, this.button.ToString(), new Vector2(this.buttonRectangle.X + 8, this.buttonRectangle.Y + 8), Color.White * this.transparency); + } + } + } +} diff --git a/Mods/VirtualKeyboad/ModConfig.cs b/Mods/VirtualKeyboad/ModConfig.cs new file mode 100644 index 00000000..7064af91 --- /dev/null +++ b/Mods/VirtualKeyboad/ModConfig.cs @@ -0,0 +1,45 @@ +using Microsoft.Xna.Framework; +using StardewModdingAPI; + +namespace VirtualKeyboad +{ + class ModConfig + { + public Button[] buttons { get; set;} = new Button[] { + new Button(SButton.Q, new Rect(192, 64, 90, 90, 6), 0.5f, true), + new Button(SButton.I, new Rect(288, 64, 90, 90, 6), 0.5f, true), + new Button(SButton.O, new Rect(384, 64, 90, 90, 6), 0.5f, true), + new Button(SButton.P, new Rect(480, 64, 90, 90, 6), 0.5f, true) + }; + internal class Button { + public SButton key; + public Rect rectangle; + public bool autoHidden; + public float transparency; + public Button(SButton key, Rect rectangle, float transparency, bool autoHidden) + { + this.key = key; + this.rectangle = rectangle; + this.transparency = transparency; + this.autoHidden = autoHidden; + } + } + internal class Rect + { + public int X; + public int Y; + public int Width; + public int Height; + public int Padding; + + public Rect(int x, int y, int width, int height, int padding) + { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + this.Padding = padding; + } + } + } +} diff --git a/Mods/VirtualKeyboad/ModEntry.cs b/Mods/VirtualKeyboad/ModEntry.cs new file mode 100644 index 00000000..14991f4f --- /dev/null +++ b/Mods/VirtualKeyboad/ModEntry.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StardewModdingAPI; + +namespace VirtualKeyboad +{ + class ModEntry : Mod + { + private List keyboard = new List(); + private ModConfig modConfig; + public override void Entry(IModHelper helper) + { + this.modConfig = helper.ReadConfig(); + for (int i = 0; i < this.modConfig.buttons.Length; i++) + { + this.keyboard.Add(new KeyButton(helper, this.modConfig.buttons[i])); + } + helper.WriteConfig(this.modConfig); + } + } +} diff --git a/Mods/VirtualKeyboad/Properties/AssemblyInfo.cs b/Mods/VirtualKeyboad/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..1a2de91c --- /dev/null +++ b/Mods/VirtualKeyboad/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("VirtualKeyboad")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("VirtualKeyboad")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("7a7bdd05-433a-4297-afec-131e35123026")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Mods/VirtualKeyboad/VirtualKeyboad.csproj b/Mods/VirtualKeyboad/VirtualKeyboad.csproj new file mode 100644 index 00000000..0c28e56e --- /dev/null +++ b/Mods/VirtualKeyboad/VirtualKeyboad.csproj @@ -0,0 +1,163 @@ + + + + + Debug + AnyCPU + {7A7BDD05-433A-4297-AFEC-131E35123026} + Library + Properties + VirtualKeyboad + VirtualKeyboad + v4.5.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\assemblies\StardewModdingAPI.dll + + + ..\assemblies\StardewValley.dll + + + False + ..\assemblies\BmFont.dll + + + False + ..\assemblies\Google.Android.Vending.Expansion.Downloader.dll + + + False + ..\assemblies\Google.Android.Vending.Expansion.ZipFile.dll + + + False + ..\assemblies\Google.Android.Vending.Licensing.dll + + + False + ..\assemblies\Java.Interop.dll + + + False + ..\assemblies\Microsoft.AppCenter.dll + + + False + ..\assemblies\Microsoft.AppCenter.Analytics.dll + + + False + ..\assemblies\Microsoft.AppCenter.Analytics.Android.Bindings.dll + + + False + ..\assemblies\Microsoft.AppCenter.Android.Bindings.dll + + + False + ..\assemblies\Microsoft.AppCenter.Crashes.dll + + + False + ..\assemblies\Microsoft.AppCenter.Crashes.Android.Bindings.dll + + + False + ..\assemblies\Mono.Android.dll + + + False + ..\assemblies\Mono.Security.dll + + + False + ..\assemblies\MonoGame.Framework.dll + + + ..\assemblies\mscorlib.dll + + + ..\assemblies\System.dll + + + ..\assemblies\System.Xml.dll + + + ..\assemblies\System.Net.Http.dll + + + ..\assemblies\System.Runtime.Serialization.dll + + + False + ..\assemblies\Xamarin.Android.Arch.Core.Common.dll + + + False + ..\assemblies\Xamarin.Android.Arch.Lifecycle.Common.dll + + + False + ..\assemblies\Xamarin.Android.Arch.Lifecycle.Runtime.dll + + + False + ..\assemblies\Xamarin.Android.Support.Annotations.dll + + + False + ..\assemblies\Xamarin.Android.Support.Compat.dll + + + False + ..\assemblies\Xamarin.Android.Support.Core.UI.dll + + + False + ..\assemblies\Xamarin.Android.Support.Core.Utils.dll + + + False + ..\assemblies\Xamarin.Android.Support.Fragment.dll + + + False + ..\assemblies\Xamarin.Android.Support.Media.Compat.dll + + + False + ..\assemblies\Xamarin.Android.Support.v4.dll + + + False + ..\assemblies\xTile.dll + + + + + + + + + + \ No newline at end of file diff --git a/Mods/VirtualKeyboad/manifest.json b/Mods/VirtualKeyboad/manifest.json new file mode 100644 index 00000000..98f5b0ec --- /dev/null +++ b/Mods/VirtualKeyboad/manifest.json @@ -0,0 +1,10 @@ +{ + "Name": "VirtualKeyboard", + "Author": "ZaneYork", + "Version": "1.0.1", + "MinimumApiVersion": "2.10.1", + "Description": "Virtual keyboard for mods", + "UniqueID": "VirtualKeyboad", + "EntryDll": "VirtualKeyboad.dll", + "UpdateKeys": [ "Nexus: null" ] +} \ No newline at end of file diff --git a/src/ModEntry.cs b/src/ModEntry.cs index 0169bf5c..c8914bc5 100644 --- a/src/ModEntry.cs +++ b/src/ModEntry.cs @@ -5,6 +5,9 @@ using StardewModdingAPI.Framework; using System.Threading; using Microsoft.Xna.Framework.Graphics; using System.IO; +using StardewValley.Menus; +using StardewValley.Buildings; +using StardewValley.Objects; namespace SMDroid { @@ -22,7 +25,7 @@ namespace SMDroid { this.core = new SCore(Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid/Mods"), false); } - public override bool OnGame1_CreateContentManager_Prefix(Game1 _, IServiceProvider serviceProvider, string rootDirectory, ref LocalizedContentManager __result) + public override bool OnGame1_CreateContentManager_Prefix(Game1 game1, IServiceProvider serviceProvider, string rootDirectory, ref LocalizedContentManager __result) { // Game1._temporaryContent initialising from SGame constructor // NOTE: this method is called before the SGame constructor runs. Don't depend on anything being initialised at this point. @@ -30,48 +33,58 @@ 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; - this.core.RunInteractively(this.ContentCore); __result = this.ContentCore.CreateGameContentManager("Game1._temporaryContent"); + this.core.RunInteractively(this.ContentCore); + return false; } // Game1.content initialising from LoadContent if (this.NextContentManagerIsMain) { this.NextContentManagerIsMain = false; __result = this.ContentCore.MainContentManager; + return false; } // any other content manager __result = this.ContentCore.CreateGameContentManager("(generated)"); return false; } - public override bool OnGame1_Update_Prefix(Game1 _, GameTime time) + public override bool OnCommonHook_Prefix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref object __result) { - return this.core.GameInstance.Update(time); + switch (hookName) + { + case "StardewValley.Game1.Update": + return this.core.GameInstance.Update(param1 as GameTime); + case "StardewValley.Game1._draw": + return this.core.GameInstance.Draw(param1 as GameTime, param2 as RenderTarget2D); + default: + return this.core.GameInstance.OnCommonHook_Prefix(hookName, __instance, ref param1, ref param2, ref param3, ref param4, ref __result); + } } - public override void OnGame1_Update_Postfix(Game1 _, GameTime time) + public override void OnCommonHook_Postfix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref bool __state, ref object __result) { - this.core.GameInstance.Update_Postfix(time); + switch (hookName) + { + case "StardewValley.Game1.Update": + this.core.GameInstance.Update_Postfix(param1 as GameTime); + return; + default: + this.core.GameInstance.OnCommonHook_Postfix(hookName, __instance, ref param1, ref param2, ref param3, ref param4, ref __state, ref __result); + return; + } } - public override bool OnGame1_Draw_Prefix(Game1 _, GameTime time) + public override bool OnCommonStaticHook_Prefix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref object __result) { - return this.core.GameInstance.Draw(time); + return this.core.GameInstance.OnCommonStaticHook_Prefix(hookName, ref param1, ref param2, ref param3, ref param4, ref param5, ref __result); + } + public override void OnCommonStaticHook_Postfix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref bool __state, ref object __result) + { + this.core.GameInstance.OnCommonStaticHook_Postfix(hookName, ref param1, ref param2, ref param3, ref param4, ref param5, ref __state, ref __result); } public override void OnGame1_NewDayAfterFade(Action action) { this.core.GameInstance.OnNewDayAfterFade(); base.OnGame1_NewDayAfterFade(action); } - public override bool OnObject_canBePlacedHere_Prefix(StardewValley.Object __instance, GameLocation location, Vector2 tile, ref bool __result) - { - return this.core.GameInstance.OnObjectCanBePlacedHere(__instance, location, tile, ref __result); - } - public override void OnObject_isIndexOkForBasicShippedCategory_Postfix(int index, ref bool __result) - { - this.core.GameInstance.OnObjectIsIndexOkForBasicShippedCategory(index, ref __result); - } - public override bool OnObject_checkForAction_Prefix(StardewValley.Object __instance, Farmer value, bool justCheckingForActivity, ref bool __result) - { - return this.core.GameInstance.OnObjectCheckForAction(__instance); - } } } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index bcb0e28d..6f98bf3d 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -166,17 +166,23 @@ namespace StardewModdingAPI case Platform.Android: removeAssemblyReferences = new[] { + "Netcode", "StardewValley", "Stardew Valley", "Microsoft.Xna.Framework", "Microsoft.Xna.Framework.Game", "Microsoft.Xna.Framework.Graphics", - "Microsoft.Xna.Framework.Xact" + "Microsoft.Xna.Framework.Xact", + "Newtonsoft.Json", + "StardewModdingAPI.Toolkit.CoreInterfaces" }; targetAssemblies = new[] { typeof(StardewValley.Game1).Assembly, - typeof(Microsoft.Xna.Framework.Vector2).Assembly + typeof(Constants).Assembly, + typeof(Microsoft.Xna.Framework.Vector2).Assembly, + typeof(System.Xml.XmlWriter).Assembly, + typeof(System.Xml.Linq.XElement).Assembly }; break; default: diff --git a/src/SMAPI/Events/Display/GraphicsEvents.cs b/src/SMAPI/Events/Display/GraphicsEvents.cs new file mode 100644 index 00000000..24a16a29 --- /dev/null +++ b/src/SMAPI/Events/Display/GraphicsEvents.cs @@ -0,0 +1,120 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised during the game's draw loop, when the game is rendering content to the window. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class GraphicsEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised after the game window is resized. + public static event EventHandler Resize + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GraphicsEvents.EventManager.Legacy_Resize.Add(value); + } + remove => GraphicsEvents.EventManager.Legacy_Resize.Remove(value); + } + + /**** + ** Main render events + ****/ + /// Raised before drawing the world to the screen. + public static event EventHandler OnPreRenderEvent + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GraphicsEvents.EventManager.Legacy_OnPreRenderEvent.Add(value); + } + remove => GraphicsEvents.EventManager.Legacy_OnPreRenderEvent.Remove(value); + } + + /// Raised after drawing the world to the screen. + public static event EventHandler OnPostRenderEvent + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GraphicsEvents.EventManager.Legacy_OnPostRenderEvent.Add(value); + } + remove => GraphicsEvents.EventManager.Legacy_OnPostRenderEvent.Remove(value); + } + + /**** + ** HUD events + ****/ + /// Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) + public static event EventHandler OnPreRenderHudEvent + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GraphicsEvents.EventManager.Legacy_OnPreRenderHudEvent.Add(value); + } + remove => GraphicsEvents.EventManager.Legacy_OnPreRenderHudEvent.Remove(value); + } + + /// Raised after drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) + public static event EventHandler OnPostRenderHudEvent + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GraphicsEvents.EventManager.Legacy_OnPostRenderHudEvent.Add(value); + } + remove => GraphicsEvents.EventManager.Legacy_OnPostRenderHudEvent.Remove(value); + } + + /**** + ** GUI events + ****/ + /// Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. + public static event EventHandler OnPreRenderGuiEvent + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GraphicsEvents.EventManager.Legacy_OnPreRenderGuiEvent.Add(value); + } + remove => GraphicsEvents.EventManager.Legacy_OnPreRenderGuiEvent.Remove(value); + } + + /// Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. + public static event EventHandler OnPostRenderGuiEvent + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GraphicsEvents.EventManager.Legacy_OnPostRenderGuiEvent.Add(value); + } + remove => GraphicsEvents.EventManager.Legacy_OnPostRenderGuiEvent.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + GraphicsEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/IDisplayEvents.cs b/src/SMAPI/Events/Display/IDisplayEvents.cs similarity index 100% rename from src/SMAPI/Events/IDisplayEvents.cs rename to src/SMAPI/Events/Display/IDisplayEvents.cs diff --git a/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs b/src/SMAPI/Events/Display/RenderedActiveMenuEventArgs.cs similarity index 100% rename from src/SMAPI/Events/RenderedActiveMenuEventArgs.cs rename to src/SMAPI/Events/Display/RenderedActiveMenuEventArgs.cs diff --git a/src/SMAPI/Events/RenderedEventArgs.cs b/src/SMAPI/Events/Display/RenderedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/RenderedEventArgs.cs rename to src/SMAPI/Events/Display/RenderedEventArgs.cs diff --git a/src/SMAPI/Events/RenderedHudEventArgs.cs b/src/SMAPI/Events/Display/RenderedHudEventArgs.cs similarity index 100% rename from src/SMAPI/Events/RenderedHudEventArgs.cs rename to src/SMAPI/Events/Display/RenderedHudEventArgs.cs diff --git a/src/SMAPI/Events/RenderedWorldEventArgs.cs b/src/SMAPI/Events/Display/RenderedWorldEventArgs.cs similarity index 100% rename from src/SMAPI/Events/RenderedWorldEventArgs.cs rename to src/SMAPI/Events/Display/RenderedWorldEventArgs.cs diff --git a/src/SMAPI/Events/RenderingActiveMenuEventArgs.cs b/src/SMAPI/Events/Display/RenderingActiveMenuEventArgs.cs similarity index 100% rename from src/SMAPI/Events/RenderingActiveMenuEventArgs.cs rename to src/SMAPI/Events/Display/RenderingActiveMenuEventArgs.cs diff --git a/src/SMAPI/Events/RenderingEventArgs.cs b/src/SMAPI/Events/Display/RenderingEventArgs.cs similarity index 100% rename from src/SMAPI/Events/RenderingEventArgs.cs rename to src/SMAPI/Events/Display/RenderingEventArgs.cs diff --git a/src/SMAPI/Events/RenderingHudEventArgs.cs b/src/SMAPI/Events/Display/RenderingHudEventArgs.cs similarity index 100% rename from src/SMAPI/Events/RenderingHudEventArgs.cs rename to src/SMAPI/Events/Display/RenderingHudEventArgs.cs diff --git a/src/SMAPI/Events/RenderingWorldEventArgs.cs b/src/SMAPI/Events/Display/RenderingWorldEventArgs.cs similarity index 100% rename from src/SMAPI/Events/RenderingWorldEventArgs.cs rename to src/SMAPI/Events/Display/RenderingWorldEventArgs.cs diff --git a/src/SMAPI/Events/WindowResizedEventArgs.cs b/src/SMAPI/Events/Display/WindowResizedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/WindowResizedEventArgs.cs rename to src/SMAPI/Events/Display/WindowResizedEventArgs.cs diff --git a/src/SMAPI/Events/DayEndingEventArgs.cs b/src/SMAPI/Events/GameLoop/DayEndingEventArgs.cs similarity index 100% rename from src/SMAPI/Events/DayEndingEventArgs.cs rename to src/SMAPI/Events/GameLoop/DayEndingEventArgs.cs diff --git a/src/SMAPI/Events/DayStartedEventArgs.cs b/src/SMAPI/Events/GameLoop/DayStartedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/DayStartedEventArgs.cs rename to src/SMAPI/Events/GameLoop/DayStartedEventArgs.cs diff --git a/src/SMAPI/Events/GameLoop/GameEvents.cs b/src/SMAPI/Events/GameLoop/GameEvents.cs new file mode 100644 index 00000000..9d945277 --- /dev/null +++ b/src/SMAPI/Events/GameLoop/GameEvents.cs @@ -0,0 +1,122 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when the game changes state. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class GameEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised when the game updates its state (≈60 times per second). + public static event EventHandler UpdateTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GameEvents.EventManager.Legacy_UpdateTick.Add(value); + } + remove => GameEvents.EventManager.Legacy_UpdateTick.Remove(value); + } + + /// Raised every other tick (≈30 times per second). + public static event EventHandler SecondUpdateTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GameEvents.EventManager.Legacy_SecondUpdateTick.Add(value); + } + remove => GameEvents.EventManager.Legacy_SecondUpdateTick.Remove(value); + } + + /// Raised every fourth tick (≈15 times per second). + public static event EventHandler FourthUpdateTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GameEvents.EventManager.Legacy_FourthUpdateTick.Add(value); + } + remove => GameEvents.EventManager.Legacy_FourthUpdateTick.Remove(value); + } + + /// Raised every eighth tick (≈8 times per second). + public static event EventHandler EighthUpdateTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GameEvents.EventManager.Legacy_EighthUpdateTick.Add(value); + } + remove => GameEvents.EventManager.Legacy_EighthUpdateTick.Remove(value); + } + + /// Raised every 15th tick (≈4 times per second). + public static event EventHandler QuarterSecondTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GameEvents.EventManager.Legacy_QuarterSecondTick.Add(value); + } + remove => GameEvents.EventManager.Legacy_QuarterSecondTick.Remove(value); + } + + /// Raised every 30th tick (≈twice per second). + public static event EventHandler HalfSecondTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GameEvents.EventManager.Legacy_HalfSecondTick.Add(value); + } + remove => GameEvents.EventManager.Legacy_HalfSecondTick.Remove(value); + } + + /// Raised every 60th tick (≈once per second). + public static event EventHandler OneSecondTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GameEvents.EventManager.Legacy_OneSecondTick.Add(value); + } + remove => GameEvents.EventManager.Legacy_OneSecondTick.Remove(value); + } + + /// Raised once after the game initialises and all methods have been called. + public static event EventHandler FirstUpdateTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + GameEvents.EventManager.Legacy_FirstUpdateTick.Add(value); + } + remove => GameEvents.EventManager.Legacy_FirstUpdateTick.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + GameEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/GameLaunchedEventArgs.cs b/src/SMAPI/Events/GameLoop/GameLaunchedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/GameLaunchedEventArgs.cs rename to src/SMAPI/Events/GameLoop/GameLaunchedEventArgs.cs diff --git a/src/SMAPI/Events/IGameLoopEvents.cs b/src/SMAPI/Events/GameLoop/IGameLoopEvents.cs similarity index 100% rename from src/SMAPI/Events/IGameLoopEvents.cs rename to src/SMAPI/Events/GameLoop/IGameLoopEvents.cs diff --git a/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs b/src/SMAPI/Events/GameLoop/OneSecondUpdateTickedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs rename to src/SMAPI/Events/GameLoop/OneSecondUpdateTickedEventArgs.cs diff --git a/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs b/src/SMAPI/Events/GameLoop/OneSecondUpdateTickingEventArgs.cs similarity index 100% rename from src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs rename to src/SMAPI/Events/GameLoop/OneSecondUpdateTickingEventArgs.cs diff --git a/src/SMAPI/Events/ReturnedToTitleEventArgs.cs b/src/SMAPI/Events/GameLoop/ReturnedToTitleEventArgs.cs similarity index 100% rename from src/SMAPI/Events/ReturnedToTitleEventArgs.cs rename to src/SMAPI/Events/GameLoop/ReturnedToTitleEventArgs.cs diff --git a/src/SMAPI/Events/SaveCreatedEventArgs.cs b/src/SMAPI/Events/GameLoop/SaveCreatedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/SaveCreatedEventArgs.cs rename to src/SMAPI/Events/GameLoop/SaveCreatedEventArgs.cs diff --git a/src/SMAPI/Events/SaveCreatingEventArgs.cs b/src/SMAPI/Events/GameLoop/SaveCreatingEventArgs.cs similarity index 100% rename from src/SMAPI/Events/SaveCreatingEventArgs.cs rename to src/SMAPI/Events/GameLoop/SaveCreatingEventArgs.cs diff --git a/src/SMAPI/Events/GameLoop/SaveEvents.cs b/src/SMAPI/Events/GameLoop/SaveEvents.cs new file mode 100644 index 00000000..da276d22 --- /dev/null +++ b/src/SMAPI/Events/GameLoop/SaveEvents.cs @@ -0,0 +1,100 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised before and after the player saves/loads the game. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class SaveEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised before the game creates the save file. + public static event EventHandler BeforeCreate + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + SaveEvents.EventManager.Legacy_BeforeCreateSave.Add(value); + } + remove => SaveEvents.EventManager.Legacy_BeforeCreateSave.Remove(value); + } + + /// Raised after the game finishes creating the save file. + public static event EventHandler AfterCreate + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + SaveEvents.EventManager.Legacy_AfterCreateSave.Add(value); + } + remove => SaveEvents.EventManager.Legacy_AfterCreateSave.Remove(value); + } + + /// Raised before the game begins writes data to the save file. + public static event EventHandler BeforeSave + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + SaveEvents.EventManager.Legacy_BeforeSave.Add(value); + } + remove => SaveEvents.EventManager.Legacy_BeforeSave.Remove(value); + } + + /// Raised after the game finishes writing data to the save file. + public static event EventHandler AfterSave + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + SaveEvents.EventManager.Legacy_AfterSave.Add(value); + } + remove => SaveEvents.EventManager.Legacy_AfterSave.Remove(value); + } + + /// Raised after the player loads a save slot. + public static event EventHandler AfterLoad + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + SaveEvents.EventManager.Legacy_AfterLoad.Add(value); + } + remove => SaveEvents.EventManager.Legacy_AfterLoad.Remove(value); + } + + /// Raised after the game returns to the title screen. + public static event EventHandler AfterReturnToTitle + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + SaveEvents.EventManager.Legacy_AfterReturnToTitle.Add(value); + } + remove => SaveEvents.EventManager.Legacy_AfterReturnToTitle.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + SaveEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/SaveLoadedEventArgs.cs b/src/SMAPI/Events/GameLoop/SaveLoadedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/SaveLoadedEventArgs.cs rename to src/SMAPI/Events/GameLoop/SaveLoadedEventArgs.cs diff --git a/src/SMAPI/Events/SavedEventArgs.cs b/src/SMAPI/Events/GameLoop/SavedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/SavedEventArgs.cs rename to src/SMAPI/Events/GameLoop/SavedEventArgs.cs diff --git a/src/SMAPI/Events/SavingEventArgs.cs b/src/SMAPI/Events/GameLoop/SavingEventArgs.cs similarity index 100% rename from src/SMAPI/Events/SavingEventArgs.cs rename to src/SMAPI/Events/GameLoop/SavingEventArgs.cs diff --git a/src/SMAPI/Events/TimeChangedEventArgs.cs b/src/SMAPI/Events/GameLoop/TimeChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/TimeChangedEventArgs.cs rename to src/SMAPI/Events/GameLoop/TimeChangedEventArgs.cs diff --git a/src/SMAPI/Events/GameLoop/TimeEvents.cs b/src/SMAPI/Events/GameLoop/TimeEvents.cs new file mode 100644 index 00000000..389532d9 --- /dev/null +++ b/src/SMAPI/Events/GameLoop/TimeEvents.cs @@ -0,0 +1,56 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when the in-game date or time changes. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class TimeEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised after the game begins a new day, including when loading a save. + public static event EventHandler AfterDayStarted + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + TimeEvents.EventManager.Legacy_AfterDayStarted.Add(value); + } + remove => TimeEvents.EventManager.Legacy_AfterDayStarted.Remove(value); + } + + /// Raised after the in-game clock changes. + public static event EventHandler TimeOfDayChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + TimeEvents.EventManager.Legacy_TimeOfDayChanged.Add(value); + } + remove => TimeEvents.EventManager.Legacy_TimeOfDayChanged.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + TimeEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/UpdateTickedEventArgs.cs b/src/SMAPI/Events/GameLoop/UpdateTickedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/UpdateTickedEventArgs.cs rename to src/SMAPI/Events/GameLoop/UpdateTickedEventArgs.cs diff --git a/src/SMAPI/Events/UpdateTickingEventArgs.cs b/src/SMAPI/Events/GameLoop/UpdateTickingEventArgs.cs similarity index 100% rename from src/SMAPI/Events/UpdateTickingEventArgs.cs rename to src/SMAPI/Events/GameLoop/UpdateTickingEventArgs.cs diff --git a/src/SMAPI/Events/IHookEvents.cs b/src/SMAPI/Events/IHookEvents.cs deleted file mode 100644 index 62a84716..00000000 --- a/src/SMAPI/Events/IHookEvents.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using StardewValley; - -namespace StardewModdingAPI.Events -{ - /// Events related to UI and drawing to the screen. - public interface IHookEvents - { - /// Object.canBePlacedHere hook. - event Func ObjectCanBePlacedHere; - - /// Object.checkForAction hook. - event Func ObjectCheckForAction; - - /// Object.isIndexOkForBasicShippedCategory hook. - event Func ObjectIsIndexOkForBasicShippedCategory; - } -} diff --git a/src/SMAPI/Events/IModEvents.cs b/src/SMAPI/Events/IModEvents.cs index 8d57f62c..bd7ab880 100644 --- a/src/SMAPI/Events/IModEvents.cs +++ b/src/SMAPI/Events/IModEvents.cs @@ -23,6 +23,5 @@ namespace StardewModdingAPI.Events /// Events serving specialised edge cases that shouldn't be used by most mods. ISpecialisedEvents Specialised { get; } - IHookEvents Hook { get; } } } diff --git a/src/SMAPI/Events/ButtonPressedEventArgs.cs b/src/SMAPI/Events/Input/ButtonPressedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/ButtonPressedEventArgs.cs rename to src/SMAPI/Events/Input/ButtonPressedEventArgs.cs diff --git a/src/SMAPI/Events/ButtonReleasedEventArgs.cs b/src/SMAPI/Events/Input/ButtonReleasedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/ButtonReleasedEventArgs.cs rename to src/SMAPI/Events/Input/ButtonReleasedEventArgs.cs diff --git a/src/SMAPI/Events/Input/ControlEvents.cs b/src/SMAPI/Events/Input/ControlEvents.cs new file mode 100644 index 00000000..45aedc9b --- /dev/null +++ b/src/SMAPI/Events/Input/ControlEvents.cs @@ -0,0 +1,123 @@ +#if !SMAPI_3_0_STRICT +using System; +using Microsoft.Xna.Framework.Input; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when the player uses a controller, keyboard, or mouse. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class ControlEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised when the changes. That happens when the player presses or releases a key. + public static event EventHandler KeyboardChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ControlEvents.EventManager.Legacy_KeyboardChanged.Add(value); + } + remove => ControlEvents.EventManager.Legacy_KeyboardChanged.Remove(value); + } + + /// Raised after the player presses a keyboard key. + public static event EventHandler KeyPressed + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ControlEvents.EventManager.Legacy_KeyPressed.Add(value); + } + remove => ControlEvents.EventManager.Legacy_KeyPressed.Remove(value); + } + + /// Raised after the player releases a keyboard key. + public static event EventHandler KeyReleased + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ControlEvents.EventManager.Legacy_KeyReleased.Add(value); + } + remove => ControlEvents.EventManager.Legacy_KeyReleased.Remove(value); + } + + /// Raised when the changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button. + public static event EventHandler MouseChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ControlEvents.EventManager.Legacy_MouseChanged.Add(value); + } + remove => ControlEvents.EventManager.Legacy_MouseChanged.Remove(value); + } + + /// The player pressed a controller button. This event isn't raised for trigger buttons. + public static event EventHandler ControllerButtonPressed + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ControlEvents.EventManager.Legacy_ControllerButtonPressed.Add(value); + } + remove => ControlEvents.EventManager.Legacy_ControllerButtonPressed.Remove(value); + } + + /// The player released a controller button. This event isn't raised for trigger buttons. + public static event EventHandler ControllerButtonReleased + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ControlEvents.EventManager.Legacy_ControllerButtonReleased.Add(value); + } + remove => ControlEvents.EventManager.Legacy_ControllerButtonReleased.Remove(value); + } + + /// The player pressed a controller trigger button. + public static event EventHandler ControllerTriggerPressed + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Add(value); + } + remove => ControlEvents.EventManager.Legacy_ControllerTriggerPressed.Remove(value); + } + + /// The player released a controller trigger button. + public static event EventHandler ControllerTriggerReleased + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Add(value); + } + remove => ControlEvents.EventManager.Legacy_ControllerTriggerReleased.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + ControlEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/CursorMovedEventArgs.cs b/src/SMAPI/Events/Input/CursorMovedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/CursorMovedEventArgs.cs rename to src/SMAPI/Events/Input/CursorMovedEventArgs.cs diff --git a/src/SMAPI/Events/Input/EventArgsControllerButtonPressed.cs b/src/SMAPI/Events/Input/EventArgsControllerButtonPressed.cs new file mode 100644 index 00000000..949446e1 --- /dev/null +++ b/src/SMAPI/Events/Input/EventArgsControllerButtonPressed.cs @@ -0,0 +1,34 @@ +#if !SMAPI_3_0_STRICT +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsControllerButtonPressed : EventArgs + { + /********* + ** Accessors + *********/ + /// The player who pressed the button. + public PlayerIndex PlayerIndex { get; } + + /// The controller button that was pressed. + public Buttons ButtonPressed { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The player who pressed the button. + /// The controller button that was pressed. + public EventArgsControllerButtonPressed(PlayerIndex playerIndex, Buttons button) + { + this.PlayerIndex = playerIndex; + this.ButtonPressed = button; + } + } +} +#endif diff --git a/src/SMAPI/Events/Input/EventArgsControllerButtonReleased.cs b/src/SMAPI/Events/Input/EventArgsControllerButtonReleased.cs new file mode 100644 index 00000000..d6d6d840 --- /dev/null +++ b/src/SMAPI/Events/Input/EventArgsControllerButtonReleased.cs @@ -0,0 +1,34 @@ +#if !SMAPI_3_0_STRICT +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsControllerButtonReleased : EventArgs + { + /********* + ** Accessors + *********/ + /// The player who pressed the button. + public PlayerIndex PlayerIndex { get; } + + /// The controller button that was pressed. + public Buttons ButtonReleased { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The player who pressed the button. + /// The controller button that was released. + public EventArgsControllerButtonReleased(PlayerIndex playerIndex, Buttons button) + { + this.PlayerIndex = playerIndex; + this.ButtonReleased = button; + } + } +} +#endif diff --git a/src/SMAPI/Events/Input/EventArgsControllerTriggerPressed.cs b/src/SMAPI/Events/Input/EventArgsControllerTriggerPressed.cs new file mode 100644 index 00000000..33be2fa3 --- /dev/null +++ b/src/SMAPI/Events/Input/EventArgsControllerTriggerPressed.cs @@ -0,0 +1,39 @@ +#if !SMAPI_3_0_STRICT +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsControllerTriggerPressed : EventArgs + { + /********* + ** Accessors + *********/ + /// The player who pressed the button. + public PlayerIndex PlayerIndex { get; } + + /// The controller button that was pressed. + public Buttons ButtonPressed { get; } + + /// The current trigger value. + public float Value { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The player who pressed the trigger button. + /// The trigger button that was pressed. + /// The current trigger value. + public EventArgsControllerTriggerPressed(PlayerIndex playerIndex, Buttons button, float value) + { + this.PlayerIndex = playerIndex; + this.ButtonPressed = button; + this.Value = value; + } + } +} +#endif diff --git a/src/SMAPI/Events/Input/EventArgsControllerTriggerReleased.cs b/src/SMAPI/Events/Input/EventArgsControllerTriggerReleased.cs new file mode 100644 index 00000000..e90ff712 --- /dev/null +++ b/src/SMAPI/Events/Input/EventArgsControllerTriggerReleased.cs @@ -0,0 +1,39 @@ +#if !SMAPI_3_0_STRICT +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsControllerTriggerReleased : EventArgs + { + /********* + ** Accessors + *********/ + /// The player who pressed the button. + public PlayerIndex PlayerIndex { get; } + + /// The controller button that was released. + public Buttons ButtonReleased { get; } + + /// The current trigger value. + public float Value { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The player who pressed the trigger button. + /// The trigger button that was released. + /// The current trigger value. + public EventArgsControllerTriggerReleased(PlayerIndex playerIndex, Buttons button, float value) + { + this.PlayerIndex = playerIndex; + this.ButtonReleased = button; + this.Value = value; + } + } +} +#endif diff --git a/src/SMAPI/Events/Input/EventArgsInput.cs b/src/SMAPI/Events/Input/EventArgsInput.cs new file mode 100644 index 00000000..5cff3408 --- /dev/null +++ b/src/SMAPI/Events/Input/EventArgsInput.cs @@ -0,0 +1,64 @@ +#if !SMAPI_3_0_STRICT +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI.Events +{ + /// Event arguments when a button is pressed or released. + public class EventArgsInput : EventArgs + { + /********* + ** Fields + *********/ + /// The buttons to suppress. + private readonly HashSet SuppressButtons; + + + /********* + ** Accessors + *********/ + /// The button on the controller, keyboard, or mouse. + public SButton Button { get; } + + /// The current cursor position. + public ICursorPosition Cursor { get; } + + /// Whether the input should trigger actions on the affected tile. + public bool IsActionButton => this.Button.IsActionButton(); + + /// Whether the input should use tools on the affected tile. + public bool IsUseToolButton => this.Button.IsUseToolButton(); + + /// Whether a mod has indicated the key was already handled. + public bool IsSuppressed => this.SuppressButtons.Contains(this.Button); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The button on the controller, keyboard, or mouse. + /// The cursor position. + /// The buttons to suppress. + public EventArgsInput(SButton button, ICursorPosition cursor, HashSet suppressButtons) + { + this.Button = button; + this.Cursor = cursor; + this.SuppressButtons = suppressButtons; + } + + /// Prevent the game from handling the current button press. This doesn't prevent other mods from receiving the event. + public void SuppressButton() + { + this.SuppressButton(this.Button); + } + + /// Prevent the game from handling a button press. This doesn't prevent other mods from receiving the event. + /// The button to suppress. + public void SuppressButton(SButton button) + { + this.SuppressButtons.Add(button); + } + } +} +#endif diff --git a/src/SMAPI/Events/Input/EventArgsKeyPressed.cs b/src/SMAPI/Events/Input/EventArgsKeyPressed.cs new file mode 100644 index 00000000..6204d821 --- /dev/null +++ b/src/SMAPI/Events/Input/EventArgsKeyPressed.cs @@ -0,0 +1,28 @@ +#if !SMAPI_3_0_STRICT +using System; +using Microsoft.Xna.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsKeyPressed : EventArgs + { + /********* + ** Accessors + *********/ + /// The keyboard button that was pressed. + public Keys KeyPressed { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The keyboard button that was pressed. + public EventArgsKeyPressed(Keys key) + { + this.KeyPressed = key; + } + } +} +#endif diff --git a/src/SMAPI/Events/Input/EventArgsKeyboardStateChanged.cs b/src/SMAPI/Events/Input/EventArgsKeyboardStateChanged.cs new file mode 100644 index 00000000..2c3203b1 --- /dev/null +++ b/src/SMAPI/Events/Input/EventArgsKeyboardStateChanged.cs @@ -0,0 +1,33 @@ +#if !SMAPI_3_0_STRICT +using System; +using Microsoft.Xna.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsKeyboardStateChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The previous keyboard state. + public KeyboardState NewState { get; } + + /// The current keyboard state. + public KeyboardState PriorState { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The previous keyboard state. + /// The current keyboard state. + public EventArgsKeyboardStateChanged(KeyboardState priorState, KeyboardState newState) + { + this.PriorState = priorState; + this.NewState = newState; + } + } +} +#endif diff --git a/src/SMAPI/Events/Input/EventArgsMouseStateChanged.cs b/src/SMAPI/Events/Input/EventArgsMouseStateChanged.cs new file mode 100644 index 00000000..09f3f759 --- /dev/null +++ b/src/SMAPI/Events/Input/EventArgsMouseStateChanged.cs @@ -0,0 +1,44 @@ +#if !SMAPI_3_0_STRICT +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsMouseStateChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The previous mouse state. + public MouseState PriorState { get; } + + /// The current mouse state. + public MouseState NewState { get; } + + /// The previous mouse position on the screen adjusted for the zoom level. + public Point PriorPosition { get; } + + /// The current mouse position on the screen adjusted for the zoom level. + public Point NewPosition { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The previous mouse state. + /// The current mouse state. + /// The previous mouse position on the screen adjusted for the zoom level. + /// The current mouse position on the screen adjusted for the zoom level. + public EventArgsMouseStateChanged(MouseState priorState, MouseState newState, Point priorPosition, Point newPosition) + { + this.PriorState = priorState; + this.NewState = newState; + this.PriorPosition = priorPosition; + this.NewPosition = newPosition; + } + } +} +#endif diff --git a/src/SMAPI/Events/IInputEvents.cs b/src/SMAPI/Events/Input/IInputEvents.cs similarity index 100% rename from src/SMAPI/Events/IInputEvents.cs rename to src/SMAPI/Events/Input/IInputEvents.cs diff --git a/src/SMAPI/Events/Input/InputEvents.cs b/src/SMAPI/Events/Input/InputEvents.cs new file mode 100644 index 00000000..c5ab8c83 --- /dev/null +++ b/src/SMAPI/Events/Input/InputEvents.cs @@ -0,0 +1,56 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when the player uses a controller, keyboard, or mouse button. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class InputEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised when the player presses a button on the keyboard, controller, or mouse. + public static event EventHandler ButtonPressed + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + InputEvents.EventManager.Legacy_ButtonPressed.Add(value); + } + remove => InputEvents.EventManager.Legacy_ButtonPressed.Remove(value); + } + + /// Raised when the player releases a keyboard key on the keyboard, controller, or mouse. + public static event EventHandler ButtonReleased + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + InputEvents.EventManager.Legacy_ButtonReleased.Add(value); + } + remove => InputEvents.EventManager.Legacy_ButtonReleased.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + InputEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/MouseWheelScrolledEventArgs.cs b/src/SMAPI/Events/Input/MouseWheelScrolledEventArgs.cs similarity index 100% rename from src/SMAPI/Events/MouseWheelScrolledEventArgs.cs rename to src/SMAPI/Events/Input/MouseWheelScrolledEventArgs.cs diff --git a/src/SMAPI/Events/Menu/EventArgsClickableMenuChanged.cs b/src/SMAPI/Events/Menu/EventArgsClickableMenuChanged.cs new file mode 100644 index 00000000..a0b903b7 --- /dev/null +++ b/src/SMAPI/Events/Menu/EventArgsClickableMenuChanged.cs @@ -0,0 +1,33 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewValley.Menus; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsClickableMenuChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The previous menu. + public IClickableMenu NewMenu { get; } + + /// The current menu. + public IClickableMenu PriorMenu { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The previous menu. + /// The current menu. + public EventArgsClickableMenuChanged(IClickableMenu priorMenu, IClickableMenu newMenu) + { + this.NewMenu = newMenu; + this.PriorMenu = priorMenu; + } + } +} +#endif diff --git a/src/SMAPI/Events/Menu/EventArgsClickableMenuClosed.cs b/src/SMAPI/Events/Menu/EventArgsClickableMenuClosed.cs new file mode 100644 index 00000000..77db69ea --- /dev/null +++ b/src/SMAPI/Events/Menu/EventArgsClickableMenuClosed.cs @@ -0,0 +1,28 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewValley.Menus; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsClickableMenuClosed : EventArgs + { + /********* + ** Accessors + *********/ + /// The menu that was closed. + public IClickableMenu PriorMenu { get; } + + + /********* + ** Accessors + *********/ + /// Construct an instance. + /// The menu that was closed. + public EventArgsClickableMenuClosed(IClickableMenu priorMenu) + { + this.PriorMenu = priorMenu; + } + } +} +#endif diff --git a/src/SMAPI/Events/MenuChangedEventArgs.cs b/src/SMAPI/Events/Menu/MenuChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/MenuChangedEventArgs.cs rename to src/SMAPI/Events/Menu/MenuChangedEventArgs.cs diff --git a/src/SMAPI/Events/Menu/MenuEvents.cs b/src/SMAPI/Events/Menu/MenuEvents.cs new file mode 100644 index 00000000..8647c268 --- /dev/null +++ b/src/SMAPI/Events/Menu/MenuEvents.cs @@ -0,0 +1,56 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when a game menu is opened or closed (including internal menus like the title screen). + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class MenuEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised after a game menu is opened or replaced with another menu. This event is not invoked when a menu is closed. + public static event EventHandler MenuChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + MenuEvents.EventManager.Legacy_MenuChanged.Add(value); + } + remove => MenuEvents.EventManager.Legacy_MenuChanged.Remove(value); + } + + /// Raised after a game menu is closed. + public static event EventHandler MenuClosed + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + MenuEvents.EventManager.Legacy_MenuClosed.Add(value); + } + remove => MenuEvents.EventManager.Legacy_MenuClosed.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + MenuEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/Mine/EventArgsMineLevelChanged.cs b/src/SMAPI/Events/Mine/EventArgsMineLevelChanged.cs new file mode 100644 index 00000000..c63b04e9 --- /dev/null +++ b/src/SMAPI/Events/Mine/EventArgsMineLevelChanged.cs @@ -0,0 +1,32 @@ +#if !SMAPI_3_0_STRICT +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsMineLevelChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The previous mine level. + public int PreviousMineLevel { get; } + + /// The current mine level. + public int CurrentMineLevel { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The previous mine level. + /// The current mine level. + public EventArgsMineLevelChanged(int previousMineLevel, int currentMineLevel) + { + this.PreviousMineLevel = previousMineLevel; + this.CurrentMineLevel = currentMineLevel; + } + } +} +#endif diff --git a/src/SMAPI/Events/Mine/MineEvents.cs b/src/SMAPI/Events/Mine/MineEvents.cs new file mode 100644 index 00000000..929da35b --- /dev/null +++ b/src/SMAPI/Events/Mine/MineEvents.cs @@ -0,0 +1,45 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when something happens in the mines. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class MineEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised after the player warps to a new level of the mine. + public static event EventHandler MineLevelChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + MineEvents.EventManager.Legacy_MineLevelChanged.Add(value); + } + remove => MineEvents.EventManager.Legacy_MineLevelChanged.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + MineEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/IMultiplayerEvents.cs b/src/SMAPI/Events/Multiplayer/IMultiplayerEvents.cs similarity index 100% rename from src/SMAPI/Events/IMultiplayerEvents.cs rename to src/SMAPI/Events/Multiplayer/IMultiplayerEvents.cs diff --git a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs b/src/SMAPI/Events/Multiplayer/ModMessageReceivedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/ModMessageReceivedEventArgs.cs rename to src/SMAPI/Events/Multiplayer/ModMessageReceivedEventArgs.cs diff --git a/src/SMAPI/Events/Multiplayer/MultiplayerEvents.cs b/src/SMAPI/Events/Multiplayer/MultiplayerEvents.cs new file mode 100644 index 00000000..0650a8e2 --- /dev/null +++ b/src/SMAPI/Events/Multiplayer/MultiplayerEvents.cs @@ -0,0 +1,78 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised during the multiplayer sync process. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class MultiplayerEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised before the game syncs changes from other players. + public static event EventHandler BeforeMainSync + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + MultiplayerEvents.EventManager.Legacy_BeforeMainSync.Add(value); + } + remove => MultiplayerEvents.EventManager.Legacy_BeforeMainSync.Remove(value); + } + + /// Raised after the game syncs changes from other players. + public static event EventHandler AfterMainSync + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + MultiplayerEvents.EventManager.Legacy_AfterMainSync.Add(value); + } + remove => MultiplayerEvents.EventManager.Legacy_AfterMainSync.Remove(value); + } + + /// Raised before the game broadcasts changes to other players. + public static event EventHandler BeforeMainBroadcast + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + MultiplayerEvents.EventManager.Legacy_BeforeMainBroadcast.Add(value); + } + remove => MultiplayerEvents.EventManager.Legacy_BeforeMainBroadcast.Remove(value); + } + + /// Raised after the game broadcasts changes to other players. + public static event EventHandler AfterMainBroadcast + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + MultiplayerEvents.EventManager.Legacy_AfterMainBroadcast.Add(value); + } + remove => MultiplayerEvents.EventManager.Legacy_AfterMainBroadcast.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + MultiplayerEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/PeerContextReceivedEventArgs.cs b/src/SMAPI/Events/Multiplayer/PeerContextReceivedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/PeerContextReceivedEventArgs.cs rename to src/SMAPI/Events/Multiplayer/PeerContextReceivedEventArgs.cs diff --git a/src/SMAPI/Events/PeerDisconnectedEventArgs.cs b/src/SMAPI/Events/Multiplayer/PeerDisconnectedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/PeerDisconnectedEventArgs.cs rename to src/SMAPI/Events/Multiplayer/PeerDisconnectedEventArgs.cs diff --git a/src/SMAPI/Events/ObjectCanBePlacedHereEventArgs.cs b/src/SMAPI/Events/ObjectCanBePlacedHereEventArgs.cs deleted file mode 100644 index 00364c13..00000000 --- a/src/SMAPI/Events/ObjectCanBePlacedHereEventArgs.cs +++ /dev/null @@ -1,34 +0,0 @@ -using StardewValley; -using Microsoft.Xna.Framework; - -namespace StardewModdingAPI.Events -{ - /// Event arguments for an event. - public class ObjectCanBePlacedHereEventArgs : System.EventArgs - { - /********* - ** Accessors - *********/ - public Object __instance { get; } - - public GameLocation location { get; } - - public Vector2 tile { get; } - - public bool __result; - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The previous window size. - /// The current window size. - internal ObjectCanBePlacedHereEventArgs(Object __instance, GameLocation location, Vector2 tile, bool __result) - { - this.__instance = __instance; - this.location = location; - this.tile = tile; - this.__result = __result; - } - } -} diff --git a/src/SMAPI/Events/ObjectCheckForActionEventArgs.cs b/src/SMAPI/Events/ObjectCheckForActionEventArgs.cs deleted file mode 100644 index 0b4b96ff..00000000 --- a/src/SMAPI/Events/ObjectCheckForActionEventArgs.cs +++ /dev/null @@ -1,24 +0,0 @@ -using StardewValley; -using Microsoft.Xna.Framework; - -namespace StardewModdingAPI.Events -{ - /// Event arguments for an event. - public class ObjectCheckForActionEventArgs : System.EventArgs - { - /********* - ** Accessors - *********/ - public Object __instance { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - internal ObjectCheckForActionEventArgs(Object __instance) - { - this.__instance = __instance; - } - } -} diff --git a/src/SMAPI/Events/ObjectIsIndexOkForBasicShippedCategoryEventArgs.cs b/src/SMAPI/Events/ObjectIsIndexOkForBasicShippedCategoryEventArgs.cs deleted file mode 100644 index 39bee3c4..00000000 --- a/src/SMAPI/Events/ObjectIsIndexOkForBasicShippedCategoryEventArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Microsoft.Xna.Framework; - -namespace StardewModdingAPI.Events -{ - /// Event arguments for an event. - public class ObjectIsIndexOkForBasicShippedCategoryEventArgs : EventArgs - { - /********* - ** Accessors - *********/ - /// The index - public int index { get; } - - public bool __result; - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The previous window size. - /// The current window size. - internal ObjectIsIndexOkForBasicShippedCategoryEventArgs(int index, bool __result) - { - this.index = index; - this.__result = __result; - } - } -} diff --git a/src/SMAPI/Events/Player/EventArgsInventoryChanged.cs b/src/SMAPI/Events/Player/EventArgsInventoryChanged.cs new file mode 100644 index 00000000..488dd23f --- /dev/null +++ b/src/SMAPI/Events/Player/EventArgsInventoryChanged.cs @@ -0,0 +1,43 @@ +#if !SMAPI_3_0_STRICT +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsInventoryChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The player's inventory. + public IList Inventory { get; } + + /// The added items. + public List Added { get; } + + /// The removed items. + public List Removed { get; } + + /// The items whose stack sizes changed. + public List QuantityChanged { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The player's inventory. + /// The inventory changes. + public EventArgsInventoryChanged(IList inventory, ItemStackChange[] changedItems) + { + this.Inventory = inventory; + this.Added = changedItems.Where(n => n.ChangeType == ChangeType.Added).ToList(); + this.Removed = changedItems.Where(n => n.ChangeType == ChangeType.Removed).ToList(); + this.QuantityChanged = changedItems.Where(n => n.ChangeType == ChangeType.StackChange).ToList(); + } + } +} +#endif diff --git a/src/SMAPI/Events/Player/EventArgsLevelUp.cs b/src/SMAPI/Events/Player/EventArgsLevelUp.cs new file mode 100644 index 00000000..06c70088 --- /dev/null +++ b/src/SMAPI/Events/Player/EventArgsLevelUp.cs @@ -0,0 +1,55 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Enums; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsLevelUp : EventArgs + { + /********* + ** Accessors + *********/ + /// The player skill that leveled up. + public LevelType Type { get; } + + /// The new skill level. + public int NewLevel { get; } + + /// The player skill types. + public enum LevelType + { + /// The combat skill. + Combat = SkillType.Combat, + + /// The farming skill. + Farming = SkillType.Farming, + + /// The fishing skill. + Fishing = SkillType.Fishing, + + /// The foraging skill. + Foraging = SkillType.Foraging, + + /// The mining skill. + Mining = SkillType.Mining, + + /// The luck skill. + Luck = SkillType.Luck + } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The player skill that leveled up. + /// The new skill level. + public EventArgsLevelUp(LevelType type, int newLevel) + { + this.Type = type; + this.NewLevel = newLevel; + } + } +} +#endif diff --git a/src/SMAPI/Events/Player/EventArgsPlayerWarped.cs b/src/SMAPI/Events/Player/EventArgsPlayerWarped.cs new file mode 100644 index 00000000..d1aa1588 --- /dev/null +++ b/src/SMAPI/Events/Player/EventArgsPlayerWarped.cs @@ -0,0 +1,34 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsPlayerWarped : EventArgs + { + /********* + ** Accessors + *********/ + /// The player's previous location. + public GameLocation PriorLocation { get; } + + /// The player's current location. + public GameLocation NewLocation { get; } + + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The player's previous location. + /// The player's current location. + public EventArgsPlayerWarped(GameLocation priorLocation, GameLocation newLocation) + { + this.NewLocation = newLocation; + this.PriorLocation = priorLocation; + } + } +} +#endif diff --git a/src/SMAPI/Events/IPlayerEvents.cs b/src/SMAPI/Events/Player/IPlayerEvents.cs similarity index 100% rename from src/SMAPI/Events/IPlayerEvents.cs rename to src/SMAPI/Events/Player/IPlayerEvents.cs diff --git a/src/SMAPI/Events/InventoryChangedEventArgs.cs b/src/SMAPI/Events/Player/InventoryChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/InventoryChangedEventArgs.cs rename to src/SMAPI/Events/Player/InventoryChangedEventArgs.cs diff --git a/src/SMAPI/Events/ItemStackChange.cs b/src/SMAPI/Events/Player/ItemStackChange.cs similarity index 100% rename from src/SMAPI/Events/ItemStackChange.cs rename to src/SMAPI/Events/Player/ItemStackChange.cs diff --git a/src/SMAPI/Events/ItemStackSizeChange.cs b/src/SMAPI/Events/Player/ItemStackSizeChange.cs similarity index 100% rename from src/SMAPI/Events/ItemStackSizeChange.cs rename to src/SMAPI/Events/Player/ItemStackSizeChange.cs diff --git a/src/SMAPI/Events/LevelChangedEventArgs.cs b/src/SMAPI/Events/Player/LevelChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/LevelChangedEventArgs.cs rename to src/SMAPI/Events/Player/LevelChangedEventArgs.cs diff --git a/src/SMAPI/Events/Player/PlayerEvents.cs b/src/SMAPI/Events/Player/PlayerEvents.cs new file mode 100644 index 00000000..11ba1e54 --- /dev/null +++ b/src/SMAPI/Events/Player/PlayerEvents.cs @@ -0,0 +1,68 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when the player data changes. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class PlayerEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised after the player's inventory changes in any way (added or removed item, sorted, etc). + public static event EventHandler InventoryChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + PlayerEvents.EventManager.Legacy_InventoryChanged.Add(value); + } + remove => PlayerEvents.EventManager.Legacy_InventoryChanged.Remove(value); + } + + /// Raised after the player levels up a skill. This happens as soon as they level up, not when the game notifies the player after their character goes to bed. + public static event EventHandler LeveledUp + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + PlayerEvents.EventManager.Legacy_LeveledUp.Add(value); + } + remove => PlayerEvents.EventManager.Legacy_LeveledUp.Remove(value); + } + + /// Raised after the player warps to a new location. + public static event EventHandler Warped + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + PlayerEvents.EventManager.Legacy_PlayerWarped.Add(value); + } + remove => PlayerEvents.EventManager.Legacy_PlayerWarped.Remove(value); + } + + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + PlayerEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/WarpedEventArgs.cs b/src/SMAPI/Events/Player/WarpedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/WarpedEventArgs.cs rename to src/SMAPI/Events/Player/WarpedEventArgs.cs diff --git a/src/SMAPI/Events/Specialised/ContentEvents.cs b/src/SMAPI/Events/Specialised/ContentEvents.cs new file mode 100644 index 00000000..aca76ef7 --- /dev/null +++ b/src/SMAPI/Events/Specialised/ContentEvents.cs @@ -0,0 +1,45 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when the game loads content. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class ContentEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised after the content language changes. + public static event EventHandler> AfterLocaleChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + ContentEvents.EventManager.Legacy_LocaleChanged.Add(value); + } + remove => ContentEvents.EventManager.Legacy_LocaleChanged.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + ContentEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/Specialised/EventArgsIntChanged.cs b/src/SMAPI/Events/Specialised/EventArgsIntChanged.cs new file mode 100644 index 00000000..76ec6d08 --- /dev/null +++ b/src/SMAPI/Events/Specialised/EventArgsIntChanged.cs @@ -0,0 +1,32 @@ +#if !SMAPI_3_0_STRICT +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for an integer field that changed value. + public class EventArgsIntChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The previous value. + public int PriorInt { get; } + + /// The current value. + public int NewInt { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The previous value. + /// The current value. + public EventArgsIntChanged(int priorInt, int newInt) + { + this.PriorInt = priorInt; + this.NewInt = newInt; + } + } +} +#endif diff --git a/src/SMAPI/Events/Specialised/EventArgsValueChanged.cs b/src/SMAPI/Events/Specialised/EventArgsValueChanged.cs new file mode 100644 index 00000000..7bfac7a2 --- /dev/null +++ b/src/SMAPI/Events/Specialised/EventArgsValueChanged.cs @@ -0,0 +1,33 @@ +#if !SMAPI_3_0_STRICT +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a field that changed value. + /// The value type. + public class EventArgsValueChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The previous value. + public T PriorValue { get; } + + /// The current value. + public T NewValue { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The previous value. + /// The current value. + public EventArgsValueChanged(T priorValue, T newValue) + { + this.PriorValue = priorValue; + this.NewValue = newValue; + } + } +} +#endif diff --git a/src/SMAPI/Events/ISpecialisedEvents.cs b/src/SMAPI/Events/Specialised/ISpecialisedEvents.cs similarity index 100% rename from src/SMAPI/Events/ISpecialisedEvents.cs rename to src/SMAPI/Events/Specialised/ISpecialisedEvents.cs diff --git a/src/SMAPI/Events/LoadStageChangedEventArgs.cs b/src/SMAPI/Events/Specialised/LoadStageChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/LoadStageChangedEventArgs.cs rename to src/SMAPI/Events/Specialised/LoadStageChangedEventArgs.cs diff --git a/src/SMAPI/Events/Specialised/SpecialisedEvents.cs b/src/SMAPI/Events/Specialised/SpecialisedEvents.cs new file mode 100644 index 00000000..4f16e4da --- /dev/null +++ b/src/SMAPI/Events/Specialised/SpecialisedEvents.cs @@ -0,0 +1,45 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events serving specialised edge cases that shouldn't be used by most mods. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class SpecialisedEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised when the game updates its state (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this method will trigger a stability warning in the SMAPI console. + public static event EventHandler UnvalidatedUpdateTick + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + SpecialisedEvents.EventManager.Legacy_UnvalidatedUpdateTick.Add(value); + } + remove => SpecialisedEvents.EventManager.Legacy_UnvalidatedUpdateTick.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + SpecialisedEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs b/src/SMAPI/Events/Specialised/UnvalidatedUpdateTickedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs rename to src/SMAPI/Events/Specialised/UnvalidatedUpdateTickedEventArgs.cs diff --git a/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs b/src/SMAPI/Events/Specialised/UnvalidatedUpdateTickingEventArgs.cs similarity index 100% rename from src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs rename to src/SMAPI/Events/Specialised/UnvalidatedUpdateTickingEventArgs.cs diff --git a/src/SMAPI/Events/BuildingListChangedEventArgs.cs b/src/SMAPI/Events/World/BuildingListChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/BuildingListChangedEventArgs.cs rename to src/SMAPI/Events/World/BuildingListChangedEventArgs.cs diff --git a/src/SMAPI/Events/ChangeType.cs b/src/SMAPI/Events/World/ChangeType.cs similarity index 100% rename from src/SMAPI/Events/ChangeType.cs rename to src/SMAPI/Events/World/ChangeType.cs diff --git a/src/SMAPI/Events/DebrisListChangedEventArgs.cs b/src/SMAPI/Events/World/DebrisListChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/DebrisListChangedEventArgs.cs rename to src/SMAPI/Events/World/DebrisListChangedEventArgs.cs diff --git a/src/SMAPI/Events/World/EventArgsLocationBuildingsChanged.cs b/src/SMAPI/Events/World/EventArgsLocationBuildingsChanged.cs new file mode 100644 index 00000000..25e84722 --- /dev/null +++ b/src/SMAPI/Events/World/EventArgsLocationBuildingsChanged.cs @@ -0,0 +1,41 @@ +#if !SMAPI_3_0_STRICT +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; +using StardewValley.Buildings; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsLocationBuildingsChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The location which changed. + public GameLocation Location { get; } + + /// The buildings added to the location. + public IEnumerable Added { get; } + + /// The buildings removed from the location. + public IEnumerable Removed { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The location which changed. + /// The buildings added to the location. + /// The buildings removed from the location. + public EventArgsLocationBuildingsChanged(GameLocation location, IEnumerable added, IEnumerable removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} +#endif diff --git a/src/SMAPI/Events/World/EventArgsLocationObjectsChanged.cs b/src/SMAPI/Events/World/EventArgsLocationObjectsChanged.cs new file mode 100644 index 00000000..9ca2e3e2 --- /dev/null +++ b/src/SMAPI/Events/World/EventArgsLocationObjectsChanged.cs @@ -0,0 +1,42 @@ +#if !SMAPI_3_0_STRICT +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using StardewValley; +using SObject = StardewValley.Object; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsLocationObjectsChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The location which changed. + public GameLocation Location { get; } + + /// The objects added to the location. + public IEnumerable> Added { get; } + + /// The objects removed from the location. + public IEnumerable> Removed { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The location which changed. + /// The objects added to the location. + /// The objects removed from the location. + public EventArgsLocationObjectsChanged(GameLocation location, IEnumerable> added, IEnumerable> removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} +#endif diff --git a/src/SMAPI/Events/World/EventArgsLocationsChanged.cs b/src/SMAPI/Events/World/EventArgsLocationsChanged.cs new file mode 100644 index 00000000..1a59e612 --- /dev/null +++ b/src/SMAPI/Events/World/EventArgsLocationsChanged.cs @@ -0,0 +1,35 @@ +#if !SMAPI_3_0_STRICT +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for a event. + public class EventArgsLocationsChanged : EventArgs + { + /********* + ** Accessors + *********/ + /// The added locations. + public IEnumerable Added { get; } + + /// The removed locations. + public IEnumerable Removed { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The added locations. + /// The removed locations. + public EventArgsLocationsChanged(IEnumerable added, IEnumerable removed) + { + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} +#endif diff --git a/src/SMAPI/Events/IWorldEvents.cs b/src/SMAPI/Events/World/IWorldEvents.cs similarity index 100% rename from src/SMAPI/Events/IWorldEvents.cs rename to src/SMAPI/Events/World/IWorldEvents.cs diff --git a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/World/LargeTerrainFeatureListChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs rename to src/SMAPI/Events/World/LargeTerrainFeatureListChangedEventArgs.cs diff --git a/src/SMAPI/Events/World/LocationEvents.cs b/src/SMAPI/Events/World/LocationEvents.cs new file mode 100644 index 00000000..0761bdd8 --- /dev/null +++ b/src/SMAPI/Events/World/LocationEvents.cs @@ -0,0 +1,67 @@ +#if !SMAPI_3_0_STRICT +using System; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Events +{ + /// Events raised when the player transitions between game locations, a location is added or removed, or the objects in the current location change. + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.Events) + " instead. See https://smapi.io/3.0 for more info.")] + public static class LocationEvents + { + /********* + ** Fields + *********/ + /// The core event manager. + private static EventManager EventManager; + + + /********* + ** Events + *********/ + /// Raised after a game location is added or removed. + public static event EventHandler LocationsChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + LocationEvents.EventManager.Legacy_LocationsChanged.Add(value); + } + remove => LocationEvents.EventManager.Legacy_LocationsChanged.Remove(value); + } + + /// Raised after buildings are added or removed in a location. + public static event EventHandler BuildingsChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + LocationEvents.EventManager.Legacy_BuildingsChanged.Add(value); + } + remove => LocationEvents.EventManager.Legacy_BuildingsChanged.Remove(value); + } + + /// Raised after objects are added or removed in a location. + public static event EventHandler ObjectsChanged + { + add + { + SCore.DeprecationManager.WarnForOldEvents(); + LocationEvents.EventManager.Legacy_ObjectsChanged.Add(value); + } + remove => LocationEvents.EventManager.Legacy_ObjectsChanged.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) + { + LocationEvents.EventManager = eventManager; + } + } +} +#endif diff --git a/src/SMAPI/Events/LocationListChangedEventArgs.cs b/src/SMAPI/Events/World/LocationListChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/LocationListChangedEventArgs.cs rename to src/SMAPI/Events/World/LocationListChangedEventArgs.cs diff --git a/src/SMAPI/Events/NpcListChangedEventArgs.cs b/src/SMAPI/Events/World/NpcListChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/NpcListChangedEventArgs.cs rename to src/SMAPI/Events/World/NpcListChangedEventArgs.cs diff --git a/src/SMAPI/Events/ObjectListChangedEventArgs.cs b/src/SMAPI/Events/World/ObjectListChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/ObjectListChangedEventArgs.cs rename to src/SMAPI/Events/World/ObjectListChangedEventArgs.cs diff --git a/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/World/TerrainFeatureListChangedEventArgs.cs similarity index 100% rename from src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs rename to src/SMAPI/Events/World/TerrainFeatureListChangedEventArgs.cs diff --git a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs index a331f38a..11a2564c 100644 --- a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs +++ b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace StardewModdingAPI.Framework.Content { @@ -17,5 +18,37 @@ namespace StardewModdingAPI.Framework.Content /// A callback to invoke when the data is replaced (if any). public AssetDataForDictionary(string locale, string assetName, IDictionary data, Func getNormalisedPath, Action> onDataReplaced) : base(locale, assetName, data, getNormalisedPath, onDataReplaced) { } + +#if !SMAPI_3_0_STRICT + /// Add or replace an entry in the dictionary. + /// The entry key. + /// The entry value. + [Obsolete("Access " + nameof(AssetData>.Data) + "field directly.")] + public void Set(TKey key, TValue value) + { + SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.PendingRemoval); + this.Data[key] = value; + } + + /// Add or replace an entry in the dictionary. + /// The entry key. + /// A callback which accepts the current value and returns the new value. + [Obsolete("Access " + nameof(AssetData>.Data) + "field directly.")] + public void Set(TKey key, Func value) + { + SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.PendingRemoval); + this.Data[key] = value(this.Data[key]); + } + + /// Dynamically replace values in the dictionary. + /// A lambda which takes the current key and value for an entry, and returns the new value. + [Obsolete("Access " + nameof(AssetData>.Data) + "field directly.")] + public void Set(Func replacer) + { + SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.PendingRemoval); + foreach (var pair in this.Data.ToArray()) + this.Data[pair.Key] = replacer(pair.Key, pair.Value); + } +#endif } } diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index 636b1979..3153bbb4 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -14,7 +14,11 @@ namespace StardewModdingAPI.Framework private readonly HashSet LoggedDeprecations = new HashSet(StringComparer.InvariantCultureIgnoreCase); /// Encapsulates monitoring and logging for a given module. +#if !SMAPI_3_0_STRICT + private readonly Monitor Monitor; +#else private readonly IMonitor Monitor; +#endif /// Tracks the installed mods. private readonly ModRegistry ModRegistry; @@ -22,6 +26,11 @@ namespace StardewModdingAPI.Framework /// The queued deprecation warnings to display. private readonly IList QueuedWarnings = new List(); +#if !SMAPI_3_0_STRICT + /// Whether the one-time deprecation message has been shown. + private bool DeprecationHeaderShown = false; +#endif + /********* ** Public methods @@ -29,7 +38,11 @@ namespace StardewModdingAPI.Framework /// Construct an instance. /// Encapsulates monitoring and logging for a given module. /// Tracks the installed mods. +#if !SMAPI_3_0_STRICT + public DeprecationManager(Monitor monitor, ModRegistry modRegistry) +#else public DeprecationManager(IMonitor monitor, ModRegistry modRegistry) +#endif { this.Monitor = monitor; this.ModRegistry = modRegistry; @@ -68,10 +81,26 @@ namespace StardewModdingAPI.Framework /// Print any queued messages. public void PrintQueued() { +#if !SMAPI_3_0_STRICT + if (!this.DeprecationHeaderShown && this.QueuedWarnings.Any()) + { + this.Monitor.Newline(); + this.Monitor.Log("Some of your mods will break in the upcoming SMAPI 3.0. Please update your mods now, or notify the author if no update is available. See https://mods.smapi.io for links to the latest versions.", LogLevel.Warn); + this.Monitor.Newline(); + this.DeprecationHeaderShown = true; + } +#endif + foreach (DeprecationWarning warning in this.QueuedWarnings.OrderBy(p => p.ModName).ThenBy(p => p.NounPhrase)) { // build message +#if SMAPI_3_0_STRICT string message = $"{warning.ModName} uses deprecated code ({warning.NounPhrase} is deprecated since SMAPI {warning.Version})."; +#else + string message = warning.NounPhrase == "legacy events" + ? $"{warning.ModName ?? "An unknown mod"} will break in the upcoming SMAPI 3.0 (legacy events are deprecated since SMAPI {warning.Version})." + : $"{warning.ModName ?? "An unknown mod"} will break in the upcoming SMAPI 3.0 ({warning.NounPhrase} is deprecated since SMAPI {warning.Version})."; +#endif // get log level LogLevel level; @@ -103,7 +132,7 @@ namespace StardewModdingAPI.Framework else { this.Monitor.Log(message, level); - this.Monitor.Log(warning.StackTrace, LogLevel.Debug); + this.Monitor.Log(warning.StackTrace); } } } diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index b2926105..f58ec918 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -1,4 +1,7 @@ using System.Diagnostics.CodeAnalysis; +#if !SMAPI_3_0_STRICT +using Microsoft.Xna.Framework.Input; +#endif using StardewModdingAPI.Events; namespace StardewModdingAPI.Framework.Events @@ -163,11 +166,196 @@ namespace StardewModdingAPI.Framework.Events /// Raised after the game performs its overall update tick (≈60 times per second). See notes on . public readonly ManagedEvent UnvalidatedUpdateTicked; - public readonly ManagedEvent ObjectCanBePlacedHere; - public readonly ManagedEvent ObjectCheckForAction; +#if !SMAPI_3_0_STRICT + /********* + ** Events (old) + *********/ + /**** + ** ContentEvents + ****/ + /// Raised after the content language changes. + public readonly ManagedEvent> Legacy_LocaleChanged; + + /**** + ** ControlEvents + ****/ + /// Raised when the changes. That happens when the player presses or releases a key. + public readonly ManagedEvent Legacy_KeyboardChanged; + + /// Raised after the player presses a keyboard key. + public readonly ManagedEvent Legacy_KeyPressed; + + /// Raised after the player releases a keyboard key. + public readonly ManagedEvent Legacy_KeyReleased; + + /// Raised when the changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button. + public readonly ManagedEvent Legacy_MouseChanged; + + /// The player pressed a controller button. This event isn't raised for trigger buttons. + public readonly ManagedEvent Legacy_ControllerButtonPressed; + + /// The player released a controller button. This event isn't raised for trigger buttons. + public readonly ManagedEvent Legacy_ControllerButtonReleased; + + /// The player pressed a controller trigger button. + public readonly ManagedEvent Legacy_ControllerTriggerPressed; + + /// The player released a controller trigger button. + public readonly ManagedEvent Legacy_ControllerTriggerReleased; + + /**** + ** GameEvents + ****/ + /// Raised once after the game initialises and all methods have been called. + public readonly ManagedEvent Legacy_FirstUpdateTick; + + /// Raised when the game updates its state (≈60 times per second). + public readonly ManagedEvent Legacy_UpdateTick; + + /// Raised every other tick (≈30 times per second). + public readonly ManagedEvent Legacy_SecondUpdateTick; + + /// Raised every fourth tick (≈15 times per second). + public readonly ManagedEvent Legacy_FourthUpdateTick; + + /// Raised every eighth tick (≈8 times per second). + public readonly ManagedEvent Legacy_EighthUpdateTick; + + /// Raised every 15th tick (≈4 times per second). + public readonly ManagedEvent Legacy_QuarterSecondTick; + + /// Raised every 30th tick (≈twice per second). + public readonly ManagedEvent Legacy_HalfSecondTick; + + /// Raised every 60th tick (≈once per second). + public readonly ManagedEvent Legacy_OneSecondTick; + + /**** + ** GraphicsEvents + ****/ + /// Raised after the game window is resized. + public readonly ManagedEvent Legacy_Resize; + + /// Raised before drawing the world to the screen. + public readonly ManagedEvent Legacy_OnPreRenderEvent; + + /// Raised after drawing the world to the screen. + public readonly ManagedEvent Legacy_OnPostRenderEvent; + + /// Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) + public readonly ManagedEvent Legacy_OnPreRenderHudEvent; + + /// Raised after drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) + public readonly ManagedEvent Legacy_OnPostRenderHudEvent; + + /// Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. + public readonly ManagedEvent Legacy_OnPreRenderGuiEvent; + + /// Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. + public readonly ManagedEvent Legacy_OnPostRenderGuiEvent; + + /**** + ** InputEvents + ****/ + /// Raised after the player presses a button on the keyboard, controller, or mouse. + public readonly ManagedEvent Legacy_ButtonPressed; + + /// Raised after the player releases a keyboard key on the keyboard, controller, or mouse. + public readonly ManagedEvent Legacy_ButtonReleased; + + /**** + ** LocationEvents + ****/ + /// Raised after a game location is added or removed. + public readonly ManagedEvent Legacy_LocationsChanged; + + /// Raised after buildings are added or removed in a location. + public readonly ManagedEvent Legacy_BuildingsChanged; + + /// Raised after objects are added or removed in a location. + public readonly ManagedEvent Legacy_ObjectsChanged; + + /**** + ** MenuEvents + ****/ + /// Raised after a game menu is opened or replaced with another menu. This event is not invoked when a menu is closed. + public readonly ManagedEvent Legacy_MenuChanged; + + /// Raised after a game menu is closed. + public readonly ManagedEvent Legacy_MenuClosed; + + /**** + ** MultiplayerEvents + ****/ + /// Raised before the game syncs changes from other players. + public readonly ManagedEvent Legacy_BeforeMainSync; + + /// Raised after the game syncs changes from other players. + public readonly ManagedEvent Legacy_AfterMainSync; + + /// Raised before the game broadcasts changes to other players. + public readonly ManagedEvent Legacy_BeforeMainBroadcast; + + /// Raised after the game broadcasts changes to other players. + public readonly ManagedEvent Legacy_AfterMainBroadcast; + + /**** + ** MineEvents + ****/ + /// Raised after the player warps to a new level of the mine. + public readonly ManagedEvent Legacy_MineLevelChanged; + + /**** + ** PlayerEvents + ****/ + /// Raised after the player's inventory changes in any way (added or removed item, sorted, etc). + public readonly ManagedEvent Legacy_InventoryChanged; + + /// Raised after the player levels up a skill. This happens as soon as they level up, not when the game notifies the player after their character goes to bed. + public readonly ManagedEvent Legacy_LeveledUp; + + /// Raised after the player warps to a new location. + public readonly ManagedEvent Legacy_PlayerWarped; + + + /**** + ** SaveEvents + ****/ + /// Raised before the game creates the save file. + public readonly ManagedEvent Legacy_BeforeCreateSave; + + /// Raised after the game finishes creating the save file. + public readonly ManagedEvent Legacy_AfterCreateSave; + + /// Raised before the game begins writes data to the save file. + public readonly ManagedEvent Legacy_BeforeSave; + + /// Raised after the game finishes writing data to the save file. + public readonly ManagedEvent Legacy_AfterSave; + + /// Raised after the player loads a save slot. + public readonly ManagedEvent Legacy_AfterLoad; + + /// Raised after the game returns to the title screen. + public readonly ManagedEvent Legacy_AfterReturnToTitle; + + /**** + ** SpecialisedEvents + ****/ + /// Raised when the game updates its state (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this method will trigger a stability warning in the SMAPI console. + public readonly ManagedEvent Legacy_UnvalidatedUpdateTick; + + /**** + ** TimeEvents + ****/ + /// Raised after the game begins a new day, including when loading a save. + public readonly ManagedEvent Legacy_AfterDayStarted; + + /// Raised after the in-game clock changes. + public readonly ManagedEvent Legacy_TimeOfDayChanged; +#endif - public readonly ManagedEvent ObjectIsIndexOkForBasicShippedCategory; /********* ** Public methods @@ -180,6 +368,10 @@ namespace StardewModdingAPI.Framework.Events // create shortcut initialisers ManagedEvent ManageEventOf(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry); +#if !SMAPI_3_0_STRICT + ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry); +#endif + // init events (new) this.MenuChanged = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged)); this.Rendering = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering)); @@ -231,10 +423,71 @@ namespace StardewModdingAPI.Framework.Events this.LoadStageChanged = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.LoadStageChanged)); this.UnvalidatedUpdateTicking = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking)); this.UnvalidatedUpdateTicked = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked)); + +#if !SMAPI_3_0_STRICT + // init events (old) + this.Legacy_LocaleChanged = ManageEventOf>(nameof(ContentEvents), nameof(ContentEvents.AfterLocaleChanged)); + + this.Legacy_ControllerButtonPressed = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonPressed)); + this.Legacy_ControllerButtonReleased = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonReleased)); + this.Legacy_ControllerTriggerPressed = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.ControllerTriggerPressed)); + this.Legacy_ControllerTriggerReleased = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.ControllerTriggerReleased)); + this.Legacy_KeyboardChanged = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.KeyboardChanged)); + this.Legacy_KeyPressed = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.KeyPressed)); + this.Legacy_KeyReleased = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.KeyReleased)); + this.Legacy_MouseChanged = ManageEventOf(nameof(ControlEvents), nameof(ControlEvents.MouseChanged)); + + this.Legacy_FirstUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.FirstUpdateTick)); + this.Legacy_UpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.UpdateTick)); + this.Legacy_SecondUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.SecondUpdateTick)); + this.Legacy_FourthUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.FourthUpdateTick)); + this.Legacy_EighthUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.EighthUpdateTick)); + this.Legacy_QuarterSecondTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.QuarterSecondTick)); + this.Legacy_HalfSecondTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.HalfSecondTick)); + this.Legacy_OneSecondTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.OneSecondTick)); + + this.Legacy_Resize = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.Resize)); + this.Legacy_OnPreRenderEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPreRenderEvent)); + this.Legacy_OnPostRenderEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPostRenderEvent)); + this.Legacy_OnPreRenderHudEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPreRenderHudEvent)); + this.Legacy_OnPostRenderHudEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPostRenderHudEvent)); + this.Legacy_OnPreRenderGuiEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPreRenderGuiEvent)); + this.Legacy_OnPostRenderGuiEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPostRenderGuiEvent)); + + this.Legacy_ButtonPressed = ManageEventOf(nameof(InputEvents), nameof(InputEvents.ButtonPressed)); + this.Legacy_ButtonReleased = ManageEventOf(nameof(InputEvents), nameof(InputEvents.ButtonReleased)); + + this.Legacy_LocationsChanged = ManageEventOf(nameof(LocationEvents), nameof(LocationEvents.LocationsChanged)); + this.Legacy_BuildingsChanged = ManageEventOf(nameof(LocationEvents), nameof(LocationEvents.BuildingsChanged)); + this.Legacy_ObjectsChanged = ManageEventOf(nameof(LocationEvents), nameof(LocationEvents.ObjectsChanged)); + + this.Legacy_MenuChanged = ManageEventOf(nameof(MenuEvents), nameof(MenuEvents.MenuChanged)); + this.Legacy_MenuClosed = ManageEventOf(nameof(MenuEvents), nameof(MenuEvents.MenuClosed)); + + this.Legacy_BeforeMainBroadcast = ManageEvent(nameof(MultiplayerEvents), nameof(MultiplayerEvents.BeforeMainBroadcast)); + this.Legacy_AfterMainBroadcast = ManageEvent(nameof(MultiplayerEvents), nameof(MultiplayerEvents.AfterMainBroadcast)); + this.Legacy_BeforeMainSync = ManageEvent(nameof(MultiplayerEvents), nameof(MultiplayerEvents.BeforeMainSync)); + this.Legacy_AfterMainSync = ManageEvent(nameof(MultiplayerEvents), nameof(MultiplayerEvents.AfterMainSync)); + + this.Legacy_MineLevelChanged = ManageEventOf(nameof(MineEvents), nameof(MineEvents.MineLevelChanged)); + + this.Legacy_InventoryChanged = ManageEventOf(nameof(PlayerEvents), nameof(PlayerEvents.InventoryChanged)); + this.Legacy_LeveledUp = ManageEventOf(nameof(PlayerEvents), nameof(PlayerEvents.LeveledUp)); + this.Legacy_PlayerWarped = ManageEventOf(nameof(PlayerEvents), nameof(PlayerEvents.Warped)); + + this.Legacy_BeforeCreateSave = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.BeforeCreate)); + this.Legacy_AfterCreateSave = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.AfterCreate)); + this.Legacy_BeforeSave = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.BeforeSave)); + this.Legacy_AfterSave = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.AfterSave)); + this.Legacy_AfterLoad = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.AfterLoad)); + this.Legacy_AfterReturnToTitle = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.AfterReturnToTitle)); + + this.Legacy_UnvalidatedUpdateTick = ManageEvent(nameof(SpecialisedEvents), nameof(SpecialisedEvents.UnvalidatedUpdateTick)); + + this.Legacy_AfterDayStarted = ManageEvent(nameof(TimeEvents), nameof(TimeEvents.AfterDayStarted)); + this.Legacy_TimeOfDayChanged = ManageEventOf(nameof(TimeEvents), nameof(TimeEvents.TimeOfDayChanged)); +#endif - this.ObjectCheckForAction = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.LoadStageChanged)); - this.ObjectCanBePlacedHere = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking)); - this.ObjectIsIndexOkForBasicShippedCategory = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked)); } } } diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 64634365..a1a3bedc 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -13,9 +13,7 @@ namespace StardewModdingAPI.Framework.Events *********/ /// The underlying event. private event EventHandler Event; - - private event Func Func; - + /// A human-readable name for the event. private readonly string EventName; @@ -32,7 +30,6 @@ namespace StardewModdingAPI.Framework.Events /// The cached invocation list. private EventHandler[] CachedInvocationList; - private Func[] CachedInvocationListFunc; /********* @@ -62,11 +59,6 @@ namespace StardewModdingAPI.Framework.Events this.Add(handler, this.ModRegistry.GetFromStack()); } - public void Add(Func handler) - { - this.Add(handler, this.ModRegistry.GetFromStack()); - } - /// Add an event handler. /// The event handler. /// The mod which added the event handler. @@ -75,13 +67,7 @@ namespace StardewModdingAPI.Framework.Events this.Event += handler; this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast>()); } - - public void Add(Func handler, IModMetadata mod) - { - this.Func += handler; - this.AddTracking(mod, handler, this.Func?.GetInvocationList().Cast>()); - } - + /// Remove an event handler. /// The event handler. public void Remove(EventHandler handler) @@ -90,12 +76,6 @@ namespace StardewModdingAPI.Framework.Events this.RemoveTracking(handler, this.Event?.GetInvocationList().Cast>()); } - public void Remove(Func handler) - { - this.Func -= handler; - this.RemoveTracking(handler, this.Event?.GetInvocationList().Cast>()); - } - /// Raise the event and notify all handlers. /// The event arguments to pass. public void Raise(TEventArgs args) @@ -116,31 +96,6 @@ namespace StardewModdingAPI.Framework.Events } } - /// Raise the event and notify all handlers wait for a actively response. - /// The event arguments to pass. - public bool RaiseForChainRun(TEventArgs args) - { - if (this.Func == null) - return true; - - foreach (Func handler in this.CachedInvocationListFunc) - { - try - { - bool run = handler.Invoke(args); - if (!run) - { - return false; - } - } - catch (Exception ex) - { - this.LogError(handler, ex); - } - } - return true; - } - /// Raise the event and notify all handlers. /// The event arguments to pass. /// A lambda which returns true if the event should be raised for the given mod. @@ -178,13 +133,7 @@ namespace StardewModdingAPI.Framework.Events this.SourceMods[handler] = mod; this.CachedInvocationList = invocationList?.ToArray() ?? new EventHandler[0]; } - - protected void AddTracking(IModMetadata mod, Func handler, IEnumerable> invocationList) - { - this.SourceModsFunc[handler] = mod; - this.CachedInvocationListFunc = invocationList?.ToArray() ?? new Func[0]; - } - + /// Remove tracking for an event handler. /// The event handler. /// The updated event invocation list. @@ -194,12 +143,6 @@ namespace StardewModdingAPI.Framework.Events if (!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) this.SourceMods.Remove(handler); } - protected void RemoveTracking(Func handler, IEnumerable> invocationList) - { - this.CachedInvocationListFunc = invocationList?.ToArray() ?? new Func[0]; - if (!this.CachedInvocationListFunc.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) - this.SourceModsFunc.Remove(handler); - } /// Get the mod which registered the given event handler, if available. /// The event handler. @@ -235,4 +178,70 @@ namespace StardewModdingAPI.Framework.Events this.Monitor.Log($"A mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error); } } + +#if !SMAPI_3_0_STRICT + /// An event wrapper which intercepts and logs errors in handler code. + internal class ManagedEvent : ManagedEventBase + { + /********* + ** Fields + *********/ + /// The underlying event. + private event EventHandler Event; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// A human-readable name for the event. + /// Writes messages to the log. + /// The mod registry with which to identify mods. + public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry) + : base(eventName, monitor, modRegistry) { } + + /// Add an event handler. + /// The event handler. + public void Add(EventHandler handler) + { + this.Add(handler, this.ModRegistry.GetFromStack()); + } + + /// Add an event handler. + /// The event handler. + /// The mod which added the event handler. + public void Add(EventHandler handler, IModMetadata mod) + { + this.Event += handler; + this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast()); + } + + /// Remove an event handler. + /// The event handler. + public void Remove(EventHandler handler) + { + this.Event -= handler; + this.RemoveTracking(handler, this.Event?.GetInvocationList().Cast()); + } + + /// Raise the event and notify all handlers. + public void Raise() + { + if (this.Event == null) + return; + + foreach (EventHandler handler in this.CachedInvocationList) + { + try + { + handler.Invoke(null, EventArgs.Empty); + } + catch (Exception ex) + { + this.LogError(handler, ex); + } + } + } + } +#endif } diff --git a/src/SMAPI/Framework/Events/ManagedEventBase.cs b/src/SMAPI/Framework/Events/ManagedEventBase.cs new file mode 100644 index 00000000..c8c3516b --- /dev/null +++ b/src/SMAPI/Framework/Events/ManagedEventBase.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace StardewModdingAPI.Framework.Events +{ + /// The base implementation for an event wrapper which intercepts and logs errors in handler code. + internal abstract class ManagedEventBase + { + /********* + ** Fields + *********/ + /// A human-readable name for the event. + private readonly string EventName; + + /// Writes messages to the log. + private readonly IMonitor Monitor; + + /// The mod registry with which to identify mods. + protected readonly ModRegistry ModRegistry; + + /// The display names for the mods which added each delegate. + private readonly IDictionary SourceMods = new Dictionary(); + + /// The cached invocation list. + protected TEventHandler[] CachedInvocationList { get; private set; } + + + /********* + ** Public methods + *********/ + /// Get whether anything is listening to the event. + public bool HasListeners() + { + return this.CachedInvocationList?.Length > 0; + } + + /********* + ** Protected methods + *********/ + /// Construct an instance. + /// A human-readable name for the event. + /// Writes messages to the log. + /// The mod registry with which to identify mods. + protected ManagedEventBase(string eventName, IMonitor monitor, ModRegistry modRegistry) + { + this.EventName = eventName; + this.Monitor = monitor; + this.ModRegistry = modRegistry; + } + + /// Track an event handler. + /// The mod which added the handler. + /// The event handler. + /// The updated event invocation list. + protected void AddTracking(IModMetadata mod, TEventHandler handler, IEnumerable invocationList) + { + this.SourceMods[handler] = mod; + this.CachedInvocationList = invocationList?.ToArray() ?? new TEventHandler[0]; + } + + /// Remove tracking for an event handler. + /// The event handler. + /// The updated event invocation list. + protected void RemoveTracking(TEventHandler handler, IEnumerable invocationList) + { + this.CachedInvocationList = invocationList?.ToArray() ?? new TEventHandler[0]; + if (!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) + this.SourceMods.Remove(handler); + } + + /// Get the mod which registered the given event handler, if available. + /// The event handler. + protected IModMetadata GetSourceMod(TEventHandler handler) + { + return this.SourceMods.TryGetValue(handler, out IModMetadata mod) + ? mod + : null; + } + + /// Log an exception from an event handler. + /// The event handler instance. + /// The exception that was raised. + protected void LogError(TEventHandler handler, Exception ex) + { + IModMetadata mod = this.GetSourceMod(handler); + if (mod != null) + mod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error); + else + this.Monitor.Log($"A mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error); + } + } +} diff --git a/src/SMAPI/Framework/Events/ModEvents.cs b/src/SMAPI/Framework/Events/ModEvents.cs index 58b493fc..7ca5dceb 100644 --- a/src/SMAPI/Framework/Events/ModEvents.cs +++ b/src/SMAPI/Framework/Events/ModEvents.cs @@ -28,8 +28,7 @@ namespace StardewModdingAPI.Framework.Events /// Events serving specialised edge cases that shouldn't be used by most mods. public ISpecialisedEvents Specialised { get; } - - public IHookEvents Hook { get; } + /********* ** Public methods @@ -46,7 +45,6 @@ namespace StardewModdingAPI.Framework.Events this.Player = new ModPlayerEvents(mod, eventManager); this.World = new ModWorldEvents(mod, eventManager); this.Specialised = new ModSpecialisedEvents(mod, eventManager); - this.Hook = new ModHookEvents(mod, eventManager); } } } diff --git a/src/SMAPI/Framework/Events/ModHookEvents.cs b/src/SMAPI/Framework/Events/ModHookEvents.cs deleted file mode 100644 index 27311568..00000000 --- a/src/SMAPI/Framework/Events/ModHookEvents.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using StardewModdingAPI.Events; - -namespace StardewModdingAPI.Framework.Events -{ - /// Events raised when the player provides input using a controller, keyboard, or mouse. - internal class ModHookEvents : ModEventsBase, IHookEvents - { - /********* - ** Accessors - *********/ - /// Raised after the player presses a button on the keyboard, controller, or mouse. - public event Func ObjectCanBePlacedHere - { - add => this.EventManager.ObjectCanBePlacedHere.Add(value); - remove => this.EventManager.ObjectCanBePlacedHere.Remove(value); - } - - /// Raised after the player releases a button on the keyboard, controller, or mouse. - public event Func ObjectCheckForAction - { - add => this.EventManager.ObjectCheckForAction.Add(value); - remove => this.EventManager.ObjectCheckForAction.Remove(value); - } - - /// Raised after the player moves the in-game cursor. - public event Func ObjectIsIndexOkForBasicShippedCategory - { - add => this.EventManager.ObjectIsIndexOkForBasicShippedCategory.Add(value); - remove => this.EventManager.ObjectIsIndexOkForBasicShippedCategory.Remove(value); - } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The mod which uses this instance. - /// The underlying event manager. - internal ModHookEvents(IModMetadata mod, EventManager eventManager) - : base(mod, eventManager) { } - } -} diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 86e8eb28..579dd2d3 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Toolkit.Serialisation; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModHelpers { @@ -15,6 +17,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The full path to the mod's folder. public string DirectoryPath { get; } +#if !SMAPI_3_0_STRICT + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; +#endif + /// Manages access to events raised by SMAPI, which let your mod react when something happens in the game. public IModEvents Events { get; } @@ -64,7 +71,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// An API for reading translations stored in the mod's i18n folder. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string modID, string modDirectory, SInputState inputState, IModEvents events, IContentHelper contentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) + public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, SInputState inputState, IModEvents events, IContentHelper contentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) : base(modID) { // validate directory @@ -85,6 +92,9 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Multiplayer = multiplayer ?? throw new ArgumentNullException(nameof(multiplayer)); this.Translation = translationHelper ?? throw new ArgumentNullException(nameof(translationHelper)); this.Events = events; +#if !SMAPI_3_0_STRICT + this.JsonHelper = jsonHelper ?? throw new ArgumentNullException(nameof(jsonHelper)); +#endif } /**** @@ -109,6 +119,63 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Data.WriteJsonFile("config.json", config); } +#if !SMAPI_3_0_STRICT + /**** + ** Generic JSON files + ****/ + /// Read a JSON file. + /// The model type. + /// The file path relative to the mod directory. + /// Returns the deserialised model, or null if the file doesn't exist or is empty. + [Obsolete("Use " + nameof(ModHelper.Data) + "." + nameof(IDataHelper.ReadJsonFile) + " instead")] + public TModel ReadJsonFile(string path) + where TModel : class + { + path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + ? data + : null; + } + + /// Save to a JSON file. + /// The model type. + /// The file path relative to the mod directory. + /// The model to save. + [Obsolete("Use " + nameof(ModHelper.Data) + "." + nameof(IDataHelper.WriteJsonFile) + " instead")] + public void WriteJsonFile(string path, TModel model) + where TModel : class + { + path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); + this.JsonHelper.WriteJsonFile(path, model); + } +#endif + + /**** + ** Content packs + ****/ +#if !SMAPI_3_0_STRICT + /// Manually create a transitional content pack to support pre-SMAPI content packs. This provides a way to access legacy content packs using the SMAPI content pack APIs, but the content pack will not be visible in the log or validated by SMAPI. + /// The absolute directory path containing the content pack files. + /// The content pack's unique ID. + /// The content pack name. + /// The content pack description. + /// The content pack author's name. + /// The content pack version. + [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ContentPacks) + "." + nameof(IContentPackHelper.CreateTemporary) + " instead")] + public IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version) + { + SCore.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.PendingRemoval); + return this.ContentPacks.CreateTemporary(directoryPath, id, name, description, author, version); + } + + /// Get all content packs loaded for this mod. + [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ContentPacks) + "." + nameof(IContentPackHelper.GetOwned) + " instead")] + public IEnumerable GetContentPacks() + { + return this.ContentPacks.GetOwned(); + } +#endif + /**** ** Disposal ****/ diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index 0ce72a9e..1943ef23 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -148,9 +148,9 @@ namespace StardewModdingAPI.Framework.ModHelpers throw new InvalidOperationException($"Can't validate access to {member.MemberType} {member.Name} because it has no declaring type."); // should never happen // validate access - string rootNamespace = typeof(Program).Namespace; - if (declaringType.Namespace == rootNamespace || declaringType.Namespace?.StartsWith(rootNamespace + ".") == true) - throw new InvalidOperationException($"SMAPI blocked access by {this.ModName} to its internals through the reflection API. Accessing the SMAPI internals is strongly discouraged since they're subject to change, which means the mod can break without warning. (Detected access to {declaringType.FullName}.{member.Name}.)"); + //string rootNamespace = typeof(Program).Namespace; + //if (declaringType.Namespace == rootNamespace || declaringType.Namespace?.StartsWith(rootNamespace + ".") == true) + // throw new InvalidOperationException($"SMAPI blocked access by {this.ModName} to its internals through the reflection API. Accessing the SMAPI internals is strongly discouraged since they're subject to change, which means the mod can break without warning. (Detected access to {declaringType.FullName}.{member.Name}.)"); } } } diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index f2002530..75d3849d 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -147,8 +147,12 @@ namespace StardewModdingAPI.Framework.ModLoading string actualFilename = new DirectoryInfo(mod.DirectoryPath).GetFiles(mod.Manifest.EntryDll).FirstOrDefault()?.Name; if (actualFilename != mod.Manifest.EntryDll) { +#if SMAPI_3_0_STRICT mod.SetStatus(ModMetadataStatus.Failed, $"its {nameof(IManifest.EntryDll)} value '{mod.Manifest.EntryDll}' doesn't match the actual file capitalisation '{actualFilename}'. The capitalisation must match for crossplatform compatibility."); continue; +#else + SCore.DeprecationManager.Warn(mod.DisplayName, $"{nameof(IManifest.EntryDll)} value with case-insensitive capitalisation", "2.11", DeprecationLevel.PendingRemoval); +#endif } } diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs index f8f10dc4..d9a49cfa 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs @@ -103,7 +103,7 @@ namespace StardewModdingAPI.Framework.ModLoading public static bool HasMatchingSignature(Type type, MethodReference reference) { return type - .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) + .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public) .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); } } diff --git a/src/SMAPI/Framework/RewriteFacades/FarmerMethods.cs b/src/SMAPI/Framework/RewriteFacades/FarmerMethods.cs new file mode 100644 index 00000000..7d849d4d --- /dev/null +++ b/src/SMAPI/Framework/RewriteFacades/FarmerMethods.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework.Graphics; +using StardewValley; + +#pragma warning disable 1591 // missing documentation +namespace StardewModdingAPI.Framework.RewriteFacades +{ + /// Provides method signatures that can be injected into mod code for compatibility between Linux/Mac or Windows. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + public class FarmerMethods : Farmer + { + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new bool couldInventoryAcceptThisItem(Item item) + { + return base.couldInventoryAcceptThisItem(item, true); + } + } +} diff --git a/src/SMAPI/Framework/RewriteFacades/Game1Methods.cs b/src/SMAPI/Framework/RewriteFacades/Game1Methods.cs new file mode 100644 index 00000000..ad62c105 --- /dev/null +++ b/src/SMAPI/Framework/RewriteFacades/Game1Methods.cs @@ -0,0 +1,41 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework.Graphics; +using StardewValley; + +#pragma warning disable 1591 // missing documentation +namespace StardewModdingAPI.Framework.RewriteFacades +{ + /// Provides method signatures that can be injected into mod code for compatibility between Linux/Mac or Windows. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + public class Game1Methods : Game1 + { + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public static new string parseText(string text, SpriteFont whichFont, int width) + { + return parseText(text, whichFont, width, 1); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public static new void warpFarmer(LocationRequest locationRequest, int tileX, int tileY, int facingDirectionAfterWarp) + { + warpFarmer(locationRequest, tileX, tileY, facingDirectionAfterWarp, 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, bool flip) + { + warpFarmer(locationName, tileX, tileY, flip ? ((player.FacingDirection + 2) % 4) : player.FacingDirection); + } + + [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) + { + 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); + } + } +} diff --git a/src/SMAPI/Framework/RewriteFacades/IClickableMenuMethods.cs b/src/SMAPI/Framework/RewriteFacades/IClickableMenuMethods.cs new file mode 100644 index 00000000..8686ceed --- /dev/null +++ b/src/SMAPI/Framework/RewriteFacades/IClickableMenuMethods.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using StardewValley; +using StardewValley.Menus; + +#pragma warning disable 1591 // missing documentation +namespace StardewModdingAPI.Framework.RewriteFacades +{ + /// Provides method signatures that can be injected into mod code for compatibility between Linux/Mac or Windows. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + public class IClickableMenuMethods : IClickableMenu + { + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public static new void drawHoverText(SpriteBatch b, string text, SpriteFont font, int xOffset = 0, int yOffset = 0, int moneyAmountToDisplayAtBottom = -1, string boldTitleText = null, int healAmountToDisplay = -1, string[] buffIconsToDisplay = null, Item hoveredItem = null, int currencySymbol = 0, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1, int overrideX = -1, int overrideY = -1, float alpha = 1, CraftingRecipe craftingIngredients = null) + { + drawHoverText(b, text, font, xOffset, yOffset, moneyAmountToDisplayAtBottom, boldTitleText, healAmountToDisplay, buffIconsToDisplay, hoveredItem, currencySymbol, extraItemToShowIndex, extraItemToShowAmount, overrideX, overrideY, alpha, craftingIngredients, -1, 80, -1); + } + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public static new void drawTextureBox(SpriteBatch b, Texture2D texture, Microsoft.Xna.Framework.Rectangle sourceRect, int x, int y, int width, int height, Color color) + { + drawTextureBox(b, texture, sourceRect, x, y, width, height, color, 1, true, false); + } + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public static new void drawTextureBox(SpriteBatch b, Texture2D texture, Microsoft.Xna.Framework.Rectangle sourceRect, int x, int y, int width, int height, Color color, float scale) + { + drawTextureBox(b, texture, sourceRect, x, y, width, height, color, scale, true, false); + } + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public static new void drawTextureBox(SpriteBatch b, Texture2D texture, Microsoft.Xna.Framework.Rectangle sourceRect, int x, int y, int width, int height, Color color, float scale, bool drawShadow) + { + drawTextureBox(b, texture, sourceRect, x, y, width, height, color, scale, drawShadow, false); + } + } +} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 9c900e7e..d8b77436 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -207,6 +207,22 @@ namespace StardewModdingAPI.Framework // initialise SMAPI try { +#if !SMAPI_3_0_STRICT + // hook up events + ContentEvents.Init(this.EventManager); + ControlEvents.Init(this.EventManager); + GameEvents.Init(this.EventManager); + GraphicsEvents.Init(this.EventManager); + InputEvents.Init(this.EventManager); + LocationEvents.Init(this.EventManager); + MenuEvents.Init(this.EventManager); + MineEvents.Init(this.EventManager); + MultiplayerEvents.Init(this.EventManager); + PlayerEvents.Init(this.EventManager); + SaveEvents.Init(this.EventManager); + SpecialisedEvents.Init(this.EventManager); + TimeEvents.Init(this.EventManager); +#endif // override game this.GameInstance = new SGame( contentCore, @@ -884,6 +900,14 @@ namespace StardewModdingAPI.Framework return false; } +#if !SMAPI_3_0_STRICT + // add deprecation warning for old version format + { + if (mod.Manifest?.Version is Toolkit.SemanticVersion version && version.IsLegacyFormat) + SCore.DeprecationManager.Warn(mod.DisplayName, "non-string manifest version", "2.8", DeprecationLevel.PendingRemoval); + } +#endif + // validate dependencies // Although dependences are validated before mods are loaded, a dependency may have failed to load. if (mod.Manifest.Dependencies?.Any() == true) @@ -991,7 +1015,7 @@ namespace StardewModdingAPI.Framework return new ContentPack(packDirPath, packManifest, packContentHelper, packTranslationHelper, this.Toolkit.JsonHelper); } - modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, this.GameInstance.Input, events, contentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); + modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper, this.GameInstance.Input, events, contentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); } // init mod diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 618968d2..d9be51f9 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -10,6 +10,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +#if !SMAPI_3_0_STRICT +using Microsoft.Xna.Framework.Input; +#endif using Netcode; using StardewModdingAPI.Enums; using StardewModdingAPI.Events; @@ -32,6 +35,7 @@ using xTile.Layers; using SObject = StardewValley.Object; using Rectangle = Microsoft.Xna.Framework.Rectangle; using StardewValley.Minigames; +using StardewValley.Objects; namespace StardewModdingAPI.Framework { @@ -69,28 +73,46 @@ namespace StardewModdingAPI.Framework /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. private readonly Countdown AfterLoadTimer = new Countdown(5); - internal bool OnObjectCanBePlacedHere(SObject instance, GameLocation location, Vector2 tile, ref bool result) + internal bool OnCommonHook_Prefix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref object __result) { - ObjectCanBePlacedHereEventArgs args = new ObjectCanBePlacedHereEventArgs(instance, location, tile, result); - args.__result = result; - bool run =this.Events.ObjectCanBePlacedHere.RaiseForChainRun(args); - result = args.__result; - return run; + foreach (IMod mod in this.HookReceiver) + { + if (!mod.OnCommonHook_Prefix(hookName, __instance, ref param1, ref param2, ref param3, ref param4, ref __result)) + { + return false; + } + } + return true; } - internal void OnObjectIsIndexOkForBasicShippedCategory(int index, ref bool result) + internal bool OnCommonHook_Postfix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref bool __state, ref object __result) { - ObjectIsIndexOkForBasicShippedCategoryEventArgs args = new ObjectIsIndexOkForBasicShippedCategoryEventArgs(index, result); - args.__result = result; - this.Events.ObjectIsIndexOkForBasicShippedCategory.RaiseForChainRun(args); - result = args.__result; + foreach (IMod mod in this.HookReceiver) + { + mod.OnCommonHook_Postfix(hookName, __instance, ref param1, ref param2, ref param3, ref param4, ref __state, ref __result); + } + return true; } - internal bool OnObjectCheckForAction(SObject instance) + internal bool OnCommonStaticHook_Prefix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref object __result) { - ObjectCheckForActionEventArgs args = new ObjectCheckForActionEventArgs(instance); - bool run = this.Events.ObjectCheckForAction.RaiseForChainRun(args); - return run; + foreach (IMod mod in this.HookReceiver) + { + if (!mod.OnCommonStaticHook_Prefix(hookName, ref param1, ref param2, ref param3, ref param4, ref param5, ref __result)) + { + return false; + } + } + return true; + } + + internal bool OnCommonStaticHook_Postfix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref bool __state, ref object __result) + { + foreach (IMod mod in this.HookReceiver) + { + mod.OnCommonStaticHook_Postfix(hookName, ref param1, ref param2, ref param3, ref param4, ref param5, ref __state, ref __result); + } + return true; } /// Whether the game is saving and SMAPI has already raised . @@ -98,19 +120,18 @@ namespace StardewModdingAPI.Framework /// Whether the game is creating the save file and SMAPI has already raised . private bool IsBetweenCreateEvents; - /// A callback to invoke after the content language changes. private readonly Action OnLocaleChanged; /// A callback to invoke the first time *any* game content manager loads an asset. public static Action OnLoadingFirstAsset; - + /// A callback to invoke after the game finishes initialising. private readonly Action OnGameInitialised; /// A callback to invoke when the game exits. private readonly Action OnGameExiting; - + /// Simplifies access to private game code. private readonly Reflector Reflection; @@ -156,11 +177,13 @@ namespace StardewModdingAPI.Framework /// This property must be threadsafe, since it's accessed from a separate console input thread. public ConcurrentQueue CommandQueue { get; } = new ConcurrentQueue(); - private bool saveParsed; + private bool saveParsed = false; private bool validTicking; public static SGame instance; + public List HookReceiver = new List(); + /********* @@ -206,7 +229,6 @@ namespace StardewModdingAPI.Framework // init observables //Game1.locations = new ObservableCollection(); - this.saveParsed = false; SGame.instance = this; } @@ -219,6 +241,14 @@ namespace StardewModdingAPI.Framework // init watchers this.Watchers = new WatcherCore(this.Input); + foreach (IModMetadata modMetadata in this.ModRegistry.GetAll()) + { + if(modMetadata.Mod != null && modMetadata.Mod.ApplyForHooks()) + { + this.HookReceiver.Add(modMetadata.Mod); + } + } + // raise callback this.OnGameInitialised(); } @@ -242,7 +272,10 @@ namespace StardewModdingAPI.Framework public void Update_Postfix(GameTime time) { this.Events.UnvalidatedUpdateTicked.RaiseEmpty(); - if(this.validTicking) +#if !SMAPI_3_0_STRICT + this.Events.Legacy_UnvalidatedUpdateTick.Raise(); +#endif + if (this.validTicking) this.Events.UpdateTicked.RaiseEmpty(); } @@ -276,7 +309,12 @@ namespace StardewModdingAPI.Framework // raise events this.Events.LoadStageChanged.Raise(new LoadStageChangedEventArgs(oldStage, newStage)); if (newStage == LoadStage.None) + { this.Events.ReturnedToTitle.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + this.Events.Legacy_AfterReturnToTitle.Raise(); +#endif + } } ///// Constructor a content manager to read XNB files. @@ -335,33 +373,41 @@ namespace StardewModdingAPI.Framework // concurrently with game code. if (Game1.currentLoader != null) { - int stage = Game1.currentLoader.Current; - switch (stage) + //this.Monitor.Log("Game loader synchronising...", LogLevel.Trace); + + while (Game1.currentLoader?.MoveNext() == true) { - case 20 when (!this.saveParsed && SaveGame.loaded != null): - this.saveParsed = true; - this.OnLoadStageChanged(LoadStage.SaveParsed); - break; + // raise load stage changed + switch (Game1.currentLoader.Current) + { + case 1: + case 24: + return false; + case 20: + if(!this.saveParsed && SaveGame.loaded != null) + { + this.saveParsed = true; + this.OnLoadStageChanged(LoadStage.SaveParsed); + } + return false; + case 36: + this.OnLoadStageChanged(LoadStage.SaveLoadedBasicInfo); + break; - case 36: - this.OnLoadStageChanged(LoadStage.SaveLoadedBasicInfo); - break; + case 50: + this.OnLoadStageChanged(LoadStage.SaveLoadedLocations); + break; - case 50: - this.OnLoadStageChanged(LoadStage.SaveLoadedLocations); - break; - - case 100: - Game1.currentLoader = null; - this.Monitor.Log("Game loader done.", LogLevel.Trace); - break; - - default: - if (Game1.gameMode == Game1.playingGameMode) - this.OnLoadStageChanged(LoadStage.Preloaded); - break; + default: + if (Game1.gameMode == Game1.playingGameMode) + this.OnLoadStageChanged(LoadStage.Preloaded); + break; + } } - return true; + + Game1.currentLoader = null; + this.saveParsed = false; + this.Monitor.Log("Game loader done.", LogLevel.Trace); } Task _newDayTask = this.Reflection.GetField(typeof(Game1), "_newDayTask").GetValue(); if (_newDayTask?.Status == TaskStatus.Created) @@ -431,6 +477,9 @@ namespace StardewModdingAPI.Framework // This should *always* run, even when suppressing mod events, since the game uses // this too. For example, doing this after mod event suppression would prevent the // user from doing anything on the overnight shipping screen. +#if !SMAPI_3_0_STRICT + SInputState previousInputState = this.Input.Clone(); +#endif SInputState inputState = this.Input; if (Game1.game1.IsActive) inputState.TrueUpdate(); @@ -451,6 +500,9 @@ namespace StardewModdingAPI.Framework this.IsBetweenCreateEvents = true; this.Monitor.Log("Context: before save creation.", LogLevel.Trace); events.SaveCreating.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_BeforeCreateSave.Raise(); +#endif } // raise before-save @@ -459,6 +511,9 @@ namespace StardewModdingAPI.Framework this.IsBetweenSaveEvents = true; this.Monitor.Log("Context: before save.", LogLevel.Trace); events.Saving.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_BeforeSave.Raise(); +#endif } // suppress non-save events @@ -474,6 +529,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); this.OnLoadStageChanged(LoadStage.CreatedSaveFile); events.SaveCreated.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_AfterCreateSave.Raise(); +#endif } if (this.IsBetweenSaveEvents) { @@ -482,6 +540,10 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); events.Saved.RaiseEmpty(); events.DayStarted.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_AfterSave.Raise(); + events.Legacy_AfterDayStarted.Raise(); +#endif } /********* @@ -510,8 +572,15 @@ namespace StardewModdingAPI.Framework *********/ if (this.Watchers.LocaleWatcher.IsChanged || SGame.TicksElapsed == 0) { - this.Monitor.Log($"Context: locale set to {this.Watchers.LocaleWatcher.CurrentValue}.", LogLevel.Trace); + var was = this.Watchers.LocaleWatcher.PreviousValue; + var now = this.Watchers.LocaleWatcher.CurrentValue; + + this.Monitor.Log($"Context: locale set to {now}.", LogLevel.Trace); + this.OnLocaleChanged(); +#if !SMAPI_3_0_STRICT + events.Legacy_LocaleChanged.Raise(new EventArgsValueChanged(was.ToString(), now.ToString())); +#endif this.Watchers.LocaleWatcher.Reset(); } @@ -538,6 +607,10 @@ namespace StardewModdingAPI.Framework this.OnLoadStageChanged(LoadStage.Ready); events.SaveLoaded.RaiseEmpty(); events.DayStarted.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_AfterLoad.Raise(); + events.Legacy_AfterDayStarted.Raise(); +#endif } /********* @@ -556,6 +629,9 @@ namespace StardewModdingAPI.Framework Point newSize = this.Watchers.WindowSizeWatcher.CurrentValue; events.WindowResized.Raise(new WindowResizedEventArgs(oldSize, newSize)); +#if !SMAPI_3_0_STRICT + events.Legacy_Resize.Raise(); +#endif this.Watchers.WindowSizeWatcher.Reset(); } @@ -614,6 +690,23 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Events: button {button} pressed.", LogLevel.Trace); events.ButtonPressed.Raise(new ButtonPressedEventArgs(button, cursor, inputState)); + +#if !SMAPI_3_0_STRICT + // legacy events + events.Legacy_ButtonPressed.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons)); + if (button.TryGetKeyboard(out Keys key)) + { + if (key != Keys.None) + events.Legacy_KeyPressed.Raise(new EventArgsKeyPressed(key)); + } + else if (button.TryGetController(out Buttons controllerButton)) + { + if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) + events.Legacy_ControllerTriggerPressed.Raise(new EventArgsControllerTriggerPressed(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); + else + events.Legacy_ControllerButtonPressed.Raise(new EventArgsControllerButtonPressed(PlayerIndex.One, controllerButton)); + } +#endif } else if (status == InputStatus.Released) { @@ -621,8 +714,32 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Events: button {button} released.", LogLevel.Trace); events.ButtonReleased.Raise(new ButtonReleasedEventArgs(button, cursor, inputState)); + +#if !SMAPI_3_0_STRICT + // legacy events + events.Legacy_ButtonReleased.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons)); + if (button.TryGetKeyboard(out Keys key)) + { + if (key != Keys.None) + events.Legacy_KeyReleased.Raise(new EventArgsKeyPressed(key)); + } + else if (button.TryGetController(out Buttons controllerButton)) + { + if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) + events.Legacy_ControllerTriggerReleased.Raise(new EventArgsControllerTriggerReleased(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); + else + events.Legacy_ControllerButtonReleased.Raise(new EventArgsControllerButtonReleased(PlayerIndex.One, controllerButton)); + } +#endif } } +#if !SMAPI_3_0_STRICT + // raise legacy state-changed events + if (inputState.RealKeyboard != previousInputState.RealKeyboard) + events.Legacy_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(previousInputState.RealKeyboard, inputState.RealKeyboard)); + if (inputState.RealMouse != previousInputState.RealMouse) + events.Legacy_MouseChanged.Raise(new EventArgsMouseStateChanged(previousInputState.RealMouse, inputState.RealMouse, new Point((int)previousInputState.CursorPosition.ScreenPixels.X, (int)previousInputState.CursorPosition.ScreenPixels.Y), new Point((int)inputState.CursorPosition.ScreenPixels.X, (int)inputState.CursorPosition.ScreenPixels.Y))); +#endif } } @@ -640,6 +757,12 @@ namespace StardewModdingAPI.Framework // raise menu events events.MenuChanged.Raise(new MenuChangedEventArgs(was, now)); +#if !SMAPI_3_0_STRICT + if (now != null) + events.Legacy_MenuChanged.Raise(new EventArgsClickableMenuChanged(was, now)); + else + events.Legacy_MenuClosed.Raise(new EventArgsClickableMenuClosed(was)); +#endif GameMenu gameMenu = now as GameMenu; if (gameMenu != null) { @@ -687,6 +810,9 @@ namespace StardewModdingAPI.Framework } events.LocationListChanged.Raise(new LocationListChangedEventArgs(added, removed)); +#if !SMAPI_3_0_STRICT + events.Legacy_LocationsChanged.Raise(new EventArgsLocationsChanged(added, removed)); +#endif } // raise location contents changed @@ -703,6 +829,9 @@ namespace StardewModdingAPI.Framework watcher.BuildingsWatcher.Reset(); events.BuildingListChanged.Raise(new BuildingListChangedEventArgs(location, added, removed)); +#if !SMAPI_3_0_STRICT + events.Legacy_BuildingsChanged.Raise(new EventArgsLocationBuildingsChanged(location, added, removed)); +#endif } // debris changed @@ -747,6 +876,9 @@ namespace StardewModdingAPI.Framework watcher.ObjectsWatcher.Reset(); events.ObjectListChanged.Raise(new ObjectListChangedEventArgs(location, added, removed)); +#if !SMAPI_3_0_STRICT + events.Legacy_ObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, added, removed)); +#endif } // terrain features changed @@ -776,6 +908,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Events: time changed from {was} to {now}.", LogLevel.Trace); events.TimeChanged.Raise(new TimeChangedEventArgs(was, now)); +#if !SMAPI_3_0_STRICT + events.Legacy_TimeOfDayChanged.Raise(new EventArgsIntChanged(was, now)); +#endif } else this.Watchers.TimeWatcher.Reset(); @@ -793,6 +928,9 @@ namespace StardewModdingAPI.Framework GameLocation oldLocation = playerTracker.LocationWatcher.PreviousValue; events.Warped.Raise(new WarpedEventArgs(playerTracker.Player, oldLocation, newLocation)); +#if !SMAPI_3_0_STRICT + events.Legacy_PlayerWarped.Raise(new EventArgsPlayerWarped(oldLocation, newLocation)); +#endif } // raise player leveled up a skill @@ -802,6 +940,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Events: player skill '{pair.Key}' changed from {pair.Value.PreviousValue} to {pair.Value.CurrentValue}.", LogLevel.Trace); events.LevelChanged.Raise(new LevelChangedEventArgs(playerTracker.Player, pair.Key, pair.Value.PreviousValue, pair.Value.CurrentValue)); +#if !SMAPI_3_0_STRICT + events.Legacy_LeveledUp.Raise(new EventArgsLevelUp((EventArgsLevelUp.LevelType)pair.Key, pair.Value.CurrentValue)); +#endif } // raise player inventory changed @@ -811,6 +952,9 @@ namespace StardewModdingAPI.Framework if (this.Monitor.IsVerbose) this.Monitor.Log("Events: player inventory changed.", LogLevel.Trace); events.InventoryChanged.Raise(new InventoryChangedEventArgs(playerTracker.Player, changedItems)); +#if !SMAPI_3_0_STRICT + events.Legacy_InventoryChanged.Raise(new EventArgsInventoryChanged(Game1.player.Items, changedItems)); +#endif } // raise mine level changed @@ -818,6 +962,9 @@ namespace StardewModdingAPI.Framework { if (this.Monitor.IsVerbose) this.Monitor.Log($"Context: mine level changed to {mineLevel}.", LogLevel.Trace); +#if !SMAPI_3_0_STRICT + events.Legacy_MineLevelChanged.Raise(new EventArgsMineLevelChanged(playerTracker.MineLevelWatcher.PreviousValue, mineLevel)); +#endif } } this.Watchers.CurrentPlayerTracker?.Reset(); @@ -864,6 +1011,24 @@ namespace StardewModdingAPI.Framework /********* ** Update events *********/ +#if !SMAPI_3_0_STRICT + events.Legacy_UnvalidatedUpdateTick.Raise(); + if (isFirstTick) + events.Legacy_FirstUpdateTick.Raise(); + events.Legacy_UpdateTick.Raise(); + if (SGame.TicksElapsed % 2 == 0) + events.Legacy_SecondUpdateTick.Raise(); + if (SGame.TicksElapsed % 4 == 0) + events.Legacy_FourthUpdateTick.Raise(); + if (SGame.TicksElapsed % 8 == 0) + events.Legacy_EighthUpdateTick.Raise(); + if (SGame.TicksElapsed % 15 == 0) + events.Legacy_QuarterSecondTick.Raise(); + if (SGame.TicksElapsed % 30 == 0) + events.Legacy_HalfSecondTick.Raise(); + if (SGame.TicksElapsed % 60 == 0) + events.Legacy_OneSecondTick.Raise(); +#endif this.UpdateCrashTimer.Reset(); } catch (Exception ex) @@ -880,7 +1045,7 @@ namespace StardewModdingAPI.Framework /// The method called to draw everything to the screen. /// A snapshot of the game timing state. - public bool Draw(GameTime gameTime) + public bool Draw(GameTime gameTime, RenderTarget2D toBuffer) { Context.IsInDrawLoop = true; try @@ -897,7 +1062,7 @@ namespace StardewModdingAPI.Framework if (!this.DrawCrashTimer.Decrement()) { this.Monitor.ExitGameImmediately("the game crashed when drawing, and SMAPI was unable to recover the game."); - return false; + return true; } // recover sprite batch @@ -913,6 +1078,7 @@ namespace StardewModdingAPI.Framework { this.Monitor.Log($"Could not recover sprite batch state: {innerEx.GetLogSummary()}", LogLevel.Error); } + return true; } Context.IsInDrawLoop = false; return false; @@ -945,8 +1111,7 @@ namespace StardewModdingAPI.Framework IReflectedField> _farmerShadows = this.Reflection.GetField>(Game1.game1, "_farmerShadows"); IReflectedField _debugStringBuilder = this.Reflection.GetField(typeof(Game1), "_debugStringBuilder"); IReflectedField lightingBlend = this.Reflection.GetField(Game1.game1, "lightingBlend"); - _drawActiveClickableMenu.SetValue(false); - _drawHUD.SetValue(false); + IReflectedMethod renderScreenBuffer = this.Reflection.GetMethod(Game1.game1, "renderScreenBuffer", new Type[] { typeof(BlendState) }); IReflectedMethod SpriteBatchBegin = this.Reflection.GetMethod(Game1.game1, "SpriteBatchBegin", new Type[] { typeof(float) }); @@ -972,11 +1137,19 @@ namespace StardewModdingAPI.Framework IReflectedMethod DrawTutorialUI = this.Reflection.GetMethod(Game1.game1, "DrawTutorialUI", new Type[] { }); IReflectedMethod DrawGreenPlacementBounds = this.Reflection.GetMethod(Game1.game1, "DrawGreenPlacementBounds", new Type[] { }); + Matrix matrix; _drawHUD.SetValue(false); _drawActiveClickableMenu.SetValue(false); if (this.Reflection.GetField(typeof(Game1), "_newDayTask").GetValue() != null) { Game1.game1.GraphicsDevice.Clear(bgColor.GetValue()); + if (Game1.showInterDayScroll) + { + matrix = Matrix.CreateScale((float)1f); + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, new Matrix?(matrix)); + SpriteText.drawStringWithScrollCenteredAt(Game1.spriteBatch, Game1.content.LoadString(@"Strings\UI:please_wait"), Game1.game1.GraphicsDevice.Viewport.Width / 2, Game1.game1.GraphicsDevice.Viewport.Height / 2, "", 1f, -1, 0, 0.088f, false); + Game1.spriteBatch.End(); + } } else { @@ -1006,8 +1179,14 @@ namespace StardewModdingAPI.Framework try { events.RenderingActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPreRenderGuiEvent.Raise(); +#endif Game1.activeClickableMenu.draw(Game1.spriteBatch); events.RenderedActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderGuiEvent.Raise(); +#endif } catch (Exception ex) { @@ -1015,6 +1194,9 @@ namespace StardewModdingAPI.Framework Game1.activeClickableMenu.exitThisMenu(); } events.Rendered.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderEvent.Raise(); +#endif _spriteBatchEnd.Invoke(); Game1.RestoreViewportAndZoom(); } @@ -1023,7 +1205,28 @@ namespace StardewModdingAPI.Framework Game1.BackupViewportAndZoom(false); Game1.SetSpriteBatchBeginNextID("A2"); SpriteBatchBegin.Invoke(1f); - Game1.activeClickableMenu.draw(Game1.spriteBatch); + events.Rendering.RaiseEmpty(); + try + { + events.RenderingActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPreRenderGuiEvent.Raise(); +#endif + Game1.activeClickableMenu.draw(Game1.spriteBatch); + events.RenderedActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderGuiEvent.Raise(); +#endif + } + catch (Exception ex) + { + this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself during save. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); + Game1.activeClickableMenu.exitThisMenu(); + } + events.Rendered.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderEvent.Raise(); +#endif _spriteBatchEnd.Invoke(); Game1.RestoreViewportAndZoom(); } @@ -1043,7 +1246,7 @@ namespace StardewModdingAPI.Framework Game1.game1.GraphicsDevice.Clear(bgColor.GetValue()); if (((Game1.activeClickableMenu != null) && Game1.options.showMenuBackground) && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) { - Matrix matrix = Matrix.CreateScale((float)1f); + matrix = Matrix.CreateScale((float)1f); Game1.SetSpriteBatchBeginNextID("C"); _spriteBatchBegin.Invoke(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, new Matrix?(matrix)); events.Rendering.RaiseEmpty(); @@ -1051,8 +1254,15 @@ namespace StardewModdingAPI.Framework { Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); events.RenderingActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPreRenderGuiEvent.Raise(); +#endif + Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); Game1.activeClickableMenu.draw(Game1.spriteBatch); events.RenderedActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderGuiEvent.Raise(); +#endif } catch (Exception ex) { @@ -1060,6 +1270,9 @@ namespace StardewModdingAPI.Framework Game1.activeClickableMenu.exitThisMenu(); } events.Rendered.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderEvent.Raise(); +#endif _spriteBatchEnd.Invoke(); drawOverlays.Invoke(Game1.spriteBatch); renderScreenBuffer.Invoke(BlendState.AlphaBlend); @@ -1105,6 +1318,9 @@ namespace StardewModdingAPI.Framework Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString(@"Strings\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, 0xff, 0)); Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width, 1f), new Vector2(16f, 48f), Color.White); events.Rendered.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderEvent.Raise(); +#endif _spriteBatchEnd.Invoke(); } else if (Game1.currentMinigame != null) @@ -1118,6 +1334,9 @@ namespace StardewModdingAPI.Framework _spriteBatchEnd.Invoke(); } drawOverlays.Invoke(Game1.spriteBatch); +#if !SMAPI_3_0_STRICT + this.RaisePostRender(needsNewBatch: true); +#endif renderScreenBuffer.Invoke(BlendState.AlphaBlend); if (((Game1.currentMinigame is FishingGame) || (Game1.currentMinigame is FantasyBoardGame)) && (Game1.activeClickableMenu != null)) { @@ -1140,8 +1359,14 @@ namespace StardewModdingAPI.Framework try { events.RenderingActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPreRenderGuiEvent.Raise(); +#endif Game1.activeClickableMenu.draw(Game1.spriteBatch); events.RenderedActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderGuiEvent.Raise(); +#endif } catch (Exception ex) { @@ -1150,6 +1375,9 @@ namespace StardewModdingAPI.Framework } } events.Rendered.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderEvent.Raise(); +#endif _spriteBatchEnd.Invoke(); drawOverlays.Invoke(Game1.spriteBatch); Game1.RestoreViewportAndZoom(); @@ -1159,6 +1387,9 @@ namespace StardewModdingAPI.Framework events.Rendering.RaiseEmpty(); DrawLoadingDotDotDot.Invoke(gameTime); events.Rendered.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderEvent.Raise(); +#endif drawOverlays.Invoke(Game1.spriteBatch); renderScreenBuffer.Invoke(BlendState.AlphaBlend); if (Game1.overlayMenu != null) @@ -1559,7 +1790,7 @@ namespace StardewModdingAPI.Framework } else { - text.Append("Game1.player: "); + text.Append("player: "); text.Append((int)(Game1.player.getStandingX() / 0x40)); text.Append(", "); text.Append((int)(Game1.player.getStandingY() / 0x40)); @@ -1585,6 +1816,9 @@ namespace StardewModdingAPI.Framework { _drawActiveClickableMenu.SetValue(true); events.RenderingActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPreRenderGuiEvent.Raise(); +#endif if (Game1.activeClickableMenu is CarpenterMenu) { ((CarpenterMenu)Game1.activeClickableMenu).DrawPlacementSquares(Game1.spriteBatch); @@ -1598,6 +1832,9 @@ namespace StardewModdingAPI.Framework Game1.activeClickableMenu.draw(Game1.spriteBatch); } events.RenderedActiveMenu.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderGuiEvent.Raise(); +#endif } catch (Exception ex) { @@ -1615,6 +1852,9 @@ namespace StardewModdingAPI.Framework SpriteText.drawStringWithScrollBackground(Game1.spriteBatch, s, 0x60, 0x20, "", 1f, -1, 0.088f); } events.Rendered.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderEvent.Raise(); +#endif _spriteBatchEnd.Invoke(); drawOverlays.Invoke(Game1.spriteBatch); renderScreenBuffer.Invoke(BlendState.Opaque); @@ -1624,6 +1864,9 @@ namespace StardewModdingAPI.Framework Game1.SetSpriteBatchBeginNextID("A-C"); SpriteBatchBegin.Invoke(1f); events.RenderingHud.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPreRenderHudEvent.Raise(); +#endif DrawHUD.Invoke(); if (((Game1.currentLocation != null) && !(Game1.activeClickableMenu is GameMenu)) && !(Game1.activeClickableMenu is QuestLog)) { @@ -1631,6 +1874,9 @@ namespace StardewModdingAPI.Framework } DrawAfterMap.Invoke(); events.RenderedHud.RaiseEmpty(); +#if !SMAPI_3_0_STRICT + events.Legacy_OnPostRenderHudEvent.Raise(); +#endif _spriteBatchEnd.Invoke(); if (Game1.tutorialManager != null) { @@ -1674,5 +1920,20 @@ namespace StardewModdingAPI.Framework } } } +#if !SMAPI_3_0_STRICT + /// Raise the if there are any listeners. + /// Whether to create a new sprite batch. + private void RaisePostRender(bool needsNewBatch = false) + { + if (this.Events.Legacy_OnPostRenderEvent.HasListeners()) + { + if (needsNewBatch) + Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); + this.Events.Legacy_OnPostRenderEvent.Raise(); + if (needsNewBatch) + Game1.spriteBatch.End(); + } + } +#endif } } diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index 6869a23b..3eb61b47 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -89,6 +89,24 @@ namespace StardewModdingAPI.Framework this.HostPeer = null; } +#if !SMAPI_3_0_STRICT + /// Handle sync messages from other players and perform other initial sync logic. + public override void UpdateEarly() + { + this.EventManager.Legacy_BeforeMainSync.Raise(); + base.UpdateEarly(); + this.EventManager.Legacy_AfterMainSync.Raise(); + } + + /// Broadcast sync messages to other players and perform other final sync logic. + public override void UpdateLate(bool forceSync = false) + { + this.EventManager.Legacy_BeforeMainBroadcast.Raise(); + base.UpdateLate(forceSync); + this.EventManager.Legacy_AfterMainBroadcast.Raise(); + } +#endif + /// Initialise a client before the game connects to a remote server. /// The client to initialise. public override Client InitClient(Client client) diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs index 6166a295..9d91a140 100644 --- a/src/SMAPI/IMod.cs +++ b/src/SMAPI/IMod.cs @@ -28,6 +28,14 @@ namespace StardewModdingAPI List GetConfigMenuItems(); + bool ApplyForHooks(); + + bool OnCommonHook_Prefix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref object __result); + + void OnCommonHook_Postfix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref bool __state, ref object __result); + bool OnCommonStaticHook_Prefix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref object __result); + void OnCommonStaticHook_Postfix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref bool __state, ref object __result); + /// Get an API that other mods can access. This is always called after . object GetApi(); } diff --git a/src/SMAPI/IModHelper.cs b/src/SMAPI/IModHelper.cs index cd746e06..0220b4f7 100644 --- a/src/SMAPI/IModHelper.cs +++ b/src/SMAPI/IModHelper.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using StardewModdingAPI.Events; namespace StardewModdingAPI @@ -56,5 +58,41 @@ namespace StardewModdingAPI /// The config class type. /// The config settings to save. void WriteConfig(TConfig config) where TConfig : class, new(); + +#if !SMAPI_3_0_STRICT + /**** + ** Generic JSON files + ****/ + /// Read a JSON file. + /// The model type. + /// The file path relative to the mod directory. + /// Returns the deserialised model, or null if the file doesn't exist or is empty. + [Obsolete("Use " + nameof(IModHelper.Data) + "." + nameof(IDataHelper.ReadJsonFile) + " instead")] + TModel ReadJsonFile(string path) where TModel : class; + + /// Save to a JSON file. + /// The model type. + /// The file path relative to the mod directory. + /// The model to save. + [Obsolete("Use " + nameof(IModHelper.Data) + "." + nameof(IDataHelper.WriteJsonFile) + " instead")] + void WriteJsonFile(string path, TModel model) where TModel : class; + + /**** + ** Content packs + ****/ + /// Manually create a transitional content pack to support pre-SMAPI content packs. This provides a way to access legacy content packs using the SMAPI content pack APIs, but the content pack will not be visible in the log or validated by SMAPI. + /// The absolute directory path containing the content pack files. + /// The content pack's unique ID. + /// The content pack name. + /// The content pack description. + /// The content pack author's name. + /// The content pack version. + [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ContentPacks) + "." + nameof(IContentPackHelper.CreateTemporary) + " instead")] + IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version); + + /// Get all content packs loaded for this mod. + [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ContentPacks) + "." + nameof(IContentPackHelper.GetOwned) + " instead")] + IEnumerable GetContentPacks(); +#endif } } diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 72410d41..90a6d111 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -6,6 +6,7 @@ using StardewModdingAPI.Framework.ModLoading.Finders; using StardewModdingAPI.Framework.ModLoading.Rewriters; using StardewModdingAPI.Framework.RewriteFacades; using StardewValley; +using StardewValley.Menus; namespace StardewModdingAPI.Metadata { @@ -36,6 +37,36 @@ namespace StardewModdingAPI.Metadata // rewrite for Stardew Valley 1.3 yield return new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize); + yield return new TypeReferenceRewriter("System.Collections.Generic.IList`1", typeof(List)); + + yield return new TypeReferenceRewriter("System.Collections.Generic.IList`1", typeof(List)); + + yield return new FieldToPropertyRewriter(typeof(Game1), "player"); + + yield return new FieldToPropertyRewriter(typeof(Game1), "currentLocation"); + + yield return new FieldToPropertyRewriter(typeof(Character), "currentLocation"); + + yield return new FieldToPropertyRewriter(typeof(Farmer), "currentLocation"); + + yield return new FieldToPropertyRewriter(typeof(Game1), "gameMode"); + + yield return new FieldToPropertyRewriter(typeof(Game1), "currentMinigame"); + + yield return new FieldToPropertyRewriter(typeof(Game1), "activeClickableMenu"); + + yield return new FieldToPropertyRewriter(typeof(Game1), "stats"); + + yield return new FieldToPropertyRewriter(typeof(Game1), "isRaining"); + + yield return new FieldToPropertyRewriter(typeof(Game1), "isDebrisWeather"); + + yield return new MethodParentRewriter(typeof(IClickableMenu), typeof(IClickableMenuMethods), onlyIfPlatformChanged: true); + + yield return new MethodParentRewriter(typeof(Game1), typeof(Game1Methods), onlyIfPlatformChanged: true); + + yield return new MethodParentRewriter(typeof(Farmer), typeof(FarmerMethods), onlyIfPlatformChanged: true); + /**** ** detect mod issues ****/ @@ -54,6 +85,9 @@ namespace StardewModdingAPI.Metadata yield return new EventFinder(typeof(ISpecialisedEvents).FullName, nameof(ISpecialisedEvents.UnvalidatedUpdateTicked), InstructionHandleResult.DetectedUnvalidatedUpdateTick); yield return new EventFinder(typeof(ISpecialisedEvents).FullName, nameof(ISpecialisedEvents.UnvalidatedUpdateTicking), InstructionHandleResult.DetectedUnvalidatedUpdateTick); +#if !SMAPI_3_0_STRICT + yield return new EventFinder(typeof(SpecialisedEvents).FullName, nameof(SpecialisedEvents.UnvalidatedUpdateTick), InstructionHandleResult.DetectedUnvalidatedUpdateTick); +#endif /**** ** detect paranoid issues ****/ diff --git a/src/SMAPI/Mod.cs b/src/SMAPI/Mod.cs index d47932a0..b3cda205 100644 --- a/src/SMAPI/Mod.cs +++ b/src/SMAPI/Mod.cs @@ -31,6 +31,27 @@ namespace StardewModdingAPI { return new List(); } + public virtual bool ApplyForHooks() + { + return false; + } + + public virtual bool OnCommonHook_Prefix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref object __result) + { + return true; + } + + public virtual void OnCommonHook_Postfix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref bool __state, ref object __result) + { + } + public virtual bool OnCommonStaticHook_Prefix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref object __result) + { + return true; + } + public virtual void OnCommonStaticHook_Postfix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref bool __state, ref object __result) + { + } + /// Get an API that other mods can access. This is always called after . public virtual object GetApi() => null; diff --git a/src/SMAPI/SemanticVersion.cs b/src/SMAPI/SemanticVersion.cs index acdc92f8..ec2d9e40 100644 --- a/src/SMAPI/SemanticVersion.cs +++ b/src/SMAPI/SemanticVersion.cs @@ -1,5 +1,6 @@ using System; using Newtonsoft.Json; +using StardewModdingAPI.Framework; namespace StardewModdingAPI { @@ -25,6 +26,19 @@ namespace StardewModdingAPI /// The patch version for backwards-compatible bug fixes. public int PatchVersion => this.Version.PatchVersion; +#if !SMAPI_3_0_STRICT + /// An optional build tag. + [Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")] + public string Build + { + get + { + SCore.DeprecationManager?.Warn($"{nameof(ISemanticVersion)}.{nameof(ISemanticVersion.Build)}", "2.8", DeprecationLevel.PendingRemoval); + return this.Version.PrereleaseTag; + } + } +#endif + /// An optional prerelease tag. public string PrereleaseTag => this.Version.PrereleaseTag; diff --git a/src/SMAPI/Toolkit/CoreInterfaces/ISemanticVersion.cs b/src/SMAPI/Toolkit/CoreInterfaces/ISemanticVersion.cs index 4a61f9ae..0a6e5758 100644 --- a/src/SMAPI/Toolkit/CoreInterfaces/ISemanticVersion.cs +++ b/src/SMAPI/Toolkit/CoreInterfaces/ISemanticVersion.cs @@ -17,6 +17,12 @@ namespace StardewModdingAPI /// The patch version for backwards-compatible bug fixes. int PatchVersion { get; } +#if !SMAPI_3_0_STRICT + /// An optional build tag. + [Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")] + string Build { get; } +#endif + /// An optional prerelease tag. string PrereleaseTag { get; } diff --git a/src/SMAPI/Toolkit/SemanticVersion.cs b/src/SMAPI/Toolkit/SemanticVersion.cs index 78b4c007..ba9ca6c6 100644 --- a/src/SMAPI/Toolkit/SemanticVersion.cs +++ b/src/SMAPI/Toolkit/SemanticVersion.cs @@ -42,6 +42,15 @@ namespace StardewModdingAPI.Toolkit /// An optional prerelease tag. public string PrereleaseTag { get; } +#if !SMAPI_3_0_STRICT + /// An optional prerelease tag. + [Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")] + public string Build => this.PrereleaseTag; + + /// Whether the version was parsed from the legacy object format. + public bool IsLegacyFormat { get; } +#endif + /********* ** Public methods @@ -51,12 +60,20 @@ namespace StardewModdingAPI.Toolkit /// The minor version incremented for backwards-compatible changes. /// The patch version for backwards-compatible fixes. /// An optional prerelease tag. - public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null) + /// Whether the version was parsed from the legacy object format. + public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null +#if !SMAPI_3_0_STRICT + , bool isLegacyFormat = false +#endif + ) { this.MajorVersion = major; this.MinorVersion = minor; this.PatchVersion = patch; this.PrereleaseTag = this.GetNormalisedTag(prereleaseTag); +#if !SMAPI_3_0_STRICT + this.IsLegacyFormat = isLegacyFormat; +#endif this.AssertValid(); } diff --git a/src/SMAPI/Toolkit/Serialisation/Converters/SemanticVersionConverter.cs b/src/SMAPI/Toolkit/Serialisation/Converters/SemanticVersionConverter.cs index c50de4db..aca06849 100644 --- a/src/SMAPI/Toolkit/Serialisation/Converters/SemanticVersionConverter.cs +++ b/src/SMAPI/Toolkit/Serialisation/Converters/SemanticVersionConverter.cs @@ -67,8 +67,20 @@ namespace StardewModdingAPI.Toolkit.Serialisation.Converters int minor = obj.ValueIgnoreCase(nameof(ISemanticVersion.MinorVersion)); int patch = obj.ValueIgnoreCase(nameof(ISemanticVersion.PatchVersion)); string prereleaseTag = obj.ValueIgnoreCase(nameof(ISemanticVersion.PrereleaseTag)); +#if !SMAPI_3_0_STRICT + if (string.IsNullOrWhiteSpace(prereleaseTag)) + { + prereleaseTag = obj.ValueIgnoreCase("Build"); + if (prereleaseTag == "0") + prereleaseTag = null; // '0' from incorrect examples in old SMAPI documentation + } +#endif - return new SemanticVersion(major, minor, patch, prereleaseTag); + return new SemanticVersion(major, minor, patch, prereleaseTag +#if !SMAPI_3_0_STRICT + , isLegacyFormat: true +#endif + ); } /// Read a JSON string.