Esempio n. 1
0
        private void LoadScripts()
        {
            Emevds = Editor.Load("Base", path => EMEVD.Read(path), "*.emevd.dcx");
            MaybeOverrideFromModDir(Emevds, name => $@"event\{name}.emevd.dcx", path => EMEVD.Read(path));
            List <string> missing = Locations.Keys.Concat(new[] { "common", "common_func" }).Except(Emevds.Keys).ToList();

            if (missing.Count != 0)
            {
                throw new Exception($@"Missing emevds in dist\Base: {string.Join(", ", missing)}");
            }
        }
Esempio n. 2
0
 public EventScripter(string file, InstructionDocs docs, EMEVD evd = null)
 {
     EMEVDPath = file;
     this.docs = docs;
     EVD       = evd ?? EMEVD.Read(file);
     if (File.Exists(file.Replace(".emevd", ".emeld")))
     {
         try
         {
             ELD = EMELD.Read(file.Replace(".emevd", ".emeld"));
         }
         catch
         {
         }
     }
     InitAll();
 }
Esempio n. 3
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);
        }
Esempio n. 4
0
        public void GenerateAbyssBattleMSB(int mapLevel, List <int> bossCategoriesUsed)
        {
            // Basically, create thin New Londo MSB containing boss, then enable "done" flag.
            // EMEVD handles the rest.

            Map   newLondo = Maps.GetMap("NewLondoRuins");
            MSB1  msb      = MSB1.Read(ModMSBPath + $"{newLondo.MsbName}.msb");
            EMEVD emevd    = EMEVD.Read(ModEMEVDPath + $"{newLondo.EmevdName}.emevd.dcx");

            EMEVD.Event     constructor   = emevd.Events.Where(evt => evt.ID == 0).First();
            HashSet <int>   enemyModels   = new HashSet <int>();
            HashSet <int>   logicGoalIDs  = new HashSet <int>();
            HashSet <int>   battleGoalIDs = new HashSet <int>();
            MapPointManager pointManager  = new MapPointManager(newLondo.Name, Rand);

            // ABYSS BOSS
            int bossEntityID = newLondo.GetBossID(0);

            if (!Maps.BossLocations.Keys.Contains(bossEntityID))
            {
                throw new Exception($"Could not find information about location for (non-twin) boss ID {bossEntityID}.");
            }
            (GamePoint bossPoint, int emevdIndex) = Maps.BossLocations[bossEntityID];
            Boss  boss      = EnemyGenerator.GetRandomBoss(Rand, bossPoint.ArenaSize, bossCategoriesUsed);
            Enemy bossEnemy = EnemyGenerator.GetEnemy(boss.Name);

            MSB1.Part.Enemy bossPart = bossEnemy.GetMSBPart("Abyss Boss", bossEntityID, bossPoint, bossPoint.Angle, mapLevel, isRedPhantom: true);
            msb.Parts.Enemies.Add(bossPart);
            enemyModels.Add(bossEnemy.ModelID);
            logicGoalIDs.Add(Mod.GPARAM.AI[bossPart.ThinkParamID].LogicID);
            battleGoalIDs.Add(Mod.GPARAM.AI[bossPart.ThinkParamID].BattleID);

            // TWIN BOSSES:
            // If arena is Large or Giant, there's a chance of a twin boss (capping their combined aggression).
            // Mornstein ring makes it 100% chance.
            Boss twinBoss = null;

            if ((Roll(TwinBossOdds) || Run.GetFlag(GameFlag.MornsteinRingFlag)) && Maps.BossLocations.ContainsKey(bossEntityID + 1) && bossPoint.ArenaSize.In(ArenaSize.Large, ArenaSize.Giant) && boss.AggressionLevel < 5)
            {
                (GamePoint twinBossPoint, _) = Maps.BossLocations[bossEntityID + 1];
                List <Boss> twinOptions = new List <Boss>(EnemyGenerator.BossList.Where(
                                                              twin => twin.Category != boss.Category && twin.AggressionLevel + boss.AggressionLevel <= 5 && twin.RequiredArenaSize <= bossPoint.ArenaSize));
                twinBoss = twinOptions.GetRandomElement(Rand);

                Enemy twinBossEnemy = EnemyGenerator.GetEnemy(twinBoss.Name);

                var twinPart = twinBossEnemy.GetMSBPart($"Abyss Boss Twin", bossEntityID + 1, twinBossPoint, twinBossPoint.Angle, mapLevel, isRedPhantom: true);
                msb.Parts.Enemies.Add(twinPart);
                logicGoalIDs.Add(Mod.GPARAM.AI[twinPart.ThinkParamID].LogicID);
                battleGoalIDs.Add(Mod.GPARAM.AI[twinPart.ThinkParamID].BattleID);
                Run.BossCategoriesUsed.Add(twinBoss.Category);
                enemyModels.Add(twinBossEnemy.ModelID);

                Run.EnableFlag(newLondo.GetBossTwinFlag(0));
            }
            else
            {
                Run.DisableFlag(newLondo.GetBossTwinFlag(0));
            }

            // Inject name and item lot reward into EMEVD constructor.
            EMEVD.Instruction bossBattleCall = constructor.Instructions[emevdIndex];
            List <byte>       argData        = bossBattleCall.ArgData.ToList();
            int itemLotID = bossEnemy.RedPhantomItemLotParamID;

            argData[32] = (byte)itemLotID;
            argData[33] = (byte)(itemLotID >> 0x08);
            argData[34] = (byte)(itemLotID >> 0x10);
            argData[35] = (byte)(itemLotID >> 0x18);
            argData[52] = (byte)boss.NameTextID;
            argData[53] = (byte)(boss.NameTextID >> 0x08);
            if (twinBoss != null)
            {
                argData[54] = (byte)twinBoss.NameTextID;
                argData[55] = (byte)(twinBoss.NameTextID >> 0x08);
            }
            bossBattleCall.ArgData = argData.ToArray();

#if DEBUG
            Console.WriteLine($"    Updating models...");
#endif
            UpdateMSBModels(msb);

#if DEBUG
            Console.WriteLine($"    Writing MSB...");
#endif
            msb.Write(Mod.GameDir + $@"map\MapStudio\{newLondo.MsbName}.msb");  // No '.rls' copy needed.
#if DEBUG
            Console.WriteLine($"    Writing EMEVD...");
#endif
            emevd.Write(Mod.GameDir + $@"event\{newLondo.EmevdName}.emevd.dcx");

#if DEBUG
            Console.WriteLine($"    Writing LUABND...");
#endif
            WriteMapLUABND(newLondo.EmevdName, battleGoalIDs, logicGoalIDs);

#if DEBUG
            Console.WriteLine($"    Writing FFXBND...");
#endif
            WriteMapFFXBND(newLondo.MapID[0], enemyModels);
        }
