예제 #1
0
        private int Scale(double value, double scale, double adjustment, MT19337 rng)
        {
            var exponent      = (double)rng.Next() / uint.MaxValue * 2.0 - 1.0;
            var adjustedScale = 1.0 + adjustment * (scale - 1.0);

            return((int)Round(Pow(adjustedScale, exponent) * value, MidpointRounding.AwayFromZero));
        }
예제 #2
0
        // Previous RangeScale(), delete if no bugs come up with new RangeScale() - 2020-10-27
        private int oldRangeScale(double value, double lowPercent, double highPercent, double adjustment, MT19337 rng)
        {
            double range            = highPercent - lowPercent;
            double randomRangeScale = rng == null ? range : range * ((double)rng.Next() / uint.MaxValue);
            double actualScale      = lowPercent + randomRangeScale;
            double adjustedScale    = actualScale > 1 ? (actualScale - 1) * adjustment + 1 : 1 - ((1 - actualScale) * adjustment);

            return((int)(adjustedScale * value));
        }
예제 #3
0
        private int Scale(double value, double scale, double adjustment, MT19337 rng, bool increaseOnly)
        {
            double exponent = 1.0;

            if (rng != null)
            {
                exponent = (double)rng.Next() / uint.MaxValue;
                exponent = increaseOnly ? exponent : exponent * 2.0 - 1.0;
            }
            double adjustedScale = 1.0 + adjustment * (scale - 1.0);

            return((int)Round(Pow(adjustedScale, exponent) * value));
        }
예제 #4
0
        private int RangeScale(double value, double lowPercent, double highPercent, double adjustment, MT19337 rng)
        {
            double exponent      = (rng != null) ? (double)rng.Next() / uint.MaxValue : 1.0;        // A number from 0 - 1
            double logLowPercent = Log(lowPercent);
            double logDifference = Log(highPercent) - logLowPercent;

            exponent = exponent * logDifference + logLowPercent;                                                             // For example for 50-200% a number from -0.69 to 0.69, for 200-400% a number from 0.69 to 1.38

            double scaleValue    = Exp(exponent);                                                                            // A number from 0.5 to 2, or 2 to 4
            double adjustedScale = scaleValue > 1 ? (scaleValue - 1) * adjustment + 1 : 1 - ((1 - scaleValue) * adjustment); // Tightens the scale so some stats are not changed by as much. For example for strength (adjustment of 0.25) this becomes 0.875 to 1.25, 1.25 to 1.75 while for hp (adjustment of 1) this stays 0.5 to 2, 2 to 4

            return((int)Round(value * adjustedScale));
        }
예제 #5
0
        private void AssureSafe(MT19337 rng)
        {
            using (SHA256 hasher = SHA256.Create())
            {
                byte[] hashable = Data.ToBytes();

                //zero out character mapman graphics
                for (int i = 0x9000; i < 0xB000; i++)
                {
                    hashable[i] = 0;
                }
                //zero out character battle graphics
                for (int i = 0x25000; i < 0x26800; i++)
                {
                    hashable[i] = 0;
                }

                //zero out character palette data
                for (int i = 0x390; i < 0x3BC; i++)
                {
                    hashable[i] = 0;
                }
                //palettes continued
                for (int i = 0x3203C; i < 0x32408; i++)
                {
                    hashable[i] = 0;
                }
                //palettes continued
                for (int i = 0x3EBA6; i < 0x3EBB2; i++)
                {
                    hashable[i] = 0;
                }
                //palettes continued
                for (int i = 0x3ECA4; i < 0x3ECB0; i++)
                {
                    hashable[i] = 0;
                }

                var Hash = hasher.ComputeHash(hashable);
                if (ByteArrayToString(Hash) != "bf3dfa0d7dcaf239f3382907b2d7aa74b6c5af4bd56d73038a17e08f3c272957")
                {
                    Console.WriteLine($"Rom hash: {ByteArrayToString(Hash)}");
                    throw new TournamentSafeException("File has been modified");
                }
            }
            Put(0x3FFE3, Blob.FromHex("66696E616C2066616E74617379"));
            rng.Next();
        }
예제 #6
0
        private int RangeScaleWithZero(double value, double lowPercent, double highPercent, double lowScalelowPercent, double adjustment, MT19337 rng)
        {
            var internalLowPercent = lowPercent;
            var lowScaleThreshold  = 0.0;

            if (lowPercent == 0 && highPercent == 0)
            {
                return(0);
            }

            if (lowPercent == 0 && highPercent >= 0.2)
            {
                internalLowPercent = 0.1;
                lowScaleThreshold  = 0.14;
            }
            else if (lowPercent == 0 && highPercent == 0.1)
            {
                var ret = RangeScale(value, lowScalelowPercent, highPercent, adjustment, rng);
                return(ret > 3 ? ret : 0);
            }

            double exponent      = (rng != null) ? (double)rng.Next() / uint.MaxValue : 1.0;        // A number from 0 - 1
            double logLowPercent = Log(internalLowPercent);
            double logDifference = Log(highPercent) - logLowPercent;

            exponent = exponent * logDifference + logLowPercent; // For example for 50-200% a number from -0.69 to 0.69, for 200-400% a number from 0.69 to 1.38

            double scaleValue = Exp(exponent);                   // A number from 0.5 to 2, or 2 to 4

            if (lowScaleThreshold > 0 && scaleValue < lowScaleThreshold)
            {
                var ret = RangeScale(value, lowScalelowPercent, lowScaleThreshold, adjustment, rng);
                return(ret > 3 ? ret : 0);
            }

            double adjustedScale = scaleValue > 1 ? (scaleValue - 1) * adjustment + 1 : 1 - ((1 - scaleValue) * adjustment);             // Tightens the scale so some stats are not changed by as much. For example for strength (adjustment of 0.25) this becomes 0.875 to 1.25, 1.25 to 1.75 while for hp (adjustment of 1) this stays 0.5 to 2, 2 to 4

            return((int)Round(value * adjustedScale));
        }
