Пример #1
0
        /// <summary>
        /// Register a new EverestModule (mod) dynamically. Invokes LoadSettings and Load.
        /// </summary>
        /// <param name="module">Mod to register.</param>
        public static void Register(this EverestModule module)
        {
            lock (_Modules) {
                _Modules.Add(module);
            }

            LuaLoader.Precache(module.GetType().Assembly);

            bool newStrawberriesRegistered = false;

            foreach (Type type in module.GetType().Assembly.GetTypesSafe())
            {
                // Search for all entities marked with the CustomEntityAttribute.
                foreach (CustomEntityAttribute attrib in type.GetCustomAttributes <CustomEntityAttribute>())
                {
                    foreach (string idFull in attrib.IDs)
                    {
                        string   id;
                        string   genName;
                        string[] split = idFull.Split('=');

                        if (split.Length == 1)
                        {
                            id      = split[0];
                            genName = "Load";
                        }
                        else if (split.Length == 2)
                        {
                            id      = split[0];
                            genName = split[1];
                        }
                        else
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                            continue;
                        }

                        id      = id.Trim();
                        genName = genName.Trim();

                        patch_Level.EntityLoader loader = null;

                        ConstructorInfo ctor;
                        MethodInfo      gen;

                        gen = type.GetMethod(genName, new Type[] { typeof(Level), typeof(LevelData), typeof(Vector2), typeof(EntityData) });
                        if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(Entity)))
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)gen.Invoke(null, new object[] { level, levelData, offset, entityData });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2), typeof(EntityID) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset, new EntityID(levelData.Name, entityData.ID) });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(_EmptyTypeArray);
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(_EmptyObjectArray);
                            goto RegisterEntityLoader;
                        }

RegisterEntityLoader:
                        if (loader == null)
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Found custom entity without suitable constructor / {genName}(Level, LevelData, Vector2, EntityData): {id} ({type.FullName})");
                            continue;
                        }
                        patch_Level.EntityLoaders[id] = loader;
                    }
                }
                // Register with the StrawberryRegistry all entities marked with RegisterStrawberryAttribute.
                foreach (RegisterStrawberryAttribute attrib in type.GetCustomAttributes <RegisterStrawberryAttribute>())
                {
                    List <string> names = new List <string>();
                    foreach (CustomEntityAttribute nameAttrib in type.GetCustomAttributes <CustomEntityAttribute>())
                    {
                        foreach (string idFull in nameAttrib.IDs)
                        {
                            string[] split = idFull.Split('=');
                            if (split.Length == 0)
                            {
                                Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                                continue;
                            }
                            names.Add(split[0]);
                        }
                    }
                    if (names.Count == 0)
                    {
                        goto NoDefinedBerryNames; // no customnames? skip out on registering berry
                    }
                    foreach (string name in names)
                    {
                        StrawberryRegistry.Register(type, name, attrib.isTracked, attrib.blocksNormalCollection);
                        newStrawberriesRegistered = true;
                    }
                }
NoDefinedBerryNames:
                ;

                // Search for all Entities marked with the CustomEventAttribute.
                foreach (CustomEventAttribute attrib in type.GetCustomAttributes <CustomEventAttribute>())
                {
                    foreach (string idFull in attrib.IDs)
                    {
                        string   id;
                        string   genName;
                        string[] split = idFull.Split('=');

                        if (split.Length == 1)
                        {
                            id      = split[0];
                            genName = "Load";
                        }
                        else if (split.Length == 2)
                        {
                            id      = split[0];
                            genName = split[1];
                        }
                        else
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom cutscene ID elements: {idFull} ({type.FullName})");
                            continue;
                        }

                        id      = id.Trim();
                        genName = genName.Trim();

                        patch_EventTrigger.CutsceneLoader loader = null;

                        ConstructorInfo ctor;
                        MethodInfo      gen;

                        gen = type.GetMethod(genName, new Type[] { typeof(EventTrigger), typeof(Player), typeof(string) });
                        if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(Entity)))
                        {
                            loader = (trigger, player, eventID) => (Entity)gen.Invoke(null, new object[] { trigger, player, eventID });
                            goto RegisterCutsceneLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EventTrigger), typeof(Player), typeof(string) });
                        if (ctor != null)
                        {
                            loader = (trigger, player, eventID) => (Entity)ctor.Invoke(new object[] { trigger, player, eventID });
                            goto RegisterCutsceneLoader;
                        }

                        ctor = type.GetConstructor(_EmptyTypeArray);
                        if (ctor != null)
                        {
                            loader = (trigger, player, eventID) => (Entity)ctor.Invoke(_EmptyObjectArray);
                            goto RegisterCutsceneLoader;
                        }