Esempio n. 5
0
        public void GenerateMapData(Map map, double redPhantomOdds)
        {
            // Generate and write MSB, LUABND, and tweaked EMEVD for given map.

#if DEBUG
            Console.WriteLine($"GENERATING MAP: {map.Name}");
#endif

            MSB1  msb   = MSB1.Read(ModMSBPath + $"{map.MsbName}.msb");
            EMEVD emevd = EMEVD.Read(ModEMEVDPath + $"{map.EmevdName}.emevd.dcx");

            EMEVD.Event constructor = emevd.Events.Where(evt => evt.ID == 0).First();

            HashSet <int> enemyModels   = new HashSet <int>();
            HashSet <int> logicGoalIDs  = new HashSet <int>(map.DefaultLogicScriptIDs);
            HashSet <int> battleGoalIDs = new HashSet <int>(map.DefaultBattleScriptIDs);

            MapPointManager pointManager      = new MapPointManager(map.Name, Rand);
            int             rarePhantomCount  = 0;
            int             basicPhantomCount = 0;

            string regionLabel;

            for (int i = 0; i < map.BossCount; i++)
            {
                // BOSSES

                int bossEntityID = map.GetBossID(i);
                if (!Maps.BossLocations.Keys.Contains(bossEntityID))
                {
                    throw new Exception($"Could not find information about location for (non-twin) boss ID {bossEntityID}.");
                }
                (GamePoint bossPoint, int emevdIndex) = Maps.BossLocations[bossEntityID];
                Boss boss = EnemyGenerator.GetRandomBoss(Rand, bossPoint.ArenaSize, Run.BossCategoriesUsed);
#if DEBUG
                Console.WriteLine($"Boss {i}: {boss.Name}");
#endif
                Enemy bossEnemy    = EnemyGenerator.GetEnemy(boss.Name);
                bool  isRedPhantom = map.Name == "ChasmOfTheAbyss" || boss.AlwaysRedPhantom; // Red phantom bosses are otherwise fought in Abyss battles only.

                var part = bossEnemy.GetMSBPart($"Boss {i}", bossEntityID, bossPoint, bossPoint.Angle, Run.MapLevel, isRedPhantom);
                msb.Parts.Enemies.Add(part);
                logicGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].LogicID);
                battleGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].BattleID);
                Run.BossCategoriesUsed.Add(boss.Category);
                enemyModels.Add(bossEnemy.ModelID);

                // TWIN BOSSES:
                // If arena is Large or Giant, there's a chance of a twin boss (capping their combined aggression).
                // Mornstein's ring makes it 100% chance whenever possible.
                Boss twinBoss = null;
                if ((Roll(TwinBossOdds) || Run.GetFlag(GameFlag.MornsteinRingFlag)) && Maps.BossLocations.ContainsKey(bossEntityID + 1) && bossPoint.ArenaSize.In(ArenaSize.Large, ArenaSize.Giant) && boss.AggressionLevel < 5)
                {
                    (GamePoint twinBossPoint, _) = Maps.BossLocations[bossEntityID + 1];
                    List <Boss> twinOptions = new List <Boss>(EnemyGenerator.BossList.Where(
                                                                  twin => twin.Category != boss.Category && twin.AggressionLevel + boss.AggressionLevel <= 5 && twin.RequiredArenaSize <= bossPoint.ArenaSize));
                    twinBoss = twinOptions.GetRandomElement(Rand);

                    Enemy twinBossEnemy    = EnemyGenerator.GetEnemy(twinBoss.Name);
                    bool  twinIsRedPhantom = map.Name == "ChasmOfTheAbyss" || twinBoss.AlwaysRedPhantom; // Red phantom bosses are otherwise fought in Abyss battles only.

                    var twinPart = twinBossEnemy.GetMSBPart($"Boss {i} Twin", bossEntityID + 1, twinBossPoint, twinBossPoint.Angle, Run.MapLevel, twinIsRedPhantom);
                    msb.Parts.Enemies.Add(twinPart);
                    logicGoalIDs.Add(Mod.GPARAM.AI[twinPart.ThinkParamID].LogicID);
                    battleGoalIDs.Add(Mod.GPARAM.AI[twinPart.ThinkParamID].BattleID);
                    Run.BossCategoriesUsed.Add(twinBoss.Category);
                    enemyModels.Add(twinBossEnemy.ModelID);

                    Run.EnableFlag(map.GetBossTwinFlag(i));
                }
                else
                {
                    Run.DisableFlag(map.GetBossTwinFlag(i));  // e.g. may have been set for New Londo Abyss boss previously
                }

                if (map.Name != "Lost Izalith")
                {
                    // Inject name and item lot reward into EMEVD constructor.
                    EMEVD.Instruction bossBattleCall = constructor.Instructions[emevdIndex];
                    List <byte>       argData        = bossBattleCall.ArgData.ToList();
                    int itemLotID = isRedPhantom ? bossEnemy.RedPhantomItemLotParamID : bossEnemy.ItemLotParamID;  // twin item lot ignored
                    argData[32] = (byte)itemLotID;
                    argData[33] = (byte)(itemLotID >> 0x08);
                    argData[34] = (byte)(itemLotID >> 0x10);
                    argData[35] = (byte)(itemLotID >> 0x18);
                    argData[52] = (byte)boss.NameTextID;
                    argData[53] = (byte)(boss.NameTextID >> 0x08);
                    if (twinBoss != null)
                    {
                        argData[54] = (byte)twinBoss.NameTextID;
                        argData[55] = (byte)(twinBoss.NameTextID >> 0x08);
                    }
                    bossBattleCall.ArgData = argData.ToArray();
                }
            }

            if (Roll(InvaderOdds))
            {
                // INVADER
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else
                {
                    regionLabel = "";
                }

                int             invaderIndex    = Run.InvadersAvailable.GetRandomElement(Rand);
                int             invaderParamID  = 7000 + 10 * invaderIndex;
                int             invaderEntityID = map.BaseNPCEntityID + 50;
                GamePoint       spawnPoint      = pointManager.CheckOutRandomPoint("Invader", (int)ChrSize.Normal);
                GamePoint       triggerPoint    = pointManager.CheckOutRandomPointWithinDistance("Invader Trigger", spawnPoint, 20.0, 40.0);
                float           angle           = MapPointManager.GetFacingPoint(spawnPoint, triggerPoint);
                MSB1.Part.Enemy invader         = GetInvaderPart(invaderEntityID, spawnPoint, angle, invaderParamID, Run.MapLevel);
                msb.Parts.Enemies.Add(invader);
                Run.InvadersAvailable.Remove(invaderIndex);
                Run.InvadersUsed.Add(invaderIndex);
                Run.EnableFlag(GameFlag.InvaderUsedBaseFlag + invaderIndex);

                MSB1.Region triggerRegion = new MSB1.Region()
                {
                    Name     = "Invader Trigger",
                    Position = triggerPoint.Position - new Vector3(0.0f, 1.0f, 0.0f),  // 1.0 units below point
                    Shape    = new MSB1.Shape.Box()
                    {
                        Width = 20.0f, Depth = 20.0f, Height = 10.0f
                    },
                    EntityID = map.InvaderTriggerRegionID,
                };
                msb.Regions.Regions.Add(triggerRegion);

                MSB1.Region spawnRegion = new MSB1.Region()
                {
                    Name     = "Invader Spawn",
                    Position = spawnPoint.Position,
                    Shape    = new MSB1.Shape.Point(),
                    EntityID = map.InvaderSpawnPointID,
                };
                msb.Regions.Regions.Add(spawnRegion);
            }

            if (Roll(VeryRareEnemyOdds * (Run.GetFlag(GameFlag.MornsteinRingFlag) ? 2 : 1)))
            {
                // VERY RARE ENEMY
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else
                {
                    regionLabel = "";
                }

                bool      isRedPhantom = Roll(redPhantomOdds);
                int       entityID     = map.BaseEntityID + VeryRareEnemyOffset;
                Enemy     enemy        = EnemyGenerator.GetRandomEnemyWithRarity(Rand, EnemyRarity.VeryRare, Run.MapLabels[map]);
                GamePoint point        = pointManager.CheckOutRandomPoint("VeryRare Enemy", (int)enemy.Size); // don't care about region label
                if (map.Name == "NewLondoRuins" && point.RegionLabel == "Lower")
                {
                    entityID += NewLondoVeryRareEnemyOffset;
                }
                var part = enemy.GetMSBPart("VeryRare Enemy 1", entityID, point, GetRandomAngle(), Run.MapLevel, isRedPhantom);
                msb.Parts.Enemies.Add(part);
                logicGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].LogicID);
                battleGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].BattleID);
                enemyModels.Add(enemy.ModelID);
            }

            if (!map.Name.In("PaintedWorld", "ChasmOfTheAbyss", "NewLondoRuins") && (Run.GetFlag(GameFlag.LoganRingFlag) || Roll(AbyssPortalOdds)))
            {
                // ABYSS PORTAL

                GamePoint        point          = pointManager.CheckOutRandomPoint("Abyss Portal", 0);
                MSB1.Part.Player portalWarpBack = new MSB1.Part.Player()
                {
                    Name         = "Portal Return",
                    Position     = point.Position,
                    Rotation     = new Vector3(0.0f, GetRandomAngle(), 0.0f),
                    EntityID     = map.PortalWarpPointID,
                    IsShadowSrc  = 1,
                    IsShadowDest = 1,
                    ModelName    = "c0000",
                };
                msb.Parts.Players.Add(portalWarpBack);
                MSB1.Region portalPoint = new MSB1.Region()
                {
                    Name     = "Portal VFX Point",
                    Position = point.Position + new Vector3(0.0f, 2.0f, 0.0f),
                    Shape    = new MSB1.Shape.Point(),
                    EntityID = -1,
                };
                msb.Regions.Regions.Add(portalPoint);
                MSB1.Event.SFX portalVFX = new MSB1.Event.SFX()
                {
                    Name       = "Portal VFX",
                    FFXID      = 120027,
                    RegionName = "Portal VFX Point",
                    PartName   = point.CollisionName,
                    EntityID   = map.PortalFXID,
                };
                msb.Events.SFXs.Add(portalVFX);
                MSB1.Part.Enemy portalTrigger = new MSB1.Part.Enemy()
                {
                    ModelName     = "c1000",
                    Name          = "Portal Trigger",
                    Position      = point.Position + new Vector3(0.0f, 0.5f, 0.0f),
                    EntityID      = map.PortalTriggerCharacterID,
                    NPCParamID    = 100000,
                    TalkID        = 0,
                    CollisionName = point.CollisionName,
                    ThinkParamID  = 1,
                };
                msb.Parts.Enemies.Add(portalTrigger);
            }

            if (!Run.MapsVisited.Contains(Maps.GetMap("PaintedWorld")) && Roll(PaintingOdds))
            {
                // Painting appears, in a random choice of its possible positions (pre-existing in base MSB).
                // TODO: 3-4 positions per map. Wherever it fits. Proper collision assignment, etc. Also prompt regions for simplicity.
            }

            foreach (Merchant merchant in CharacterGenerator.MerchantList)
            {
                // MERCHANTS
                if (Run.GetFlag(merchant.DeadFlag))
                {
                    continue;  // Merchant is dead and cannot appear again.
                }
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else
                {
                    regionLabel = "";
                }

                double merchantOdds = merchant.Name == "Marvelous Chester" ? ChesterOdds : MerchantOdds;
                if (Roll(merchantOdds))
                {
                    if (map.Name == "NewLondoRuins")
                    {
                        regionLabel = "Upper";  // no merchants in lower
                    }
                    GamePoint point = pointManager.CheckOutRandomPointCustomNearbyRadius("Merchant", 2.0, 5, regionLabel);
                    var       part  = merchant.GetPart(point, pointManager.GetAngleFacingNearestPoint(point), map.TalkIDBase);
                    msb.Parts.Enemies.Add(part);
                    logicGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].LogicID);
                    battleGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].BattleID);
                }
            }

            int        chestCount        = Math.Max(0, Rand.Next(map.ChestCount - 2, map.ChestCount));
            List <int> usedChestTreasure = new List <int>();
            for (int i = 0; i < chestCount; i++)
            {
                // CHESTS
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else
                {
                    regionLabel = "";
                }

                int       chestID = map.BaseChestEntityID + i;
                GamePoint point   = pointManager.CheckOutRandomPointCustomNearbyRadius("Chest", 2.0, 5, regionLabel);
                float     angle   = pointManager.GetAngleFacingNearestPoint(point);
                var       part    = GetChestPart(i, chestID, angle, point);
                msb.Parts.Objects.Add(part);

                var objAct = new MSB1.Event.ObjAct()
                {
                    Name           = $"Chest ObjAct {i}",
                    ObjActEntityID = map.BaseChestObjActFlagID + i,
                    ObjActPartName = $"Chest {i}",
                    EventFlagID    = 0,
                };
                msb.Events.ObjActs.Add(objAct);

                var treasure = new MSB1.Event.Treasure()
                {
                    Name             = $"Chest Treasure {i}",
                    TreasurePartName = $"Chest {i}",
                    PartName         = point.CollisionName,
                    InChest          = true,
                    StartDisabled    = true, // can't get the treasure to disable itself based on 'InChest' alone...
                };
                // Choose random treasure in unlocked range.
                treasure.ItemLots[0] = GetChestTreasureID(map.BaseChestItemLotID, usedChestTreasure);
                usedChestTreasure.Add(treasure.ItemLots[0]);
                msb.Events.Treasures.Add(treasure);
            }

            if (Roll(MimicOdds))
            {
                // MIMIC
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else
                {
                    regionLabel = "";
                }

                bool isRedPhantom  = Roll(redPhantomOdds);
                int  mimicEntityID = map.BaseEntityID + MimicOffset;
                if (map.Name == "NewLondoRuins")
                {
                    regionLabel = "Upper";  // no Mimics in lower
                }
                Enemy     mimic = EnemyGenerator.GetEnemy("Mimic");
                GamePoint point = pointManager.CheckOutRandomPoint("Mimic", (int)mimic.Size, regionLabel);
                float     angle = pointManager.GetAngleFacingNearestPoint(point);
                var       part  = mimic.GetMSBPart("Mimic", mimicEntityID, point, angle, Run.MapLevel, isRedPhantom);
                msb.Parts.Enemies.Add(part);
                logicGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].LogicID);
                battleGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].BattleID);
                enemyModels.Add(mimic.ModelID);

                MSB1.Region mimicNest = new MSB1.Region()
                {
                    Name     = "Mimic Nest",
                    Position = new Vector3(point.Position.X, point.Position.Y - 0.1f, point.Position.Z),
                    Rotation = new Vector3(0.0f, angle, 0.0f),
                    Shape    = new MSB1.Shape.Box()
                    {
                        Width = 5.0f, Depth = 5.0f, Height = 2.0f
                    },
                    EntityID = mimicEntityID + 5,
                };
                msb.Regions.Regions.Add(mimicNest);
            }

            int        itemCount          = Math.Max(0, Rand.Next(map.ItemCorpseCount - 5, map.ItemCorpseCount));
            List <int> usedCorpseTreasure = new List <int>();
            for (int i = 0; i < itemCount; i++)
            {
                // ITEM CORPSES
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else
                {
                    regionLabel = "";
                }

                GamePoint point      = pointManager.CheckOutRandomPointWeightedInverseNearby($"Item Corpse {i}", 0, regionLabel);
                int       pose       = Objects.CorpsePoses.GetRandomElement(Rand);
                float     poseOffset = Objects.CorpsePoseYOffsets[pose];

                MSB1.Part.Object corpse = new MSB1.Part.Object()
                {
                    ModelName     = "o0500", // Not using o0502 or o0504, as they have different pose enums.
                    Name          = $"Item Corpse {i}",
                    CollisionName = point.CollisionName,
                    Position      = new Vector3(point.Position.X, point.Position.Y + poseOffset, point.Position.Z),
                    Rotation      = new Vector3(0.0f, GetRandomAngle(), 0.0f),
                    EntityID      = -1, // not needed
                    ObjectPose    = (short)pose,
                };
                corpse.ClearDrawGroups();
                corpse.ClearDispGroups();
                msb.Parts.Objects.Add(corpse);

                MSB1.Event.Treasure treasure = new MSB1.Event.Treasure()
                {
                    Name             = $"Item Corpse Treasure {i}",
                    TreasurePartName = $"Item Corpse {i}",
                    PartName         = point.CollisionName,
                    InChest          = false,
                };
                // Assign random corpse treasure from unlocked ranges.
                treasure.ItemLots[0] = GetCorpseTreasureID(map.BaseCorpseItemLotID, usedCorpseTreasure);
                usedCorpseTreasure.Add(treasure.ItemLots[0]);
                msb.Events.Treasures.Add(treasure);
            }

            int          rareCount       = Math.Max(0, Rand.Next(map.RareEnemyCount - 3, map.RareEnemyCount));
            List <Enemy> usedRareEnemies = new List <Enemy>();
            for (int i = 0; i < rareCount; i++)
            {
                // RARE ENEMIES
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else if (map.Name == "NewLondoRuins" && i == 0)
                {
                    regionLabel = "Upper";  // first rare enemy is always in Upper New Londo Ruins (for Holy Sigil drop)
                }
                else
                {
                    regionLabel = "";
                }

                Enemy enemy;
                if (usedRareEnemies.Count > MaxRareEnemyTypeCount)
                {
                    enemy = usedRareEnemies.GetRandomElement(Rand);
                }
                else
                {
                    enemy = EnemyGenerator.GetRandomEnemyWithRarity(Rand, EnemyRarity.Rare, Run.MapLabels[map]);
                    usedRareEnemies.Add(enemy);
                }

                bool isRedPhantom = rarePhantomCount < 15 && Roll(redPhantomOdds);

                GamePoint point = pointManager.CheckOutRandomPoint("Rare Enemy", (int)enemy.Size, regionLabel);

                int entityID = map.BaseEntityID + RareEnemyOffset + i;
                if (map.Name == "NewLondoRuins" && point.RegionLabel == "Lower")
                {
                    entityID += NewLondoLowerRareEnemyOffset;
                }

                MSB1.Part.Enemy enemyPart = enemy.GetMSBPart($"Rare Enemy {i}", entityID, point, GetRandomAngle(), Run.MapLevel, isRedPhantom);
                msb.Parts.Enemies.Add(enemyPart);
                logicGoalIDs.Add(Mod.GPARAM.AI[enemyPart.ThinkParamID].LogicID);
                battleGoalIDs.Add(Mod.GPARAM.AI[enemyPart.ThinkParamID].BattleID);
                enemyModels.Add(enemy.ModelID);

                if (isRedPhantom)
                {
                    rarePhantomCount++;
                }
            }

            int          basicEnemyCount        = Math.Max(0, Rand.Next(map.BasicEnemyCount - 5, map.BasicEnemyCount));
            int          basicNonPhantomCount   = 0;
            List <Enemy> usedCommonEnemyTypes   = new List <Enemy>();
            List <Enemy> usedUncommonEnemyTypes = new List <Enemy>();
            for (int i = 0; i < basicEnemyCount; i++)
            {
                // BASIC (COMMON/UNCOMMON) ENEMIES
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else
                {
                    regionLabel = "";
                }

                Enemy       enemy;
                EnemyRarity rarity = Roll(UncommonEnemyOdds) ? EnemyRarity.Uncommon : EnemyRarity.Common;
                if (rarity == EnemyRarity.Common)
                {
                    if (usedCommonEnemyTypes.Count > MaxCommonEnemyTypeCount)
                    {
                        enemy = usedCommonEnemyTypes.GetRandomElement(Rand);
                    }
                    else
                    {
                        enemy = EnemyGenerator.GetRandomEnemyWithRarity(Rand, rarity, Run.MapLabels[map]);
                        usedCommonEnemyTypes.Add(enemy);
                    }
                }
                else
                {
                    if (usedUncommonEnemyTypes.Count > MaxUncommonEnemyTypeCount)
                    {
                        enemy = usedUncommonEnemyTypes.GetRandomElement(Rand);
                    }
                    else
                    {
                        enemy = EnemyGenerator.GetRandomEnemyWithRarity(Rand, rarity, Run.MapLabels[map]);
                        usedUncommonEnemyTypes.Add(enemy);
                    }
                }

                bool isRedPhantom = basicPhantomCount < 15 && Roll(redPhantomOdds);

                int       entityID = map.BaseEntityID + (isRedPhantom ? BasicPhantomOffset + basicPhantomCount : BasicEnemyOffset + basicNonPhantomCount);
                GamePoint point    = pointManager.CheckOutRandomPoint("Basic Enemy", (int)enemy.Size, regionLabel);
                if (map.Name == "NewLondoRuins" && point.RegionLabel == "Lower")
                {
                    entityID += isRedPhantom ? NewLondoLowerBasicPhantomOffset : NewLondoLowerBasicEnemyOffset;
                }
                MSB1.Part.Enemy enemyPart = enemy.GetMSBPart($"Basic Enemy {i}", entityID, point, GetRandomAngle(), Run.MapLevel, isRedPhantom);
                msb.Parts.Enemies.Add(enemyPart);
                logicGoalIDs.Add(Mod.GPARAM.AI[enemyPart.ThinkParamID].LogicID);
                battleGoalIDs.Add(Mod.GPARAM.AI[enemyPart.ThinkParamID].BattleID);
                enemyModels.Add(enemy.ModelID);

                if (isRedPhantom)
                {
                    basicPhantomCount++;
                }
                else
                {
                    basicNonPhantomCount++;
                }
            }

            int vagrantCount = Rand.Next(3);
            for (int i = 0; i < vagrantCount; i++)
            {
                // VAGRANTS (max of 2)
                if (map.Name == "Blighttown")
                {
                    regionLabel = Roll(BlighttownSwampOdds) ? "Swamp" : "Shanty";
                }
                else
                {
                    regionLabel = "";
                }

                if (map.Name == "NewLondoRuins")
                {
                    regionLabel = "Upper";  // No Vagrants in Lower New Londo
                }
                bool      isRedPhantom = Run.GetFlag(GameFlag.MornsteinRingFlag) ? Roll(0.2) : Roll(0.1);
                int       entityID     = map.BaseEntityID + VagrantOffset + i;
                Enemy     enemy        = EnemyGenerator.GetEnemy(Roll(0.5) ? "Good Vagrant" : "Evil Vagrant");
                GamePoint point        = pointManager.CheckOutRandomPoint("Vagrant", (int)enemy.Size, regionLabel);
                var       part         = enemy.GetMSBPart($"Vagrant {i}", entityID, point, GetRandomAngle(), Run.MapLevel, isRedPhantom);
                msb.Parts.Enemies.Add(part);
                logicGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].LogicID);
                battleGoalIDs.Add(Mod.GPARAM.AI[part.ThinkParamID].BattleID);
                enemyModels.Add(enemy.ModelID);
            }