예제 #7
0
        public void Randomize(Blob seed, Flags flags, Preferences preferences)
        {
            MT19337 rng;

            using (SHA256 hasher = SHA256.Create())
            {
                Blob FlagsBlob    = Encoding.UTF8.GetBytes(Flags.EncodeFlagsText(flags));
                Blob SeedAndFlags = Blob.Concat(new Blob[] { FlagsBlob, seed });
                Blob hash         = hasher.ComputeHash(SeedAndFlags);
                rng = new MT19337(BitConverter.ToUInt32(hash, 0));
            }
            if (flags.TournamentSafe)
            {
                AssureSafe();
            }

            UpgradeToMMC3();
            MakeSpace();
            Bank1E();
            Bank1B();
            EasterEggs();
            DynamicWindowColor(preferences.MenuColor);
            PermanentCaravan();
            ShiftEarthOrbDown();
            CastableItemTargeting();
            FixEnemyPalettes();       // fixes a bug in the original game's programming that causes third enemy slot's palette to render incorrectly
            FixWarpBug();             // The warp bug must be fixed for magic level shuffle and spellcrafter
            SeparateUnrunnables();
            var talkroutines = new TalkRoutines();
            var npcdata      = new NPCdata(this);

            UpdateDialogs(npcdata);

            if (flags.TournamentSafe)
            {
                Put(0x3FFE3, Blob.FromHex("66696E616C2066616E74617379"));
            }

            flags = Flags.ConvertAllTriState(flags, rng);

            TeleportShuffle teleporters      = new TeleportShuffle();
            var             palettes         = OverworldMap.GeneratePalettes(Get(OverworldMap.MapPaletteOffset, MapCount * OverworldMap.MapPaletteSize).Chunk(OverworldMap.MapPaletteSize));
            var             overworldMap     = new OverworldMap(this, flags, palettes, teleporters);
            var             maps             = ReadMaps();
            var             shopItemLocation = ItemLocations.CaravanItemShop1;
            var             oldItemNames     = ReadText(ItemTextPointerOffset, ItemTextPointerBase, ItemTextPointerCount);

            if (flags.EFGWaterfall || flags.EFGEarth1 || flags.EFGEarth2)
            {
                MapRequirements      reqs;
                MapGeneratorStrategy strategy;
                MapGenerator         generator = new MapGenerator();
                if (flags.EFGWaterfall)
                {
                    reqs = new MapRequirements
                    {
                        MapId = MapId.Waterfall,
                        Rom   = this,
                    };
                    strategy = MapGeneratorStrategy.WaterfallClone;
                    CompleteMap waterfall = generator.Generate(rng, strategy, reqs);

                    // Should add more into the reqs so that this can be done inside the generator.
                    teleporters.Waterfall.SetEntrance(waterfall.Entrance);
                    overworldMap.PutOverworldTeleport(OverworldTeleportIndex.Waterfall, teleporters.Waterfall);
                    maps[(int)MapId.Waterfall] = waterfall.Map;
                }

                if (flags.EFGEarth1)
                {
                    reqs = new MapRequirements
                    {
                        MapId = MapId.EarthCaveB1,
                        Rom   = this,
                    };

                    strategy = MapGeneratorStrategy.Square;
                    var earthB1 = generator.Generate(rng, strategy, reqs);

                    // Should add more into the reqs so that this can be done inside the generator.
                    teleporters.EarthCave1.SetEntrance(earthB1.Entrance);
                    overworldMap.PutOverworldTeleport(OverworldTeleportIndex.EarthCave1, teleporters.EarthCave1);
                    maps[(int)MapId.EarthCaveB1] = earthB1.Map;
                }
                if (flags.EFGEarth2)
                {
                    reqs = new MapRequirements
                    {
                        MapId = MapId.EarthCaveB2,
                        Rom   = this,
                    };

                    strategy = MapGeneratorStrategy.Square;
                    var earthB2 = generator.Generate(rng, strategy, reqs);

                    // Should add more into the reqs so that this can be done inside the generator.
                    teleporters.EarthCave2.SetEntrance(earthB2.Entrance);
                    overworldMap.PutStandardTeleport(TeleportIndex.EarthCave2, teleporters.EarthCave2, OverworldTeleportIndex.EarthCave1);
                    maps[(int)MapId.EarthCaveB2] = earthB2.Map;
                }
            }

            var flippedMaps = new List <MapId>();

            if ((bool)flags.FlipDungeons)
            {
                flippedMaps = HorizontalFlipDungeons(rng, maps, teleporters, overworldMap);
            }

            if ((bool)flags.RandomizeFormationEnemizer)
            {
                DoEnemizer(rng, (bool)flags.RandomizeEnemizer, (bool)flags.RandomizeFormationEnemizer, flags.EnemizerDontMakeNewScripts);
            }

            if (preferences.ModernBattlefield)
            {
                EnableModernBattlefield();
            }

            if ((bool)flags.TitansTrove)
            {
                EnableTitansTrove(maps);
            }

            if ((bool)flags.LefeinShops)
            {
                EnableLefeinShops(maps);
            }

            // This has to be done before we shuffle spell levels.
            if (flags.SpellBugs)
            {
                FixSpellBugs();
            }

            //must be done before spells get shuffled around otherwise we'd be changing a spell that isnt lock
            if (flags.LockMode != LockHitMode.Vanilla)
            {
                ChangeLockMode(flags.LockMode);
            }

            if (flags.EnemySpellsTargetingAllies)
            {
                FixEnemyAOESpells();
            }

            if (flags.AllSpellLevelsForKnightNinja)
            {
                KnightNinjaChargesForAllLevels();
            }

            if (flags.BuffHealingSpells)
            {
                BuffHealingSpells();
            }

            UpdateMagicAutohitThreshold(rng, flags.MagicAutohitThreshold);

            if ((bool)flags.GenerateNewSpellbook)
            {
                CraftNewSpellbook(rng, (bool)flags.SpellcrafterMixSpells, flags.LockMode, (bool)flags.MagicLevels, (bool)flags.SpellcrafterRetainPermissions);
            }

            if ((bool)flags.MagisizeWeapons)
            {
                MagisizeWeapons(rng, (bool)flags.MagisizeWeaponsBalanced);
            }

            if ((bool)flags.ItemMagic)
            {
                ShuffleItemMagic(rng, (bool)flags.BalancedItemMagicShuffle);
            }

            if ((bool)flags.GuaranteedRuseItem)
            {
                CraftRuseItem();
            }

            if ((bool)flags.ShortToFR)
            {
                ShortenToFR(maps, (bool)flags.PreserveFiendRefights, (bool)flags.PreserveAllFiendRefights, rng);
            }

            if (((bool)flags.Treasures) && flags.ShardHunt && !flags.FreeOrbs)
            {
                EnableShardHunt(rng, talkroutines, flags.ShardCount);
            }

            if ((bool)flags.TransformFinalFormation && !flags.SpookyFlag)
            {
                TransformFinalFormation((FinalFormation)rng.Between(0, Enum.GetValues(typeof(FinalFormation)).Length - 1), flags.EvadeCap);
            }

            var maxRetries = 8;

            for (var i = 0; i < maxRetries; i++)
            {
                try
                {
                    overworldMap = new OverworldMap(this, flags, palettes, teleporters);
                    if (((bool)flags.Entrances || (bool)flags.Floors || (bool)flags.Towns) && ((bool)flags.Treasures) && ((bool)flags.NPCItems))
                    {
                        overworldMap.ShuffleEntrancesAndFloors(rng, flags);

                        // Disable the Princess Warp back to Castle Coneria
                        if ((bool)flags.Entrances || (bool)flags.Floors)
                        {
                            talkroutines.ReplaceChunk(newTalkRoutines.Talk_Princess1, Blob.FromHex("20CC90"), Blob.FromHex("EAEAEA"));
                        }
                    }

                    if ((bool)flags.Treasures && (bool)flags.ShuffleObjectiveNPCs)
                    {
                        overworldMap.ShuffleObjectiveNPCs(rng);
                    }

                    IncentiveData incentivesData = new IncentiveData(rng, flags, overworldMap, shopItemLocation);

                    if (((bool)flags.Shops))
                    {
                        var excludeItemsFromRandomShops = new List <Item>();
                        if ((bool)flags.Treasures)
                        {
                            excludeItemsFromRandomShops = incentivesData.ForcedItemPlacements.Select(x => x.Item).Concat(incentivesData.IncentiveItems).ToList();
                        }

                        if (!((bool)flags.RandomWaresIncludesSpecialGear))
                        {
                            excludeItemsFromRandomShops.AddRange(ItemLists.SpecialGear);
                            if ((bool)flags.GuaranteedRuseItem)
                            {
                                excludeItemsFromRandomShops.Add(Item.PowerRod);
                            }
                        }

                        if ((bool)flags.NoMasamune)
                        {
                            excludeItemsFromRandomShops.Add(Item.Masamune);
                        }

                        shopItemLocation = ShuffleShops(rng, (bool)flags.ImmediatePureAndSoftRequired, ((bool)flags.RandomWares), excludeItemsFromRandomShops, flags.WorldWealth);
                        incentivesData   = new IncentiveData(rng, flags, overworldMap, shopItemLocation);
                    }

                    if ((bool)flags.Treasures)
                    {
                        generatedPlacement = ShuffleTreasures(rng, flags, incentivesData, shopItemLocation, overworldMap, teleporters);
                    }
                    break;
                }
                catch (InsaneException e)
                {
                    Console.WriteLine(e.Message);
                    if (maxRetries > (i + 1))
                    {
                        continue;
                    }
                    throw new InvalidOperationException(e.Message);
                }
            }

            // Change Astos routine so item isn't lost in wall of text
            if ((bool)flags.NPCItems || (bool)flags.NPCFetchItems || (bool)flags.ShuffleAstos)
            {
                talkroutines.Replace(newTalkRoutines.Talk_Astos, Blob.FromHex("A674F005BD2060F027A5738561202096B020A572203D96A575200096A476207F90207392A5611820109F201896A9F060A57060"));
            }

            npcdata.UpdateItemPlacement(generatedPlacement);

            if ((bool)flags.MagicShopLocs)
            {
                ShuffleMagicLocations(rng);
            }

            if (((bool)flags.MagicShops))
            {
                ShuffleMagicShops(rng);
            }

            if (((bool)flags.MagicLevels))
            {
                ShuffleMagicLevels(rng, ((bool)flags.MagicPermissions), (bool)flags.MagicLevelsTiered, (bool)flags.MagicLevelsMixed, (bool)!flags.GenerateNewSpellbook);
            }

            new StartingInventory(rng, flags, this).SetStartingInventory();

            /*
             * if (flags.WeaponPermissions)
             * {
             *      ShuffleWeaponPermissions(rng);
             * }
             *
             * if (flags.ArmorPermissions)
             * {
             *      ShuffleArmorPermissions(rng);
             * }
             */

            if (flags.SaveGameWhenGameOver)
            {
                EnableSaveOnDeath(flags);
            }

            // Ordered before RNG shuffle. In the event that both flags are on, RNG shuffle depends on this.
            if (((bool)flags.FixMissingBattleRngEntry))
            {
                FixMissingBattleRngEntry();
            }

            if (((bool)flags.Rng))
            {
                ShuffleRng(rng);
            }

            if (((bool)flags.EnemyScripts))
            {
                ShuffleEnemyScripts(rng, (bool)flags.AllowUnsafePirates, (bool)!flags.BossScriptsOnly, ((bool)flags.EnemySkillsSpellsTiered || (bool)flags.ScaryImps), (bool)flags.ScaryImps);
            }

            if (((bool)flags.EnemySkillsSpells))
            {
                if ((bool)flags.EnemySkillsSpellsTiered && (bool)!flags.BossSkillsOnly)
                {
                    GenerateBalancedEnemyScripts(rng, (bool)flags.SwolePirates);
                    ShuffleEnemySkillsSpells(rng, false);
                }
                else
                {
                    ShuffleEnemySkillsSpells(rng, (bool)!flags.BossSkillsOnly);
                }
            }

            if (((bool)flags.EnemyStatusAttacks))
            {
                if (((bool)flags.RandomStatusAttacks))
                {
                    RandomEnemyStatusAttacks(rng, (bool)flags.AllowUnsafePirates, (bool)flags.DisableStunTouch);
                }
                else
                {
                    ShuffleEnemyStatusAttacks(rng, (bool)flags.AllowUnsafePirates);
                }
            }

            if (flags.Runnability == Runnability.Random)
            {
                flags.Runnability = (Runnability)Rng.Between(rng, 0, 3);
            }

            if (flags.Runnability == Runnability.AllRunnable)
            {
                CompletelyRunnable();
            }
            else if (flags.Runnability == Runnability.AllUnrunnable)
            {
                CompletelyUnrunnable();
            }
            else if (flags.Runnability == Runnability.Shuffle)
            {
                ShuffleUnrunnable(rng);
            }

            // Always on to supply the correct changes for WaitWhenUnrunnable
            AllowStrikeFirstAndSurprise(flags.WaitWhenUnrunnable, (bool)flags.UnrunnablesStrikeFirstAndSurprise);

            if (((bool)flags.EnemyFormationsSurprise))
            {
                ShuffleSurpriseBonus(rng);
            }

            // Put this before other encounter / trap tile edits.
            if ((bool)flags.AllowUnsafeMelmond)
            {
                EnableMelmondGhetto(flags.EnemizerEnabled);
            }

            // After unrunnable shuffle and before formation shuffle. Perfect!
            if (flags.WarMECHMode != WarMECHMode.Vanilla)
            {
                WarMECHNpc(flags.WarMECHMode, npcdata, rng, maps);
            }

            if (flags.WarMECHMode == WarMECHMode.Unleashed)
            {
                UnleashWarMECH();
            }

            if ((bool)flags.ClassAsNpcFiends || (bool)flags.ClassAsNpcKeyNPC)
            {
                ClassAsNPC(flags, talkroutines, npcdata, flippedMaps, rng);
            }

            if ((bool)flags.FiendShuffle)
            {
                FiendShuffle(rng);
            }

            if (flags.FormationShuffleMode != FormationShuffleMode.None && !flags.EnemizerEnabled)
            {
                ShuffleEnemyFormations(rng, flags.FormationShuffleMode);
            }

            if ((bool)flags.RemoveTrapTiles)
            {
                RemoveTrapTiles(flags.EnemizerEnabled);
            }

            if (((bool)flags.EnemyTrapTiles) && !flags.EnemizerEnabled)
            {
                ShuffleTrapTiles(rng, ((bool)flags.RandomTrapFormations));
            }

            if ((bool)flags.OrdealsPillars)
            {
                ShuffleOrdeals(rng, maps);
            }

            if (flags.SkyCastle4FMazeMode == SkyCastle4FMazeMode.Maze)
            {
                DoSkyCastle4FMaze(rng, maps);
            }
            else if (flags.SkyCastle4FMazeMode == SkyCastle4FMazeMode.Teleporters)
            {
                ShuffleSkyCastle4F(rng, maps);
            }

            if ((bool)flags.ConfusedOldMen)
            {
                EnableConfusedOldMen(rng);
            }

            if ((bool)flags.EarlyOrdeals)
            {
                EnableEarlyOrdeals();
            }

            if (flags.ChaosRush)
            {
                EnableChaosRush();
            }
            if ((bool)flags.EarlyKing)
            {
                EnableEarlyKing(npcdata);
            }

            if ((bool)flags.EarlySarda)
            {
                EnableEarlySarda(npcdata);
            }

            if ((bool)flags.EarlySage)
            {
                EnableEarlySage(npcdata);
            }

            if ((bool)flags.FreeBridge)
            {
                EnableFreeBridge();
            }

            if ((bool)flags.FreeAirship)
            {
                EnableFreeAirship();
            }

            if ((bool)flags.FreeShip)
            {
                EnableFreeShip();
            }

            if (flags.FreeOrbs)
            {
                EnableFreeOrbs();
            }

            if ((bool)flags.FreeCanal)
            {
                EnableFreeCanal((bool)flags.NPCItems, npcdata);
            }

            if ((bool)flags.FreeCanoe)
            {
                EnableFreeCanoe();
            }

            if ((bool)flags.FreeLute)
            {
                EnableFreeLute();
            }

            if ((bool)flags.FreeTail && !(bool)flags.NoTail)
            {
                EnableFreeTail();
            }

            if (flags.NoPartyShuffle)
            {
                DisablePartyShuffle();
            }

            if (flags.SpeedHacks)
            {
                EnableSpeedHacks();
            }

            if (flags.IdentifyTreasures)
            {
                EnableIdentifyTreasures();
            }

            if (flags.Dash)
            {
                EnableDash();
            }

            if (flags.BuyTen)
            {
                EnableBuyQuantity();
            }
            else if (flags.BuyTenOld)
            {
                EnableBuyTen();
            }

            if (flags.WaitWhenUnrunnable)
            {
                ChangeUnrunnableRunToWait();
            }

            if (flags.SpeedHacks && flags.EnableCritNumberDisplay)
            {
                EnableCritNumberDisplay();
            }

            if (flags.BattleMagicMenuWrapAround)
            {
                BattleMagicMenuWrapAround();
            }

            if (flags.NPCSwatter)
            {
                EnableNPCSwatter(npcdata);
            }

            if (flags.EasyMode)
            {
                EnableEasyMode();
            }

            if ((bool)flags.TrappedChests || (bool)flags.TCMasaGuardian || (bool)flags.TrappedShards)
            {
                MonsterInABox(rng, flags);
            }

            if (flags.HouseMPRestoration || flags.HousesFillHp)
            {
                FixHouse(flags.HouseMPRestoration, flags.HousesFillHp);
            }

            if (flags.BBCritRate)
            {
                DontDoubleBBCritRates();
            }

            if (flags.WeaponCritRate)
            {
                DoubleWeaponCritRates();
            }

            //needs to go after item magic, moved after double weapon crit to have more control over the actual number of crit gained.
            if ((bool)flags.RandomWeaponBonus)
            {
                RandomWeaponBonus(rng, flags.RandomWeaponBonusLow, flags.RandomWeaponBonusHigh, (bool)flags.RandomWeaponBonusExcludeMasa);
            }

            if ((bool)flags.RandomArmorBonus)
            {
                RandomArmorBonus(rng, flags.RandomArmorBonusLow, flags.RandomArmorBonusHigh);
            }

            if (flags.WeaponBonuses)
            {
                IncreaseWeaponBonus(flags.WeaponTypeBonusValue);
            }

            if (flags.WeaponStats)
            {
                FixWeaponStats();
            }

            if (flags.ChanceToRun)
            {
                FixChanceToRun();
            }

            if (flags.EnemyStatusAttackBug)
            {
                FixEnemyStatusAttackBug();
            }

            if (flags.BlackBeltAbsorb)
            {
                FixBBAbsorbBug();
            }

            if (flags.MDefMode != MDEFGrowthMode.None)
            {
                MDefChanges(flags.MDefMode);
            }

            if (flags.ThiefHitRate)
            {
                ThiefHitRate();
            }

            if (flags.ImproveTurnOrderRandomization)
            {
                ImproveTurnOrderRandomization(rng);
            }

            if (flags.FixHitChanceCap)
            {
                FixHitChanceCap();
            }

            if (flags.EnemyElementalResistancesBug)
            {
                FixEnemyElementalResistances();
            }

            if (preferences.FunEnemyNames && !flags.EnemizerEnabled)
            {
                FunEnemyNames(preferences.TeamSteak);
            }

            var itemText = ReadText(ItemTextPointerOffset, ItemTextPointerBase, ItemTextPointerCount);

            itemText[(int)Item.Ribbon] = itemText[(int)Item.Ribbon].Remove(7);

            if ((bool)flags.HintsVillage || (bool)flags.HintsDungeon)
            {
                if ((bool)flags.HintsDungeon)
                {
                    SetDungeonNPC(flippedMaps, rng);
                }

                NPCHints(rng, npcdata, flags, overworldMap);
            }

            if (flags.Etherizer && !flags.HouseMPRestoration && !flags.HousesFillHp)
            {
                Etherizer();
                itemText[(int)Item.Tent]  = "ETHR@p";
                itemText[(int)Item.Cabin] = "DRY@p ";
                itemText[(int)Item.House] = "XETH@p";
            }

            ExpGoldBoost(flags.ExpBonus, flags.ExpMultiplier);
            ScalePrices(flags, itemText, rng, ((bool)flags.ClampMinimumPriceScale), shopItemLocation);
            ScaleEncounterRate(flags.EncounterRate / 30.0, flags.DungeonEncounterRate / 30.0);

            overworldMap.ApplyMapEdits();
            WriteMaps(maps);

            WriteText(itemText, ItemTextPointerOffset, ItemTextPointerBase, ItemTextOffset, UnusedGoldItems);

            if ((bool)flags.SwolePirates)
            {
                EnableSwolePirates();
            }

            if (flags.EnemyScaleStatsHigh != 100 || flags.EnemyScaleStatsLow != 100 || ((bool)flags.SeparateEnemyHPScaling && (flags.EnemyScaleHpLow != 100 || flags.EnemyScaleHpHigh != 100)))
            {
                ScaleEnemyStats(rng, flags);
            }

            if (flags.BossScaleStatsHigh != 100 || flags.BossScaleStatsLow != 100 || ((bool)flags.SeparateBossHPScaling && (flags.BossScaleHpLow != 100 || flags.BossScaleHpHigh != 100)))
            {
                ScaleBossStats(rng, flags);
            }

            PartyComposition(rng, flags, preferences);

            if (((bool)flags.RecruitmentMode))
            {
                PubReplaceClinic(rng, flags);
            }

            if ((bool)flags.ChangeMaxMP)
            {
                SetMPMax(flags.RedMageMaxMP, flags.WhiteMageMaxMP, flags.BlackMageMaxMP, flags.KnightMaxMP, flags.NinjaMaxMP);
            }

            if ((bool)flags.ShuffleAstos)
            {
                ShuffleAstos(flags, npcdata, talkroutines, rng);
            }

            if ((bool)flags.EnablePoolParty)
            {
                EnablePoolParty(flags, rng);
            }

            if ((bool)flags.MapCanalBridge)
            {
                EnableCanalBridge();
            }

            if (flags.NoDanMode)
            {
                NoDanMode();
            }

            SetProgressiveScaleMode(flags);

            if ((bool)flags.RandomizeClass)
            {
                RandomizeClass(rng, flags, oldItemNames);
            }

            if ((bool)flags.EnableRandomPromotions)
            {
                EnableRandomPromotions(flags, rng);
            }

            if (flags.DisableTentSaving)
            {
                CannotSaveOnOverworld();
            }

            if (flags.DisableInnSaving)
            {
                CannotSaveAtInns();
            }

            if (flags.PacifistMode && !flags.SpookyFlag)
            {
                PacifistEnd(talkroutines, npcdata, (bool)flags.EnemyTrapTiles || flags.EnemizerEnabled);
            }

            if (flags.ShopInfo)
            {
                ShopUpgrade();
            }

            if (flags.SpookyFlag)
            {
                Spooky(talkroutines, npcdata, rng, flags);
            }

            if (flags.InventoryAutosort && !(preferences.RenounceAutosort))
            {
                EnableInventoryAutosort();
            }

            // We have to do "fun" stuff last because it alters the RNG state.
            // Back up Rng so that fun flags are uniform when different ones are selected
            uint funRngSeed = rng.Next();

            RollCredits(rng);

            if (preferences.DisableDamageTileFlicker)
            {
                DisableDamageTileFlicker();
            }

            if (preferences.ThirdBattlePalette)
            {
                UseVariablePaletteForCursorAndStone();
            }

            if (preferences.PaletteSwap && !flags.EnemizerEnabled)
            {
                rng = new MT19337(funRngSeed);
                PaletteSwap(rng);
            }

            if (preferences.TeamSteak && !(bool)flags.RandomizeEnemizer)
            {
                TeamSteak();
            }

            if (preferences.ChangeLute)
            {
                rng = new MT19337(funRngSeed);
                ChangeLute(rng);
            }

            rng = new MT19337(funRngSeed);

            HurrayDwarfFate(preferences.HurrayDwarfFate, npcdata, rng);

            if (preferences.Music != MusicShuffle.None)
            {
                rng = new MT19337(funRngSeed);
                ShuffleMusic(preferences.Music, rng);
            }

            if (preferences.DisableSpellCastFlash)
            {
                DisableSpellCastScreenFlash();
            }

            npcdata.WriteNPCdata(this);
            talkroutines.WriteRoutines(this);
            talkroutines.UpdateNPCRoutines(this, npcdata);

            WriteSeedAndFlags(seed.ToHex(), Flags.EncodeFlagsText(flags));
            ExtraTrackingAndInitCode(flags);
        }
