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); } }