RegisterCutsceneLoader:
                        if (loader == null)
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Found custom cutscene without suitable constructor / {genName}(EventTrigger, Player, string): {id} ({type.FullName})");
                            continue;
                        }
                        patch_EventTrigger.CutsceneLoaders[id] = loader;
                    }
                }
            }

            module.LoadSettings();
            module.Load();
            if (_ContentLoaded)
            {
                module.LoadContent(true);
            }
            if (_Initialized)
            {
                Tracker.Initialize();
                module.Initialize();
                Input.Initialize();

                if (SaveData.Instance != null)
                {
                    // we are in a save. we are expecting the save data to already be loaded at this point
                    Logger.Log("core", $"Loading save data slot {SaveData.Instance.FileSlot} for {module.Metadata}");
                    module.LoadSaveData(SaveData.Instance.FileSlot);

                    if (SaveData.Instance.CurrentSession?.InArea ?? false)
                    {
                        // we are in a level. we are expecting the session to already be loaded at this point
                        Logger.Log("core", $"Loading session slot {SaveData.Instance.FileSlot} for {module.Metadata}");
                        module.LoadSession(SaveData.Instance.FileSlot, false);
                    }
                }

                // Check if the module defines a PrepareMapDataProcessors method. If this is the case, we want to reload maps so that they are applied.
                // We should also run the map data processors again if new berry types are registered, so that CoreMapDataProcessor assigns them checkpoint IDs and orders.
                if (newStrawberriesRegistered || module.GetType().GetMethod("PrepareMapDataProcessors", new Type[] { typeof(MapDataFixup) })?.DeclaringType == module.GetType())
                {
                    Logger.Log("core", $"Module {module.Metadata} has custom strawberries or map data processors: reloading maps.");
                    AssetReloadHelper.ReloadAllMaps();
                }
            }

            if (Engine.Instance != null && Engine.Scene is Overworld overworld)
            {
                // we already are in the overworld. Register new Ouis real quick!
                Type[] types = FakeAssembly.GetFakeEntryAssembly().GetTypesSafe();
                foreach (Type type in types)
                {
                    if (typeof(Oui).IsAssignableFrom(type) && !type.IsAbstract && !overworld.UIs.Any(ui => ui.GetType() == type))
                    {
                        Logger.Log("core", $"Instanciating UI from {module.Metadata}: {type.FullName}");

                        Oui oui = (Oui)Activator.CreateInstance(type);
                        oui.Visible = false;
                        overworld.Add(oui);
                        overworld.UIs.Add(oui);
                    }
                }
            }

            InvalidateInstallationHash();

            EverestModuleMetadata meta = module.Metadata;

            meta.Hash = GetChecksum(meta);

            // Audio banks are cached, and as such use the module's hash. We can only ingest those now.
            if (patch_Audio.AudioInitialized)
            {
                patch_Audio.IngestNewBanks();
            }

            Logger.Log(LogLevel.Info, "core", $"Module {module.Metadata} registered.");

            CheckDependenciesOfDelayedMods();
        }