예제 #8
0
        public void Randomize(Blob seed, Flags flags, Preferences preferences)
        {
            var rng = new MT19337(BitConverter.ToUInt32(seed, 0));

            // Spoilers => different rng immediately
            if (flags.Spoilers)
            {
                rng = new MT19337(rng.Next());
            }

            UpgradeToMMC3();
            MakeSpace();
            Bank1E();
            Bank1B();
            EasterEggs();
            DynamicWindowColor(preferences.MenuColor);
            PermanentCaravan();
            ShiftEarthOrbDown();
            CastableItemTargeting();
            flags = Flags.ConvertAllTriState(flags, rng);


            TeleportShuffle teleporters      = new TeleportShuffle();
            var             palettes         = OverworldMap.GeneratePalettes(Get(OverworldMap.MapPaletteOffset, MapCount * OverworldMap.MapPaletteSize).Chunk(OverworldMap.MapPaletteSize));
            var             overworldMap     = new OverworldMap(this, flags, palettes, teleporters);
            var             maps             = ReadMaps();
            var             shopItemLocation = ItemLocations.CaravanItemShop1;

#if DEBUG
            if (flags.ExperimentalFloorGeneration)
            {
                MapRequirements      reqs;
                MapGeneratorStrategy strategy;
                MapGenerator         generator = new MapGenerator();
                if (flags.EFGWaterfall)
                {
                    reqs = new MapRequirements
                    {
                        MapId = MapId.Waterfall,
                        Rom   = this,
                    };
                    strategy = MapGeneratorStrategy.WaterfallClone;
                    CompleteMap waterfall = generator.Generate(rng, strategy, reqs);

                    // Should add more into the reqs so that this can be done inside the generator.
                    teleporters.Waterfall.SetEntrance(waterfall.Entrance);
                    overworldMap.PutOverworldTeleport(OverworldTeleportIndex.Waterfall, teleporters.Waterfall);
                    maps[(int)MapId.Waterfall] = waterfall.Map;
                }

                if (flags.EFGEarth1)
                {
                    reqs = new MapRequirements
                    {
                        MapId = MapId.EarthCaveB1,
                        Rom   = this,
                    };

                    strategy = MapGeneratorStrategy.Square;
                    var earthB1 = generator.Generate(rng, strategy, reqs);

                    // Should add more into the reqs so that this can be done inside the generator.
                    teleporters.EarthCave1.SetEntrance(earthB1.Entrance);
                    overworldMap.PutOverworldTeleport(OverworldTeleportIndex.EarthCave1, teleporters.EarthCave1);
                    maps[(int)MapId.EarthCaveB1] = earthB1.Map;
                }
                if (flags.EFGEarth2)
                {
                    reqs = new MapRequirements
                    {
                        MapId = MapId.EarthCaveB2,
                        Rom   = this,
                    };

                    strategy = MapGeneratorStrategy.Square;
                    var earthB2 = generator.Generate(rng, strategy, reqs);

                    // Should add more into the reqs so that this can be done inside the generator.
                    teleporters.EarthCave2.SetEntrance(earthB2.Entrance);
                    overworldMap.PutStandardTeleport(TeleportIndex.EarthCave2, teleporters.EarthCave2, OverworldTeleportIndex.EarthCave1);
                    maps[(int)MapId.EarthCaveB2] = earthB2.Map;
                }
            }
#endif

            if (flags.RandomizeFormationEnemizer)
            {
                DoEnemizer(rng, false, flags.RandomizeFormationEnemizer, false);
            }

            if (preferences.ModernBattlefield)
            {
                EnableModernBattlefield();
            }

            if ((bool)flags.TitansTrove)
            {
                EnableTitansTrove(maps);
            }

            if ((bool)flags.LefeinShops)
            {
                EnableLefeinShops(maps);
            }

            // This has to be done before we shuffle spell levels.
            if (flags.SpellBugs)
            {
                FixSpellBugs();
            }

            if (flags.RebalanceSpells)
            {
                RebalanceSpells();
            }

            if (flags.EnemySpellsTargetingAllies)
            {
                FixEnemyAOESpells();
            }

            if ((bool)flags.ItemMagic)
            {
                ShuffleItemMagic(rng);
            }

            if ((bool)flags.ShortToFR)
            {
                ShortenToFR(maps, (bool)flags.PreserveFiendRefights, rng);
            }

            if (((bool)flags.Treasures) && flags.ShardHunt && !flags.FreeOrbs)
            {
                EnableShardHunt(rng, flags.ExtraShards ? rng.Between(24, 30) : 16, ((bool)flags.NPCItems));
            }

            if ((bool)flags.TransformFinalFormation)
            {
                TransformFinalFormation((FinalFormation)rng.Between(0, Enum.GetValues(typeof(FinalFormation)).Length - 1));
            }

            var maxRetries = 8;
            for (var i = 0; i < maxRetries; i++)
            {
                try
                {
                    overworldMap = new OverworldMap(this, flags, palettes, teleporters);
                    if (((bool)flags.Entrances || (bool)flags.Floors || (bool)flags.Towns) && ((bool)flags.Treasures) && ((bool)flags.NPCItems))
                    {
                        overworldMap.ShuffleEntrancesAndFloors(rng, flags);
                    }

                    if ((bool)flags.ShuffleObjectiveNPCs)
                    {
                        overworldMap.ShuffleObjectiveNPCs(rng);
                    }

                    IncentiveData incentivesData = new IncentiveData(rng, flags, overworldMap, shopItemLocation);

                    if (((bool)flags.Shops))
                    {
                        var excludeItemsFromRandomShops = new List <Item>();
                        if ((bool)flags.Treasures)
                        {
                            excludeItemsFromRandomShops = incentivesData.ForcedItemPlacements.Select(x => x.Item).Concat(incentivesData.IncentiveItems).ToList();
                        }

                        if (!((bool)flags.RandomWaresIncludesSpecialGear))
                        {
                            excludeItemsFromRandomShops.AddRange(ItemLists.SpecialGear);
                        }

                        shopItemLocation = ShuffleShops(rng, (bool)flags.ImmediatePureAndSoftRequired, ((bool)flags.RandomWares), excludeItemsFromRandomShops, flags.WorldWealth);
                        incentivesData   = new IncentiveData(rng, flags, overworldMap, shopItemLocation);
                    }

                    if ((bool)flags.Treasures)
                    {
                        ShuffleTreasures(rng, flags, incentivesData, shopItemLocation, overworldMap, teleporters);
                    }
                    break;
                }
                catch (InsaneException e)
                {
                    Console.WriteLine(e.Message);
                    if (maxRetries > (i + 1))
                    {
                        continue;
                    }
                    throw new InvalidOperationException(e.Message);
                }
            }

            if (((bool)flags.MagicShops))
            {
                ShuffleMagicShops(rng);
            }

            if (((bool)flags.MagicLevels))
            {
                FixWarpBug();                 // The warp bug only needs to be fixed if the magic levels are being shuffled
                ShuffleMagicLevels(rng, ((bool)flags.MagicPermissions));
            }

            /*
             * if (flags.WeaponPermissions)
             * {
             *      ShuffleWeaponPermissions(rng);
             * }
             *
             * if (flags.ArmorPermissions)
             * {
             *      ShuffleArmorPermissions(rng);
             * }
             */

            if (((bool)flags.Rng))
            {
                ShuffleRng(rng);
            }

            if (((bool)flags.EnemyScripts))
            {
                ShuffleEnemyScripts(rng, (bool)flags.AllowUnsafePirates);
            }

            if (((bool)flags.EnemySkillsSpells))
            {
                ShuffleEnemySkillsSpells(rng);
            }

            if (((bool)flags.EnemyStatusAttacks))
            {
                if (((bool)flags.RandomStatusAttacks))
                {
                    RandomEnemyStatusAttacks(rng, (bool)flags.AllowUnsafePirates);
                }
                else
                {
                    ShuffleEnemyStatusAttacks(rng, (bool)flags.AllowUnsafePirates);
                }
            }

            if (((bool)flags.EnemyFormationsUnrunnable))
            {
                if (((bool)flags.EverythingUnrunnable))
                {
                    CompletelyUnrunnable();
                }
                else
                {
                    ShuffleUnrunnable(rng);
                }
            }

            if (((bool)flags.UnrunnablesStrikeFirstAndSurprise))
            {
                AllowStrikeFirstAndSurprise();
            }


            if (((bool)flags.EnemyFormationsSurprise))
            {
                ShuffleSurpriseBonus(rng);
            }

            // Put this before other encounter / trap tile edits.
            if ((bool)flags.AllowUnsafeMelmond)
            {
                EnableMelmondGhetto(flags.RandomizeFormationEnemizer);
            }

            // After unrunnable shuffle and before formation shuffle. Perfect!
            if (flags.WarMECHMode != WarMECHMode.Vanilla)
            {
                WarMECHNpc(flags.WarMECHMode, rng, maps);
            }

            if (flags.WarMECHMode == WarMECHMode.Unleashed)
            {
                UnleashWarMECH();
            }

            if (flags.FiendShuffle)
            {
                FiendShuffle(rng);
            }

            if (flags.FormationShuffleMode != FormationShuffleModeEnum.None)
            {
                ShuffleEnemyFormations(rng, flags.FormationShuffleMode);
            }

            if (((bool)flags.EnemyTrapTiles))
            {
                ShuffleTrapTiles(rng, ((bool)flags.RandomTrapFormations));
            }

            if ((bool)flags.OrdealsPillars)
            {
                ShuffleOrdeals(rng, maps);
            }

            if (flags.SkyCastle4FMazeMode == SkyCastle4FMazeMode.Maze)
            {
                DoSkyCastle4FMaze(rng, maps);
            }
            else if (flags.SkyCastle4FMazeMode == SkyCastle4FMazeMode.Teleporters)
            {
                ShuffleSkyCastle4F(rng, maps);
            }

            if ((bool)flags.ConfusedOldMen)
            {
                EnableConfusedOldMen(rng);
            }

            if ((bool)flags.EarlyOrdeals)
            {
                EnableEarlyOrdeals();
            }

            if (flags.ChaosRush)
            {
                EnableChaosRush();
            }

            if ((bool)flags.EarlySarda && !((bool)flags.NPCItems))
            {
                EnableEarlySarda();
            }

            if ((bool)flags.EarlySage && !((bool)flags.NPCItems))
            {
                EnableEarlySage();
            }

            if ((bool)flags.FreeBridge)
            {
                EnableFreeBridge();
            }

            if ((bool)flags.FreeAirship)
            {
                EnableFreeAirship();
            }

            if ((bool)flags.FreeShip)
            {
                EnableFreeShip();
            }

            if (flags.FreeOrbs)
            {
                EnableFreeOrbs();
            }

            if ((bool)flags.FreeCanal)
            {
                EnableFreeCanal();
            }

            if ((bool)flags.FreeLute)
            {
                EnableFreeLute();
            }

            if (flags.NoPartyShuffle)
            {
                DisablePartyShuffle();
            }

            if (flags.SpeedHacks)
            {
                EnableSpeedHacks();
            }

            if (flags.IdentifyTreasures)
            {
                EnableIdentifyTreasures();
            }

            if (flags.Dash)
            {
                EnableDash();
            }

            if (flags.BuyTen)
            {
                EnableBuyTen();
            }

            if (flags.WaitWhenUnrunnable)
            {
                ChangeUnrunnableRunToWait();
            }

            if (flags.SpeedHacks && flags.EnableCritNumberDisplay)
            {
                EnableCritNumberDisplay();
            }

            if (flags.NPCSwatter)
            {
                EnableNPCSwatter();
            }

            if (flags.EasyMode)
            {
                EnableEasyMode();
            }

            if (flags.HouseMPRestoration || flags.HousesFillHp)
            {
                FixHouse(flags.HouseMPRestoration, flags.HousesFillHp);
            }

            if (flags.WeaponStats)
            {
                FixWeaponStats();
            }

            if (flags.ChanceToRun)
            {
                FixChanceToRun();
            }

            if (flags.EnemyStatusAttackBug)
            {
                FixEnemyStatusAttackBug();
            }

            if (flags.BlackBeltAbsorb)
            {
                FixBBAbsorbBug();
            }

            if (flags.MDefMode != MDefChangesEnum.None)
            {
                MDefChanges(flags.MDefMode);
            }

            if (flags.ThiefHitRate)
            {
                ThiefHitRate();
            }

            if (flags.ImproveTurnOrderRandomization)
            {
                ImproveTurnOrderRandomization(rng);
            }

            if (flags.FixHitChanceCap)
            {
                FixHitChanceCap();
            }

            if (flags.EnemyElementalResistancesBug)
            {
                FixEnemyElementalResistances();
            }

            if (preferences.FunEnemyNames)
            {
                FunEnemyNames(preferences.TeamSteak);
            }

            var itemText = ReadText(ItemTextPointerOffset, ItemTextPointerBase, ItemTextPointerCount);
            itemText[(int)Item.Ribbon].Trim();

            ExpGoldBoost(flags.ExpBonus, flags.ExpMultiplier);
            ScalePrices(flags, itemText, rng, ((bool)flags.ClampMinimumPriceScale), shopItemLocation);
            ScaleEncounterRate(flags.EncounterRate / 30.0, flags.DungeonEncounterRate / 30.0);

            overworldMap.ApplyMapEdits();
            WriteMaps(maps);

            WriteText(itemText, ItemTextPointerOffset, ItemTextPointerBase, ItemTextOffset, UnusedGoldItems);

            if (flags.EnemyScaleFactor > 1)
            {
                ScaleEnemyStats(flags.EnemyScaleFactor, flags.WrapStatOverflow, flags.IncludeMorale, rng, ((bool)flags.ClampMinimumStatScale));
            }

            if (flags.BossScaleFactor > 1)
            {
                ScaleBossStats(flags.BossScaleFactor, flags.WrapStatOverflow, flags.IncludeMorale, rng, ((bool)flags.ClampMinimumBossStatScale));
            }

            PartyComposition(rng, flags);

            if (((bool)flags.RecruitmentMode))
            {
                PubReplaceClinic(rng, flags);
            }

            if ((bool)flags.MapCanalBridge)
            {
                EnableCanalBridge();
            }

            if (flags.NoDanMode)
            {
                NoDanMode();
            }

            SetProgressiveScaleMode(flags.ProgressiveScaleMode);

            if (flags.DisableTentSaving)
            {
                CannotSaveOnOverworld();
            }

            if (flags.DisableInnSaving)
            {
                CannotSaveAtInns();
            }

            // We have to do "fun" stuff last because it alters the RNG state.
            RollCredits(rng);

            if (preferences.DisableDamageTileFlicker)
            {
                DisableDamageTileFlicker();
            }

            if (preferences.ThirdBattlePalette)
            {
                UseVariablePaletteForCursorAndStone();
            }

            if (preferences.PaletteSwap)
            {
                PaletteSwap(rng);
            }

            if (preferences.TeamSteak)
            {
                TeamSteak();
            }

            if (preferences.Music != MusicShuffle.None)
            {
                ShuffleMusic(preferences.Music, rng);
            }

            WriteSeedAndFlags(Version, seed.ToHex(), Flags.EncodeFlagsText(flags));
            ExtraTrackingAndInitCode();
        }
