Ejemplo n.º 1
0
        public static void Init(short width, short height, bool withEditor)        //Метод инициализации
        {
            AssetManager.LoadAssetsByPath(GetDirectoryPath() + GAME_SPRITES_PATH); //Загружаются ассеты
            InputManager.Init();                                                   //Запускается управление
            GraphicsManager.Init(width, height);                                   //Запускается графика
            if (File.Exists(GAME_SCENES_PATH + "defalutScene.scn"))
            {
                SceneManager.LoadScene(GetDirectoryPath() + GAME_SCENES_PATH + "defalutScene.scn");
            }
            else
            {
                SceneManager.CreateNewScene(); //Создаётся пустая сцена
                SceneManager.SaveScene(GetDirectoryPath() + GAME_SCENES_PATH + "defalutScene.scn");
            }

            GameEngine.withEditor = withEditor;
            OnGameStart();
            if (!withEditor)
            {
                Application.Run();
            }
            else
            {
                GameEditor.Init();
            }
        }
Ejemplo n.º 2
0
 public void LoadEditor()           //djelomična inicijializacija game editora
 {
     LevelLoader.Load("Empty.xml"); //učitaj prazni level
     //postavke scene i igre...
     PlayerCamera.main.mode = PlayerCamera.PlayerCameraMode.Editor;
     Player.playing         = false;
     //isključi nepotrebne UI objekte...
     for (int i = 0; i < GameData.main.mainButtons.Length; i++)
     {
         GameData.main.mainButtons[i].gameObject.SetActive(false);
     }
     for (int i = 0; i < GameData.main.inGameStuff.Length; i++)
     {
         GameData.main.inGameStuff[i].SetActive(false);
     }
     for (int i = 0; i < GameData.main.optionsStuff.Length; i++)
     {
         GameData.main.optionsStuff[i].SetActive(false);
     }
     //uključi UI editora
     for (int i = 0; i < GameData.main.editorStuff.Length; i++)
     {
         GameData.main.editorStuff[i].SetActive(true);
     }
     currentGameState = GameState.editing;
     GameEditor.Setup();                      //editor se treba inicijalizirati
     Player.main.anim.SetBool("Idle", false); //dino ne smije biti u ikakvoj animaciji
     Player.main.anim.Play("Nothing", 0);
 }
Ejemplo n.º 3
0
    private void GenerateObstacleTag()
    {
        GameEditor.RemoveAllTag();
        //var entitys = Directory.GetFiles(PathRecoder.EntityRoot, "*", SearchOption.AllDirectories);
        var obstacles = Directory.GetFiles(PathRecoder.ObstacleRoot, "*", SearchOption.AllDirectories);
        var files     = new List <string>();

        //files.AddRange(entitys);
        files.AddRange(obstacles);
        foreach (var file in files)
        {
            var fullPath = file.Replace('\\', '/');
            if (fullPath.EndsWith(".meta"))
            {
                continue;
            }
            var tag    = fullPath.Replace(Application.dataPath + "/", "");
            var path   = "Assets/" + tag;
            var assets = AssetDatabase.LoadAssetAtPath <GameObject>(path);
            GameEditor.AddTag(tag);
            assets.tag = tag;
            foreach (var t in assets.GetComponentsInChildren <Transform>(true))
            {
                t.tag = tag;
            }
        }
        Debug.Log("finish!");
    }
Ejemplo n.º 4
0
    static void Init()
    {
        // Get existing open window or if none, make a new one:
        GameEditor window = (GameEditor)EditorWindow.GetWindow(typeof(GameEditor));

        window.Show();
    }
Ejemplo n.º 5
0
    static void Init()
    {
        // Get existing open window or if none, make a new one:
        GameEditor window = (GameEditor)EditorWindow.GetWindow(typeof(GameEditor));

        window.devMode = bool.Parse(PlayerPrefs.GetString(Constants.cst_DeveloperMode, "false"));
        window.Show();
    }
Ejemplo n.º 6
0
		// Use this for initialization
		void Start ()
		{
		Debug.Log (Application.persistentDataPath);
				gridContainer = new GameObject ("GridContainer");
				Instance = this;
				//Load ("zxczczx");
				//CreateGridBackground ();
		}
    //Responsible for saving each specific level based on set level number function
    public static void SaveLevelData(GameEditor data)
    {
        BinaryFormatter binaryFormat = new BinaryFormatter();
        FileStream      fileStream   = new FileStream(Application.persistentDataPath + levelData + saveNum + extension, FileMode.Create);
        LevelData       newData      = new LevelData(data);

        binaryFormat.Serialize(fileStream, newData);
        fileStream.Close();
    }
Ejemplo n.º 8
0
 public CommandRunner(ESDOptions options, ESDOptions.Option opt)
 {
     this.options = options;
     this.opt     = opt;
     this.spec    = options.Spec;
     this.editor  = new GameEditor(spec);
     this.scraper = new Scraper(spec);
     this.u       = new Universe();
 }
Ejemplo n.º 9
0
 public void LoadMenu()  //učitavanje meni-ja
 {
     Cursor.visible = true;
     if (currentGameState == GameState.editing)  //ako je igrač prije bio u level editor-u, počisti sve što je od toga ostalo
     {
         GameEditor.Clear();
     }
     if (LevelLoader.currentLevel != "Menu.xml") //učitaj Level menija (ako već nije učitan)
     {
         LevelLoader.Load("Menu.xml");
     }
     //iskluči sve ostale UI grupe objekata
     for (int i = 0; i < GameData.main.youDiedStuff.Length; i++)
     {
         GameData.main.youDiedStuff[i].SetActive(false);
     }
     for (int i = 0; i < GameData.main.youWonStuff.Length; i++)
     {
         GameData.main.youWonStuff[i].SetActive(false);
     }
     for (int i = 0; i < GameData.main.inGameStuff.Length; i++)
     {
         GameData.main.inGameStuff[i].SetActive(false);
     }
     for (int i = 0; i < GameData.main.pauseStuff.Length; i++)
     {
         GameData.main.pauseStuff[i].SetActive(false);
     }
     for (int i = 0; i < GameData.main.editorStuff.Length; i++)
     {
         GameData.main.editorStuff[i].SetActive(false);
     }
     for (int i = 0; i < GameData.main.levelSelectionStuff.Length; i++)
     {
         GameData.main.levelSelectionStuff[i].gameObject.SetActive(false);
     }
     for (int i = 0; i < GameData.main.optionsStuff.Length; i++)
     {
         GameData.main.optionsStuff[i].SetActive(false);
     }
     GameData.main.helpUIStuff.SetActive(false);
     Time.timeScale = 1f;    //za uklanjanje pauze (ako je bila)
     //općenite postavke scene i igre...
     PlayerCamera.main.mode = PlayerCamera.PlayerCameraMode.Stand;
     Player.playing         = false;
     currentGameState       = GameState.menu;
     //uključi UI menija
     for (int i = 0; i < GameData.main.mainButtons.Length; i++)
     {
         GameData.main.mainButtons[i].gameObject.SetActive(true);
     }
     if (Player.main)
     {
         Player.main.anim.Play("Idles", 0); //dino treba biti u idle animaciji
     }
 }
Ejemplo n.º 10
0
    static void EditGame()
    {
        TextAsset file = Selection.activeObject as TextAsset;

        if (file != null)
        {
            GameEditor window = EditorWindow.GetWindow <GameEditor>("Game Editor");
            window.SetFile(file);
            window.Show();
        }
    }
Ejemplo n.º 11
0
 public GameData(string dir, bool sekiro)
 {
     this.dir              = dir;
     Sekiro                = sekiro;
     Editor                = new GameEditor(sekiro ? GameSpec.FromGame.SDT : GameSpec.FromGame.DS3);
     Editor.Spec.GameDir   = $@"{dir}";
     Editor.Spec.NameDir   = $@"{dir}\Names";
     Editor.Spec.LayoutDir = $@"{dir}\Layouts";
     Locations             = Sekiro ? sekiroLocationNames : ds3LocationNames;
     RevLocations          = Locations.ToDictionary(e => e.Value, e => e.Key);
     LocationNames         = Sekiro ? sekiroMapName : null;
 }
 public int[] sizeData;                  //stores all other values within the level like height and width
 public LevelData(GameEditor data)
 {
     allLevelData    = new int[3][];
     sizeData        = new int[2];
     sizeData[0]     = data.SavedWidth;
     sizeData[1]     = data.SavedHeight;
     paths           = data.NumOfPath.ToArray();
     blocks          = data.BlockIds.ToArray();
     allLevelData[0] = sizeData;
     allLevelData[1] = blocks;
     allLevelData[2] = paths;
 }
Ejemplo n.º 13
0
 protected override void OnClick(EventArgs e)
 {
     if (ControlList.data.current_game == null)
     {
         MessageBox.Show("Please Select a Game");
     }
     else
     {
         GameEditor ge = new GameEditor(ControlList.data.current_game);
         ge.ShowDialog();
         ge.Dispose();
     }
 }