Пример #2
0
        internal static void Initialize()
        {
            // Initialize misc stuff.
            TextInput.Initialize(Celeste.Instance);
            if (!Flags.IsDisabled)
            {
                Discord.Initialize();
            }

            // Add the previously created managers.
            if (TouchInputManager.Instance != null)
            {
                Celeste.Instance.Components.Add(TouchInputManager.Instance);
            }
            Celeste.Instance.Components.Add(MainThreadHelper.Instance);

            foreach (EverestModule mod in _Modules)
            {
                mod.Initialize();
            }
            _Initialized = true;

            // Search for all entities marked with the CustomEntityAttribute.
            foreach (EverestModule mod in _Modules)
            {
                foreach (Type type in mod.GetType().Assembly.GetTypes())
                {
                    foreach (CustomEntityAttribute attrib in type.GetCustomAttributes <CustomEntityAttribute>())
                    {
                        foreach (string idFull in attrib.IDs)
                        {
                            string   id;
                            string   genName;
                            string[] split = idFull.Split('=');

                            if (split.Length == 1)
                            {
                                id      = split[0];
                                genName = "Load";
                            }
                            else if (split.Length == 2)
                            {
                                id      = split[0];
                                genName = split[1];
                            }
                            else
                            {
                                Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                                continue;
                            }

                            id      = id.Trim();
                            genName = genName.Trim();

                            patch_Level.EntityLoader loader = null;

                            ConstructorInfo ctor;
                            MethodInfo      gen;

                            gen = type.GetMethod(genName, new Type[] { typeof(Level), typeof(LevelData), typeof(Vector2), typeof(EntityData) });
                            if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(Entity)))
                            {
                                loader = (level, levelData, offset, entityData) => (Entity)gen.Invoke(null, new object[] { level, levelData, offset, entityData });
                                goto RegisterEntityLoader;
                            }

                            ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2), typeof(EntityID) });
                            if (ctor != null)
                            {
                                loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset, new EntityID(levelData.Name, entityData.ID) });
                                goto RegisterEntityLoader;
                            }

                            ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2) });
                            if (ctor != null)
                            {
                                loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset });
                                goto RegisterEntityLoader;
                            }

                            ctor = type.GetConstructor(new Type[] { typeof(Vector2) });
                            if (ctor != null)
                            {
                                loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { offset });
                                goto RegisterEntityLoader;
                            }

                            ctor = type.GetConstructor(_EmptyTypeArray);
                            if (ctor != null)
                            {
                                loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(_EmptyObjectArray);
                                goto RegisterEntityLoader;
                            }

RegisterEntityLoader:
                            if (loader == null)
                            {
                                Logger.Log(LogLevel.Warn, "core", $"Found custom entity without suitable constructor / {genName}(Level, LevelData, Vector2, EntityData): {id} ({type.FullName})");
                                continue;
                            }
                            patch_Level.EntityLoaders[id] = loader;
                        }
                    }
                }
            }
        }
Пример #3
0
        /// <summary>
        /// Register a new EverestModule (mod) dynamically. Invokes LoadSettings and Load.
        /// </summary>
        /// <param name="module">Mod to register.</param>
        public static void Register(this EverestModule module)
        {
            lock (_Modules) {
                _Modules.Add(module);
                _ModuleTypes.Add(module.GetType());
            }

            LuaLoader.Precache(module.GetType().Assembly);

            foreach (Type type in module.GetType().Assembly.GetTypes())
            {
                // Search for all entities marked with the CustomEntityAttribute.
                foreach (CustomEntityAttribute attrib in type.GetCustomAttributes <CustomEntityAttribute>())
                {
                    foreach (string idFull in attrib.IDs)
                    {
                        string   id;
                        string   genName;
                        string[] split = idFull.Split('=');

                        if (split.Length == 1)
                        {
                            id      = split[0];
                            genName = "Load";
                        }
                        else if (split.Length == 2)
                        {
                            id      = split[0];
                            genName = split[1];
                        }
                        else
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                            continue;
                        }

                        id      = id.Trim();
                        genName = genName.Trim();

                        patch_Level.EntityLoader loader = null;

                        ConstructorInfo ctor;
                        MethodInfo      gen;

                        gen = type.GetMethod(genName, new Type[] { typeof(Level), typeof(LevelData), typeof(Vector2), typeof(EntityData) });
                        if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(Entity)))
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)gen.Invoke(null, new object[] { level, levelData, offset, entityData });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2), typeof(EntityID) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset, new EntityID(levelData.Name, entityData.ID) });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(_EmptyTypeArray);
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(_EmptyObjectArray);
                            goto RegisterEntityLoader;
                        }