예제 #9
0
        public static async Task <OwMapExchange> FromFlags(FF1Rom _rom, OverworldMap _overworldMap, Flags flags, MT19337 rng)
        {
            int seed;

            if (flags.MapGenSeed != 0)
            {
                seed = flags.MapGenSeed;
            }
            else
            {
                seed = (int)rng.Next();
            }

            MT19337 maprng = new MT19337((uint)seed);


            var gm = flags.GameMode;

            switch (gm)
            {
            case GameModes.NoOverworld:
                return(new OwMapExchange(_rom, _overworldMap, "nooverworld"));

            case GameModes.DeepDungeon:
                return(null);

            case GameModes.Standard:
                break;
            }

            var mx = flags.OwMapExchange;

            switch (mx)
            {
            case OwMapExchanges.None:
                return(null);

            case OwMapExchanges.Desert:
                if (flags.ReplacementMap == null)
                {
                    flags.ReplacementMap = DesertOfDeath.GenerateDesert(maprng);
                }
                return(new OwMapExchange(_rom, _overworldMap, flags.ReplacementMap));

            case OwMapExchanges.GenerateNewOverworld:
            case OwMapExchanges.LostWoods:
                if (flags.OwRandomPregen)
                {
                    return(new OwMapExchange(_rom, flags, _overworldMap, rng));
                }
                else if (flags.ReplacementMap == null)
                {
                    flags.ReplacementMap = await NewOverworld.GenerateNewOverworld(maprng, mx, flags.OwShuffledAccess, flags.OwUnsafeStart, _rom.Progress);
                }
                return(new OwMapExchange(_rom, _overworldMap, flags.ReplacementMap));

            case OwMapExchanges.ImportCustomMap:
                if (flags.ReplacementMap == null)
                {
                    throw new Exception("No replacement map was supplied");
                }
                return(new OwMapExchange(_rom, _overworldMap, flags.ReplacementMap));
            }

            throw new Exception("oops");
        }
