commit 6bfadb86e5be9af407caecc2d773379163ebfed5 Author: Nathaniel Cosford Date: Wed Jun 4 15:20:40 2025 +0930 Upload from upload_mods.ps1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f69e0d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,80 @@ +# Build and Object Folders +bin/ +obj/ + +# Nuget packages directory +packages/ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.log +*.vspscc +*.vssscc +.builds + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help +UpgradeLog*.XML + +# Lightswitch +_Pvt_Extensions +GeneratedArtifacts +*.xap +ModelManifest.xml + +#Backup file +*.bak + +#zzzili +v15/ + diff --git a/CheckStackSize.bat b/CheckStackSize.bat new file mode 100644 index 0000000..b0ebb15 --- /dev/null +++ b/CheckStackSize.bat @@ -0,0 +1,16 @@ +echo off +set size=4194304 +set client=7DaysToDie.exe +set dedi=7DaysToDieServer.exe +if exist ..\..\%client% ( + .\editbin.exe /stack:%size% ..\..\%client% + echo Setting max stack size for %client% to %size% +) ^ +else if exist ..\..\%dedi% ( + .\editbin.exe /stack:%size% ..\..\%dedi% + echo Setting max stack size for %dedi% to %size% +) ^ +else ( + echo 7 days to die executable not found! +) +pause \ No newline at end of file diff --git a/CustomParticleLoader.csproj b/CustomParticleLoader.csproj new file mode 100644 index 0000000..61d4fa8 --- /dev/null +++ b/CustomParticleLoader.csproj @@ -0,0 +1,98 @@ + + + + + Debug + AnyCPU + {12A59F51-6655-49D7-BE95-F3E86A173979} + Library + Properties + CustomParticleLoader + CustomParticleLoader + v4.8 + 512 + true + + + true + full + false + .\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + .\ + TRACE + prompt + 4 + + + + ..\0_TFP_Harmony\0Harmony.dll + False + + + ..\..\7DaysToDie_Data\Managed\Assembly-CSharp.dll + False + + + ..\..\7DaysToDie_Data\Managed\LogLibrary.dll + False + + + + + + + + + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.AnimationModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.AssetBundleModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.CoreModule.dll + False + + + ..\..\7DaysToDie_Data\Managed\UnityEngine.PhysicsModule.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CustomParticleLoader.dll b/CustomParticleLoader.dll new file mode 100644 index 0000000..666f606 Binary files /dev/null and b/CustomParticleLoader.dll differ diff --git a/CustomParticleLoader.pdb b/CustomParticleLoader.pdb new file mode 100644 index 0000000..212e03c Binary files /dev/null and b/CustomParticleLoader.pdb differ diff --git a/Harmony/Init.cs b/Harmony/Init.cs new file mode 100644 index 0000000..f89a46e --- /dev/null +++ b/Harmony/Init.cs @@ -0,0 +1,17 @@ +using System.Reflection; + +public class CustomParticleEffectLoaderInit : IModApi +{ + private static bool inited = false; + public void InitMod(Mod _modInstance) + { + if (inited) + return; + inited = true; + Log.Out(" Loading Patch: " + GetType()); + ModEvents.GameAwake.RegisterHandler(CustomExplosionManager.CreatePropertyParsers); + var harmony = new HarmonyLib.Harmony(GetType().ToString()); + harmony.PatchAll(Assembly.GetExecutingAssembly()); + } +} + diff --git a/Harmony/Patches.cs b/Harmony/Patches.cs new file mode 100644 index 0000000..f109c75 --- /dev/null +++ b/Harmony/Patches.cs @@ -0,0 +1,411 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using UnityEngine; + +[HarmonyPatch(typeof(GameManager))] +internal class ExplosionEffectPatch +{ + public static void SendCustomExplosionPackage(int _clrIdx, Vector3 _center, Vector3i _blockpos, Quaternion _rotation, ExplosionData _explosionData, int _playerId, ItemValue _itemValueExplosive, List _explosionChanges, GameObject result) + { + uint id = CustomExplosionManager.LastInitializedComponent != null ? CustomExplosionManager.LastInitializedComponent.CurrentExplosionParams._explId : uint.MaxValue; + SingletonMonoBehaviour.Instance.SendPackage(NetPackageManager.GetPackage().Setup(_clrIdx, _center, _blockpos, _rotation, _explosionData, _playerId, id, _itemValueExplosive, _explosionChanges, result), true); + } + + private struct ExplodeState + { + public bool useCustom; + public int playerLayer; + } + + [HarmonyPatch(nameof(GameManager.explode))] + [HarmonyPrefix] + private static bool explode_Prefix(GameManager __instance, int _clrIdx, Vector3 _worldPos, Vector3i _blockPos, Quaternion _rotation, ExplosionData _explosionData, int _entityId, ItemValue _itemValueExplosionSource, out ExplodeState __state) + { + __state = new ExplodeState() + { + useCustom = false, + playerLayer = -1, + }; + int index = _explosionData.ParticleIndex; + //Log.Out(_worldPos.ToString() + _blockPos.ToString()); + //Log.Out("Particle index:" + index.ToString()); + if (index >= WorldStaticData.prefabExplosions.Length) + { + //Log.Out("Retrieving particle index:" + index.ToString()); + bool flag = CustomExplosionManager.GetCustomParticleComponents(index, out ExplosionComponent components); + if (flag && components != null) + { + //Log.Out("Retrieved particle index:" + index.ToString()); + //_explosionData = components.BoundExplosionData; + ExplosionValue value = new ExplosionValue() + { + Component = components, + CurrentExplosionParams = new ExplosionParams(_clrIdx, _worldPos, _blockPos, _rotation, _explosionData, _entityId, CustomExplosionManager.NextExplosionIndex++), + CurrentItemValue = _itemValueExplosionSource + }; + //Log.Out("params:" + _clrIdx + _blockPos + _playerId + _rotation + _worldPos + _explosionData.ParticleIndex); + //Log.Out("params:" + components.CurrentExplosionParams._clrIdx + components.CurrentExplosionParams._blockPos + components.CurrentExplosionParams._playerId + components.CurrentExplosionParams._rotation + components.CurrentExplosionParams._worldPos + components.CurrentExplosionParams._explosionData.ParticleIndex); + CustomExplosionManager.PushLastInitComponent(value); + __state.useCustom = true; + } +#if DEBUG + else + Log.Warning("Failed to retrieve particle on server! Index:" + index.ToString()); +#endif + } + EntityPlayerLocal player = __instance.World.GetPrimaryPlayer(); + if (player != null) + { + __state.playerLayer = player.GetModelLayer(); + if (__state.playerLayer != 24) + { + player.SetModelLayer(24, false); + } + else + { + __state.playerLayer = -1; + } + } + return true; + } + + [HarmonyPatch("explode")] + [HarmonyTranspiler] + private static IEnumerable explode_Transpiler(IEnumerable instructions) + { + var codes = new List(instructions); + MethodInfo mtd_sendpackage = AccessTools.Method(typeof(ConnectionManager), nameof(ConnectionManager.SendPackage), new Type[] { typeof(NetPackage), typeof(bool), typeof(int), typeof(int), typeof(int), typeof(Vector3?), typeof(int) }); + + for (int i = 0, totali = codes.Count; i < totali; i++) + { + if (codes[i].opcode == OpCodes.Callvirt && codes[i].Calls(mtd_sendpackage)) + { + codes.InsertRange(i + 1, new CodeInstruction[] + { + new CodeInstruction(OpCodes.Ldarg_1), + new CodeInstruction(OpCodes.Ldarg_2), + new CodeInstruction(OpCodes.Ldarg_3), + new CodeInstruction(OpCodes.Ldarg, 4), + new CodeInstruction(OpCodes.Ldarg, 5), + new CodeInstruction(OpCodes.Ldarg, 6), + new CodeInstruction(OpCodes.Ldarg, 7), + new CodeInstruction(OpCodes.Ldarg_0), + CodeInstruction.LoadField(typeof(GameManager), nameof(GameManager.tempExplPositions)), + new CodeInstruction(OpCodes.Ldloc_1), + CodeInstruction.Call(typeof(ExplosionEffectPatch), nameof(SendCustomExplosionPackage)) + }); + codes.RemoveRange(i - 26, 27); + break; + } + } + + return codes; + } + + [HarmonyPatch(nameof(GameManager.explode))] + [HarmonyPostfix] + private static void explode_Postfix(GameManager __instance, ExplodeState __state) + { + if (__state.playerLayer >= 0) + { + __instance.World.GetPrimaryPlayer().SetModelLayer(__state.playerLayer, false); + } + if (!__state.useCustom) + return; + CustomExplosionManager.PopLastInitComponent(); + } + + [HarmonyPatch(nameof(GameManager.ExplosionClient))] + [HarmonyPostfix] + private static void ExplosionClient_Postfix(GameManager __instance, ref GameObject __result, Vector3 _center, Quaternion _rotation, int _index, int _blastPower, float _blastRadius) + { + if (__result != null || __instance.World == null) + return; + + ExplosionValue components = CustomExplosionManager.LastInitializedComponent; + //sorcery uses index over 20 to trigger explosion without particle and spawn visual particle on its own, + //such usage could potentially break the chained explosion stack, thus this additional check is added. + //invalid explosion component is not pushed to the stack, if index param does not match index on the stack, + //then the param must be invalid, skip particle creation. + if (components != null && components.CurrentExplosionParams._explosionData.ParticleIndex == _index) + { + ApplyExplosionForce.Explode(_center, (float)_blastPower, _blastRadius); + __result = CustomExplosionManager.InitializeParticle(components.Component, _center - Origin.position, _rotation); + } +#if DEBUG + else + Log.Warning("Failed to retrieve particle on client! Index:" + _index.ToString()); +#endif + } + + [HarmonyPatch(nameof(GameManager.SaveAndCleanupWorld))] + [HarmonyPostfix] + private static void SaveAndCleanupWorld_Postfix() + { + CustomExplosionManager.OnCleanUp(); + } + + /* + [HarmonyPatch(nameof(GameManager.PlayerSpawnedInWorld))] + [HarmonyPostfix] + private static void PlayerSpawnedInWorld_Postfix(ClientInfo _cInfo, RespawnType _respawnReason) + { + if(SingletonMonoBehaviour.Instance.IsServer && _cInfo != null && _cInfo.entityId != -1 && (_respawnReason == RespawnType.EnterMultiplayer || _respawnReason == RespawnType.JoinMultiplayer)) + CustomParticleEffectLoader.OnClientConnected(_cInfo); + } + */ +} + +[HarmonyPatch(typeof(NetEntityDistribution), nameof(NetEntityDistribution.Add), new Type[] { typeof(Entity) })] +internal class ExplosionSyncPatch +{ + private static void Postfix(Entity _e) + { + if (_e is EntityPlayer) + CustomExplosionManager.OnClientConnected(SingletonMonoBehaviour.Instance.Clients.ForEntityId(_e.entityId)); + } +} + +[HarmonyPatch] +internal class ExplosionParsePatch +{ + [HarmonyPatch(typeof(ItemClass), nameof(ItemClass.Init))] + [HarmonyPrefix] + private static bool Init_ItemClass_Prefix(ItemClass __instance) + { + if (CustomExplosionManager.parseParticleData(__instance.Properties, out var component)) + { + component.BoundItemClass = __instance; + } + return true; + } + + [HarmonyPatch(typeof(ItemAction), nameof(ItemAction.ReadFrom))] + [HarmonyPrefix] + private static bool ReadFrom_ItemAction_Prefix(ItemAction __instance, ref DynamicProperties _props) + { + if (CustomExplosionManager.parseParticleData(_props, out var component)) + { + component.BoundItemClass = __instance.item; + } + return true; + } + + [HarmonyPatch(typeof(Block), nameof(Block.Init))] + [HarmonyPrefix] + private static bool Init_Block_Prefix(Block __instance) + { + CustomExplosionManager.parseParticleData(__instance.Properties, out _); + return true; + } + + [HarmonyPatch(typeof(EntityClass), nameof(EntityClass.Init))] + [HarmonyPrefix] + private static bool Init_EntityClass_Prefix(EntityClass __instance) + { + CustomExplosionManager.parseParticleData(__instance.Properties, out _); + return true; + } +} + +[HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.OnEntityDeath))] +internal class EntityExplosionPatch +{ + private static void Postfix(EntityAlive __instance) + { + if (__instance.isEntityRemote || __instance is EntityCar || __instance is EntityZombieCop) + return; + + ref ExplosionData explosion = ref EntityClass.list[__instance.entityClass].explosionData; + if (explosion.ParticleIndex > 0) + GameManager.Instance.ExplosionServer(0, __instance.GetPosition(), World.worldToBlockPos(__instance.GetPosition()), Quaternion.identity, explosion, __instance.entityId, 0f, false, null); + } +} + +[HarmonyPatch(typeof(Explosion), nameof(Explosion.AttackEntites))] +internal class ExplosionAttackPatch +{ + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + var codes = new List(instructions); + + FieldInfo fld_hit_entities = AccessTools.Field(typeof(Explosion), nameof(Explosion.hitEntities)); + Type entityDict = fld_hit_entities.FieldType; + + LocalBuilder lbd_entity_dict = generator.DeclareLocal(entityDict); + + codes.InsertRange(0, new CodeInstruction[] + { + new CodeInstruction(OpCodes.Newobj, entityDict.GetConstructor(new Type[]{ })), + new CodeInstruction(OpCodes.Stloc_S, lbd_entity_dict) + }); + + for (int i = 0; i < codes.Count; ++i) + { + if (codes[i].opcode == OpCodes.Ldsfld && codes[i].LoadsField(fld_hit_entities)) + { + codes[i].opcode = OpCodes.Ldloc_S; + codes[i].operand = lbd_entity_dict; + } + } + + return codes; + } +} + +[HarmonyPatch(typeof(ExplosionData))] +internal class ExplosionDataPatch +{ + [HarmonyPatch(nameof(ExplosionData.Write))] + [HarmonyPrefix] + private static bool Prefix_ExplosionData_Write(BinaryWriter _bw, ref ExplosionData __instance) + { + _bw.Write((ushort)__instance.ParticleIndex); + _bw.Write((short)(__instance.Duration * 10f)); + _bw.Write((short)(__instance.BlockRadius * 20f)); + _bw.Write((short)__instance.EntityRadius); + _bw.Write((short)__instance.BlastPower); + _bw.Write(__instance.BlockDamage); + _bw.Write(__instance.EntityDamage); + _bw.Write(__instance.BlockTags); + _bw.Write(__instance.IgnoreHeatMap); + if (__instance.damageMultiplier != null) + { + _bw.Write(true); + __instance.damageMultiplier.Write(_bw); + } + else + { + _bw.Write(false); + } + if (__instance.BuffActions != null) + { + _bw.Write((byte)__instance.BuffActions.Count); + for (int i = 0; i < __instance.BuffActions.Count; i++) + { + _bw.Write(__instance.BuffActions[i]); + } + } + else + { + _bw.Write(0); + } + return false; + } + + [HarmonyPatch(nameof(ExplosionData.Read))] + [HarmonyPrefix] + private static bool Prefix_ExplosionData_Read(BinaryReader _br, ref ExplosionData __instance) + { + __instance.ParticleIndex = _br.ReadUInt16(); + __instance.Duration = _br.ReadInt16() * 0.1f; + __instance.BlockRadius = _br.ReadInt16() * 0.05f; + __instance.EntityRadius = _br.ReadInt16(); + __instance.BlastPower = _br.ReadInt16(); + __instance.BlockDamage = _br.ReadSingle(); + __instance.EntityDamage = _br.ReadSingle(); + __instance.BlockTags = _br.ReadString(); + __instance.IgnoreHeatMap = _br.ReadBoolean(); + if (_br.ReadBoolean()) + { + __instance.damageMultiplier = new DamageMultiplier(); + __instance.damageMultiplier.Read(_br); + } + + int num = _br.ReadByte(); + if (num > 0) + { + __instance.BuffActions = new List(); + for (int i = 0; i < num; i++) + { + __instance.BuffActions.Add(_br.ReadString()); + } + } + else + { + __instance.BuffActions = null; + } + return false; + } +} + +[HarmonyPatch(typeof(ItemHasTags), nameof(ItemHasTags.IsValid))] +internal class ItemHasTagsPatch : HarmonyPatch +{ + private static bool Prefix(MinEventParams _params, ref bool __result) + { + if (_params.ItemValue == null) + { + __result = false; + return false; + } + return true; + } +} + +//MinEventParams workarounds +[HarmonyPatch(typeof(ItemActionRanged), "fireShot")] +internal class ItemActionRangedFireShotPatch +{ + private static IEnumerable Transpiler(IEnumerable instructions) + { + var codes = new List(instructions); + + var fld_ranged_tag = AccessTools.Field(typeof(ItemActionAttack), nameof(ItemActionAttack.RangedTag)); + var fld_params = AccessTools.Field(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)); + + for (int i = 0; i < codes.Count; i++) + { + if (codes[i].LoadsField(fld_ranged_tag)) + { + if (!codes[i + 3].LoadsField(fld_params)) + { + codes.InsertRange(i + 2, new CodeInstruction[] + { + new CodeInstruction(OpCodes.Ldloc_1), + CodeInstruction.LoadField(typeof(EntityAlive), nameof(EntityAlive.MinEventContext)), + new CodeInstruction(OpCodes.Dup), + new CodeInstruction(OpCodes.Ldloc, 10), + CodeInstruction.LoadField(typeof(WorldRayHitInfo), nameof(WorldRayHitInfo.hit)), + CodeInstruction.LoadField(typeof(HitInfoDetails), nameof(HitInfoDetails.pos)), + CodeInstruction.StoreField(typeof(MinEventParams), nameof(MinEventParams.Position)), + new CodeInstruction(OpCodes.Ldloc_1), + CodeInstruction.Call(typeof(EntityAlive), nameof(EntityAlive.GetPosition)), + CodeInstruction.StoreField(typeof(MinEventParams), nameof(MinEventParams.StartPosition)) + }); + } + break; + } + } + + return codes; + } +} + +//[HarmonyPatch(typeof(MinEventActionExplode), nameof(MinEventActionExplode.Execute))] +//class TempPatch +//{ +// private static IEnumerable Transpiler(IEnumerable instructions) +// { +// var codes = instructions.ToList(); +// var mtd_explode_server = AccessTools.Method(typeof(GameManager), nameof(GameManager.ExplosionServer)); +// for (int i = 0; i < codes.Count; i++) +// { +// if (codes[i].Calls(mtd_explode_server)) +// { +// codes.InsertRange(i + 1, new CodeInstruction[] +// { +// new CodeInstruction(OpCodes.Ldloca_S, 2), +// new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ExplosionData), nameof(ExplosionData.ToByteArray))), +// new CodeInstruction(OpCodes.Pop) +// }); +// break; +// } +// } +// return codes; +// } +//} \ No newline at end of file diff --git a/ModInfo.xml b/ModInfo.xml new file mode 100644 index 0000000..76bc84a --- /dev/null +++ b/ModInfo.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a04ede4 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("CustomParticleLoader")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CustomParticleLoader")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("12a59f51-6655-49d7-be95-f3e86a173979")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Scripts/Core/CustomExplosionManager.cs b/Scripts/Core/CustomExplosionManager.cs new file mode 100644 index 0000000..19f7c04 --- /dev/null +++ b/Scripts/Core/CustomExplosionManager.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +public static class CustomExplosionManager +{ + private static Dictionary hash_paths = new Dictionary(); + private static Dictionary hash_components = new Dictionary(); + private static Dictionary hash_assets = new Dictionary(); + private static HashSet hash_initialized = new HashSet(); + private static List list_parsers = new List(); + private static Stack last_init_components = new Stack(); + + public static event Action ClientConnected; + + public static event Action HandleClientInfo; + + public static event Action CleanUp; + + public static ExplosionValue LastInitializedComponent + { + get => last_init_components.Count > 0 ? last_init_components.Peek() : null; + } + + public static uint NextExplosionIndex { get; set; } = 0; + + internal static void OnCleanUp() + { + Log.Out("Custom Explosion Manager cleanup..."); + destroyAllParticles(); + NextExplosionIndex = 0; + CleanUp?.Invoke(); + } + + internal static void OnClientConnected(ClientInfo client) + { + var handler = ClientConnected; + if (handler != null) + { + uint count = (uint)ClientConnected.GetInvocationList().Length; + MemoryStream memoryStream = new MemoryStream(); + using (PooledBinaryWriter _bw = MemoryPools.poolBinaryWriter.AllocSync(false)) + { + _bw.SetBaseStream(memoryStream); + _bw.Write(count); + handler(_bw); + if (HandleClientInfo != null) + HandleClientInfo(client); + } + byte[] data = memoryStream.ToArray(); + client.SendPackage(NetPackageManager.GetPackage().Setup(data)); + } + } + + internal static void CreatePropertyParsers() + { + var assemblies = ModManager.GetLoadedAssemblies(); + string iname = nameof(IExplosionPropertyParser); + + foreach (var assembly in assemblies) + { + var types = assembly.GetTypes(); + foreach (var type in types) + { + if (type.GetInterface(iname) != null) + { + var parser = Activator.CreateInstance(type) as IExplosionPropertyParser; + list_parsers.Add(parser); + Log.Out("Found custom explosion property parser: " + type.Name); + } + } + } + } + + internal static GameObject InitializeParticle(ExplosionComponent component, Vector3 position, Quaternion rotation) + { + GameObject __result = UnityEngine.Object.Instantiate(component.Particle, position, rotation); + __result.AddComponent(); + if (component.TemporaryObjectType != null) + __result.AddComponent(component.TemporaryObjectType); + if (component.ExplosionDamageAreaType != null) + __result.AddComponent(component.ExplosionDamageAreaType); + if (component.AudioPlayerType != null) + { + AudioPlayer audio_script = __result.AddComponent(component.AudioPlayerType) as AudioPlayer; + if (component.SoundName != null) + audio_script.soundName = component.SoundName; + if (component.AudioDuration >= 0) + audio_script.duration = component.AudioDuration; + } + if (component.List_CustomTypes.Count > 0) + foreach (Type customtype in component.List_CustomTypes) + if (customtype != null) + __result.AddComponent(customtype); + AutoRemove remove_script = __result.AddComponent(); + CustomExplosionManager.addInitializedParticle(__result); + return __result; + } + + internal static void PushLastInitComponent(ExplosionValue component) + { + last_init_components.Push(component); + } + + internal static void PopLastInitComponent() + { + if (last_init_components.Count > 0) + last_init_components.Pop(); + } + + private static bool LoadParticleEffect(string fullpath, ExplosionData data, out ExplosionComponent component, string sound_name = null, float duration_audio = -1, List CustomScriptList = null) + { + component = null; + fullpath = fullpath.Trim(); + if (!parsePathString(fullpath, out string path, out string assetname)) + return false; + //check if asset is loaded + string path_asset = path + "?" + assetname; + bool flag = hash_assets.TryGetValue(path_asset, out GameObject obj); + if (!flag) + { + //load asset + string path_bundle = ModManager.PatchModPathString(path).TrimStart('#'); + Log.Out("Bundle path: " + path_bundle); + AssetBundleManager.Instance.LoadAssetBundle(path_bundle); + obj = AssetBundleManager.Instance.Get(path_bundle, assetname); + if (obj == null) + Log.Error("Failed to load asset " + assetname); + else + hash_assets.Add(path_asset, obj); + } + + if (obj == null) + { + Log.Error("Particle not loaded:" + path_asset); + return false; + } + + //pair particle with scripts + if (hash_components.Remove(fullpath)) + Log.Out("Particle data already exists:" + fullpath + ", now overwriting"); + component = new ExplosionComponent(obj, sound_name, duration_audio, data, CustomScriptList); + hash_components.Add(fullpath, component); + return true; + } + + //this should get a unique index for each particle + public static int getHashCode(string str) + { + int value = (PlatformIndependentHash.StringToUInt16(str)); + + while (hash_paths.TryGetValue(value, out string path)) + { + if (path == str) + break; + if (value > Int16.MaxValue || (value >= 0 && value < WorldStaticData.prefabExplosions.Length)) + value = WorldStaticData.prefabExplosions.Length; + else + value++; + } + return value; + } + + private static bool parsePathString(string fullpath, out string path, out string assetname) + { + //get path to asset + path = null; + assetname = null; + if (fullpath == null) + { + Log.Error("Null fullpath parameter:" + fullpath); + return false; + } + int index_path = fullpath.IndexOf('?'); + if (index_path <= 0) + { + Log.Error("Particle path does not specify the asset name! fullpath:" + fullpath); + return false; + } + path = fullpath.Substring(0, index_path); + int index_postfix = fullpath.IndexOf('$'); + if (index_postfix <= 0) + assetname = fullpath.Substring(index_path + 1); + else + assetname = fullpath.Substring(index_path + 1, index_postfix - index_path - 1); + return true; + } + + private static void getTypeListFromString(string str, out List list_types) + { + list_types = new List(); + if (str == null || str == string.Empty) + return; + string[] array = str.Split(new char[] { '$' }); + foreach (string typename in array) + { + if (typename != null) + { + Type type = Type.GetType(typename.Trim()); + if (type != null) + { + if (!list_types.Contains(type)) + list_types.Add(type); + else + Log.Out("duplicated script type detected: " + type.AssemblyQualifiedName); + } + else + Log.Warning("CustomScriptType not found:" + typename); + } + } + } + + public static bool parseParticleData(DynamicProperties _props, out ExplosionComponent component) + { + string str_index = null; + component = null; + _props.ParseString("Explosion.ParticleIndex", ref str_index); + if (str_index != null && str_index.StartsWith("#")) + { + Log.Out("Original path:" + str_index); + int hashed_index = getHashCode(str_index); + _props.Values["Explosion.ParticleIndex"] = hashed_index.ToString(); + Log.Out("Hashed index:" + _props.Values["Explosion.ParticleIndex"]); + if (!hash_paths.ContainsKey(hashed_index)) + hash_paths.Add(hashed_index, str_index); + bool overwrite = false; + _props.ParseBool("Explosion.Overwrite", ref overwrite); + if (overwrite || !GetCustomParticleComponents(hashed_index, out _)) + { + string sound_name = null; + _props.ParseString("Explosion.AudioName", ref sound_name); + float duration_audio = -1; + _props.ParseFloat("Explosion.AudioDuration", ref duration_audio); + bool sync = false; + _props.ParseBool("Explosion.SyncOnConnect", ref sync); + bool observe = false; + _props.ParseBool("Explosion.IsChunkObserver", ref observe); + getTypeListFromString(_props.Values["Explosion.CustomScriptTypes"], out List list_customtypes); + bool flag = LoadParticleEffect(str_index, new ExplosionData(_props), out component, sound_name, duration_audio, list_customtypes); + if (flag && component != null) + { + component.SyncOnConnect = sync; + foreach (var parser in list_parsers) + if (list_customtypes.Contains(parser.MatchScriptType()) && parser.ParseProperty(_props, out var property)) + component.AddCustomProperty(parser.Name(), property); + } + return flag; + } + } + return false; + } + + internal static void addInitializedParticle(GameObject obj) + { + hash_initialized.Add(obj); + //Log.Out("Particle initialized:" + obj.name); + } + + internal static void removeInitializedParticle(GameObject obj) + { + hash_initialized.Remove(obj); + //Log.Out("Particle removed on destroy:" + obj.name); + } + + internal static void destroyAllParticles() + { + foreach (GameObject obj in hash_initialized) + { + Log.Out("Active particle destroyed on disconnect:" + obj.name); + GameObject.Destroy(obj); + } + hash_initialized.Clear(); + /* + foreach (var pair in hash_assets) + { + Log.Out("Loaded particle destroyed on disconnect:" + pair.Value.name); + GameObject.Destroy(pair.Value); + } + hash_assets.Clear(); + hash_components.Clear(); + Log.Out("Loaded particle data cleared on disconnect."); + */ + last_init_components.Clear(); + } + + public static bool GetCustomParticleComponents(int index, out ExplosionComponent component) + { + component = null; + if (!hash_paths.TryGetValue(index, out string fullpath) || fullpath == null) + return false; + return hash_components.TryGetValue(fullpath, out component); + } +} \ No newline at end of file diff --git a/Scripts/Core/ExplosionComponent.cs b/Scripts/Core/ExplosionComponent.cs new file mode 100644 index 0000000..6cc2737 --- /dev/null +++ b/Scripts/Core/ExplosionComponent.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +public struct ExplosionParams +{ + public ExplosionParams(int _clrIdx, Vector3 _worldPos, Vector3i _blockPos, Quaternion _rotation, ExplosionData _explosionData, int _playerId, uint _explId) + { + this._clrIdx = _clrIdx; + this._worldPos = _worldPos; + this._blockPos = _blockPos; + this._rotation = _rotation; + this._explosionData = _explosionData; + this._playerId = _playerId; + this._explId = _explId; + } + + public ExplosionParams(byte[] _explosionParamsAsArr) + { + _clrIdx = 0; + _worldPos = Vector3.zero; + _blockPos = Vector3i.zero; + _rotation = Quaternion.identity; + _explosionData = default(ExplosionData); + _playerId = -1; + _explId = uint.MaxValue; + using (PooledBinaryReader pooledBinaryReader = MemoryPools.poolBinaryReader.AllocSync(false)) + { + pooledBinaryReader.SetBaseStream(new MemoryStream(_explosionParamsAsArr)); + read(pooledBinaryReader); + } + } + + public void read(PooledBinaryReader _br) + { + _clrIdx = (int)_br.ReadUInt16(); + _worldPos = StreamUtils.ReadVector3(_br); + _blockPos = StreamUtils.ReadVector3i(_br); + _rotation = StreamUtils.ReadQuaterion(_br); + _explosionData.Read(_br); + _playerId = _br.ReadInt32(); + _explId = _br.ReadUInt32(); + } + + public void write(PooledBinaryWriter _bw) + { + _bw.Write((ushort)_clrIdx); + StreamUtils.Write(_bw, _worldPos); + StreamUtils.Write(_bw, _blockPos); + StreamUtils.Write(_bw, _rotation); + _explosionData.Write(_bw); + _bw.Write(_playerId); + _bw.Write(_explId); + } + + public byte[] ToByteArray() + { + MemoryStream memoryStream = new MemoryStream(); + using (PooledBinaryWriter pooledBinaryWriter = MemoryPools.poolBinaryWriter.AllocSync(false)) + { + pooledBinaryWriter.SetBaseStream(memoryStream); + write(pooledBinaryWriter); + } + return memoryStream.ToArray(); + } + + public int _clrIdx; + public Vector3 _worldPos; + public Vector3i _blockPos; + public Quaternion _rotation; + public ExplosionData _explosionData; + public int _playerId; + public uint _explId; +} +public class ExplosionComponent +{ + public ExplosionComponent(GameObject obj, string sound_name, float duration_audio, ExplosionData data, List CustomScriptTypes) + { + this.obj = obj; + this.list_custom = new List(); + foreach (Type type in CustomScriptTypes) + { + if (type == null) + continue; + if ((type.IsSubclassOf(typeof(TemporaryObject)) || type == typeof(TemporaryObject))) + this.TempObjType = type; + else if ((type.IsSubclassOf(typeof(ExplosionDamageArea)) || type == typeof(ExplosionDamageArea))) + this.ExplAreaType = type; + else if ((type.IsSubclassOf(typeof(AudioPlayer)) || type == typeof(AudioPlayer))) + this.AudioType = AudioPlayerType; + else + this.list_custom.Add(type); + } + + this.sound_name = sound_name; + this.duration_audio = duration_audio; + this.hash_custom_properties = new Dictionary(); + this.data = data; + if (this.sound_name != null && this.AudioPlayerType == null) + this.AudioType = typeof(AudioPlayer); + } + + private GameObject obj; + private Type TempObjType = null; + private Type ExplAreaType = null; + private Type AudioType = null; + private List list_custom; + private float duration_audio; + private string sound_name; + private ExplosionData data; + private Dictionary hash_custom_properties; + + public bool TryGetCustomProperty(string name, out object value) + { + return hash_custom_properties.TryGetValue(name, out value); + } + + internal void AddCustomProperty(string name, object value) + { + if(hash_custom_properties.Remove(name)) + Log.Out("Custom explosion component property already exists, overwriting: " + name); + hash_custom_properties.Add(name, value); + } + + public GameObject Particle { get => obj; } + public Type TemporaryObjectType { get => TempObjType; } + public Type ExplosionDamageAreaType { get => ExplAreaType; } + public Type AudioPlayerType { get => AudioType; } + public List List_CustomTypes { get => list_custom; } + public float AudioDuration{ get => duration_audio; } + public string SoundName { get => sound_name; } + public ExplosionData BoundExplosionData { get => data; } + public ItemClass BoundItemClass { get; set; } + public bool SyncOnConnect { get; set; } = false; +} + +public class ExplosionValue +{ + public ExplosionComponent Component { get; set; } + public ExplosionParams CurrentExplosionParams { get; set; } + public ItemValue CurrentItemValue { get; set; } +} \ No newline at end of file diff --git a/Scripts/Core/IExplosionProperty.cs b/Scripts/Core/IExplosionProperty.cs new file mode 100644 index 0000000..85a821e --- /dev/null +++ b/Scripts/Core/IExplosionProperty.cs @@ -0,0 +1,6 @@ +public interface IExplosionPropertyParser +{ + bool ParseProperty(DynamicProperties _props, out object property); + System.Type MatchScriptType(); + string Name(); +} diff --git a/Scripts/MinEventActions/MinEventActionRangedExplosion.cs b/Scripts/MinEventActions/MinEventActionRangedExplosion.cs new file mode 100644 index 0000000..facf56c --- /dev/null +++ b/Scripts/MinEventActions/MinEventActionRangedExplosion.cs @@ -0,0 +1,103 @@ +using System.Xml; +using System.Xml.Linq; +using UnityEngine; + +public class MinEventActionRangedExplosion : MinEventActionBase +{ + private ExplosionData _explosionData; + private int itemType = -1; + private ExplosionComponent _explosionComponent; + //private float delay = 0; + private bool _initialized = false; + private bool _useCustomParticle = false; + private int customParticleIndex; + private ItemValue ammoItem; + + public override void Execute(MinEventParams _params) + { + bool hasEntity = _params.Self != null; + //int layer = 0; + // if (hasEntity) + // { + // layer = _params.Self.GetModelLayer(); + // _params.Self.SetModelLayer(24, false); + // } + GameManager.Instance.ExplosionServer(0, _params.Position, World.worldToBlockPos(_params.Position), hasEntity ? _params.Self.qrotation : Quaternion.identity, _useCustomParticle ? _explosionComponent.BoundExplosionData : _explosionData, hasEntity ? _params.Self.entityId : -1, Delay, false, ammoItem ?? _params.ItemValue); + //if (hasEntity) + //{ + // _params.Self.SetModelLayer(layer, false); + //} + } + + public override bool CanExecute(MinEventTypes _eventType, MinEventParams _params) + { + if (!base.CanExecute(_eventType, _params)) + return false; + + if(!_initialized || !_useCustomParticle) + { + if (_useCustomParticle) + { + if (!CustomExplosionManager.GetCustomParticleComponents(customParticleIndex, out _explosionComponent)) + return false; + if(_explosionComponent.BoundItemClass != null) + ammoItem = new ItemValue(_explosionComponent.BoundItemClass.Id, false); + } + else + { + if (_params.ItemValue == null) + return false; + if (_params.ItemValue.type == itemType) + return true; + ItemClass itemClass = _params.ItemValue.ItemClass; + string particleIndex = null; + itemClass.Properties.ParseString("Explosion.ParticleIndex", ref particleIndex); + if (string.IsNullOrEmpty(particleIndex)) + return false; + + if (int.TryParse(particleIndex, out int index)) + { + _explosionData = new ExplosionData(itemClass.Properties); + itemType = itemClass.Id; + ammoItem = new ItemValue(itemType, false); + } + else if (CustomExplosionManager.GetCustomParticleComponents(CustomExplosionManager.getHashCode(particleIndex), out _explosionComponent)) + { + itemType = itemClass.Id; + if(_explosionComponent.BoundItemClass != null) + ammoItem = new ItemValue(_explosionComponent.BoundItemClass.Id, false); + } + else + { + return false; + } + } + + _initialized = true; + } + + return true; + } + + public override bool ParseXmlAttribute(XAttribute _attribute) + { + bool flag = base.ParseXmlAttribute(_attribute); + if (!flag) + { + string name = _attribute.Name.LocalName; + switch(name) + { + case "particle_index": + customParticleIndex = CustomExplosionManager.getHashCode(_attribute.Value); + _useCustomParticle = true; + flag = true; + break; + //case "delay": + // float.TryParse(_attribute.Value, out delay); + // flag = true; + // break; + } + } + return flag; + } +} diff --git a/Scripts/MonoBehaviours/AutoRemove.cs b/Scripts/MonoBehaviours/AutoRemove.cs new file mode 100644 index 0000000..a245179 --- /dev/null +++ b/Scripts/MonoBehaviours/AutoRemove.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +public class AutoRemove : TrackedBehaviourBase +{ + public float lifetime = -1; + + protected override void Awake() + { + ExplosionValue value = CustomExplosionManager.LastInitializedComponent; + lifetime = value.CurrentExplosionParams._explosionData.Duration; + if(lifetime > 0) + syncOnConnect = value.Component.SyncOnConnect; + base.Awake(); + if (lifetime > 0) + Destroy(gameObject, lifetime); + } + + private void Update() + { + if(lifetime > 0) + lifetime -= Time.deltaTime; + } + + protected override void OnDestroy() + { + base.OnDestroy(); + CustomExplosionManager.removeInitializedParticle(gameObject); + } + + protected override void OnClientConnected(PooledBinaryWriter _bw) + { + _bw.Write(lifetime); + } + + protected override void OnConnectedToServer(PooledBinaryReader _br) + { + lifetime = _br.ReadSingle(); + if (lifetime > 0) + Destroy(gameObject, lifetime); + } +} + diff --git a/Scripts/MonoBehaviours/NetSyncHelper.cs b/Scripts/MonoBehaviours/NetSyncHelper.cs new file mode 100644 index 0000000..c66c111 --- /dev/null +++ b/Scripts/MonoBehaviours/NetSyncHelper.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +public class NetSyncHelper : MonoBehaviour +{ + public event Action ClientConnected + { + add { list_connect_server_handlers.Add(value); } + remove { list_connect_server_handlers.Remove(value); } + } + public event Action ConnectedToServer + { + add { list_connect_client_handlers.Add(value); } + remove { list_connect_client_handlers.Remove(value); } + } + public event Action ExplosionServerInit + { + add { list_init_server_handlers.Add(value); } + remove { list_init_server_handlers.Remove(value); } + } + public event Action ExplosionClientInit + { + add { list_init_client_handlers.Add(value); } + remove { list_init_client_handlers.Remove(value); } + } + private List> list_connect_server_handlers = new List>(); + private List> list_connect_client_handlers = new List>(); + private List> list_init_server_handlers = new List>(); + private List> list_init_client_handlers = new List>(); + public ExplosionParams explParams; + public ItemValue explValue; + public uint explId; + private static Dictionary hash_helpers = new Dictionary(); + + static NetSyncHelper() + { + CustomExplosionManager.CleanUp += hash_helpers.Clear; + } + + void Awake() + { + hash_helpers.Add((explId = CustomExplosionManager.LastInitializedComponent.CurrentExplosionParams._explId), this); + if (SingletonMonoBehaviour.Instance.IsServer && CustomExplosionManager.LastInitializedComponent.Component.SyncOnConnect) + { + explParams = CustomExplosionManager.LastInitializedComponent.CurrentExplosionParams; + explValue = CustomExplosionManager.LastInitializedComponent.CurrentItemValue?.Clone(); + CustomExplosionManager.ClientConnected += OnClientConnected; + } + } + + public static bool TryGetValue(uint id, out NetSyncHelper helper) + { + return hash_helpers.TryGetValue(id, out helper); + } + + void OnDestroy() + { + CustomExplosionManager.ClientConnected -= OnClientConnected; + hash_helpers.Remove(explId); + } + + void OnClientConnected(PooledBinaryWriter _bw) + { + explParams._worldPos = transform.position + Origin.position; + explParams._rotation = transform.rotation; + byte[] array = explParams.ToByteArray(); + _bw.Write((ushort)array.Length); + _bw.Write(array); + _bw.Write(explValue != null); + if (explValue != null) + { + explValue.Write(_bw); + } + foreach (var handler in list_connect_server_handlers) + handler(_bw); + } + + public void OnConnectedToServer(PooledBinaryReader _br) + { + foreach (var handler in list_connect_client_handlers) + handler(_br); + } + + public void OnExplosionServerInit(PooledBinaryWriter _bw) + { + foreach (var handler in list_init_server_handlers) + handler(_bw); + } + + public void OnExplosionClientInit(PooledBinaryReader _br) + { + foreach (var handler in list_init_client_handlers) + handler(_br); + } +} + diff --git a/Scripts/MonoBehaviours/ReverseTrackedBehaviour.cs b/Scripts/MonoBehaviours/ReverseTrackedBehaviour.cs new file mode 100644 index 0000000..5277504 --- /dev/null +++ b/Scripts/MonoBehaviours/ReverseTrackedBehaviour.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using UnityEngine; + +public class ReverseTrackedBehaviour : TrackedBehaviourBase where T : ReverseTrackedBehaviour +{ + protected static Dictionary> hash_instances = new Dictionary>(); + + static ReverseTrackedBehaviour() + { + CustomExplosionManager.CleanUp += hash_instances.Clear; + } + protected override void Awake() + { + track = true; + base.Awake(); + } + protected override void addRef() + { + if (!hash_instances.ContainsKey(key)) + hash_instances.Add(key, new Dictionary()); + hash_instances[key].Add(explId, (T)this); + } + protected override void removeRef() + { + var dict = hash_instances[key]; + if (dict != null) + { + dict.Remove(explId); + if (dict.Count <= 0) + hash_instances.Remove(key); + } + } + + public static bool TryGetValue(uint id, object key, out T controller) + { + controller = null; + if (hash_instances.TryGetValue(key, out var dict)) + return dict.TryGetValue(id, out controller); + return false; + } +} + diff --git a/Scripts/MonoBehaviours/Timer.cs b/Scripts/MonoBehaviours/Timer.cs new file mode 100644 index 0000000..983e99e --- /dev/null +++ b/Scripts/MonoBehaviours/Timer.cs @@ -0,0 +1,124 @@ +using UnityEngine; +using UnityEngine.Events; + +/// +/// 计时器 +/// ZhangYu 2018-04-08 +/// code from: https://segmentfault.com/a/1190000015325310 +/// +public class Timer : MonoBehaviour +{ + // delay in second + public float delay = 0; + // interval in second + public float interval = 1; + // repeat count + public int repeatCount = 1; + // automatically start + public bool autoStart = false; + // automatically destroy + public bool autoDestory = true; + // current time + public float currentTime = 0; + // current count + public int currentCount = 0; + // event when interval reached + public UnityEvent onIntervalEvent; + // event when timer completed + public UnityEvent onCompleteEvent; + // callback delegate + public delegate void TimerCallback(Timer timer); + // last interval time + private float lastTime = 0; + // interval callback + private TimerCallback onIntervalCall; + // complete callback + private TimerCallback onCompleteCall; + + private void Start() + { + enabled = autoStart; + } + + private void FixedUpdate() + { + if (!enabled) return; + addInterval(Time.deltaTime); + } + + /// add interval + private void addInterval(float deltaTime) + { + currentTime += deltaTime; + if (currentTime < delay) return; + if (currentTime - lastTime >= interval) + { + currentCount++; + lastTime = currentTime; + if (repeatCount <= 0) + { + // repeate forever + if (currentCount == int.MaxValue) reset(); + if (onIntervalCall != null) onIntervalCall(this); + if (onIntervalEvent != null) onIntervalEvent.Invoke(); + } + else + { + if (currentCount < repeatCount) + { + if (onIntervalCall != null) onIntervalCall(this); + if (onIntervalEvent != null) onIntervalEvent.Invoke(); + } + else + { + stop(); + if (onCompleteCall != null) onCompleteCall(this); + if (onCompleteEvent != null) onCompleteEvent.Invoke(); + if (autoDestory && !enabled) Destroy(this); + } + } + } + } + + public void start() + { + enabled = autoStart = true; + } + + public void start(float time, TimerCallback onComplete) + { + start(time, 1, null, onComplete); + } + + public void start(float interval, int repeatCount, TimerCallback onComplete) + { + start(interval, repeatCount, null, onComplete); + } + + public void start(float interval, int repeatCount, TimerCallback onInterval, TimerCallback onComplete) + { + this.interval = interval; + this.repeatCount = repeatCount; + onIntervalCall = onInterval; + onCompleteCall = onComplete; + reset(); + enabled = autoStart = true; + } + + public void stop() + { + enabled = autoStart = false; + } + + public void reset() + { + lastTime = currentTime = currentCount = 0; + } + + public void restart() + { + reset(); + start(); + } + +} \ No newline at end of file diff --git a/Scripts/MonoBehaviours/TrackedBehaviour.cs b/Scripts/MonoBehaviours/TrackedBehaviour.cs new file mode 100644 index 0000000..a53665b --- /dev/null +++ b/Scripts/MonoBehaviours/TrackedBehaviour.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using UnityEngine; + +public class TrackedBehaviour : TrackedBehaviourBase where T : TrackedBehaviour +{ + protected static Dictionary> hash_instances = new Dictionary>(); + + static TrackedBehaviour() + { + CustomExplosionManager.CleanUp += hash_instances.Clear; + } + + protected override void Awake() + { + track = true; + base.Awake(); + } + protected override void addRef() + { + if (!hash_instances.ContainsKey(explId)) + hash_instances.Add(explId, new Dictionary()); + hash_instances[explId].Add(key, (T)this); + } + + protected override void removeRef() + { + var dict = hash_instances[explId]; + if (dict != null) + { + dict.Remove(key); + if (dict.Count <= 0) + hash_instances.Remove(explId); + } + } + + public static bool TryGetValue(uint id, object key, out T controller) + { + controller = null; + if (hash_instances.TryGetValue(id, out var dict)) + return dict.TryGetValue(key, out controller); + return false; + } +} \ No newline at end of file diff --git a/Scripts/MonoBehaviours/TrackedBehaviourBase.cs b/Scripts/MonoBehaviours/TrackedBehaviourBase.cs new file mode 100644 index 0000000..df3244e --- /dev/null +++ b/Scripts/MonoBehaviours/TrackedBehaviourBase.cs @@ -0,0 +1,96 @@ +using UnityEngine; + +public class TrackedBehaviourBase : MonoBehaviour +{ + protected uint explId; + protected bool isServer; + protected object key = null; + protected bool syncOnConnect = false; + protected bool syncOnInit = false; + protected bool track = false; + protected bool handleClientInfo = false; + protected NetSyncHelper helper = null; + + protected virtual void Awake() + { + explId = CustomExplosionManager.LastInitializedComponent.CurrentExplosionParams._explId; + if (track) + { + if (key == null) + { + Log.Error("TrackedBehaviourBase: key is not initialized before addRef!"); + return; + } + addRef(); + } + bool flag = NetSyncHelper.TryGetValue(explId, out helper); + isServer = SingletonMonoBehaviour.Instance.IsServer; + if (flag) + { + if (syncOnConnect) + { + if (isServer) + helper.ClientConnected += OnClientConnected; + else + helper.ConnectedToServer += OnConnectedToServer; + } + if (syncOnInit) + { + if (isServer) + helper.ExplosionServerInit += OnExplosionInitServer; + else + helper.ExplosionClientInit += OnExplosionInitClient; + } + }else + Log.Error("NetSyncHelper not initialized: explId: " + explId); + if (handleClientInfo) + CustomExplosionManager.HandleClientInfo += OnHandleClientInfo; + } + + protected virtual void OnDestroy() + { + if (helper != null) + { + if (syncOnConnect) + { + if (isServer) + helper.ClientConnected -= OnClientConnected; + else + helper.ConnectedToServer -= OnConnectedToServer; + } + if (syncOnInit) + { + if (isServer) + helper.ExplosionServerInit -= OnExplosionInitServer; + else + helper.ExplosionClientInit -= OnExplosionInitClient; + } + } + if (handleClientInfo) + CustomExplosionManager.HandleClientInfo -= OnHandleClientInfo; + if (track) + removeRef(); + } + protected virtual void addRef() + { + } + protected virtual void removeRef() + { + } + protected virtual void OnClientConnected(PooledBinaryWriter _bw) + { + } + protected virtual void OnConnectedToServer(PooledBinaryReader _br) + { + } + protected virtual void OnExplosionInitServer(PooledBinaryWriter _bw) + { + } + protected virtual void OnExplosionInitClient(PooledBinaryReader _br) + { + } + protected virtual void OnHandleClientInfo(ClientInfo info) + { + } +} + diff --git a/Scripts/NetPackages/NetPackageExplosionParams.cs b/Scripts/NetPackages/NetPackageExplosionParams.cs new file mode 100644 index 0000000..c21ff40 --- /dev/null +++ b/Scripts/NetPackages/NetPackageExplosionParams.cs @@ -0,0 +1,158 @@ +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +public class NetPackageExplosionParams : NetPackage +{ + public NetPackageExplosionParams Setup(int _clrIdx, Vector3 _worldPos, Vector3i _blockPos, Quaternion _rotation, ExplosionData _explosionData, int _entityId, uint _explId, ItemValue _itemValueExplosive, List explosionChanges, GameObject particle) + { + clrIdx = _clrIdx; + worldPos = _worldPos; + blockPos = _blockPos; + rotation = _rotation; + explosionData = _explosionData; + entityId = _entityId; + explosionId = _explId; + itemValueExplosive = null; + if(_itemValueExplosive != null) + itemValueExplosive = _itemValueExplosive.Clone(); + this.explosionChanges.Clear(); + this.explosionChanges.AddRange(explosionChanges); + if(particle != null) + { + if(particle.TryGetComponent(out var helper)) + { + MemoryStream memoryStream = new MemoryStream(); + using (PooledBinaryWriter _bw = MemoryPools.poolBinaryWriter.AllocSync(false)) + { + _bw.SetBaseStream(memoryStream); + helper.OnExplosionServerInit(_bw); + } + dataToSync = memoryStream.ToArray(); + } + } + return this; + } + + public override void read(PooledBinaryReader _br) + { + clrIdx = (int)_br.ReadUInt16(); + worldPos = StreamUtils.ReadVector3(_br); + blockPos = StreamUtils.ReadVector3i(_br); + rotation = StreamUtils.ReadQuaterion(_br); + int count = (int)_br.ReadUInt16(); + explosionData = new ExplosionData(_br.ReadBytes(count)); + entityId = _br.ReadInt32(); + explosionId = _br.ReadUInt32(); + int num = (int)_br.ReadUInt16(); + explosionChanges = new List(num); + for (int i = 0; i < num; i++) + { + BlockChangeInfo blockChangeInfo = new BlockChangeInfo(); + blockChangeInfo.Read(_br); + explosionChanges.Add(blockChangeInfo); + } + if (_br.ReadBoolean()) + { + itemValueExplosive = new ItemValue(); + itemValueExplosive.Read(_br); + } + ushort bytes = _br.ReadUInt16(); + if (bytes > 0) + dataToSync = _br.ReadBytes(bytes); + } + + public override void write(PooledBinaryWriter _bw) + { + base.write(_bw); + _bw.Write((ushort)clrIdx); + StreamUtils.Write(_bw, worldPos); + StreamUtils.Write(_bw, blockPos); + StreamUtils.Write(_bw, rotation); + byte[] array = explosionData.ToByteArray(); + _bw.Write((ushort)array.Length); + _bw.Write(array); + _bw.Write(entityId); + _bw.Write(explosionId); + _bw.Write((ushort)explosionChanges.Count); + for (int i = 0; i < explosionChanges.Count; i++) + { + explosionChanges[i].Write(_bw); + } + _bw.Write(itemValueExplosive != null); + if (itemValueExplosive != null) + { + itemValueExplosive.Write(_bw); + } + if (dataToSync != null && dataToSync.Length > 0) + { + _bw.Write((ushort)dataToSync.Length); + _bw.Write(dataToSync); + } + else + _bw.Write((ushort)0); + } + + public override void ProcessPackage(World _world, GameManager _callbacks) + { + if (_world == null) + { + return; + } + bool isCustom = false; + if(explosionData.ParticleIndex >= WorldStaticData.prefabExplosions.Length) + { + isCustom = CustomExplosionManager.GetCustomParticleComponents(explosionData.ParticleIndex, out ExplosionComponent component) && component != null; + if (isCustom) + { + ExplosionValue value = new ExplosionValue() + { + Component = component, + CurrentExplosionParams = new ExplosionParams(clrIdx, worldPos, blockPos, rotation, explosionData, entityId, explosionId), + CurrentItemValue = itemValueExplosive?.Clone() + }; + CustomExplosionManager.PushLastInitComponent(value); + } + } + + GameObject result = _callbacks.ExplosionClient(clrIdx, worldPos, rotation, explosionData.ParticleIndex, explosionData.BlastPower, (float)explosionData.EntityRadius, (float)explosionData.BlockDamage, entityId, explosionChanges); + if (isCustom) + { + NetSyncHelper helper = result?.GetComponent(); + if (helper != null && dataToSync != null) + { + using (PooledBinaryReader _br = MemoryPools.poolBinaryReader.AllocSync(false)) + { + _br.SetBaseStream(new MemoryStream(dataToSync)); + helper.OnExplosionClientInit(_br); + } + } + CustomExplosionManager.PopLastInitComponent(); + } + } + + public override NetPackageDirection PackageDirection + { + get + { + return NetPackageDirection.ToClient; + } + } + + public override int GetLength() + { + return 80 + explosionChanges.Count * 30; + } + + private int clrIdx; + private Vector3 worldPos; + private Vector3i blockPos; + private Quaternion rotation; + private ExplosionData explosionData; + private int entityId; + private uint explosionId; + private ItemValue itemValueExplosive; + private List explosionChanges = new List(); + private byte[] dataToSync = null; +} + diff --git a/Scripts/NetPackages/NetPackageExplosionSyncOnConnect.cs b/Scripts/NetPackages/NetPackageExplosionSyncOnConnect.cs new file mode 100644 index 0000000..63a252c --- /dev/null +++ b/Scripts/NetPackages/NetPackageExplosionSyncOnConnect.cs @@ -0,0 +1,77 @@ +using System.IO; +using UnityEngine; +using XMLData.Item; + +public class NetPackageExplosionSyncOnConnect : NetPackage +{ + public NetPackageExplosionSyncOnConnect Setup(byte[] data) + { + this.data = data != null ? data : new byte[0]; + return this; + } + + public override void read(PooledBinaryReader _br) + { + int bytes = _br.ReadInt32(); + data = _br.ReadBytes(bytes); + } + + public override void write(PooledBinaryWriter _bw) + { + base.write(_bw); + _bw.Write(data.Length); + _bw.Write(data); + } + + public override void ProcessPackage(World _world, GameManager _callbacks) + { + if (_world == null) + { + return; + } + + using (PooledBinaryReader _br = MemoryPools.poolBinaryReader.AllocSync(false)) + { + _br.SetBaseStream(new MemoryStream(data)); + uint count = _br.ReadUInt32(); + for(int i = 0; i < count; ++i) + { + int bytes = (int)_br.ReadUInt16(); + ExplosionParams explParams = new ExplosionParams(_br.ReadBytes(bytes)); + ItemValue explValue = null; + if (_br.ReadBoolean()) + { + explValue = new ItemValue(); + explValue.Read(_br); + } + CustomExplosionManager.GetCustomParticleComponents(explParams._explosionData.ParticleIndex, out ExplosionComponent component); + + ExplosionValue value = new ExplosionValue() + { + Component = component, + CurrentExplosionParams = explParams, + CurrentItemValue = explValue?.Clone() + }; + CustomExplosionManager.PushLastInitComponent(value); + GameObject obj = CustomExplosionManager.InitializeParticle(component, explParams._worldPos - Origin.position, explParams._rotation); + obj.GetComponent().OnConnectedToServer(_br); + } + CustomExplosionManager.PopLastInitComponent(); + } + } + + public override NetPackageDirection PackageDirection + { + get + { + return NetPackageDirection.ToClient; + } + } + + public override int GetLength() + { + return 8 + data.Length; + } + + private byte[] data; +} diff --git a/Scripts/Utilities/PlatformIndependentHash.cs b/Scripts/Utilities/PlatformIndependentHash.cs new file mode 100644 index 0000000..9b395e4 --- /dev/null +++ b/Scripts/Utilities/PlatformIndependentHash.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +public static class PlatformIndependentHash +{ + public static int StringToInt32(string str) + { + byte[] encoded = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(str)); + return BitConverter.ToInt32(encoded, 0); + } + + public static UInt16 StringToUInt16(string str) + { + byte[] encoded = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(str)); + return (UInt16)BitConverter.ToUInt32(encoded, 0); + } +} + diff --git a/Scripts/Utilities/StreamUtilsCompressed.cs b/Scripts/Utilities/StreamUtilsCompressed.cs new file mode 100644 index 0000000..8b6db8c --- /dev/null +++ b/Scripts/Utilities/StreamUtilsCompressed.cs @@ -0,0 +1,31 @@ +using System.IO; +using UnityEngine; + +public static class StreamUtilsCompressed +{ + public static void Write(BinaryWriter _bw, Vector3 vec) + { + _bw.Write(Mathf.FloatToHalf(vec.x)); + _bw.Write(Mathf.FloatToHalf(vec.y)); + _bw.Write(Mathf.FloatToHalf(vec.z)); + } + + public static Vector3 ReadHalfVector3(BinaryReader _br) + { + return new Vector3(Mathf.HalfToFloat(_br.ReadUInt16()), Mathf.HalfToFloat(_br.ReadUInt16()), Mathf.HalfToFloat(_br.ReadUInt16())); + } + + public static void Write(BinaryWriter _bw, Quaternion rot) + { + _bw.Write(Mathf.FloatToHalf(rot.x)); + _bw.Write(Mathf.FloatToHalf(rot.y)); + _bw.Write(Mathf.FloatToHalf(rot.z)); + _bw.Write(Mathf.FloatToHalf(rot.w)); + } + + public static Quaternion ReadHalfQuaternion(BinaryReader _br) + { + return new Quaternion(Mathf.HalfToFloat(_br.ReadUInt16()), Mathf.HalfToFloat(_br.ReadUInt16()), Mathf.HalfToFloat(_br.ReadUInt16()), Mathf.HalfToFloat(_br.ReadUInt16())); + } +} + diff --git a/editbin.exe b/editbin.exe new file mode 100644 index 0000000..611aabc Binary files /dev/null and b/editbin.exe differ diff --git a/link.exe b/link.exe new file mode 100644 index 0000000..f476590 Binary files /dev/null and b/link.exe differ