RegisterEntityLoader:
                        if (loader == null)
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Found custom entity without suitable constructor / {genName}(Level, LevelData, Vector2, EntityData): {id} ({type.FullName})");
                            continue;
                        }
                        patch_Level.EntityLoaders[id] = loader;
                    }
                }
            }

            module.LoadSettings();
            module.Load();
            if (_Initialized)
            {
                module.Initialize();
            }

            InvalidateInstallationHash();

            EverestModuleMetadata meta = module.Metadata;

            meta.Hash = GetChecksum(meta);

            Logger.Log(LogLevel.Info, "core", $"Module {module.Metadata} registered.");

            // Attempt to load mods after their dependencies have been loaded.
            // Only load and lock the delayed list if we're not already loading delayed mods.
            if (Interlocked.CompareExchange(ref Loader.DelayedLock, 1, 0) == 0)
            {
                lock (Loader.Delayed) {
                    for (int i = Loader.Delayed.Count - 1; i > -1; i--)
                    {
                        Tuple <EverestModuleMetadata, Action> entry = Loader.Delayed[i];
                        if (!Loader.DependenciesLoaded(entry.Item1))
                        {
                            continue;
                        }

                        Loader.LoadMod(entry.Item1);
                        Loader.Delayed.RemoveAt(i);

                        entry.Item2?.Invoke();
                    }
                }
                Interlocked.Decrement(ref Loader.DelayedLock);
            }
        }
Пример #4
0
        /// <summary>
        /// Register a new EverestModule (mod) dynamically. Invokes LoadSettings and Load.
        /// </summary>
        /// <param name="module">Mod to register.</param>
        public static void Register(this EverestModule module)
        {
            lock (_Modules) {
                _Modules.Add(module);
            }

            LuaLoader.Precache(module.GetType().Assembly);

            foreach (Type type in module.GetType().Assembly.GetTypes())
            {
                // Search for all entities marked with the CustomEntityAttribute.
                foreach (CustomEntityAttribute attrib in type.GetCustomAttributes <CustomEntityAttribute>())
                {
                    foreach (string idFull in attrib.IDs)
                    {
                        string   id;
                        string   genName;
                        string[] split = idFull.Split('=');

                        if (split.Length == 1)
                        {
                            id      = split[0];
                            genName = "Load";
                        }
                        else if (split.Length == 2)
                        {
                            id      = split[0];
                            genName = split[1];
                        }
                        else
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                            continue;
                        }

                        id      = id.Trim();
                        genName = genName.Trim();

                        patch_Level.EntityLoader loader = null;

                        ConstructorInfo ctor;
                        MethodInfo      gen;

                        gen = type.GetMethod(genName, new Type[] { typeof(Level), typeof(LevelData), typeof(Vector2), typeof(EntityData) });
                        if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(Entity)))
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)gen.Invoke(null, new object[] { level, levelData, offset, entityData });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2), typeof(EntityID) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset, new EntityID(levelData.Name, entityData.ID) });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(_EmptyTypeArray);
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(_EmptyObjectArray);
                            goto RegisterEntityLoader;
                        }