예제 #10
0
 private void InitializeMap(MT19337 rng, Map map, double initialAliveChance)
 {
     map.Where(element => element.Value == SentinelDead && (double)rng.Next() / uint.MaxValue < initialAliveChance)
     .ToList().ForEach(element => element.Value = SentinelAlive);
 }
예제 #11
0
		private int Scale(int value, double scale, double adjustment, MT19337 rng)
		{
			var exponent = (double)rng.Next()/uint.MaxValue*2.0 - 1.0;
			var adjustedScale = 1.0 + adjustment*(scale - 1.0);

			return (int)Round(Pow(adjustedScale, exponent)*value, MidpointRounding.AwayFromZero);
		}
예제 #12
0
        public IRewardSource Pick(List <IRewardSource> sources, bool forward, bool spread, bool incentive, MT19337 rng)
        {
            if (!sources.Any())
            {
                return(null);
            }

            IRewardSource result = null;

            //This loop sums up all weights of the reward source.
            double sum = 0.0;

            foreach (var s in sources)
            {
                //If weight already exists for rewardsource use it
                if (weights.TryGetValue(s.Address, out var v))
                {
                    sum += v;
                }
                //If it doesn't have a weight and it's a chest, give it the weight 1
                else if (s is TreasureChest)
                {
                    sum += 1.0;
                    weights.Add(s.Address, 1.0);
                }
                //If it's not a chest, give it a significantly higher weight. There are way more chests than nonchet rewards.
                //That way there is a reasonable chance, that some loose key items land on npcs.
                //changed nonchest weight is tied to the spread placement flag.
                else if (!incentive)
                {
                    sum += nonchest;
                    weights.Add(s.Address, nonchest);
                }
                else
                {
                    sum += 1.0;
                    weights.Add(s.Address, 1.0);
                }
            }

            //We pick a value between 0 and sum. We can't work with the full double range, but with reasonable weight reduction, it's not going to be a problem.
            var r = rng.Next() / (double)uint.MaxValue * sum;

            //This loop finds the reward source associated with the rng number.
            //If Forward placement is active, reduce the weight of all reward sources that were elligible for an item this step.
            //Normally chest like ToF or Matoya have a chance to roll a loose item at every step, because they are accessible from the start.
            //TFC is accessible way later so is used in way less rolls.
            //By reducing the weight on chests, that already had a chance, it's more likely to select a chest or npc from a newly opened up area(hence forward placement).
            //The forward placement is deactivated for incentive items, so it doesn't skew the placement.
            //Forward placement alone produces ..... results.
            sum = 0.0;
            foreach (var s in sources)
            {
                var v = weights[s.Address];
                if (spread && forward)
                {
                    weights[s.Address] = v * reduction;
                }

                sum += v;
                if (result == null && r <= sum)
                {
                    result = s;
                }
            }

            //If we fail to find a reward source because of numerical problems, just pick a random one. It hasn't happend so far, but just to be sure.
            if (result == null)
            {
                result = sources.PickRandom(rng);
            }

            //The V2 builds a list of chests for each overworld entrance. GetNearRewardSources selects chests accessible from the same entrance with the same requirements.
            //So something like all accessible chests in marsh, or all keylocked chests in marsh. The function works for F/E. Otherwise V2 wouldn't work for F/E.
            //All chests found are demoted heavily. So the placement algorithm will only place another item there if it "has" to.
            //That spreads the key items out into different places(hence spread placement).
            //The spread placement remains active for incentive items. There is only one incentive location per dungeon and access requirement.
            //This has the effect of reducing the chance of a loose item in incentivized dungeons.
            //There is a small chance of two incentive locations in one dungeon with F/E. But the effect will not be noticable by any unaware observer.
            if (spread)
            {
                foreach (var s in checker.GetNearRewardSources(sources, result))
                {
                    if (weights.TryGetValue(s.Address, out var v))
                    {
                        weights[s.Address] = v * reduction * reduction;
                    }
                }
            }

            return(result);
        }