#if DEBUG
            Console.WriteLine($"    Updating models...");
#endif
            UpdateMSBModels(msb);

            // Output MSB. Also output a '.rls' copy, so it can be reloaded and modified at bonfire creation.
#if DEBUG
            Console.WriteLine($"    Writing MSB...");
#endif
            msb.Write(Mod.GameDir + $@"map\MapStudio\{map.MsbName}.msb");
            msb.Write(Mod.GameDir + $@"map\MapStudio\{map.MsbName}.msb.rls");  // file to modify for bonfire creation
#if DEBUG
            Console.WriteLine($"    Writing EMEVD...");
#endif
            emevd.Write(Mod.GameDir + $@"event\{map.EmevdName}.emevd.dcx");

#if DEBUG
            Console.WriteLine($"    Writing LUABND...");
#endif
            WriteMapLUABND(map.EmevdName, battleGoalIDs, logicGoalIDs);

#if DEBUG
            Console.WriteLine($"    Writing FFXBND...");
#endif
            WriteMapFFXBND(map.MapID[0], enemyModels);
        }
Esempio n. 6
0
        // Usages

        // Usages for display names of these symbols: instruction name, enum name, enum value
        private Dictionary <string, Usages> GetSymbolUsages(string game, string emevdDir, InstructionDocs docs)
        {
            Dictionary <string, HashSet <string> > symbolsByFile = new Dictionary <string, HashSet <string> >();
            HashSet <string> allSymbols = new HashSet <string>();

            interestingEmevds.TryGetValue(game, out Regex mainRegex);
            List <string> files = new List <string>();

            Console.WriteLine($"------ Usages from [{emevdDir}]");
            foreach (string emevdPath in Directory.GetFiles(emevdDir))
            {
                if (mainRegex != null && !mainRegex.Match(Path.GetFileName(emevdPath)).Success)
                {
                    continue;
                }
                string name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(emevdPath));
                files.Add(name);
                Console.WriteLine($"--- {name}");
                HashSet <string> symbols = symbolsByFile[name] = new HashSet <string>();
                EMEVD            emevd   = EMEVD.Read(emevdPath);
                foreach (EMEVD.Event evt in emevd.Events)
                {
                    for (int insIndex = 0; insIndex < evt.Instructions.Count; insIndex++)
                    {
                        // This is all very best-effort
                        EMEVD.Instruction ins = evt.Instructions[insIndex];
                        EMEDF.InstrDoc    doc = docs.DOC[ins.Bank]?[ins.ID];
                        if (doc == null)
                        {
                            continue;
                        }
                        symbols.Add(doc.DisplayName);
                        Dictionary <EMEVD.Parameter, string> paramNames = docs.ParamNames(evt);
                        try
                        {
                            // A slight abuse of this function, ignoring the returned list
                            docs.UnpackArgsWithParams(ins, insIndex, doc, paramNames, (argDoc, val) =>
                            {
                                if (argDoc.GetDisplayValue(val) is string displayStr)
                                {
                                    symbols.Add(displayStr);
                                }
                                return(val);
                            });
                            // Also add a usage if the enum is present at all, even if parameterized
                            foreach (EMEDF.ArgDoc argDoc in doc.Arguments)
                            {
                                if (argDoc.EnumDoc != null && argDoc.EnumName != "BOOL")
                                {
                                    symbols.Add(argDoc.EnumDoc.DisplayName);
                                }
                            }
                        }
                        catch
                        {
                        }
                    }
                }
                allSymbols.UnionWith(symbols);
            }

            Dictionary <string, Usages> symbolUsages = new Dictionary <string, Usages>();
            List <string> primaryFiles = null;

            if (secondaryEmevd.TryGetValue(game, out Regex secondaryRegex))
            {
                primaryFiles = files.Where(f => !secondaryRegex.Match(f).Success).ToList();
            }
            foreach (string symbol in allSymbols)
            {
                List <string> matchFiles = files.Where(f => symbolsByFile[f].Contains(symbol)).ToList();
                List <string> target     = files;
                if (primaryFiles != null)
                {
                    List <string> primaryMatchFiles = matchFiles.Intersect(primaryFiles).ToList();
                    if (primaryMatchFiles.Count > 0)
                    {
                        matchFiles = primaryMatchFiles;
                        target     = primaryFiles;
                    }
                }
                // Combining PTDE and DS1R is done after.
                symbolUsages[symbol] = new Usages {
                    Files = matchFiles, AllFiles = target
                };
            }
            return(symbolUsages);
        }