Ejemplo n.º 14
0
        public void FogConfig()
        {
            AnnotationData ret = new AnnotationData();

            ret.SetGame(FromGame.DS3);
            GameEditor editor = new GameEditor(FromGame.DS3);
            Dictionary <string, MSB3> maps = editor.Load(@"map\MapStudio", path => ret.Specs.ContainsKey(GameEditor.BaseName(path)) ? MSB3.Read(path) : null, "*.msb.dcx");

            ret.Entrances = new List <Entrance>();
            foreach (KeyValuePair <string, MSB3> entry in maps)
            {
                if (!ret.Specs.TryGetValue(entry.Key, out MapSpec spec))
                {
                    continue;
                }
                // Console.WriteLine(spec.Name);
                MSB3 msb = entry.Value;
                Dictionary <string, MSB3.Part.Object> objs = new Dictionary <string, MSB3.Part.Object>();
                foreach (MSB3.Part.Object e in msb.Parts.Objects)
                {
                    objs[e.Name] = e;
                    int model = int.Parse(e.ModelName.Substring(1));
                    if (model >= spec.Start && model <= spec.End)
                    {
                        ret.Entrances.Add(new Entrance
                        {
                            Area  = spec.Name,
                            Name  = e.Name,
                            ID    = e.EventEntityID,
                            Text  = "Between",
                            Tags  = "pvp boss",
                            ASide = new Side {
                                Area = spec.Name
                            },
                            BSide = new Side {
                                Area = spec.Name
                            },
                        });
                    }
                }
            }
            ISerializer serializer = new SerializerBuilder().DisableAliases().Build();

            using (TextWriter writer = File.CreateText("fog.txt"))
            {
                serializer.Serialize(writer, ret);
            }
        }
        private void OnEditGamesListClicked(object sender, RoutedEventArgs e)
        {
            if (this.Splitter.EmulationManager.IsEmulationStarted)
            {
                Controls.MessageBox.Show(
                    "You can not edit games, when emulation is running!",
                    ApplicationInfo.AppName,
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);

                return;
            }

            var gameEditor = new GameEditor();

            gameEditor.Owner = this;
            gameEditor.WindowStartupLocation = WindowStartupLocation.CenterOwner;
            gameEditor.ShowDialog();
        }