예제 #13
0
        int OnExecute(IConsole console)
        {
            if (Seed == 0 && this.SeedFile == null)
            {
                Console.WriteLine("Missing seed");
                return(1);
            }

            OwMapExchanges subtype = Enum.Parse <OwMapExchanges>(Subtype);

            var        numberOfMapsToGenerate = this.Pack;
            List <int> seeds = null;

            if (this.SeedFile != null)
            {
                seeds = new List <int>();
                foreach (string line in System.IO.File.ReadLines(this.SeedFile))
                {
                    seeds.Add(Int32.Parse(line));
                }
                numberOfMapsToGenerate = seeds.Count;
            }

            Parallel.For(0, numberOfMapsToGenerate, i =>
            {
                OwMapExchangeData replacementMap = null;
                int effectiveSeed;
                if (seeds == null)
                {
                    effectiveSeed = this.Seed + i;
                }
                else
                {
                    effectiveSeed = seeds[i];
                }
                var rng = new MT19337((uint)effectiveSeed);
                do
                {
                    try {
                        replacementMap = Task.Run <OwMapExchangeData>(async() => await FF1Lib.Procgen.NewOverworld.GenerateNewOverworld(rng, subtype, SuffleAccess, UnsafeStart, this.Progress)).Result;
                    } catch (FailedToGenerate) {
                        if (!this.Retry)
                        {
                            Console.WriteLine($"Failed to generate seed {effectiveSeed}");
                            throw;
                        }
                        effectiveSeed = (int)rng.Next() & 0x7FFFFFFF;
                        rng           = new MT19337((uint)effectiveSeed);
                    }
                } while (replacementMap == null);

                replacementMap.Checksum   = replacementMap.ComputeChecksum();
                replacementMap.Seed       = effectiveSeed;
                replacementMap.FFRVersion = FF1Lib.FFRVersion.Version;

                string fn;
                if (numberOfMapsToGenerate == 1)
                {
                    fn = $"FFR_map_{replacementMap.Checksum}.json";
                }
                else
                {
                    fn = $"{replacementMap.Seed,8:X8}.json";
                }
                using (StreamWriter file = File.CreateText(fn)) {
                    JsonSerializer serializer = new JsonSerializer();
                    if (this.Pack == 1)
                    {
                        serializer.Formatting = Formatting.Indented;
                    }
                    serializer.Serialize(file, replacementMap);
                }
                Console.WriteLine(fn);

                if (this.DoRender)
                {
                    MapRender.RenderMap(fn);
                }
            });

            return(0);
        }
        protected override ItemPlacementResult DoSanePlacement(MT19337 rng, ItemPlacementContext ctx)
        {
            exceptionWeights   = _flags.AllowUnsafePlacement || (_flags.Entrances ?? false) ? UnsafeExceptionWeights : SafeExceptionWeights;
            exceptionMinCycles = _flags.AllowUnsafePlacement || (_flags.Entrances ?? false) ? UnsafeExceptioMinCycles : SafeExceptioMinCycles;
            minAccessibility   = _flags.AllowUnsafePlacement || (_flags.Entrances ?? false) ? UnsafeMinAccessibility : SafeMinAccessibility;

            _sanityCounter = 0;
            var incentiveLocationPool   = new HashSet <IRewardSource>(_incentivesData.IncentiveLocations, new RewardSourceEqualityComparer());
            var preBlackOrbLocationPool = _incentivesData.AllValidPreBlackOrbItemLocations.ToList();
            var preBlackOrbUnincentivizedLocationPool = preBlackOrbLocationPool.Where(x => !incentiveLocationPool.Any(y => y.Address == x.Address)).ToList();

            if ((bool)_flags.LooseExcludePlacedDungeons)
            {
                preBlackOrbUnincentivizedLocationPool = IncentivizedDungeons(preBlackOrbUnincentivizedLocationPool);
            }

            var unincentivizedLocationPool = new HashSet <IRewardSource>(preBlackOrbUnincentivizedLocationPool, new RewardSourceEqualityComparer());

            var allRewardSources = preBlackOrbLocationPool.Append(new ItemShopSlot(_caravanItemLocation, Item.None)).ToList();

            List <IRewardSource> placedItems  = null;
            List <Item>          treasurePool = null;

            freeRequirements = BuildFreeRequirements();

            bool placementFailed;

            do
            {
                ((SanityCheckerV2)_checker).Shiplocations.SetShipLocation(255);

                BuildLogic(allRewardSources);

                placementFailed = false;

                var balancedPicker = new RewardSourcePicker(0.5, _flags.LooseItemsNpcBalance ? 7.0 : 1.0, _checker);

                _sanityCounter++;
                if (_sanityCounter > 2)
                {
                    throw new InsaneException("Item Placement could not meet incentivization requirements!");
                }

                placedItems = ctx.Forced.ToList();
                var incentives    = new HashSet <Item>(ctx.Incentivized);
                var nonincentives = ctx.Unincentivized.ToList();
                var shards        = ctx.Shards.ToList();
                treasurePool = ctx.AllTreasures.ToList();
                var state = PlacementState.Normal;

                while (incentives.Count() > incentiveLocationPool.Count())
                {
                    nonincentives.Add(incentives.SpliceRandom(rng));
                }

                if (((bool)_flags.NPCItems) || ((bool)_flags.NPCFetchItems))
                {
                    HashSet <Item> allPlacements = new HashSet <Item>(nonincentives.Concat(incentives));
                    HashSet <Item> allKeyItems   = new HashSet <Item>(MapChangeItems.Concat(FetchQuestItems).Concat(GatingItems).Intersect(allPlacements));

                    if ((bool)_flags.IsFloaterRemoved)
                    {
                        allKeyItems.Remove(Item.Floater);
                    }

                    //The sanity checker currently doesn't allow tracking which shops are available
                    //It could be easily added, but a little randomnes can't hurt(or so I'm thinking)
                    //So it places the vendoritem upfront.
                    var itemShopItem = SelectVendorItem(incentives.ToList(), nonincentives, treasurePool, incentiveLocationPool, rng);
                    placedItems.Add(new ItemShopSlot(_caravanItemLocation, itemShopItem));

                    allPlacements.Remove(itemShopItem);
                    allKeyItems.Remove(itemShopItem);

                    if (_flags.Archipelago && allKeyItems.Contains(Item.Bridge))
                    {
                        var accessibleSources = GetAllAccessibleRewardSources(preBlackOrbUnincentivizedLocationPool, placedItems);

                        var rewardSource = balancedPicker.Pick(accessibleSources, _flags.LooseItemsForwardPlacement, _flags.LooseItemsSpreadPlacement, false, rng);
                        placedItems.Add(NewItemPlacement(rewardSource, Item.Bridge));

                        allPlacements.Remove(Item.Bridge);
                        allKeyItems.Remove(Item.Bridge);
                    }

                    //Since it can waste cycles, this number needs to be high enough to be able to place all incentives and all nonincentives and still have some leeway
                    //When it's done it breaks anyway
                    //When it reached enough cycles so no cycle based restricitons apply and can place neither an incentive nor a nonincentive it also breaks
                    for (int cycle = 1; cycle <= 100; cycle++)
                    {
                        var accessibleSources = GetAllAccessibleRewardSources(preBlackOrbLocationPool, placedItems);

                        //look how many rewardsources of each type are available
                        var incSourceCount = accessibleSources.Where(s => incentiveLocationPool.Contains(s)).Count();
                        var nonSourceCount = accessibleSources.Count - incSourceCount;

                        List <(Item item, int incCount, int nonCount)> candidates = new List <(Item item, int incCount, int nonCount)>();

                        //go through each KI, place it in the testRewardSource, run the sanity checker and see how many RewardSources are available afterwards.
                        foreach (var item in allKeyItems)
                        {
                            var accessibleSources2 = GetAllAccessibleRewardSources(preBlackOrbLocationPool, placedItems, item);

                            var incCount = accessibleSources2.Where(s => incentiveLocationPool.Contains(s)).Count();
                            var nonCount = accessibleSources2.Count - incCount - nonSourceCount;
                            incCount -= incSourceCount;

                            //if the placed item is an incentive, it will take up an incentive location when it will be placed
                            //and vice versa
                            if (incentives.Contains(item))
                            {
                                incCount--;
                            }
                            if (!incentives.Contains(item))
                            {
                                nonCount--;
                            }

                            candidates.Add((item, incCount, nonCount));
                        }

                        //Since it can happen, that neither the ship, nor the canal by themselves open up something, but the combination does,
                        //here the combination is checked
                        //it grabs another Rewardsource, places Ship and Canal, runs the SanityChecker and calculates the results
                        if (allKeyItems.Contains(Item.Ship) && allKeyItems.Contains(Item.Canal))
                        {
                            var accessibleSources2 = GetAllAccessibleRewardSources(preBlackOrbLocationPool, placedItems, Item.Ship, Item.Canal);

                            var incCount = accessibleSources2.Where(s => incentiveLocationPool.Contains(s)).Count();
                            var nonCount = accessibleSources2.Count - incCount - nonSourceCount;
                            incCount -= incSourceCount;

                            if (incentives.Contains(Item.Ship))
                            {
                                incCount--;
                            }
                            if (!incentives.Contains(Item.Ship))
                            {
                                nonCount--;
                            }

                            if (incentives.Contains(Item.Canal))
                            {
                                incCount--;
                            }
                            if (!incentives.Contains(Item.Canal))
                            {
                                nonCount--;
                            }

                            //it gets the ship and canal candidate from the list, later it's put back in
                            var shipCandidate  = candidates.FirstOrDefault(c => c.item == Item.Ship);
                            var canalCandidate = candidates.FirstOrDefault(c => c.item == Item.Canal);

                            candidates.Remove(shipCandidate);
                            candidates.Remove(canalCandidate);

                            //half of the opened up incentive locations and nonincentive locations hould go to either item(rounded down)
                            //In order to continue the seed, Ship and Canal must open up 2 incentive locations, since they may take up 2
                            //it's not an exact estimate, since it doesn'T differentiate between them beeing incentive or nonincentive items
                            incCount /= 2;
                            nonCount /= 2;

                            if (shipCandidate.incCount < incCount && canalCandidate.incCount < incCount)
                            {
                                shipCandidate.incCount  = incCount;
                                canalCandidate.incCount = incCount;
                            }

                            if (shipCandidate.nonCount < nonCount && canalCandidate.nonCount < nonCount)
                            {
                                shipCandidate.nonCount  = nonCount;
                                canalCandidate.nonCount = nonCount;
                            }

                            candidates.Add(shipCandidate);
                            candidates.Add(canalCandidate);
                        }

                        var incKeyItemCount = incentives.Where(i => allKeyItems.Contains(i)).Count();
                        var nonKeyItemCount = nonincentives.Where(i => allKeyItems.Contains(i)).Count();

                        List <(float weight, Item item)> weightedCandidates;

                        var incItemCount = allPlacements.Where(i => incentives.Contains(i)).Count();

                        //here it randomly decides whether to place and incentive or a non incentive
                        //the chance is based on the number of incentive and nonincentive items
                        //If however it couldn't place an incentive item in the last cycle, it'll always try a nonincentive this time
                        //and vice versa
                        if (state == PlacementState.Incentive || state == PlacementState.Normal && rng.Between(1, allPlacements.Count) <= incItemCount || _flags.LaterLoose && state == PlacementState.Normal && cycle <= 5)
                        {
                            //Filter out nonincentive items
                            candidates = candidates.Where(c => incentives.Contains(c.item)).ToList();
                            var nonKeyCandidates = allPlacements.Where(i => !allKeyItems.Contains(i) && incentives.Contains(i));

                            if (incSourceCount <= 1 || incSourceCount <= CriticalIncentiveSourceCount && allKeyItems.Contains(Item.Ship) && allKeyItems.Contains(Item.Canal))
                            {
                                //we are in incentive critical mode
                                //the function takes weights for different categories of items(depending of how they're expected they change the amount of incentive locations available)
                                //bin0 doesn't open up a new location(it therefore reduces the incentive location count by one)
                                //bin1 opens up 1 location and therefore keeps the count the same
                                //bin2 opens up 2 locations and therefore increases the amount by 1
                                //bin3 opens up more than two locations. It has two parameters here, more on that further down

                                //Anyway, we can't afford to reduce the incentive location count here(bin0)
                                //We also don't neccesarily want to keep it the same, but that's okish(bin1)
                                //we want to increase the amount(bin2, bin3)
                                weightedCandidates = AssignWeights(candidates, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f, cycle, accessibleSources.Count);
                            }
                            else if (incSourceCount <= SafeIncentiveSourceCount)
                            {
                                //We have 1 incentive location to squander, not a lot, but sufficient
                                //We don't want to reduce it or keep it the same, but it would still be ok(bin0, bin1)
                                //We'd like to increase it a little(bin2)
                                //but not too much(bin3)
                                weightedCandidates = AssignWeights(candidates, 0.5f, 0.5f, 1.0f, 0.5f, 0.25f, cycle, accessibleSources.Count);

                                //it's also ok to place a NonKeyItem, wa have an incentive location to squander after all
                                //but don't make it too likely
                                weightedCandidates.AddRange(nonKeyCandidates.Select(i => (GetItemWeight(i, 0.5f, cycle, accessibleSources.Count), i)));
                            }
                            else
                            {
                                //We have at least 2 incentive locations to squander(considered a lot)
                                //We like reducing it by one(for variety)
                                //We like keeping it the same(for variety)
                                //We also like to increase it slightly(for variety)
                                //But we don't like to increase it a lot, but then again it's not a problem, so make it less likely
                                weightedCandidates = AssignWeights(candidates, 1.0f, 1.0f, 1.0f, 0.25f, 0.25f, cycle, accessibleSources.Count);

                                //Also we like placing NoneKeyItems
                                weightedCandidates.AddRange(nonKeyCandidates.Select(i => (GetItemWeight(i, 1.0f, cycle, accessibleSources.Count), i)));
                            }

                            //If we didn't find any candidates, but we have enough locations to place all remaining items, then we just do that
                            //Otherwise the algorithm would refuse to give up the last 2 incentive locations
                            if (weightedCandidates.Sum(i => i.weight) == 0 && incItemCount <= incSourceCount)
                            {
                                weightedCandidates.AddRange(allPlacements.Where(i => incentives.Contains(i)).Select(i => (1.0f, i)));
                            }

                            //If we don't have any candidates and not enough incentive locations to place all, we can't place an incentive in this cycle
                            if (weightedCandidates.Sum(i => i.weight) == 0)
                            {
                                //If it's at least cycle 15 and therefore no cycle based restricctions apply
                                //And in the last cycle we tried to place a NonIncentive
                                //we had all KI options available, but couldn't place any of them, so we're doomed
                                if (cycle >= 15 && state != PlacementState.Normal)
                                {
                                    placementFailed = true;
                                    break;
                                }

                                //since we couldn't place an incentive, we won't be able to do so next cycle(same results), so we don't try to do that next cycle
                                //This heavily speeds up the situations when there are very few loose(low chance of trying to place a loose), but a loose is required to go on.
                                state = PlacementState.NonIncentice;
                                continue;
                            }
                        }
                        else
                        {
                            //same as above, but for nonincentive items.
                            //The main difference is that the RewardSource thresholds are way higher
                            //While it's not a safety concern, cou generally want a lot more chest's available than neccesary to be in the confort zone
                            candidates = candidates.Where(c => !incentives.Contains(c.item)).ToList();
                            var nonKeyCandidates = allPlacements.Where(i => !allKeyItems.Contains(i) && !incentives.Contains(i));

                            if (nonSourceCount <= CriticalNonIncentiveSourceCount)
                            {
                                weightedCandidates = AssignWeights2(candidates, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f, cycle, accessibleSources.Count);
                            }
                            else if (nonSourceCount <= SafeNonIncentiveSourceCount)
                            {
                                weightedCandidates = AssignWeights2(candidates, 0.5f, 0.5f, 1.0f, 0.5f, 0.25f, cycle, accessibleSources.Count);
                                weightedCandidates.AddRange(nonKeyCandidates.Select(i => (GetItemWeight(i, 0.5f, cycle, accessibleSources.Count), i)));
                            }
                            else
                            {
                                weightedCandidates = AssignWeights2(candidates, 1.0f, 1.0f, 1.0f, 0.25f, 0.25f, cycle, accessibleSources.Count);
                                weightedCandidates.AddRange(nonKeyCandidates.Select(i => (GetItemWeight(i, 1.0f, cycle, accessibleSources.Count), i)));
                            }

                            if (weightedCandidates.Sum(i => i.weight) == 0)
                            {
                                if (cycle >= 15 && state != PlacementState.Normal)
                                {
                                    placementFailed = true;
                                    break;
                                }

                                state = PlacementState.Incentive;
                                continue;
                            }
                        }

                        //The above weighting strategy works with this mindset:
                        //If it's an all incentive seed, we just need to manage the incentive locations
                        //If it's an all loose seed, we just need to manage the nonincentive locations
                        //If it's something in between, we switch between those two ideas.
                        //It works, because a nonincentive item goes into a nonincentive chest and can't negatively impact the incentive locations.
                        //and vice versa.
                        //There is a flaw in this simplistic approach. When placing a nonincentive, it could additionally weigh in if it's desired for incentive placment.
                        //But that's a thing for another time to think through

                        //remove the candidates with weight 0(should be unneccesary, but anyway)
                        weightedCandidates = weightedCandidates.Where(w => w.weight > 0).ToList();

                        //shuffle the list(should be totally unneccesary, but it makes me more comfortable)
                        weightedCandidates.Shuffle(rng);

                        //randomly select an item from the weighted list
                        Item nextPlacment = Item.None;

                        float sum = weightedCandidates.Sum(i => i.weight);

                        var r = rng.Next() / (float)uint.MaxValue * sum;

                        sum = 0.0f;
                        foreach (var s in weightedCandidates)
                        {
                            sum += s.weight;
                            if (r <= sum)
                            {
                                nextPlacment = s.item;
                                break;
                            }
                        }

                        //if we didn't find one, just select a random one(this shouldn't happen, but safe is safe)
                        if (nextPlacment == Item.None)
                        {
                            nextPlacment = weightedCandidates.PickRandom(rng).item;
                        }

                        //place the item(same code as in GuidedPlacement to have compatibility with other flags)
                        if (incentives.Contains(nextPlacment))
                        {
                            var rewardSources = accessibleSources.Where(s => incentiveLocationPool.Contains(s)).ToList();

                            //can't happen per definition. We already know, that there is a location available for the item(but in case anything unexpectedly goes wrong sure)
                            if (rewardSources.Count == 0)
                            {
                                continue;
                            }

                            var rewardSource = balancedPicker.Pick(rewardSources, false, _flags.LooseItemsSpreadPlacement, false, rng);
                            placedItems.Add(NewItemPlacement(rewardSource, nextPlacment));
                        }
                        else
                        {
                            var rewardSources = accessibleSources.Where(s => unincentivizedLocationPool.Contains(s)).ToList();
                            if (rewardSources.Count == 0)
                            {
                                continue;
                            }

                            var rewardSource = balancedPicker.Pick(rewardSources, _flags.LooseItemsForwardPlacement, _flags.LooseItemsSpreadPlacement, false, rng);
                            placedItems.Add(NewItemPlacement(rewardSource, nextPlacment));
                        }

                        //remove the item from the lists of items to place
                        allPlacements.Remove(nextPlacment);
                        allKeyItems.Remove(nextPlacment);

                        if (nextPlacment == Item.Ship)
                        {
                            BuildLogic(allRewardSources);
                        }

                        //we placed an item so we should randomly select incentive/nonincentive next cycle
                        state = PlacementState.Normal;

                        //we're finished
                        if (allPlacements.Count == 0)
                        {
                            break;
                        }
                    }

                    //we couldn't place all items or encountered a placement problem
                    if (allPlacements.Count > 0 || placementFailed)
                    {
                        continue;
                    }
                }

                //place the shards(almost same code as in GuidedPlacement)
                if (!placementFailed && shards.Count > 0)
                {
                    var leftoverItemLocations = GetAllAccessibleRewardSources(preBlackOrbLocationPool, placedItems);

                    foreach (var shard in shards)
                    {
                        if (!leftoverItemLocations.Any())
                        {
                            placementFailed = true;
                            break;
                        }

                        placedItems.Add(NewItemPlacement(leftoverItemLocations.SpliceRandom(rng), shard));
                    }
                }

                //finally check the placement(if we arrive here, it's safe, but let's do it anyway in case something goes wrong)
            } while (placementFailed || !_checker.CheckSanity(placedItems, null, _flags).Complete);

            return(new ItemPlacementResult {
                PlacedItems = placedItems, RemainingTreasures = treasurePool
            });
        }