Esempio n. 7
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')
            });
        }
Esempio n. 8
0
        private bool OpenEMEVDFile(
            string fileName,
            string gameDocs,
            EMEVD evd     = null,
            string jsText = null,
            bool isFancy  = false,
            Dictionary <string, string> extraFields = null)
        {
            // Can reuse docs if for the same game
            if (!AllDocs.TryGetValue(gameDocs, out InstructionDocs docs))
            {
                docs = AllDocs[gameDocs] = new InstructionDocs(gameDocs);
            }
            ScriptSettings settings = new ScriptSettings(docs, extraFields);
            EventScripter  scripter = new EventScripter(fileName, docs, evd);

            string fileVersion = ProgramVersion.VERSION;

            if (jsText == null)
            {
                try
                {
                    if (isFancy && docs.Translator != null)
                    {
                        jsText = new FancyEventScripter(scripter, docs, settings.CFGOptions).Unpack();
                    }
                    else
                    {
                        jsText = scripter.Unpack();
                    }
                }
                catch (Exception ex)
                {
                    // Also try to do it in compatibility mode, for emevd files which are no longer allowed, such as changing EMEDFs.
                    try
                    {
                        jsText = scripter.Unpack(compatibilityMode: true);
                    }
                    catch
                    {
                        // If this also fails, we only care about the original exception.
                    }
                    if (jsText == null)
                    {
                        MessageBox.Show(ex.Message);
                    }
                    else
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine(ex.Message);
                        sb.AppendLine("Proceed anyway? You will have to fix instruction arguments before resaving.");
                        DialogResult result = MessageBox.Show(sb.ToString(), "Error", MessageBoxButtons.YesNoCancel);
                        if (result != DialogResult.Yes)
                        {
                            jsText = null;
                        }
                    }
                    if (jsText == null)
                    {
                        return(false);
                    }
                }
            }
            else
            {
                fileVersion = extraFields != null && extraFields.TryGetValue("version", out string version) ? version : null;
            }
            if (Editor != null)
            {
                display.Panel2.Controls.Clear();
                SharedControls.RemoveEditor(Editor);
                Editor.Dispose();
            }
            Editor = new EditorGUI(SharedControls, scripter, docs, settings, fileVersion, jsText);
            SharedControls.AddEditor(Editor);
            SharedControls.RefreshGlobalStyles();
            display.Panel2.Controls.Add(Editor);
            // PerformLayout();
            Text = $"DARKSCRIPT 3 - {scripter.FileName}";
            // Notify about possible compatibility issues
            int versionCmp = ProgramVersion.CompareVersions(ProgramVersion.VERSION, fileVersion);

            if (versionCmp > 0)
            {
                SharedControls.SetStatus("Note: File was previously saved using an earlier version of DarkScript3");
            }
            else if (versionCmp < 0)
            {
                SharedControls.SetStatus("Note: File was previously saved using an newer version of DarkScript3. Please update!");
            }
            return(true);
        }
