Beispiel #1
0
    public override MSB1.Event Serialize(GameObject parent)
    {
        var evt = new MSB1.Event.ObjAct();

        _Serialize(evt, parent);
        evt.ObjActEntityID = ObjActEntityID;
        evt.ObjActPartName = (ObjActPartName == "") ? null : ObjActPartName;
        evt.ObjActParamID  = ObjActParamID;
        evt.UnkT0A         = UnkT0A;
        evt.EventFlagID    = EventFlagID;
        return(evt);
    }
Beispiel #2
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);
        }
        static void translateMsbs(string a)
        {
            string msbDir = a.EndsWith("\\") ? a.Substring(a.Length - 1, 1) : a;

            string[]      msbFileList     = Directory.GetFiles(msbDir);
            List <string> msbFileNameList = new List <string>();
            List <MSB1>   msbs            = new List <MSB1>();

            Console.WriteLine("### " + msbDir);

            for (int i = 0; i < msbFileList.Length; i++)
            {
                string fn = msbFileList[i].Substring(msbDir.Length + 1, msbFileList[i].Length - (msbDir.Length + 1));
                msbFileNameList.Add(fn);
                msbs.Add(MSB1.Read(File.ReadAllBytes(msbFileList[i])));
            }

            TranslationClient client = TranslationClient.Create(GoogleCredential.FromFile("C:\\Users\\dmtin\\google-translate-api-key.txt"));

            /* I could also translate the region names but I'd have to build a map of all the original names -> translated names and apply the new names to the right events */
            /* A lot of work and potentially buggy so I'm not going to do it right now. */

            for (int i = 0; i < msbs.Count; i++)
            {
                MSB1 msb = msbs[i];
                Console.WriteLine("\n\n\n\n==================" + msbFileNameList[i] + "==================");

                Console.WriteLine("\n\n#### EventType: Environment ####");
                for (int j = 0; j < msb.Events.Environments.Count; j++)
                {
                    MSB1.Event.Environment evto = msb.Events.Environments[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: Generator ####");
                for (int j = 0; j < msb.Events.Generators.Count; j++)
                {
                    MSB1.Event.Generator evto = msb.Events.Generators[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: Navmesh ####");
                for (int j = 0; j < msb.Events.Navmeshes.Count; j++)
                {
                    MSB1.Event.Navmesh evto = msb.Events.Navmeshes[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: Light ####");
                for (int j = 0; j < msb.Events.Lights.Count; j++)
                {
                    MSB1.Event.Light evto = msb.Events.Lights[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: Message ####");
                for (int j = 0; j < msb.Events.Messages.Count; j++)
                {
                    MSB1.Event.Message evto = msb.Events.Messages[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: ObjAct ####");
                for (int j = 0; j < msb.Events.ObjActs.Count; j++)
                {
                    MSB1.Event.ObjAct evto = msb.Events.ObjActs[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: SFX ####");
                for (int j = 0; j < msb.Events.SFX.Count; j++)
                {
                    MSB1.Event.SFX evto = msb.Events.SFX[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: Sound ####");
                for (int j = 0; j < msb.Events.Sounds.Count; j++)
                {
                    MSB1.Event.Sound evto = msb.Events.Sounds[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: SpawnPoint ####");
                for (int j = 0; j < msb.Events.SpawnPoints.Count; j++)
                {
                    MSB1.Event.SpawnPoint evto = msb.Events.SpawnPoints[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: Treasure ####");
                for (int j = 0; j < msb.Events.Treasures.Count; j++)
                {
                    MSB1.Event.Treasure evto = msb.Events.Treasures[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }

                Console.WriteLine("\n\n#### EventType: Wind ####");
                for (int j = 0; j < msb.Events.Wind.Count; j++)
                {
                    MSB1.Event.Wind evto = msb.Events.Wind[j];
                    try
                    {
                        if (evto.Name != null && !evto.Name.Trim().Equals(""))
                        {
                            TranslationResult response = client.TranslateText(evto.Name, LanguageCodes.English, LanguageCodes.Japanese); // Translate request
                            if (response != null && response.TranslatedText != null && response.TranslatedText.Trim().Length > 0)
                            {
                                evto.Name = response.TranslatedText;
                            }
                        }
                    }
                    catch (Exception ex) { Console.WriteLine("EXCEPTION :: " + ex.Message); }
                    Console.WriteLine(evto.EventID + ":: " + evto.Name);
                }
            }

            Directory.CreateDirectory(msbDir + "\\translated\\");
            for (int i = 0; i < msbs.Count; i++)
            {
                string outPath = msbDir + "\\translated\\" + msbFileNameList[i];
                byte[] outData = msbs[i].Write();
                File.WriteAllBytes(outPath, outData);
            }

            Console.WriteLine("\n\n Done!");
        }