RegisterEntityLoader:
                        if (loader == null)
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Found custom entity without suitable constructor / {genName}(Level, LevelData, Vector2, EntityData): {id} ({type.FullName})");
                            continue;
                        }
                        patch_Level.EntityLoaders[id] = loader;
                    }
                }
                // Register with the StrawberryRegistry all entities marked with RegisterStrawberryAttribute.
                foreach (RegisterStrawberryAttribute attrib in type.GetCustomAttributes <RegisterStrawberryAttribute>())
                {
                    List <string> names = new List <string>();
                    foreach (CustomEntityAttribute nameAttrib in type.GetCustomAttributes <CustomEntityAttribute>())
                    {
                        foreach (string idFull in nameAttrib.IDs)
                        {
                            string[] split = idFull.Split('=');
                            if (split.Length == 0)
                            {
                                Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                                continue;
                            }
                            names.Add(split[0]);
                        }
                    }
                    if (names.Count == 0)
                    {
                        goto NoDefinedBerryNames; // no customnames? skip out on registering berry
                    }
                    foreach (string name in names)
                    {
                        StrawberryRegistry.Register(type, name, attrib.isTracked, attrib.blocksNormalCollection);
                    }
                }
NoDefinedBerryNames:
                ;
            }

            module.LoadSettings();
            module.Load();
            if (_ContentLoaded)
            {
                module.LoadContent(true);
            }
            if (_Initialized)
            {
                Tracker.Initialize();
                module.Initialize();

                // check if the module defines a PrepareMapDataProcessors method. If this is the case, we want to reload maps so that they are applied.
                if (module.GetType().GetMethod("PrepareMapDataProcessors", new Type[] { typeof(MapDataFixup) })?.DeclaringType == module.GetType())
                {
                    Logger.Log("core", $"Module {module.Metadata} has map data processors: reloading maps.");
                    OuiHelper_ChapterSelect_Reload.Reload(false);
                }
            }

            if (Engine.Instance != null && Engine.Scene is Overworld overworld)
            {
                // we already are in the overworld. Register new Ouis real quick!
                Type[] types = FakeAssembly.GetFakeEntryAssembly().GetTypes();
                foreach (Type type in types)
                {
                    if (typeof(Oui).IsAssignableFrom(type) && !type.IsAbstract && !overworld.UIs.Any(ui => ui.GetType() == type))
                    {
                        Logger.Log("core", $"Instanciating UI from {module.Metadata}: {type.FullName}");

                        Oui oui = (Oui)Activator.CreateInstance(type);
                        oui.Visible = false;
                        overworld.Add(oui);
                        overworld.UIs.Add(oui);
                    }
                }
            }

            InvalidateInstallationHash();

            EverestModuleMetadata meta = module.Metadata;

            meta.Hash = GetChecksum(meta);

            // Audio banks are cached, and as such use the module's hash. We can only ingest those now.
            if (patch_Audio.AudioInitialized)
            {
                patch_Audio.IngestNewBanks();
            }

            Logger.Log(LogLevel.Info, "core", $"Module {module.Metadata} registered.");

            // Attempt to load mods after their dependencies have been loaded.
            // Only load and lock the delayed list if we're not already loading delayed mods.
            if (Interlocked.CompareExchange(ref Loader.DelayedLock, 1, 0) == 0)
            {
                try {
                    lock (Loader.Delayed) {
                        for (int i = 0; i < Loader.Delayed.Count; i++)
                        {
                            Tuple <EverestModuleMetadata, Action> entry = Loader.Delayed[i];
                            if (!Loader.DependenciesLoaded(entry.Item1))
                            {
                                continue; // dependencies are still missing!
                            }
                            Logger.Log(LogLevel.Info, "core", $"Dependencies of mod {entry.Item1} are now satisfied: loading");
                            entry.Item2?.Invoke();
                            Loader.LoadMod(entry.Item1);
                            Loader.Delayed.RemoveAt(i);

                            // we now loaded an extra mod, consider all delayed mods again to deal with transitive dependencies.
                            i = -1;
                        }
                    }
                } finally {
                    Interlocked.Decrement(ref Loader.DelayedLock);
                }
            }
        }