Esempio n. 9
0
        private bool OpenJSFile(string fileName)
        {
            string org  = fileName.Substring(0, fileName.Length - 3);
            string text = File.ReadAllText(fileName);
            Dictionary <string, string> headers = GetHeaderValues(text);
            List <string> emevdFileHeaders      = new List <string> {
                "docs", "compress", "game", "string", "linked"
            };

            EMEVD  evd;
            string docs;

            if (emevdFileHeaders.All(name => headers.ContainsKey(name)))
            {
                docs = headers["docs"];
                if (!Enum.TryParse(headers["compress"], out DCX.Type compression))
                {
                    // TODO look at SekiroDFLT
                    if (Enum.TryParse(headers["compress"], out DCX.DefaultType defaultComp))
                    {
                        compression = (DCX.Type)defaultComp;
                    }
                    else
                    {
                        throw new Exception($"Unknown compression type in file header {headers["compress"]}");
                    }
                }
                if (!Enum.TryParse(headers["game"], out EMEVD.Game game))
                {
                    throw new Exception($"Unknown game type in file header {headers["game"]}");
                }
                string linked = headers["linked"].TrimStart('[').TrimEnd(']');
                evd = new EMEVD()
                {
                    Compression       = compression,
                    Format            = game,
                    StringData        = Encoding.Unicode.GetBytes(headers["string"]),
                    LinkedFileOffsets = Regex.Split(linked, @"\s*,\s*")
                                        .Where(o => !string.IsNullOrWhiteSpace(o))
                                        .Select(o => long.Parse(o))
                                        .ToList()
                };
            }
            else if (!File.Exists(org))
            {
                MessageBox.Show($"{fileName} requires either a corresponding emevd file or JS headers to open");
                return(false);
            }
            else
            {
                evd  = null;
                docs = ChooseGame();
                if (docs == null)
                {
                    return(false);
                }
            }

            text = Regex.Replace(text, @"(^|\n)\s*// ==EMEVD==(.|\n)*// ==/EMEVD==", "");
            return(OpenEMEVDFile(org, docs, evd: evd, jsText: text.Trim(), extraFields: headers));
        }
Esempio n. 10
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;