Ejemplo n.º 16
0
        /// <summary>
        /// Initializes server-side world.
        /// </summary>
        /// <param name="maxPlayers"></param>
        /// <param name="maxEntities"></param>
        public MapEditor(GameEditor editor, string map)
        {
            this.mapName = map;

            Log.Verbose("game editor");
            this.Game = editor.Game;
            this.rs   = Game.RenderSystem;
            Content   = new ContentManager(Game);

            Config = Game.Config.GetConfig <EditorConfig>();

            camera      = new EditorCamera(this);
            hud         = new EditorHud(this);
            manipulator = new NullTool(this);
            world       = new GameWorld(Game, true, new Guid());
            world.InitServerAtoms();

            SetupUI();

            Game.Keyboard.ScanKeyboard = true;

            fullPath = Builder.GetFullPath(@"maps\" + map + ".map");
        }
Ejemplo n.º 17
0
        //public WorkspaceService workspace => desktopRunner.workspaceService;

        public override void ConfigureScript(Script luaScript)
        {
            base.ConfigureScript(luaScript);

            // File APIs
            luaScript.Globals["UniqueFilePath"]  = new Func <WorkspacePath, WorkspacePath>(workspace.UniqueFilePath);
            luaScript.Globals["CreateDirectory"] = new Action <WorkspacePath>(workspace.CreateDirectoryRecursive);
            luaScript.Globals["MoveTo"]          = new Action <WorkspacePath, WorkspacePath>(workspace.Move);
            luaScript.Globals["CopyTo"]          = new Action <WorkspacePath, WorkspacePath>(workspace.Copy);
            luaScript.Globals["Delete"]          = new Action <WorkspacePath>(workspace.Delete);
            luaScript.Globals["PathExists"]      = new Func <WorkspacePath, bool>(workspace.Exists);
            luaScript.Globals["GetEntities"]     = new Func <WorkspacePath, List <WorkspacePath> >(path =>
                                                                                                   workspace.GetEntities(path).OrderBy(o => o.EntityName, StringComparer.OrdinalIgnoreCase).ToList());
            luaScript.Globals["GetEntitiesRecursive"] = new Func <WorkspacePath, List <WorkspacePath> >(path =>
                                                                                                        workspace.GetEntitiesRecursive(path).ToList());

            luaScript.Globals["PlayWav"] = new Action <WorkspacePath>(PlayWav);
            luaScript.Globals["StopWav"] = new Action(StopWav);

            luaScript.Globals["CreateDisk"] =
                new Func <string, WorkspacePath[], WorkspacePath, int, string[], Dictionary <string, object> >(CreateDisk);
            luaScript.Globals["CreateExe"] =
                new Func <string, WorkspacePath[], WorkspacePath, WorkspacePath, string[], Dictionary <string, object> >(
                    CreateExe);
            luaScript.Globals["ClearLog"]     = new Action(workspace.ClearLog);
            luaScript.Globals["ReadLogItems"] = new Func <List <string> >(workspace.ReadLogItems);

            // TODO these are deprecated
            luaScript.Globals["ReadTextFile"]   = new Func <string, string>(ReadTextFile);
            luaScript.Globals["SaveTextToFile"] = (SaveTextToFileDelegator)SaveTextToFile;

            // File helpers
            luaScript.Globals["NewImage"] = new Func <int, int, string[], int[], Image>(NewImage);

            // Read file helpers
            luaScript.Globals["ReadJson"]  = new Func <WorkspacePath, Dictionary <string, object> >(ReadJson);
            luaScript.Globals["ReadText"]  = new Func <WorkspacePath, string>(ReadText);
            luaScript.Globals["ReadImage"] = new Func <WorkspacePath, string, Image>(ReadImage);

            // Save file helpers
            luaScript.Globals["SaveText"]  = new Action <WorkspacePath, string>(SaveText);
            luaScript.Globals["SaveImage"] = new Action <WorkspacePath, Image>(SaveImage);

//
//            // Expose Bios APIs
//            luaScript.Globals["ReadBiosData"] = new Func<string, string, string>((key, defaultValue) =>
//                desktopRunner.bios.ReadBiosData(key, defaultValue));
//            luaScript.Globals["WriteBiosData"] = new Action<string, string>(desktopRunner.bios.UpdateBiosData);

            //            luaScript.Globals["RemapKey"] = new Action<string, int>(RemapKey);

            luaScript.Globals["NewWorkspacePath"] = new Func <string, WorkspacePath>(WorkspacePath.Parse);

            UserData.RegisterType <WorkspacePath>();
            UserData.RegisterType <Image>();

            // Register the game editor with  the lua service
            gameEditor = new GameEditor(desktopRunner, locator);
            UserData.RegisterType <GameEditor>();
            luaScript.Globals["gameEditor"] = gameEditor;
        }
Ejemplo n.º 18
0
        public Assignment SplitAll()
        {
            // Add skills as item drops in the world. Esoteric texts still work, but they are removed from randomizer pool.
            Assignment ret = new Assignment();

            // First, create skill items, including editing item fmgs
            SortedDictionary <int, ItemKey> newSkills = new SortedDictionary <int, ItemKey>();
            SortedDictionary <int, ItemKey> oldSkills = new SortedDictionary <int, ItemKey>();

            Dictionary <int, ItemKey> texts = new Dictionary <int, ItemKey>
            {
                [0] = game.ItemForName("Shinobi Esoteric Text"),
                [1] = game.ItemForName("Prosthetic Esoteric Text"),
                [2] = game.ItemForName("Ashina Esoteric Text"),
                [3] = game.ItemForName("Senpou Esoteric Text"),
                [4] = game.ItemForName("Mushin Esoteric Text"),
            };

            // Note: there are some events in common which can be used to detect skills which are already granted by emevd.
            // But they all have a text of -1 so it's unnecessary to scan emevd for this.
            // For example:
            // Initialize Event (Event Slot ID: 4, Event ID: 450, Parameters: 6719){, 3, 2450, 620}
            // Initialize Event (Event Slot ID: 0, Event ID: 460, Parameters: 6710){, 2470, 600}

            PARAM.Row baseGood = game.Params["EquipParamGoods"][2470];
            int       baseId   = 6405;

            FMG itemName   = game.ItemFMGs["アイテム名"];
            FMG itemDesc   = game.ItemFMGs["アイテム説明"];
            FMG weaponName = game.ItemFMGs["武器名"];
            FMG weaponDesc = game.ItemFMGs["武器説明"];
            SortedDictionary <ItemKey, string> gameNames = game.Names();

            bool          explain       = false;
            HashSet <int> copiedWeapons = new HashSet <int>();

            foreach (PARAM.Row r in game.Params["SkillParam"].Rows)
            {
                int skillId = (int)r.ID;
                if (skillId >= 700)
                {
                    continue;
                }

                int text = (byte)r["Unk7"].Value;
                if (!texts.ContainsKey(text))
                {
                    continue;
                }

                int descItem = (int)r["SkilLDescriptionId"].Value;
                if (copiedWeapons.Contains(descItem))
                {
                    continue;
                }
                copiedWeapons.Add(descItem);

                PARAM.Row weaponRow = game.Params["EquipParamWeapon"][descItem];
                int       sortId    = (int)weaponRow["sortId"].Value;
                short     iconId    = (short)weaponRow["iconId"].Value;

                int       good    = baseId++;
                PARAM.Row newGood = game.AddRow("EquipParamGoods", good);
                GameEditor.CopyRow(baseGood, newGood);
                ItemKey goodKey = new ItemKey(ItemType.GOOD, good);
                newSkills[skillId] = goodKey;

                gameNames[goodKey] = weaponName[descItem];
                itemName[good]     = weaponName[descItem];
                itemDesc[good]     = weaponDesc[descItem];

                newGood["sortId"].Value = sortId;
                newGood["iconId"].Value = iconId;
                // These should be set in base row, but do this just in case
                // Don't show up in inventory
                newGood["goodsType"].Value = (byte)7;
                // But pop up on acquisition
                newGood["Unk20"].Value = (byte)6;

                if (explain)
                {
                    Console.WriteLine($"-- {r.ID} -> {good}: {descItem}, {weaponName[descItem]}");
                }
                ret.Assign[new ItemKey(ItemType.WEAPON, descItem)] = goodKey;
            }
            game.Params["EquipParamGoods"].Rows.Sort((a, b) => a.ID.CompareTo(b.ID));

            // Second, add event scripting to grant skills, with new common_func
            EMEVD common  = game.Emevds["common"];
            int   grantId = 11615600;

            EMEVD.Event   grantEv   = new EMEVD.Event(grantId);
            List <string> grantCmds = new List <string> {
                "IF Player Has/Doesn't Have Item (0,3,X0_4,1)", "Grant Skill (X4_4)"
            };

            for (int i = 0; i < grantCmds.Count; i++)
            {
                (EMEVD.Instruction ins, List <EMEVD.Parameter> ps) = events.ParseAddArg(grantCmds[i], i);
                grantEv.Instructions.Add(ins);
                grantEv.Parameters.AddRange(ps);
            }
            common.Events.Add(grantEv);

            int slot = 0;

            foreach (KeyValuePair <int, ItemKey> entry in newSkills)
            {
                common.Events[0].Instructions.Add(new EMEVD.Instruction(2000, 0, new List <object> {
                    slot++, grantId, entry.Value.ID, entry.Key
                }));
            }

            // Third, edit drops
            // Remove text drops
            ann.ItemGroups["remove"].AddRange(texts.Values);

            // Add skill drops
            foreach (ItemKey item in newSkills.Values)
            {
                data.AddLocationlessItem(item);
            }

            // Copy restrictions from the weapons over to the goods
            ann.CopyRestrictions(ret.Assign);
            foreach (KeyValuePair <ItemKey, ItemKey> entry in ret.Assign)
            {
                ItemKey weapon = entry.Key;
                ItemKey good   = entry.Value;
                // Mikiri Counter in hint log
                if (weapon.ID == 200300)
                {
                    ann.ItemGroups["upgradehints"].Add(good);
                }
                // Carp scalesmen
                if (!ann.ExcludeTags.ContainsKey(weapon))
                {
                    ann.ItemGroups["premium"].Add(good);
                }
            }

            // For balancing Dancing Dragon Mask, greatly reduce enemy xp drops
            // All NG bosses together give 93k XP. This gives enough for 45 skill points.
            // So allow 15 levels/3 AP upgrades, or 9 XP. (Next threshhold: 4 AP upgrades, 13k XP)
            // (Or not, since only got 9 levels in a full run, just double it.)
            foreach (PARAM.Row row in game.Params["NpcParam"].Rows)
            {
                row["Experience"].Value = (int)row["Experience"].Value / 5;
            }
            foreach (PARAM.Row row in game.Params["GameAreaParam"].Rows)
            {
                row["BonusExperience"].Value = (int)row["BonusExperience"].Value / 5;
            }

            // Also in this mode, acquire skills option is removed from Sculptor's Idols, in case it has been there from previous runs. Done in PermutationWriter.

            return(ret);
        }
Ejemplo n.º 19
0
 private void Btn_CreateOwnGame_Click(object sender, RoutedEventArgs e)
 {
     gameEditor = new GameEditor();
     ChangeMainUserControl(gameEditor);
 }
Ejemplo n.º 20
0
            public override bool Initialize()
            {
                viewer = GameViewer.Instance;
                if (viewer == null)
                    return false;
                GameViewer.GameDb = GameDb.Instance;
                GameDbBrowser browser = new GameDbBrowser ();
                CsBoard.CsBoardApp.Instance.AddApp (browser);

                editor = new GameEditor (viewer);
                viewer.ChessGameDetailsBox.PackStart (editor,
                                      false,
                                      true,
                                      2);

                viewer.AddToFileMenu (saveItem);
                //viewer.AddToViewMenu (openDbItem);

                return true;
            }
Ejemplo n.º 21
0
    static void ShowMyWindow()
    {
        GameEditor window = EditorWindow.GetWindow <GameEditor>();

        window.Show();
    }
Ejemplo n.º 22
0
    override public void OnInspectorGUI()
    {
        gameEditor = target as GameEditor;
        #region PlayerPrefs
        GUILayout.Label("PlayerPrefs", headerText);
        #region PlayerPrefs Edit
        EditorGUILayout.LabelField("PlayerPrefs Edit", EditorStyles.boldLabel);
        gameEditor.playerPrefsState = (PlayerPrefsState)EditorGUILayout.EnumPopup("Mode", gameEditor.playerPrefsState);
        gameEditor.editKey          = EditorGUILayout.TextField("Key Name", gameEditor.editKey);
        GUILayout.BeginHorizontal();
        if (gameEditor.playerPrefsState == PlayerPrefsState.Int)
        {
            gameEditor.intValue = EditorGUILayout.IntField("Value", gameEditor.intValue);
            if (GUILayout.Button($"Set '{gameEditor.editKey}' Key"))
            {
                PlayerPrefs.SetInt(gameEditor.editKey, gameEditor.intValue);
                Debug.Log($"{gameEditor.editKey} is {(PlayerPrefs.GetInt(gameEditor.editKey)).ToString()} now");
            }
        }
        else if (gameEditor.playerPrefsState == PlayerPrefsState.String)
        {
            gameEditor.stringValue = EditorGUILayout.TextField("Value", gameEditor.stringValue);

            if (GUILayout.Button($"Set '{gameEditor.editKey}' Key"))
            {
                PlayerPrefs.SetString(gameEditor.editKey, gameEditor.stringValue);
                Debug.Log($"{gameEditor.editKey} is {(PlayerPrefs.GetString(gameEditor.editKey)).ToString()} now");
            }
        }
        else if (gameEditor.playerPrefsState == PlayerPrefsState.Float)
        {
            gameEditor.floatValue = EditorGUILayout.FloatField("Value", gameEditor.floatValue);

            if (GUILayout.Button($"Set '{gameEditor.editKey}' Key"))
            {
                PlayerPrefs.SetFloat(gameEditor.editKey, gameEditor.floatValue);
                Debug.Log($"{gameEditor.editKey} is {(PlayerPrefs.GetFloat(gameEditor.editKey)).ToString()} now");
            }
        }
        GUILayout.EndHorizontal();
        #endregion
        GUILayout.Space(15);
        #region PlayerPrefs Learn
        EditorGUILayout.LabelField("PlayerPrefs Learn", EditorStyles.boldLabel);
        GUILayout.BeginHorizontal();

        gameEditor.learnKey = EditorGUILayout.TextField("Key Name", gameEditor.learnKey);

        GUILayout.Space(10);
        if (gameEditor.playerPrefsState == PlayerPrefsState.Int)
        {
            if (GUILayout.Button($"Learn '{gameEditor.learnKey}' Key"))
            {
                Debug.Log($"'{gameEditor.learnKey}' Key's value is '{(PlayerPrefs.GetInt(gameEditor.learnKey))}'");
            }
        }
        else if (gameEditor.playerPrefsState == PlayerPrefsState.String)
        {
            if (GUILayout.Button($"Learn '{gameEditor.learnKey}' Key"))
            {
                Debug.Log($"'{gameEditor.learnKey}' Key's value is '{(PlayerPrefs.GetString(gameEditor.learnKey))}'");
            }
        }
        else if (gameEditor.playerPrefsState == PlayerPrefsState.Float)
        {
            if (GUILayout.Button($"Learn '{gameEditor.learnKey}' Key"))
            {
                Debug.Log($"'{gameEditor.learnKey}' Key's value is '{(PlayerPrefs.GetFloat(gameEditor.learnKey))}'");
            }
        }
        GUILayout.EndHorizontal();
        #endregion
        GUILayout.Space(15);
        #region PlayerPrefs Delete
        EditorGUILayout.LabelField("PlayerPrefs Delete", EditorStyles.boldLabel);
        GUILayout.BeginHorizontal();
        gameEditor.deleteKey = EditorGUILayout.TextField("Key Name", gameEditor.deleteKey);

        if (GUILayout.Button("Delete Key"))
        {
            Debug.Log($"{gameEditor.deleteKey} Key Deleted");
            PlayerPrefs.DeleteKey(gameEditor.deleteKey);
        }
        GUILayout.EndHorizontal();

        GUILayout.Space(15);

        if (GUILayout.Button("Delete All Keys"))
        {
            Debug.Log("All Keys Deleted");
            PlayerPrefs.DeleteAll();
        }
        EditorGUILayout.HelpBox("Please be careful when pressing this button. It is an irreversible action.", MessageType.Warning);
        #endregion
        #endregion
        GUILayout.Space(20);
        #region Language
        GUILayout.Label("Language", headerText);
        #region Language Change
        GUILayout.Space(5);
        GUILayout.Label("Language Change", EditorStyles.boldLabel);
        CreateButtons();
        GUILayout.Label("Current Language: " + PlayerPrefs.GetString("language").ToUpper(), infoText);
        #endregion
        GUILayout.Space(15);
        #region Language Add
        GUILayout.Label("Language Add", EditorStyles.boldLabel);
        GUILayout.BeginHorizontal();
        gameEditor.addLanguage = EditorGUILayout.TextField("Language Add", gameEditor.addLanguage);
        if (GUILayout.Button("Add"))
        {
            MultiLang.AddLanguage(gameEditor.addLanguage);
        }
        GUILayout.EndHorizontal();
        #endregion
        GUILayout.Space(15);
        #region Language Delete
        GUILayout.Label("Language Delete", EditorStyles.boldLabel);
        GUILayout.BeginHorizontal();
        gameEditor.deleteLanguage = EditorGUILayout.TextField("Language Delete", gameEditor.deleteLanguage);
        if (GUILayout.Button("Delete"))
        {
            MultiLang.DeleteLanguage(gameEditor.deleteLanguage);
        }
        GUILayout.EndHorizontal();
        #endregion
        #endregion
        serializedObject.ApplyModifiedProperties();
    }
Ejemplo n.º 23
0
        static void Main()
        {
            GameEditor gameEditor = new GameEditor();

            gameEditor.Launch();
        }
Ejemplo n.º 24
0
        public void WriteEventConfig(AnnotationData ann, Events events, RandomizerOptions opt)
        {
            GameEditor editor = new GameEditor(FromGame.DS3);

            editor.Spec.GameDir = "fogdist";
            Dictionary <string, MSB3>  maps   = editor.Load(@"Base", path => ann.Specs.ContainsKey(GameEditor.BaseName(path)) ? MSB3.Read(path) : null, "*.msb.dcx");
            Dictionary <string, EMEVD> emevds = editor.Load(@"Base", path => ann.Specs.ContainsKey(GameEditor.BaseName(path)) || path.Contains("common") ? EMEVD.Read(path) : null, "*.emevd.dcx");

            void deleteEmpty <K, V>(Dictionary <K, V> d)
            {
                foreach (K key in d.Keys.ToList())
                {
                    if (d[key] == null)
                    {
                        d.Remove(key);
                    }
                }
            }

            // Should this be in GameEditor?
            deleteEmpty(maps);
            deleteEmpty(emevds);

            editor.Spec.NameDir = @"fogdist\Names";
            Dictionary <string, string>    modelNames = editor.LoadNames("ModelName", n => n);
            SortedDictionary <int, string> chars      = new SortedDictionary <int, string>(editor.LoadNames("CharaInitParam", n => int.Parse(n)));

            Dictionary <string, List <string> > description          = new Dictionary <string, List <string> >();
            Dictionary <int, string>            entityNames          = new Dictionary <int, string>();
            Dictionary <int, List <int> >       groupIds             = new Dictionary <int, List <int> >();
            Dictionary <(string, string), MSB3.Event.ObjAct> objacts = new Dictionary <(string, string), MSB3.Event.ObjAct>();

            HashSet <int> highlightIds = new HashSet <int>();
            HashSet <int> selectIds    = new HashSet <int>();

            foreach (Entrance e in ann.Warps.Concat(ann.Entrances))
            {
                int id = e.ID;
                AddMulti(description, id.ToString(), (ann.Warps.Contains(e) ? "" : "fog gate ") + e.Text);
                selectIds.Add(e.ID);
                highlightIds.Add(e.ID);
            }
            HashSet <string> gameObjs = new HashSet <string>();

            foreach (GameObject obj in ann.Objects)
            {
                if (int.TryParse(obj.ID, out int id))
                {
                    AddMulti(description, id.ToString(), obj.Text);
                    selectIds.Add(id);
                    highlightIds.Add(id);
                }
                else
                {
                    gameObjs.Add($"{obj.Area}_{obj.ID}");
                }
            }

            Dictionary <string, Dictionary <string, FMG> > fmgs = new GameEditor(FromGame.DS3).LoadBnds($@"msg\engus", (data, name) => FMG.Read(data), ext: "*_dlc2.msgbnd.dcx");

            void addFMG(FMG fmg, string desc)
            {
                foreach (FMG.Entry e in fmg.Entries)
                {
                    if (e.ID > 25000 && !string.IsNullOrWhiteSpace(e.Text))
                    {
                        highlightIds.Add(e.ID);
                        AddMulti(description, e.ID.ToString(), desc + " " + "\"" + e.Text.Replace("\r", "").Replace("\n", "\\n") + "\"");
                    }
                }
            }

            addFMG(fmgs["item_dlc2"]["NPC名"], "name");
            addFMG(fmgs["menu_dlc2"]["イベントテキスト"], "text");

            foreach (KeyValuePair <string, MSB3> entry in maps)
            {
                string map = ann.Specs[entry.Key].Name;
                MSB3   msb = entry.Value;

                foreach (MSB3.Part e in msb.Parts.GetEntries())
                {
                    string shortName = $"{map}_{e.Name}";
                    if (modelNames.TryGetValue(e.ModelName, out string modelDesc))
                    {
                        if (e is MSB3.Part.Enemy en && modelDesc == "Human NPC" && en.CharaInitID > 0)
                        {
                            modelDesc = CharacterName(chars, en.CharaInitID);
                        }
                        else if (e is MSB3.Part.Player)
                        {
                            modelDesc = "Warp Point";
                        }
                        AddMulti(description, shortName, modelDesc);
                    }
                    AddMulti(description, shortName, $"{map} {e.GetType().Name.ToString().ToLowerInvariant()} {e.Name}");  // {(e.EventEntityID > 0 ? $" {e.EventEntityID}" : "")}
                    if (e.EventEntityID > 10)
                    {
                        highlightIds.Add(e.EventEntityID);
                        string idStr = e.EventEntityID.ToString();
                        if (description.ContainsKey(idStr))
                        {
                            AddMulti(description, shortName, description[idStr]);
                        }
                        description[idStr] = description[shortName];
                        if (e is MSB3.Part.Player || e.ModelName == "o000100")
                        {
                            selectIds.Add(e.EventEntityID);
                        }
                        if (selectIds.Contains(e.EventEntityID))
                        {
                            gameObjs.Add(shortName);
                        }

                        foreach (int id in e.EventEntityGroups)
                        {
                            if (id > 0)
                            {
                                AddMulti(groupIds, id, e.EventEntityID);
                                highlightIds.Add(id);
                            }
                        }
                    }
                }
                foreach (MSB3.Region r in msb.Regions.GetEntries())
                {
                    if (r.EventEntityID < 1000000)
                    {
                        continue;
                    }
                    AddMulti(description, r.EventEntityID.ToString(), $"{map} {r.GetType().Name.ToLowerInvariant()} region {r.Name}");
                    highlightIds.Add(r.EventEntityID);
                }
                foreach (MSB3.Event e in msb.Events.GetEntries())
                {
                    if (e is MSB3.Event.ObjAct oa)
                    {
                        // It can be null, basically for commented out objacts
                        string part = oa.PartName ?? oa.PartName2;
                        if (part == null)
                        {
                            continue;
                        }
                        string desc = description.TryGetValue($"{map}_{part}", out List <string> p) ? string.Join(" - ", p) : throw new Exception($"{map} {oa.Name}");
                        objacts[(map, part)] = oa;
Ejemplo n.º 25
0
        public Result FindItems(RandomizerOptions opt, AnnotationData ann, Graph g, Events events, string gameDir, FromGame game)
        {
            Dictionary <string, string>         itemsById = ann.KeyItems.ToDictionary(item => item.ID, item => item.Name);
            Dictionary <string, List <string> > itemAreas = g.ItemAreas;

            if (ann.LotLocations != null)
            {
                GameEditor editor = new GameEditor(game);
                editor.Spec.GameDir = gameDir;
                Dictionary <string, PARAM.Layout> layouts = editor.LoadLayouts();
                Dictionary <string, PARAM>        Params  = editor.LoadParams(layouts);

                Dictionary <int, PARAM.Row> lots = Params["ItemLotParam"].Rows.ToDictionary(r => (int)r.ID, r => r);
                foreach (KeyValuePair <int, string> entry in ann.LotLocations)
                {
                    int lot = entry.Key;
                    if (!g.Areas.ContainsKey(entry.Value))
                    {
                        throw new Exception($"Internal error in lot config for {entry.Key}: {entry.Value} does not exist");
                    }
                    while (true)
                    {
                        // It's also fine to not have a lot defined as long as all key items are found
                        if (!lots.TryGetValue(lot, out PARAM.Row row))
                        {
                            break;
                        }
                        for (int i = 1; i <= 8; i++)
                        {
                            int item = (int)row[$"lotItemId0{i}"].Value;
                            if (item == 0)
                            {
                                continue;
                            }
                            int category = (int)row[$"lotItemCategory0{i}"].Value;
                            category = Universe.LotTypes.TryGetValue((uint)category, out int value) ? value : -1;
                            if (category == -1)
                            {
                                continue;
                            }
                            string id = $"{category}:{item}";
                            if (opt["debuglots"])
                            {
                                Console.WriteLine($"{entry.Key} in {entry.Value} has {id}");
                            }
                            if (itemsById.TryGetValue(id, out string name))
                            {
                                if (itemAreas[name].Count > 0 && itemAreas[name][0] != entry.Value)
                                {
                                    throw new Exception($"Item {name} found in both {itemAreas[name][0]} and {entry.Value}");
                                }
                                itemAreas[name] = new List <string> {
                                    entry.Value
                                };
                            }
                        }
                        lot++;
                    }
                }
            }
            if (ann.Locations != null)
            {
                // It's a bit hacky, but should work from anywhere, probably
                GameEditor editor = new GameEditor(game);
                editor.Spec.GameDir   = $@"fogdist";
                editor.Spec.LayoutDir = $@"fogdist\Layouts";
                editor.Spec.NameDir   = $@"fogdist\Names";

                Dictionary <string, PARAM.Layout> layouts = editor.LoadLayouts();

                int dragonFlag = -1;
                Dictionary <string, PARAM> Params = editor.LoadParams(@"fogdist\Base\Data0.bdt", layouts, true);
                if (gameDir != null)
                {
                    string paramPath = $@"{gameDir}\Data0.bdt";
                    if (File.Exists(paramPath))
                    {
                        Params = editor.LoadParams(paramPath, layouts, true);
                    }

                    string commonEmevdPath = $@"{gameDir}\event\common.emevd.dcx";
                    if (File.Exists(commonEmevdPath))
                    {
                        EMEVD       commonEmevd = EMEVD.Read(commonEmevdPath);
                        EMEVD.Event flagEvent   = commonEmevd.Events.Find(e => e.ID == 13000904);
                        if (flagEvent != null)
                        {
                            Events.Instr check = events.Parse(flagEvent.Instructions[1]);
                            dragonFlag = (int)check[3];
                            if (opt["debuglots"])
                            {
                                Console.WriteLine($"Dragon flag: {dragonFlag}");
                            }
                        }
                    }
                }

                Dictionary <int, PARAM.Row> lots  = Params["ItemLotParam"].Rows.ToDictionary(r => (int)r.ID, r => r);
                Dictionary <int, PARAM.Row> shops = Params["ShopLineupParam"].Rows.ToDictionary(r => (int)r.ID, r => r);

                void setArea(string itemName, List <string> areas)
                {
                    if (opt["debuglots"])
                    {
                        Console.WriteLine($"-- name: {itemName}");
                    }
                    if (itemAreas[itemName].Count > 0 && !itemAreas[itemName].SequenceEqual(areas))
                    {
                        throw new Exception($"Item {itemName} found in both {string.Join(",", itemAreas[itemName])} and {string.Join(",", areas)}");
                    }
                    itemAreas[itemName] = areas;
                }

                foreach (KeyItemLoc loc in ann.Locations.Items)
                {
                    List <string> areas = loc.Area.Split(' ').ToList();
                    if (!areas.All(a => g.Areas.ContainsKey(a) || itemAreas.ContainsKey(a)))
                    {
                        // Currently happens with multi-area intersection lots/shops
                        throw new Exception($"Warning: Areas not found for {loc.Area} - {loc.DebugText[0]}");
                    }
                    List <int> lotIds = loc.Lots == null ? new List <int>() : loc.Lots.Split(' ').Select(i => int.Parse(i)).ToList();
                    foreach (int baseLot in lotIds)
                    {
                        int lot = baseLot;
                        while (true)
                        {
                            // It's also fine to not have a lot defined as long as all key items are found
                            if (!lots.TryGetValue(lot, out PARAM.Row row))
                            {
                                break;
                            }
                            for (int i = 1; i <= 8; i++)
                            {
                                int item = (int)row[$"ItemLotId{i}"].Value;
                                if (item == 0)
                                {
                                    continue;
                                }
                                uint category = (uint)row[$"LotItemCategory0{i}"].Value;
                                if (!Universe.LotTypes.TryGetValue(category, out int catVal))
                                {
                                    continue;
                                }
                                string id = $"{catVal}:{item}";
                                if (opt["debuglots"])
                                {
                                    Console.WriteLine($"lot {lot} in {loc.Area} has {id}");
                                }
                                if (itemsById.TryGetValue(id, out string name))
                                {
                                    setArea(name, areas);
                                }
                            }
                            if (dragonFlag > 0 && (int)row["getItemFlagId"].Value == dragonFlag)
                            {
                                setArea("pathofthedragon", areas);
                            }
                            lot++;
                        }
                    }
                    List <int> shopIds = loc.Shops == null ? new List <int>() : loc.Shops.Split(' ').Select(i => int.Parse(i)).ToList();
                    foreach (int shopId in shopIds)
                    {
                        // Not as fine for a shop to be missing, but also whatever
                        if (!shops.TryGetValue(shopId, out PARAM.Row row))
                        {
                            continue;
                        }
                        int    item   = (int)row["EquipId"].Value;
                        int    catVal = (byte)row["equipType"].Value;
                        string id     = $"{catVal}:{item}";
                        if (opt["debuglots"])
                        {
                            Console.WriteLine($"shop {shopId} in {loc.Area} has {id}");
                        }
                        if (itemsById.TryGetValue(id, out string name))
                        {
                            setArea(name, areas);
                        }
                        if (dragonFlag > 0 && (int)row["EventFlag"].Value == dragonFlag)
                        {
                            setArea("pathofthedragon", areas);
                        }
                    }
                }
            }
            // lots:.*[1-9]\r
            // Iterative approach for items which depend simply on other items
            // Recursion would look a lot nicer but lazy
            bool itemExpanded;

            do
            {
                itemExpanded = false;
                foreach (KeyValuePair <string, List <string> > entry in itemAreas)
                {
                    foreach (string dep in entry.Value.ToList())
                    {
                        if (itemAreas.TryGetValue(dep, out List <string> deps))
                        {
                            entry.Value.Remove(dep);
                            entry.Value.AddRange(deps);
                            itemExpanded = true;
                        }
                    }
                }
            }while (itemExpanded);

            if (opt["explain"] || opt["debuglots"])
            {
                foreach (Item item in ann.KeyItems)
                {
                    Console.WriteLine($"{item.Name} {item.ID}: default {item.Area}, found [{string.Join(", ", itemAreas[item.Name])}]");
                }
            }
            // Collect items in graph
            SortedSet <string> itemRecord = new SortedSet <string>();
            bool randomized = false;

            foreach (Item item in ann.KeyItems)
            {
                if (itemAreas[item.Name].Count == 0)
                {
                    if (item.HasTag("randomonly"))
                    {
                        itemAreas[item.Name] = new List <string> {
                            item.Area
                        };
                    }
                    else if (item.HasTag("hard") && !opt["hard"])
                    {
                        continue;
                    }
                    else
                    {
                        throw new Exception($"Couldn't find {item.Name} in item lots");
                    }
                }
                List <string> areas = itemAreas[item.Name];
                foreach (string area in areas)
                {
                    g.Nodes[area].Items.Add(item.Name);
                }
                if (!item.HasTag("randomonly"))
                {
                    if (areas.Count > 1 || areas[0] != item.Area)
                    {
                        randomized = true;
                    }
                    itemRecord.Add($"{item.Name}={string.Join(",", areas)}");
                }
            }
            return(new Result
            {
                Randomized = randomized,
                ItemHash = (RandomizerOptions.JavaStringHash($"{string.Join(";", itemRecord)}") % 99999).ToString().PadLeft(5, '0')
            });
        }
        public void WriteList(GameData game, Dictionary <int, EnemyInfo> fullInfos)
        {
            // Generate things
            HashSet <int> allBonfires = new HashSet <int>
            {
                1001950,  // Dragonspring - Hirata Estate
                1001951,  // Estate Path
                1001952,  // Bamboo Thicket Slope
                1001953,  // Hirata Estate - Main Hall
                1001955,  // Hirata Audience Chamber
                1001954,  // Hirata Estate - Hidden Temple
                1101950,  // Dilapidated Temple
                1101956,  // Ashina Outskirts
                1101951,  // Outskirts Wall - Gate Path
                1101952,  // Outskirts Wall - Stairway
                1101953,  // Underbridge Valley
                1101954,  // Ashina Castle Gate Fortress
                1101955,  // Ashina Castle Gate
                1101957,  // Flames of Hatred
                1111950,  // Ashina Castle
                1111951,  // Upper Tower - Antechamber
                1111957,  // Upper Tower - Ashina Dojo
                1111952,  // Castle Tower Lookout
                1111953,  // Upper Tower - Kuro's Room
                1111956,  // Old Grave
                1111954,  // Great Serpent Shrine
                1111955,  // Abandoned Dungeon Entrance
                1121951,  // Ashina Reservoir
                1121950,  // Near Secret Passage
                1301950,  // Underground Waterway
                1301951,  // Bottomless Hole
                1701955,  // Ashina Depths
                1701954,  // Poison Pool
                1701956,  // Guardian Ape's Burrow
                1501950,  // Hidden Forest
                1501951,  // Mibu Village
                1501952,  // Water Mill
                1501953,  // Wedding Cave Door
                1701957,  // Under-Shrine Valley
                1701950,  // Sunken Valley
                1701951,  // Gun Fort
                1701952,  // Riven Cave
                1701958,  // Bodhisattva Valley
                1701953,  // Guardian Ape's Watering Hole
                2001950,  // Senpou Temple,  Mt. Kongo
                2001951,  // Shugendo
                2001952,  // Temple Grounds
                2001953,  // Main Hall
                2001954,  // Inner Sanctum
                2001955,  // Sunken Valley Cavern
                2001956,  // Bell Demon's Temple
                2501950,  // Fountainhead Palace
                2501951,  // Vermilion Bridge
                2501956,  // Mibu Manor
                2501952,  // Flower Viewing Stage
                2501953,  // Great Sakura
                2501954,  // Palace Grounds
                2501957,  // Feeding Grounds
                2501958,  // Near Pot Noble
                2501955,  // Sanctuary
            };

            // Probably shouldn't use tuples, but too late now
            List <(int, List <string>, List <int>, List <int>)> paths = new List <(int, List <string>, List <int>, List <int>)>
            {
                // Tutorial
                (1, new List <string> {
                    "ashinareservoir", "ashinacastle"
                },
                 new List <int> {
                    8306, 0
                }, new List <int>
                {
                    1121951,  // Ashina Reservoir
                    1121950,  // Near Secret Passage
                }),
                // First stretch of Ashina Outskirts
                (1, new List <string> {
                    "ashinaoutskirts"
                },
                 new List <int> {
                    8302, 1, 8302, -1, 1100330, 1
                }, new List <int>
                {
                    1101956,  // Ashina Outskirts
                    1101951,  // Outskirts Wall - Gate Path
                    1101952,  // Outskirts Wall - Stairway
                }),
                // Ashina Outskirts up to Blazing Bull
                (1, new List <string> {
                    "ashinaoutskirts", "ashinacastle"
                },
                 new List <int> {
                    8302, 1, 8302, -1, 8301, 1, 8301, -1, 1100330, 1
                }, new List <int>
                {
                    1101952,  // Outskirts Wall - Stairway
                    1101953,  // Underbridge Valley
                    1101954,  // Ashina Castle Gate Fortress
                    1101955,  // Ashina Castle Gate
                    // 1111950,  // Ashina Castle
                }),
                // Hirata 1
                (1, new List <string> {
                    "hirata"
                },
                 new List <int> {
                    1000353, 1, 1005601, 1, 1000301, 1, 1000900, 1
                }, new List <int>
                {
                    1001950,  // Dragonspring - Hirata Estate
                    1001951,  // Estate Path
                    1001952,  // Bamboo Thicket Slope
                    1001953,  // Hirata Estate - Main Hall
                    1001955,  // Hirata Audience Chamber
                    1001954,  // Hirata Estate - Hidden Temple
                }),
                // Ashina Castle to Genichiro
                (2, new List <string> {
                    "ashinacastle"
                },
                 new List <int> {
                    8301, 1, 8302, 1, 8302, -1
                }, new List <int>
                {
                    1111950,  // Ashina Castle
                    1111951,  // Upper Tower - Antechamber
                    1111957,  // Upper Tower - Ashina Dojo
                    1111952,  // Castle Tower Lookout
                }),
                // Ashina Castle to Reservoir to Dungeon
                (2, new List <string> {
                    "ashinareservoir"
                },
                 new List <int> {
                    8302, 1, 1120300, 0
                }, new List <int>
                {
                    1111950,  // Ashina Castle
                    1121951,  // Ashina Reservoir
                    1301951,  // Bottomless Hole
                }),
                // Dungeon
                (2, new List <string> {
                    "dungeon"
                },
                 new List <int> {
                }, new List <int>
                {
                    1111955,  // Abandoned Dungeon Entrance
                    1301950,  // Underground Waterway
                    1301951,  // Bottomless Hole
                }),
                // Senpou temple
                (2, new List <string> {
                    "senpou"
                },
                 new List <int> {
                }, new List <int>
                {
                    2001950,  // Senpou Temple,  Mt. Kongo
                    2001951,  // Shugendo
                    2001952,  // Temple Grounds
                    2001953,  // Main Hall
                }),
                // Hidden Forest to Water Mill
                (3, new List <string> {
                    "mibuvillage"
                },
                 new List <int> {
                    1700850, 1, 1700520, 1
                }, new List <int>
                {
                    1501950,  // Hidden Forest
                    1501951,  // Mibu Village
                    1501952,  // Water Mill
                }),
                // End of Ashina Depths
                (3, new List <string> {
                    "mibuvillage"
                },
                 new List <int> {
                }, new List <int>
                {
                    1501952,  // Water Mill
                    1501953,  // Wedding Cave Door
                }),
                // Most of Sunken Valley
                (3, new List <string> {
                    "ashinacastle", "sunkenvalley"
                },
                 new List <int> {
                    8301, 1, 8301, -1, 8302, 1, 8302, -1
                }, new List <int>
                {
                    1111952,  // Castle Tower Lookout
                    1111956,  // Old Grave
                    1111954,  // Great Serpent Shrine
                    1701957,  // Under-Shrine Valley
                    1701950,  // Sunken Valley
                    1701951,  // Gun Fort
                    1701952,  // Riven Cave
                    1701958,  // Bodhisattva Valley
                    1701953,  // Guardian Ape's Watering Hole
                }),
                // Sunken Valley to Poison Pool path
                (3, new List <string> {
                    "sunkenvalley"
                },
                 new List <int> {
                    1700850, 0, 1700520, 0
                }, new List <int>
                {
                    1701958,  // Bodhisattva Valley
                    1701954,  // Poison Pool
                    1701956,  // Guardian Ape's Burrow
                }),
                // Ashina Castle Revisited, also down to Masanaga
                (4, new List <string> {
                    "ashinacastle"
                },
                 new List <int> {
                    8301, 0, 8302, 1, 8302, -1
                }, new List <int>
                {
                    1111955,  // Abandoned Dungeon Entrance
                    1111950,  // Ashina Castle
                    1111951,  // Upper Tower - Antechamber
                    1111957,  // Upper Tower - Ashina Dojo
                    1111952,  // Castle Tower Lookout
                    1111956,  // Old Grave
                    1111954,  // Great Serpent Shrine
                }),
                // Fountainhead
                (5, new List <string> {
                    "fountainhead"
                },
                 new List <int> {
                }, new List <int>
                {
                    2501950,  // Fountainhead Palace
                    2501951,  // Vermilion Bridge
                    2501956,  // Mibu Manor
                    2501952,  // Flower Viewing Stage
                    2501958,  // Near Pot Noble
                    2501953,  // Great Sakura
                    2501954,  // Palace Grounds
                    2501955,  // Sanctuary
                }),
                // Hirata Revisited
                (5, new List <string> {
                    "hirata"
                },
                 new List <int> {
                    1000353, 0, 1005601, 0, 1000301, 0, 1000900, 0
                }, new List <int>
                {
                    1001952,  // Bamboo Thicket Slope
                    1001953,  // Hirata Estate - Main Hall
                    1001955,  // Hirata Audience Chamber
                    1001954,  // Hirata Estate - Hidden Temple
                }),
                // Ashina Castle End to Outskirts
                (5, new List <string> {
                    "ashinacastle", "ashinaoutskirts"
                },
                 new List <int> {
                    8302, 0
                }, new List <int>
                {
                    1111953,  // Upper Tower - Kuro's Room
                    1111956,  // Old Grave
                    1101952,  // Outskirts Wall - Stairway
                    1101951,  // Outskirts Wall - Gate Path
                }),
                // Ashina Castle End to Reservoir
                (5, new List <string> {
                    "ashinacastle", "ashinareservoir"
                },
                 new List <int> {
                    8302, 0
                }, new List <int>
                {
                    1111953,  // Upper Tower - Kuro's Room
                    1111957,  // Upper Tower - Ashina Dojo
                    1111951,  // Upper Tower - Antechamber
                    1111950,  // Ashina Castle
                    1121951,  // Ashina Reservoir
                    1121950,  // Near Secret Passage
                }),
            };
            FMG bonfires = new GameEditor(GameSpec.FromGame.SDT).LoadBnd(@"C:\Program Files (x86)\Steam\steamapps\common\Sekiro\msg\engus\menu.msgbnd.dcx", (p, n) => FMG.Read(p))["NTC_\u30e1\u30cb\u30e5\u30fc\u30c6\u30ad\u30b9\u30c8"];
            Dictionary <int, string> names = new Dictionary <int, string>();

            foreach (PARAM.Row r in game.Params["BonfireWarpParam"].Rows)
            {
                // break;
                int    entity  = (int)r["WarpEventId"].Value;
                string bonfire = bonfires[(int)r["BonfireNameId"].Value];
                if (bonfire != null && entity > 0)
                {
                    names[entity] = bonfire;
                    // Console.WriteLine($"{entity},  // {bonfire}");
                }
            }
            Dictionary <int, Vector3> points = new Dictionary <int, Vector3>();

            // Find location of all bonfires
            foreach (KeyValuePair <string, MSBS> entry in game.Smaps)
            {
                if (!game.Locations.ContainsKey(entry.Key))
                {
                    continue;
                }
                string map = game.Locations[entry.Key];
                MSBS   msb = entry.Value;
                foreach (MSBS.Part.Object e in msb.Parts.Objects)
                {
                    if (allBonfires.Contains(e.EntityID))
                    {
                        points[e.EntityID] = e.Position;
                    }
                }
            }
            string pathText(int p)
            {
                int first = paths[p].Item4.First();
                int last  = paths[p].Item4.Last();

                return($"#{paths[p].Item1} {names[first]}->{names[last]}");
            }

            bool investigateScaling = false;
            List <List <EnemyClass> > typeGroups = new List <List <EnemyClass> >
            {
                new List <EnemyClass> {
                    EnemyClass.Boss, EnemyClass.TutorialBoss
                },
                new List <EnemyClass> {
                    EnemyClass.Miniboss
                },
                new List <EnemyClass> {
                    EnemyClass.Basic, EnemyClass.FoldingMonkey, EnemyClass.OldDragon
                }
            };
            List <EnemyClass>           types = typeGroups.SelectMany(c => c).ToList();
            Dictionary <int, EnemyInfo> infos = fullInfos.Values.Where(e => types.Contains(e.Class)).ToDictionary(e => e.ID, e => e);

            if (!investigateScaling)
            {
                infos.Remove(1110920);
                infos.Remove(1110900);
                infos.Remove(1120800);
            }
            Dictionary <int, List <int> > possiblePaths = new Dictionary <int, List <int> >();
            bool explainCat = false;

            for (int i = 0; i < paths.Count; i++)
            {
                if (explainCat)
                {
                    Console.WriteLine($"--- Processing {pathText(i)}");
                }
                (int section, List <string> maps, List <int> cond, List <int> order) = paths[i];
                Dictionary <int, List <int> > eventFlags = new Dictionary <int, List <int> >();
                HashSet <int> excludeEntity = new HashSet <int>();
                HashSet <int> expectEntity  = new HashSet <int>();
                HashSet <int> getEntity     = new HashSet <int>();
                for (int j = 0; j < cond.Count; j += 2)
                {
                    int check = cond[j];
                    int val   = cond[j + 1];
                    if (check >= 1000000)
                    {
                        if (val == 0)
                        {
                            expectEntity.Add(check);
                        }
                        else
                        {
                            excludeEntity.Add(check);
                        }
                    }
                    else
                    {
                        AddMulti(eventFlags, check, val);
                    }
                }
                foreach (KeyValuePair <string, MSBS> entry in game.Smaps)
                {
                    if (!game.Locations.ContainsKey(entry.Key))
                    {
                        continue;
                    }
                    string map = game.Locations[entry.Key];
                    MSBS   msb = entry.Value;

                    if (!maps.Contains(map))
                    {
                        continue;
                    }
                    foreach (MSBS.Part.Enemy e in msb.Parts.Enemies)
                    {
                        if (!infos.ContainsKey(e.EntityID))
                        {
                            continue;
                        }
                        points[e.EntityID] = e.Position;
                        names[e.EntityID]  = game.ModelName(e.ModelName);
                        List <int> ids = new List <int> {
                            e.EntityID
                        };
                        ids.AddRange(e.EntityGroupIDs.Where(id => id > 0));
                        if (excludeEntity.Overlaps(ids))
                        {
                            if (explainCat)
                            {
                                Console.WriteLine($"excluded: {string.Join(",", ids)} from {string.Join(",", excludeEntity)}");
                            }
                            continue;
                        }
                        else if (expectEntity.Overlaps(ids))
                        {
                            getEntity.UnionWith(ids);
                        }
                        else if (eventFlags.Count > 0)
                        {
                            // If not explicitly expected, do a check for game progression
                            Dictionary <int, int> flags = new Dictionary <int, int>();
                            if (e.EventFlagID != -1)
                            {
                                flags[e.EventFlagID] = e.EventFlagCompareState;
                            }
                            if (e.UnkT48 != -1)
                            {
                                flags[e.UnkT48] = e.UnkT4C;
                            }
                            if (e.UnkT50 != -1)
                            {
                                flags[e.UnkT50] = 1;
                            }
                            bool mismatch = false;
                            foreach (KeyValuePair <int, List <int> > flagPair in eventFlags)
                            {
                                int        flag = flagPair.Key;
                                List <int> cmps = flagPair.Value;
                                int        cmp  = flags.TryGetValue(flag, out int tmp) ? tmp : -1;
                                if (explainCat && e.EntityID == 9999999)
                                {
                                    Console.WriteLine($"for {e.EntityID} expected {flag} = {string.Join(",", cmps)}, found result {cmp}");
                                }
                                if (!cmps.Contains(cmp))
                                {
                                    if (explainCat)
                                    {
                                        Console.WriteLine($"excluded: {string.Join(",", ids)} with {flag} = {cmp} (not {string.Join(",", cmps)})");
                                    }
                                    mismatch = true;
                                }
                            }
                            if (mismatch)
                            {
                                continue;
                            }
                        }
                        if (explainCat)
                        {
                            Console.WriteLine($"added: {string.Join(",", ids)}");
                        }
                        AddMulti(possiblePaths, e.EntityID, i);
                    }
                }
                List <int> missing = expectEntity.Except(getEntity).ToList();
                if (missing.Count > 0)
                {
                    throw new Exception($"Missing {string.Join(",", missing)} in {string.Join(",", maps)}");
                }
            }
            // Hardcode headless into Senpou, because it is out of the way and sort of a singleton
            possiblePaths[1100330] = new List <int> {
                7
            };

            Console.WriteLine("Categories");
            Dictionary <int, (int, float)> chosenPath = new Dictionary <int, (int, float)>();

            foreach (EnemyInfo info in infos.Values)
            {
                if (!possiblePaths.TryGetValue(info.ID, out List <int> pathList))
                {
                    throw new Exception($"{info.ID} has no categorization: {info.DebugText}");
                }
                if (paths[pathList[0]].Item2.Contains("hirata"))
                {
                    // If Hirata, greedily choose pre-revisited Hirata
                    pathList = new List <int> {
                        pathList[0]
                    };
                }
                float   score = float.PositiveInfinity;
                Vector3 pos   = points[info.ID];
                foreach (int path in pathList)
                {
                    (int section, List <string> maps, List <int> cond, List <int> order) = paths[path];
                    for (int i = 0; i < order.Count - 1; i++)
                    {
                        Vector3 p1    = points[order[i]];
                        Vector3 p2    = points[order[i + 1]];
                        float   dist1 = Vector3.Distance(p1, pos);
                        float   dist2 = Vector3.Distance(p2, pos);
                        float   dist  = dist1 + dist2;
                        if (info.ID == 9999999)
                        {
                            Console.WriteLine($"Found dist {dist1} to {names[order[i]]}, and {dist2} to {names[order[i + 1]]}. TOTAL {dist}");
                        }
                        if (dist < score)
                        {
                            score = dist;
                            chosenPath[info.ID] = (path, i + Vector3.Distance(p1, pos) / dist);
                        }
                    }
                }
                if (float.IsInfinity(score))
                {
                    throw new Exception($"{info.ID} with paths {string.Join(",", pathList.Select(pathText))} had nothing checked for it");
                }
            }

            // Put bosses in phase order
            foreach (int id in chosenPath.Keys.ToList())
            {
                EnemyInfo info = infos[id];
                if (info.Class == EnemyClass.Boss && info.OwnedBy != 0)
                {
                    chosenPath[id] = chosenPath[info.OwnedBy];
                }
            }

            if (investigateScaling)
            {
                Dictionary <int, MSBS.Part.Enemy> enemies = new Dictionary <int, MSBS.Part.Enemy>();
                foreach (KeyValuePair <string, MSBS> entry in game.Smaps)
                {
                    if (!game.Locations.ContainsKey(entry.Key))
                    {
                        continue;
                    }
                    string map = game.Locations[entry.Key];
                    MSBS   msb = entry.Value;
                    foreach (MSBS.Part.Enemy e in msb.Parts.Enemies)
                    {
                        enemies[e.EntityID] = e;
                    }
                }

                // Exclude these from scaling considerations, since they are not really part of the area (meant for when visiting later)
                HashSet <int> phantomGroups = new HashSet <int>
                {
                    // Ashina phantoms
                    1505201, 1505211, 1705200, 1705201, 2005200, 2005201,
                    // Sunken Valley phantoms
                    1505202, 1505212, 2005210, 2005211,
                    // Mibu Village phantoms
                    1705220, 1705221, 2005220, 2005221,
                };

                // haveSoulRate Unk85: NG+ only
                // EventFlagId: used for scaling speffect
                // There are these overall groups: vitality, damage, experience, cash. (is there haveSoulRate for cash/xp? maybe it's Unk85)
                List <string> scaleSp   = "maxHpRate maxStaminaCutRate physAtkPowerRate magicAtkPowerRate fireAtkPowerRate thunderAtkPowerRate staminaAttackRate darkAttackPowerRate NewGameBonusUnk".Split(' ').ToList();
                List <string> scaleNpc  = "Hp getSoul stamina staminaRecoverBaseVal Experience".Split(' ').ToList();
                List <string> allFields = scaleSp.Concat(scaleNpc).ToList();
                // Disp: ModelDispMask0 -> ModelDispMask31
                // Npc param has GameClearSpEffectID
                Dictionary <(string, int, int), List <float> > allScales = new Dictionary <(string, int, int), List <float> >();
                Dictionary <int, int> allSections = new Dictionary <int, int>();
                foreach (List <EnemyClass> typeGroup in typeGroups)
                {
                    // Consider two enemies the same if they have the same think id, or same disp mask
                    // Or for minibosses, if they are just the same model, that's probably fine
                    Dictionary <string, List <int> > thinks = new Dictionary <string, List <int> >();
                    Dictionary <string, List <int> > masks  = new Dictionary <string, List <int> >();
                    Dictionary <string, List <int> > bosses = new Dictionary <string, List <int> >();
                    List <string>         order             = new List <string>();
                    Dictionary <int, int> sections          = new Dictionary <int, int>();
                    foreach (KeyValuePair <int, (int, float)> entry in chosenPath.OrderBy(e => (e.Value, e.Key)))
                    {
                        int       id   = entry.Key;
                        EnemyInfo info = infos[id];
                        if (!typeGroup.Contains(info.Class))
                        {
                            continue;
                        }
                        MSBS.Part.Enemy e       = enemies[id];
                        int             path    = entry.Value.Item1;
                        int             section = paths[path].Item1;
                        sections[id]    = section;
                        allSections[id] = section;
                        if (e.EntityGroupIDs.Any(g => phantomGroups.Contains(g)))
                        {
                            continue;
                        }
                        string model = game.ModelName(e.ModelName);
                        if (typeGroup.Contains(EnemyClass.Miniboss) || typeGroup.Contains(EnemyClass.Boss))
                        {
                            AddMulti(bosses, model, id);
                            continue;
                        }
                        string think = $"{model} {e.ThinkParamID}";
                        AddMulti(thinks, think, id);
                        PARAM.Row npc = game.Params["NpcParam"][e.NPCParamID];
                        if (e.NPCParamID > 0 && npc != null)
                        {
                            uint mask = 0;
                            for (int i = 0; i < 32; i++)
                            {
                                if ((byte)npc[$"ModelDispMask{i}"].Value == 1)
                                {
                                    mask |= ((uint)1 << i);
                                }
                            }
                            string dispMask = $"{model} 0x{mask:X8}";
                            AddMulti(masks, dispMask, id);
                        }
                    }
                    foreach (KeyValuePair <string, List <int> > entry in thinks.Concat(masks.Concat(bosses)))
                    {
                        if (entry.Value.Count == 1)
                        {
                            continue;
                        }
                        List <int> secs = entry.Value.Select(i => sections[i]).Distinct().ToList();
                        if (secs.Count == 1)
                        {
                            continue;
                        }

                        Console.WriteLine($"{entry.Key}: {string.Join(",", entry.Value.Select(i => $"{i}[{sections[i]}]"))}");
                        SortedDictionary <string, List <(int, float)> > fieldValues = new SortedDictionary <string, List <(int, float)> >();
                        foreach (int id in entry.Value)
                        {
                            MSBS.Part.Enemy e   = enemies[id];
                            PARAM.Row       npc = game.Params["NpcParam"][e.NPCParamID];
                            if (e.NPCParamID == 0 || npc == null)
                            {
                                continue;
                            }
                            Dictionary <string, float> values = new Dictionary <string, float>();
                            foreach (string f in scaleNpc)
                            {
                                values[f] = float.Parse(npc[f].Value.ToString());
                            }
                            int       spVal = (int)npc["EventFlagId"].Value; // GameClearSpEffectID is for NG+ only, or time-of-day only, or something like that
                            PARAM.Row sp    = game.Params["SpEffectParam"][spVal];
                            if (spVal > 0 && sp != null)
                            {
                                foreach (string f in scaleSp)
                                {
                                    values[f] = float.Parse(sp[f].Value.ToString());
                                }
                            }
                            foreach (KeyValuePair <string, float> val in values)
                            {
                                AddMulti(fieldValues, val.Key, (sections[id], val.Value));
                            }
                        }
                        foreach (KeyValuePair <string, List <(int, float)> > val in fieldValues)
                        {
                            // Console.WriteLine($"  {val.Key}: {string.Join(", ", val.Value.OrderBy(v => v).Select(v => $"[{v.Item1}]{v.Item2}"))}");
                            Dictionary <int, float> bySection = val.Value.GroupBy(v => v.Item1).ToDictionary(g => g.Key, g => g.Select(v => v.Item2).Average());
                            List <string>           sorts     = new List <string>();
                            foreach (int i in bySection.Keys)
                            {
                                foreach (int j in bySection.Keys)
                                {
                                    if (i >= j)
                                    {
                                        continue;
                                    }
                                    float ratio = bySection[j] / bySection[i];
                                    if (float.IsNaN(ratio) || float.IsInfinity(ratio) || ratio == 1 || ratio == 0)
                                    {
                                        continue;
                                    }
                                    sorts.Add($"{i}{j}: {ratio:f3}x");
                                    AddMulti(allScales, (val.Key, i, j), ratio);
                                    // Can be used for complete table, but easier to leave out for lower diagonal
                                    // AddMulti(allScales, (val.Key, j, i), 1 / ratio);
                                }
                            }
                            if (sorts.Count > 0)
                            {
                                Console.WriteLine($"  {val.Key}: {string.Join(", ", sorts)}");
                            }
                        }
                    }
                }
                foreach (string field in allFields)
                {
                    Console.WriteLine($"-- {field} ({allScales.Where(k => k.Key.Item1 == field).Sum(e => e.Value.Count)})");
                    for (int i = 1; i <= 5; i++)
                    {
                        // row: the target class. column: the source class. value: how much to multiply to place the source in the target.
                        Console.WriteLine("  " + string.Join(" ", Enumerable.Range(1, 5).Select(j => allScales.TryGetValue((field, j, i), out List <float> floats) ? $"{floats.Average():f5}," : "        ")));
                    }
                }
                foreach (EnemyInfo info in fullInfos.Values)
                {
                    if (!allSections.ContainsKey(info.ID) && info.Class == EnemyClass.Helper && allSections.TryGetValue(info.OwnedBy, out int section))
                    {
                        allSections[info.ID] = section;
                    }
                }
                foreach (KeyValuePair <int, int> entry in allSections.OrderBy(e => (e.Value, e.Key)))
                {
                    Console.WriteLine($"  {entry.Key}: {entry.Value}");
                }
            }

            bool debugOutput = false;

            foreach (List <EnemyClass> typeGroup in typeGroups)
            {
                List <string> order = new List <string>();
                foreach (KeyValuePair <int, (int, float)> entry in chosenPath.OrderBy(e => (e.Value, e.Key)))
                {
                    int       id   = entry.Key;
                    EnemyInfo info = infos[id];
                    if (!typeGroup.Contains(info.Class))
                    {
                        continue;
                    }
                    if (debugOutput)
                    {
                        Console.WriteLine($"{info.DebugText}\n- {pathText(entry.Value.Item1)}, progress {entry.Value.Item2}\n");
                    }
                    order.Add($"{info.ExtraName ?? names[id]} {id}");
                }
                for (int i = 0; i < order.Count; i++)
                {
                    if (!debugOutput)
                    {
                        Console.WriteLine($"  {order[i]}: {order[order.Count - 1 - i]}");
                    }
                }
            }
        }
Ejemplo n.º 27
0
        public void RandomizeTrees(Random random, Permutation permutation, SkillSplitter.Assignment split)
        {
            // >= 700: prosthetics
            // < 400: skills before mushin
            GameEditor editor = game.Editor;
            PARAM      param  = game.Params["SkillParam"];

            // Orderings for skills which completely supersede each other. (For prosthetics, just use their natural id ordering)
            Dictionary <int, int> skillOrderings = new Dictionary <int, int>
            {
                [110] = 111,  // Nightjar slash
                [210] = 211,  // Ichimonji
                [310] = 311,  // Praying Strikes
            };
            Dictionary <int, ItemKey> texts = new Dictionary <int, ItemKey>
            {
                [0] = game.ItemForName("Shinobi Esoteric Text"),
                [1] = game.ItemForName("Prosthetic Esoteric Text"),
                [2] = game.ItemForName("Ashina Esoteric Text"),
                [3] = game.ItemForName("Senpou Esoteric Text"),
                // [4] = game.ItemForName("Mushin Esoteric Text"),
            };
            SortedDictionary <ItemKey, string> names = game.Names();

            string descName(int desc)
            {
                return(names[new ItemKey(ItemType.WEAPON, desc)]);
            }

            Dictionary <int, SkillData>     allData    = new Dictionary <int, SkillData>();
            Dictionary <int, SkillSlot>     allSlots   = new Dictionary <int, SkillSlot>();
            Dictionary <ItemKey, SkillData> skillItems = new Dictionary <ItemKey, SkillData>();
            List <SkillData> skills          = new List <SkillData>();
            List <SkillSlot> skillSlots      = new List <SkillSlot>();
            List <SkillData> prosthetics     = new List <SkillData>();
            List <SkillSlot> prostheticSlots = new List <SkillSlot>();

            bool explain = false;

            foreach (PARAM.Row r in param.Rows)
            {
                SkillData data = new SkillData
                {
                    ID           = (int)r.ID,
                    Item         = (int)r["SkilLDescriptionId"].Value,
                    Equip        = (int)r["Unk1"].Value,
                    Flag         = (int)r["EventFlagId"].Value,
                    Placeholder  = (int)r["Unk5"].Value,
                    SpEffects    = new[] { (int)r["Unk2"].Value, (int)r["Unk3"].Value },
                    EmblemChange = (byte)r["Unk10"].Value != 0,
                };
                data.Key             = new ItemKey(ItemType.WEAPON, data.Item);
                skillItems[data.Key] = data;
                SkillSlot slot = new SkillSlot
                {
                    ID   = (int)r.ID,
                    Col  = (short)r["MenuDisplayPositionIndexXZ"].Value,
                    Row  = (short)r["MenuDisplayPositionIndexY"].Value,
                    Text = data.ID < 400 && texts.TryGetValue((byte)r["Unk7"].Value, out ItemKey text) ? text : null,
                };
                if (explain)
                {
                    Console.WriteLine($"{r.ID}: {data.Item}, {data.Equip}, {descName(data.Item)}");
                }
                if (data.ID < 400)
                {
                    skills.Add(data);
                    skillSlots.Add(slot);
                }
                else if (data.ID >= 700)
                {
                    prosthetics.Add(data);
                    prostheticSlots.Add(slot);
                }
                allData[data.ID]  = data;
                allSlots[slot.ID] = slot;
            }
            void applyData(PARAM.Row r, SkillData data)
            {
                r["SkilLDescriptionId"].Value = data.Item;
                r["EventFlagId"].Value        = data.Flag;
                r["Unk1"].Value  = data.Equip;
                r["Unk2"].Value  = data.SpEffects[0];
                r["Unk3"].Value  = data.SpEffects[0];
                r["Unk5"].Value  = data.Placeholder;
                r["Unk10"].Value = (byte)(data.EmblemChange ? 1 : 0);
            }

            Shuffle(random, skills);
            Shuffle(random, skillSlots);
            Shuffle(random, prosthetics);
            Shuffle(random, prostheticSlots);

            // Skills rando
            if (split == null)
            {
                Dictionary <ItemKey, string> textWeight    = new Dictionary <ItemKey, string>();
                Dictionary <ItemKey, string> textLocations = texts.Values.ToDictionary(t => t, t => {
                    SlotKey target    = permutation.GetFiniteTargetKey(t);
                    textWeight[t]     = permutation.GetLogOrder(target);
                    SlotAnnotation sn = ann.Slot(data.Location(target).LocScope);
                    if (explain)
                    {
                        Console.WriteLine($"{game.Name(t)} in {sn.Area} - {sn.Text}. Lateness {(permutation.ItemLateness.TryGetValue(t, out double val) ? val : -1)}");
                    }
                    return(sn.Area);
                });