From 50999711bfb9b9fc6eced4b7976d29b0f8f3597a Mon Sep 17 00:00:00 2001 From: Kiel <95580337+kielbasiago@users.noreply.github.com> Date: Sun, 27 Feb 2022 09:17:23 -0500 Subject: [PATCH 01/60] Update coliseum random to handle percentage * Remove shuffle as -crsr 0 is the same as -cos --- args/coliseum.py | 39 +++++++++++++++++++++------------------ data/coliseum.py | 26 ++++++++++++++++---------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/args/coliseum.py b/args/coliseum.py index 6f7d6cfc..81688bb6 100644 --- a/args/coliseum.py +++ b/args/coliseum.py @@ -7,16 +7,20 @@ def parse(parser): coliseum = parser.add_argument_group("Coliseum") coliseum_opponents = coliseum.add_mutually_exclusive_group() - coliseum_opponents.add_argument("-cos", "--coliseum-opponents-shuffle", action = "store_true", - help = "Coliseum opponents shuffled") - coliseum_opponents.add_argument("-cor", "--coliseum-opponents-random", action = "store_true", - help = "Coliseum opponents randomized") + coliseum_opponents.add_argument("-cor", "--coliseum-opponents-random", default = None, type = int, + metavar = "PERCENT", choices = range(101), + help = "Coliseum opponents original with a given percent randomized") + coliseum_opponents.add_argument("-cosr", "--coliseum-opponents-shuffle-random", default = None, type = int, + metavar = "PERCENT", choices = range(101), + help = "Coliseum opponents shuffled and then given percent randomized") coliseum_rewards = coliseum.add_mutually_exclusive_group() - coliseum_rewards.add_argument("-crs", "--coliseum-rewards-shuffle", action = "store_true", - help = "Coliseum rewards shuffled") - coliseum_rewards.add_argument("-crr", "--coliseum-rewards-random", action = "store_true", - help = "Coliseum rewards randomized") + coliseum_rewards.add_argument("-crr", "--coliseum-rewards-random", default = None, type = int, + metavar = "PERCENT", choices = range(101), + help = "Coliseum rewards original with a given percent randomized") + coliseum_rewards.add_argument("-crsr", "--coliseum-rewards-shuffle-random", default = None, type = int, + metavar = "PERCENT", choices = range(101), + help = "Coliseum rewards shuffled and then a given percent randomized") coliseum.add_argument("-crvr", "--coliseum-rewards-visible-random", default = None, type = int, nargs = 2, metavar = ("MIN", "MAX"), choices = range(ITEM_COUNT), @@ -34,16 +38,15 @@ def process(args): def flags(args): flags = "" - - if args.coliseum_opponents_shuffle: - flags += " -cos" - elif args.coliseum_opponents_random: - flags += " -cor" - - if args.coliseum_rewards_shuffle: - flags += " -crs" - elif args.coliseum_rewards_random: - flags += " -crr" + if args.coliseum_opponents_random: + flags += f" -cor {args.coliseum_opponents_random}" + elif args.coliseum_opponents_shuffle_random: + flags += f" -cor {args.coliseum_opponents_shuffle_random}" + + if args.coliseum_rewards_random: + flags += f" -crr {args.coliseum_rewards_random}" + elif args.coliseum_rewards_shuffle_random: + flags += f" -crr {args.coliseum_rewards_shuffle_random}" if args.coliseum_rewards_visible_random: flags += f" -crvr {args.coliseum_rewards_visible_random_min} {args.coliseum_rewards_visible_random_max}" diff --git a/data/coliseum.py b/data/coliseum.py index 095f6a2f..ebb65909 100644 --- a/data/coliseum.py +++ b/data/coliseum.py @@ -30,9 +30,11 @@ def shuffle_opponents(self): for match_index, match in enumerate(self.matches): match.opponent = opponents[match_index] - def randomize_opponents(self): + def randomize_opponents(self, random_opponent_percent = None): + import random + for match in self.matches: - match.opponent = self.enemies.get_random() + match.opponent = self.enemies.get_random() if random_opponent_percent is not None and (random.random() < random_opponent_percent) else match.opponent def shuffle_rewards(self): rewards = [] @@ -44,9 +46,11 @@ def shuffle_rewards(self): for match_index, match in enumerate(self.matches): match.reward = rewards[match_index] - def randomize_rewards(self): + def randomize_rewards(self, random_reward_percent = None): + import random + for match in self.matches: - match.reward = self.items.get_random() + match.reward = self.items.get_random() if random_reward_percent is not None and (random.random() < random_reward_percent) else match.reward def remove_excluded_items(self): import random @@ -76,15 +80,17 @@ def randomize_rewards_hidden(self): self.matches[match_index].reward_hidden = 1 def mod(self): - if self.args.coliseum_opponents_shuffle: + if self.args.coliseum_opponents_random: + self.randomize_opponents(self.args.coliseum_opponents_random / 100.0) + elif self.args.coliseum_opponents_shuffle_random: self.shuffle_opponents() - elif self.args.coliseum_opponents_random: - self.randomize_opponents() + self.randomize_opponents(self.args.coliseum_opponents_shuffle_random / 100.0) - if self.args.coliseum_rewards_shuffle: + if self.args.coliseum_rewards_random: + self.randomize_rewards(self.args.coliseum_rewards_random / 100.0) + elif self.args.coliseum_rewards_shuffle_random: self.shuffle_rewards() - elif self.args.coliseum_rewards_random: - self.randomize_rewards() + self.randomize_rewards(self.args.coliseum_rewards_shuffle_random / 100.0) self.remove_excluded_items() From 6a7ef1584a38eb47bb085e17cde08f85a72218a7 Mon Sep 17 00:00:00 2001 From: Kiel <95580337+kielbasiago@users.noreply.github.com> Date: Sun, 27 Feb 2022 09:22:20 -0500 Subject: [PATCH 02/60] Remove deprecated -cor and -crr flags --- args/coliseum.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/args/coliseum.py b/args/coliseum.py index 81688bb6..357672d5 100644 --- a/args/coliseum.py +++ b/args/coliseum.py @@ -7,17 +7,11 @@ def parse(parser): coliseum = parser.add_argument_group("Coliseum") coliseum_opponents = coliseum.add_mutually_exclusive_group() - coliseum_opponents.add_argument("-cor", "--coliseum-opponents-random", default = None, type = int, - metavar = "PERCENT", choices = range(101), - help = "Coliseum opponents original with a given percent randomized") coliseum_opponents.add_argument("-cosr", "--coliseum-opponents-shuffle-random", default = None, type = int, metavar = "PERCENT", choices = range(101), help = "Coliseum opponents shuffled and then given percent randomized") coliseum_rewards = coliseum.add_mutually_exclusive_group() - coliseum_rewards.add_argument("-crr", "--coliseum-rewards-random", default = None, type = int, - metavar = "PERCENT", choices = range(101), - help = "Coliseum rewards original with a given percent randomized") coliseum_rewards.add_argument("-crsr", "--coliseum-rewards-shuffle-random", default = None, type = int, metavar = "PERCENT", choices = range(101), help = "Coliseum rewards shuffled and then a given percent randomized") From fa84fbb2a87d85d626bd3714f716c2fec141273c Mon Sep 17 00:00:00 2001 From: Kiel <95580337+kielbasiago@users.noreply.github.com> Date: Sun, 27 Feb 2022 09:24:24 -0500 Subject: [PATCH 03/60] readd cor and crr flags --- args/coliseum.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/args/coliseum.py b/args/coliseum.py index 357672d5..81688bb6 100644 --- a/args/coliseum.py +++ b/args/coliseum.py @@ -7,11 +7,17 @@ def parse(parser): coliseum = parser.add_argument_group("Coliseum") coliseum_opponents = coliseum.add_mutually_exclusive_group() + coliseum_opponents.add_argument("-cor", "--coliseum-opponents-random", default = None, type = int, + metavar = "PERCENT", choices = range(101), + help = "Coliseum opponents original with a given percent randomized") coliseum_opponents.add_argument("-cosr", "--coliseum-opponents-shuffle-random", default = None, type = int, metavar = "PERCENT", choices = range(101), help = "Coliseum opponents shuffled and then given percent randomized") coliseum_rewards = coliseum.add_mutually_exclusive_group() + coliseum_rewards.add_argument("-crr", "--coliseum-rewards-random", default = None, type = int, + metavar = "PERCENT", choices = range(101), + help = "Coliseum rewards original with a given percent randomized") coliseum_rewards.add_argument("-crsr", "--coliseum-rewards-shuffle-random", default = None, type = int, metavar = "PERCENT", choices = range(101), help = "Coliseum rewards shuffled and then a given percent randomized") From 51636fc38a5d5fe9a8ac9ebb3299774efeac1242 Mon Sep 17 00:00:00 2001 From: Kiel <95580337+kielbasiago@users.noreply.github.com> Date: Sun, 27 Feb 2022 11:20:46 -0500 Subject: [PATCH 04/60] Remove removed shuffle options from options --- args/coliseum.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/args/coliseum.py b/args/coliseum.py index 81688bb6..35d0868a 100644 --- a/args/coliseum.py +++ b/args/coliseum.py @@ -65,15 +65,11 @@ def options(args): result = [] opponents = "Original" - if args.coliseum_opponents_shuffle: - opponents = "Shuffle" - elif args.coliseum_opponents_random: + if args.coliseum_opponents_random: opponents = "Random" rewards = "Original" - if args.coliseum_rewards_shuffle: - rewards = "Shuffle" - elif args.coliseum_rewards_random: + if args.coliseum_rewards_random: rewards = "Random" rewards_visible = "Original" From aa2bc0a1d2b8e4d7454198718b3381efc2c467e4 Mon Sep 17 00:00:00 2001 From: Franklin Date: Mon, 25 Jul 2022 11:11:40 -0700 Subject: [PATCH 05/60] Added as misc. options to have NPC dialog replaced with general game tips. --- args/misc.py | 11 +- data/dialogs/dialogs.py | 316 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 326 insertions(+), 1 deletion(-) diff --git a/args/misc.py b/args/misc.py index 7d55fc10..215a2dfb 100644 --- a/args/misc.py +++ b/args/misc.py @@ -14,6 +14,10 @@ def parse(parser): misc.add_argument("-scan", "--scan-all", action = "store_true", help = "All enemies scannable. All characters start with scan learned. Scan costs 0 MP. Useful for testing/debugging") + ### new option for NPC tips + misc.add_argument("-npctips", "--npc-dialog-tips", action = "store_true", + help = "NPC provide general game tips") + event_timers = misc.add_mutually_exclusive_group() event_timers.add_argument("-etr", "--event-timers-random", action = "store_true", help = "Collapsing House, Opera House, and Floating Continent timers randomized") @@ -72,6 +76,10 @@ def flags(args): if args.scan_all: flags += " -scan" + ### NPC tips + if args.npc_dialog_tips: + flags += " -npctips" + if args.event_timers_random: flags += " -etr" elif args.event_timers_none: @@ -148,7 +156,8 @@ def options(args): ("Scan All", args.scan_all), ("Event Timers", event_timers), ("Y NPC", y_npc), - ("Remove Flashes", remove_flashes) + ("Remove Flashes", remove_flashes), + ("NPC Tips", args.npc_dialog_tips), ] def menu(args): diff --git a/data/dialogs/dialogs.py b/data/dialogs/dialogs.py index 79803299..32e8fec6 100644 --- a/data/dialogs/dialogs.py +++ b/data/dialogs/dialogs.py @@ -183,6 +183,322 @@ def mod(self): self.move_battle_messages() self.objectives_mod() + #### NPC dialog tip mod + import args + if args.npc_dialog_tips: + self.set_text(81, "The Figaro Throne reward exists in both worlds!") + self.set_text(82, "MasterPug doesn't grant any XP. Too bad…") + self.set_text(83, "Blue Drgn uses Water magic. Use Imp gear to absorb it.") + self.set_text(85, "Open a hidden stairwell in the Ancient Castle by walking 5 steps south of the queen's throne and pressing “A”.") + self.set_text(87, "Use a combination of RunningShoes and Wall Ring to be completely safe from Tentacles.") + self.set_text(89, "Hitting an enemy with Air Anchor means it'll be dead after its next turn.") + self.set_text(90, "Drill and Chain Saw both ignore enemy defenses.") + self.set_text(91, "Drill and Chain Saw both ignore enemy defenses.") + self.set_text(92, "Chests in Figaro Castle are the same in both worlds…") + self.set_text(93, "But shops sell different items!") + self.set_text(98, "Hey! Let us outta here!") + self.set_text(100, "Hey! Let us outta here!") + self.set_text(101, "Hey! Let us outta here!") + self.set_text(102, "When given a dialogue option, hold up/down before the options appear to automatically select the top or bottom option. This is useful for Auction House or Ancient Castle.") + self.set_text(176, "An old clock.") + self.set_text(177, "An old clock.") + self.set_text(179, "Towns sell different things between worlds, even if they look the same.") + self.set_text(180, "South Figaro has more free chests than any other town! The mansion's basement is open right from the setart.") + self.set_text(181, "If you enter Mt. Kolts from the south and exit from the north, the airship will follow you to the northern exit.") + self.set_text(182, "If you land a Pummel on Vargas, the fight will end immediately. No XP from him if you do it that though!") + self.set_text(183, "There are 2 hidden paths in Mt. Kolts that lead to treasure chests.") + self.set_text(184, "Equip relics to gain a variety of abilities!") + self.set_text(185, "Most Blitz moves utilize a character's MagPwr instead of Vigor. The 2 Blitzes that use Vigor are Pummel and Suplex.") + self.set_text(198, "Thrown weapons ignore enemy defenses - doesn't matter what type of weapon either.") + self.set_text(199, "Equip/unequip weapons/shields in battle to gain utility from weapons like Drainer or Assassin, take off an elemental shield to break it, or equip a Cursed Shld mid-battle. You can even take your weapon off and then throw it!") + self.set_text(201, "Shadow's Interceptor helps block attacks and can counter with powerful magic damage.") + self.set_text(202, "After recruiting a character, set up your party for Zone Eater or Veldt to save an airship trip. You can also do this after certain checks like Phoenix Cave.") + self.set_text(203, "Thrown elemental weapons will hit elemental weaknesses.For example, Blossom is Wind. Trident is Water.") + self.set_text(204, "If you're not sure you can survive a fight, running away with just one character will let you survive the battle.") + self.set_text(205, "Buy more consumables than you think you'll need! Better safe than sorry.") + self.set_text(206, "I'm blocking the eastern exit now, but in the World of Ruin I won't be here.") + self.set_text(207, "Hold A in the menus during battle to select commands/spells fast - best done with Memory Cursor.") + self.set_text(208, "Hold A in the overworld to buffer interactions with NPCs/chests/the environment.") + self.set_text(210, "If you Jump with a spear, it will do double damage instead of 50% more damage that a normal weapon would do.") + self.set_text(211, "Jumps land faster if characters are Hasted or have higher Speed.") + self.set_text(213, "Fixed encounter checks are good after free progression checks to catch up in levels before a boss fight.") + self.set_text(215, "There's always a nasty draft in this room. Check behind the bookshelf.") + self.set_text(216, "Depending on the seed, enemies will get stronger based on 1. How many characters, Espers, dragons, and/or checks you've done2. How high your party's levels are3. How much time has passed in the game.Check the flags if you're not sure!") + self.set_text(221, "Save before certain checks and reset if the reward is something you don't need. We call this a Save Scum.") + self.set_text(228, "When entering a map on a staircase, hold up or down, then once your character turns, you're able to walk the stairs faster. Saves time and looks cool.") + self.set_text(229, "Memorize each map to be able to buffer the direction you will need to walk ahead of time.") + self.set_text(230, "Enter South Figaro Cave from the south entrance to trigger the boss fight near the healing pool.") + self.set_text(297, "Morph will double the damage of spells and physical damage as long as it doesn't ignore defense.") + self.set_text(313, "Check this room for a hidden treasure passage!") + self.set_text(474, "Cherub Downs and Gaia Gear help with Dirt Drgn's Earth attacks.") + self.set_text(476, "Use Sleep to put Dirt Drgn to bed.") + self.set_text(599, "Welcome to Worlds Collide! NPCs throughout this world will now give you useful tips instead of vanilla dialog.By the way, default settings allow every character to equip a Moogle Charm.") + self.set_text(600, "Some monsters will freeze your characters. Use Fire to restore them to back to normal.") + self.set_text(601, "Phantom Forest and South Figaro Cave have healing springs that work like this pot.") + self.set_text(602, "This is a Save Point.You may use Sleeping Bags and Tents here. If you can, use Sleeping Bags to save time.") + self.set_text(603, "Check barrels, clocks, crates, and pots such as this one for hidden items.") + self.set_text(604, "Staying at an Inn will revive your party in full.") + self.set_text(605, "Ha!Sometimes monsters lurk inside of treasure chests!Memorize all locations of these kinds of chests! It will help in future runs!") + self.set_text(606, "Relics?") + self.set_text(607, "If the battle mode is set to “Wait”, opening a menu during battle/having the cursor up for some commands will pause ATB. We call this a “Wait trick” - use it to plan out actions in menus while letting animations play.") + self.set_text(608, "When shopping, you'll see some symbols next to your characters:Triangles pointing up indicate increasing battle power.Triangles pointing down indicate decreasing battle power.“=” indicates no change in battle power.“E” means the item is already equipped on that character.A symbol under a character means that person is now in your party.") + self.set_text(609, "Select the “Wait” Battle Mode from the Config Menu to take all the time you need to select spells or items without being attacked. Easiest settings? Set Battle Speed to 6 and Message Speed to 1.") + self.set_text(610, "Run from some battles by pressing (and holding) both the L and R Buttons.This can take time, but will be fast in a preemptive strike/side attack.Stock up on Smoke Bombs/Warp Stones and use them to escape.") + self.set_text(611, "In fights, “X” cycles character turns in order. “Y” cycles to the last character that had their ATB filled.") + self.set_text(612, "When selecting a spell, press the L or R Button to select multiple targets.Sometimes, this causes the damage to be lower than a single target attack, so be careful!") + self.set_text(614, "Damage is more severe when caught in a pincer attack! Enemies that hit you from behind deal double damage.") + self.set_text(616, "In the back row, damage and attack power are halved.Change rows using the Main Menu: press left on the Control Pad, then press “A”.Maximize character's Fight damage by placing them in the front row. Characters not using Fight should be kept in the back.") + self.set_text(619, "Use a curative spell or item on an undead creature for maximum damage.This will not work on some bosses under specific flags.") + self.set_text(621, "3-way attack indicates a fire, ice and lightning attack.If an enemy nullifies or absorbs ANY ONE of those elements, they will nullify/absorb all the damage.This applies to attacks like Maduin and Tritoch summons.") + self.set_text(622, "Use Rflect on your party to change enemy scripts - try it against SrBehemoth or Red Dragon.") + self.set_text(623, "Runic turns many spells, including those used by other party members, into MP. Can be used repeatedly, and expires when the Runic activates or the user performs another action.Morph increases Attack/Magic power. Duration increases after battles awarding Magic Points.Once selected, Dance and Rage make the user uncontrollable until KO or the battle is over.") + self.set_text(625, "To use an Esper it must be equipped. Choose “Skills” from the menu, then select “Espers.”During battle, select Magic, and press up on the Control Pad. Press the A Button to use the Esper.An Esper can only be used once per battle if the Multi Summon flag is off.Learning MagicLearn new spells by equipping Espers. Switch Espers to learn different sets of spells.The higher the “Learning Speed” the faster a spell is learned.When equipped, some Espers will raise qualities (Strength, HP, MP etc.) to their maximum limits at the next “level up.”") + self.set_text(626, "When HP is critical, you go into “Near Fatal” status. During this condition, you have a 1 in 16 chance for a super strong desperation move when choosing “Fight”.") + self.set_text(627, "Want to learn more about Espers? Yes No") + self.set_text(628, "See the NPC at the very end of this hall by the last door.") + self.set_text(629, "Each SwdTech has its own unique name.You'll gain more SwdTech skills by leveling up or completing objectives.") + self.set_text(630, "“Rflect” doesn't block spells that have been “Rflected” off others.Enemy protected by Rflect?Try bouncing an attack off a Rflect-protected individual in your party!") + self.set_text(744, "Io Rage uses Flare Star, which does damage based on the enemies' levels - it's solid if you're under-leveled.Prussian and Luridan Rage use Land Slide - stronger than Flare!") + self.set_text(745, "Talk to a shopkeeper to get the next letter to appear when doing the Injured Lad quest.") + self.set_text(746, "Each item you send for the Injured Lad costs roughly 1 to 3000 GP.") + self.set_text(747, "The Veldt check can be done in both the World of Balance and World of Ruin.This check is best done early if possible. Only Lobos roam the Veldt at the very start of a seed.") + self.set_text(748, "Summon Ragnarok to Morph Lobos on the Veldt for Dried Meat. Most beast-like enemies also Morph into Dried Meat.") + self.set_text(749, "The Veldt check reward will not appear on a back or pincer attack, nor will it appear after several battles against formations with many enemies. Ensure you have Dried Meat and less than 3 characters in the party when doing this check.") + self.set_text(750, "The Doom Drgn Rage freezes enemies. It works on bosses, but has a chance of failing.The Nightshade Rage charms enemies, but sometimes is nerfed - check the flags!") + self.set_text(752, "Many Rage specials boost physical damage, like Stray Cat, Gold Bear, and Trooper. Try them with Sniper or Man Eater. Even Fixed Dice damage benefits from these kinds of boosts!") + self.set_text(753, "Heard about the Serpent Trench? Yes No") + self.set_text(754, "Jump into the Trench from Crescent Mountain.") + self.set_text(755, "Jump into the Trench from Crescent Mountain.") + self.set_text(756, "In the Serpent Trench, go right twice to get both chests.") + self.set_text(757, "The Injured Lad quest can be started in the house right behind me.") + self.set_text(758, "By default, Rage users will learn monster skills after defeating them in battle.This eliminates the need to use Leap on the Veldt.") + self.set_text(759, "Dried Meat is guaranteed to be in at least 1 shop. It can randomly show up in chests too.Don't forget the airship has a shop too!") + self.set_text(761, "The entrance to the Serpent Trench is south of here, in Crescent Mountain.") + self.set_text(805, "If you wish, you can fight the enemy soldier NPCs in the Doma Castle siege.Your party will be fully healed after you defeat the boss there.") + self.set_text(806, "Hit the chest, don't kick it!") + self.set_text(807, "Items sold will be at most 1/2 their shop value, depending on the seed.") + self.set_text(809, "You can exit from this town from both the east and west.") + self.set_text(810, "South Figaro-bound ferry. (No thanks) (Hop aboard)") + self.set_text(812, "Nikeah-bound ferry. (No thanks.) (Hop aboard)") + self.set_text(813, "After the Serpent Trench check, you'll end up on the docks of this town.") + self.set_text(814, "Some towns have all 4 shop types: Item, Weapon, Armor, and Relic. This is one of them!") + self.set_text(815, "DANCER: Yoo hoo! You handsome thing. How 'bout joining me?Tee hee!") + self.set_text(825, "Save time shopping by knowing exactly what you want to buy ahead of time.") + self.set_text(826, "With No Priceless Items on, consider selling items for high GP values early on, like Elixirs or Genji Helmets.") + self.set_text(917, "The reward you don't pick during the Lone Wolf check can be found in the World of Ruin Moogle Cave.") + self.set_text(918, "With good timing you can run past the last line of soldiers during the Kefka at Narshe battle!") + self.set_text(919, "You can't warp out of the Narshe mines in World of Balance, except for the Moogle Cave.") + self.set_text(920, "In World of Ruin Narshe, some doors might be locked until you recruit Locke.") + self.set_text(926, "A Peace Ring or Ribbon can help deal with the negative effects of a Cursed Shld in battle.") + self.set_text(927, "The entrance to Umaro's cave can be found in the World of Ruin, after defeating the boss at Tritoch. If the reward is a character, they will be peeking out of a cave just north of town.") + self.set_text(928, "Once you spot him, follow Lone Wolf to the Narshe peak!") + self.set_text(929, "Use caution with Umaro: enemies like Whelk have mechanics where you need to carefully time your attacks.") + self.set_text(930, "Umaro comes equipped with a Snow Muffler. Consider giving this to another character, or selling even it.") + self.set_text(931, "Umaro's body slam attack ignores enemy defense.") + self.set_text(932, "Activate the Security Checkpoint from the checkpoint room's southern door in the World of Balance.") + self.set_text(981, "Beyond is the Engine Room. Head to Figaro Castle World of Ruin to go below.") + self.set_text(986, "ValiantKnife's HP differential damage is not subject to Offering's damage penalty - making for an effective pair.") + self.set_text(987, "Power up ValiantKnife by unequipping and reequipping Muscle Belt/Red Cap before a battle.Try not to heal ValiantKnife wielders in-between fights.") + self.set_text(988, "The airship faces North when entering from the world map. Buffer the direction you want to go as you enter.") + self.set_text(989, "If the Falcon allows you to “Search the Skies”, select that to start the Doom Gaze spot's fight immediately.") + self.set_text(990, "Hold R or L to turn the Airship faster.") + self.set_text(991, "Hold R or L to turn the Airship faster.") + self.set_text(992, "Sniper and Hawk Eye both have a 50% chance to deal 150% damage / 300% damage against floating units.") + self.set_text(993, "Fixed Dice ignores enemy defense and deals damage based on character level and the dice rolls.") + self.set_text(994, "Hold Y when flying to strafe. You won't trigger the Search the Skies encounter while moving this.Avoid that encounter entirely by flying to where your destination would be in the World of Balance, then switching to the World of Ruin and landing. Check your minimap to learn where locations are in both worlds.") + self.set_text(1012, "ValiantKnife deals bonus damage equal to the difference between the wielder's max HP and current HP.") + self.set_text(1014, "The Kohlingen Inn reward appears in both worlds.") + self.set_text(1016, "You might not expect it, but Dice and Fixed Dice gain Jump damage bonuses.") + self.set_text(1018, "Higher levels and equipping Sneak Ring will improve Steal success rate.") + self.set_text(1019, "Phoenix revives all Wounded characters in battle.") + self.set_text(1028, "Higher levels and equipping Sneak Ring will improve Steal success rate.") + self.set_text(1029, "Save time by only attacking with your highest damage character to reduce animation time.") + self.set_text(1030, "Two items in the Auction House can be bought repeatedly. That's not the case in World of Ruin though!") + self.set_text(1031, "Strength or MagPwr +2 Espers may guide which character builds you end up using.Consider keeping characters at low levels (e.g. out of the party) to maximize the use of the bonus later, after another character has made good use of it.") + self.set_text(1032, "Higher Stamina will increase the amount of damage you take from Poison each turn, as well as the amount of HP healed by Regen each turn.") + self.set_text(1033, "The Opera House is far to the south of here.") + self.set_text(1034, "Want physical damage? Go for Atlas Armlet or Hero Ring. Magic user? Equip 2 Earrings, 2 Hero Rings, or 1 of each.") + self.set_text(1036, "Bring Warp Stones if you venture into Zozo - both Mt. Zozo and Zozo Tower are long climbs down.") + self.set_text(1038, "In Zozo, the thieves will give you clues to solving the clock puzzle. Remember, they're all liars!") + self.set_text(1039, "Breaking elemental shields cast tier 3 spells that ignore defense and Rflect status.") + self.set_text(1040, "Breaking Rods cast tier 2 spells that ignore defense and Rflect status.") + self.set_text(1041, "The Auction House will always have 2 high tier items or Espers at 20000 GP and 10000 GP.") + self.set_text(1042, "Quick is a great defensive spell. After casting it, enemy ATB is frozen until your caster completes 2 turns.Enemies won't be able to counter your first turn actions. Damage your enemy one turn, then heal up the next.") + self.set_text(1206, "Oh my hero, so far away now. Will I ever see your smile?Love goes away, like night into day. It's just a fading dream…I'm the darkness, you're the stars. Our love is brighter than the sun. For eternity, for me there can be,only you, my chosen one…Must I forget you? Our solemn promise? Will autumn take the place of spring?What shall I do? I'm lost without you. Speak to me once more!…here you pick up the flowers.Climb the stairs to the balcony high atop the castle. Raise the flowers to the stars.") + self.set_text(1260, "Talk to Impresario first.") + self.set_text(1262, "Hit the 3rd switch from the left.") + self.set_text(1329, "Illumina deals the same amount of damage from the back row as the front.") + self.set_text(1334, "My child will heal you for 1 HP at a time!") + self.set_text(1338, "Magus Rod provides +7 MagPwr and 30% MBlock, an ideal choice for spellcasters.") + self.set_text(1339, "Man Eater will do 2x damage on human targets. SwordBreaker provides 30% Evade.") + self.set_text(1344, "Illumina's Pearl will critical hit with MP, dealing twice the damage as a normal Pearl.") + self.set_text(1346, "If you deal a killing blow with Ragnarok or Illumina, there will be no spell proc.") + self.set_text(1355, "Illumina and Ragnarok provide great Evade and MBlock - they're like great shields.") + self.set_text(1365, "Danger…") + self.set_text(1367, "Aura Lance has stronger battle power, but Pearl Lance can proc the Pearl spell.") + self.set_text(1368, "Tempest has a 50% chance of casting Wind Slash instead of its regular attack. Try it with Offering.") + self.set_text(1507, "Level 42 yet?") + self.set_text(1521, "The other reward you didn't pick is in the Narshe Mines World of Balance.") + self.set_text(1522, "Gogo and Umaro can't uncurse the Cursed Shld, so don't try it with them!") + self.set_text(1551, "Offering makes Fight swing 4 untargettable times with a 100% hit rate and 1/2 damage for each attack.") + self.set_text(1552, "Certain weapons drain MP to guarantee a critical hit. These criticals won't occur with Offering equipped, however.") + self.set_text(1553, "Desperation attacks have extremely high spell power and defense ignoring capabilities - don't forget about them when you're up against a wall!") + self.set_text(1554, "Physical damage is largely based off your level. Pair your Exp. Eggs with physical damage dealers.") + self.set_text(1555, "Atma Weapon damage increases with Level, but is not dependent on max HP.Damage IS lowered the more HP you have lost.") + self.set_text(1556, "Gauntlet is great with natural command Jump characters. It also works well with Fight at higher levels if you have a strong weapon, but beware of attacks since you can't equip a shield.") + self.set_text(1558, "Haste isn't great at getting extra turns, but can help for getting the first turn or running quicker.") + self.set_text(1560, "Memento Ring does the same thing as Safety Bit.") + self.set_text(1562, "The Remedy spell won't restore Imp status, but the item will.") + self.set_text(1564, "Seizure deals damage like Poison, but Antidote items won't cure it. Use Remedy.") + self.set_text(1566, "The Remedy spell will remove Stop status, but the item won't.") + self.set_text(1568, "Safety Bit protects against Demi, Quartr, W-Wind, Doom, and all other fractional or instant death attacks.It also prevents the Petrify status too! Great against Delta Hit.") + self.set_text(1573, "Recover from status effects in a pinch by KOing your own character, then reviving them. This can be a lifesaver if you need to unmute/unfreeze somebody!") + self.set_text(1574, "Berserked characters will be able to use: Fight, Capture, Jump, Rage, Magitek. They also deal more damage!") + self.set_text(1575, "Equipping a Relic that prevents a status will also heal that status immediately.") + self.set_text(1576, "Each time you apply Poison to a monster, it will deal 100% more damage during each poison tick (up to 8x).") + self.set_text(1577, "Muddled characters can be deadly. Hit them with the Fight command.") + self.set_text(1581, "Use Scan to identify enemy elemental weaknesses. Spells and elemental weapons can take advantage of those.") + self.set_text(1584, "Merton damage can be stopped with elemental shields, Rage Ring, Blizzard Orb, Red Jacket, Paladin Shld, and Minerva.") + self.set_text(1587, "Quake heals your whole party if they're all wearing Gaia Gear.") + self.set_text(1588, "Magic damage is greatly boosted by the MagPwr stat. Even gear like Magus Hat/White Dress can be worthwhile.") + self.set_text(1589, "Outside of battle, use Cure instead of Cure 2 and 3. It's more MP efficient!") + self.set_text(1590, "Fire, Ice, and Bolt 3 are incredibly strong spells you can use throughout the entire game.") + self.set_text(1592, "Pearl is a strong spell, but several bosses surprisingly absorb it: Atma, Goddess, Wrexsoul.") + self.set_text(1594, "Beating the boss at the Atma Weapon spot in Kefka's Tower will reward a high tier item and a Save Point.") + self.set_text(1595, "You can't get the treasure box behind me unless the world ends!") + self.set_text(1596, "Welcome!") + self.set_text(1597, "Outta here!") + self.set_text(1598, "Broke? Sell unused items!") + self.set_text(1599, "Whooopie!") + self.set_text(1600, "NPCs here have tips related to Magic and elements.") + self.set_text(1601, "Elemental shields teach tier 2 magic while equipped. Trade shields in your party to teach them to other characters.") + self.set_text(1602, "Meteor deals full damage even against multiple targets. It's not the strongest option against a single enemy, though.") + self.set_text(1603, "Cursed Rings teach X-Zone while equipped.") + self.set_text(1605, "Doom is more accurate at hitting enemies than X-Zone, but is only single target.") + self.set_text(1607, "Flare ignores enemy defense, although it takes a long time to cast.") + self.set_text(1608, "Sure!") + self.set_text(1734, "If you're looking for Lone Wolf, recruit Mog first and come back here!") + self.set_text(1748, "If you should perish, you'll be able to play from your last save. You can save a game anywhere on the world map.") + self.set_text(1750, "Use Osmose on enemies if you need to recover MP mid-fight.") + self.set_text(1751, "Mirage Vests will provide Image status at the start of every tier.") + self.set_text(1766, "Dance chances, in order of their listing in the Skills menu:7/16, 6/16, 2/16, 1/16Think about those odds when Dancing!") + self.set_text(2082, "Pearl Wind heals the party for the same amount of HP the caster has.") + self.set_text(2083, "Try dodging Burning House flames by running past them the same moment they start moving towards you.") + self.set_text(2084, "Blow Fish is a great early Lore - unblockable 1000 damage!") + self.set_text(2085, "Characters won't learn Lores if they have the Dark status. Use Eyedrops or Remedy before the battle ends!") + self.set_text(2201, "The Tzen Thief in World of Balance will mention a glowing stone if he is selling an Esper. However, he won't tell you that here in the World of Ruin.") + self.set_text(2202, "Gigantos is weak to instant death and only uses physical damage. Use Phantom to be safe.") + self.set_text(2203, "Monster in a box fights CAN be run from. But sometimes it's better to save before opening them if you're looking for Gigantos (EXP) or PM Stalkers (MP).") + self.set_text(2204, "Don't use instant death on Specter, PM Stalkers, or other undead - they'll be fully healed.") + self.set_text(2205, "Allo Ver can be taken out with a Revivify or Fenix Down. Sketch works too.") + self.set_text(2206, "Telstar will start summoning Soldiers if you don't kill it quickly.") + self.set_text(2207, "Claw weapons dropped by Presenter and Allo Ver often sell for very high GP.You can't Throw them, so why not get some GP instead?") + self.set_text(2208, "You can steal Minervas from Pugs, and win them from the fight as drops too.") + self.set_text(2209, "The Tzen Thief price can vary between 1 and 65535 GP, and it is different between worlds.") + self.set_text(2212, "There are monsters inside!") + self.set_text(2229, "NPCs here have tips for the game's final battle. There are 4 tiers to it, and each tier has its own challenges.") + self.set_text(2230, "Beads have a hidden effect to block physical attacks. Use them to dodge dangerous physical attacks like Calmness.") + self.set_text(2231, "Girl absorbs all elemental attacks. Use non-elemental damage on her.") + self.set_text(2232, "Heal Sleep out of Meteo phase (less than 10000 HP) if you're overwhelmed. Revivify will heal him for 5000 HP.") + self.set_text(2233, "Summon Fenrir or Golem in Tier 3 - they'll protect your entire party from Calmness.") + self.set_text(2234, "Tiger in Tier 2 can freeze your party, or turn them into Zombies. It's weak to Ice magic.") + self.set_text(2235, "You can retain your Morph infinitely if it runs out during a tier phase transition, or if the character is frozen or stopped.") + self.set_text(2236, "Kefka can't counterattack while charging Goner. Save big damage for when the screen starts shaking.") + self.set_text(2237, "Long Arm of Tier 1 and Tools in Tier 2 are weak to instant death. Mute or summon Siren against Magic in Tier 2.") + self.set_text(2238, "Vanish status or the Phantom summon will prevent all damage from 10 Hits or Tier 1 physicals.") + self.set_text(2239, "Watch me despawn this entire room…") + self.set_text(2240, "It'll never be the same again!") + self.set_text(2246, "You can only use Magic and Item commands in here.") + self.set_text(2247, "Check the wall to the right of the chest in this tower's first treasure room.") + self.set_text(2250, "You'll have to defeat the boss at the top to get the reward down here.") + self.set_text(2278, "We know about Esper summons!") + self.set_text(2279, "Summon Ragnarok to Morph dragons for a 1/8 instant kill chance.") + self.set_text(2280, "If affected by party-wide status effects (like Train), Unicorn is a great summon (casts Remedy on everyone).") + self.set_text(2281, "Odin and Raiden are instant death attacks on all enemies. Summon Sraphim and Starlet for a party-wide heal.") + self.set_text(2282, "Phunbaba's BabaBreath will send up to 2 of your characters back to the airship.You'll have at least 1 character remaining in the party afterwards.") + self.set_text(2306, "Queue up attacks as you summon Palidor for extra damage.") + self.set_text(2323, "Set Battle Speed to 1 before fighting Zone Eater. This will save you time if it decides to use Demi instead of Engulf. But don't forget sure to change it back after!") + self.set_text(2324, "Use Mimic to re-use items like Super Balls, elemental shields, or strong throws like Excalibur. You can “Mimic” Mimic to keep using the same throw indefinitely.") + self.set_text(2326, "Flare, Hyper Drive, and Ultima all can be Runic'ed.") + self.set_text(2327, "Got money? GP Rain deals more damage with higher levels, and ignores all enemy defenses.") + self.set_text(2328, "Set Gogo's 3 unused command slots to any abilities of your choosing in his Status menu.") + self.set_text(2329, "If Possess hits, it will immediately kill its target. Works on bosses too!") + self.set_text(2335, "Once you start the Doma Dream sequence, you won't be able to warp out. Be careful when saving!") + self.set_text(2340, "Don't forget the reward on the Doma throne after finishing up the dream sequence.") + self.set_text(2350, "Left Crane absorbs Lightning. Right Crane absorbs Fire. Think: Left for Lightning!") + self.set_text(2351, "Fight Wrexsoul with physical damage, since his magic defense is so high.") + self.set_text(2353, "When Piranhas appear, you'll have to wait 5-55 seconds before Rizopas - the true final boss - appears.") + self.set_text(2354, "Ultros is always weak to Fire and always absorbs Water.") + self.set_text(2355, "Dragon Horn gives you 2-4 jumps a turn - a must for any Dragoon build. Make sure your character has Jump!") + self.set_text(2356, "If you defeat Moe and Curly in the stooges fight, Larry will eventually run away. Larry is weak to instant death.") + self.set_text(2357, "Use Ice on Ifrit, and Fire on Shiva. Non-elemental will work on both.If you time it right, you can even run away from the battle during their transition phases.") + self.set_text(2358, "Casting Imp on Number 024 and SrBehemoth will force them into using physical attacks only.") + self.set_text(2364, "Try using Pearl Rods on Doom Gaze, who has high magic defense but is weak to Pearl.") + self.set_text(2365, "Despite his appearance, Dullahan is weak to Fire.") + self.set_text(2366, "Chadarnook is weak to Fire and Pearl…") + self.set_text(2367, "But don't attack while the girl is visible!") + self.set_text(2368, "Poltrgeist is vulnerable to Stop and weak to Poison.") + self.set_text(2369, "MagiMaster is vulnerable to Bserk. Also, you can hit him with one elemental attack before the first WallChange. Make it count!") + self.set_text(2370, "Phunbaba has a lot of HP, but is weak to Poison. Just don't use Bolt on him!") + self.set_text(2373, "Inferno uses a lot of powerful magic attacks. Focus down the main body with Bolt magic.") + self.set_text(2397, "Debilitator will create a new elemental weakness for its target.") + self.set_text(2398, "Flash and BioBlaster both deal magic damage. Flash applies Dark status, while BioBlaster applies Poison.") + self.set_text(2400, "Sketch, if it hits, has a 25% of killing KatanaSoul.") + self.set_text(2402, "Muddle KatanaSoul to kill him fast.") + self.set_text(2403, "The Figaro Throne reward exists in both worlds!") + self.set_text(2421, "Try fighting here early in a seed. Low level enemies won't have MP to use their abilities.") + self.set_text(2424, "My Colosseum is built in the World of Ruin!") + self.set_text(2425, "This is my colosseum.") + self.set_text(2426, "This is my colosseum.") + self.set_text(2427, "I'm in 4 different boss fights throughout the game!") + self.set_text(2429, "Colosseum can turn low GP items into great ones. Buy stacks of cheap items to maximize Colosseum potential.") + self.set_text(2430, "Umaro is great in the Colosseum since he won't cast random Magic spells in battle.") + self.set_text(2431, "Betting the Striker here doesn't unlock any character.") + self.set_text(2432, "This is Dragon's Neck Colosseum.") + self.set_text(2436, "You can enter the Phoenix Cave as long as you have at least 2 characters.") + self.set_text(2437, "Those beautiful days…") + self.set_text(2438, "Pressing Start in battle to pause the game can help with lining up slots in the Slots command.") + self.set_text(2442, "Red Dragon uses Fire magic, and is weak to Ice. You can also Muddle it!") + self.set_text(2443, "At low enough HP, Red Dragon will start using Flare too - which isn't Fire elemental!") + self.set_text(2444, "Slots' triple Bar will summon a random Esper... hope it's not Crusader!") + self.set_text(2446, "Several Slots results can deal non-elemental, unblockable damage to enemies.") + self.set_text(2453, "Slots will give leniency after you stop the first reel, allowing you to land 7-Flush and Chocobop if you can line up Diamonds/Chocobo on the first reel.") + self.set_text(2454, "Spikes in the Phoenix Cave will drop your HP as you step on them!") + self.set_text(2455, "Slots' Lagomorph can heal Dark, Sleep, and Poison.") + self.set_text(2457, "Slots RNG starts off the same in each battle. If you can use H-Bomb turn 1 in a battle, it'll work in every battle if nothing has affected the RNG.") + self.set_text(2532, "SwdTech 2 and 6 deal more damage with higher MagPwr (instead of Vigor).") + self.set_text(2533, "Buy Rust Aid in Zozo for 1000 GP to gain access to Mt. Zozo.") + self.set_text(2536, "If the Retort bug fix flag is disabled, try KO'ing your SwdTech user, reviving them, and then using Retort.") + self.set_text(2537, "What shall I talk about? (Narshe) (The Veldt) (Doma Castle) (Nothing, thanks!)") + self.set_text(2538, "Storm Drgn is weak to Bolt attacks, despite its name.") + self.set_text(2539, "Use SwdTech 5 to restore both HP and MP.") + self.set_text(2541, "Did you know that SwdTech damage is not increased by equipped weapon battle power?SwdTech 1, 4, and 7 do get stronger with higher Vigor, however.") + self.set_text(2603, "Characters selected to fight here have HP restored before battle, even Wounded ones.There's no restore after battles - so if they're injured when the fight's done, they'll stay that way!") + self.set_text(2623, "Hold A at the victory screen to skip text at max speed.") + self.set_text(2626, "In tough battles, wait to see the outcome of enemy attacks before queuing up your own.") + self.set_text(2640, "Equip the Cursed Shld in the middle of a battle to avoid its negative status effects entirely.If the character survives the battle, this will still count towards uncursing it!") + self.set_text(2678, "128+ Evade/MBlock will block everything except unblockable abilities like Ultima.") + self.set_text(2679, "Higher Stamina will increase the amount of damage you take from Poison each turn, as well as the amount of HP healed by Regen each turn.") + self.set_text(2681, "Save time by arranging your item menu during battle animations. Group up items you want to sell.") + self.set_text(2682, "Use Economizer with an Esper like Bahamut or Lore like GrandTrain to quickly break open a seed.") + self.set_text(2691, "Super Balls can help win a few fights if under-leveled. Also, seek undead enemies and kill them with Revivify/Fenix Down.") + self.set_text(2692, "Memorizing good Rages will help break open seeds quicker.") + self.set_text(2694, "Learn which attacks ignore enemy defense. Some examples are Flare, Bum Rush, and SwdTech 7.") + self.set_text(2695, "Use X-Magic on the second turn of Quick and you'll still have full ATB after the X-Magic.") + self.set_text(2696, "Use Tents/Sleeping Bags in multiparty dungeons when not on a save point by moving onto a save point, then immediately switching to a different party and going into the menu.") + self.set_text(2699, "Avoid counter attacks by not attacking with weak party members. Use X/Y to skip their turn if needed.") + self.set_text(2726, "You can save some frames by closing the command menu before a battle ends. I like to use Defend on everyone.") + self.set_text(2746, "The Auction House is always a bit of a gamble, isn't it?") + self.set_text(2747, "In the World of Ruin, you can eventually buy out every item in the Auction House for 90000 GP.Watch out for Imp Robots though - they'll just waste your time.") + self.set_text(2848, "Killing all the Hidonites will cause Hidon to use a strong non-elemental attack. Be careful when using attacks that damage all enemies.") + self.set_text(2849, "Hidon is weak to Fire and Pearl! If you kill Hidon first, any other Hidonites will die as well.") + self.set_text(2850, "22 Coral are required to get past the talking chest in Ebot's Rock.") + self.set_text(2857, "Lores can't be Runic'ed, making them a good pair (e.g. Runic + Pearl Wind).") + self.set_text(2915, "This is Figaro Castle.") + self.set_text(2920, "If Edgar leads your party, all the shops in Figaro Castle are 1/2 off.") + self.set_text(2921, "This discount works in South Figaro World of Ruin too!") + #### end of NPC dialog tip mod + def write(self): self.dialog_data.assign([dialog.data() for dialog in self.dialogs]) for dialog_index, dialog in enumerate(self.dialogs): From 42cb7e7d8f6933c950516452823bad0d8019e879 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:41:46 -0700 Subject: [PATCH 06/60] Adding Auto Life 3 and Auto Dog Block objective results (#36) --- battle/auto_status.py | 34 +++++++++++++++++++++++++--- constants/objectives/results.py | 3 +++ objectives/results/auto_dog_block.py | 14 ++++++++++++ objectives/results/auto_life_3.py | 14 ++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 objectives/results/auto_dog_block.py create mode 100644 objectives/results/auto_life_3.py diff --git a/battle/auto_status.py b/battle/auto_status.py index e374fb20..09108dc7 100644 --- a/battle/auto_status.py +++ b/battle/auto_status.py @@ -9,12 +9,15 @@ class _AutoStatus: def __init__(self): auto_b_status_effects = ["Condemned", "Image", "Mute", "Berserk", "Muddle", "Seizure", "Sleep"] auto_c_status_effects = ["Float", "Regen", "Slow", "Haste", "Shell", "Safe", "Reflect"] + auto_d_status_effects = ["Life 3", "Dog Block"] auto_addresses = [] for status in auto_b_status_effects: auto_addresses.append(self.auto_status(status, status_effects.B)) for status in auto_c_status_effects: auto_addresses.append(self.auto_status(status, status_effects.C)) + for status in auto_d_status_effects: + auto_addresses.append(self.auto_status(status, status_effects.D)) src = [ # original replaced code @@ -40,8 +43,11 @@ def __init__(self): asm.JSL(START_ADDRESS_SNES + auto_status_effects), ) + # Ensure that Life 3 can also be applied at the start of battle + space = Reserve(0x22823, 0x22823, "Only keep Dog Block and Float") + space.write(0xC4) # Original: 0xC0; also keeping Life 3 + def auto_status(self, status_name, status_effects_group): - status_name = status_name.capitalize() auto_status_name = "Auto " + status_name auto_status_name_upper = auto_status_name.upper() @@ -51,8 +57,13 @@ def auto_status(self, status_name, status_effects_group): status_bit = 1 << status_effects_group.name_id[status_name] if status_effects_group == status_effects.B: status_address = 0x3c6c + opcode = asm.ABS_X elif status_effects_group == status_effects.C: status_address = 0x3c6d + opcode = asm.ABS_X + elif status_effects_group == status_effects.D: + status_address = 0x1615 + opcode = asm.ABS_Y src = [] if auto_status_name in objectives.results: @@ -71,9 +82,26 @@ def auto_status(self, status_name, status_effects_group): asm.RTS(), auto_status_name_upper, - asm.LDA(status_address, asm.ABS_X), + ] + + # if the opcode is Y, that means we're accessing the SRAM offset, for which Y is multiples of 37 + if(opcode == asm.ABS_Y): + src += [ + asm.PHY(), # push current Y + asm.XY16(), # 16-bit X & Y + asm.LDY(0x3010, asm.ABS_X) # get the pointer to the character + ] + src += [ + asm.LDA(status_address, opcode), asm.ORA(status_bit, asm.IMM8), - asm.STA(status_address, asm.ABS_X), + asm.STA(status_address, opcode), + ] + if(opcode == asm.ABS_Y): + src += [ + asm.XY8(), # revert back to 8-bit X&Y + asm.PLY(), # pull original Y + ] + src += [ asm.RTS(), ] space = Write(Bank.F0, src, auto_status_name) diff --git a/constants/objectives/results.py b/constants/objectives/results.py index 2606d9b2..f777e916 100644 --- a/constants/objectives/results.py +++ b/constants/objectives/results.py @@ -85,6 +85,9 @@ ], } +category_types["Auto"].append(ResultType(61, "Auto Dog Block", "Auto Dog Block", None)) +category_types["Auto"].append(ResultType(62, "Auto Life 3", "Auto Life 3", None)) + categories = list(category_types.keys()) id_type = {} diff --git a/objectives/results/auto_dog_block.py b/objectives/results/auto_dog_block.py new file mode 100644 index 00000000..5a8c77a5 --- /dev/null +++ b/objectives/results/auto_dog_block.py @@ -0,0 +1,14 @@ +from objectives.results._objective_result import * + +class Field(field_result.Result): + def src(self): + return [] + +class Battle(battle_result.Result): + def src(self): + return [] + +class Result(ObjectiveResult): + NAME = "Auto Dog Block" + def __init__(self): + super().__init__(Field, Battle) diff --git a/objectives/results/auto_life_3.py b/objectives/results/auto_life_3.py new file mode 100644 index 00000000..7b20f62b --- /dev/null +++ b/objectives/results/auto_life_3.py @@ -0,0 +1,14 @@ +from objectives.results._objective_result import * + +class Field(field_result.Result): + def src(self): + return [] + +class Battle(battle_result.Result): + def src(self): + return [] + +class Result(ObjectiveResult): + NAME = "Auto Life 3" + def __init__(self): + super().__init__(Field, Battle) From c2d1f2b10957af89468e8ccc9018b81d52e3dc18 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:42:19 -0700 Subject: [PATCH 07/60] Stray Flash mod (#33) --- battle/animations.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/battle/animations.py b/battle/animations.py index 34e5c834..cafbafb4 100644 --- a/battle/animations.py +++ b/battle/animations.py @@ -6,6 +6,7 @@ class Animations: def __init__(self): self.health_animation_reflect_mod() + self.stray_flash_mod() if args.flashes_remove_most: flash_address_arrays = battle_animation_scripts.BATTLE_ANIMATION_FLASHES.values() @@ -42,7 +43,11 @@ def remove_battle_flashes_mod(self, flash_address_arrays): else: # This is an error, reflecting a difference between the disassembly used to generate BATTLE_ANIMATION_FLASHES and the ROM raise ValueError(f"Battle Animation Script Command at 0x{flash_address:x} (0x{animation_cmd[0]:x}) did not match an expected value.") - + + def stray_flash_mod(self): + # port of https://www.romhacking.net/hacks/6740/ + Write(0x10784b, 0xa7, "Flash tool position") #default: 0xaf + def health_animation_reflect_mod(self): # Ref: https://www.ff6hacking.com/forums/thread-4145.html # Banon's Health command casts Cure 2 on the party with a unique animation. From 8836319e44dbe3b1610e1d7c8c427a8895d5aca4 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:42:50 -0700 Subject: [PATCH 08/60] Adding Osteoclave's bugfix (#29) --- event/airship.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/event/airship.py b/event/airship.py index 89b74097..1d114243 100644 --- a/event/airship.py +++ b/event/airship.py @@ -19,6 +19,7 @@ def mod(self): self.unequip_party_members_npc_mod() self.inside_blackjack() self.return_to_airship() + self.fix_fly_offscreen_bug() def controls_mod(self): fly_wor_fc_cancel_dialog = 1315 @@ -342,3 +343,18 @@ def return_to_airship(self): field.ShowEntity(field_entity.PARTY0), field.RefreshEntities(), ) + + def fix_fly_offscreen_bug(self): + # ref: https://discord.com/channels/666661907628949504/666811452350398493/1025236553875857468 + # fixes the vanilla bug that can occur in which characters can fly offscreen to the bottom-right + # per Osteoclave's research, this all originates with the the H ($0871,Y)) and V ($0873,Y) + # values of (0x4D0, 0x39C) being set after changing party in WoB airship. + # CA/F5B2: C0 If ($1E80($06A) [$1E8D, bit 2] is clear), branch to $CAF5BC + # -> Replace with six [FD] (no-op) + Reserve(0xaf5b2, 0xaf5b7, "skip 06a bit clear check", field.NOP()) + # CA/F5BC: If ($1E80($06A) [$1E8D, bit 2] is set), branch to $CAF5C6 (force Locke and Celes into party) + # -> Replace with six [FD] (no-op) + # -> ref: https://discord.com/channels/666661907628949504/666811452350398493/1025271937750016020 + # CA/F5C2: Call subroutine $CAF601 + # -> Replace with four [FD] (no-op) + Reserve(0xaf5bc, 0xaf5c5, "skip force Locke/Celes into party", field.NOP()) \ No newline at end of file From 7eb5948e3ebb943ab1d6cf8cf67d7efee59fa89a Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:43:14 -0700 Subject: [PATCH 09/60] Adding expensive rods and super balls flags (#27) --- args/shops.py | 38 ++++++++++++++++++++++++++++++++++---- data/item.py | 4 ++++ data/items.py | 17 +++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/args/shops.py b/args/shops.py index 05a87188..012eb7df 100644 --- a/args/shops.py +++ b/args/shops.py @@ -33,12 +33,22 @@ def parse(parser): help = "%(metavar)s shops will contain dried meat") shops.add_argument("-npi", "--no-priceless-items", action = "store_true", help = "Assign values to items which normally sell for 1 gold. Recommended with random inventory") - shops.add_argument("-snbr", "--shops-no-breakable-rods", action = "store_true", + breakable_rods = shops.add_mutually_exclusive_group() + breakable_rods.add_argument("-snbr", "--shops-no-breakable-rods", action = "store_true", help = "Poison, Fire, Ice, Thunder, Gravity, and Pearl Rods not sold in shops") + breakable_rods.add_argument("-sebr", "--shops-expensive-breakable-rods", action = "store_true", + help = "Poison, Fire, Ice, Thunder, Gravity, and Pearl Rods base price increased") + shops.add_argument("-snes", "--shops-no-elemental-shields", action = "store_true", help = "Flame, Ice, and Thunder Shields not sold in shops") - shops.add_argument("-snsb", "--shops-no-super-balls", action = "store_true", + + super_balls = shops.add_mutually_exclusive_group() + super_balls.add_argument("-snsb", "--shops-no-super-balls", action = "store_true", help = "Super Balls not sold in shops") + super_balls.add_argument("-sesb", "--shops-expensive-super-balls", action = "store_true", + help = "Super Balls base price increase") + + shops.add_argument("-snee", "--shops-no-exp-eggs", action = "store_true", help = "Exp. Eggs not sold in shops") shops.add_argument("-snil", "--shops-no-illuminas", action = "store_true", @@ -78,12 +88,20 @@ def flags(args): flags += f" -sdm {args.shop_dried_meat}" if args.no_priceless_items: flags += " -npi" + if args.shops_no_breakable_rods: flags += " -snbr" + elif args.shops_expensive_breakable_rods: + flags += " -sebr" + if args.shops_no_elemental_shields: flags += " -snes" + if args.shops_no_super_balls: flags += " -snsb" + elif args.shops_expensive_super_balls: + flags += " -sesb" + if args.shops_no_exp_eggs: flags += " -snee" if args.shops_no_illuminas: @@ -114,6 +132,18 @@ def options(args): elif args.shop_sell_fraction0: sell_fraction = "0" + breakable_rods = "Available" + if args.shops_no_breakable_rods: + breakable_rods = "No" + elif args.shops_expensive_breakable_rods: + breakable_rods = "Expensive" + + super_balls = "Available" + if args.shops_no_super_balls: + super_balls = "No" + elif args.shops_expensive_super_balls: + super_balls = "Expensive" + result = [("Inventory", inventory)] if args.shop_inventory_shuffle_random: result.append(("Random Percent", f"{args.shop_inventory_shuffle_random_percent}%")) @@ -123,9 +153,9 @@ def options(args): ("Sell Fraction", sell_fraction), ("Dried Meat", args.shop_dried_meat), ("No Priceless Items", args.no_priceless_items), - ("No Breakable Rods", args.shops_no_breakable_rods), + ("Breakable Rods", breakable_rods), ("No Elemental Shields", args.shops_no_elemental_shields), - ("No Super Balls", args.shops_no_super_balls), + ("Super Balls", super_balls), ("No Exp. Eggs", args.shops_no_exp_eggs), ("No Illuminas", args.shops_no_illuminas), ]) diff --git a/data/item.py b/data/item.py index d17db58e..578f4048 100644 --- a/data/item.py +++ b/data/item.py @@ -42,6 +42,10 @@ def remove_learnable_spell(self): self.learnable_spell = 0 self.learnable_spell_rate = 0 + def scale_price(self, factor): + self.price = int(self.price * factor) + self.price = max(min(self.price, 2**16 - 1), 0) + def read(self): name_bytes = self.rom.get_bytes(self.name_addr, self.NAME_LENGTH) self.icon = value_text[name_bytes[0]] diff --git a/data/items.py b/data/items.py index d3e0f5db..42b4c7a2 100644 --- a/data/items.py +++ b/data/items.py @@ -155,6 +155,17 @@ def random_prices_percent(self): value = int(item.price * price_percent) item.price = max(min(value, 2**16 - 1), 0) + def expensive_breakable_rods(self): + self.items[name_id["Poison Rod"]].scale_price(3) + self.items[name_id["Fire Rod"]].scale_price(4) + self.items[name_id["Ice Rod"]].scale_price(4) + self.items[name_id["Thunder Rod"]].scale_price(4) + self.items[name_id["Gravity Rod"]].scale_price(1.2) + self.items[name_id["Pearl Rod"]].scale_price(1.2) + + def expensive_super_balls(self): + self.items[name_id["Super Ball"]].scale_price(2) + def assign_values(self): from data.item_custom_values import custom_values for item in self.items: @@ -198,6 +209,12 @@ def mod(self): if self.args.no_priceless_items: self.assign_values() + if self.args.shops_expensive_breakable_rods: + self.expensive_breakable_rods() + + if self.args.shops_expensive_super_balls: + self.expensive_super_balls() + if self.args.shop_prices_random_value: self.random_prices_value() elif self.args.shop_prices_random_percent: From 32f824a61087e28ee29a45f4b1ba757945b5bb4c Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:43:40 -0700 Subject: [PATCH 10/60] Bugfix: Fix weapon special abilities with Capture and Multi-steals only giving 1 item (#25) * Porting Assassin's Capture bugfix * Adding Bropedio's Multi-Steal fix * Adding multi-steal dialog fix * updating flag doc * Small reduction in C1 usage * Fixing bug with -fc that causes escape from battle (and possibly other actions) to crash --- args/bug_fixes.py | 5 ++ bug_fixes/__init__.py | 2 + bug_fixes/capture.py | 202 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 bug_fixes/capture.py diff --git a/args/bug_fixes.py b/args/bug_fixes.py index a9c8518b..3fcccc28 100644 --- a/args/bug_fixes.py +++ b/args/bug_fixes.py @@ -17,6 +17,8 @@ def parse(parser): help = "Poltergeist and Inferno in Kefka's Tower cannot be skipped") bug_fixes.add_argument("-fedc", "--fix-enemy-damage-counter", action = "store_true", help = "Enemy damage counters only trigger if HP is reduced") + bug_fixes.add_argument("-fc", "--fix-capture", action = "store_true", + help = "Fix Capture such that Weapon Special Effects are applied and Multi-Steals work") def process(args): pass @@ -38,6 +40,8 @@ def flags(args): flags += " -fbs" if args.fix_enemy_damage_counter: flags += " -fedc" + if args.fix_capture: + flags += " -fc" return flags @@ -50,6 +54,7 @@ def options(args): ("Jump", args.fix_jump), ("Boss Skip", args.fix_boss_skip), ("Enemy Damage Counter", args.fix_enemy_damage_counter), + ("Capture", args.fix_capture), ] def menu(args): diff --git a/bug_fixes/__init__.py b/bug_fixes/__init__.py index fc1eb5fe..98f63b8e 100644 --- a/bug_fixes/__init__.py +++ b/bug_fixes/__init__.py @@ -4,6 +4,7 @@ from bug_fixes.jump import Jump from bug_fixes.retort import Retort from bug_fixes.enemy_damage_counter import EnemyDamageCounter +from bug_fixes.capture import Capture __all__ = ["BugFixes"] class BugFixes: @@ -14,3 +15,4 @@ def __init__(self): self.jump = Jump() self.retort = Retort() self.enemy_damage_counter = EnemyDamageCounter() + self.capture = Capture() diff --git a/bug_fixes/capture.py b/bug_fixes/capture.py new file mode 100644 index 00000000..603308bb --- /dev/null +++ b/bug_fixes/capture.py @@ -0,0 +1,202 @@ +from memory.space import Bank, Reserve, Write +import instruction.asm as asm +import args + +class Capture: + def __init__(self): + if args.fix_capture: + self.weapon_special_mod() + self.multisteal_mod() + + def multisteal_mod(self): + # Fixes issue with multiple steals caused by Genji Glove and/or Offering Capture. + # Issues resolved: + # 1) the stolen items are not all added to your inventory (only the last successful steal is actually added) + # 2) the message display window does not clear in between steal animations, + # meaning that the first item name is the one that is displayed for all subsequent successful steals. + # Based in part on https://www.angelfire.com/al2/imzogelmo/patches.html#patches's Multi-Steal Fix + # and Bropedio's Multi-Steal fix (https://www.ff6hacking.com/forums/thread-4124-post-40232.html#pid40232) + + # Custom variable locations + STOLEN_ITEM_ARRAY_START = 0x2f35 + STOLEN_ITEM_ARRAY_INDEX = 0x2f3b + + # Make the "Steal " text go through the array + src = [ + asm.REP(0x20), #Set A to 16 bits + asm.LDA(0x76, asm.DIR_16), #Load first two bytes of current animation entry + asm.CMP(0x0302, asm.IMM16), #Check for animation opcode 2 (upper text box) and text message 3 (Steal ) + asm.SEP(0x20), #Set A back to 8 bits + asm.BEQ("GET_STEAL_ITEM"), #If the above condition was true, branch + asm.LDA(0x2f35, asm.ABS), #Else, perform the displaced command (Note: it's unclear if this will ever get called) + asm.RTS(), # and return + "GET_STEAL_ITEM", + asm.SEP(0x10), #Set X to 8 bits + asm.LDX(STOLEN_ITEM_ARRAY_INDEX, asm.ABS), # Load the index to the stolen item array + asm.LDA(STOLEN_ITEM_ARRAY_START, asm.ABS_X), # Put the item from index into A + asm.REP(0x10), # Set X back to 16 bits + asm.INC(STOLEN_ITEM_ARRAY_INDEX, asm.ABS), # Increment the array index + asm.RTS() + ] + space = Write(Bank.C1, src, "Multisteal Fix: steal text") + c1_steal_print_addr = space.start_address + + # Call our new subroutine + space = Reserve(0x15f06, 0x15f08, "Multisteal Fix: call new C1 subroutine to load stolen item into A", asm.NOP()) + space.write( + asm.JSR(c1_steal_print_addr, asm.ABS) + ) + + #These two subroutines reset the stolen item index + src = [ + asm.JSR(0x1429, asm.ABS), # displaced instruction + asm.STZ(STOLEN_ITEM_ARRAY_INDEX, asm.ABS), # zero the index + asm.RTS() + ] + space = Write(Bank.C2, src, "Multisteal fix: reset stolen item array index routine") + stolen_item_index_reset = space.start_address + + space = Reserve(0x2140f, 0x21411, "Multisteal Fix: reset stolen item index") + space.write( + asm.JSR(stolen_item_index_reset, asm.ABS) + ) + + src = [ + asm.LDA(0xb5, asm.DIR), # displaced instruction + asm.ASL(), # displaced instruction + asm.STZ(STOLEN_ITEM_ARRAY_INDEX, asm.ABS), # zero the index + asm.RTS() + ] + space = Write(Bank.C2, src, "Multisteal fix: reset stolen item array index routine") + stolen_item_index_reset = space.start_address + + space = Reserve(0x213fa, 0x213fc, "Multisteal Fix: reset stolen item index") + space.write( + asm.JSR(stolen_item_index_reset, asm.ABS) + ) + + + + # New subroutine for storing acquired item + src = [ + asm.TSB(0x3a8c, asm.ABS), # set character's reserve item to be added + asm.LDA(0x32f4, asm.ABS_X), # load current reserve item + asm.PHA(), # save reserve item on stack + asm.XBA(), # get new item in A + asm.STA(0x32f4, asm.ABS_X), # store new item in reserve byte + # Store item in array for textbox + asm.PHX(), # save X + asm.LDX(STOLEN_ITEM_ARRAY_INDEX, asm.ABS), # Load the index for the array + asm.STA(STOLEN_ITEM_ARRAY_START, asm.ABS_X), # Store the item number into the array + asm.INC(STOLEN_ITEM_ARRAY_INDEX, asm.ABS), # Increment the index for highest variable stored + asm.PLX(), # restore X + # Done storing item in array for textbox + asm.PHX(), # save X + asm.JSR(0x62C7, asm.ABS), # add reserve to obtained-items buffer + asm.PLX(), # restore X + asm.PLA(), # restore previous reserve item + asm.STA(0x32f4, asm.ABS_X), # store in reserve item byte again + asm.RTS() + ] + space = Write(Bank.C2, src, "Multisteal Fix: store acquired item") + store_acquired_addr = space.start_address + + # Update steal formula where it stores the acquired item + space = Reserve(0x239e9, 0x239f4, "Multisteal Fix: call new subroutine", asm.NOP()) + space.write( + asm.XBA(), # store acquired item in B + asm.LDA(0x3018, asm.ABS_X), # character's unique bit + asm.JSR(store_acquired_addr, asm.ABS), # save new item to buffer + ) + + # Fix Item Return Buffer + space = Reserve(0x112d5, 0x112d7, "Multisteal Fix: avoid item return buffer overrun") + space.write( + asm.CPX(0x50, asm.IMM16) # the game only clears #$40 for item buffer, but it expects #$50 + ) + + def weapon_special_mod(self): + # http://assassin17.brinkster.net/patches.htm#anchor18 + NEW_SPECIAL_EFFECT_VAR = 0x2f3d + + ##### + # New subroutines + ##### + # Null the dog block [displaced Square code], and clear my custom special effect byte. + src = [ + asm.STA(0x3a83, asm.ABS), #Null Dog block + asm.STZ(NEW_SPECIAL_EFFECT_VAR, asm.ABS), #Clear new special effect variable + asm.RTS() + ] + space = Write(Bank.C2, src, "Capture Fix: null dog block") + null_dog_block_addr = space.start_address + + #Call Square's per-target special effect function as normal. Then call it again with + # a secondary variable so the Capture command can steal, unless the first function call + # already handled stealing. + src = [ + asm.PHP(), + asm.A8(), # Set 8 bit accumulator + asm.LDA(0x11a9, asm.ABS), # Load A with the current attack special effect -- based on table at c2/3dcd + asm.PHA(), + asm.JSR(0x387e, asm.ABS), # Call special effect function once for value in 11a9 + asm.LDA(NEW_SPECIAL_EFFECT_VAR, asm.ABS), + asm.CMP(0x1, asm.S), # does the custom match the original? + asm.BEQ("SKIP_IT"), # branch if so + asm.STA(0x11a9, asm.ABS), + asm.JSR(0x387e, asm.ABS), # Call special effect function again for our special effect var + "SKIP_IT", + asm.PLA(), + asm.STA(0x11a9, asm.ABS), + asm.PLP(), + asm.RTS() + ] + space = Write(Bank.C2, src, "Capture Fix: new special effect function") + new_special_effect_addr = space.start_address + + ##### + # Modify data in "Character Executes One Hit" function to use new subroutines and variable + ##### + space = Reserve(0x23185, 0x23187, "Capture Fix: call new null dog block subroutine")#, asm.NOP()) + space.write( + asm.JSR(null_dog_block_addr, asm.ABS) #(Null Dog block, then clear my custom special effect + # variable for Capture) + ) + space = Reserve(0x231b0, 0x231b2, "Capture Fix: Save Special Effect to new byte") + space.write( + asm.STA(NEW_SPECIAL_EFFECT_VAR, asm.ABS) #save special effect in our fancy new byte, so we won't + # overwrite the weapon's special effect. + ) + space = Reserve(0x2345c, 0x2345e, "Capture Fix: call new special effect function") + space.write( + asm.JSR(new_special_effect_addr, asm.ABS) #Special effect code for target .. customized + ) + + #### + # Dice Effect + #### + # FF6WC note: Rather than transfering Assassin's extensive changes made to the Dice Effect subroutine (C2/4168 - C2/41E5), + # which were seemingly made just to save space, I'm just transfering the main change as a subroutine: + # replacing the Capture animation with Dice with that of Fight starting at C2/41D9 + src = [ + asm.A8(), # Set 8 bit accumulator + asm.LDA(0xb5, asm.DIR), # Load Command Index + asm.CMP(0x00, asm.IMM8), # Maybe unnecessary? Compare Command with Fight + asm.BEQ("SET_ANIMATION"), # Branch if Fight command + asm.CMP(0x06, asm.IMM8), # Compare Command with Capture + asm.BNE("NO_CHANGE"), # Branch if not Capture command + "SET_ANIMATION", + asm.LDA(0x26, asm.IMM8), + asm.STA(0xb5, asm.DIR), # Store a dice toss animation + "NO_CHANGE", + asm.RTS() + ] + space = Write(Bank.C2, src, "Capture Fix: new dice toss animation") + dice_toss_animation_addr = space.start_address + + space = Reserve(0x241d9, 0x241e5, "Capture Fix: replace dice toss animation", asm.NOP()) + space.write( + asm.JSR(dice_toss_animation_addr, asm.ABS), #Jump to our new routine + asm.RTS() #Done + ) + From 45d3c4dbb5ab510bc5d9217c7a2a24f868d0feaf Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:44:05 -0700 Subject: [PATCH 11/60] Feature: Flashes: Moving flashes to Graphics; Slowing scrolling background during Air Force fight and Baren falls; add minimap high contrast option (#23) * Removing scrolling background during Air Force fight * switching to slowing the bg scroll. Also adding baren falls * Removing baren falls background vibration * Fixing typo in baren falls background palette change for frm * Changing events flashes from no-ops to flash-none to avoid any CPU cycle changes * Moving Remove Flashes flags from misc to graphics, so that they don't affect the seed * Adding -wmhc option to improve visibility of minimap * Making location indicator for wmhc stand out even more * Adding more event flashes to frw * Changing wmhc based on color feedback * Replacing boss death flash removal with flash of monster sprite * switching to Osteoclave's pixel remaster inspired minimap --- args/graphics.py | 42 +++++++++++++++++++++++ args/misc.py | 18 ---------- battle/animations.py | 49 ++++++++++++++++++++++----- data/maps.py | 3 ++ data/world_map.py | 56 +++++++++++++++++++++++++++++++ event/baren_falls.py | 16 +++++++++ event/collapsing_house.py | 12 +++---- event/doma_wor.py | 12 +++---- event/duncan_house_wor.py | 2 +- event/floating_continent.py | 7 ++++ event/magitek_factory.py | 4 ++- event/owzer_mansion.py | 2 +- instruction/field/instructions.py | 3 +- 13 files changed, 183 insertions(+), 43 deletions(-) create mode 100644 data/world_map.py diff --git a/args/graphics.py b/args/graphics.py index 91425980..36d56262 100644 --- a/args/graphics.py +++ b/args/graphics.py @@ -10,6 +10,15 @@ def parse(parser): graphics.add_argument("-cspr", "--character-sprites", type = str, help = "Character sprite indices") graphics.add_argument("-cspp", "--character-sprite-palettes", type = str, help = "Character sprite palette indices") + remove_flashes = graphics.add_mutually_exclusive_group() + remove_flashes.add_argument("-frw", "--flashes-remove-worst", action = "store_true", + help = "Removes only the worst flashes from animations. Ex: Learning Bum Rush, Bum Rush, Quadra Slam/Slice, Flash, etc.") + remove_flashes.add_argument("-frm", "--flashes-remove-most", action = "store_true", + help = "Removes most flashes from animations. Includes Kefka Death.") + + graphics.add_argument("-wmhc", "--world-minimap-high-contrast", action = "store_true", + help = "World Minimap made Opaque with Minimap icon changed to higher contrast to improve visibility.") + def process(args): import graphics.palettes.palettes as palettes import graphics.portraits.portraits as portraits @@ -98,6 +107,12 @@ def flags(args): if args.character_sprite_palettes: flags += " -cspp " + args.character_sprite_palettes + if args.flashes_remove_worst: + flags += " -frw" + if args.flashes_remove_most: + flags += " -frm" + if args.world_minimap_high_contrast: + flags += " -wmhc" return flags def _truncated_name(name): @@ -148,6 +163,30 @@ def _character_customization_log(args): return log +def _other_options_log(args): + from log import format_option + log = ["Other Graphics"] + + remove_flashes = "Original" + if args.flashes_remove_worst: + remove_flashes = "Worst" + elif args.flashes_remove_most: + remove_flashes = "Most" + + world_minimap = "Original" + if args.world_minimap_high_contrast: + world_minimap = "High Contrast" + + entries = [ + ("Remove Flashes", remove_flashes), + ("World Minimap", world_minimap), + ] + + for entry in entries: + log.append(format_option(*entry)) + + return log + def log(args): lcolumn = [""] lcolumn.extend(_sprite_palettes_log(args)) @@ -155,6 +194,9 @@ def log(args): lcolumn.append("") lcolumn.extend(_other_portraits_sprites_log(args)) + lcolumn.append("") + lcolumn.extend(_other_options_log(args)) + rcolumn = [""] rcolumn.extend(_character_customization_log(args)) diff --git a/args/misc.py b/args/misc.py index 7d55fc10..2f216557 100644 --- a/args/misc.py +++ b/args/misc.py @@ -43,12 +43,6 @@ def parse(parser): help = "Remove NPC") parser.y_npc_group = y_npc - remove_flashes = misc.add_mutually_exclusive_group() - remove_flashes.add_argument("-frw", "--flashes-remove-worst", action = "store_true", - help = "Removes only the worst flashes from animations. Ex: Learning Bum Rush, Bum Rush, Quadra Slam/Slice, Flash, etc.") - remove_flashes.add_argument("-frm", "--flashes-remove-most", action = "store_true", - help = "Removes most flashes from animations. Includes Kefka Death.") - def process(args): args.y_npc = False # are any y_npc flags enabled? @@ -98,11 +92,6 @@ def flags(args): elif args.y_npc_remove: flags += " -yremove" - if args.flashes_remove_worst: - flags += " -frw" - if args.flashes_remove_most: - flags += " -frm" - return flags def options(args): @@ -134,12 +123,6 @@ def options(args): elif args.y_npc_remove: y_npc = "Remove" - remove_flashes = "Original" - if args.flashes_remove_worst: - remove_flashes = "Worst" - elif args.flashes_remove_most: - remove_flashes = "Most" - return [ ("Auto Sprint", args.auto_sprint), ("Original Name Display", args.original_name_display), @@ -148,7 +131,6 @@ def options(args): ("Scan All", args.scan_all), ("Event Timers", event_timers), ("Y NPC", y_npc), - ("Remove Flashes", remove_flashes) ] def menu(args): diff --git a/battle/animations.py b/battle/animations.py index cafbafb4..d6de4e52 100644 --- a/battle/animations.py +++ b/battle/animations.py @@ -8,23 +8,54 @@ def __init__(self): self.health_animation_reflect_mod() self.stray_flash_mod() + # Flash removal + replace_flash_animation = [] # The background flash to replace with monster flashes + remove_flash_animation = [] # The background flash addresses to remove + if args.flashes_remove_most: - flash_address_arrays = battle_animation_scripts.BATTLE_ANIMATION_FLASHES.values() - self.remove_battle_flashes_mod(flash_address_arrays) + # Replace Boss Death and Final Kefka + replace_flash_animation.extend(["Boss Death", "Final KEFKA Death"]) + # And remove the rest + remove_flash_animation.extend(battle_animation_scripts.BATTLE_ANIMATION_FLASHES.keys()) + # Also removing critical flash self.remove_critical_flash() - - if args.flashes_remove_worst: - flash_address_arrays = [] - animation_names = ["Boss Death", "Ice 3", "Fire 3", "Bolt 3", "Schiller", "R.Polarity", "X-Zone", + elif args.flashes_remove_worst: + replace_flash_animation.extend(["Boss Death"]) + remove_flash_animation.extend(["Ice 3", "Fire 3", "Bolt 3", "Schiller", "R.Polarity", "X-Zone", "Muddle", "Dispel", "Shock", "Bum Rush", "Quadra Slam", "Slash", "Flash", - "Step Mine", "Rippler", "WallChange", "Ultima", "ForceField"] - for name in animation_names: - flash_address_arrays.append(battle_animation_scripts.BATTLE_ANIMATION_FLASHES[name]) + "Step Mine", "Rippler", "WallChange", "Ultima", "ForceField"]) + + # Replace any specified above + flash_address_arrays = [battle_animation_scripts.BATTLE_ANIMATION_FLASHES[name] for name in replace_flash_animation] + if flash_address_arrays: + self.replace_bg_flash_with_monster_flash_mod(flash_address_arrays) + + # Remove any remainder specified above + flash_address_arrays = [battle_animation_scripts.BATTLE_ANIMATION_FLASHES[name] for name in remove_flash_animation if name not in replace_flash_animation] + if flash_address_arrays: self.remove_battle_flashes_mod(flash_address_arrays) def remove_critical_flash(self): space = Reserve(0x23410, 0x23413, "Critical hit screen flash", asm.NOP()) + def replace_bg_flash_with_monster_flash_mod(self, flash_address_arrays): + REPLACEMENTS = { + 0xAF: 0xB9, # Set background palette color subtraction (absolute) -> Set monster palettes color subtraction (absolute) + 0xB0: 0xBA, # Set background palette color addition (absolute) -> Set monster palettes color addition (absolute) + 0xB5: 0xBB, # Add color to background palette (relative) -> Add color to monster palettes (relative) + 0xB6: 0xBC, # Subtract color from background palette (relative) -> Subtract color from monster palettes (relative) + } + for flash_addresses in flash_address_arrays: + # For each address in its array + for flash_address in flash_addresses: + # Read the current animation command at the address + animation_cmd = Read(flash_address, flash_address+1) + if(animation_cmd[0] in REPLACEMENTS.keys()): + Write(flash_address, REPLACEMENTS[animation_cmd[0]], "BG flash to monster flash") + else: + # This is an error, reflecting a difference between the disassembly used to generate BATTLE_ANIMATION_FLASHES and the ROM + raise ValueError(f"Battle Animation Script Command at 0x{flash_address:x} (0x{animation_cmd[0]:x}) did not match an expected value.") + def remove_battle_flashes_mod(self, flash_address_arrays): ABSOLUTE_CHANGES = [0xb0, 0xaf] RELATIVE_CHANGES = [0xb5, 0xb6] diff --git a/data/maps.py b/data/maps.py index 8c63f69f..064797a1 100644 --- a/data/maps.py +++ b/data/maps.py @@ -12,6 +12,7 @@ from data.map_exit import ShortMapExit, LongMapExit import data.world_map_event_modifications as world_map_event_modifications +from data.world_map import WorldMap class Maps(): MAP_COUNT = 416 @@ -33,6 +34,7 @@ def __init__(self, rom, args, items): self.events = events.MapEvents(rom) self.exits = exits.MapExits(rom) self.world_map_event_modifications = world_map_event_modifications.WorldMapEventModifications(rom) + self.world_map = WorldMap(rom, args) self.read() def read(self): @@ -189,6 +191,7 @@ def _fix_imperial_camp_boxes(self): def mod(self, characters): self.npcs.mod(characters) self.chests.mod() + self.world_map.mod() self._fix_imperial_camp_boxes() diff --git a/data/world_map.py b/data/world_map.py new file mode 100644 index 00000000..2107ca53 --- /dev/null +++ b/data/world_map.py @@ -0,0 +1,56 @@ +from memory.space import Reserve + +class WorldMap: + def __init__(self, rom, args): + self.rom = rom + self.args = args + + def world_minimap_high_contrast_mod(self): + # Thanks to Osteoclave for identifying these changes + + # Increases the sprite priority for the minimap sprites + # So it gets drawn on top of the overworld instead of being translucent + #ee4146=1b + space = Reserve(0x2e4146, 0x2e4146, "minimap sprite priority") + space.write(0x1b) # default: 0x0b + + # Colors bytes: gggrrrrr, xbbbbbgg + # High contrast location indicator on minimaps + # d2eeb8=ff + d2eeb9=7f + # d2efb8=ff + d2efb9=7f + location_indicator_addr = [0x12eeb8, # WoB default: 1100 + 0x12efb8] # WoR default: 1100 + for loc_addr in location_indicator_addr: + space = Reserve(loc_addr, loc_addr+1, "high contrast minimap indicator") + space.write(0xff, 0x7f) + + # d2eeba=ff + d2eebb=7f + # d2efba=ff + d2efbb=7f + location_indicator_addr = [0x12eeba, # WoB default: 1f00 + 0x12efba] # WoR default: 1f00 + for loc_addr in location_indicator_addr: + space = Reserve(loc_addr, loc_addr+1, "high contrast minimap indicator") + space.write(0xff, 0x7f) + + # Additional minimap palette mods + # default: 84 10 e7 1c 4a 29 10 42 ff 7f + # WoB: d2eea2=00 + d2eea3=14 + d2eea4=82 + d2eea5=28 + d2eea6=e4 + d2eea7=38 + d2eea8=67 + d2eea9=51 + d2eeaa=9c + d2eeab=02 + # WoR: d2efa2=00 + d2efa3=14 + d2efa4=82 + d2efa5=28 + d2efa6=e4 + d2efa7=38 + d2efa8=67 + d2efa9=51 + d2efaa=9c + d2efab=02 + minimap_palette_bytes = [0x00, 0x14, 0x82, 0x28, 0xe4, 0x38, 0x67, 0x51, 0x9c, 0x02] + minimap_palette_addr = [0x12eea2, # WoB + 0x12efa2] # WoR + for addr in minimap_palette_addr: + space = Reserve(addr, addr+len(minimap_palette_bytes)-1, "minimap palette") + space.write(minimap_palette_bytes) + + # This changes the color of the Floating Continent (pre-floating) on WoB + # default: e7 1c 4a 29 10 42 + # d2eeac=82 + d2eead=28 + d2eeae=e4 + d2eeaf=38 + d2eeb0=67 + d2eeb1=51 + addr = 0x12eeac + minimap_palette_bytes = [0x82, 0x28, 0xe4, 0x38, 0x67, 0x51] + space = Reserve(addr, addr+len(minimap_palette_bytes)-1, "floating continent palette") + space.write(minimap_palette_bytes) + + def mod(self): + if self.args.world_minimap_high_contrast: + self.world_minimap_high_contrast_mod() \ No newline at end of file diff --git a/event/baren_falls.py b/event/baren_falls.py index b479b802..ebafea06 100644 --- a/event/baren_falls.py +++ b/event/baren_falls.py @@ -22,6 +22,9 @@ def mod(self): self.after_battle_mod() self.already_complete_mod() + if self.args.flashes_remove_most: + self.background_scrolling_mod() + if self.reward.type == RewardType.CHARACTER: self.character_mod(self.reward.id) elif self.reward.type == RewardType.ESPER: @@ -160,3 +163,16 @@ def item_mod(self, item): field.AddItem(item), field.Dialog(self.items.get_receive_dialog(item)), ]) + + def background_scrolling_mod(self): + # Slow the scrolling background by modifying the ADC command. + space = Reserve(0x2b1f7, 0x2b1f9, "waterfall background movement") + space.write( + asm.ADC(0x0001, asm.IMM16) #default: 0x0006 + ) + + # Eliminate the palette swaps without reducing any cpu cycles by just writing back the value from the previous LDA + space = Reserve(0x2b20b, 0x2b20d, "waterfall palette change") + space.write( + asm.STA(0xEC71, asm.ABS_X) + ) diff --git a/event/collapsing_house.py b/event/collapsing_house.py index 75cf0068..fbb22ecf 100644 --- a/event/collapsing_house.py +++ b/event/collapsing_house.py @@ -22,7 +22,7 @@ def mod(self): if self.args.character_gating: self.add_gating_condition() - if self.args.flashes_remove_most: + if self.args.flashes_remove_most or self.args.flashes_remove_worst: self.flash_mod() if self.reward.type == RewardType.CHARACTER: @@ -65,11 +65,11 @@ def dialogs_mod(self): space = Reserve(0xc5a79, 0xc5a7c, "collapsing house smash kefka dialog", field.NOP()) def flash_mod(self): - space = Reserve(0xc5848, 0xc5849, "collapsing house initial flash 1", field.NOP()) - space = Reserve(0xc5863, 0xc5864, "collapsing house initial flash 2", field.NOP()) - space = Reserve(0xc5868, 0xc5869, "collapsing house initial flash 3", field.NOP()) - space = Reserve(0xc59ec, 0xc59ed, "collapsing house final flash 1", field.NOP()) - space = Reserve(0xc59f5, 0xc59f6, "collapsing house final flash 2", field.NOP()) + space = Reserve(0xc5848, 0xc5849, "collapsing house initial flash 1", field.FlashScreen(field.Flash.NONE)) + space = Reserve(0xc5863, 0xc5864, "collapsing house initial flash 2", field.FlashScreen(field.Flash.NONE)) + space = Reserve(0xc5868, 0xc5869, "collapsing house initial flash 3", field.FlashScreen(field.Flash.NONE)) + space = Reserve(0xc59ec, 0xc59ed, "collapsing house final flash 1", field.FlashScreen(field.Flash.NONE)) + space = Reserve(0xc59f5, 0xc59f6, "collapsing house final flash 2", field.FlashScreen(field.Flash.NONE)) def add_gating_condition(self): start_event = 0xc5844 diff --git a/event/doma_wor.py b/event/doma_wor.py index ac17ea41..c38a9baf 100644 --- a/event/doma_wor.py +++ b/event/doma_wor.py @@ -165,10 +165,10 @@ def doma_mod(self): field.Branch(space.end_address + 1), # skip nops ) - if(self.args.flashes_remove_most): - space = Reserve(0xb9952, 0xb9953, "doma wor sword appears flash 1", field.NOP()) - space = Reserve(0xb9975, 0xb9976, "doma wor sword appears flashes", field.NOP()) - space = Reserve(0xb99a9, 0xb99aa, "doma wor sword appears flash 3", field.NOP()) + if(self.args.flashes_remove_most or self.args.flashes_remove_worst): + space = Reserve(0xb9952, 0xb9953, "doma wor sword appears flash 1", field.FlashScreen(field.Flash.NONE)) + space = Reserve(0xb9975, 0xb9976, "doma wor sword appears flashes", field.FlashScreen(field.Flash.NONE)) + space = Reserve(0xb99a9, 0xb99aa, "doma wor sword appears flash 3", field.FlashScreen(field.Flash.NONE)) space = Reserve(0xb997d, 0xb9984, "doma wor cyan kneeling", field.NOP()) space = Reserve(0xb99df, 0xb99e0, "doma wor pause before loading room slept in", field.NOP()) @@ -262,8 +262,8 @@ def cyan_esper_mod(self, esper): ) def finish_dream_awaken_mod(self): - if(self.args.flashes_remove_most): - space = Reserve(0xb9a47, 0xb9a48, "doma wor peak swordmanship flash", field.NOP()) + if(self.args.flashes_remove_most or self.args.flashes_remove_worst): + space = Reserve(0xb9a47, 0xb9a48, "doma wor peak swordmanship flash", field.FlashScreen(field.Flash.NONE)) src = [ field.FinishCheck(), diff --git a/event/duncan_house_wor.py b/event/duncan_house_wor.py index f925dcbe..4bd3ddac 100644 --- a/event/duncan_house_wor.py +++ b/event/duncan_house_wor.py @@ -60,4 +60,4 @@ def bum_rush_learn_mod(self): def bum_rush_flash_mod(self): flash_addresses = [0xc0d12, 0xc0d5f, 0xc0d7f, 0xc0d9f, 0xc0df0, 0xc0e09, 0xc0e22, 0xc0e3b, 0xc0e65, 0xc0e74] for address in flash_addresses: - space = Reserve(address, address + 1, "duncan house wor bum rush flash", field.NOP()) \ No newline at end of file + space = Reserve(address, address + 1, "duncan house wor bum rush flash", field.FlashScreen(field.Flash.NONE)) \ No newline at end of file diff --git a/event/floating_continent.py b/event/floating_continent.py index 927107cb..2782a099 100644 --- a/event/floating_continent.py +++ b/event/floating_continent.py @@ -143,6 +143,13 @@ def ultros_chupon_battle_mod(self): ) def air_force_battle_mod(self): + if self.args.flashes_remove_most or self.args.flashes_remove_worst: + # Slow the scrolling background by modifying the ADC command. + space = Reserve(0x2b1b1, 0x2b1b3, "falling through clouds background movement") + space.write( + asm.ADC(0x0001, asm.IMM16) #default: 0x0006 + ) + boss_pack_id = self.get_boss("Air Force") battle_background = 7 # sky, falling diff --git a/event/magitek_factory.py b/event/magitek_factory.py index edaca95a..c23a9b1e 100644 --- a/event/magitek_factory.py +++ b/event/magitek_factory.py @@ -105,7 +105,9 @@ def ifrit_shiva_mod(self, esper_item_instructions): space = Reserve(0xc79a4, 0xc79cf, "magitek factory ifrit/shiva magicite", field.NOP()) src = [] - if not self.args.flashes_remove_most: + if self.args.flashes_remove_most or self.args.flashes_remove_worst: + src.append(field.FlashScreen(field.Flash.NONE)) + else: src.append(field.FlashScreen(field.Flash.WHITE)) src.append([ diff --git a/event/owzer_mansion.py b/event/owzer_mansion.py index c0e53dec..8fd17a90 100644 --- a/event/owzer_mansion.py +++ b/event/owzer_mansion.py @@ -42,7 +42,7 @@ def mod(self): self.log_reward(self.reward) def flash_mod(self): - space = Reserve(0xb4d10, 0xb4d11, "owzer mansion flash", field.NOP()) + space = Reserve(0xb4d10, 0xb4d11, "owzer mansion flash", field.FlashScreen(field.Flash.NONE)) def dialog_mod(self): space = Reserve(0xb4d0d, 0xb4d0f, "owzer mansion help that painting!!", field.NOP()) diff --git a/instruction/field/instructions.py b/instruction/field/instructions.py index 25351566..c9967eeb 100644 --- a/instruction/field/instructions.py +++ b/instruction/field/instructions.py @@ -406,6 +406,7 @@ def TintSpritePalette(tint, palette, invert = False): return TintSpritePalette(tint, palette * 0x10, palette * 0x10 + 0x0f, invert) class Flash(IntFlag): + NONE = 0x00 RED = 0x20 GREEN = 0x40 BLUE = 0x80 @@ -413,7 +414,7 @@ class Flash(IntFlag): WHITE = RED | GREEN | BLUE class FlashScreen(_Instruction): def __init__(self, color): - if not (color & Flash.RED) and not (color & Flash.GREEN) and not (color & Flash.BLUE): + if color != Flash.NONE and (not (color & Flash.RED) and not (color & Flash.GREEN) and not (color & Flash.BLUE)): raise ValueError(f"FlashScreen: invalid color {hex(color)}") super().__init__(0x55, color) From cb16371334a176d52d4b8db3a5cb9d6c7ad10493 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:45:03 -0700 Subject: [PATCH 12/60] Feature: Adding flags to improve Sketch/Control (#19) * Adding flag to make Sketch/Control 100% effective against valid targets * fixing minor typo * Refactoring data/sketch_control.py into separate files. Adding better abilities for Sketch & Control. Making Sketch use caster's stats. * Minor change to option logging * Modification to sketch custom command; split into multiple flags; removing control custom commands * Expanding sia to also include control. Separating Controls from Coliseum monster table. * Minor arg desc change * Control uses controller's stats, fix Control bug with Dance moves * Removing ultima from sketch magimaster --- args/arguments.py | 2 +- args/log.py | 2 +- args/sketch_control.py | 43 +++++++++ data/bosses.py | 3 + data/control.py | 20 +++++ data/controls.py | 154 +++++++++++++++++++++++++++++++++ data/data.py | 11 +++ data/enemies.py | 9 +- data/enemy.py | 10 ++- data/sketch.py | 18 ++++ data/sketch_custom_commands.py | 54 ++++++++++++ data/sketches.py | 102 ++++++++++++++++++++++ 12 files changed, 424 insertions(+), 4 deletions(-) create mode 100644 args/sketch_control.py create mode 100644 data/control.py create mode 100644 data/controls.py create mode 100644 data/sketch.py create mode 100644 data/sketch_custom_commands.py create mode 100644 data/sketches.py diff --git a/args/arguments.py b/args/arguments.py index 155e985a..8766e49a 100644 --- a/args/arguments.py +++ b/args/arguments.py @@ -4,7 +4,7 @@ def __init__(self): self.groups = [ "settings", "objectives", - "starting_party", "characters", "swdtechs", "blitzes", "lores", "rages", "dances", "steal", "commands", + "starting_party", "characters", "swdtechs", "blitzes", "lores", "rages", "dances", "steal", "sketch_control", "commands", "xpmpgp", "scaling", "bosses", "encounters", "boss_ai", "espers", "natural_magic", "starting_gold_items", "items", "shops", "chests", diff --git a/args/log.py b/args/log.py index 0160ae56..381c5466 100644 --- a/args/log.py +++ b/args/log.py @@ -17,7 +17,7 @@ def _log_tab(tab_name, left_groups, right_groups): def log(): _log_tab("Game", ["settings"], []) args.group_modules["objectives"].log(args) - _log_tab("Party", ["starting_party", "swdtechs", "blitzes", "lores", "rages", "dances", "steal"], ["characters", "commands"]) + _log_tab("Party", ["starting_party", "swdtechs", "blitzes", "lores", "rages", "dances", "steal", "sketch_control"], ["characters", "commands"]) _log_tab("Battle", ["xpmpgp", "bosses", "boss_ai"], ["scaling", "encounters"]) _log_tab("Magic", ["espers"], ["natural_magic"]) _log_tab("Items", ["starting_gold_items", "items"], ["shops", "chests"]) diff --git a/args/sketch_control.py b/args/sketch_control.py new file mode 100644 index 00000000..401ba584 --- /dev/null +++ b/args/sketch_control.py @@ -0,0 +1,43 @@ +def name(): + return "Sketch/Control" + +def parse(parser): + sketch_control = parser.add_argument_group("Sketch/Control") + + sketch_control.add_argument("-scis", "--sketch-control-improved-stats", action = "store_true", + help = "Sketch & Control 100%% accurate and use Sketcher/Controller's stats") + sketch_control.add_argument("-scia", "--sketch-control-improved-abilities", action = "store_true", + help = "Improves Sketch & Control abilities. Removes Battle from Sketch. Adds Rage as a Sketch/Control possibility for most monsters. Gives Sketch abilities to most bosses.") + +def process(args): + pass + +def flags(args): + flags = "" + + if args.sketch_control_improved_stats: + flags += " -scis" + if args.sketch_control_improved_abilities: + flags += " -scia" + + return flags + +def options(args): + + return [ + ("Improved Stats", args.sketch_control_improved_stats), + ("Improved Abilities", args.sketch_control_improved_abilities), + ] + +def menu(args): + return (name(), options(args)) + +def log(args): + from log import format_option + log = [name()] + + entries = options(args) + for entry in entries: + log.append(format_option(*entry)) + + return log diff --git a/data/bosses.py b/data/bosses.py index 612fd968..1fc0be80 100644 --- a/data/bosses.py +++ b/data/bosses.py @@ -142,6 +142,9 @@ 364 : "Phunbaba 3", 365 : "Phunbaba 4", 256 : "Whelk", + 257 : "Presenter", + 361 : "Naughty", + 292 : "KatanaSoul", 308 : "Head", 259 : "Vargas", 333 : "Ipooh", diff --git a/data/control.py b/data/control.py new file mode 100644 index 00000000..95e971f8 --- /dev/null +++ b/data/control.py @@ -0,0 +1,20 @@ +class Control(): + def __init__(self, id, attack_data): + self.id = id + + self.attack_data_array = attack_data + + def attack_data(self): + from data.controls import Controls + data = [0x00] * Controls.ATTACKS_DATA_SIZE + + data = self.attack_data_array + + return data + + def print(self): + attack_str = "" + for attack in self.attack_data: + attack_str += f"{attack} " + + print(f"{self.id} {attack_str}") diff --git a/data/controls.py b/data/controls.py new file mode 100644 index 00000000..9012eac8 --- /dev/null +++ b/data/controls.py @@ -0,0 +1,154 @@ +from data.control import Control +from data.structures import DataArray +from memory.space import Reserve, Allocate, Bank, Write +import instruction.asm as asm + +class Controls(): + ATTACKS_DATA_START = 0xf3d00 + ATTACKS_DATA_END = 0xf42ff + ATTACKS_DATA_SIZE = 4 + ATTACKS_DATA_TOTAL_BYTES = (ATTACKS_DATA_END - ATTACKS_DATA_START) + 1 + + def __init__(self, rom, args, enemies, rages): + self.rom = rom + self.args = args + self.enemies = enemies + self.rages = rages + + # Copy the vanilla table to a new location, so that any modifications do not affect Coliseum/Muddle behavior + self.new_attack_data_space = Allocate(Bank.F0, self.ATTACKS_DATA_TOTAL_BYTES, "new Controls table") + self.new_attack_data_space.copy_from(self.ATTACKS_DATA_START, self.ATTACKS_DATA_END) + + self.attack_data = DataArray(self.rom, self.new_attack_data_space.start_address, self.new_attack_data_space.end_address, self.ATTACKS_DATA_SIZE) + + self.controls = [] + for control_index in range(len(self.attack_data)): + control = Control(control_index, self.attack_data[control_index]) + self.controls.append(control) + + def split_control_table(self): + # Update the vanilla lookup of the table for Control commands + # Default: LDA $CF3D00,X + space = Reserve(0x23758, 0x2375B, "get Control command table") + space.write( + asm.LDA(self.new_attack_data_space.start_address_snes, asm.LNG_X) + ) + + def ignore_randomize_target(self): + # Ignoring Randomize Target bit when Control is used, to ensure that those commands respect the selected targetting + # This is a bug-fix for a vanilla bug, in which Controlled Dance abilities (ex: Sandstorm) swap targetting. + src = [ + asm.LDA(0x3A7A, asm.ABS), # load the command + asm.CMP(0x0E, asm.IMM8), # is it Control? + asm.BEQ("exit"), # if so, skip over displaced code + # displaced code from C2/276A - C2/2771 to read the "Randomize target bit" and set the equivalent in $BA + asm.LDA(0x01, asm.S), + asm.AND(0x10, asm.IMM8), + asm.ASL(), + asm.ASL(), + asm.TSB(0xBA, asm.DIR), + "exit", + asm.RTS(), + ] + space = Write(Bank.C2, src, "Control: ignore Randomize Target bit") + ignore_randomize_target_addr = space.start_address + + # Call our new subroutine + space = Reserve(0x2276A, 0x22771, "control: call ignore randomize target bit subroutine", asm.NOP()) + space.write( + asm.JSR(ignore_randomize_target_addr, asm.ABS) + ) + + def enable_control_casters_stats(self): + src = [ + # X = entity using command (in Control case, this is the monster being controlled) + asm.LDA(0x32B9,asm.ABS_X), # who's Controlling this entity? + asm.CMP(0xFF, asm.IMM8), + asm.BEQ("exit"), # branch if nobody controls them + asm.TAX(), # if there's a valid Controller, use their stats (vigor/magic/level) + asm.LDA(0x11A2, asm.ABS), #Spell Properties + asm.LSR(), #Check if Physical/Magical + asm.LDA(0x3B41, asm.ABS_X), #Controller's Mag.Pwr + asm.BCC("magical"), #Branch if not physical damage + asm.LDA(0x3B2C, asm.ABS_X), #Controller's Vigor * 2 + "magical", + asm.STA(0x11AE, asm.ABS), #Set Controller's Magic or Vigor + "exit", + asm.LDA(0x3B18, asm.ABS_X), # displaced code: get Level + asm.RTS(), + ] + space = Write(Bank.C2, src, "Controller Caster Stats") + use_controller_stats_addr = space.start_address + + # Call our new subroutine + space = Reserve(0x22c28, 0x22c2A, "jump to new routine") + space.write( + asm.JSR(use_controller_stats_addr, asm.ABS) + ) + + def enable_control_chances_always(self): + # Always Control if the target is valid + # NOPing the JSR and BCS that can prevent Control from working + space = Reserve(0x023ae8, 0x023aec, "control always", asm.NOP()) + + def enable_control_improved_abilities(self): + from data.spell_names import name_id + # Ensure that Rage & Special are available (if there are open Controls) + for control in self.controls: + # Search for blanks, rages, and specials + index_of_blank = self.ATTACKS_DATA_SIZE # default to end + control_has_rage = False + control_has_special = False + for attack_index, attack in enumerate(control.attack_data()): + # Look for the first blank entry + if index_of_blank == self.ATTACKS_DATA_SIZE and attack == name_id["??????????"]: + index_of_blank = attack_index + # Look for a rage + if control.id < self.rages.RAGE_COUNT: # Enemy has a rage + if attack == self.rages.rages[control.id].attack2 and not control_has_rage: + control_has_rage = True + else: + control_has_rage = True # no rages to have + # Look for a special + if attack == name_id["Special"] and not control_has_special: + control_has_special = True + + # If we found that it doesn't have a rage and there's room, add the rage + if not control_has_rage and index_of_blank < self.ATTACKS_DATA_SIZE: + control.attack_data_array[index_of_blank] = self.rages.rages[control.id].attack2 + # Avoid duplicate Specials if Rage == Special + if control.attack_data_array[index_of_blank] == name_id["Special"]: + control_has_special = True + index_of_blank = index_of_blank + 1 + + # If we found that it doesn't have a Special and there's room, add the Special + if not control_has_special and index_of_blank < self.ATTACKS_DATA_SIZE: + control.attack_data_array[index_of_blank] = name_id["Special"] + index_of_blank = index_of_blank + 1 + + def mod(self): + + self.ignore_randomize_target() + + if self.args.sketch_control_improved_stats: + self.enable_control_chances_always() + self.enable_control_casters_stats() + if self.args.sketch_control_improved_abilities: + self.split_control_table() + self.enable_control_improved_abilities() + + def write(self): + if self.args.spoiler_log: + self.log() + + for control_index, control in enumerate(self.controls): + self.attack_data[control_index] = control.attack_data() + + self.attack_data.write() + + def log(self): + pass + + def print(self): + for control in self.controls: + control.print() diff --git a/data/data.py b/data/data.py index 90baa85d..67e118a4 100644 --- a/data/data.py +++ b/data/data.py @@ -11,6 +11,8 @@ import data.rages as rages import data.dances as dances import data.steal as steal +import data.sketches as sketches +import data.controls as controls import data.magiteks as magiteks import data.espers as espers import data.shops as shops @@ -56,6 +58,13 @@ def __init__(self, rom, args): self.steal = steal.Steal(rom, args) self.steal.mod() + + self.sketches = sketches.Sketches(rom, args, self.enemies, self.rages) + self.sketches.mod() + + self.controls = controls.Controls(rom, args, self.enemies, self.rages) + self.controls.mod() + self.magiteks = magiteks.Magiteks(rom, args) self.magiteks.mod() @@ -85,6 +94,8 @@ def write(self): self.rages.write() self.dances.write() self.steal.write() + self.sketches.write() + self.controls.write() self.magiteks.write() self.espers.write() self.shops.write() diff --git a/data/enemies.py b/data/enemies.py index ff5de70c..b8e368b3 100644 --- a/data/enemies.py +++ b/data/enemies.py @@ -20,6 +20,10 @@ class Enemies(): ITEMS_END = 0xf35ff ITEMS_SIZE = 4 + SPECIAL_NAMES_START = 0xfd0d0 + SPECIAL_NAMES_END = 0xfdfdf + SPECIAL_NAMES_SIZE = 10 + DRAGON_COUNT = 8 SRBEHEMOTH2_ID = 127 @@ -32,11 +36,12 @@ def __init__(self, rom, args): self.enemy_data = DataArray(self.rom, self.DATA_START, self.DATA_END, self.DATA_SIZE) self.enemy_name_data = DataArray(self.rom, self.NAMES_START, self.NAMES_END, self.NAME_SIZE) self.enemy_item_data = DataArray(self.rom, self.ITEMS_START, self.ITEMS_END, self.ITEMS_SIZE) + self.enemy_special_name_data = DataArray(self.rom, self.SPECIAL_NAMES_START, self.SPECIAL_NAMES_END, self.SPECIAL_NAMES_SIZE) self.enemies = [] self.bosses = [] for enemy_index in range(len(self.enemy_data)): - enemy = Enemy(enemy_index, self.enemy_data[enemy_index], self.enemy_name_data[enemy_index], self.enemy_item_data[enemy_index]) + enemy = Enemy(enemy_index, self.enemy_data[enemy_index], self.enemy_name_data[enemy_index], self.enemy_item_data[enemy_index], self.enemy_special_name_data[enemy_index]) self.enemies.append(enemy) if enemy_index in bosses.enemy_name and enemy_index not in bosses.removed_enemy_name: @@ -353,10 +358,12 @@ def write(self): self.enemy_data[enemy_index] = self.enemies[enemy_index].data() self.enemy_name_data[enemy_index] = self.enemies[enemy_index].name_data() self.enemy_item_data[enemy_index] = self.enemies[enemy_index].item_data() + self.enemy_special_name_data[enemy_index] = self.enemies[enemy_index].special_name_data() self.enemy_data.write() self.enemy_name_data.write() self.enemy_item_data.write() + self.enemy_special_name_data.write() self.formations.write() self.packs.write() diff --git a/data/enemy.py b/data/enemy.py index 9bc68deb..c7c2491c 100644 --- a/data/enemy.py +++ b/data/enemy.py @@ -2,7 +2,7 @@ from data.status_effects import StatusEffects class Enemy: - def __init__(self, id, data, name_data, item_data): + def __init__(self, id, data, name_data, item_data, special_name_data): self.id = id self.name = text.get_string(name_data, text.TEXT2).rstrip('\0') @@ -70,6 +70,8 @@ def __init__(self, id, data, name_data, item_data): self.drop_rare = item_data[2] self.drop_common = item_data[3] + self.special_name = text.get_string(special_name_data, text.TEXT2).rstrip('\0') + # copy stats for reference after modifications self.original_speed = self.speed self.original_vigor = self.vigor @@ -181,5 +183,11 @@ def item_data(self): return item_data + def special_name_data(self): + from data.enemies import Enemies + data = text.get_bytes(self.special_name, text.TEXT2) + data.extend([0xff] * (Enemies.SPECIAL_NAMES_SIZE - len(data))) + return data + def print(self): print(f"{self.id} {self.name}") diff --git a/data/sketch.py b/data/sketch.py new file mode 100644 index 00000000..0f90018c --- /dev/null +++ b/data/sketch.py @@ -0,0 +1,18 @@ +class Sketch(): + def __init__(self, id, attack_data): + self.id = id + + self.rare = attack_data[0] + self.common = attack_data[1] + + def attack_data(self): + from data.sketches import Sketches + data = [0x00] * Sketches.ATTACKS_DATA_SIZE + + data[0] = self.rare + data[1] = self.common + + return data + + def print(self): + print(f"{self.id} {self.rare} {self.common}") diff --git a/data/sketch_custom_commands.py b/data/sketch_custom_commands.py new file mode 100644 index 00000000..ec2762ef --- /dev/null +++ b/data/sketch_custom_commands.py @@ -0,0 +1,54 @@ +from data.bosses import name_enemy +from data.spell_names import name_id + +# This dictionary contains sketch command overrides for specific enemies +# Each array is in the order of [Rare (25%), Common (75%)] +custom_commands = { + name_enemy["Vargas"] : [name_id["Gale Cut"] , name_id["Special"]], + name_enemy["TunnelArmr"] : [name_id["Tek Laser"] , name_id["Special"]], + name_enemy["GhostTrain"] : [name_id["Scar Beam"] , name_id["Special"]], + name_enemy["Dadaluma"] : [name_id["Shock Wave"], name_id["Special"]], + name_enemy["Shiva"] : [name_id["Rflect"] , name_id["Special"]], + name_enemy["Number 024"] : [name_id["Cure 2"] , name_id["Scan"]], + name_enemy["Number 128"] : [name_id["Net"] , name_id["Special"]], + name_enemy["Inferno"] : [name_id["Bolt 3"] , name_id["TekBarrier"]], + name_enemy["Left Crane"] : [name_id["TekBarrier"], name_id["Special"]], + name_enemy["Right Crane"] : [name_id["TekBarrier"], name_id["Special"]], + name_enemy["AtmaWeapon"] : [name_id["Bio"] , name_id["Special"]], + name_enemy["KatanaSoul"] : [name_id["Special"] , name_id["Shock Wave"]], + name_enemy["Red Dragon"] : [name_id["Flare"] , name_id["L? Pearl"]], # L? Pearl is a vanilla sketch + name_enemy["Blue Drgn"] : [name_id["Slow"] , name_id["Ice 3"]], # Ice 3 is a vanilla sketch + name_enemy["Skull Drgn"] : [name_id["Elf Fire"] , name_id["Rasp"]], # Rasp is a vanilla sketch + name_enemy["Gold Drgn"] : [name_id["Ice 3"] , name_id["Rflect"]], # Ice 3 is a vanilla sketch + name_enemy["Storm Drgn"] : [name_id["Aero"] , name_id["Pearl Wind"]], # vanilla sketches; just reversed. Useful for Lore. + name_enemy["Whelk"] : [name_id["Mega Volt"] , name_id["Mega Volt"]], + name_enemy["Presenter"] : [name_id["Magnitude8"], name_id["Blow Fish"]], + name_enemy["Air Force"] : [name_id["WaveCannon"], name_id["Tek Laser"]], + name_enemy["Laser Gun"] : [name_id["Diffuser"] , name_id["Tek Laser"]], + name_enemy["FlameEater"] : [name_id["Flare"] , name_id["Rflect"]], + name_enemy["Nerapa"] : [name_id["Condemned"] , name_id["Condemned"]], + name_enemy["SrBehemoth"] : [name_id["Meteo"] , name_id["Pearl"]], + name_enemy["Dullahan"] : [name_id["Pearl"] , name_id["Cure 2"]], + name_enemy["Doom Gaze"] : [name_id["Aero"] , name_id["Aero"]], + name_enemy["Curley"] : [name_id["Fire 3"] , name_id["Pearl Wind"]], + name_enemy["Larry"] : [name_id["Ice 3"] , name_id["Rflect"]], + name_enemy["Moe"] : [name_id["Bolt 3"] , name_id["Shell"]], + name_enemy["Wrexsoul"] : [name_id["Bolt 3"] , name_id["Bolt 3"]], + name_enemy["Hidon"] : [name_id["GrandTrain"], name_id["Poison"]], # Poison will be more common, and heal. May be worth it to learn GrandTrain + name_enemy["Doom"] : [name_id["Special"] , name_id["ForceField"]], + name_enemy["Goddess"] : [name_id["Quasar"] , name_id["Bolt 3"]], # Bolt 3 will be more common and heal. May be worth it to learn Quasar + name_enemy["Poltrgeist"] : [name_id["Meteo"] , name_id["Shrapnel"]], + name_enemy["Ultros 1"] : [name_id["Special"] , name_id["Tentacle"]], + name_enemy["Ultros 2"] : [name_id["Special"] , name_id["Tentacle"]], + name_enemy["Ultros 4"] : [name_id["Special"] , name_id["Tentacle"]], + name_enemy["Striker"] : [name_id["Shrapnel"] , name_id["Special"]], + name_enemy["Tritoch"] : [name_id["Rasp"] , name_id["Rasp"]], + name_enemy["Chadarnook (Demon)"] : [name_id["Flash Rain"], name_id["Flash Rain"]], + name_enemy["Kefka (Narshe)"] : [name_id["Ice 2"] , name_id["Ice 2"]], + name_enemy["Rizopas"] : [name_id["Mega Volt"] , name_id["Special"]], + name_enemy["MagiMaster"] : [name_id["Fire 3"] , name_id["Ice 3"]], # Powerful abilities, but may heal + name_enemy["Naughty"] : [name_id["Cold Dust"] , name_id["Mute"]], + name_enemy["Phunbaba 3"] : [name_id["Special"] , name_id["Blow Fish"]], + name_enemy["Phunbaba 4"] : [name_id["Special"] , name_id["Blow Fish"]], + name_enemy["Atma"] : [name_id["Flare Star"], name_id["S. Cross"]], +} \ No newline at end of file diff --git a/data/sketches.py b/data/sketches.py new file mode 100644 index 00000000..233956f2 --- /dev/null +++ b/data/sketches.py @@ -0,0 +1,102 @@ +from data.sketch import Sketch +from data.structures import DataArray +from memory.space import Reserve, Bank, Write +import instruction.asm as asm + +class Sketches(): + ATTACKS_DATA_START = 0xf4300 + ATTACKS_DATA_END = 0xf45ff + ATTACKS_DATA_SIZE = 2 + + def __init__(self, rom, args, enemies, rages): + self.rom = rom + self.args = args + self.enemies = enemies + self.rages = rages + + self.attack_data = DataArray(self.rom, self.ATTACKS_DATA_START, self.ATTACKS_DATA_END, self.ATTACKS_DATA_SIZE) + + self.sketches = [] + for sketch_index in range(len(self.attack_data)): + sketch = Sketch(sketch_index, self.attack_data[sketch_index]) + self.sketches.append(sketch) + + def enable_sketch_chances_always(self): + # Always Sketch if the target is valid + # NOPing the JSR and BCS that can prevent Sketch from working + space = Reserve(0x023b3d, 0x023b41, "sketch always", asm.NOP()) + + def enable_sketch_casters_stats(self): + # Based on https://www.ff6hacking.com/forums/thread-3478.html + + # New subroutine. Note that most of this logic is the same as vanilla logic at C2/2954. + src = [ + # A = Character using sketch (from $3417) + asm.BMI("exit"), #Branch if no Sketcher + asm.TAX(), #if there's a valid Sketcher, use their Level for attack by making X = character offset + asm.LDA(0x11A2, asm.ABS), #Spell Properties + asm.LSR(), #Check if Physical/Magical + asm.LDA(0x3B41, asm.ABS_X), #Sketcher's Mag.Pwr + asm.BCC("magical"), #Branch if not physical damage + asm.LDA(0x3B2C, asm.ABS_X), #Sketcher's Vigor * 2 + "magical", + asm.STA(0x11AE, asm.ABS), #Set Sketcher's Magic or Vigor + "exit", + asm.RTS(), + ] + space = Write(Bank.C2, src, "Sketch Caster Stats") + use_sketcher_stats_addr = space.start_address + + # Call our new subroutine + space = Reserve(0x22c25, 0x22c27, "jump to new routine") + space.write( + asm.JSR(use_sketcher_stats_addr, asm.ABS) + ) + + def enable_sketch_improved_abilities(self): + from data.spell_names import name_id + from data.sketch_custom_commands import custom_commands + + for sketch in self.sketches: + # if either is Battle, replace with opposite + if sketch.rare == name_id["Battle"]: + sketch.rare = sketch.common + if sketch.common == name_id["Battle"]: + sketch.common = sketch.rare + # If both are Battle, replace both with Rage (if it exists) + if sketch.rare == name_id["Battle"] and sketch.common == name_id["Battle"]: + if sketch.id < self.rages.RAGE_COUNT: + rage = self.rages.rages[sketch.id] + sketch.rare = rage.attack2 + sketch.common = rage.attack2 + # If both are identical, replace rare with Rage (if it exists) + if sketch.rare == sketch.common: + if sketch.id < self.rages.RAGE_COUNT: + sketch.rare = self.rages.rages[sketch.id].attack2 + # Override with custom commands + if sketch.id in custom_commands: + sketch.rare = custom_commands[sketch.id][0] + sketch.common = custom_commands[sketch.id][1] + + def mod(self): + if self.args.sketch_control_improved_stats: + self.enable_sketch_chances_always() + self.enable_sketch_casters_stats() + if self.args.sketch_control_improved_abilities: + self.enable_sketch_improved_abilities() + + def write(self): + if self.args.spoiler_log: + self.log() + + for sketch_index, sketch in enumerate(self.sketches): + self.attack_data[sketch_index] = sketch.attack_data() + + self.attack_data.write() + + def log(self): + pass + + def print(self): + for sketch in self.sketches: + sketch.print() From 116a1fee96202253325122a5c2cf4115042af62c Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:45:25 -0700 Subject: [PATCH 13/60] Feature: Kielbasiago's Adding flag for giving starting Espers (#18) * add --starting-espers-random flag - this will give you between the selected range of espers from the very start * fix err * Add better esper log * chore: update flag from sespr => stesp * chore: remove commented code * Remove "Random" from "Starting Espers" menu * Update starting_espers MAX to account for logic issues * ws, help updated Co-authored-by: Kiel <95580337+kielbasiago@users.noreply.github.com> --- args/espers.py | 20 ++++++++++++++++++++ data/espers.py | 15 ++++++++++++++- event/event_reward.py | 1 + event/events.py | 15 +++++++++++++-- event/start.py | 17 +++++++++++++++++ 5 files changed, 65 insertions(+), 3 deletions(-) diff --git a/args/espers.py b/args/espers.py index 6cfbb466..9d097c5d 100644 --- a/args/espers.py +++ b/args/espers.py @@ -1,3 +1,12 @@ +from data.espers import Espers +from event.event_reward import CHARACTER_ESPER_ONLY_REWARDS + +# If all 27 espers are allocated at start, there will be logic errors when it comes to +# assigning characters to character/esper only checks. +# We would have to ensure that a character is assigned to the {6} char/esper only rewards. +# We could account for this in the logic, but it would gentrify the routing logic a bit much. +MAX_STARTING_ESPERS = Espers.ESPER_COUNT - CHARACTER_ESPER_ONLY_REWARDS + def name(): return "Espers" @@ -6,7 +15,13 @@ def parse(parser): from data.characters import Characters espers = parser.add_argument_group("Espers") + esper_start = espers.add_mutually_exclusive_group() + esper_start.add_argument("-stesp", "--starting-espers", default = [0, 0], type = int, + nargs = 2, metavar = ("MIN", "MAX"), choices = range(MAX_STARTING_ESPERS + 1), + help = "Party starts with %(metavar) random espers") + esper_spells = espers.add_mutually_exclusive_group() + esper_spells.add_argument("-esrr", "--esper-spells-random-rates", action = "store_true", help = "Original esper spells with random learn rates") esper_spells.add_argument("-ess", "--esper-spells-shuffle", action = "store_true", @@ -50,6 +65,7 @@ def parse(parser): help = "Espers can be summoned multiple times in battle") def process(args): + args._process_min_max("starting_espers") args._process_min_max("esper_spells_random") args._process_min_max("esper_mp_random_value") args._process_min_max("esper_mp_random_percent") @@ -66,6 +82,9 @@ def process(args): def flags(args): flags = "" + if args.starting_espers_min or args.starting_espers_max: + flags += f" -stesp {args.starting_espers_min} {args.starting_espers_max}" + if args.esper_spells_random_rates: flags += " -esrr" elif args.esper_spells_shuffle: @@ -133,6 +152,7 @@ def options(args): equipable = f"Balanced Random {args.esper_equipable_balanced_random_value}" result = [] + result.append(("Starting Espers", f"{args.starting_espers_min}-{args.starting_espers_max}")) result.append(("Spells", spells)) result.append(("Bonuses", bonuses)) if args.esper_bonuses_random: diff --git a/data/espers.py b/data/espers.py index 8d41a950..8f0007f4 100644 --- a/data/espers.py +++ b/data/espers.py @@ -45,6 +45,11 @@ def __init__(self, rom, args, spells, characters): self.espers.append(esper) self.available_espers = set(range(self.ESPER_COUNT)) + self.starting_espers = [] + + if args.starting_espers_min > 0: + count = random.randint(args.starting_espers_min, args.starting_espers_max) + self.starting_espers = [self.get_random_esper() for _esp in range(count)] def receive_dialogs_mod(self, dialogs): self.receive_dialogs = [1133, 1380, 1381, 1134, 1535, 1082, 1091, 1092, 1136, 1534, 2618, 1093, 1087,\ @@ -271,6 +276,9 @@ def mod(self, dialogs): if self.args.esper_spells_random_rates or self.args.esper_spells_shuffle_random_rates: self.randomize_rates() + if len(self.starting_espers): + self.randomize_rates() + if self.args.esper_spells_shuffle or self.args.esper_spells_shuffle_random_rates: self.shuffle_spells() elif self.args.esper_spells_random: @@ -345,8 +353,10 @@ def log(self): for entry_index in range(self.ESPER_COUNT): esper_index = self.esper_menu_order[entry_index] esper = self.espers[esper_index] + prefix = "*" if esper.id in self.starting_espers else "" + + entry = [f"{prefix}{esper.get_name():<{self.NAME_SIZE}} {esper.mp:>3} MP"] - entry = [f"{esper.get_name():<{self.NAME_SIZE}} {esper.mp:>3} MP"] for spell_index in range(esper.spell_count): spell_name = self.spells.get_name(esper.spells[spell_index].id) learn_rate = esper.spells[spell_index].rate @@ -376,6 +386,9 @@ def log(self): else: lentries.append(entry) + lentries.append("") + lentries.append("* = Starting Esper") + section_entries("Espers", lentries, rentries) def print(self): diff --git a/event/event_reward.py b/event/event_reward.py index 0334886d..f1063e2e 100644 --- a/event/event_reward.py +++ b/event/event_reward.py @@ -6,6 +6,7 @@ class RewardType(Flag): ESPER = auto() ITEM = auto() +CHARACTER_ESPER_ONLY_REWARDS = 6 class Reward: def __init__(self, event, possible_types): self.id = None diff --git a/event/events.py b/event/events.py index cb0887b7..f1d40477 100644 --- a/event/events.py +++ b/event/events.py @@ -1,5 +1,5 @@ from memory.space import Bank, Allocate -from event.event_reward import RewardType, Reward, choose_reward, weighted_reward_choice +from event.event_reward import CHARACTER_ESPER_ONLY_REWARDS, RewardType, choose_reward, weighted_reward_choice import instruction.field as field class Events(): @@ -15,7 +15,9 @@ def __init__(self, rom, args, data): self.espers = data.espers self.shops = data.shops - self.mod() + events = self.mod() + + self.validate(events) def mod(self): # generate list of events from files @@ -58,6 +60,8 @@ def mod(self): from log import section section("Events", log_strings, []) + return events + def init_reward_slots(self, events): import random reward_slots = [] @@ -161,3 +165,10 @@ def open_world_mod(self, events): # choose the rest of the rewards, items given to events after all characters/events assigned self.choose_item_possible_rewards(reward_slots) + + def validate(self, events): + char_esper_checks = [] + for event in events: + char_esper_checks += [r for r in event.rewards if r.possible_types == (RewardType.CHARACTER | RewardType.ESPER)] + + assert len(char_esper_checks) == CHARACTER_ESPER_ONLY_REWARDS, "Number of char/esper only checks changed - Check usages of CHARACTER_ESPER_ONLY_REWARDS and ensure no breaking changes" \ No newline at end of file diff --git a/event/start.py b/event/start.py index 80f35b42..320ef8a2 100644 --- a/event/start.py +++ b/event/start.py @@ -63,6 +63,7 @@ def mod(self): self.intro_loop_mod() self.init_characters_mod() self.start_party_mod() + self.start_esper_mod() self.start_gold_mod() self.start_items_mod() self.start_game_mod() @@ -73,6 +74,7 @@ def mod(self): field.Call(self.event_bit_init), field.Call(self.character_init), field.Call(self.start_party), + field.Call(self.start_esper), field.Call(self.start_gold), field.Call(self.start_items), field.Call(self.start_game), @@ -142,6 +144,21 @@ def start_party_mod(self): space = Write(Bank.CC, src, "start party") self.start_party = space.start_address + def start_esper_mod(self): + src = [] + + for esper_id in self.espers.starting_espers: + src += [ + field.AddEsper(esper_id, sound_effect = False) + ] + + src += [ + field.Return() + ] + + space = Write(Bank.CC, src, "start espers") + self.start_esper = space.start_address + def start_gold_mod(self): gold = self.args.gold if self.args.debug: From bc3b19a2b2ba840565b3b5a652fba46953cd25e3 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:45:48 -0700 Subject: [PATCH 14/60] Feature: Kielbasiago's Update ability to shuffle/mix both dragons and statues (#17) * add -bmbs flag: adds statues to shuffle pool * add context for statue ids being in EnemyPacks * set default true ffor backwards compatibility * remove default as no way to turn it off * wip refactor shuffle to "boss location" flags * bug fixes * add boilerplate for adding future checks * chore: no longer appnd dragon locations to flags automatically * chore: add EOL * chore: remove unused ids from EnemyPacks * remove dbugpy code * Clean up enemy_packs.randomize_event_bosses * fix when original bosses with mixed dragon/statues * now exclude statues when not mix from normal encs * add KT bosses to condition checks (#2) Preview: https://youtu.be/wgBJHPq3p6o (Just me clearing KT while playing at 200-400%) Added the following KT boss locations as objective conditions: - Kefka's Tower Ambush - Inferno - Bit 59 - Kefka's Tower Guardian - Guardian - Bit 60 - KT Left Triad Statue - Doom - Bit 61 - KT Mid Triad Statue - Poltergeist - Bit 62 - KT Right Triad Statue - Goddess - Bit 63 ## Testing Used the following flags to test the five encounters: `-oa 40.1.1.11.59 -ob 40.1.1.11.60 -oc 40.1.1.11.61 -od 40.1.1.11.62 -oe 40.1.1.11.63` * fix initialization of exclude_bosses Co-authored-by: Nolan <10077353+nolanlocke@users.noreply.github.com> Co-authored-by: Kiel <95580337+kielbasiago@users.noreply.github.com> --- args/bosses.py | 47 +++++- constants/objectives/condition_bits.py | 8 + data/bosses.py | 26 ++++ data/enemy_formations.py | 6 +- data/enemy_packs.py | 201 +++++++++++++++---------- data/event_bit.py | 9 +- event/kefka_tower.py | 66 ++++++++ objectives/conditions/boss.py | 9 +- objectives/conditions/dragon.py | 6 +- 9 files changed, 288 insertions(+), 90 deletions(-) diff --git a/args/bosses.py b/args/bosses.py index 29bffd56..7fe1b107 100644 --- a/args/bosses.py +++ b/args/bosses.py @@ -1,3 +1,8 @@ +from data.bosses import BossLocations + + +DEFAULT_DRAGON_PROTOCOL = BossLocations.SHUFFLE +DEFAULT_STATUE_PROTOCOL = BossLocations.MIX def name(): return "Bosses" @@ -9,8 +14,17 @@ def parse(parser): help = "Boss battles shuffled") bosses_battles.add_argument("-bbr", "--boss-battles-random", action = "store_true", help = "Boss battles randomized") - bosses.add_argument("-bmbd", "--mix-bosses-dragons", action = "store_true", + + dragons = bosses.add_mutually_exclusive_group() + dragons.add_argument("-drloc", "--dragon-boss-location", default = DEFAULT_DRAGON_PROTOCOL, type = str.lower, choices = BossLocations.ALL, + help = "Decides which locations the eight dragon encounters can be fought") + dragons.add_argument("-bmbd", "--mix-bosses-dragons", action = "store_true", help = "Shuffle/randomize bosses and dragons together") + + statues = bosses.add_mutually_exclusive_group() + statues.add_argument("-stloc", "--statue-boss-location", default = DEFAULT_STATUE_PROTOCOL, type = str.lower, choices = BossLocations.ALL, + help = "Decides which locations the three statue encounters can be fought") + bosses.add_argument("-srp3", "--shuffle-random-phunbaba3", action = "store_true", help = "Apply Shuffle/Random to Phunbaba 3 (otherwise he will only appear in Mobliz WOR)") bosses.add_argument("-bnds", "--boss-normalize-distort-stats", action = "store_true", @@ -21,7 +35,15 @@ def parse(parser): help = "Undead status removed from bosses") def process(args): - pass + if args.mix_bosses_dragons: + args.dragon_boss_location = BossLocations.MIX + args.mix_bosses_dragons = None + # if neither shuffling or randomizing bosses, and we try to mix the dragons/statues, simply shuffle them instead + vanilla_locations = not (args.boss_battles_shuffle or args.boss_battles_random) + if vanilla_locations and args.dragon_boss_location == BossLocations.MIX: + args.dragon_boss_location = BossLocations.SHUFFLE + if vanilla_locations and args.statue_boss_location == BossLocations.MIX: + args.statue_boss_location = BossLocations.SHUFFLE def flags(args): flags = "" @@ -31,8 +53,14 @@ def flags(args): elif args.boss_battles_random: flags += " -bbr" - if args.mix_bosses_dragons: - flags += " -bmbd" + if args.dragon_boss_location: + flags += f" -drloc {args.dragon_boss_location}" + elif args.mix_bosses_dragons: + flags += f" -drloc {BossLocations.MIX}" + + if args.statue_boss_location: + flags += f" -stloc {args.statue_boss_location}" + if args.shuffle_random_phunbaba3: flags += " -srp3" if args.boss_normalize_distort_stats: @@ -51,9 +79,18 @@ def options(args): elif args.boss_battles_random: boss_battles = "Random" + dragon_battles = DEFAULT_DRAGON_PROTOCOL + if args.dragon_boss_location: + dragon_battles = args.dragon_boss_location.capitalize() + + statue_battles = DEFAULT_DRAGON_PROTOCOL + if args.statue_boss_location: + statue_battles = args.statue_boss_location.capitalize() + return [ ("Boss Battles", boss_battles), - ("Mix Bosses & Dragons", args.mix_bosses_dragons), + ("Dragons", dragon_battles), + ("Statues", statue_battles), ("Shuffle/Random Phunbaba 3", args.shuffle_random_phunbaba3), ("Normalize & Distort Stats", args.boss_normalize_distort_stats), ("Boss Experience", args.boss_experience), diff --git a/constants/objectives/condition_bits.py b/constants/objectives/condition_bits.py index bc46f131..8e45eebe 100644 --- a/constants/objectives/condition_bits.py +++ b/constants/objectives/condition_bits.py @@ -68,6 +68,14 @@ NameBit("Zozo Tower", event_bit.GOT_ZOZO_REWARD), ] +check_bit += [ # Index + NameBit("Kefka's Tower Ambush", event_bit.DEFEATED_INFERNO), # 59 + NameBit("Kefka's Tower Guardian", event_bit.DEFEATED_GUARDIAN), # 60 + NameBit("KT Left Triad Statue", event_bit.DEFEATED_DOOM), # 61 + NameBit("KT Mid Triad Statue", event_bit.DEFEATED_POLTERGEIST), # 62 + NameBit("KT Right Triad Statue", event_bit.DEFEATED_GODDESS), # 63 +] + quest_bit = [ NameBit("Defeat Sealed Cave Ninja", event_bit.DEFEATED_NINJA_CAVE_TO_SEALED_GATE), NameBit("Help Injured Lad", event_bit.HELPED_INJURED_LAD), diff --git a/data/bosses.py b/data/bosses.py index 1fc0be80..fa1910e3 100644 --- a/data/bosses.py +++ b/data/bosses.py @@ -92,6 +92,25 @@ 396 : "Guardian", # defeatable guardian in kefka's tower 401 : "MagiMaster", } + +# These ids are repeated in normal_pack_name as well +# This is intentional as they are used to iterate over ALL bosses for things like objective conditions +statue_pack_name = { + 354 : "Doom", + 355 : "Goddess", + 356 : "Poltrgeist", +} +statue_formation_name = { + 468 : "Doom", + 469 : "Goddess", + 470 : "Poltrgeist", +} +statue_enemy_name = { + 295 : "Doom", + 296 : "Goddess", + 297 : "Poltrgeist", +} + normal_formation_name = { 79 : "Rizopas", 354 : "MagiMaster", @@ -258,3 +277,10 @@ enemy_name.update(removed_enemy_name) name_enemy = {v: k for k, v in enemy_name.items()} + +class BossLocations: + MIX = "mix" + ORIGINAL = "original" + SHUFFLE = "shuffle" + + ALL = [MIX, ORIGINAL, SHUFFLE] diff --git a/data/enemy_formations.py b/data/enemy_formations.py index 22765e34..e6d334be 100644 --- a/data/enemy_formations.py +++ b/data/enemy_formations.py @@ -11,8 +11,10 @@ class EnemyFormations(): ENEMIES_END = 0xf83bf ENEMIES_SIZE = 15 - PHUNBABA3 = 422 - DOOM_GAZE = 463 + PHUNBABA3 = bosses.name_formation["Phunbaba 3"] + DOOM_GAZE = bosses.name_formation["Doom Gaze"] + ALL_STATUES = list(bosses.statue_formation_name) + ALL_DRAGONS = list(bosses.dragon_formation_name) PRESENTER = 433 COLISEUM = 575 diff --git a/data/enemy_packs.py b/data/enemy_packs.py index 5aa64871..266dd3ad 100644 --- a/data/enemy_packs.py +++ b/data/enemy_packs.py @@ -15,8 +15,9 @@ class EnemyPacks(): ZONE_EATER = 32 VELDT = 255 # placeholder for veldt in wob/wor - PHUNBABA3 = 386 - DOOM_GAZE = 349 + + PHUNBABA3 = bosses.name_pack["Phunbaba 3"] + DOOM_GAZE = bosses.name_pack["Doom Gaze"] def __init__(self, rom, args, formations): self.rom = rom @@ -35,18 +36,80 @@ def __init__(self, rom, args, formations): pack = EnemyPack2(pack2_index, self.pack2_data[pack2_index]) self.packs.append(pack) + # Returns the list of all boss packs that can be used during randomization def _replaceable_bosses(self): - replaceable = list(bosses.normal_pack_name) + dragon_packs = list(bosses.dragon_pack_name) + statue_packs = list(bosses.statue_pack_name) + boss_packs = list(bosses.normal_pack_name) + replaceable = [boss for boss in boss_packs if boss not in statue_packs and boss not in dragon_packs] + if not self.args.shuffle_random_phunbaba3: - replaceable.remove(self.PHUNBABA3) self.event_boss_replacements[self.PHUNBABA3] = self.PHUNBABA3 + if self.PHUNBABA3 in replaceable: + replaceable.remove(self.PHUNBABA3) + if not self.args.doom_gaze_no_escape: # if doom gaze can escape, don't shuffle/randomize him # possibly having multiple doom gazes while trying to keep track of hp is awkward # how would that work with him being in his original spot and the others? How to know when to get bahamut esper? - replaceable.remove(self.DOOM_GAZE) self.event_boss_replacements[self.DOOM_GAZE] = self.DOOM_GAZE - return replaceable + + if self.DOOM_GAZE in replaceable: + replaceable.remove(self.DOOM_GAZE) + + return replaceable + self._replaceable_dragons() + self._replaceable_statues() + + # Statue locations that become available for the general boss pool + def _replaceable_statues(self): + import random + statues = list(bosses.statue_pack_name) + random.shuffle(statues) + return statues if self.args.statue_boss_location == bosses.BossLocations.MIX else [] + + # Dragon locations that become available for the general boss pool + def _replaceable_dragons(self): + import random + statues = list(bosses.dragon_pack_name) + random.shuffle(statues) + return statues if self.args.dragon_boss_location == bosses.BossLocations.MIX else [] + + # As MIX is handled in the shuffle/random functions, this is for handling the other options + def _handle_original_shuffle_statues(self): + statues = list(bosses.statue_pack_name) + + if self.args.statue_boss_location == bosses.BossLocations.ORIGINAL: + for statue in statues: + self.event_boss_replacements[statue] = statue + elif self.args.statue_boss_location == bosses.BossLocations.SHUFFLE: + import random + replacements = statues.copy() + random.shuffle(statues) + random.shuffle(replacements) + + for statue in statues: + self.event_boss_replacements[replacements.pop()] = statue + else: + # boss assignment is handled in the shuffle/random functions + pass + + + # As MIX is handled in the shuffle/random functions, this is for handling the other options + def _handle_original_shuffle_dragons(self): + dragons = list(bosses.dragon_pack_name) + if self.args.dragon_boss_location == bosses.BossLocations.ORIGINAL: + for dragon in dragons: + self.event_boss_replacements[dragon] = dragon + elif self.args.dragon_boss_location == bosses.BossLocations.SHUFFLE: + import random + replacements = dragons.copy() + random.shuffle(dragons) + random.shuffle(replacements) + + for dragon in dragons: + self.event_boss_replacements[replacements.pop()] = dragon + else: + # boss assignment is handled in the shuffle/random functions + pass def phunbaba3_safety_check(self, bosses_possible): import random @@ -72,40 +135,20 @@ def phunbaba3_safety_check(self, bosses_possible): def shuffle_event_bosses(self): import random - self.event_boss_replacements = {} - - if self.args.mix_bosses_dragons: - bosses_dragons_to_replace = self._replaceable_bosses() + list(bosses.dragon_pack_name) - bosses_dragons_possible = bosses_dragons_to_replace.copy() - - random.shuffle(bosses_dragons_possible) - for index, boss in enumerate(bosses_dragons_to_replace): - self.event_boss_replacements[boss] = bosses_dragons_possible[index] - - self.phunbaba3_safety_check(bosses_dragons_to_replace) - else: - bosses_to_replace = self._replaceable_bosses() - bosses_possible = bosses_to_replace.copy() - - random.shuffle(bosses_possible) - for index, boss in enumerate(bosses_to_replace): - self.event_boss_replacements[boss] = bosses_possible[index] - dragons_to_replace = list(bosses.dragon_pack_name) - dragons_possible = dragons_to_replace.copy() + bosses_to_replace = self._replaceable_bosses() + bosses_possible = bosses_to_replace.copy() - random.shuffle(dragons_possible) - for index, dragon in enumerate(dragons_to_replace): - self.event_boss_replacements[dragon] = dragons_possible[index] + random.shuffle(bosses_possible) + for index, boss in enumerate(bosses_to_replace): + self.event_boss_replacements[boss] = bosses_possible[index] - self.phunbaba3_safety_check(bosses_to_replace) + self.phunbaba3_safety_check(bosses_to_replace) def randomize_event_bosses(self): import args, random, objectives from constants.objectives.conditions import names as possible_condition_names - self.event_boss_replacements = {} - boss_condition_name = "Boss" dragon_condition_name = "Dragon" dragons_condition_name = "Dragons" @@ -115,13 +158,19 @@ def randomize_event_bosses(self): required_boss_formations = set() required_dragon_formations = set() + required_statue_formations = set() + min_dragon_formations = 0 for objective in objectives: for condition in objective.conditions: if condition.NAME == boss_condition_name: - required_boss_formations.add(bosses.name_formation[condition.boss_name()]) + formation = condition.boss_formation + if formation in list(bosses.statue_formation_name): + required_statue_formations.add(formation) + else: + required_boss_formations.add(formation) elif condition.NAME == dragon_condition_name: - required_dragon_formations.add(bosses.name_formation[condition.dragon_name()]) + required_dragon_formations.add(condition.dragon_formation) elif condition.NAME == dragons_condition_name and condition.count > min_dragon_formations: min_dragon_formations = condition.count @@ -133,11 +182,14 @@ def randomize_event_bosses(self): required_dragon_formations |= set(random_dragon_formations) required_boss_packs = set() + required_statue_packs = set() for pack_id, pack_name in bosses.normal_pack_name.items(): formations = self.get_formations(pack_id) for formation_id in formations: if formation_id in required_boss_formations: required_boss_packs.add(pack_id) + elif formation_id in required_statue_formations: + required_statue_packs.add(pack_id) required_dragon_packs = set() for pack_id, pack_name in bosses.dragon_pack_name.items(): @@ -146,56 +198,47 @@ def randomize_event_bosses(self): if formation_id in required_dragon_formations: required_dragon_packs.add(pack_id) - if self.args.mix_bosses_dragons: - bosses_dragons_to_replace = self._replaceable_bosses() + list(bosses.dragon_pack_name) - random.shuffle(bosses_dragons_to_replace) - - for pack in required_boss_packs: - self.event_boss_replacements[bosses_dragons_to_replace.pop()] = pack - - for pack in required_dragon_packs: - self.event_boss_replacements[bosses_dragons_to_replace.pop()] = pack - - # guarantee 8 dragons - dragons_possible = list(bosses.dragon_pack_name) - for index in range(len(required_dragon_packs), len(bosses.dragon_pack_name)): - self.event_boss_replacements[bosses_dragons_to_replace.pop()] = random.choice(dragons_possible) - bosses_possible = self._replaceable_bosses() - for boss in bosses_dragons_to_replace: - self.event_boss_replacements[boss] = random.choice(bosses_possible) + # randomizing and shuffling + bosses_to_replace = self._replaceable_bosses() + random.shuffle(bosses_to_replace) + for pack in required_boss_packs: + self.event_boss_replacements[bosses_to_replace.pop()] = pack - self.phunbaba3_safety_check(bosses_possible + dragons_possible) - else: - bosses_to_replace = self._replaceable_bosses() - random.shuffle(bosses_to_replace) - for pack in required_boss_packs: + # If statue locations are not mixed, they will always + if self.args.statue_boss_location == bosses.BossLocations.MIX: + for pack in required_statue_packs: self.event_boss_replacements[bosses_to_replace.pop()] = pack - bosses_possible = self._replaceable_bosses() - for boss in bosses_to_replace: - self.event_boss_replacements[boss] = random.choice(bosses_possible) - - dragons_to_replace = list(bosses.dragon_pack_name) - random.shuffle(dragons_to_replace) + if self.args.dragon_boss_location == bosses.BossLocations.MIX: for pack in required_dragon_packs: - self.event_boss_replacements[dragons_to_replace.pop()] = pack + self.event_boss_replacements[bosses_to_replace.pop()] = pack - dragons_possible = list(bosses.dragon_pack_name) - for dragon in dragons_to_replace: - self.event_boss_replacements[dragon] = random.choice(dragons_possible) + random.shuffle(bosses_to_replace) + bosses_possible = self._replaceable_bosses() + for boss in bosses_to_replace: + self.event_boss_replacements[boss] = random.choice(bosses_possible) - self.phunbaba3_safety_check(bosses_possible) + self.phunbaba3_safety_check(bosses_possible) def randomize_packs(self, packs, boss_percent, no_phunbaba3 = False): - exclude_bosses = None + exclude_bosses = [] if no_phunbaba3 or not self.args.shuffle_random_phunbaba3: - exclude_bosses = [self.formations.PHUNBABA3] + exclude_bosses += [self.formations.PHUNBABA3] if not self.args.doom_gaze_no_escape: - if exclude_bosses is None: - exclude_bosses = [self.formations.DOOM_GAZE] - else: - exclude_bosses.append(self.formations.DOOM_GAZE) + exclude_bosses += [self.formations.DOOM_GAZE] + + # We only want statues and dragons to show up when they are intentionally + # mixed into the general boss pool + # Statues are currently seen as normal bosses in regards to scaling, + # but the long-term goal is to add their own scaling option so it + # makes most sense to begin treating these similarly to dragons. + if self.args.statue_boss_location != bosses.BossLocations.MIX: + exclude_bosses += self.formations.ALL_STATUES + + # This is more futureproofing in the event we consolidate dragons in the future + if self.args.dragon_boss_location != bosses.BossLocations.MIX: + exclude_bosses += self.formations.ALL_DRAGONS import random for pack_id in packs: @@ -290,7 +333,7 @@ def has_enemy(self, pack_id, enemy_id): def get_event_boss_replacement(self, original_boss_name): original_boss_id = self.get_id(original_boss_name) - if self.event_boss_replacements is None: + if not original_boss_id in self.event_boss_replacements: return original_boss_id return self.event_boss_replacements[original_boss_id] @@ -302,12 +345,18 @@ def remove_extra_formations(self): pack.extra_formations[formation_index] = False def mod(self): + self.event_boss_replacements = { + self.DOOM_GAZE: self.DOOM_GAZE, + self.PHUNBABA3: self.PHUNBABA3 + } + if self.args.boss_battles_shuffle: self.shuffle_event_bosses() elif self.args.boss_battles_random: self.randomize_event_bosses() - else: - self.event_boss_replacements = None + + self._handle_original_shuffle_dragons() + self._handle_original_shuffle_statues() if not self.args.fixed_encounters_original: self.randomize_fixed() diff --git a/data/event_bit.py b/data/event_bit.py index b8775c9d..3fab5490 100644 --- a/data/event_bit.py +++ b/data/event_bit.py @@ -1,5 +1,5 @@ # NOTE: (address - 1e80) * 0x8 + bit -# e.g. (1eb7 - 1e80) * 0x8 + 0x1 = 1b9 (airship visible) +# e.g. (1eb7 - 1e80) * 0x8 + 0x1 = 1b9 (airship visible) # (1f43 - 1e80) * 0x8 + 0x3 = 61b (characters on narshe battlefield) DISABLE_SAVE_POINT_TUTORIAL = 0x133 @@ -182,6 +182,13 @@ DEFEATED_PHOENIX_CAVE_DRAGON = 0x120 # custom DEFEATED_FANATICS_TOWER_DRAGON = 0x121 # custom +# KT Battles +DEFEATED_GUARDIAN = 0x0bc +DEFEATED_INFERNO = 0x0bd +DEFEATED_DOOM = 0x072 +DEFEATED_GODDESS = 0x073 +DEFEATED_POLTERGEIST = 0x074 + LEFT_WEIGHT_PUSHED_KEFKA_TOWER = 0x063 RIGHT_WEIGHT_PUSHED_KEFKA_TOWER = 0x064 WEST_PATH_BLOCKED_KEFKA_TOWER = 0x065 # path to doom in switch room diff --git a/event/kefka_tower.py b/event/kefka_tower.py index ba1b1264..4b31ec08 100644 --- a/event/kefka_tower.py +++ b/event/kefka_tower.py @@ -28,6 +28,11 @@ def mod(self): self.item = self.atma_reward.id self.atma_battle_mod() self.atma_mod() + self.inferno_mod() + self.guardian_mod() + self.doom_mod() + self.goddess_mod() + self.poltergeist_mod() self.inferno_battle_mod() if self.args.fix_boss_skip: @@ -243,6 +248,67 @@ def atma_battle_mod(self): field.InvokeBattle(boss_pack_id), ) + # Copy no less than 4 bytes between start_target and end_target + # This will be called after one of the kt encounters has completed, but just prior to finishing the check + def kt_encounter_objective_mod(self, boss_name, bit, start_target, end_target, description): + src = Read(start_target, end_target) + src += [ + field.SetEventBit(bit), + field.CheckObjectives(), + field.Return(), + ] + post_battle = Write(Bank['CC'], src, f"{boss_name} post-battle. 1) Set event bit. 2) Finish check") + + space = Reserve(start_target, end_target, description, asm.NOP()) + space.write([ + field.Call(post_battle.start_address) + ]) + + def guardian_mod(self): + self.kt_encounter_objective_mod( + "Guardian", + event_bit.DEFEATED_GUARDIAN, + 0xc186c, + 0xc186f, + "Guardian battle post-script, wait for fade, set bit", + ) + + def inferno_mod(self): + self.kt_encounter_objective_mod( + "Inferno", + event_bit.DEFEATED_INFERNO, + 0xc18ae, + 0xc18b1, + "Inferno battle post-script, fade in, wait, set bit", + ) + + def doom_mod(self): + self.kt_encounter_objective_mod( + "Doom", + event_bit.DEFEATED_DOOM, + 0xc16f0, + 0xc16f3, + "Doom battle post-script, Hide NPC 5, set npc bit", + ) + + def goddess_mod(self): + self.kt_encounter_objective_mod( + "Goddess", + event_bit.DEFEATED_GODDESS, + 0xc1730, + 0xc1733, + "Goddess battle post-script, Hide NPC 2, set npc bit", + ) + + def poltergeist_mod(self): + self.kt_encounter_objective_mod( + "Goddess", + event_bit.DEFEATED_POLTERGEIST, + 0xc1786, + 0xc1789, + "Poltergeist battle post-script, Hide NPCs, set npc bit", + ) + def atma_mod(self): src = [ Read(0xc18d3, 0xc18d6), # show save point, set save point npc bit diff --git a/objectives/conditions/boss.py b/objectives/conditions/boss.py index 8e465871..7a852dbf 100644 --- a/objectives/conditions/boss.py +++ b/objectives/conditions/boss.py @@ -1,14 +1,15 @@ from objectives.conditions._objective_condition import * from constants.objectives.condition_bits import boss_bit +from data.bosses import name_formation, name_pack class Condition(ObjectiveCondition): NAME = "Boss" def __init__(self, boss): self.boss = boss + self.boss_name = boss_bit[self.boss].name + self.boss_formation = name_formation[self.boss_name] + self.boss_pack = name_pack[self.boss_name] super().__init__(ConditionType.BattleBit, boss_bit[self.boss].bit) def __str__(self): - return super().__str__(self.boss) - - def boss_name(self): - return boss_bit[self.boss].name + return super().__str__(self.boss) \ No newline at end of file diff --git a/objectives/conditions/dragon.py b/objectives/conditions/dragon.py index 90c0ca84..37a36664 100644 --- a/objectives/conditions/dragon.py +++ b/objectives/conditions/dragon.py @@ -1,14 +1,16 @@ from objectives.conditions._objective_condition import * from constants.objectives.condition_bits import dragon_bit +from data.bosses import name_formation, name_pack class Condition(ObjectiveCondition): NAME = "Dragon" def __init__(self, dragon): self.dragon = dragon + self.dragon_name = dragon_bit[self.dragon].name + self.dragon_formation = name_formation[self.dragon_name] + self.dragon_pack = name_pack[self.dragon_name] super().__init__(ConditionType.BattleBit, dragon_bit[self.dragon].bit) def __str__(self): return super().__str__(self.dragon) - def dragon_name(self): - return dragon_bit[self.dragon].name From 995cc33c7b85b98e7319de4d897f5070e4c0c087 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:46:36 -0700 Subject: [PATCH 15/60] Feature: Magic MP Randomization (#15) * Adding Ultima 255 MP flag, Adding Franklin's Magic MP Randomization, Fixing display bugs with 3 digit MP, letting Lore MP randomization go to 255 MP * Allowing random esper MP cost to go up to 255 * Switching max to 254 to avoid boundary conditions * making 254 max to avoid issues with MP checks * Moving from c3 to f0 --- args/arguments.py | 2 +- args/challenges.py | 16 +++++++++-- args/espers.py | 2 +- args/log.py | 2 +- args/lores.py | 2 +- args/misc_magic.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++ data/espers.py | 2 +- data/lores.py | 2 +- data/spells.py | 53 ++++++++++++++++++++++++++++++++++++ menus/magic.py | 42 ++++++++++++++++++++++++++++ menus/menus.py | 2 ++ 11 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 args/misc_magic.py create mode 100644 menus/magic.py diff --git a/args/arguments.py b/args/arguments.py index 8766e49a..2326571f 100644 --- a/args/arguments.py +++ b/args/arguments.py @@ -6,7 +6,7 @@ def __init__(self): "objectives", "starting_party", "characters", "swdtechs", "blitzes", "lores", "rages", "dances", "steal", "sketch_control", "commands", "xpmpgp", "scaling", "bosses", "encounters", "boss_ai", - "espers", "natural_magic", + "espers", "natural_magic", "misc_magic", "starting_gold_items", "items", "shops", "chests", "graphics", "coliseum", "auction_house", "challenges", "bug_fixes", "misc", diff --git a/args/challenges.py b/args/challenges.py index 8ebbbc9f..225853fe 100644 --- a/args/challenges.py +++ b/args/challenges.py @@ -9,8 +9,11 @@ def parse(parser): help = "Exp. Eggs will not appear in coliseum/auction/shops/chests/events") challenges.add_argument("-nil", "--no-illuminas", action = "store_true", help = "Illuminas will not appear in coliseum/auction/shops/chests/events") - challenges.add_argument("-nu", "--no-ultima", action = "store_true", + ultima = challenges.add_mutually_exclusive_group() + ultima.add_argument("-nu", "--no-ultima", action = "store_true", help = "Ultima cannot be learned from espers/items/natural magic") + ultima.add_argument("-u254", "--ultima-254-mp", action = "store_true", + help = "Ultima costs 254 MP") challenges.add_argument("-nfps", "--no-free-paladin-shields", action = "store_true", help = "Paladin/Cursed Shields will not appear in coliseum/auction/shops/chests/events (Narshe WOR exclusive)") challenges.add_argument("-nfce", "--no-free-characters-espers", action = "store_true", @@ -32,6 +35,9 @@ def flags(args): flags += " -nil" if args.no_ultima: flags += " -nu" + elif args.ultima_254_mp: + flags += " -u254" + if args.no_free_paladin_shields: flags += " -nfps" if args.no_free_characters_espers: @@ -42,11 +48,17 @@ def flags(args): return flags def options(args): + ultima = "Original" + if args.no_ultima: + ultima = "No" + elif args.ultima_254_mp: + ultima = "254 MP" + return [ ("No Moogle Charms", args.no_moogle_charms), ("No Exp Eggs", args.no_exp_eggs), ("No Illuminas", args.no_illuminas), - ("No Ultima", args.no_ultima), + ("Ultima", ultima), ("No Free Paladin Shields", args.no_free_paladin_shields), ("No Free Characters/Espers", args.no_free_characters_espers), ("Permadeath", args.permadeath), diff --git a/args/espers.py b/args/espers.py index 9d097c5d..b35c5994 100644 --- a/args/espers.py +++ b/args/espers.py @@ -45,7 +45,7 @@ def parse(parser): esper_mp.add_argument("-emps", "--esper-mp-shuffle", action = "store_true", help = "Esper MP costs shuffled") esper_mp.add_argument("-emprv", "--esper-mp-random-value", default = None, type = int, - nargs = 2, metavar = ("MIN", "MAX"), choices = range(129), + nargs = 2, metavar = ("MIN", "MAX"), choices = range(255), help = "Each esper's MP cost set to random value within given range") esper_mp.add_argument("-emprp", "--esper-mp-random-percent", default = None, type = int, nargs = 2, metavar = ("MIN", "MAX"), choices = range(201), diff --git a/args/log.py b/args/log.py index 381c5466..504be767 100644 --- a/args/log.py +++ b/args/log.py @@ -19,7 +19,7 @@ def log(): args.group_modules["objectives"].log(args) _log_tab("Party", ["starting_party", "swdtechs", "blitzes", "lores", "rages", "dances", "steal", "sketch_control"], ["characters", "commands"]) _log_tab("Battle", ["xpmpgp", "bosses", "boss_ai"], ["scaling", "encounters"]) - _log_tab("Magic", ["espers"], ["natural_magic"]) + _log_tab("Magic", ["espers", "misc_magic"], ["natural_magic"]) _log_tab("Items", ["starting_gold_items", "items"], ["shops", "chests"]) args.group_modules["graphics"].log(args) _log_tab("Other", ["coliseum", "auction_house", "misc"], ["challenges", "bug_fixes"]) diff --git a/args/lores.py b/args/lores.py index 04ab7c8a..b7db179c 100644 --- a/args/lores.py +++ b/args/lores.py @@ -14,7 +14,7 @@ def parse(parser): lores_mp.add_argument("-lmps", "--lores-mp-shuffle", action = "store_true", help = "Lore MP costs shuffled") lores_mp.add_argument("-lmprv", "--lores-mp-random-value", default = None, type = int, - nargs = 2, metavar = ("MIN", "MAX"), choices = range(100), + nargs = 2, metavar = ("MIN", "MAX"), choices = range(255), help = "Lore MP costs randomized") lores_mp.add_argument("-lmprp", "--lores-mp-random-percent", default = None, type = int, nargs = 2, metavar = ("MIN", "MAX"), choices = range(201), diff --git a/args/misc_magic.py b/args/misc_magic.py new file mode 100644 index 00000000..e4250d57 --- /dev/null +++ b/args/misc_magic.py @@ -0,0 +1,68 @@ +def name(): + return "Misc. Magic" + +def parse(parser): + magic = parser.add_argument_group("Misc. Magic") + + magic_mp = magic.add_mutually_exclusive_group() + magic_mp.add_argument("-mmps", "--magic-mp-shuffle", action = "store_true", + help = "Magic spells' MP costs shuffled") + magic_mp.add_argument("-mmprv", "--magic-mp-random-value", default = None, type = int, + nargs = 2, metavar = ("MIN", "MAX"), choices = range(255), + help = "Magic spells' MP costs randomized") + magic_mp.add_argument("-mmprp", "--magic-mp-random-percent", default = None, type = int, + nargs = 2, metavar = ("MIN", "MAX"), choices = range(201), + help = "Each Magic spell's MP cost set to random percent of original within given range") + +def process(args): + args._process_min_max("magic_mp_random_value") + args._process_min_max("magic_mp_random_percent") + +def flags(args): + flags = "" + + if args.magic_mp_shuffle: + flags += " -mmps" + elif args.magic_mp_random_value: + flags += f" -mmprv {args.magic_mp_random_value_min} {args.magic_mp_random_value_max}" + elif args.magic_mp_random_percent: + flags += f" -mmprp {args.magic_mp_random_percent_min} {args.magic_mp_random_percent_max}" + + return flags + +def options(args): + + mp = "Original" + if args.magic_mp_shuffle: + mp = "Shuffle" + elif args.magic_mp_random_value: + mp = f"Random Value {args.magic_mp_random_value_min}-{args.magic_mp_random_value_max}" + elif args.magic_mp_random_percent: + mp = f"Random Percent {args.magic_mp_random_percent_min}-{args.magic_mp_random_percent_max}%" + + return [ + ("MP", mp), + ] + +def menu(args): + entries = options(args) + for index, entry in enumerate(entries): + key, value = entry + try: + if key == "MP": + value = value.replace("Random Value ", "") + value = value.replace("Random Percent ", "") + entries[index] = (key, value) + except: + pass + return (name(), entries) + +def log(args): + from log import format_option + log = [name()] + + entries = options(args) + for entry in entries: + log.append(format_option(*entry)) + + return log diff --git a/data/espers.py b/data/espers.py index 8f0007f4..df2259f2 100644 --- a/data/espers.py +++ b/data/espers.py @@ -207,7 +207,7 @@ def random_mp_percent(self): mp_percent = random.randint(self.args.esper_mp_random_percent_min, self.args.esper_mp_random_percent_max) / 100.0 value = int(esper.mp * mp_percent) - esper.mp = max(min(value, 255), 1) + esper.mp = max(min(value, 254), 1) def equipable_random(self): from data.characters import Characters diff --git a/data/lores.py b/data/lores.py index 842bbc7c..d1f6b60d 100644 --- a/data/lores.py +++ b/data/lores.py @@ -158,7 +158,7 @@ def random_mp_percent(self): mp_percent = random.randint(self.args.lores_mp_random_percent_min, self.args.lores_mp_random_percent_max) / 100.0 value = int(lore.mp * mp_percent) - lore.mp = max(min(value, 255), 0) + lore.mp = max(min(value, 254), 0) def mod(self): self.write_learners_table() diff --git a/data/spells.py b/data/spells.py index 281ef00a..7e39af0e 100644 --- a/data/spells.py +++ b/data/spells.py @@ -53,11 +53,53 @@ def no_mp_scan(self): scan_id = name_id["Scan"] self.spells[scan_id].mp = 0 + def ultima_254_mp(self): + ultima_id = name_id["Ultima"] + self.spells[ultima_id].mp = 254 + + def shuffle_mp(self): + mp = [] + for spell in self.spells: + mp.append(spell.mp) + + import random + random.shuffle(mp) + for spell in self.spells: + spell.mp = mp.pop() + + def random_mp_value(self): + import random + for spell in self.spells: + spell.mp = random.randint(self.args.magic_mp_random_value_min, self.args.magic_mp_random_value_max) + + def random_mp_percent(self): + import random + for spell in self.spells: + mp_percent = random.randint(self.args.magic_mp_random_percent_min, + self.args.magic_mp_random_percent_max) / 100.0 + value = int(spell.mp * mp_percent) + spell.mp = max(min(value, 254), 0) + def mod(self): + if self.args.magic_mp_shuffle: + self.shuffle_mp() + elif self.args.magic_mp_random_value: + self.random_mp_value() + elif self.args.magic_mp_random_percent: + self.random_mp_percent() + + # Apply No MP Scan after any MP shuffle/rando if self.args.scan_all: self.no_mp_scan() + # Apply Ultima 254 MP after any MP shuffle/rando + if self.args.ultima_254_mp: + self.ultima_254_mp() + def write(self): + if self.args.spoiler_log: + self.log() + for spell_index, spell in enumerate(self.spells): self.name_data[spell_index] = spell.name_data() self.ability_data[spell_index] = spell.ability_data() @@ -65,6 +107,17 @@ def write(self): self.name_data.write() self.ability_data.write() + def log(self): + from log import section + + lcolumn = [] + for spell in self.spells: + spell_name = spell.get_name() + + lcolumn.append(f"{spell_name:<{self.NAME_SIZE}} {spell.mp:>3} MP") + + section("Spells", lcolumn, []) + def print(self): for spell in self.spells: spell.print() diff --git a/menus/magic.py b/menus/magic.py new file mode 100644 index 00000000..af5e1ff9 --- /dev/null +++ b/menus/magic.py @@ -0,0 +1,42 @@ +from memory.space import Write, Bank, Reserve +import instruction.asm as asm +import args + +class MagicMenu: + def __init__(self): + self.mod() + + def draw_three_digits(self): + # Enable drawing of 3 digits + # Create string function + STRING_DRAW_ADDR = 0x2180 # Where to write strings to be written + src = [ + asm.LDA(0xF7, asm.DIR), # Hundreds digit + asm.STA(STRING_DRAW_ADDR, asm.ABS), # Add to string + # displaced vanilla logic, from C3/51E9 - 51ED + asm.LDA(0xF8, asm.DIR), # Tens digit + asm.STA(STRING_DRAW_ADDR, asm.ABS), # Add to string + asm.RTL() + ] + space = Write(Bank.F0, src, "Create MP Cost string") + create_string = space.start_address_snes + + space = Reserve(0x351e9, 0x351ed, "Call create_string", asm.NOP()) + space.write( + asm.JSL(create_string), + ) + + # Move where MP gets written 1 space to the left, + # to avoid having the number show up at the top of the "Espers" menu + space = Reserve(0x351cd, 0x351cd, "MP String location") + space.write(0xbd) #original: 0xbf (each text space is a value of 2) + + def fix_in_battle_mp_tens_digit(self): + # Fix Vanilla in-battle MP listing in which the ten's digit is blanked + # if it is 0 but the hundreds digit is not + space = Reserve(0x1057b, 0x1057b, "MP Hundreds non-zero BNE offset") + space.write(0x14) # original: 0x08; 0x14 causes it to jump to RTS if the hundreds place is non-zero + + def mod(self): + self.draw_three_digits() + self.fix_in_battle_mp_tens_digit() diff --git a/menus/menus.py b/menus/menus.py index 9ac749a9..76b13cc6 100644 --- a/menus/menus.py +++ b/menus/menus.py @@ -6,6 +6,7 @@ import menus.final_lineup as final_lineup import menus.coliseum as coliseum import menus.sell as sell +import menus.magic as magic class Menus: def __init__(self, characters, dances): @@ -20,6 +21,7 @@ def __init__(self, characters, dances): self.final_lineup_menu = final_lineup.FinalLineupMenu(self.characters) self.coliseum_menu = coliseum.ColiseumMenu() self.sell_menu = sell.SellMenu() + self.magic_menu = magic.MagicMenu() self.scrollbar_bugfix() From f6a6fd485e8ee4c941c6e1cff72137b1166f6ad4 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:47:03 -0700 Subject: [PATCH 16/60] Adding --starting-level flag (#14) --- args/characters.py | 5 +++++ data/characters.py | 9 +++++---- data/characters_asm.py | 4 ++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/args/characters.py b/args/characters.py index f80634c1..4cb482fa 100644 --- a/args/characters.py +++ b/args/characters.py @@ -6,6 +6,8 @@ def parse(parser): characters.add_argument("-sal", "--start-average-level", action = "store_true", help = "Recruited characters start at the average character level") + characters.add_argument("-stl", "--start-level", default = 3, type = int, choices = range(3, 100), metavar = "COUNT", + help = "Start game at level %(metavar)s.") characters.add_argument("-sn", "--start-naked", action = "store_true", help = "Recruited characters start with no equipment") characters.add_argument("-eu", "--equipable-umaro", action = "store_true", @@ -22,6 +24,8 @@ def flags(args): if args.start_average_level: flags += " -sal" + if args.start_level != 3: + flags += f" -stl {args.start_level}" if args.start_naked: flags += " -sn" if args.equipable_umaro: @@ -36,6 +40,7 @@ def options(args): return [ ("Start Average Level", args.start_average_level), + ("Start Level", args.start_level), ("Start Naked", args.start_naked), ("Equipable Umaro", args.equipable_umaro), ("Character Stats", character_stats), diff --git a/data/characters.py b/data/characters.py index 5476e9b3..31990fa5 100644 --- a/data/characters.py +++ b/data/characters.py @@ -78,10 +78,11 @@ def get_character_path(self, character): return self.character_paths[character] def mod_init_levels(self): - if self.args.start_average_level: - # characters recruited at average level, set everyone's initial level to 3 - for character in self.characters: - character.init_level_factor = 0 + # remove all variation in leveling, since we're controlling level directly + for character in self.characters: + character.init_level_factor = 0 + + characters_asm.set_starting_level(self.args.start_level) def stats_random_percent(self): import random diff --git a/data/characters_asm.py b/data/characters_asm.py index 0d501bc3..3698a3b1 100644 --- a/data/characters_asm.py +++ b/data/characters_asm.py @@ -7,6 +7,10 @@ def equipable_umaro(character_count): space = Reserve(0x39ef4, 0x39ef7, "Reequip Umaro if genji glove/gauntlet/merit award equipped/removed", asm.NOP()) +def set_starting_level(start_level): + space = Reserve(0x09fc6, 0x09fc6, "Starting level") + space.write(start_level.to_bytes(1, 'little')) + def update_morph_character(characters): from constants.commands import id_name From ccc66f451cec4e90889bdbdcca2ebb69870608eb Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:47:27 -0700 Subject: [PATCH 17/60] QoL: Adding Rage Move descriptions to rage menu (#13) * Initial commit of Rage ability names in menu * Adding logic to display Special rage names * Adding logic for Special (0xef) attacks * Rewrite of Rage Description to use custom string table that includes more move details * More details in rage desc; handling special cases * Simplifying rage strings --- data/rages.py | 10 ++++ menus/menus.py | 6 ++- menus/rage.py | 138 +++++++++++++++++++++++++++++++++++++++++++++++++ wc.py | 2 +- 4 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 menus/rage.py diff --git a/data/rages.py b/data/rages.py index 71795ac9..504e97a7 100644 --- a/data/rages.py +++ b/data/rages.py @@ -1,5 +1,6 @@ from data.rage import Rage from data.structures import DataBits, DataArray +from data.ability_data import AbilityData class Rages(): RAGE_COUNT = 256 # 255 available @@ -15,6 +16,9 @@ class Rages(): ATTACKS_DATA_END = 0xf47ff ATTACKS_DATA_SIZE = 2 + ABILITY_DATA_START = 0x046ac0 + ABILITY_DATA_END = 0x0478bf + def __init__(self, rom, args, enemies): self.rom = rom self.args = args @@ -22,12 +26,18 @@ def __init__(self, rom, args, enemies): self.init_data = DataBits(self.rom, self.INITIAL_RAGES_START, self.INITIAL_RAGES_END) self.attack_data = DataArray(self.rom, self.ATTACKS_DATA_START, self.ATTACKS_DATA_END, self.ATTACKS_DATA_SIZE) + self.ability_data = DataArray(self.rom, self.ABILITY_DATA_START, self.ABILITY_DATA_END, AbilityData.DATA_SIZE) self.rages = [] for rage_index in range(len(self.attack_data)): rage = Rage(rage_index, self.attack_data[rage_index]) self.rages.append(rage) + self.abilities = [] + for ability_index in range(len(self.ability_data)): + ability = AbilityData(ability_index, self.ability_data[ability_index]) + self.abilities.append(ability) + def start_random_rages(self): import random diff --git a/menus/menus.py b/menus/menus.py index 76b13cc6..db145c64 100644 --- a/menus/menus.py +++ b/menus/menus.py @@ -2,6 +2,7 @@ import menus.pregame as pregame import menus.track as track import menus.dance as dance +import menus.rage as rage import menus.status as status import menus.final_lineup as final_lineup import menus.coliseum as coliseum @@ -9,14 +10,17 @@ import menus.magic as magic class Menus: - def __init__(self, characters, dances): + def __init__(self, characters, dances, rages, enemies): self.characters = characters self.dances = dances + self.rages = rages + self.enemies = enemies self.pregame_track = pregame_track.PreGameTrack(self.characters) self.pregame_menu = pregame.PreGameMenu(self.pregame_track) self.track_menu = track.TrackMenu(self.pregame_track) self.dance_menu = dance.DanceMenu(self.dances) + self.rage_menu = rage.RageMenu(self.rages, self.enemies) self.status_menu = status.StatusMenu(self.characters) self.final_lineup_menu = final_lineup.FinalLineupMenu(self.characters) self.coliseum_menu = coliseum.ColiseumMenu() diff --git a/menus/rage.py b/menus/rage.py new file mode 100644 index 00000000..71a51dce --- /dev/null +++ b/menus/rage.py @@ -0,0 +1,138 @@ +from memory.space import Bank, Reserve, Allocate, Write +import instruction.asm as asm +from data.spell_names import name_id, id_name + + +class RageMenu: + def __init__(self, rages, enemies): + self.rages = rages + self.enemies = enemies + + # Build an array to lookup enemy-specific Special command effects + from constants.status_effects import A, B, C, D + self.status_effects = [] + self.status_effects.extend(list(A.id_name.values())) + self.status_effects.extend(list(B.id_name.values())) + self.status_effects.extend(list(C.id_name.values())) + self.status_effects.extend(list(D.id_name.values())) + + # Remove death from the status effects list, as it requires a second bit from flags1 + self.status_effects = list(map(lambda x: x.replace("Death", ""), self.status_effects)) + + # Remove other statuses that aren't really relevant + self.status_effects = list(map(lambda x: x.replace("Near Fatal", ""), self.status_effects)) + self.status_effects = list(map(lambda x: x.replace("Hide", ""), self.status_effects)) + + self.special_effects = [] + self.special_effects.extend(self.status_effects) + for i in range(15, 95, 5): # going from 1.5x - 9.0x damage + dmg_multiplier = i / 10 + self.special_effects.append(f"{dmg_multiplier:.1f}x dmg") + self.special_effects.append("Drain HP") + self.special_effects.append("Drain MP") + self.special_effects.append("Remove Reflect") + + self.mod() + + def get_rage_string(self, id, attack_id): + from data.spell_names import id_name, name_id + + if attack_id == name_id["Special"]: + # handle special name lookup + special attack info (dmg multipler, status effect) + enemy = self.enemies.enemies[id] + special_name = enemy.special_name + special_effect = enemy.special_effect + + rage_str = f"{special_name}: " + rage_str += self.special_effects[special_effect] + else: + rage_str = f"{id_name[attack_id]}" + + # # remove duplicate white spaces + import re + rage_str = re.sub(' +', ' ', rage_str) + + # remove leading and trailing spaces + rage_str = rage_str.strip() + return rage_str + + def draw_ability_names_mod(self): + import data.text as text + + # Get the custom strings for each rage to be written to the ROM + lines = [] + for rage in self.rages.rages: + # Only focusing on attack2, as attack1 is simply "Battle" -- if that changes in the future, this string can be revisited + rage_str = f"{self.get_rage_string(rage.id, rage.attack2)}" + lines.append(rage_str) + + line_offsets = [0] + running_offset = 0 + # Write the lines to F0 + src = [] + for line in lines: + # convert to bytes + bytes = text.get_bytes(line, text.TEXT3) + running_offset += len(bytes) + line_offsets.append(running_offset) + src.append(bytes) + space = Write(Bank.F0, src, "rage description lines table") + lines_table = space.start_address_snes + + # write the 2-byte line offsets to F0 + src = [] + for offset in line_offsets: + src.append(offset.to_bytes(2, 'little')) + space = Write(Bank.F0, src, "rage description lines table offsets") + lines_table_offsets = space.start_address_snes + + src = [ + asm.LDX(0x9ec9, asm.IMM16), # dest WRAM LBs + asm.STX(0x2181, asm.ABS), # store dest WRAM LBs + + asm.TDC(), # a = 0x0000 + asm.LDA(0x4b, asm.DIR), # a = cursor index (rage index) + asm.TAX(), # x = cursor index (rage index) + asm.LDA(0x7e9d89, asm.LNG_X), # a = rage at cursor index + asm.CMP(0xff, asm.IMM8), # compare with no rage + asm.BEQ("END_STRING_RETURN"), # branch if rage at cursor index not learned + asm.A16(), + asm.ASL(), # a = rage index * 2 (2 bytes per table offset) + asm.TAX(), # x = rage index * 2 + asm.LDA(lines_table_offsets, asm.LNG_X), # get the offset + asm.TAX(), + asm.A8(), + "STRING_LOOP_START", + asm.LDA(lines_table, asm.LNG_X), # get the character + asm.STA(0x2180, asm.ABS), # add character to string + asm.CMP(0x00, asm.IMM8), # was it the end of the string? + asm.BEQ("RETURN"), # if so, be done + asm.INX(), # move to next character in ability name + asm.BRA("STRING_LOOP_START"), + "END_STRING_RETURN", + asm.STZ(0x2180, asm.ABS), # end string + "RETURN", + asm.RTS(), + ] + space = Write(Bank.C3, src, "draw ability names") + draw_ability_names = space.start_address + + sustain_replace = 0x328c6 # handle L and R + replace_size = 3 # replacing jsr instructions + + src = [ + asm.LDA(0x10, asm.IMM8),# enable description menu flag bitmask + asm.TRB(0x45, asm.DIR), # enable descriptions + asm.JSR(0x4c52, asm.ABS), # displaced code: handle D-Pad + asm.JMP(draw_ability_names, asm.ABS), + ] + space = Write(Bank.C3, src, "sustain rage list") + sustain_rage_list = space.start_address + + space = Reserve(sustain_replace, sustain_replace + replace_size - 1, "rage menu sustain handle D-Pad") + space.write( + asm.JSR(sustain_rage_list, asm.ABS), + ) + + def mod(self): + self.draw_ability_names_mod() \ No newline at end of file diff --git a/wc.py b/wc.py index a6d8ea49..bff7d690 100644 --- a/wc.py +++ b/wc.py @@ -12,7 +12,7 @@ def main(): events = Events(memory.rom, args, data) from menus.menus import Menus - menus = Menus(data.characters, data.dances) + menus = Menus(data.characters, data.dances, data.rages, data.enemies) from battle import Battle battle = Battle() From a134980d962689e22b4062f0896a09d20e45968c Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:47:54 -0700 Subject: [PATCH 18/60] Feature: Add Remove learnable spells flag + Submenus (#12) * All original remove learnable spells changes * Making Life 3 learnable in permadeath seeds * Fixing bug where Natural Magic users can learn Fire even if excluded * correcting natural magic learning with excluded commands * Fixing seed gen error when there's not enough spells available for randomization * Fixing possibility for long-branches with many submenus --- args/challenges.py | 64 +++++++- constants/spells.py | 4 + data/esper.py | 17 ++ data/espers.py | 41 ++--- data/items.py | 5 +- data/natural_magic.py | 53 +++--- data/spells.py | 11 ++ instruction/c3.py | 24 +++ menus/flags.py | 8 + menus/flags_remove_learnable_spells.py | 55 +++++++ menus/pregame.py | 67 +++++--- menus/pregame_track.py | 216 +++++++++++++++++++------ menus/pregame_track_scroll_area.py | 31 ++-- menus/track.py | 46 ++++-- objectives/results/forget_spells.py | 128 ++++++++------- objectives/results/learn_spells.py | 126 ++++++++------- 16 files changed, 619 insertions(+), 277 deletions(-) create mode 100644 instruction/c3.py create mode 100644 menus/flags_remove_learnable_spells.py diff --git a/args/challenges.py b/args/challenges.py index 225853fe..50acd3eb 100644 --- a/args/challenges.py +++ b/args/challenges.py @@ -20,9 +20,44 @@ def parse(parser): help = "Remove character/esper rewards from: Auction House, Collapsing House, Figaro Castle Throne, Gau's Father's House, Kohlingen Inn, Narshe Weapon Shop, Sealed Gate, South Figaro Basement") challenges.add_argument("-pd", "--permadeath", action = "store_true", help = "Life spells cannot be learned. Fenix Downs unavailable (except from starting items). Buckets/inns/tents/events do not revive characters. Phoenix casts Life 3 on party instead of Life") + challenges.add_argument("-rls", "--remove-learnable-spells", type = str, + help = "Remove spells from learnable sources: Items, Espers, Natural Magic, and Objectives") def process(args): - pass + from constants.spells import black_magic_ids, white_magic_ids, gray_magic_ids, spell_id + + # If no_ultima is on, add it to our exclude list for downstream use + # If permadeath is on, add it to our exclude list for downstream use + args.remove_learnable_spell_ids = [] + if args.no_ultima: + args.remove_learnable_spell_ids.append(spell_id["Ultima"]) + if args.permadeath: + args.remove_learnable_spell_ids.append(spell_id["Life"]) + args.remove_learnable_spell_ids.append(spell_id["Life 2"]) + + if args.remove_learnable_spells: + # Split the comma-separated string + for a_spell_id in args.remove_learnable_spells.split(','): + # look for strings first + a_spell_id = a_spell_id.lower().strip() + if a_spell_id == 'all': + args.remove_learnable_spell_ids.extend(range(len(spell_id))) + elif a_spell_id == 'white': + args.remove_learnable_spell_ids.extend(white_magic_ids) + elif a_spell_id == 'black': + args.remove_learnable_spell_ids.extend(black_magic_ids) + elif a_spell_id == 'gray' or a_spell_id == 'grey': + args.remove_learnable_spell_ids.extend(gray_magic_ids) + else: + spell_ids_lower = {k.lower():v for k,v in spell_id.items()} + if a_spell_id in spell_ids_lower: + args.remove_learnable_spell_ids.append(spell_ids_lower[a_spell_id]) + else: + # assuming it's a number... it'll error out if not + args.remove_learnable_spell_ids.append(int(a_spell_id)) + # remove duplicates and sort + args.remove_learnable_spell_ids = list(set(args.remove_learnable_spell_ids)) + args.remove_learnable_spell_ids.sort() def flags(args): flags = "" @@ -44,6 +79,8 @@ def flags(args): flags += " -nfce" if args.permadeath: flags += " -pd" + if args.remove_learnable_spells: + flags += f" -rls {args.remove_learnable_spells}" return flags @@ -62,9 +99,19 @@ def options(args): ("No Free Paladin Shields", args.no_free_paladin_shields), ("No Free Characters/Espers", args.no_free_characters_espers), ("Permadeath", args.permadeath), + ("Remove Learnable Spells", args.remove_learnable_spell_ids), ] +def _format_spells_log_entries(spell_ids): + from constants.spells import id_spell + spell_entries = [] + for spell_id in spell_ids: + spell_entries.append(("", id_spell[spell_id])) + return spell_entries + def menu(args): + from menus.flags_remove_learnable_spells import FlagsRemoveLearnableSpells + entries = options(args) for index, entry in enumerate(entries): key, value = entry @@ -72,6 +119,9 @@ def menu(args): entries[index] = ("No Free Paladin Shlds", entry[1]) elif key == "No Free Characters/Espers": entries[index] = ("No Free Chars/Espers", entry[1]) + elif key == "Remove Learnable Spells": + entries[index] = ("Remove L. Spells", FlagsRemoveLearnableSpells(value)) # flags sub-menu + return (name(), entries) def log(args): @@ -80,6 +130,16 @@ def log(args): entries = options(args) for entry in entries: - log.append(format_option(*entry)) + key, value = entry + if key == "Remove Learnable Spells": + if len(value) == 0: + entry = (key, "None") + else: + entry = (key, "") # The entries will show up on subsequent lines + log.append(format_option(*entry)) + for spell_entry in _format_spells_log_entries(value): + log.append(format_option(*spell_entry)) + else: + log.append(format_option(*entry)) return log diff --git a/constants/spells.py b/constants/spells.py index 6c245f44..a1c0a4e0 100644 --- a/constants/spells.py +++ b/constants/spells.py @@ -55,3 +55,7 @@ 53 : "Life 3", } spell_id = {v: k for k, v in id_spell.items()} + +black_magic_ids = range(0, 24) +gray_magic_ids = range(24, 45) +white_magic_ids = range(45, 54) \ No newline at end of file diff --git a/data/esper.py b/data/esper.py index 401db6d4..96d9f62a 100644 --- a/data/esper.py +++ b/data/esper.py @@ -92,6 +92,23 @@ def remove_spell(self, spell): for spell_index in range(spells_removed): self.spells.append(self.SpellEntry(self.NO_SPELL, 0)) + def get_spell_ids(self): + return [spell.id for spell in self.spells if spell.id != self.NO_SPELL] + + def replace_spell(self, old_spell, new_spell): + if(old_spell == self.NO_SPELL): + return + + # get the old learn rate to reuse it + learn_rate = self.LEARN_RATES[0] + for spell_index in range(self.SPELL_COUNT): + if self.spells[spell_index].id == old_spell: + learn_rate = self.spells[spell_index].rate + + self.remove_spell(old_spell) + if new_spell is not None: + self.add_spell(new_spell, learn_rate) + def clear_spells(self): for spell_index in range(self.SPELL_COUNT): self.spells[spell_index].id = self.NO_SPELL diff --git a/data/espers.py b/data/espers.py index df2259f2..0b595398 100644 --- a/data/espers.py +++ b/data/espers.py @@ -146,23 +146,23 @@ def get_spell(): learn_rate = Esper.LEARN_RATES[learn_rate_index] esper.add_spell(get_spell(), learn_rate) - def remove_all_ultima(self): - ultima_id = self.spells.get_id("Ultima") - for esper in self.espers: - if esper.has_spell(ultima_id): - esper.remove_spell(ultima_id) + def remove_flagged_learnables(self): + for a_spell_id in self.args.remove_learnable_spell_ids: + for esper in self.espers: + if(esper.has_spell(a_spell_id)): + esper.remove_spell(a_spell_id) - def remove_all_life(self): - life_id = self.spells.get_id("Life") - life2_id = self.spells.get_id("Life 2") - life3_id = self.spells.get_id("Life 3") + def replace_flagged_learnables(self): for esper in self.espers: - if esper.has_spell(life_id): - esper.remove_spell(life_id) - if esper.has_spell(life2_id): - esper.remove_spell(life2_id) - if esper.has_spell(life3_id): - esper.remove_spell(life3_id) + for a_spell_id in self.args.remove_learnable_spell_ids: + if(esper.has_spell(a_spell_id)): + # Also exclude spells this Esper already knows, to avoid duplicates + exclude_spell_ids = self.args.remove_learnable_spell_ids.copy() + exclude_spell_ids.extend(esper.get_spell_ids()) + + new_spell_id = self.spells.get_replacement(a_spell_id, exclude = exclude_spell_ids) + esper.replace_spell(a_spell_id, new_spell_id) + def clear_spells(self): for esper in self.espers: @@ -286,9 +286,6 @@ def mod(self, dialogs): elif self.args.esper_spells_random_tiered: self.randomize_spells_tiered() - if self.args.no_ultima: - self.remove_all_ultima() - if self.args.esper_bonuses_shuffle: self.shuffle_bonuses() elif self.args.esper_bonuses_random: @@ -308,9 +305,15 @@ def mod(self, dialogs): espers_asm.equipable_mod(self) if self.args.permadeath: - self.remove_all_life() self.phoenix_life3() + if self.args.esper_spells_random or self.args.esper_spells_random_tiered: + # if random, replace the spells + self.replace_flagged_learnables() + else: + # otherwise (original or shuffled), remove them + self.remove_flagged_learnables() + if self.args.esper_multi_summon: self.multi_summon() diff --git a/data/items.py b/data/items.py index 42b4c7a2..e625868d 100644 --- a/data/items.py +++ b/data/items.py @@ -220,9 +220,8 @@ def mod(self): elif self.args.shop_prices_random_percent: self.random_prices_percent() - if self.args.no_ultima: - from data.spell_names import name_id as spell_name_id - self.remove_learnable_spell(spell_name_id["Ultima"]) + for a_spell_id in self.args.remove_learnable_spell_ids: + self.remove_learnable_spell(a_spell_id) if self.args.cursed_shield_battles_original: self.cursed_shield_battles = 256 diff --git a/data/natural_magic.py b/data/natural_magic.py index 15d65007..17a2a48f 100644 --- a/data/natural_magic.py +++ b/data/natural_magic.py @@ -146,43 +146,29 @@ def mod_learners(self): self.after_battle_check_mod() def randomize_spells1(self): - exclude = [] - if self.args.no_ultima: - exclude.append(self.spells.get_id("Ultima")) - if self.args.permadeath: - exclude.append(self.spells.get_id("Life")) - exclude.append(self.spells.get_id("Life 2")) - exclude.append(self.spells.get_id("Life 3")) - - random_spells = self.spells.get_random(count = len(self.terra_spells), exclude = exclude) + random_spells = self.spells.get_random(count = len(self.terra_spells), exclude = self.args.remove_learnable_spell_ids) for index, spell in enumerate(random_spells): self.terra_spells[index].spell = spell def randomize_spells2(self): - exclude = [] - if self.args.no_ultima: - exclude.append(self.spells.get_id("Ultima")) - if self.args.permadeath: - exclude.append(self.spells.get_id("Life")) - exclude.append(self.spells.get_id("Life 2")) - exclude.append(self.spells.get_id("Life 3")) - - random_spells = self.spells.get_random(count = len(self.celes_spells), exclude = exclude) + random_spells = self.spells.get_random(count = len(self.celes_spells), exclude = self.args.remove_learnable_spell_ids) for index, spell in enumerate(random_spells): self.celes_spells[index].spell = spell - def remove_natural_life(self): - life1_index = 4 - life2_index = 10 - self.terra_spells[life1_index].spell = 0 - self.terra_spells[life1_index].level = 0 + def remove_excluded(self): + for a_spell_id in self.args.remove_learnable_spell_ids: - self.terra_spells[life2_index].spell = 0 - self.terra_spells[life2_index].level = 0 + #linear search through Terra's spells + for terra_spell in self.terra_spells: + if terra_spell.spell == a_spell_id: + terra_spell.spell = 0xFF + terra_spell.level = 0 - def remove_natural_ultima(self): - self.terra_spells[-1].spell = 0 - self.terra_spells[-1].level = 0 + #linear search through Celes' spells + for celes_spell in self.celes_spells: + if celes_spell.spell == a_spell_id: + celes_spell.spell = 0xFF + celes_spell.level = 0 def randomize_levels1(self): import random @@ -208,13 +194,6 @@ def mod(self): self.randomize_levels1() if self.args.random_natural_spells1: self.randomize_spells1() - else: - if self.args.no_ultima: - # not randomizing spells terra normally learns but no ultima chosen so remove it - self.remove_natural_ultima() - if self.args.permadeath: - # not randomizing spells terra normally learns but permadeath chosen so remove life spells - self.remove_natural_life() if self.args.natural_magic2: if self.args.random_natural_levels2: @@ -222,6 +201,10 @@ def mod(self): if self.args.random_natural_spells2: self.randomize_spells2() + # Remove any excluded spells remaining. + # As randomize_spells respects the exclusion list, this should only have the effect of removing any excluded non-random spells. + self.remove_excluded() + def log(self): from log import section, format_option diff --git a/data/spells.py b/data/spells.py index 7e39af0e..459a49c4 100644 --- a/data/spells.py +++ b/data/spells.py @@ -47,8 +47,19 @@ def get_random(self, count = 1, exclude = None): import random possible_spell_ids = [spell.id for spell in self.spells if spell.id not in exclude] + count = min(len(possible_spell_ids), count) return random.sample(possible_spell_ids, count) + def get_replacement(self, spell_id, exclude): + ''' get a random spell from the same tier as the given spell_id ''' + import random + from data.esper_spell_tiers import tiers + + same_tier = next((tier for tier in tiers if spell_id in tier), []) + replacements = [i for i in same_tier if i not in exclude] + replacement = random.choice(replacements) if len(replacements) else None + return replacement + def no_mp_scan(self): scan_id = name_id["Scan"] self.spells[scan_id].mp = 0 diff --git a/instruction/c3.py b/instruction/c3.py new file mode 100644 index 00000000..0e10ecc0 --- /dev/null +++ b/instruction/c3.py @@ -0,0 +1,24 @@ +from memory.space import Bank, START_ADDRESS_SNES, Space, Reserve, Allocate, Free, Write +import instruction.asm as asm + +# Allow Eggers jumps into C3 -- that is, enable calls to JSR routines from other banks +# Ref: https://www.ff6hacking.com/forums/thread-2301.html +def _eggers_jump_return_mod(): + src = [ + asm.RTS(), + asm.RTL() + ] + space = Write(Bank.C3, src, "C3 eggers jump return") + return space.start_address +eggers_jump_return = _eggers_jump_return_mod() + +# Eggers jump src to jump to the specified C3 subroutine and successfully return to another bank +def eggers_jump(c3addr): + src = [ + asm.PHK(), + asm.PER(0x0009), + asm.PEA(eggers_jump_return), + asm.PEA(c3addr-1), # return after execution + asm.JMP(eggers_jump_return + START_ADDRESS_SNES, asm.LNG), + ] + return src \ No newline at end of file diff --git a/menus/flags.py b/menus/flags.py index 206b3741..4b390d83 100644 --- a/menus/flags.py +++ b/menus/flags.py @@ -4,12 +4,14 @@ import args import menus.pregame_track_scroll_area as scroll_area +from data.text.text2 import text_value class Flags(scroll_area.ScrollArea): MENU_NUMBER = 12 def __init__(self): self.lines = [] + self.submenus = {} # dictionary of submenus. key = line number, value = ScrollArea derived class for _, group in args.group_modules.items(): if hasattr(group, "menu"): name, options = group.menu(args) @@ -19,6 +21,12 @@ def __init__(self): key, value = option key = " " + key.replace("&", "+") + + # if we're given a scroll area, save it as a sub-menu with a value of X …, where X is the number of items in the sub-menu + if isinstance(value, scroll_area.ScrollArea): + self.submenus[len(self.lines)] = value + value = f"{value.number_items} {chr(text_value['…'])}" + value = str(value) if value == "True": value = "T" diff --git a/menus/flags_remove_learnable_spells.py b/menus/flags_remove_learnable_spells.py new file mode 100644 index 00000000..8bb4eb21 --- /dev/null +++ b/menus/flags_remove_learnable_spells.py @@ -0,0 +1,55 @@ +import menus.pregame_track_scroll_area as scroll_area +from data.text.text2 import text_value +import instruction.f0 as f0 + +class FlagsRemoveLearnableSpells(scroll_area.ScrollArea): + MENU_NUMBER = 15 + + def __init__(self, spell_ids): + self.number_items = len(spell_ids) + self.lines = [] + + self.lines.append(scroll_area.Line(f"Remove Learnable Spells", f0.set_blue_text_color)) + + spell_lines = FlagsRemoveLearnableSpells._format_spells_menu(spell_ids) + + for list_value in spell_lines: + padding = scroll_area.WIDTH - (len(list_value)) + self.lines.append(scroll_area.Line(f"{' ' * padding}{list_value}", f0.set_user_text_color)) + + super().__init__() + + def _format_spells_menu(spell_ids): + from constants.spells import id_spell + COLUMN_WIDTHS = [8, 8, 8] + spell_lines = [] + + # Step through each spell by the number of columns + for spell_idx in range(0, len(spell_ids), len(COLUMN_WIDTHS)): + current_line = '' + # Populate each column on the line + for col in range(0, len(COLUMN_WIDTHS)): + if(spell_idx + col < len(spell_ids)): + a_spell_id = spell_ids[spell_idx + col] + icon = FlagsRemoveLearnableSpells._get_spell_icon(a_spell_id) + spell_str = f"{icon}{id_spell[a_spell_id]}" + padding = COLUMN_WIDTHS[col] - len(spell_str) + current_line += f"{spell_str}{' ' * padding}" + else: + # No spell, add padding + current_line += f"{' ' * COLUMN_WIDTHS[col]}" + # Write the line + spell_lines.append(current_line) + return spell_lines + + def _get_spell_icon(spell_id): + from constants.spells import black_magic_ids, gray_magic_ids, white_magic_ids + from data.text.text2 import text_value + icon = '' + if spell_id in black_magic_ids: + icon = chr(text_value['']) + elif spell_id in gray_magic_ids: + icon = chr(text_value['']) + elif spell_id in white_magic_ids: + icon = chr(text_value['']) + return icon diff --git a/menus/pregame.py b/menus/pregame.py index 1af6d27d..52dd93ed 100644 --- a/menus/pregame.py +++ b/menus/pregame.py @@ -1,5 +1,6 @@ -from memory.space import Bank, Write, Reserve, Allocate, Read +from memory.space import START_ADDRESS_SNES, Bank, Write, Reserve, Allocate, Read import instruction.asm as asm +import instruction.c3 as c3 class PreGameMenu: MENU_NUMBER = 9 @@ -8,6 +9,7 @@ class PreGameMenu: def __init__(self, pregame_track): self.common = pregame_track + self.invoke_flags_submenu = {} self.mod() def draw_options_mod(self): @@ -46,10 +48,10 @@ def draw_options_mod(self): def initialize_mod(self): src = [ - asm.JSR(self.common.initialize, asm.ABS), + asm.JSL(self.common.initialize + START_ADDRESS_SNES), asm.JSR(self.draw_options, asm.ABS), - asm.JSR(self.common.upload_bg123ab, asm.ABS), + asm.JSL(self.common.upload_bg123ab + START_ADDRESS_SNES), asm.LDA(self.MENU_NUMBER, asm.IMM8), asm.STA(0x0200, asm.ABS), @@ -60,6 +62,7 @@ def initialize_mod(self): asm.STA(0x26, asm.DIR), # add fade in pregame menu to queue asm.JMP(0x3541, asm.ABS), # set brightness and refresh screen ] + # called by C3 JSR jump table space = Write(Bank.C3, src, "pregame initialize") self.initialize = space.start_address @@ -79,6 +82,14 @@ def invoke_flags_menu_mod(self): space = Write(Bank.C3, src, "pregame invoke flags") self.invoke_flags = space.start_address + def invoke_flags_submenu_mod(self, submenu_idx): + src = [ + asm.JSR(0x6a3c, asm.ABS), # clear BG3 a (workaround for bizhawk snes9x core bug) + asm.JMP(self.common.invoke_flags_submenu[submenu_idx], asm.ABS), + ] + space = Write(Bank.C3, src, "pregame invoke flag submenu") + self.invoke_flags_submenu[submenu_idx] = space.start_address + def sustain_mod(self): src = [ asm.JSR(0x2a21, asm.ABS), # reset game play time @@ -120,12 +131,21 @@ def sustain_mod(self): src = [ asm.JSR(self.common.refresh_sprites, asm.ABS), - asm.LDA(0x0200, asm.ABS), + # if in a scroll area, sustain it + asm.LDA(0x0200, asm.ABS), asm.CMP(self.common.flags.MENU_NUMBER, asm.IMM8), asm.BEQ("SUSTAIN_SCROLL_AREA"), asm.CMP(self.common.objectives.MENU_NUMBER, asm.IMM8), asm.BEQ("SUSTAIN_SCROLL_AREA"), + ] + for submenu_idx in self.common.flags.submenus.keys(): + src += [ + asm.CMP(self.common.flags.submenus[submenu_idx].MENU_NUMBER, asm.IMM8), + asm.BEQ("SUSTAIN_SCROLL_AREA"), + ] + + src += [ asm.JSR(0x072d, asm.ABS), # handle d-pad asm.LDY(self.common.cursor_positions, asm.IMM16), asm.JSR(0x0640, asm.ABS), # update cursor position @@ -134,8 +154,9 @@ def sustain_mod(self): asm.LDA(0x08, asm.DIR), # load buttons pressed this frame asm.BIT(0x80, asm.IMM8), # a pressed? - asm.BEQ("RETURN"), # branch if not - + asm.BNE("A_PRESSED"), # branch if so + asm.RTS(), + "A_PRESSED", asm.TDC(), asm.JSR(0x0eb2, asm.ABS), # click sound asm.LDA(0x4b, asm.DIR), # a = cursor index @@ -144,36 +165,40 @@ def sustain_mod(self): asm.JMP(options_table, asm.ABS_X_16), "SUSTAIN_SCROLL_AREA", - asm.LDA(0x0d, asm.DIR), + asm.LDA(0x09, asm.DIR), asm.BIT(0x80, asm.IMM8), # b pressed? - asm.BNE("EXIT_SCROLL_AREA"), + asm.BNE("EXIT_SCROLL_AREA"), # branch if so + + ] + + for submenu_id in self.common.flags.submenus.keys(): + src.extend(self.common.get_submenu_src(submenu_id, self.invoke_flags_submenu[submenu_id])) + + src += [ asm.JMP(self.common.sustain_scroll_area, asm.ABS), "EXIT_SCROLL_AREA", - asm.JSR(self.common.exit_scroll_area, asm.ABS), - asm.LDA(self.MENU_NUMBER, asm.IMM8), - asm.STA(0x0200, asm.ABS), - - "RETURN", - asm.RTS(), ] + src.extend(self.common.get_scroll_area_exit_src(self.MENU_NUMBER, self.invoke_flags)) + + # Called by C3 JSR jump table space = Write(Bank.C3, src, "pregame sustain") self.sustain = space.start_address def initialize_config_menu_mod(self): src = [ - asm.JSR(0x352f, asm.ABS), # reset - + c3.eggers_jump(0x352f), # displaced code: reset + asm.STZ(0x4A, asm.DIR), # displaced code: screen 1st asm.LDA(0xc0, asm.IMM8), # hdma channels 6 and 7 asm.TRB(0x43, asm.DIR), - asm.RTS(), + asm.RTL(), ] - space = Write(Bank.C3, src, "pregame initialize config menu reset uncondense") + space = Write(Bank.F0, src, "pregame initialize config menu reset uncondense") reset_uncondense = space.start_address - space = Reserve(0x31c7d, 0x31c7f, "pregame initialize config menu reset") + space = Reserve(0x31c7d, 0x31c81, "pregame initialize config menu reset", asm.NOP()) space.write( - asm.JSR(reset_uncondense, asm.ABS), + asm.JSL(reset_uncondense + START_ADDRESS_SNES), ) def exit_config_menu_mod(self): @@ -235,6 +260,8 @@ def mod(self): self.initialize_mod() self.invoke_objectives_menu_mod() self.invoke_flags_menu_mod() + for submenu_idx in self.common.flags.submenus.keys(): + self.invoke_flags_submenu_mod(submenu_idx) self.sustain_mod() self.initialize_config_menu_mod() diff --git a/menus/pregame_track.py b/menus/pregame_track.py index c26edf45..975ea355 100644 --- a/menus/pregame_track.py +++ b/menus/pregame_track.py @@ -1,5 +1,6 @@ from memory.space import Bank, START_ADDRESS_SNES, Reserve, Allocate, Write, Read import instruction.asm as asm +import instruction.c3 as c3 import args import menus.pregame_track_scroll_area as scroll_area @@ -22,9 +23,67 @@ def __init__(self, characters): self.progress = progress.Progress() self.flags = flags.Flags() + self.invoke_flags_submenu = {} self.characters = characters self.mod() + def get_submenu_src(self, submenu_id, invoke_submenu_addr): + SUBMENU_LABEL = f"SUBMENU_CHECK{submenu_id}" + SUBMENU_END_LABEL = f"SUBMENU_CHECK{submenu_id}_END" + HANDLE_SCROLLING_LABEL = f"HANDLE_SCROLLING_{submenu_id}" + # get the ASM for sustain_mod that checks whether we are in the flags menu + # and the A button is clicked to launch a submenu. + src = [ + # if on the flags menu, check A button press + asm.LDA(0x200, asm.ABS), + asm.CMP(self.flags.MENU_NUMBER, asm.IMM8), # in Flags menu? + asm.BNE(HANDLE_SCROLLING_LABEL), # branch if not + asm.LDA(0x08, asm.DIR), + asm.BIT(0x80, asm.IMM8), # a pressed? + asm.BEQ(HANDLE_SCROLLING_LABEL), # branch if not + ] + src += [ + SUBMENU_LABEL, + asm.LDA(0x4b, asm.DIR), # a = cursor index + asm.CMP(submenu_id, asm.IMM8), # is the cursor index = this submenu? + asm.BNE(SUBMENU_END_LABEL), # branch if not + asm.TDC(), + asm.JSR(0x0eb2, asm.ABS), # click sound + asm.JSL(self.exit_scroll_area + START_ADDRESS_SNES), # save current submenu position + asm.JMP(invoke_submenu_addr, asm.ABS), # load the flags submenu + SUBMENU_END_LABEL, + ] + src += [HANDLE_SCROLLING_LABEL] + return src + + def get_scroll_area_exit_src(self, destination_menu_number, invoke_flags_addr): + # Get the ASM for sustain_mod that handles exit from a scroll area, either returning to flags if in + # a flags submenu or to the given destination_menu_number otherwise. + src = [ + asm.JSR(0x0EA9, asm.ABS), # cursor sound + asm.JSL(self.exit_scroll_area + START_ADDRESS_SNES), # save current submenu position + asm.LDA(0x0200, asm.ABS), + ] + + for submenu_idx in self.flags.submenus.keys(): + # if current menu is a flags sub-menu, cause it to return to that, rather than main menu + src += [ + asm.CMP(self.flags.submenus[submenu_idx].MENU_NUMBER, asm.IMM8), # in Flags submenu? + asm.BEQ("INVOKE_FLAGS"), # branch if so + ] + + src += [ + asm.LDA(destination_menu_number, asm.IMM8), # queue up this menu + asm.STA(0x0200, asm.ABS), + "RETURN", + asm.RTS(), + + "INVOKE_FLAGS", + asm.JMP(invoke_flags_addr, asm.ABS), + ] + + return src + def draw_layout_mod(self): # layouts: 2 bytes for bg/tilemap/position, 1 byte inner width, 1 byte inner height # e.g. $5849 is start of bg2 tilemap a, add 0x42 for top left of visible screen area @@ -33,6 +92,7 @@ def draw_layout_mod(self): 0x8b, 0x58, # top-left to top-right 0x1c, 0x07, # 28x7 ] + # Note: keep in C3 as this is then used by the C3/0341 subroutine called below space = Write(Bank.C3, src, "pregame track top window layout") top_window_layout = space.start_address @@ -40,19 +100,20 @@ def draw_layout_mod(self): 0xcb, 0x5a, # x/y position 0x1c, 0x0f, # width/height (excluding border) ] + # Note: keep in C3 as this is then used by the C3/0341 subroutine called below space = Write(Bank.C3, src, "pregame track bottom window layout") bottom_window_layout = space.start_address src = [ - asm.JSR(0x6a15, asm.ABS), # clear BG1 A - asm.JSR(0x6a3c, asm.ABS), # clear BG3 A + c3.eggers_jump(0x6a15), # clear BG1 A + c3.eggers_jump(0x6a3c), # clear BG3 A asm.LDY(top_window_layout, asm.IMM16), - asm.JSR(0x0341, asm.ABS), # draw top window + c3.eggers_jump(0x0341), # draw top window asm.LDY(bottom_window_layout, asm.IMM16), - asm.JSR(0x0341, asm.ABS), # draw bottom window + c3.eggers_jump(0x0341), # draw bottom window asm.RTS(), ] - space = Write(Bank.C3, src, "pregame track draw layout") + space = Write(Bank.F0, src, "pregame track draw layout") self.draw_layout = space.start_address def decrease_line_height_mod(self): @@ -65,6 +126,7 @@ def decrease_line_height_mod(self): 0x0c, 0x14, 0x00, # config/flags 0x00, # end ] + # Keep in C3 as it's used by C3 subroutine called below space = Write(Bank.C3, src, "pregame track bg3 shift table") bg3_shift_table = space.start_address @@ -97,17 +159,17 @@ def draw_labels_mod(self): ) src = [ - asm.JSR(0xc2f7, asm.ABS), # set text color to blue + c3.eggers_jump(0xc2f7), # set text color to blue ] for label in labels: src += [ asm.LDY(label, asm.IMM16), - asm.JSR(0x02f9, asm.ABS), # draw text + c3.eggers_jump(0x02f9), # draw text ] src += [ asm.RTS(), ] - space = Write(Bank.C3, src, "pregame track draw labels") + space = Write(Bank.F0, src, "pregame track draw labels") self.draw_labels = space.start_address def draw_entry_mod(self): @@ -123,7 +185,15 @@ def draw_entry_mod(self): asm.BEQ("DRAW_PROGRESS"), asm.CMP(self.flags.MENU_NUMBER, asm.IMM8), asm.BEQ("DRAW_FLAG"), + ] + + for submenu_idx in self.flags.submenus.keys(): + src += [ + asm.CMP(self.flags.submenus[submenu_idx].MENU_NUMBER, asm.IMM8), + asm.BEQ(f"DRAW_FLAGS_SUBMENU{submenu_idx}"), + ] + src += [ "DRAW_ITEM", Read(0x37fa1, 0x37fa3), asm.JMP(0x7fa4, asm.ABS), @@ -140,6 +210,13 @@ def draw_entry_mod(self): "DRAW_FLAG", asm.JMP(self.flags.draw_line, asm.ABS), ] + + for submenu_idx in self.flags.submenus.keys(): + src += [ + f"DRAW_FLAGS_SUBMENU{submenu_idx}", + asm.JMP(self.flags.submenus[submenu_idx].draw_line, asm.ABS), + ] + space = Write(Bank.C3, src, "pregame track draw entry") draw_entry = space.start_address @@ -150,12 +227,12 @@ def draw_entry_mod(self): def upload_bg123ab_mod(self): src = [ - asm.JSR(0x0e28, asm.ABS), # upload bg1 a+b - asm.JSR(0x0e52, asm.ABS), # upload bg2 a+b - asm.JSR(0x0e6e, asm.ABS), # upload bg3 a+b - asm.RTS(), + c3.eggers_jump(0x0e28), # upload bg1 a+b + c3.eggers_jump(0x0e52), # upload bg2 a+b + c3.eggers_jump(0x0e6e), # upload bg3 a+b + asm.RTL(), ] - space = Write(Bank.C3, src, "pregame track upload bg123ab") + space = Write(Bank.F0, src, "pregame track upload bg123ab") self.upload_bg123ab = space.start_address def initialize_cursor_mod(self): @@ -165,6 +242,7 @@ def initialize_cursor_mod(self): 0x08, 0x35, # flags / progress 0x08, 0x41, # config / flags ] + # Keep in C3 -- used by subroutines space = Write(Bank.C3, src, "pregame track cursor positions") self.cursor_positions = space.start_address @@ -175,15 +253,16 @@ def initialize_cursor_mod(self): 0x01, # columns 0x04, # rows ] + # Keep in C3 -- used by subroutines space = Write(Bank.C3, src, "pregame track navigation data") navigation_data = space.start_address src = [ asm.LDY(navigation_data, asm.IMM16), - asm.JSR(0x05fe, asm.ABS), # load navigation data + c3.eggers_jump(0x05fe), # load navigation data asm.RTS(), ] - space = Write(Bank.C3, src, "pregame track update navigation data") + space = Write(Bank.F0, src, "pregame track update navigation data") self.update_navigation_data = space.start_address src = [ @@ -191,16 +270,16 @@ def initialize_cursor_mod(self): asm.STA(0x4e, asm.DIR), # cursor row = saved row asm.RTS(), ] - space = Write(Bank.C3, src, "pregame track remember cursor position") + space = Write(Bank.F0, src, "pregame track remember cursor position") self.remember_cursor_position = space.start_address src = [ asm.LDY(self.cursor_positions, asm.IMM16), - asm.JSR(0x0640, asm.ABS), # update cursor position - asm.JSR(0x07b0, asm.ABS), # add cursor to animation queue - asm.RTS(), + c3.eggers_jump(0x0640), # update cursor position + c3.eggers_jump(0x07b0), # add cursor to animation queue + asm.RTS(), ] - space = Write(Bank.C3, src, "pregame track update cursor position") + space = Write(Bank.F0, src, "pregame track update cursor position") self.update_cursor_position = space.start_address src = [ @@ -211,9 +290,10 @@ def initialize_cursor_mod(self): asm.JSR(self.remember_cursor_position, asm.ABS), "UPDATE_CURSOR_POSITION", - asm.JMP(self.update_cursor_position, asm.ABS), + asm.JSR(self.update_cursor_position, asm.ABS), + asm.RTS(), ] - space = Write(Bank.C3, src, "pregame track initialize cursor") + space = Write(Bank.F0, src, "pregame track initialize cursor") self.initialize_cursor = space.start_address def initialize_scroll_area_mod(self): @@ -237,6 +317,11 @@ def initialize_scroll_area_mod(self): src+= [ asm.JSL(START_ADDRESS_SNES + self.flags.initialize), ] + for submenu_idx in self.flags.submenus.keys(): + if self.flags.submenus[submenu_idx].initialize is not None: + src+= [ + asm.JSL(START_ADDRESS_SNES + self.flags.submenus[submenu_idx].initialize), + ] src += [ asm.STZ(0x4a, asm.DIR), # index of first row displayed @@ -249,8 +334,8 @@ def initialize_scroll_area_mod(self): asm.LDA(self.objectives.number_excess_lines, asm.IMM8), asm.STA(0x5c, asm.DIR), - asm.JSR(0x07b0, asm.ABS), # queue scrollbar animation - asm.JSR(0x091f, asm.ABS), # create scrollbar + c3.eggers_jump(0x07b0), # queue scrollbar animation + c3.eggers_jump(0x091f), # create scrollbar asm.A16(), asm.LDA(self.objectives.scrollbar_speed, asm.IMM16), asm.STA(0x7e354a, asm.LNG_X), @@ -266,7 +351,7 @@ def initialize_scroll_area_mod(self): asm.LDA(0x1d4e, asm.ABS), # load game config asm.AND(0x40, asm.IMM8), # cursor memory enabled? asm.BNE("REMEMBER_SCROLL_AREA"), # branch if so - asm.JSR(scroll_area.draw, asm.ABS), + c3.eggers_jump(scroll_area.draw), asm.RTS(), "REMEMBER_SCROLL_AREA", @@ -278,7 +363,15 @@ def initialize_scroll_area_mod(self): asm.BEQ("REMEMBER_PROGRESS"), asm.CMP(self.flags.MENU_NUMBER, asm.IMM8), asm.BEQ("REMEMBER_FLAGS"), + ] + + for submenu_idx in self.flags.submenus.keys(): + src += [ + asm.CMP(self.flags.submenus[submenu_idx].MENU_NUMBER, asm.IMM8), + asm.BEQ(f"REMEMBER_FLAGS_SUBMENU{submenu_idx}"), + ] + src += [ "REMEMBER_OBJECTIVES", asm.LDA(self.objectives.MENU_NUMBER, asm.IMM8), # load objectives menu number asm.STA(self.MEMORY_SCROLL_AREA_NUMBER, asm.ABS), # save in case no scroll area memory @@ -293,7 +386,14 @@ def initialize_scroll_area_mod(self): "REMEMBER_FLAGS", asm.JMP(self.flags.remember_draw, asm.ABS), ] - space = Write(Bank.C3, src, "pregame track initialize scroll area") + + for submenu_idx in self.flags.submenus.keys(): + src += [ + f"REMEMBER_FLAGS_SUBMENU{submenu_idx}", + asm.JMP(self.flags.submenus[submenu_idx].remember_draw, asm.ABS), + ] + + space = Write(Bank.F0, src, "pregame track initialize scroll area") self.initialize_scroll_area = space.start_address def InvokeScrollArea(self, scroll_area_menu): @@ -301,7 +401,7 @@ def InvokeScrollArea(self, scroll_area_menu): asm.LDA(scroll_area_menu.MENU_NUMBER, asm.IMM8), asm.STA(self.MEMORY_SCROLL_AREA_NUMBER, asm.ABS), asm.JSR(scroll_area_menu.invoke, asm.ABS), - asm.JSR(self.refresh_sprites, asm.ABS), + asm.JSR(self.refresh_sprites, asm.ABS), # JSL asm.RTS(), ] @@ -333,7 +433,15 @@ def invoke_flags_mod(self): space = Write(Bank.C3, src, "pregame track invoke flags") self.invoke_flags = space.start_address + def invoke_flags_submenu_mod(self, submenu_idx): + src = [ + self.InvokeScrollArea(self.flags.submenus[submenu_idx]), + ] + space = Write(Bank.C3, src, "pregame track invoke flags submenu") + self.invoke_flags_submenu[submenu_idx] = space.start_address + def sustain_scroll_area_mod(self): + # Called via JMP src = [ asm.TDC(), asm.STA(0x2a, asm.DIR), @@ -370,13 +478,13 @@ def exit_scroll_area_mod(self): asm.STA(scroll_area.MEMORY_PAGE_POSITIONS_START_ADDR, asm.ABS_Y), "UPDATE_PREGAME_TRACK_CURSOR", - asm.JSR(self.update_navigation_data, asm.ABS), - asm.JSR(self.remember_cursor_position, asm.ABS), + asm.JSR(self.update_navigation_data, asm.ABS), + asm.JSR(self.remember_cursor_position, asm.ABS), asm.JSR(self.update_cursor_position, asm.ABS), - asm.RTS(), + asm.RTL(), ] - space = Write(Bank.C3, src, "pregame track exit scroll area") + space = Write(Bank.F0, src, "pregame track exit scroll area") self.exit_scroll_area = space.start_address def load_sprite_palettes_mod(self): @@ -384,7 +492,7 @@ def load_sprite_palettes_mod(self): # copy the original palettes here so the sprite hash is not affected by custom palette changes palette_size = 16 * 2 # 16 colors, 2 bytes each palettes_size = palette_size * 6 # 6 palettes * 16 colors * 2 bytes - palette_space = Allocate(Bank.C3, palettes_size, "pregame track palettes") + palette_space = Allocate(Bank.F0, palettes_size, "pregame track palettes") palette0_address = palette_space.next_address palette_space.write( @@ -452,7 +560,7 @@ def load_sprite_palettes_mod(self): "LOAD_COLOR_LOOP_START", asm.A16(), - asm.LDA(palettes, asm.LNG_X), # load current color + asm.LDA(palettes + START_ADDRESS_SNES, asm.LNG_X), # load current color asm.STA(0x7e3149, asm.LNG_X), # store in ram asm.A8(), asm.STA(0x2122, asm.ABS), # store low byte in cgram @@ -463,10 +571,10 @@ def load_sprite_palettes_mod(self): asm.CPX(palettes_size, asm.IMM16), # all colors loaded? asm.BNE("LOAD_COLOR_LOOP_START"), # loop if not - asm.JSR(0x6ce9, asm.ABS), # load single pose for characters terra, locke, ..., ghost, kefka + c3.eggers_jump(0x6ce9), # load single pose for characters terra, locke, ..., ghost, kefka asm.RTS(), ] - space = Write(Bank.C3, src, "pregame track load sprite palettes") + space = Write(Bank.F0, src, "pregame track load sprite palettes") self.load_sprite_palettes = space.start_address def refresh_sprites_mod(self): @@ -480,7 +588,7 @@ def refresh_sprites_mod(self): src += [ x_start + x_spacing * index, ] - space = Write(Bank.C3, src, "pregame track refresh sprites x positions") + space = Write(Bank.F0, src, "pregame track refresh sprites x positions") x_positions_address = space.start_address y_start = 0x32 # higher is lower on screen @@ -490,20 +598,20 @@ def refresh_sprites_mod(self): src += [ y_start + entry.y_offset * 8, ] - space = Write(Bank.C3, src, "pregame track refresh sprites y positions") + space = Write(Bank.F0, src, "pregame track refresh sprites y positions") y_positions_address = space.start_address # if not zero, these palettes override sprite oam palettes (at 0xc31324) src = [ 0x00, 0x00, 0x00, 0x00, ] - space = Write(Bank.C3, src, "pregame track refresh sprites palettes address") + space = Write(Bank.F0, src, "pregame track refresh sprites palettes address") palettes_address = space.start_address src = [ HASH_CHARACTERS, ] - space = Write(Bank.C3, src, "pregame track refresh sprites characters address") + space = Write(Bank.F0, src, "pregame track refresh sprites characters address") characters_address = space.start_address # modified version of c31903 used for save/load menus @@ -546,6 +654,7 @@ def refresh_sprites_mod(self): asm.RTS(), ] + # Keep in C3 -- called by JMP methods that are called from C3 JSR jump table space = Write(Bank.C3, src, "pregame track refresh sprites") self.refresh_sprites = space.start_address @@ -576,8 +685,8 @@ def initialize_mod(self): asm.LDA(menu_flags, asm.IMM8), asm.STA(0x46, asm.DIR), - asm.JSR(0x352f, asm.ABS), # reset oam/queue/etc.. blank screen - asm.JSR(0x6904, asm.ABS), # reset BG1-3 x/y + c3.eggers_jump(0x352f), # reset oam/queue/etc.. blank screen + c3.eggers_jump(0x6904), # reset BG1-3 x/y asm.LDA(bg1_size_position, asm.IMM8), # a = BG1 size and position asm.STA(0x2107, asm.ABS), # BG1 64x32 at $0000 @@ -585,24 +694,24 @@ def initialize_mod(self): asm.STA(0x43, asm.DIR), # set active hdma channels asm.JSR(self.draw_layout, asm.ABS), - asm.JSR(self.decrease_line_height, asm.ABS), + c3.eggers_jump(self.decrease_line_height), asm.JSR(self.initialize_scroll_area, asm.ABS), - asm.JSR(0x6a19, asm.ABS), # clear BG1 b - asm.JSR(0x6a3c, asm.ABS), # clear BG3 a - asm.JSR(0x6a41, asm.ABS), # clear BG3 b - asm.JSR(0x6a46, asm.ABS), # clear BG3 c - asm.JSR(0x6a4b, asm.ABS), # clear BG3 d + c3.eggers_jump(0x6a19), # clear BG1 b + c3.eggers_jump(0x6a3c), # clear BG3 a + c3.eggers_jump(0x6a41), # clear BG3 b + c3.eggers_jump(0x6a46), # clear BG3 c + c3.eggers_jump(0x6a4b), # clear BG3 d asm.JSR(self.draw_labels, asm.ABS), - asm.JSR(0x6ca5, asm.ABS), # load cursor colors, skip loading status icon colors at 0x6c84 + c3.eggers_jump(0x6ca5), # load cursor colors, skip loading status icon colors at 0x6c84 asm.JSR(self.load_sprite_palettes, asm.ABS), asm.JSR(self.initialize_cursor, asm.ABS), - asm.RTS(), + asm.RTL(), ] - space = Write(Bank.C3, src, "pregame track initialize") + space = Write(Bank.F0, src, "pregame track initialize") self.initialize = space.start_address def wait_for_fade_mod(self): @@ -614,6 +723,7 @@ def wait_for_fade_mod(self): "LOAD_SPRITE_DATA", asm.JMP(self.refresh_sprites, asm.ABS), ] + # Keep in C3 -- called by C3 JSR jump table space = Write(Bank.C3, src, "pregame track wait for fade") self.wait_for_fade = space.start_address @@ -626,6 +736,7 @@ def fade_in_mod(self): asm.STA(0x26, asm.DIR), # add wait for fade to queue asm.JMP(self.refresh_sprites, asm.ABS), ] + # Keep in C3 -- called by C3 JSR jump table space = Write(Bank.C3, src, "pregame track fade in") self.fade_in = space.start_address @@ -638,6 +749,7 @@ def fade_out_mod(self): asm.STA(0x26, asm.DIR), # add wait for fade to queue asm.JMP(self.refresh_sprites, asm.ABS), # refresh sprites ] + # Keep in C3 -- called by C3 JSR jump table space = Write(Bank.C3, src, "pregame track fade out") self.fade_out = space.start_address @@ -668,8 +780,10 @@ def mod(self): self.invoke_checks_mod() self.invoke_progress_mod() self.invoke_flags_mod() - self.sustain_scroll_area_mod() + for submenu_idx in self.flags.submenus.keys(): + self.invoke_flags_submenu_mod(submenu_idx) self.exit_scroll_area_mod() + self.sustain_scroll_area_mod() self.initialize_mod() self.wait_for_fade_mod() diff --git a/menus/pregame_track_scroll_area.py b/menus/pregame_track_scroll_area.py index 89ed581e..ac6bffa3 100644 --- a/menus/pregame_track_scroll_area.py +++ b/menus/pregame_track_scroll_area.py @@ -1,6 +1,7 @@ from memory.space import START_ADDRESS_SNES, Bank, Write import instruction.asm as asm import instruction.f0 as f0 +import instruction.c3 as c3 from enum import IntEnum from collections import namedtuple @@ -136,7 +137,7 @@ def invoke_mod(self): asm.AND(0x40, asm.IMM8), # cursor memory enabled? asm.BEQ("UPDATE_CURSOR"), # branch if not - asm.JSR(self.remember_cursor, asm.ABS), + asm.JSL(self.remember_cursor + START_ADDRESS_SNES), "UPDATE_CURSOR", asm.JSR(0x7d25, asm.ABS), # update cursor @@ -221,7 +222,7 @@ def draw_line_mod(self): asm.DEY(), # decrement count asm.BNE("LOOP_START"), # branch if more characters in line asm.STZ(0x2180, asm.ABS), # write end of string - asm.RTS(), + asm.RTL(), "WRITE_BLANK_LINE", asm.LDY(WIDTH, asm.IMM16), @@ -231,14 +232,14 @@ def draw_line_mod(self): asm.DEY(), asm.BNE("WRITE_BLANK_LINE_LOOP"), asm.STZ(0x2180, asm.ABS), - asm.RTS(), + asm.RTL(), ] - space = Write(Bank.C3, src, "pregame track scroll area write line") + space = Write(Bank.F0, src, "pregame track scroll area write line") write_line = space.start_address src = [ asm.JSL(START_ADDRESS_SNES + self.initialize_line), - asm.JSR(write_line, asm.ABS), + asm.JSL(START_ADDRESS_SNES + write_line), asm.JSR(0x37fd9, asm.ABS), # draw line asm.RTS(), ] @@ -252,10 +253,10 @@ def remember_cursor_mod(self): asm.LDA(0x4f, asm.DIR), asm.STA(0x4d, asm.DIR), asm.LDA(self.memory_page_position, asm.ABS), - asm.JSR(0x0e1e, asm.ABS), - asm.RTS(), + c3.eggers_jump(0x0e1e), + asm.RTL(), ] - space = Write(Bank.C3, src, "pregame track scroll area remember cursor") + space = Write(Bank.F0, src, "pregame track scroll area remember cursor") self.remember_cursor = space.start_address def remember_scrollbar_mod(self): @@ -268,9 +269,9 @@ def remember_scrollbar_mod(self): asm.STA(0x7e354a, asm.LNG_X), asm.A8(), - asm.RTS(), + asm.RTL(), ] - space = Write(Bank.C3, src, "pregame track scroll area remember scrollbar") + space = Write(Bank.F0, src, "pregame track scroll area remember scrollbar") self.remember_scrollbar = space.start_address def remember_draw_mod(self): @@ -278,13 +279,13 @@ def remember_draw_mod(self): asm.LDA(self.MENU_NUMBER, asm.IMM8), asm.STA(0x0200, asm.ABS), - asm.JSR(self.remember_cursor, asm.ABS), - asm.JSR(self.remember_scrollbar, asm.ABS), + asm.JSL(self.remember_cursor + START_ADDRESS_SNES), + asm.JSL(self.remember_scrollbar + START_ADDRESS_SNES), - asm.JSR(draw, asm.ABS), - asm.RTS(), + c3.eggers_jump(draw), + asm.RTS(), ] - space = Write(Bank.C3, src, "pregame track scroll area remember draw") + space = Write(Bank.F0, src, "pregame track scroll area remember draw") self.remember_draw = space.start_address def mod(self): diff --git a/menus/track.py b/menus/track.py index 10f04fbf..28fd2d3d 100644 --- a/menus/track.py +++ b/menus/track.py @@ -1,5 +1,6 @@ -from memory.space import Bank, Write, Reserve, Allocate, Read +from memory.space import START_ADDRESS_SNES, Bank, Write, Reserve, Allocate, Read import instruction.asm as asm +import instruction.c3 as c3 class TrackMenu: MENU_NUMBER = 10 @@ -192,10 +193,10 @@ def invoke_mod(self): def initialize_mod(self): src = [ - asm.JSR(self.common.initialize, asm.ABS), + asm.JSL(self.common.initialize + START_ADDRESS_SNES), asm.JSR(self.draw_options, asm.ABS), - asm.JSR(self.common.upload_bg123ab, asm.ABS), + asm.JSL(self.common.upload_bg123ab + START_ADDRESS_SNES), asm.LDA(self.MENU_NUMBER, asm.IMM8), asm.STA(0x0200, asm.ABS), @@ -206,6 +207,7 @@ def initialize_mod(self): asm.STA(0x26, asm.DIR), # add fade in menu to queue asm.JMP(0x3541, asm.ABS), # set brightness and refresh screen ] + # called by C3 JSR jump table space = Write(Bank.C3, src, "track initialize") self.initialize = space.start_address @@ -222,7 +224,8 @@ def sustain_mod(self): src = [ asm.JSR(self.common.refresh_sprites, asm.ABS), - asm.LDA(0x0200, asm.ABS), + # if in a scroll-area menu, sustain the scroll area + asm.LDA(0x0200, asm.ABS), asm.CMP(self.common.objectives.MENU_NUMBER, asm.IMM8), asm.BEQ("SUSTAIN_SCROLL_AREA"), asm.CMP(self.common.checks.MENU_NUMBER, asm.IMM8), @@ -231,7 +234,15 @@ def sustain_mod(self): asm.BEQ("SUSTAIN_SCROLL_AREA"), asm.CMP(self.common.flags.MENU_NUMBER, asm.IMM8), asm.BEQ("SUSTAIN_SCROLL_AREA"), + ] + for submenu_idx in self.common.flags.submenus.keys(): + src += [ + asm.CMP(self.common.flags.submenus[submenu_idx].MENU_NUMBER, asm.IMM8), + asm.BEQ("SUSTAIN_SCROLL_AREA"), + ] + + src += [ asm.JSR(0x072d, asm.ABS), # handle d-pad asm.LDY(self.common.cursor_positions, asm.IMM16), asm.JSR(0x0640, asm.ABS), # update cursor position @@ -252,8 +263,9 @@ def sustain_mod(self): "HANDLE_B", asm.LDA(0x09, asm.DIR), # load buttons pressed this frame asm.BIT(0x80, asm.IMM8), # b pressed? - asm.BEQ("RETURN"), # return if not - + asm.BNE("B_PRESSED"), # return if not + asm.RTS(), + "B_PRESSED", asm.LDA(0x04, asm.IMM8), # a = initialize main menu command asm.STA(0x27, asm.DIR), # add initialize main menu to queue asm.LDA(self.common.FADE_OUT_COMMAND, asm.IMM8), @@ -261,19 +273,23 @@ def sustain_mod(self): asm.RTS(), "SUSTAIN_SCROLL_AREA", - asm.LDA(0x0d, asm.DIR), - asm.BIT(0x80, asm.IMM8), # b pressed? - asm.BNE("EXIT_SCROLL_AREA"), + asm.LDA(0x09, asm.DIR), + asm.BIT(0x80, asm.IMM8), # b pressed? + asm.BNE("EXIT_SCROLL_AREA"), # branch if so + ] + + for submenu_idx in self.common.flags.submenus.keys(): + src.extend(self.common.get_submenu_src(submenu_idx, self.common.invoke_flags_submenu[submenu_idx])) + + src += [ asm.JMP(self.common.sustain_scroll_area, asm.ABS), "EXIT_SCROLL_AREA", - asm.JSR(self.common.exit_scroll_area, asm.ABS), - asm.LDA(self.MENU_NUMBER, asm.IMM8), - asm.STA(0x0200, asm.ABS), - - "RETURN", - asm.RTS(), ] + + src.extend(self.common.get_scroll_area_exit_src(self.MENU_NUMBER, self.common.invoke_flags)) + + # Called by C3 JSR jump table space = Write(Bank.C3, src, "track sustain") self.sustain = space.start_address diff --git a/objectives/results/forget_spells.py b/objectives/results/forget_spells.py index d5846ba5..0b0eaf1a 100644 --- a/objectives/results/forget_spells.py +++ b/objectives/results/forget_spells.py @@ -5,12 +5,17 @@ def _random_spell_table(): from constants.spells import spell_id spell_table = list(range(len(spell_id))) - if args.no_ultima: - spell_table.remove(spell_id["Ultima"]) + + for a_spell_id in args.remove_learnable_spell_ids: + spell_table.remove(a_spell_id) + random.shuffle(spell_table) - space = Write(Bank.F0, spell_table, "forget spells random spell table") - return space.start_address, len(spell_table) + if len(spell_table) > 0: + space = Write(Bank.F0, spell_table, "forget spells random spell table") + return space.start_address, len(spell_table) + else: + return None, 0 random_forget_spell_table, random_forget_spell_table_size = _random_spell_table() def _forget_random_spells(): @@ -27,61 +32,66 @@ def _forget_random_spells(): learned_start = 0x30 # temporarily store learned_start_address in field/battle scratch ram spells_left = 0x32 # temporarily store number spells left to forget in field/battle scratch ram - src = [ - asm.PHP(), - asm.XY16(), - asm.LDY(learned_start, asm.DIR), - asm.PHY(), # store value at $learned_start to be restored before returning - asm.LDA(spells_left, asm.DIR), - asm.PHA(), # store value at $spells_left to be restored before returning - - asm.LDY(learned_start_address, asm.IMM16), - asm.STY(learned_start, asm.DIR), # $learned_start = start of character's learned spells - asm.LDY(character_count, asm.IMM16), # y = number of characters remaining - - "CHARACTER_LOOP_START", - asm.PHY(), - asm.TDC(), - asm.LDA(field.LongCall.ARG_ADDRESS, asm.DIR), # a = number of spells to forget - asm.STA(spells_left, asm.DIR), # $spells_left = number of spells left to forget - asm.LDX(0x0000, asm.IMM16), # x = spell table index - - "SPELL_LOOP_START", - asm.LDA(START_ADDRESS_SNES + random_forget_spell_table, asm.LNG_X), - asm.TAY(), # y = id of next spell in table - asm.LDA(0xff, asm.IMM8), # a = known spell value - asm.CMP(learned_start, asm.DIR_16_Y), # compare with value at learned start address for character + spell id - asm.BNE("NEXT_SPELL"), # branch if character does not know this spell - asm.LDA(0x00, asm.IMM8), - asm.STA(learned_start, asm.DIR_16_Y), # forget spell - asm.DEC(spells_left, asm.DIR), # decrement number of spells left to forget - asm.BEQ("NEXT_CHARACTER"), # next character if number of spells to forget is 0 - "NEXT_SPELL", - asm.INX(), # next spell in spell table - asm.CPX(random_forget_spell_table_size, asm.IMM16), - asm.BLT("SPELL_LOOP_START"), # branch if spell index < len(spell table) - - "NEXT_CHARACTER", - asm.PLY(), # y = characters remaining - asm.DEY(), - asm.BEQ("RETURN"), # return if zero characters remaining - - asm.A16(), - asm.LDA(learned_start, asm.DIR), # a = start of learned spells for character - asm.CLC(), - asm.ADC(spell_count, asm.IMM16), # add number of spells - asm.STA(learned_start, asm.DIR), # $learned_start = start of learned spells for next character - asm.A8(), - asm.BRA("CHARACTER_LOOP_START"), # forget spells with next character - - "RETURN", - asm.PLA(), - asm.STA(spells_left, asm.DIR), # restore original value at $spells_left - asm.PLY(), - asm.STY(learned_start, asm.DIR), # restore original value at $learned_start - asm.PLP(), - asm.RTL(), - ] + if random_forget_spell_table_size > 0: + src = [ + asm.PHP(), + asm.XY16(), + asm.LDY(learned_start, asm.DIR), + asm.PHY(), # store value at $learned_start to be restored before returning + asm.LDA(spells_left, asm.DIR), + asm.PHA(), # store value at $spells_left to be restored before returning + + asm.LDY(learned_start_address, asm.IMM16), + asm.STY(learned_start, asm.DIR), # $learned_start = start of character's learned spells + asm.LDY(character_count, asm.IMM16), # y = number of characters remaining + + "CHARACTER_LOOP_START", + asm.PHY(), + asm.TDC(), + asm.LDA(field.LongCall.ARG_ADDRESS, asm.DIR), # a = number of spells to forget + asm.STA(spells_left, asm.DIR), # $spells_left = number of spells left to forget + asm.LDX(0x0000, asm.IMM16), # x = spell table index + + "SPELL_LOOP_START", + asm.LDA(START_ADDRESS_SNES + random_forget_spell_table, asm.LNG_X), + asm.TAY(), # y = id of next spell in table + asm.LDA(0xff, asm.IMM8), # a = known spell value + asm.CMP(learned_start, asm.DIR_16_Y), # compare with value at learned start address for character + spell id + asm.BNE("NEXT_SPELL"), # branch if character does not know this spell + asm.LDA(0x00, asm.IMM8), + asm.STA(learned_start, asm.DIR_16_Y), # forget spell + asm.DEC(spells_left, asm.DIR), # decrement number of spells left to forget + asm.BEQ("NEXT_CHARACTER"), # next character if number of spells to forget is 0 + "NEXT_SPELL", + asm.INX(), # next spell in spell table + asm.CPX(random_forget_spell_table_size, asm.IMM16), + asm.BLT("SPELL_LOOP_START"), # branch if spell index < len(spell table) + + "NEXT_CHARACTER", + asm.PLY(), # y = characters remaining + asm.DEY(), + asm.BEQ("RETURN"), # return if zero characters remaining + + asm.A16(), + asm.LDA(learned_start, asm.DIR), # a = start of learned spells for character + asm.CLC(), + asm.ADC(spell_count, asm.IMM16), # add number of spells + asm.STA(learned_start, asm.DIR), # $learned_start = start of learned spells for next character + asm.A8(), + asm.BRA("CHARACTER_LOOP_START"), # forget spells with next character + + "RETURN", + asm.PLA(), + asm.STA(spells_left, asm.DIR), # restore original value at $spells_left + asm.PLY(), + asm.STY(learned_start, asm.DIR), # restore original value at $learned_start + asm.PLP(), + asm.RTL(), + ] + else: # no spells to forget + src = [ + asm.RTL() + ] space = Write(Bank.F0, src, "forget spells forget random spells") return space.start_address forget_random_spells = _forget_random_spells() diff --git a/objectives/results/learn_spells.py b/objectives/results/learn_spells.py index bb29dbf0..601212bd 100644 --- a/objectives/results/learn_spells.py +++ b/objectives/results/learn_spells.py @@ -5,12 +5,17 @@ def _random_spell_table(): from constants.spells import spell_id spell_table = list(range(len(spell_id))) - if args.no_ultima: - spell_table.remove(spell_id["Ultima"]) + + for a_spell_id in args.remove_learnable_spell_ids: + spell_table.remove(a_spell_id) + random.shuffle(spell_table) - space = Write(Bank.F0, spell_table, "learn spells random spell table") - return space.start_address, len(spell_table) + if len(spell_table) > 0: + space = Write(Bank.F0, spell_table, "learn spells random spell table") + return space.start_address, len(spell_table) + else: + return None, 0 random_learn_spell_table, random_learn_spell_table_size = _random_spell_table() def _learn_random_spells(): @@ -27,60 +32,65 @@ def _learn_random_spells(): learned_start = 0x30 # temporarily store learned_start_address in field/battle scratch ram spells_left = 0x32 # temporarily store number spells left to learn in field/battle scratch ram - src = [ - asm.PHP(), - asm.XY16(), - asm.LDY(learned_start, asm.DIR), - asm.PHY(), # store value at $learned_start to be restored before returning - asm.LDA(spells_left, asm.DIR), - asm.PHA(), # store value at $spells_left to be restored before returning - - asm.LDY(learned_start_address, asm.IMM16), - asm.STY(learned_start, asm.DIR), # $learned_start = start of character's learned spells - asm.LDY(character_count, asm.IMM16), # y = number of characters remaining - - "CHARACTER_LOOP_START", - asm.PHY(), - asm.TDC(), - asm.LDA(field.LongCall.ARG_ADDRESS, asm.DIR), # a = number of spells to learn - asm.STA(spells_left, asm.DIR), # $spells_left = number of spells left to learn - asm.LDX(0x0000, asm.IMM16), # x = spell table index - - "SPELL_LOOP_START", - asm.LDA(START_ADDRESS_SNES + random_learn_spell_table, asm.LNG_X), - asm.TAY(), # y = id of next spell in table - asm.LDA(0xff, asm.IMM8), # a = known spell value - asm.CMP(learned_start, asm.DIR_16_Y), # compare with value at learned start address for character + spell id - asm.BEQ("NEXT_SPELL"), # branch if character already knows this spell - asm.STA(learned_start, asm.DIR_16_Y), # learn spell - asm.DEC(spells_left, asm.DIR), # decrement number of spells left to learn - asm.BEQ("NEXT_CHARACTER"), # next character if number of spells to learn is 0 - "NEXT_SPELL", - asm.INX(), # next spell in spell table - asm.CPX(random_learn_spell_table_size, asm.IMM16), - asm.BLT("SPELL_LOOP_START"), # branch if spell index < len(spell table) - - "NEXT_CHARACTER", - asm.PLY(), # y = characters remaining - asm.DEY(), - asm.BEQ("RETURN"), # return if zero characters remaining - - asm.A16(), - asm.LDA(learned_start, asm.DIR), # a = start of learned spells for character - asm.CLC(), - asm.ADC(spell_count, asm.IMM16), # add number of spells - asm.STA(learned_start, asm.DIR), # $learned_start = start of learned spells for next character - asm.A8(), - asm.BRA("CHARACTER_LOOP_START"), # learn spells with next character - - "RETURN", - asm.PLA(), - asm.STA(spells_left, asm.DIR), # restore original value at $spells_left - asm.PLY(), - asm.STY(learned_start, asm.DIR), # restore original value at $learned_start - asm.PLP(), - asm.RTL(), - ] + if random_learn_spell_table_size > 0: + src = [ + asm.PHP(), + asm.XY16(), + asm.LDY(learned_start, asm.DIR), + asm.PHY(), # store value at $learned_start to be restored before returning + asm.LDA(spells_left, asm.DIR), + asm.PHA(), # store value at $spells_left to be restored before returning + + asm.LDY(learned_start_address, asm.IMM16), + asm.STY(learned_start, asm.DIR), # $learned_start = start of character's learned spells + asm.LDY(character_count, asm.IMM16), # y = number of characters remaining + + "CHARACTER_LOOP_START", + asm.PHY(), + asm.TDC(), + asm.LDA(field.LongCall.ARG_ADDRESS, asm.DIR), # a = number of spells to learn + asm.STA(spells_left, asm.DIR), # $spells_left = number of spells left to learn + asm.LDX(0x0000, asm.IMM16), # x = spell table index + + "SPELL_LOOP_START", + asm.LDA(START_ADDRESS_SNES + random_learn_spell_table, asm.LNG_X), + asm.TAY(), # y = id of next spell in table + asm.LDA(0xff, asm.IMM8), # a = known spell value + asm.CMP(learned_start, asm.DIR_16_Y), # compare with value at learned start address for character + spell id + asm.BEQ("NEXT_SPELL"), # branch if character already knows this spell + asm.STA(learned_start, asm.DIR_16_Y), # learn spell + asm.DEC(spells_left, asm.DIR), # decrement number of spells left to learn + asm.BEQ("NEXT_CHARACTER"), # next character if number of spells to learn is 0 + "NEXT_SPELL", + asm.INX(), # next spell in spell table + asm.CPX(random_learn_spell_table_size, asm.IMM16), + asm.BLT("SPELL_LOOP_START"), # branch if spell index < len(spell table) + + "NEXT_CHARACTER", + asm.PLY(), # y = characters remaining + asm.DEY(), + asm.BEQ("RETURN"), # return if zero characters remaining + + asm.A16(), + asm.LDA(learned_start, asm.DIR), # a = start of learned spells for character + asm.CLC(), + asm.ADC(spell_count, asm.IMM16), # add number of spells + asm.STA(learned_start, asm.DIR), # $learned_start = start of learned spells for next character + asm.A8(), + asm.BRA("CHARACTER_LOOP_START"), # learn spells with next character + + "RETURN", + asm.PLA(), + asm.STA(spells_left, asm.DIR), # restore original value at $spells_left + asm.PLY(), + asm.STY(learned_start, asm.DIR), # restore original value at $learned_start + asm.PLP(), + asm.RTL(), + ] + else: # no spells to learn + src = [ + asm.RTL() + ] space = Write(Bank.F0, src, "learn spells learn random spells") return space.start_address learn_random_spells = _learn_random_spells() From 1f5509db661ede49fec0019d02e6f3fe69bd2f76 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:48:27 -0700 Subject: [PATCH 19/60] Removing excluded non-S tier items from tiered or scaled chests (#11) --- data/chests.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/chests.py b/data/chests.py index 8d9715b4..d80f8234 100644 --- a/data/chests.py +++ b/data/chests.py @@ -50,10 +50,14 @@ def __init__(self, rom, args, items): from data.chest_item_tiers import tiers, tier_s_distribution self.item_tiers = tiers - # remove excluded items from tier s - # remaining item weights raised proportionally to their original weight - # e.g. if original weights were [0.10, 0.50, 0.40] and 0.50 removed, the remaining ones become [0.20, 0.80] + # remove excluded items from tiers excluded_items = self.items.get_excluded() + for idx, tier in enumerate(self.item_tiers): + tier = [(item) for item in tier if item not in excluded_items] + self.item_tiers[idx] = tier + + # for S-tier, remaining item weights raised proportionally to their original weight + # e.g. if original weights were [0.10, 0.50, 0.40] and 0.50 removed, the remaining ones become [0.20, 0.80] self.item_tier_s_distribution = [(item_weight[0], item_weight[1]) for item_weight in tier_s_distribution if item_weight[0] not in excluded_items] From 72e91b3a837d6bfae0a4c5be7f557f1b9e594e92 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:48:50 -0700 Subject: [PATCH 20/60] Ensuring that Gau can use Magic in FT (#10) --- event/fanatics_tower.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/event/fanatics_tower.py b/event/fanatics_tower.py index 68089485..124992c0 100644 --- a/event/fanatics_tower.py +++ b/event/fanatics_tower.py @@ -15,6 +15,7 @@ def mod(self): self.strago_npc_id = 0x13 self.strago_npc = self.maps.get_npc(0x16a, self.strago_npc_id) + self.gau_magic_mod() self.relm_event_mod() self.tower_top_mod() self.magimaster_battle_mod() @@ -32,6 +33,11 @@ def mod(self): self.log_reward(self.reward1) self.log_reward(self.reward2) + def gau_magic_mod(self): + # normally only the Fight command replaces magic, causing Gau to only have Item. + # NOP the logic that blanks out everything except Magic + Item + Reserve(0x2538c, 0x2538d, "fanatics tower magic cmd", field.NOP()) + def relm_event_mod(self): # normally there are 4 event tiles surrounding player when they enter map and if player steps on one # with relm in party and strago not already recruited the relm/strago event is triggered From a277906c564d326decffe4999acac4d8aabcb26d Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:49:52 -0700 Subject: [PATCH 21/60] Feature: Adding random-encounters-chupon flag (#8) * Adding random-encounters-chupon flag * Making Chupon 64 sneeze always target entire party --- args/encounters.py | 6 ++++++ data/enemies.py | 17 +++++++++++++++++ data/enemy_formations.py | 16 ++++++++++++++++ data/enemy_packs.py | 6 ++++++ data/enemy_scripts.py | 9 +++++++++ 5 files changed, 54 insertions(+) diff --git a/args/encounters.py b/args/encounters.py index 8fb3fb9c..c8d3a91d 100644 --- a/args/encounters.py +++ b/args/encounters.py @@ -10,6 +10,8 @@ def parse(parser): random.add_argument("-rer", "--random-encounters-random", default = None, type = int, metavar = "PERCENT", choices = range(101), help = "Random encounters are randomized") + random.add_argument("-rechu", "--random-encounters-chupon", action = "store_true", + help = "All Random Encounters are replaced with Chupon (Coliseum)") fixed = encounters.add_mutually_exclusive_group() fixed.add_argument("-fer", "--fixed-encounters-random", @@ -33,6 +35,8 @@ def flags(args): flags += " -res" elif args.random_encounters_random is not None: flags += f" -rer {args.random_encounters_random}" + elif args.random_encounters_chupon: + flags += " -rechu" if args.fixed_encounters_random is not None: flags += f" -fer {args.fixed_encounters_random}" @@ -50,6 +54,8 @@ def options(args): random_encounters = "Shuffle" elif args.random_encounters_random is not None: random_encounters = "Random" + elif args.random_encounters_chupon: + random_encounters = "Chupon" result.append(("Random Encounters", random_encounters)) if args.random_encounters_random is not None: diff --git a/data/enemies.py b/data/enemies.py index b8e368b3..e2d25833 100644 --- a/data/enemies.py +++ b/data/enemies.py @@ -274,6 +274,21 @@ def shuffle_encounters(self, maps): # NOTE: any remaining formations (due to extra_formations) are lost + def chupon_encounters(self, maps): + # find all packs that are randomly encountered in zones + packs = [] + for zone in self.zones.zones: + if self.skip_shuffling_zone(maps, zone): + continue + + for x in range(zone.PACK_COUNT): + if self.skip_shuffling_pack(zone.packs[x], zone.encounter_rates[x]): + continue + + packs.append(zone.packs[x]) + + self.packs.chupon_packs(packs) + def randomize_encounters(self, maps): # find all packs that are randomly encountered in zones packs = [] @@ -331,6 +346,8 @@ def mod(self, maps): if self.args.random_encounters_shuffle: self.shuffle_encounters(maps) + elif self.args.random_encounters_chupon: + self.chupon_encounters(maps) elif not self.args.random_encounters_original: self.randomize_encounters(maps) diff --git a/data/enemy_formations.py b/data/enemy_formations.py index e6d334be..24e6fa64 100644 --- a/data/enemy_formations.py +++ b/data/enemy_formations.py @@ -17,6 +17,7 @@ class EnemyFormations(): ALL_DRAGONS = list(bosses.dragon_formation_name) PRESENTER = 433 COLISEUM = 575 + CHUPON = 563 # Otherwise unused formation -- we'll use it for the random_encounters_chupon flag. def __init__(self, rom, args, enemies): self.rom = rom @@ -107,6 +108,18 @@ def set_chadarnook_position_left_screen(self): self.formations[456].enemy_x_positions[0] = 1 # painting self.formations[456].enemy_x_positions[1] = 1 # demon + def add_chupon(self): + # Add Chupon (Coliseum) to an unused formation for use with random_encounters_chupon + self.formations[self.CHUPON].enemy_ids[0] = 64 # Chupon (Coliseum) + self.formations[self.CHUPON].enemy_slots = 1 + self.formations[self.CHUPON].not_on_veldt = 1 + self.formations[self.CHUPON].disable_back_attack = 1 + self.formations[self.CHUPON].disable_pincer_attack = 1 + self.formations[self.CHUPON].disable_side_attack = 1 + self.formations[self.CHUPON].enemy_y_positions[0] = 5 + self.formations[self.CHUPON].enemy_x_positions[0] = 6 + self.formations[self.CHUPON].mold = 6 << 4 + def write(self): for formation_index in range(len(self.formations)): self.flags_data[formation_index] = self.formations[formation_index].flags_data() @@ -133,6 +146,9 @@ def mod(self): # but it looks better than having chadarnook's left edge showing on all the other battle backgrounds self.set_chadarnook_position_left_screen() + if self.args.random_encounters_chupon: + self.add_chupon() + def print_scripts(self): for formation_index, formation in enumerate(self.formations): if formation.enable_event_script: diff --git a/data/enemy_packs.py b/data/enemy_packs.py index 266dd3ad..ba17953d 100644 --- a/data/enemy_packs.py +++ b/data/enemy_packs.py @@ -249,6 +249,12 @@ def randomize_packs(self, packs, boss_percent, no_phunbaba3 = False): for formation_index in range(self.packs[pack_id].FORMATION_COUNT): self.packs[pack_id].formations[formation_index] = self.formations.get_random_normal() + def chupon_packs(self, packs): + # Replace all packs with the CHUPON formation + for pack_id in packs: + for formation_index in range(self.packs[pack_id].FORMATION_COUNT): + self.packs[pack_id].formations[formation_index] = self.formations.CHUPON + def randomize_fixed(self): lete_river = [263, 264] # nautiloid, exocite, pterodon imperial_camp = [272, 298, 300, 269, 270] # soldier, dogs, templar/soldier, final 3 battles diff --git a/data/enemy_scripts.py b/data/enemy_scripts.py index ad802020..0655e70a 100644 --- a/data/enemy_scripts.py +++ b/data/enemy_scripts.py @@ -257,6 +257,12 @@ def magic_urn_no_life(self): ] magic_urn_script.remove(life) + def chupon_sneeze_all(self): + # Make Chupon 64 (Coliseum) target all allies with initial sneeze + chupon_id = 64 + chupon_script = self.scripts[chupon_id] + chupon_script.insert(0, ai_instr.SetTarget(0x43)) # Target: Allies + def mod(self): # first free up some space for other mods self.cleanup_mod() @@ -298,6 +304,9 @@ def mod(self): self.hidon_no_chokesmoke() self.magic_urn_no_life() + if self.args.random_encounters_chupon: + self.chupon_sneeze_all() + if self.args.ability_scaling: self.enemy_script_abilities.scale_abilities_mod() From 0875363c076dbcde1c8575630670cdb8cf6c3ac3 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:50:23 -0700 Subject: [PATCH 22/60] Feature: Kielbasiago's add --start-junk flag (#7) * add --start-junk flag * Add relics to starting junk Co-authored-by: Kiel <95580337+kielbasiago@users.noreply.github.com> --- args/starting_gold_items.py | 7 ++++++- event/start.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/args/starting_gold_items.py b/args/starting_gold_items.py index 79da17e3..dcf3c8ac 100644 --- a/args/starting_gold_items.py +++ b/args/starting_gold_items.py @@ -14,7 +14,9 @@ def parse(parser): starting_gold_items.add_argument("-sfd", "--start-fenix-downs", default = 0, type = int, choices = range(11), metavar = "COUNT", help = "Start game with %(metavar)s Fenix Downs") starting_gold_items.add_argument("-sto", "--start-tools", default = 0, type = int, choices = range(9), metavar = "COUNT", - help = "Start game with %(metavar)s different random tools") + help = "Start game with %(metavar)s different random tools"), + starting_gold_items.add_argument("-sj", "--start-junk", default = 0, type = int, choices = range(25), metavar = "COUNT", + help = "Start game with %(metavar)s unique low tier items. Includes weapons, armors, helmets, shields, and relics"), def process(args): pass @@ -32,6 +34,8 @@ def flags(args): flags += f" -sfd {args.start_fenix_downs}" if args.start_tools != 0: flags += f" -sto {args.start_tools}" + if args.start_junk != 0: + flags += f" -sj {args.start_junk}" return flags @@ -42,6 +46,7 @@ def options(args): ("Start Warp Stones", args.start_warp_stones), ("Start Fenix Downs", args.start_fenix_downs), ("Start Tools", args.start_tools), + ("Start Junk", args.start_junk), ] def menu(args): diff --git a/event/start.py b/event/start.py index 320ef8a2..f63faeed 100644 --- a/event/start.py +++ b/event/start.py @@ -205,6 +205,23 @@ def start_items_mod(self): field.AddItem(tool, sound_effect = False), ] + from constants.items import id_name + from data.shop_item_tiers import tiers + from data.item import Item + junk = [] + junk += tiers[Item.WEAPON][0] + junk += tiers[Item.SHIELD][0] + junk += tiers[Item.HELMET][0] + junk += tiers[Item.ARMOR][0] + junk += tiers[Item.RELIC][0] + + start_junk = random.sample(junk, self.args.start_junk) + + for junk_id in start_junk: + src += [ + field.AddItem(id_name[junk_id], sound_effect = False) + ] + if self.args.debug: src += [ field.AddItem("Dried Meat", sound_effect = False), From 55355a7a3e61fc6391cf8a8b4fd78ae993b17075 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:50:45 -0700 Subject: [PATCH 23/60] QoL: Setting default config options to most commonly used values (#6) * adding comments * setting default back to Reset and adding Config 2 relocation for compatibility with DoctorDT tool --- settings/__init__.py | 2 ++ settings/config.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 settings/config.py diff --git a/settings/__init__.py b/settings/__init__.py index 4510fb25..5c50bcd0 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -3,6 +3,7 @@ from settings.random_rng import RandomRNG from settings.permadeath import Permadeath from settings.y_npc import YNPC +from settings.config import Config from memory.space import Reserve import instruction.asm as asm @@ -15,6 +16,7 @@ def __init__(self): self.random_rng = RandomRNG() self.permadeath = Permadeath() self.y_npc = YNPC() + self.config = Config() # do not auto load save file after game over space = Reserve(0x00c4fe, 0x00c500, "load where to return to after game over", asm.NOP()) diff --git a/settings/config.py b/settings/config.py new file mode 100644 index 00000000..d1677e1f --- /dev/null +++ b/settings/config.py @@ -0,0 +1,51 @@ +from memory.space import Reserve, Bank, Write +import instruction.asm as asm +import args + +class Config: + def __init__(self): + self.mod() + + def mod(self): + # Thanks to DoctorDT for most of this code + + # Set default configuration options to the most popular: + # Config1: Msg Speed = 1 (Fastest), Bat Speed = 6 (Slowest), Bat Mode = 1 (Wait) + + # Config 1, set by this code: + # C3/70B8: A92A LDA #$2A ; Bat.Mode, etc. + # RAM $1D4D, one byte sets: cmmm wbbb (command set c, message spd mmm + 1, battle mode w, battle speed bbb + 1) + space = Reserve(0x370b9, 0x370b9, "config 1 default") + space.write(0x0D) # default: 0x2A + + # Moving default location for Config 2 and 3 to support command line re-configuration + # Set default memory location for Config #2: + src = [ + asm.LDA(0x00, asm.IMM8), # LDA #$00; + asm.STA(0x1D54, asm.ABS), # STA $1D54; # Config #2 + asm.RTS(), + ] + space = Write(Bank.C3, src, "Config #2 default value") + + # Update the JSR for Config default #2 + config2_loc = space.start_address + space = Reserve(0x370c2, 0x370c4, "Config_2_default") # 0x0370C2: ['20', PP, NN, '20', PP + 06, NN]]) # JSR #$CONF2; JSR #$CONF3 + space.write( + asm.JSR(config2_loc, asm.ABS), + ) + # Config 3, set by this code: + # C3/70C5: 9C4E1D STZ $1D4E ; Wallpaper, etc. + # RAM $1D4E, one byte sets: gcsr wwww (gauge g, cursor c, sound s, reequip r, wallpaper wwww (0-7)) + src = [ + asm.LDA(0x00, asm.IMM8), # default: 0 + asm.STA(0x1D4E, asm.ABS), + asm.RTS(), + ] + space = Write(Bank.C3, src, "Config_3_default") + + # Update the JSR for Config default #3 + config3_loc = space.start_address + space = Reserve(0x370c5, 0x370c7, "Config_3_default") + space.write( + asm.JSR(config3_loc, asm.ABS), + ) From 6d76ecf4088e7d1f0dc4295dae7835eac26b8549 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:51:06 -0700 Subject: [PATCH 24/60] Making Party warp to Arvis house following K@N (#4) --- event/narshe_battle.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/event/narshe_battle.py b/event/narshe_battle.py index 59777176..abc7e062 100644 --- a/event/narshe_battle.py +++ b/event/narshe_battle.py @@ -208,6 +208,19 @@ def end_event_mod(self, reward_instructions): reward_instructions, field.SetParty(1), + + # ref: CB/7217 + field.HoldScreen(), + field.DisableEntityCollision(field_entity.PARTY0), + + field.EntityAct(field_entity.PARTY0, True, + field_entity.SetSpeed(field_entity.Speed.FAST), + field_entity.Move(direction.DOWN, 8), + ), + + field.FadeOutScreen(4), + field.WaitForFade(), + field.Call(field.REMOVE_ALL_CHARACTERS_FROM_ALL_PARTIES), field.Call(field.REFRESH_CHARACTERS_AND_SELECT_PARTY), @@ -215,8 +228,10 @@ def end_event_mod(self, reward_instructions): field.ShowEntity(field_entity.PARTY0), field.RefreshEntities(), - field.LoadMap(0x16, direction.DOWN, default_music = True, - x = 19, y = 36, fade_in = True, entrance_event = True), + field.FreeScreen(), + field.LoadMap(0x1e, direction.DOWN, default_music = True, + x = 60, y = 37, fade_in = True, entrance_event = True), + field.FinishCheck(), field.Return(), ] From 2662a91c86b91ed6b947e32dcf7bd9a2761e2160 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:51:31 -0700 Subject: [PATCH 25/60] Feature: Adding -llr flag to randomize L.x lore levels (#3) * Adding -llr flag to randomize L.x lore levels * Updating lore battle dialogs --- args/lores.py | 6 +++++ data/data.py | 2 +- data/lore.py | 11 ++++++++- data/lores.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/args/lores.py b/args/lores.py index b7db179c..c4acb9f2 100644 --- a/args/lores.py +++ b/args/lores.py @@ -23,6 +23,9 @@ def parse(parser): lores.add_argument("-lel", "--lores-everyone-learns", action = "store_true", help = "Lores learnable by characters without the Lore command") + lores.add_argument("-llr", "--lores-level-randomize", action = "store_true", + help = "Level based lores will have the level randomized (L?, L1-L5)") + def process(args): args._process_min_max("start_lores_random") args._process_min_max("lores_mp_random_value") @@ -44,6 +47,8 @@ def flags(args): if args.lores_everyone_learns: flags += " -lel" + if args.lores_level_randomize: + flags += " -llr" return flags def options(args): @@ -63,6 +68,7 @@ def options(args): ("Start Lores", start_lores), ("MP", mp), ("Everyone Learns", args.lores_everyone_learns), + ("Lx Level Random", args.lores_level_randomize) ] def menu(args): diff --git a/data/data.py b/data/data.py index 67e118a4..1d22f42e 100644 --- a/data/data.py +++ b/data/data.py @@ -48,7 +48,7 @@ def __init__(self, rom, args): self.blitzes.mod() self.lores = lores.Lores(rom, args, self.characters) - self.lores.mod() + self.lores.mod(self.dialogs) self.rages = rages.Rages(rom, args, self.enemies) self.rages.mod() diff --git a/data/lore.py b/data/lore.py index 6bd02c36..7167f626 100644 --- a/data/lore.py +++ b/data/lore.py @@ -2,11 +2,12 @@ import data.text as text class Lore(AbilityData): - def __init__(self, id, name_data, ability_data): + def __init__(self, id, name_data, ability_data, desc_data): super().__init__(id, ability_data) self.id = id self.name = text.get_string(name_data, text.TEXT2).rstrip('\0') + self.desc = text.get_string(desc_data, text.TEXT2).rstrip('\0') def name_data(self): from data.lores import Lores @@ -14,8 +15,16 @@ def name_data(self): data.extend([0xff] * (Lores.NAME_SIZE - len(data))) return data + def desc_data(self): + from data.lores import Lores + data = text.get_bytes(self.desc, text.TEXT2) + return data + def get_name(self): return self.name.strip('\0') + def get_desc(self): + return self.desc.strip('\0') + def print(self): print(f"{self.id} {self.get_name()}") diff --git a/data/lores.py b/data/lores.py index d1f6b60d..a9054f49 100644 --- a/data/lores.py +++ b/data/lores.py @@ -1,14 +1,16 @@ from data.lore import Lore from data.ability_data import AbilityData -from data.structures import DataBits, DataArray +from data.structures import DataBits, DataArray, DataList -from memory.space import Bank, Reserve, Allocate, Write +from memory.space import Bank, Reserve, Allocate, Write, Space import instruction.asm as asm class Lores: LORE_COUNT = 24 CONDEMNED, ROULETTE, CLEAN_SWEEP, AQUA_RAKE, AERO, BLOW_FISH, BIG_GUARD, REVENGE, PEARL_WIND, L_5_DOOM, L_4_FLARE, L_3_MUDDLE, REFLECT, L_PEARL, STEP_MINE, FORCE_FIELD, DISCHORD, SOUR_MOUTH, PEP_UP, RIPPLER, STONE, QUASAR, GRAND_TRAIN, EXPLODER = range(LORE_COUNT) + DIALOG_OFFSET = 139 # starting offset for battle dialog + INITIAL_LORES_START = 0x26f564 INITIAL_LORES_END = 0x26f566 @@ -16,6 +18,12 @@ class Lores: NAMES_END = 0x26fb65 NAME_SIZE = 10 + DESC_PTRS_START = 0x2d7a70 + DESC_PTRS_END = 0x2d7a9f + + DESC_START = 0x2d77a0 + DESC_END = 0x2d7a6f + ABILITY_DATA_START = 0x04725a ABILITY_DATA_END = 0x0473a9 @@ -28,9 +36,13 @@ def __init__(self, rom, args, characters): self.name_data = DataArray(self.rom, self.NAMES_START, self.NAMES_END, self.NAME_SIZE) self.ability_data = DataArray(self.rom, self.ABILITY_DATA_START, self.ABILITY_DATA_END, AbilityData.DATA_SIZE) + self.desc_data = DataList(Space.rom, self.DESC_PTRS_START, self.DESC_PTRS_END, + Space.rom.SHORT_PTR_SIZE, self.DESC_START, + self.DESC_START, self.DESC_END) + self.lores = [] for lore_index in range(len(self.ability_data)): - lore = Lore(lore_index, self.name_data[lore_index], self.ability_data[lore_index]) + lore = Lore(lore_index, self.name_data[lore_index], self.ability_data[lore_index], self.desc_data[lore_index]) self.lores.append(lore) def write_learners_table(self): @@ -160,7 +172,48 @@ def random_mp_percent(self): value = int(lore.mp * mp_percent) lore.mp = max(min(value, 254), 0) - def mod(self): + def _get_new_level_desc(lore_index, level_divisor): + level_string = f'LV{level_divisor}' # keeping it simple to not use extra space + new_desc = '' + if(lore_index == Lores.L_5_DOOM): + new_desc = f'Casts Doom" on {level_string} enemy' + elif(lore_index == Lores.L_4_FLARE): + new_desc = f'Casts Flare" on {level_string} enemy' + elif(lore_index == Lores.L_3_MUDDLE): + new_desc = f'Casts Muddle" on {level_string} enemy' + elif(lore_index == Lores.L_PEARL): + new_desc = f'Pearl attack on {level_string} enemy' + else: + raise ValueError(f'Unexpected lore index: {lore_index}') + return new_desc + + def random_lx_levels(self, dialogs): + import random, re + LX_LORE_IDX = [Lores.L_5_DOOM, Lores.L_4_FLARE, Lores.L_3_MUDDLE, Lores.L_PEARL] + LQ_EFFECT = 29 # the AbilityData.effect setting for L? + NO_EFFECT = 255 # The AbilityData.effect setting for no effect + MAX_DIVISOR = 5 + + for lore_index in LX_LORE_IDX: + lore = self.lores[lore_index] + + level_divisor = random.randint(0, MAX_DIVISOR) + if lore_index == Lores.L_5_DOOM: + # prevent soft-locks with bosses by removing unmissable doom + level_divisor = random.randint(2, MAX_DIVISOR) + + lore.accuracy = level_divisor + if level_divisor: # non-zero + lore.effect = NO_EFFECT + else: # zero - use for L? + level_divisor = '?' + lore.effect = LQ_EFFECT + lore.name = re.sub('L.*[?1-9]', f'L.{level_divisor}', lore.name) + lore.desc = Lores._get_new_level_desc(lore_index, level_divisor) + battle_message = re.sub('', '“', lore.desc) + dialogs.set_battle_message_text(self.DIALOG_OFFSET + lore_index, battle_message) + + def mod(self, dialogs): self.write_learners_table() self.write_is_learner() self.after_battle_check_mod() @@ -175,6 +228,9 @@ def mod(self): elif self.args.lores_mp_random_percent: self.random_mp_percent() + if self.args.lores_level_randomize: + self.random_lx_levels(dialogs) + def write(self): if self.args.spoiler_log: self.log() @@ -182,10 +238,12 @@ def write(self): for lore_index, lore in enumerate(self.lores): self.name_data[lore_index] = lore.name_data() self.ability_data[lore_index] = lore.ability_data() + self.desc_data[lore_index] = lore.desc_data() self.init_data.write() self.name_data.write() self.ability_data.write() + self.desc_data.write() def log(self): from log import section From 84cc4ba6ddb9ced116c98265f1e6b37bfdffd3eb Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 18 Dec 2022 14:52:08 -0700 Subject: [PATCH 26/60] QoL: Adding clarification of quantity of objective conditions required (#1) * Adding clarification of quantity of objective conditions required * responding to feedback on objective menu mods --- menus/objectives.py | 47 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/menus/objectives.py b/menus/objectives.py index 310aa6ff..75cc2a74 100644 --- a/menus/objectives.py +++ b/menus/objectives.py @@ -17,18 +17,28 @@ def __init__(self): self.lines = [] self.line_color_addresses = [] for oi, objective in enumerate(objectives): - condition_fraction = chr(self.special_characters_start + oi) + "/" + str(objective.conditions_required) - result_line = objective.letter + " " + str(objective.result) + " " + condition_fraction + result_line = objective.letter + " " + str(objective.result) self.lines.append(scroll_area.Line(result_line, f0.set_blue_text_color)) - for condition in objective.conditions: - condition_line = " " + str(condition) - line_color_address = condition.menu( - asm.JMP(f0.set_gray_text_color, asm.ABS), - asm.JMP(f0.set_user_text_color, asm.ABS), - ).space.start_address - self.lines.append(scroll_area.Line(condition_line, line_color_address)) + if len(objective.conditions) == 0: + self.lines.append(scroll_area.Line(" " + chr(self.special_characters_start + oi) + " required", f0.set_user_text_color)) + else: + conditions_required = " All " + str(objective.conditions_required) + " of" + if objective.conditions_required < len(objective.conditions): + conditions_required = " Any " + str(objective.conditions_required) + " of" + self.lines.append(scroll_area.Line(conditions_required, f0.set_user_text_color)) + for condition in objective.conditions: + condition_line = " " + str(condition) + # When you finish a condition, gray it out + line_color_address = condition.menu( + asm.JMP(f0.set_gray_text_color, asm.ABS), + asm.JMP(f0.set_user_text_color, asm.ABS), + ).space.start_address + self.lines.append(scroll_area.Line(condition_line, line_color_address)) + + completed_line = " -- " + chr(self.special_characters_start + oi) + " completed --" + self.lines.append(scroll_area.Line(completed_line, f0.set_user_text_color)) self.lines.append(scroll_area.Line("", f0.set_user_text_color)) if len(self.lines) == 0: @@ -54,6 +64,14 @@ def draw_character_mod(self): space = Write(Bank.F0, src, "objectives menu count conditions complete table") count_table = space.start_address + src = [] + for objective in objectives: + src += [ + objective.conditions_required.to_bytes(1, "little"), + ] + space = Write(Bank.F0, src, "objectives menu conditions required table") + conditions_required_table = space.start_address_snes + src = [ asm.CMP(self.special_characters_start, asm.IMM8), asm.BLT("WRITE_CHARACTER"), # branch if less than first special character value @@ -62,14 +80,25 @@ def draw_character_mod(self): asm.BGE("WRITE_CHARACTER"), # branch if greater than last special character value asm.PHX(), + asm.PHY(), asm.XY8(), asm.SEC(), asm.SBC(self.special_characters_start, asm.IMM8), # a = 0 based objective index + asm.TAY(), # y = 0 based objective index asm.ASL(), # a = objective index * 2 asm.TAX(), # x = objective index * 2 asm.JSR(count_table, asm.ABS_X_16), # x = number conditions complete asm.TXA(), # a = number conditions complete + asm.TYX(), # x = 0 based objective index + # when you finish an objective, gray it out + asm.CMP(conditions_required_table, asm.LNG_X), + asm.BLT("SKIP_COLOR_CHANGE"), + asm.PHA(), + asm.JSR(f0.set_gray_text_color, asm.ABS), + asm.PLA(), + "SKIP_COLOR_CHANGE", asm.XY16(), + asm.PLY(), asm.PLX(), asm.CLC(), asm.ADC(text_value['0'], asm.IMM8), # a = number conditions complete converted to character From fda338079ea7a19f19a489845dc2619e4d88f846 Mon Sep 17 00:00:00 2001 From: FF6 Worlds Collide <115676184+ff6wc@users.noreply.github.com> Date: Sun, 18 Dec 2022 16:57:10 -0500 Subject: [PATCH 27/60] Update version.py v1.1.0 -> v.1.2.0 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 6849410a..c68196d1 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = "1.1.0" +__version__ = "1.2.0" From 0bfde9d1e5a5f2f8fb65302e53540dc752bc51f3 Mon Sep 17 00:00:00 2001 From: FF6 Worlds Collide <115676184+ff6wc@users.noreply.github.com> Date: Mon, 19 Dec 2022 00:20:30 -0500 Subject: [PATCH 28/60] Update flags.py Updating to match dev branch --- menus/flags.py | 55 ++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/menus/flags.py b/menus/flags.py index 4b390d83..35938c08 100644 --- a/menus/flags.py +++ b/menus/flags.py @@ -12,31 +12,34 @@ class Flags(scroll_area.ScrollArea): def __init__(self): self.lines = [] self.submenus = {} # dictionary of submenus. key = line number, value = ScrollArea derived class - for _, group in args.group_modules.items(): - if hasattr(group, "menu"): - name, options = group.menu(args) - - self.lines.append(scroll_area.Line(name, f0.set_blue_text_color)) - for option in options: - key, value = option - - key = " " + key.replace("&", "+") - - # if we're given a scroll area, save it as a sub-menu with a value of X …, where X is the number of items in the sub-menu - if isinstance(value, scroll_area.ScrollArea): - self.submenus[len(self.lines)] = value - value = f"{value.number_items} {chr(text_value['…'])}" - - value = str(value) - if value == "True": - value = "T" - elif value == "False": - value = "F" - - padding = scroll_area.WIDTH - (len(key) + len(value)) - self.lines.append(scroll_area.Line(f"{key}{' ' * padding}{value}", f0.set_user_text_color)) - - self.lines.append(scroll_area.Line("", f0.set_user_text_color)) - del self.lines[-1] # exclude final empty line + if args.hide_flags: + self.lines.append(scroll_area.Line("Flags Hidden", f0.set_blue_text_color)) + else: + for _, group in args.group_modules.items(): + if hasattr(group, "menu"): + name, options = group.menu(args) + + self.lines.append(scroll_area.Line(name, f0.set_blue_text_color)) + for option in options: + key, value = option + + key = " " + key.replace("&", "+") + + # if we're given a scroll area, save it as a sub-menu with a value of X …, where X is the number of items in the sub-menu + if isinstance(value, scroll_area.ScrollArea): + self.submenus[len(self.lines)] = value + value = f"{value.number_items} {chr(text_value['…'])}" + + value = str(value) + if value == "True": + value = "T" + elif value == "False": + value = "F" + + padding = scroll_area.WIDTH - (len(key) + len(value)) + self.lines.append(scroll_area.Line(f"{key}{' ' * padding}{value}", f0.set_user_text_color)) + + self.lines.append(scroll_area.Line("", f0.set_user_text_color)) + del self.lines[-1] # exclude final empty line super().__init__() From 5e8737085016dcb44dd74440cc72e882cb1ad88a Mon Sep 17 00:00:00 2001 From: FF6 Worlds Collide <115676184+ff6wc@users.noreply.github.com> Date: Mon, 19 Dec 2022 00:21:22 -0500 Subject: [PATCH 29/60] Update spells.py Updated to match dev branch --- data/spells.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/spells.py b/data/spells.py index 459a49c4..b6071edf 100644 --- a/data/spells.py +++ b/data/spells.py @@ -64,6 +64,10 @@ def no_mp_scan(self): scan_id = name_id["Scan"] self.spells[scan_id].mp = 0 + def no_mp_warp(self): + warp_id = name_id["Warp"] + self.spells[warp_id].mp = 0 + def ultima_254_mp(self): ultima_id = name_id["Ultima"] self.spells[ultima_id].mp = 254 @@ -102,6 +106,8 @@ def mod(self): # Apply No MP Scan after any MP shuffle/rando if self.args.scan_all: self.no_mp_scan() + if self.args.warp_all: + self.no_mp_warp() # Apply Ultima 254 MP after any MP shuffle/rando if self.args.ultima_254_mp: From a018e84ff2becf5ba992f4f8ad89efe0fcbab41f Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Mon, 19 Dec 2022 07:30:26 -0700 Subject: [PATCH 30/60] Feature: Making Top 4 Magitek commands an objective result (#21) * Making Terra's Magitek commands an objective result * Minor changes to address PR comments Co-authored-by: Will Jones --- battle/__init__.py | 1 + battle/magitek_upgrade.py | 101 ++++++++++++++++++++++++++ constants/objectives/results.py | 2 + data/magiteks.py | 23 +----- objectives/results/magitek_upgrade.py | 14 ++++ 5 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 battle/magitek_upgrade.py create mode 100644 objectives/results/magitek_upgrade.py diff --git a/battle/__init__.py b/battle/__init__.py index 5734ed52..1bafc283 100644 --- a/battle/__init__.py +++ b/battle/__init__.py @@ -5,6 +5,7 @@ import battle.suplex_train_check import battle.auto_status import battle.end_checks +import battle.magitek_upgrade from battle.animations import Animations __all__ = ["Battle"] diff --git a/battle/magitek_upgrade.py b/battle/magitek_upgrade.py new file mode 100644 index 00000000..928063b0 --- /dev/null +++ b/battle/magitek_upgrade.py @@ -0,0 +1,101 @@ +from memory.space import Bank, START_ADDRESS_SNES, Reserve, Write, Read +import instruction.asm as asm + +import data.event_bit as event_bit +import objectives + +class _MagitekUpgrade: + '''Set the Magitek menu in battle to match the Magitek Upgrade objective result.''' + def __init__(self): + # Write our 2 magitek tables + # We're moving them from C1/910C - C1/911B + # Default: Match Regular character's default + src = [ + 0x00, 0x01, #FIRE_BEAM, BOLT_BEAM, + 0x02, 0xFF, #ICE_BEAM, , + 0x04, 0xFF, #HEAL_FORCE, , + 0xFF, 0xFF #, + ] + space = Write(Bank.F0, src, "magitek default table") + magitek_default_table_addr = space.start_address + + # Upgraded: Match Terra's options + src = [ + 0x00, 0x01, #FIRE_BEAM, BOLT_BEAM, + 0x02, 0x03, #ICE_BEAM, BIO_BLAST, + 0x04, 0x05, #HEAL_FORCE, CONFUSER, + 0x06, 0x07 #X_FER, TEKMISSILE + ] + space = Write(Bank.F0, src, "magitek upgraded table") + magitek_upgraded_table_addr = space.start_address + + # Write our modifications to the C1 routines that use the + # magitek tables. + # There are 2 that use the magitek tables in C1: + # 1) C1/4D42 - C1/4D6D builds the magitek menu by writing to $575A & $5760 + # 2) C1/866A - C1/8683 used when selecting from the menu - + # stores the menu option in A. + self.magitek_upgrade_name = "Magitek Upgrade" + magitek_upgrade_name_upper = self.magitek_upgrade_name.upper() + + # 1) Build the magitek menu + branch_name = f"{magitek_upgrade_name_upper}_MENU" + src = self.get_branch_if_objective_complete_src(branch_name) + src += [ + f"NO_{branch_name}", + asm.LDA(START_ADDRESS_SNES + magitek_default_table_addr, asm.LNG_X), + asm.STA(0x575A, asm.ABS), + asm.LDA(START_ADDRESS_SNES + magitek_default_table_addr + 1, asm.LNG_X), + asm.STA(0x5760, asm.ABS), + asm.RTL(), + branch_name, + asm.LDA(START_ADDRESS_SNES + magitek_upgraded_table_addr, asm.LNG_X), + asm.STA(0x575A, asm.ABS), + asm.LDA(START_ADDRESS_SNES + magitek_upgraded_table_addr + 1, asm.LNG_X), + asm.STA(0x5760, asm.ABS), + asm.RTL(), + ] + space = Write(Bank.F0, src, "build magitek menu") + build_magitek_menu_addr = space.start_address + + space = Reserve(0x14d42, 0x14d6d, "build magitek menu jsl", asm.NOP()) + space.write( + asm.JSL(START_ADDRESS_SNES + build_magitek_menu_addr), + ) + + # 2) Select from the magitek menu + branch_name = f"{magitek_upgrade_name_upper}_SELECT" + src = self.get_branch_if_objective_complete_src(branch_name) + src += [ + f"NO_{branch_name}", + asm.LDA(START_ADDRESS_SNES + magitek_default_table_addr, asm.LNG_X), + asm.RTL(), + branch_name, + asm.LDA(START_ADDRESS_SNES + magitek_upgraded_table_addr, asm.LNG_X), + asm.RTL(), + ] + space = Write(Bank.F0, src, "select from magitek menu") + select_from_magitek_menu_addr_snes = space.start_address_snes + + space = Reserve(0x1866a, 0x18683, "select from magitek menu jsl", asm.NOP()) + space.write( + asm.JSL(select_from_magitek_menu_addr_snes), + ) + + def get_branch_if_objective_complete_src(self, branch_name): + src = [] + if self.magitek_upgrade_name in objectives.results: + for objective in objectives.results[self.magitek_upgrade_name]: + objective_event_bit = event_bit.objective(objective.id) + bit = event_bit.bit(objective_event_bit) + address = event_bit.address(objective_event_bit) + + src += [ + asm.LDA(address, asm.ABS), + asm.AND(2 ** bit, asm.IMM8), + asm.BNE(branch_name), + ] + return src + +magitek_upgrade = _MagitekUpgrade() + diff --git a/constants/objectives/results.py b/constants/objectives/results.py index f777e916..e44717e4 100644 --- a/constants/objectives/results.py +++ b/constants/objectives/results.py @@ -85,6 +85,8 @@ ], } +#Additional results +category_types["Command"].append(ResultType(59, "Magitek Upgrade", "Magitek Upgrade", None)) category_types["Auto"].append(ResultType(61, "Auto Dog Block", "Auto Dog Block", None)) category_types["Auto"].append(ResultType(62, "Auto Life 3", "Auto Life 3", None)) diff --git a/data/magiteks.py b/data/magiteks.py index 6fcbfcd8..1dadccec 100644 --- a/data/magiteks.py +++ b/data/magiteks.py @@ -1,13 +1,12 @@ from data.magitek import Magitek from data.ability_data import AbilityData -from data.structures import DataBits, DataArray +from data.structures import DataArray from memory.space import Bank, Reserve, Allocate, Write class Magiteks: MAGITEK_COUNT = 8 FIRE_BEAM, BOLT_BEAM, ICE_BEAM, BIO_BLAST, HEAL_FORCE, CONFUSER, X_FER, TEKMISSILE = range(MAGITEK_COUNT) - DISABLED_MAGITEK = 0xFF NAMES_START = 0x26f9ad NAMES_END = 0x26f9fc @@ -16,21 +15,12 @@ class Magiteks: ABILITY_DATA_START = 0x0471ea ABILITY_DATA_END = 0x047259 - # These 8 bytes control the Magitek that Terra can use - TERRA_MAGITEK_ATTACKS_START = 0x1910C - TERRA_MAGITEK_ATTACKS_END = 0x19113 - # These 8 bytes control the Magitek that other characters can use - OTHER_CHAR_MAGITEK_ATTACKS_START = 0x19114 - OTHER_CHAR_MAGITEK_ATTACKS_END = 0x1911B - MAGITEK_ATTACKS_SIZE = 1 - def __init__(self, rom, args): self.rom = rom self.args = args self.name_data = DataArray(self.rom, self.NAMES_START, self.NAMES_END, self.NAME_SIZE) self.ability_data = DataArray(self.rom, self.ABILITY_DATA_START, self.ABILITY_DATA_END, AbilityData.DATA_SIZE) - self.other_char_magitek_attacks = DataArray(self.rom, self.OTHER_CHAR_MAGITEK_ATTACKS_START, self.OTHER_CHAR_MAGITEK_ATTACKS_END, self.MAGITEK_ATTACKS_SIZE) self.magiteks = [] for magitek_index in range(len(self.ability_data)): @@ -43,18 +33,8 @@ def fix_reflectable_beams(self): self.magiteks[self.BOLT_BEAM].flags2 = 0x22 self.magiteks[self.ICE_BEAM].flags2 = 0x22 - def give_all_magiteks(self): - # Give all magitek abilities to every character, not just Terra - # FF = disabled - # 0 = Fire Beam, ... 7 = TekMissile - self.other_char_magitek_attacks[self.BIO_BLAST] = self.BIO_BLAST.to_bytes(1, 'little') - self.other_char_magitek_attacks[self.CONFUSER] = self.CONFUSER.to_bytes(1, 'little') - self.other_char_magitek_attacks[self.X_FER] = self.X_FER.to_bytes(1, 'little') - self.other_char_magitek_attacks[self.TEKMISSILE] = self.TEKMISSILE.to_bytes(1, 'little') - def mod(self): self.fix_reflectable_beams() - self.give_all_magiteks() pass def write(self): @@ -67,7 +47,6 @@ def write(self): self.name_data.write() self.ability_data.write() - self.other_char_magitek_attacks.write() def log(self): pass diff --git a/objectives/results/magitek_upgrade.py b/objectives/results/magitek_upgrade.py new file mode 100644 index 00000000..381edcde --- /dev/null +++ b/objectives/results/magitek_upgrade.py @@ -0,0 +1,14 @@ +from objectives.results._objective_result import * + +class Field(field_result.Result): + def src(self): + return [] + +class Battle(battle_result.Result): + def src(self): + return [] + +class Result(ObjectiveResult): + NAME = "Magitek Upgrade" + def __init__(self): + super().__init__(Field, Battle) From 766f2999a8ce42067fc47f8f095c56929ed6585c Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Mon, 19 Dec 2022 07:31:12 -0700 Subject: [PATCH 31/60] Feature: Adding warp-all flag for 0 cost starting Warp (#9) * Adding warp-all flag for 0 cost starting Warp. Refactor ScanAll class to InitialSpells * Adding missed flag to log Co-authored-by: Will Jones --- args/misc.py | 5 +++ settings/__init__.py | 4 +-- settings/initial_spells.py | 68 ++++++++++++++++++++++++++++++++++++++ settings/scan_all.py | 48 --------------------------- 4 files changed, 75 insertions(+), 50 deletions(-) create mode 100644 settings/initial_spells.py delete mode 100644 settings/scan_all.py diff --git a/args/misc.py b/args/misc.py index 2f216557..525e253a 100644 --- a/args/misc.py +++ b/args/misc.py @@ -13,6 +13,8 @@ def parse(parser): help = "Randomize clock's correct time and NPC clues in Zozo") misc.add_argument("-scan", "--scan-all", action = "store_true", help = "All enemies scannable. All characters start with scan learned. Scan costs 0 MP. Useful for testing/debugging") + misc.add_argument("-warp", "--warp-all", action = "store_true", + help = "All characters start with Warp learned. Warp costs 0 MP. Useful for seeds that limit Warp Stone access") event_timers = misc.add_mutually_exclusive_group() event_timers.add_argument("-etr", "--event-timers-random", action = "store_true", @@ -65,6 +67,8 @@ def flags(args): flags += " -rc" if args.scan_all: flags += " -scan" + if args.warp_all: + flags += " -warp" if args.event_timers_random: flags += " -etr" @@ -129,6 +133,7 @@ def options(args): ("Random RNG", args.random_rng), ("Random Clock", args.random_clock), ("Scan All", args.scan_all), + ("Warp All", args.warp_all), ("Event Timers", event_timers), ("Y NPC", y_npc), ] diff --git a/settings/__init__.py b/settings/__init__.py index 5c50bcd0..87a91b2b 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -1,5 +1,5 @@ from settings.auto_sprint import AutoSprint -from settings.scan_all import ScanAll +from settings.initial_spells import InitialSpells from settings.random_rng import RandomRNG from settings.permadeath import Permadeath from settings.y_npc import YNPC @@ -12,7 +12,7 @@ class Settings: def __init__(self): self.auto_sprint = AutoSprint() - self.scan_all = ScanAll() + self.initial_spells = InitialSpells() self.random_rng = RandomRNG() self.permadeath = Permadeath() self.y_npc = YNPC() diff --git a/settings/initial_spells.py b/settings/initial_spells.py new file mode 100644 index 00000000..cc83fecf --- /dev/null +++ b/settings/initial_spells.py @@ -0,0 +1,68 @@ +from memory.space import START_ADDRESS_SNES, Bank, Reserve, Write, Read +import instruction.asm as asm +import args + +class InitialSpells: + def __init__(self): + from data.spell_names import name_id + + self.initial_spells = [] + if args.scan_all: + self.initial_spells.append(name_id["Scan"]) + + if args.warp_all: + self.initial_spells.append(name_id["Warp"]) + + if len(self.initial_spells) > 0: + self.teach_spells() + + def teach_spells(self): + from data.spells import Spells + from data.characters import Characters + + learned_spells_start = 0x1a6e + learner_count = Characters.CHARACTER_COUNT - 2 # no gogo/umaro + last_offset = Spells.SPELL_COUNT * learner_count + + src = [ + Read(0x0bdcc, 0x0bdd6), # initialize spells to 0% learned + + asm.PHY(), + asm.LDX(0x00, asm.DIR), # x = 0x0000 + + "CHARACTER_LOOP_START", + ] + for spell_id in self.initial_spells: + src += [ + # Put spell offset from learned_spells_start in Y (X [character offset] + spell_id) + asm.A16(), + asm.TXA(), + asm.CLC(), + asm.ADC(spell_id, asm.IMM16), + asm.TAY(), + asm.A8(), + # Write 0xff to the learned_spells_start + Y to initialize it as learned + asm.LDA(0xff, asm.IMM8), # a = 0xff (spell learned value) + asm.STA(learned_spells_start, asm.ABS_Y), # set spell learned for current character + ] + src += [ + asm.A16(), + asm.TXA(), # a = spell address offset for current character + asm.CLC(), + asm.ADC(Spells.SPELL_COUNT, asm.IMM16), # go to next character + asm.TAX(), # x = spell address offset for next character + asm.A8(), + asm.CPX(last_offset, asm.IMM16), # all characters done? + asm.BLT("CHARACTER_LOOP_START"), # branch if not + + asm.PLY(), + asm.TDC(), + asm.RTL(), + ] + space = Write(Bank.F0, src, "scan all learn_scan") + learn_spells_snes = space.start_address_snes + + space = Reserve(0x0bdcc, 0x0bdd6, "initialize spells and learn initial", asm.NOP()) + space.write( + asm.JSL(learn_spells_snes), + ) diff --git a/settings/scan_all.py b/settings/scan_all.py deleted file mode 100644 index 140f6491..00000000 --- a/settings/scan_all.py +++ /dev/null @@ -1,48 +0,0 @@ -from memory.space import Bank, Reserve, Write, Read -import instruction.asm as asm -import args - -class ScanAll: - def __init__(self): - if args.scan_all: - self.teach_scan() - - def teach_scan(self): - from data.spells import Spells - from data.spell_names import name_id - from data.characters import Characters - - learned_spells_start = 0x1a6e - scan_id = name_id["Scan"] - - start_addr = learned_spells_start + scan_id - learner_count = Characters.CHARACTER_COUNT - 2 # no gogo/umaro - last_offset = Spells.SPELL_COUNT * learner_count - - src = [ - Read(0x0bdcc, 0x0bdd6), # initialize spells to 0% learned - - asm.LDX(0x00, asm.DIR), # x = 0x0000 - - "LOOP_START", - asm.LDA(0xff, asm.IMM8), # a = 0xff (spell learned value) - asm.STA(start_addr, asm.ABS_X), # set scan learned for current character - asm.A16(), - asm.TXA(), # a = scan address offset for current character - asm.CLC(), - asm.ADC(Spells.SPELL_COUNT, asm.IMM16), # go to next character - asm.TAX(), # x = scan address offset for next character - asm.A8(), - asm.CPX(last_offset, asm.IMM16), # all characters done? - asm.BLT("LOOP_START"), # branch if not - - asm.TDC(), - asm.RTS(), - ] - space = Write(Bank.C0, src, "scan all learn_scan") - learn_scan = space.start_address - - space = Reserve(0x0bdcc, 0x0bdd6, "initialize spells and learn scan", asm.NOP()) - space.write( - asm.JSR(learn_scan, asm.ABS), - ) From a10b2d8bb5ef9b877e462b2cc4063737f00ec09e Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Mon, 19 Dec 2022 07:31:42 -0700 Subject: [PATCH 32/60] Adding -hf flag to hide flags for fun mystery seeds (#2) Co-authored-by: Will Jones --- args/arguments.py | 1 + log/__init__.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/args/arguments.py b/args/arguments.py index 2326571f..0f78b26c 100644 --- a/args/arguments.py +++ b/args/arguments.py @@ -25,6 +25,7 @@ def __init__(self): self.parser.add_argument("-nro", dest = "no_rom_output", action = "store_true", help = "Do not output a modified rom file") self.parser.add_argument("-slog", dest = "stdout_log", action = "store_true", help = "Write log to stdout instead of file") + self.parser.add_argument("-hf", dest = "hide_flags", action = "store_true", help = "Hide Flags (no log, no flags menu)") for group in self.group_modules.values(): group.parse(self.parser) diff --git a/log/__init__.py b/log/__init__.py index d2cfeb71..e7d89da5 100644 --- a/log/__init__.py +++ b/log/__init__.py @@ -20,7 +20,8 @@ if args.website_link: log_msg += f"Website {args.website_link}\n" log_msg += f"Seed {args.seed}\n" -log_msg += f"Flags {args.flags}\n" +if not args.hide_flags: + log_msg += f"Flags {args.flags}\n" log_msg += f"Hash {', '.join([entry.name for entry in args.sprite_hash])}" if args.debug: @@ -31,4 +32,5 @@ if not args.stdout_log: print(log_msg) -args.log() +if not args.hide_flags: + args.log() From b8515ed72be8335c13946df63b973a684339c588 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Mon, 19 Dec 2022 08:05:00 -0700 Subject: [PATCH 33/60] Feature: Add movement options, b-dash, sprint shoe options (#16) Co-authored-by: Kiel <95580337+kielbasiago@users.noreply.github.com> --- args/challenges.py | 6 ++ args/misc.py | 27 ++++- args/starting_gold_items.py | 6 ++ constants/objectives/results.py | 1 + data/items.py | 5 + data/movement.py | 34 ++++++ event/start.py | 5 +- objectives/results/sprint_shoes.py | 19 ++++ settings/__init__.py | 4 +- settings/auto_sprint.py | 36 ------- settings/movement.py | 164 +++++++++++++++++++++++++++++ 11 files changed, 263 insertions(+), 44 deletions(-) create mode 100644 data/movement.py create mode 100644 objectives/results/sprint_shoes.py delete mode 100644 settings/auto_sprint.py create mode 100644 settings/movement.py diff --git a/args/challenges.py b/args/challenges.py index 50acd3eb..44fd76f3 100644 --- a/args/challenges.py +++ b/args/challenges.py @@ -10,6 +10,8 @@ def parse(parser): challenges.add_argument("-nil", "--no-illuminas", action = "store_true", help = "Illuminas will not appear in coliseum/auction/shops/chests/events") ultima = challenges.add_mutually_exclusive_group() + challenges.add_argument("-noshoes", "--no-sprint-shoes", action = "store_true", + help = "Sprint Shoes will not appear in coliseum/auction/shops/chests") ultima.add_argument("-nu", "--no-ultima", action = "store_true", help = "Ultima cannot be learned from espers/items/natural magic") ultima.add_argument("-u254", "--ultima-254-mp", action = "store_true", @@ -68,6 +70,9 @@ def flags(args): flags += " -nee" if args.no_illuminas: flags += " -nil" + if args.no_sprint_shoes: + flags += " -noshoes" + if args.no_ultima: flags += " -nu" elif args.ultima_254_mp: @@ -96,6 +101,7 @@ def options(args): ("No Exp Eggs", args.no_exp_eggs), ("No Illuminas", args.no_illuminas), ("Ultima", ultima), + ("No Sprint Shoes", args.no_sprint_shoes), ("No Free Paladin Shields", args.no_free_paladin_shields), ("No Free Characters/Espers", args.no_free_characters_espers), ("Permadeath", args.permadeath), diff --git a/args/misc.py b/args/misc.py index 525e253a..43c32153 100644 --- a/args/misc.py +++ b/args/misc.py @@ -1,10 +1,11 @@ + + def name(): return "Misc." def parse(parser): misc = parser.add_argument_group("Misc.") - misc.add_argument("-as", "--auto-sprint", action = "store_true", - help = "Player always sprints. Sprint Shoes have no effect") + misc.add_argument("-ond", "--original-name-display", action = "store_true", help = "Display original character names in party and party select menus") misc.add_argument("-rr", "--random-rng", action = "store_true", @@ -16,6 +17,15 @@ def parse(parser): misc.add_argument("-warp", "--warp-all", action = "store_true", help = "All characters start with Warp learned. Warp costs 0 MP. Useful for seeds that limit Warp Stone access") + from data.movement import ALL + movement = misc.add_mutually_exclusive_group() + movement.name = "Movement" + movement.add_argument("-move", "--movement", type = str.lower, choices = ALL, + help = "Player movement options") + # Completely ignore this argument, and default to auto sprint when -move is not defined + misc.add_argument("-as", "--auto-sprint", action = "store_true", + help = "DEPRECATED - Use `-move as` instead. Player always sprints. Sprint Shoes have no effect") + event_timers = misc.add_mutually_exclusive_group() event_timers.add_argument("-etr", "--event-timers-random", action = "store_true", help = "Collapsing House, Opera House, and Floating Continent timers randomized") @@ -57,8 +67,8 @@ def process(args): def flags(args): flags = "" - if args.auto_sprint: - flags += " -as" + if args.movement: + flags += f" -move {args.movement}" if args.original_name_display: flags += " -ond" if args.random_rng: @@ -127,8 +137,15 @@ def options(args): elif args.y_npc_remove: y_npc = "Remove" + from data.movement import key_name, AUTO_SPRINT + # Similar logic is present in the init fn of settings/movement.py + if args.movement: + movement = key_name[args.movement] + else: + movement = key_name[AUTO_SPRINT] + return [ - ("Auto Sprint", args.auto_sprint), + ("Movement", movement), ("Original Name Display", args.original_name_display), ("Random RNG", args.random_rng), ("Random Clock", args.random_clock), diff --git a/args/starting_gold_items.py b/args/starting_gold_items.py index dcf3c8ac..b769e8a3 100644 --- a/args/starting_gold_items.py +++ b/args/starting_gold_items.py @@ -9,6 +9,9 @@ def parse(parser): starting_gold_items.add_argument("-smc", "--start-moogle-charms", default = 0, type = int, choices = range(4), metavar = "COUNT", help = "Start game with %(metavar)s Moogle Charms. Overrides No Moogle Charms option") + starting_gold_items.add_argument("-sshoes", "--start-sprint-shoes", default = 0, type = int, choices = range(4), metavar = "COUNT", + help = "Start game with %(metavar)s Sprint Shoes. Overrides No Sprint Shoes option") + starting_gold_items.add_argument("-sws", "--start-warp-stones", default = 0, type = int, choices = range(11), metavar = "COUNT", help = "Start game with %(metavar)s Warp Stones") starting_gold_items.add_argument("-sfd", "--start-fenix-downs", default = 0, type = int, choices = range(11), metavar = "COUNT", @@ -28,6 +31,8 @@ def flags(args): flags += f" -gp {args.gold}" if args.start_moogle_charms != 0: flags += f" -smc {args.start_moogle_charms}" + if args.start_sprint_shoes != 0: + flags += f" -sshoes {args.start_sprint_shoes}" if args.start_warp_stones != 0: flags += f" -sws {args.start_warp_stones}" if args.start_fenix_downs != 0: @@ -43,6 +48,7 @@ def options(args): return [ ("Start Gold", args.gold), ("Start Moogle Charms", args.start_moogle_charms), + ("Start Sprint Shoes", args.start_sprint_shoes), ("Start Warp Stones", args.start_warp_stones), ("Start Fenix Downs", args.start_fenix_downs), ("Start Tools", args.start_tools), diff --git a/constants/objectives/results.py b/constants/objectives/results.py index e44717e4..445bb88d 100644 --- a/constants/objectives/results.py +++ b/constants/objectives/results.py @@ -87,6 +87,7 @@ #Additional results category_types["Command"].append(ResultType(59, "Magitek Upgrade", "Magitek Upgrade", None)) +category_types["Item"].append(ResultType(60, "Sprint Shoes", "Sprint Shoes", None)) category_types["Auto"].append(ResultType(61, "Auto Dog Block", "Auto Dog Block", None)) category_types["Auto"].append(ResultType(62, "Auto Life 3", "Auto Life 3", None)) diff --git a/data/items.py b/data/items.py index e625868d..a03afa2f 100644 --- a/data/items.py +++ b/data/items.py @@ -297,6 +297,11 @@ def get_excluded(self): exclude.append(name_id["Exp. Egg"]) if self.args.no_illuminas: exclude.append(name_id["Illumina"]) + + from data.movement import AUTO_SPRINT, B_DASH + # Sprint Shoes are a literal dead item if any of these options + if self.args.no_sprint_shoes or self.args.movement in [AUTO_SPRINT, B_DASH]: + exclude.append(name_id["Sprint Shoes"]) if self.args.no_free_paladin_shields: exclude.append(name_id["Paladin Shld"]) exclude.append(name_id["Cursed Shld"]) diff --git a/data/movement.py b/data/movement.py new file mode 100644 index 00000000..b8c52601 --- /dev/null +++ b/data/movement.py @@ -0,0 +1,34 @@ + +from enum import IntEnum + + +class MovementSpeed(IntEnum): + WALK = 2 # Original FF6 walk speed + SPRINT = 3 # Original FF6 sprint speed + DASH = 4 # Custom, move twice as fast as sprint. + +ORIGINAL = 'og' +AUTO_SPRINT = 'as' +B_DASH = 'bd' +SPRINT_SHOES_B_DASH = 'ssbd' + +name_key = { + # WALK by default + # SPRINT with sprint shoes equipped + 'ORIGINAL' : ORIGINAL, + # SPRINT by default + # WALK when holding B + 'AUTO_SPRINT' : AUTO_SPRINT, + # SPRINT by default + # DASH when holding B + 'B-DASH' : B_DASH, + # SPRINT by default + # DASH when holding B with sprint shoes equipped + # WALK when holding B without sprint shoes equipped + 'SPR SHOE B-DASH' : SPRINT_SHOES_B_DASH, +} + +key_name = {v: k for k, v in name_key.items()} + +ALL = [ORIGINAL, AUTO_SPRINT, B_DASH, SPRINT_SHOES_B_DASH] + diff --git a/event/start.py b/event/start.py index f63faeed..39f9b4b1 100644 --- a/event/start.py +++ b/event/start.py @@ -187,7 +187,10 @@ def start_items_mod(self): src += [ field.AddItem("Moogle Charm", sound_effect = False), ] - + for mc in range(self.args.start_sprint_shoes): + src += [ + field.AddItem("Sprint Shoes", sound_effect = False), + ] for ws in range(self.args.start_warp_stones): src += [ field.AddItem("Warp Stone", sound_effect = False), diff --git a/objectives/results/sprint_shoes.py b/objectives/results/sprint_shoes.py new file mode 100644 index 00000000..4318850c --- /dev/null +++ b/objectives/results/sprint_shoes.py @@ -0,0 +1,19 @@ +from objectives.results._objective_result import * +from data.item_names import name_id as item_name_id + +class Field(field_result.Result): + def src(self): + return [ + field.AddItem(item_name_id["Sprint Shoes"]), + ] + +class Battle(battle_result.Result): + def src(self): + return [ + battle_result.AddItem(item_name_id["Sprint Shoes"]), + ] + +class Result(ObjectiveResult): + NAME = "Sprint Shoes" + def __init__(self): + super().__init__(Field, Battle) diff --git a/settings/__init__.py b/settings/__init__.py index 87a91b2b..d0355987 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -1,5 +1,5 @@ -from settings.auto_sprint import AutoSprint from settings.initial_spells import InitialSpells +from settings.movement import Movement from settings.random_rng import RandomRNG from settings.permadeath import Permadeath from settings.y_npc import YNPC @@ -11,8 +11,8 @@ __all__ = ["Settings"] class Settings: def __init__(self): - self.auto_sprint = AutoSprint() self.initial_spells = InitialSpells() + self.movement = Movement() self.random_rng = RandomRNG() self.permadeath = Permadeath() self.y_npc = YNPC() diff --git a/settings/auto_sprint.py b/settings/auto_sprint.py deleted file mode 100644 index e03252db..00000000 --- a/settings/auto_sprint.py +++ /dev/null @@ -1,36 +0,0 @@ -from memory.space import Reserve -import instruction.asm as asm -import args - -class AutoSprint: - def __init__(self): - if args.auto_sprint: - self.mod() - - def mod(self): - # set sprint by default, b button to walk, sprint shoes do nothing - - WALK_SPEED = 2 - SPRINT_SPEED = 3 - - CONTROLLER1_BYTE2 = 0x4219 - B_BUTTON_MASK = 0x80 - FIELD_RAM_SPEED = 0x0875 - - src = [ - asm.LDA(CONTROLLER1_BYTE2, asm.ABS), - asm.AND(B_BUTTON_MASK, asm.IMM8), - asm.BNE("WALK"), # branch if b button down - - "SPRINT", - asm.LDA(SPRINT_SPEED, asm.IMM8), - asm.BRA("STORE_SPEED"), - - "WALK", - asm.LDA(WALK_SPEED, asm.IMM8), - - "STORE_SPEED", - asm.STA(FIELD_RAM_SPEED, asm.ABS_Y), - ] - space = Reserve(0x04e21, 0x04e37, "auto sprint", asm.NOP()) - space.write(src) diff --git a/settings/movement.py b/settings/movement.py new file mode 100644 index 00000000..de9b3e9f --- /dev/null +++ b/settings/movement.py @@ -0,0 +1,164 @@ +from memory.space import Allocate, Bank, Reserve, Write +import instruction.asm as asm +from data.movement import AUTO_SPRINT, B_DASH, ORIGINAL, SPRINT_SHOES_B_DASH, MovementSpeed + +class Movement: + def __init__(self): + import args + self.movement = args.movement or AUTO_SPRINT + + if self.movement != ORIGINAL: + self.mod() + + def mod(self): + length = 0 + src = [] + + src = self.get_auto_sprint_src() + + space = Write(Bank.F0, src, "Sprint subroutine") + + src = [ + asm.JSL(space.start_address_snes), + ] + space = Reserve(0x04e21, 0x04e37, "auto sprint", asm.NOP()) + space.write(src) + + self.sliding_dash_fix() + + + def get_auto_sprint_src(self): + import args + CURRENT_MAP_BYTE = 0x82 # 2 bytes + OWZERS_MANSION_ID = 0x00CF # the door room can create visual artifacts on the map while dashing + CONTROLLER1_BYTE2 = 0x4219 + SPRINT_SHOES_BYTE = 0x11df + SPRINT_SHOES_MASK = 0x20 + B_BUTTON_MASK = 0x80 + FIELD_RAM_SPEED = 0x0875 + + # moving at dash speed in Owzer's door room, or carrying it out via the door glitch will cause graphical artifacting randomly. + # Simply disabling B button in Owzers to keep it consistent in WC. Will not worry about the door glitch + owzers_src = [ + "CHECK_OWZERS", + asm.A16(), # set register A bit size to 16 + asm.LDA(CURRENT_MAP_BYTE, asm.ABS), # if current map owzers mansion, disable the b-button + asm.CMP(OWZERS_MANSION_ID, asm.IMM16), + asm.BEQ("STORE_DEFAULT"), + asm.LDA(0x0000, asm.IMM16), # clear A, otherwise will cause issues in albrook/imperial base + asm.A8(), + ] + + src = [ + "B_BUTTON_CHECK", + asm.LDA(CONTROLLER1_BYTE2, asm.ABS), + asm.AND(B_BUTTON_MASK, asm.IMM8), + asm.BEQ("STORE_DEFAULT"), # do nothing if b pressed + ] + + if self.movement == AUTO_SPRINT: + src += [ + "ON_B_BUTTON", + asm.LDA(MovementSpeed.WALK, asm.IMM8), + asm.BRA("STORE"), + ] + elif self.movement == B_DASH: + src += owzers_src + src += [ + "ON_B_BUTTON", + asm.LDA(MovementSpeed.DASH, asm.IMM8), + asm.BRA("STORE"), + ] + + elif self.movement == SPRINT_SHOES_B_DASH: + src += owzers_src + src += [ + "ON_B_BUTTON", + asm.LDA(SPRINT_SHOES_BYTE, asm.ABS), # If sprint shoes equipped, store secondary movement speed + asm.AND(SPRINT_SHOES_MASK, asm.IMM8), + asm.BEQ("WALK") + ] + src += [ + "DASH", + asm.LDA(MovementSpeed.DASH, asm.IMM8), + asm.BRA("STORE"), + "WALK", + asm.LDA(MovementSpeed.WALK, asm.IMM8), + asm.BRA("STORE"), + ] + + src += [ + "STORE_DEFAULT", + asm.A8(), + asm.LDA(MovementSpeed.SPRINT, asm.IMM8), + + "STORE", + asm.STA(FIELD_RAM_SPEED, asm.ABS_Y), # store speed in ram + asm.RTL(), # return + ] + + return src + + + # DIRECTION VALUE + # $087F ------dd + # d: facing direction + # 00 = up + # 01 = right + # 10 = down + # 11 = left + + # https://silentenigma.neocities.org/ff6/index.html + # Will leave bits of documentation about in the event neocities does not stand the test of time + + # With dash enabled, this causes a bug that the player will appear to be standing still when + # running down or right at move speed 5. This is because two sprite instances are thrown out of + # the animation cycle when running at that speed; while Up/Left correctly omits the standing + # sprite, Down/Right omits the stepping sprites, since the offsets lag by an iteration. + def sliding_dash_fix(self): + DIRECTION_VALUE = 0x087f + + # C0/0000: 4A LSR ; Shift offset bits right + # C0/0001: 4A LSR ; Shift offset bits right + # C0/0002: 48 PHA ; Push offset value to stack + # C0/0003: B9 7F 08 LDA $087F,y ; Load direction value + # C0/0006: C9 01 CMP #$01 ; Check if direction is Right + # C0/0008: F0 07 BEQ $D69E ; Branch if the direction is Right + # C0/000A: C9 02 CMP #$02 ; Check if the direction is Down + # C0/000C: F0 03 BEQ $D69E ; Branch if the direction is Down + # C0/000E: 68 PLA ; Pull offset back off of stack + # C0/000F: 80 02 BRA $D6A0 ; Branch to the third LSR + # C0/0011: 68 PLA ; Pull offset back off of stack + # C0/0012: 1A INC ; Increase the offset value by 1 + # C0/0013: 4A LSR ; Shift offset bits right + # C0/0014: 60 RTS ; Return from subfunction + subroutine_src = [ + asm.LSR(), + asm.LSR(), + asm.PHA(), + asm.LDA(DIRECTION_VALUE, asm.ABS_Y), + asm.DEC(), + asm.BEQ("FACING_RIGHT"), + asm.DEC(), + asm.BNE("RETURN"), + "FACING_RIGHT", + asm.PLA(), + asm.INC(), + asm.PHA(), + "RETURN", + asm.PLA(), + asm.LSR(), + asm.RTS(), + ] + subroutine_space = Allocate(Bank.C0, 20, "walking speed calculation", asm.NOP()) + subroutine_space.write(subroutine_src) + + src = [ + asm.JSR(subroutine_space.start_address, asm.ABS) + ] + + space = Reserve(0x5885, 0x5887, "Sprite offset calculation 1", asm.NOP()) + space.write(src) + + space = Reserve(0x5892, 0x5894, "Sprite offset calculation 2") + space.write(src) From 00421df446499b6d916de2d0c07f48e8085c7bae Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Thu, 22 Dec 2022 11:41:05 -0700 Subject: [PATCH 34/60] Showing MP in menus if character knows Lore --- data/lores.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/data/lores.py b/data/lores.py index a9054f49..55314114 100644 --- a/data/lores.py +++ b/data/lores.py @@ -213,10 +213,50 @@ def random_lx_levels(self, dialogs): battle_message = re.sub('', '“', lore.desc) dialogs.set_battle_message_text(self.DIALOG_OFFSET + lore_index, battle_message) + def show_mp_mod(self): + # Show Party member MP in menus if they have Lore, even if they don't know any Magic + # Thanks to Lenophis for most of this work: https://discord.com/channels/666661907628949504/931737764205047858/1054557544942673940 + src = [ + asm.JSR(0x30d2b, asm.ABS), # check to see if this character knows magic; this is displaced code + asm.BCS("magic_exit"), + # if we are at this point, we have a magic command but no magic. + # so now we are going to do a back-up check and see if a secondary command is present so MP can be shown + # if not, it will be grayed out as normal + asm.LDY(0x67, asm.DIR), # this gets set earlier in our route. Let's pull this character's index again for our back-up check + asm.LDX(0x0000, asm.IMM16), + "command_loop", + asm.LDA(0x0016, asm.ABS_Y), + asm.CMP(0x0C, asm.IMM8), # Lore + asm.BEQ("command_ok"), + asm.INY(), + asm.INX(), + asm.CPX(0x0004, asm.IMM16), # have we done 4 commands yet? + asm.BNE("command_loop"), # branch if not + # if we have exited the loop with no match, we need to flag MP to not show up + asm.CLC(), + asm.RTS(), + "command_ok", + # at this point, we have matched supplemental command, so let's flag MP as ok to show up + asm.SEC(), + "magic_exit", + asm.RTS(), + ] + space = Write(Bank.C3, src, "check for Lore") + mp_hook = space.start_address + space = Reserve(0x30cb7, 0x30cb9, "check for magic command") + space.write( + asm.JSR(mp_hook, asm.ABS), + ) + space = Reserve(0x36134, 0x36136, "check for magic command 2") + space.write( + asm.JSR(mp_hook, asm.ABS), + ) + def mod(self, dialogs): self.write_learners_table() self.write_is_learner() self.after_battle_check_mod() + self.show_mp_mod() if self.args.start_lores_random: self.start_random_lores() From 6851b401255e7fe844d6a877f5ac012721cdb57c Mon Sep 17 00:00:00 2001 From: Gerard Noseworthy Date: Sat, 4 Feb 2023 10:58:30 -0330 Subject: [PATCH 35/60] feature: Add filtering of low value items from shops and chests. flag: -nti --no-trash-items --- args/items.py | 5 +++++ constants/items.py | 21 +++++++++++++++++++++ data/chests.py | 11 +++++++++++ data/item.py | 10 ++++++++++ data/shops.py | 4 ++++ 5 files changed, 51 insertions(+) diff --git a/args/items.py b/args/items.py index b97737ce..b5707c14 100644 --- a/args/items.py +++ b/args/items.py @@ -49,6 +49,9 @@ def parse(parser): items.add_argument("-saw", "--stronger-atma-weapon", action = "store_true", help = "Atma Weapon moved to higher tier and divisor reduced from 64 to 32") + items.add_argument("-nti", "--no-trash-items", action = "store_true", + help="Replace Low Tier Items with gold in chests and skip adding to shop lists") + def process(args): args._process_min_max("item_equipable_random") if args.item_equipable_balanced_random is not None: @@ -107,6 +110,8 @@ def flags(args): if args.stronger_atma_weapon: flags += " -saw" + if args.no_trash_items: + flags += " -nti" return flags diff --git a/constants/items.py b/constants/items.py index 4617dedb..59d5a251 100644 --- a/constants/items.py +++ b/constants/items.py @@ -295,3 +295,24 @@ "Marvel Shoes", "Exp. Egg", ] + +## TRASH LIST https://docs.google.com/spreadsheets/d/1Cit5Xl_TCBPFI4q1NEVQhSYGH1wKY9st_1yee3lzCP8/edit#gid=0 + +TRASH_WEAPONS = [ + "Dirk", "MithrilKnife", "Guardian", "MithrilBlade", "RegalCutlass", "Crystal", "Ogre Nix", "Mithril Pike", + "Stout Spear", "Gold Lance", "Partisan", "Imperial", "Kodachi", "Hardened", "Ashura", "Kotetsu", "Forged", + "Aura", "Strato", "Poison Rod", "Punisher", "Gravity Rod", "MetalKnuckle", "Mithril Claw", "Kaiser", + "Flail", "Morning Star", "Full Moon", "Boomerang", "Rising Sun", "Cards", "Darts", "Chocobo Brsh", "DaVinci Brsh" + ] + +TRASH_ARMOR = ["Buckler", "Mithril Shld", "Heavy Shld", "Gold Shld", "Leather Hat", "Plumed Hat", "Bandana", + "Iron Helmet", "Mithril Helm", "Gold Helmet", "LeatherArmor", "Kung Fu Suit", "Mithril Vest", + "Diamond Vest", "Cotton Robe", "Silk Robe", "Iron Armor", "DiamondArmor", "Crystal Mail", + "Mithril Mail" +] +TRASH_RElICS = ["Charm Bangle", "Coin Toss", "FakeMustache"] + + +TRASH_ITEMS = TRASH_WEAPONS + TRASH_ARMOR + TRASH_RElICS + +TRASH_IDS = [name_id.get(value) for value in TRASH_ITEMS] diff --git a/data/chests.py b/data/chests.py index d80f8234..f4ee32d2 100644 --- a/data/chests.py +++ b/data/chests.py @@ -1,5 +1,6 @@ from data.chest import Chest import data.chests_asm as chests_asm +from data.item import Item from data.structures import DataArrays import random @@ -105,6 +106,16 @@ def shuffle_random(self): elif chest.type == Chest.ITEM: chest.contents = self.items.get_random() + if self.args.no_trash_items: + for chest in possible_chests: + if not chest.type == Chest.ITEM: + continue + item = Item(chest.contents, self.rom) + if item.is_trash: + chest.type = Chest.GOLD + chest.contents = item.sell_gold_value + + def random_tiered(self): def get_item(tiers, tier_s_distribution): from data.chest_item_tiers import weights diff --git a/data/item.py b/data/item.py index 578f4048..a1ed563f 100644 --- a/data/item.py +++ b/data/item.py @@ -1,4 +1,5 @@ import data.text as text +from constants.items import TRASH_IDS from data.text.text2 import text_value, value_text class Item(): @@ -20,6 +21,10 @@ def __init__(self, id, rom): self.read() + @property + def is_trash(self): + return self.id in TRASH_IDS + def is_equipable(self): return self.equipable_characters @@ -42,6 +47,11 @@ def remove_learnable_spell(self): self.learnable_spell = 0 self.learnable_spell_rate = 0 + @property + def sell_gold_value(self): + ## half price for sell and //100 + return self.price//200 + def scale_price(self, factor): self.price = int(self.price * factor) self.price = max(min(self.price, 2**16 - 1), 0) diff --git a/data/shops.py b/data/shops.py index a78d4c0b..60e7c6de 100644 --- a/data/shops.py +++ b/data/shops.py @@ -1,3 +1,5 @@ +from constants.items import TRASH_IDS +from data.item import Item from data.shop import Shop from data.structures import DataArray @@ -226,6 +228,8 @@ def remove_excluded_items(self): exclude.append(self.items.get_id("Exp. Egg")) if self.args.shops_no_illuminas: exclude.append(self.items.get_id("Illumina")) + if self.args.no_trash_items: + exclude.extend(TRASH_IDS) for shop in self.shops: for item in exclude: From 5d5bd0bb2d064e38f418c82e49e842d6fd359c08 Mon Sep 17 00:00:00 2001 From: Gerard Noseworthy Date: Sat, 4 Feb 2023 15:12:42 -0330 Subject: [PATCH 36/60] chore: Add gold and iron armor to trash --- constants/items.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/constants/items.py b/constants/items.py index 59d5a251..82e67e9b 100644 --- a/constants/items.py +++ b/constants/items.py @@ -307,7 +307,8 @@ TRASH_ARMOR = ["Buckler", "Mithril Shld", "Heavy Shld", "Gold Shld", "Leather Hat", "Plumed Hat", "Bandana", "Iron Helmet", "Mithril Helm", "Gold Helmet", "LeatherArmor", "Kung Fu Suit", "Mithril Vest", - "Diamond Vest", "Cotton Robe", "Silk Robe", "Iron Armor", "DiamondArmor", "Crystal Mail", + "Diamond Vest", "Cotton Robe", "Silk Robe", "Iron Armor", + "DiamondArmor", "Crystal Mail", "Gold Armor", "Mithril Mail" ] TRASH_RElICS = ["Charm Bangle", "Coin Toss", "FakeMustache"] From b974b52812d25e33d0ad57b652f8af865ccce0b9 Mon Sep 17 00:00:00 2001 From: Gerard Noseworthy Date: Sat, 4 Feb 2023 15:17:55 -0330 Subject: [PATCH 37/60] fix: when removing trash if item has no sell value asign chest empty. --- data/chests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/chests.py b/data/chests.py index f4ee32d2..200580aa 100644 --- a/data/chests.py +++ b/data/chests.py @@ -112,6 +112,8 @@ def shuffle_random(self): continue item = Item(chest.contents, self.rom) if item.is_trash: + if not item.sell_gold_value: + chest.type = Chest.EMPTY chest.type = Chest.GOLD chest.contents = item.sell_gold_value From 1fb7edd6a2fe9b9f72149a6e90295988212de198 Mon Sep 17 00:00:00 2001 From: Gerard Noseworthy Date: Sun, 5 Feb 2023 11:18:19 -0330 Subject: [PATCH 38/60] feat: split trash filtering on shops and chests via -nts and -ntc --- args/items.py | 14 +++++++++----- data/chests.py | 2 +- data/shops.py | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/args/items.py b/args/items.py index b5707c14..9ce5028b 100644 --- a/args/items.py +++ b/args/items.py @@ -49,8 +49,11 @@ def parse(parser): items.add_argument("-saw", "--stronger-atma-weapon", action = "store_true", help = "Atma Weapon moved to higher tier and divisor reduced from 64 to 32") - items.add_argument("-nti", "--no-trash-items", action = "store_true", - help="Replace Low Tier Items with gold in chests and skip adding to shop lists") + items.add_argument("-ntc", "--no-trash-chests", action = "store_true", + help="Replace Low Tier Items with gold in chests") + + items.add_argument("-nts", "--no-trash-shops", action = "store_true", + help="Omit Low tier items in shops") def process(args): args._process_min_max("item_equipable_random") @@ -110,9 +113,10 @@ def flags(args): if args.stronger_atma_weapon: flags += " -saw" - if args.no_trash_items: - flags += " -nti" - + if args.no_trash_chests: + flags += " -ntc" + if args.no_trash_shops: + flags += " -nts" return flags def options(args): diff --git a/data/chests.py b/data/chests.py index 200580aa..cd1d963d 100644 --- a/data/chests.py +++ b/data/chests.py @@ -106,7 +106,7 @@ def shuffle_random(self): elif chest.type == Chest.ITEM: chest.contents = self.items.get_random() - if self.args.no_trash_items: + if self.args.no_trash_chests: for chest in possible_chests: if not chest.type == Chest.ITEM: continue diff --git a/data/shops.py b/data/shops.py index 60e7c6de..264efb75 100644 --- a/data/shops.py +++ b/data/shops.py @@ -228,7 +228,7 @@ def remove_excluded_items(self): exclude.append(self.items.get_id("Exp. Egg")) if self.args.shops_no_illuminas: exclude.append(self.items.get_id("Illumina")) - if self.args.no_trash_items: + if self.args.no_trash_shops: exclude.extend(TRASH_IDS) for shop in self.shops: From 9287fa0993a359887789f674704f6867447f55fe Mon Sep 17 00:00:00 2001 From: Gerard Noseworthy Date: Sun, 5 Feb 2023 11:24:23 -0330 Subject: [PATCH 39/60] fix: place filter trash args to relevant parsers --- args/chests.py | 8 ++++++++ args/items.py | 10 ++-------- args/shops.py | 6 ++++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/args/chests.py b/args/chests.py index dfefa4e7..c7a1d384 100644 --- a/args/chests.py +++ b/args/chests.py @@ -18,6 +18,12 @@ def parse(parser): chests.add_argument("-cms", "--chest-monsters-shuffle", action = "store_true", help = "Monsters-in-a-box shuffled but locations unchanged") + chests.add_argument("-ntc", "--no-trash-chests", action = "store_true", + help="Replace Low Tier Items with gold in chests") + + + + def process(args): if args.chest_contents_shuffle_random is not None: args.chest_contents_shuffle_random_percent = args.chest_contents_shuffle_random @@ -37,6 +43,8 @@ def flags(args): if args.chest_monsters_shuffle: flags += " -cms" + if args.no_trash_chests: + flags += " -ntc" return flags diff --git a/args/items.py b/args/items.py index 9ce5028b..eed7a77f 100644 --- a/args/items.py +++ b/args/items.py @@ -49,11 +49,7 @@ def parse(parser): items.add_argument("-saw", "--stronger-atma-weapon", action = "store_true", help = "Atma Weapon moved to higher tier and divisor reduced from 64 to 32") - items.add_argument("-ntc", "--no-trash-chests", action = "store_true", - help="Replace Low Tier Items with gold in chests") - items.add_argument("-nts", "--no-trash-shops", action = "store_true", - help="Omit Low tier items in shops") def process(args): args._process_min_max("item_equipable_random") @@ -113,10 +109,8 @@ def flags(args): if args.stronger_atma_weapon: flags += " -saw" - if args.no_trash_chests: - flags += " -ntc" - if args.no_trash_shops: - flags += " -nts" + + return flags def options(args): diff --git a/args/shops.py b/args/shops.py index 1ab2dab7..0a5c941e 100644 --- a/args/shops.py +++ b/args/shops.py @@ -53,6 +53,9 @@ def parse(parser): shops.add_argument("-snil", "--shops-no-illuminas", action = "store_true", help = "Illuminas not sold in shops") + shops.add_argument("-nts", "--no-trash-shops", action = "store_true", + help="Omit Low tier items in shops") + def process(args): if args.shop_inventory_shuffle_random is not None: args.shop_inventory_shuffle_random_percent = args.shop_inventory_shuffle_random @@ -106,6 +109,9 @@ def flags(args): if args.shops_no_illuminas: flags += " -snil" + if args.no_trash_shops: + flags += " -nts" + return flags def options(args): From 5554c26441f81267afcdbd10b5a48a15adf0c501 Mon Sep 17 00:00:00 2001 From: Gerard Noseworthy Date: Sun, 5 Feb 2023 16:04:15 -0330 Subject: [PATCH 40/60] fix: add new flags to options --- args/chests.py | 4 ++++ args/shops.py | 1 + 2 files changed, 5 insertions(+) diff --git a/args/chests.py b/args/chests.py index c7a1d384..960236ef 100644 --- a/args/chests.py +++ b/args/chests.py @@ -66,6 +66,10 @@ def options(args): result.append(("Random Percent", f"{args.chest_contents_shuffle_random_percent}%")) result.append(("Monsters-In-A-Box Shuffled", args.chest_monsters_shuffle)) + if args.no_trash_chests: + result.append(("No Trash Chests", args.no_trash_chests)) + + return result def menu(args): diff --git a/args/shops.py b/args/shops.py index 0a5c941e..771e0b2a 100644 --- a/args/shops.py +++ b/args/shops.py @@ -165,6 +165,7 @@ def options(args): ("Expensive Balls", args.shops_expensive_super_balls), ("No Exp. Eggs", args.shops_no_exp_eggs), ("No Illuminas", args.shops_no_illuminas), + ("No Trash Shops", args.no_trash_shops) ]) return result From a2aeb80633e8fe44a6593b5f404cbc3af1856ae7 Mon Sep 17 00:00:00 2001 From: Gerard Noseworthy Date: Sun, 5 Feb 2023 16:04:43 -0330 Subject: [PATCH 41/60] feature: trash item price is based on vendor sell price flag --- data/chests.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/data/chests.py b/data/chests.py index cd1d963d..0b644bca 100644 --- a/data/chests.py +++ b/data/chests.py @@ -107,15 +107,24 @@ def shuffle_random(self): chest.contents = self.items.get_random() if self.args.no_trash_chests: + if self.args.shop_sell_fraction4: + sell_factor = 1/4 + elif self.args.shop_sell_fraction8: + sell_factor = 1 / 8 + elif self.args.shop_sell_fraction0: + sell_factor = 0 + else: + sell_factor = 1/2 for chest in possible_chests: if not chest.type == Chest.ITEM: continue item = Item(chest.contents, self.rom) if item.is_trash: + item_chest_value = int(min((item.price * sell_factor)//100, Chest.MAX_GOLD_VALUE)) if not item.sell_gold_value: chest.type = Chest.EMPTY chest.type = Chest.GOLD - chest.contents = item.sell_gold_value + chest.contents = item_chest_value def random_tiered(self): From 65ba717792c9c107228970aec46511f41ba4eb09 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sat, 18 Feb 2023 18:17:07 -0700 Subject: [PATCH 42/60] Removing clear of wound bit for coliseum in permadeath --- settings/permadeath.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/settings/permadeath.py b/settings/permadeath.py index 62aebbd2..4b753580 100644 --- a/settings/permadeath.py +++ b/settings/permadeath.py @@ -13,6 +13,7 @@ def __init__(self): if args.permadeath: self.remove_status_mod(remove_status_space) self.heal_hp_mod(heal_hp_space) + self.coliseum_mod() def remove_status_mod(self, space): # change remove status effects field command to never remove death @@ -47,3 +48,8 @@ def heal_hp_mod(self, space): space.write( asm.JMP(death_or_max_hp, asm.ABS), ) + + def coliseum_mod(self): + # don't revive permadeath characters by retaining the wound bit + space = Reserve(0x227f3, 0x227f3, "coliseum permadeath") + space.write(0xad) # default: 0x2d, which clears the wound bit \ No newline at end of file From a657aee1ffb2c051548ecebad9f6229a849dc3d3 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 19 Mar 2023 14:37:47 -0600 Subject: [PATCH 43/60] Fixing bug with b-dash in which player can catch up to Vargas, soft-locking the game --- event/mt_kolts.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/event/mt_kolts.py b/event/mt_kolts.py index cbaff87d..cd5cea9d 100644 --- a/event/mt_kolts.py +++ b/event/mt_kolts.py @@ -21,6 +21,7 @@ def mod(self): self.shadow_vargas_mod() self.vargas_battle_mod() self.entrance_exit_mod() + self.vargas_trigger_mod() if self.reward.type == RewardType.CHARACTER: self.character_mod(self.reward.id) @@ -139,6 +140,29 @@ def entrance_exit_mod(self): new_event.event_address = exit_move_airship - EVENT_CODE_START self.maps.add_event(0x64, new_event) + def vargas_trigger_mod(self): + # Vargas appears on the map 0x62 via 2 tile triggers. With B-Dash, players can outpace him leading to soft-locks. + # Change the 2 event tile triggers to a different location. + old_event = self.maps.get_event(0x62, 10, 32) # get existing event + + self.maps.delete_event(0x62, 10, 32) # vargas event tile (left) + self.maps.delete_event(0x62, 11, 32) # vargas event tile (right) + + from data.map_event import MapEvent + # add event tile to earlier on the path + new_event = MapEvent() + new_event.x = 21 + new_event.y = 19 + new_event.event_address = old_event.event_address + self.maps.add_event(0x62, new_event) + + # add event tile to bottom right of stairs + new_event = MapEvent() + new_event.x = 21 + new_event.y = 20 + new_event.event_address = old_event.event_address + self.maps.add_event(0x62, new_event) + def character_mod(self, character): boss_pack_id = self.get_boss("Vargas") From bb16d429e7db0940f7c9d6e8c1d56f3410adfb96 Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Wed, 22 Mar 2023 08:31:56 -0400 Subject: [PATCH 44/60] Loot (-loot) and Chests-All-MIAB (-cam) First commit, added flags for randomize steals and drops (-loot) and make all chests monster-in-a-boxes (-cam). Note that there's currently no selection on what can be in boxes, you will get bosses. --- args/chests.py | 6 ++++++ args/steal.py | 6 ++++++ data/chests.py | 7 +++++++ data/data.py | 2 +- data/enemies.py | 22 +++++++++++++++++++++- 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/args/chests.py b/args/chests.py index dfefa4e7..33cfa7b3 100644 --- a/args/chests.py +++ b/args/chests.py @@ -14,6 +14,8 @@ def parse(parser): help = "Chest contents randomized by tier. Probability of higher tiers begins low and increases as more chests are opened") chests_contents.add_argument("-cce", "--chest-contents-empty", action = "store_true", help = "Chest contents empty") + chests_contents.add_argument("-cam", "--chest-all-monsters", action="store_true", + help="Chest contents all monster-in-a-boxes") chests.add_argument("-cms", "--chest-monsters-shuffle", action = "store_true", help = "Monsters-in-a-box shuffled but locations unchanged") @@ -34,6 +36,8 @@ def flags(args): flags += " -ccrs" elif args.chest_contents_empty: flags += " -cce" + elif args.chest_all_monsters: + flags += " -cam" if args.chest_monsters_shuffle: flags += " -cms" @@ -52,6 +56,8 @@ def options(args): contents_value = "Random Scaled" elif args.chest_contents_empty: contents_value = "Empty" + elif args.chest_all_monsters: + contents_value = "All MiaB" result.append(("Contents", contents_value)) if args.chest_contents_shuffle_random: diff --git a/args/steal.py b/args/steal.py index baffee98..2aa558cb 100644 --- a/args/steal.py +++ b/args/steal.py @@ -10,6 +10,9 @@ def parse(parser): steal_chances.add_argument("-sca", "--steal-chances-always", action = "store_true", help = "Steal will always succeed if enemy has an item") + steal.add_argument("-loot", "--loot", action="store_true", + help="Randomize items stolen and dropped") + def process(args): pass @@ -20,6 +23,8 @@ def flags(args): flags += " -sch" if args.steal_chances_always: flags += " -sca" + if args.loot: + flags += " -loot" return flags @@ -32,6 +37,7 @@ def options(args): return [ ("Chances", steal_chances), + ("Loot", args.loot) ] def menu(args): diff --git a/data/chests.py b/data/chests.py index d80f8234..acda2c94 100644 --- a/data/chests.py +++ b/data/chests.py @@ -194,6 +194,11 @@ def random_scaled(self): chests_asm.scale_gold(gold_bits, self.gold_contents) + def chest_all_monsters(self): + for chest in self.chests: + chest.type = Chest.MONSTER + chest.contents = random.randint(0,255) + def clear_contents(self): for chest in self.chests: if chest.type == Chest.ITEM or chest.type == Chest.GOLD: @@ -274,6 +279,8 @@ def mod(self): self.random_scaled() elif self.args.chest_contents_empty: self.clear_contents() + elif self.args.chest_all_monsters: + self.chest_all_monsters() else: self.remove_excluded_items() diff --git a/data/data.py b/data/data.py index 1d22f42e..3624b986 100644 --- a/data/data.py +++ b/data/data.py @@ -38,7 +38,7 @@ def __init__(self, rom, args): self.maps = maps.Maps(rom, args, self.items) self.maps.mod(self.characters) - self.enemies = enemies.Enemies(rom, args) + self.enemies = enemies.Enemies(rom, args, self.items) self.enemies.mod(self.maps) self.swdtechs = swdtechs.SwdTechs(rom, args, self.characters) diff --git a/data/enemies.py b/data/enemies.py index 2e686a6f..ee7e6da9 100644 --- a/data/enemies.py +++ b/data/enemies.py @@ -29,9 +29,10 @@ class Enemies(): SRBEHEMOTH2_ID = 127 INVINCIBLE_GUARDIAN_ID = 273 - def __init__(self, rom, args): + def __init__(self, rom, args, items=[]): self.rom = rom self.args = args + self.items = items self.enemy_data = DataArray(self.rom, self.DATA_START, self.DATA_END, self.DATA_SIZE) self.enemy_name_data = DataArray(self.rom, self.NAMES_START, self.NAMES_END, self.NAME_SIZE) @@ -305,6 +306,19 @@ def randomize_encounters(self, maps): self.packs.randomize_packs(packs, boss_percent) + def randomize_loot(self): + for enemy in self.enemies: + self.set_common_steal(enemy.id, self.items.get_random()) + self.set_rare_steal(enemy.id, self.items.get_random()) + self.set_common_drop(enemy.id, self.items.get_random()) + self.set_rare_drop(enemy.id, self.items.get_random()) + + def pad_enemy_packs(self): + for pack in self.packs.packs: + if pack.formations == [0, 0] and pack.id > 0: + # Add random formations to the empty pack + pack.formations = [self.formations.get_random_normal(), self.formations.get_random_normal()] + def set_escapable(self): import random @@ -330,6 +344,12 @@ def mod(self, maps): if self.args.boss_normalize_distort_stats: self.boss_normalize_distort_stats() + if self.args.loot: + self.randomize_loot() + + if self.args.chest_all_monsters: + self.pad_enemy_packs() + if self.args.permadeath: self.remove_fenix_downs() From 360158d869103e818f33df5ba968611181795884 Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Wed, 22 Mar 2023 09:51:00 -0400 Subject: [PATCH 45/60] No Random Encounters (-nre) self-explanatory. All encounter rates = 0. For use with Chests All MiaB and Loot. --- args/challenges.py | 5 +++++ event/start.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/args/challenges.py b/args/challenges.py index 12bd61db..4f09975e 100644 --- a/args/challenges.py +++ b/args/challenges.py @@ -24,6 +24,8 @@ def parse(parser): help = "Life spells cannot be learned. Fenix Downs unavailable (except from starting items). Buckets/inns/tents/events do not revive characters. Phoenix casts Life 3 on party instead of Life") challenges.add_argument("-rls", "--remove-learnable-spells", type = str, help = "Remove spells from learnable sources: Items, Espers, Natural Magic, and Objectives") + challenges.add_argument("-nre", "--no-random-encounters", action = "store_true", + help="Turn off random encounters") def process(args): from constants.spells import black_magic_ids, white_magic_ids, gray_magic_ids, spell_id @@ -86,6 +88,8 @@ def flags(args): flags += " -pd" if args.remove_learnable_spells: flags += f" -rls {args.remove_learnable_spells}" + if args.no_random_encounters: + flags += " -nre" return flags @@ -106,6 +110,7 @@ def options(args): ("Permadeath", args.permadeath), ("Ultima", ultima), ("Remove Learnable Spells", args.remove_learnable_spell_ids), + ("No Random Encounters", args.no_random_encounters), ] return opts diff --git a/event/start.py b/event/start.py index 39f9b4b1..7af7b371 100644 --- a/event/start.py +++ b/event/start.py @@ -59,6 +59,22 @@ def init_event_bits(self, space): field.SetBattleEventBit(battle_bit.MAGIC_POINTS_AFTER_BATTLE), ) + def no_random_encounters_mod(self): + # Set all encounter rates to zero + # (normal. no Charm Bangle or Moogle Charm) + # C0/C29F: C000 ("less encounter" frequency ==> looks like "normal encounter") + # C0/C2A1: 6000 ("norm encounter" frequency ==> looks like "less encounter") + # C0/C2A3: 8001 ("more encounter" frequency ==> looks right) + # C0/C2A5: 0000 ("no encounter" frequency ==> looks right) + # + # (Charm Bangle, halves of above numbers) + # C0/C2A7: 6000 ("less encounter" frequency ==> looks like "normal encounter") + # C0/C2A9: 3000 ("norm encounter" frequency ==> looks like "less encounter") + # C0/C2AB: C000 ("more encounter" frequency ==> looks right) + # C0/C2AD: 0000 ("no encounter" frequency ==> looks right) + self.rom.set_bytes(0x0c29f, [0x00 for i in range(16)]) + + def mod(self): self.intro_loop_mod() self.init_characters_mod() @@ -68,6 +84,9 @@ def mod(self): self.start_items_mod() self.start_game_mod() + if self.args.no_random_encounters: + self.no_random_encounters_mod() + # where the game begins after intro/pregame space = Reserve(0xc9a4f, 0xc9ad4, "setup and start game", field.NOP()) space.write( From 83a7956c7f05017b778cf3fa7255b91022024aaf Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Wed, 22 Mar 2023 21:08:21 -0400 Subject: [PATCH 46/60] bufgix - now no random encounters in dungeons --- event/start.py | 1 + 1 file changed, 1 insertion(+) diff --git a/event/start.py b/event/start.py index 7af7b371..a12c4cc3 100644 --- a/event/start.py +++ b/event/start.py @@ -73,6 +73,7 @@ def no_random_encounters_mod(self): # C0/C2AB: C000 ("more encounter" frequency ==> looks right) # C0/C2AD: 0000 ("no encounter" frequency ==> looks right) self.rom.set_bytes(0x0c29f, [0x00 for i in range(16)]) + self.rom.set_bytes(0x0c2bf, [0x00 for i in range(16)]) def mod(self): From 01f64910bd20afea2d32d2708294eaf433e710f3 Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Thu, 23 Mar 2023 13:28:31 -0400 Subject: [PATCH 47/60] Add Moogle Curse The Moogle Curse (reskin of Moogle Charm) draws random encounters on the world map, when equipped. It does not work in dungeons. This is a workaround so you can still complete Gau and Gogo checks. The encounter rate is very low to avoid abuse. --- data/items.py | 18 ++++++++++++++++++ event/start.py | 8 ++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/data/items.py b/data/items.py index 06c5270d..7dd1ad8e 100644 --- a/data/items.py +++ b/data/items.py @@ -5,6 +5,7 @@ from constants.items import id_name, name_id import data.items_asm as items_asm +import data.text as text class Items(): ITEM_COUNT = 256 @@ -187,6 +188,20 @@ def moogle_starting_equipment(self): self.characters.characters[index].init_body = random.choice(tiers[Item.ARMOR][1]) self.characters.characters[index].init_head = random.choice(tiers[Item.HELMET][1]) + def moogle_curse_description(self): + # If using -no-random-encounters, the Moogle Charm becomes the Moogle Curse + # (enables random encounters on world map). Update the name and description. + moogle_charm = self.items[name_id["Moogle Charm"]] + moogle_charm.name = "Moogle Curse" + + descr_offset = self.rom.get_bytes(0x2d7aa0 + moogle_charm.id * 2, 2) + offset = descr_offset[1] * 0x100 + descr_offset[0] + + # new_descr = "No random enemy encounters" + new_descr = "Draw monsters on world map" + name_bytes = text.get_bytes(new_descr, text.TEXT2) + self.rom.set_bytes(0x2d6400 + offset, name_bytes) + def mod(self): not_relic_condition = lambda x : x != Item.RELIC if self.args.item_equipable_random: @@ -261,6 +276,9 @@ def mod(self): self.moogle_starting_equipment() + if self.args.no_random_encounters: + self.moogle_curse_description() + def write(self): for item in self.items: item.write() diff --git a/event/start.py b/event/start.py index a12c4cc3..4871c86c 100644 --- a/event/start.py +++ b/event/start.py @@ -72,8 +72,12 @@ def no_random_encounters_mod(self): # C0/C2A9: 3000 ("norm encounter" frequency ==> looks like "less encounter") # C0/C2AB: C000 ("more encounter" frequency ==> looks right) # C0/C2AD: 0000 ("no encounter" frequency ==> looks right) - self.rom.set_bytes(0x0c29f, [0x00 for i in range(16)]) - self.rom.set_bytes(0x0c2bf, [0x00 for i in range(16)]) + self.rom.set_bytes(0x0c29f, [0x00 for i in range(64)]) # Set all encounter rates to zero + rate = 0x10 # 1/3 charm bangle + self.rom.set_bytes(0x0c2af, [rate, 0x00, rate, 0x00, rate, 0x00]) # Set World Map - Moogle Charm encounter rates to non-zero + + # Rename moogle charm to moogle curse: in data.items + def mod(self): From 5dc664f8966384615534b1e179e05e005ed0a983 Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Thu, 23 Mar 2023 16:07:55 -0400 Subject: [PATCH 48/60] Exclude buggy encounters Removes empty and bugged event battle groups from the MIAB encounter pool. (Doesn't currently overwrite them.) --- data/chests.py | 5 ++- data/data.py | 3 ++ event_battle_groups.txt | 83 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 event_battle_groups.txt diff --git a/data/chests.py b/data/chests.py index acda2c94..788c69ad 100644 --- a/data/chests.py +++ b/data/chests.py @@ -195,9 +195,12 @@ def random_scaled(self): chests_asm.scale_gold(gold_bits, self.gold_contents) def chest_all_monsters(self): + event_battle_groups_to_avoid = [49, 50, 51, 52, 53, 54, 56, 60, 61, 62, 75, 77, 78, 83, 97, 101, 105, 106, + 121, 122, 123, 124, 127, 128, 129] + MIAB_contents = [a for a in range(256) if a not in event_battle_groups_to_avoid] for chest in self.chests: chest.type = Chest.MONSTER - chest.contents = random.randint(0,255) + chest.contents = random.choice(MIAB_contents) def clear_contents(self): for chest in self.chests: diff --git a/data/data.py b/data/data.py index 3624b986..bd70756a 100644 --- a/data/data.py +++ b/data/data.py @@ -40,6 +40,9 @@ def __init__(self, rom, args): self.enemies = enemies.Enemies(rom, args, self.items) self.enemies.mod(self.maps) + #for p in self.enemies.packs.packs: + # forms = [(f, self.enemies.formations.get_name(f)) for f in p.formations] + # print(p.id, ': ', forms) self.swdtechs = swdtechs.SwdTechs(rom, args, self.characters) self.swdtechs.mod() diff --git a/event_battle_groups.txt b/event_battle_groups.txt new file mode 100644 index 00000000..b82c7378 --- /dev/null +++ b/event_battle_groups.txt @@ -0,0 +1,83 @@ +Formations to avoid: +49 : ['', ''] +50 : ['', ''] +51 : ['', ''] +52 : ['', ''] +53 : ['Zone Eater', 'Zone Eater'] +54 : ['', ''] +56 : ['', ''] +60 : ['', ''] +61 : ['', ''] +62 : ['', ''] +75 : ['Guardian', 'Guardian'] +77 : ['Tritoch (Vicks/Wedge/Terra)', 'Tritoch (Vicks/Wedge/Terra)'] +78 : ['Tritoch (Terra)', 'Tritoch (Terra)'] +83 : ['Kefka', 'Kefka'] +97 : ['', ''] +101 : ['Face/Long Arm/Short Arm', 'Face/Long Arm/Short Arm'] +105 : ['', ''] +106 : ['', ''] +120 : ['Tritoch', 'Tritoch'] +121 : ['', ''] +122 : ['', ''] +123 : ['', ''] +124 : ['Kefka (Thamasa)', 'Kefka (Thamasa)'] +127 : ['', ''] +128 : ['Phunbaba 1', 'Phunbaba 1'] +129 : ['Phunbaba 2', 'Phunbaba 2'] + +Bosses: +6 : ['Marshal', 'Marshal'] +18 : ['Rizopas', 'Rizopas'] +46 : ['Leader', 'Leader'] +57 : ['Kefka (Narshe)', 'Kefka (Narshe)'] +63 : ['Atma', 'Atma'] +64 : ['Whelk', 'Whelk'] +66 : ['Vargas', 'Vargas'] +67 : ['TunnelArmr', 'TunnelArmr'] +68 : ['GhostTrain', 'GhostTrain'] +69 : ['Dadaluma', 'Dadaluma'] +70 : ['Ifrit/Shiva', 'Ifrit/Shiva'] +71 : ['Cranes', 'Cranes'] +72 : ['Number 024', 'Number 024'] +73 : ['Number 128', 'Number 128'] +74 : ['Umaro', 'Umaro'] +76 : ['Guardian', 'Guardian'] +79 : ['FlameEater', 'FlameEater'] +80 : ['AtmaWeapon', 'AtmaWeapon'] +81 : ['Nerapa', 'Nerapa'] +82 : ['SrBehemoth', 'SrBehemoth'] +84 : ['Tentacles', 'Tentacles'] +85 : ['Dullahan', 'Dullahan'] +86 : ['Chadarnook', 'Chadarnook'] +87 : ['Prometheus', 'Prometheus'] +88 : ['Dirt Drgn', 'Dirt Drgn'] +89 : ['Air Force', 'Air Force'] +90 : ['Stooges', 'Stooges'] +92 : ['Wrexsoul', 'Wrexsoul'] +93 : ['Doom Gaze', 'Doom Gaze'] +94 : ['Hidon', 'Hidon'] +98 : ['Doom', 'Doom'] +99 : ['Goddess', 'Goddess'] +100 : ['Poltrgeist', 'Poltrgeist'] +103 : ['Ultros 1', 'Ultros 1'] +104 : ['Ultros 2', 'Ultros 2'] +107 : ['Ultros/Chupon', 'Ultros/Chupon'] +111 : ['White Drgn', 'White Drgn'] +112 : ['Atma', 'Atma'] +114 : ['Inferno', 'Inferno'] +117 : ['Umaro', 'Umaro'] +119 : ['Tritoch', 'Tritoch'] +125 : ['Ultros 3', 'Ultros 3'] +130 : ['Phunbaba 3', 'Phunbaba 3'] +131 : ['Phunbaba 4', 'Phunbaba 4'] +132 : ['Ice Dragon', 'Ice Dragon'] +133 : ['Storm Drgn', 'Storm Drgn'] +134 : ['Dirt Drgn', 'Dirt Drgn'] +135 : ['Gold Drgn', 'Gold Drgn'] +136 : ['Skull Drgn', 'Skull Drgn'] +137 : ['Blue Drgn', 'Blue Drgn'] +138 : ['Red Dragon', 'Red Dragon'] +139 : ['White Drgn', 'White Drgn'] +140 : ['Guardian', 'Guardian'] +145 : ['MagiMaster', 'MagiMaster'] \ No newline at end of file From 7d2346a7dcf57a12486cb3cdd9d484c2981a45a9 Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Thu, 23 Mar 2023 17:34:49 -0400 Subject: [PATCH 49/60] Make each MiaB unique; take over unused event_battle_groups - Changed pad_enemy_packs to only put a single random formation in each one. This would make races fairer. - Expanded the event_battle_groups rewrite to claim those unused in FF6WC. This leaves 54 bosses, 199 single normal enemy formations (for MiaB), and 3 untouched [Zone Eater, Tier 1, Final Kefka]. --- data/chests.py | 3 +- data/enemies.py | 9 ++-- data/enemy_battle_groups.py | 88 +++++++++++++++++++++++++++++++++++++ event_battle_groups.txt | 2 +- 4 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 data/enemy_battle_groups.py diff --git a/data/chests.py b/data/chests.py index 788c69ad..b823ebf2 100644 --- a/data/chests.py +++ b/data/chests.py @@ -195,8 +195,7 @@ def random_scaled(self): chests_asm.scale_gold(gold_bits, self.gold_contents) def chest_all_monsters(self): - event_battle_groups_to_avoid = [49, 50, 51, 52, 53, 54, 56, 60, 61, 62, 75, 77, 78, 83, 97, 101, 105, 106, - 121, 122, 123, 124, 127, 128, 129] + from data.enemy_battle_groups import event_battle_groups_to_avoid MIAB_contents = [a for a in range(256) if a not in event_battle_groups_to_avoid] for chest in self.chests: chest.type = Chest.MONSTER diff --git a/data/enemies.py b/data/enemies.py index ee7e6da9..d69bd7b0 100644 --- a/data/enemies.py +++ b/data/enemies.py @@ -314,10 +314,13 @@ def randomize_loot(self): self.set_rare_drop(enemy.id, self.items.get_random()) def pad_enemy_packs(self): + from data.enemy_battle_groups import unused_event_battle_groups for pack in self.packs.packs: - if pack.formations == [0, 0] and pack.id > 0: - # Add random formations to the empty pack - pack.formations = [self.formations.get_random_normal(), self.formations.get_random_normal()] + if pack.FORMATION_COUNT == 2: + if (pack.formations == [0, 0] and pack.id > 0) or (pack.id in unused_event_battle_groups): + # Add random formations to the empty pack + this_formation = self.formations.get_random_normal() + pack.formations = [this_formation, this_formation] def set_escapable(self): import random diff --git a/data/enemy_battle_groups.py b/data/enemy_battle_groups.py new file mode 100644 index 00000000..82fa3974 --- /dev/null +++ b/data/enemy_battle_groups.py @@ -0,0 +1,88 @@ +unused_event_battle_groups = [ + 49, # Empty + 50, # Empty + 51, # Empty + 52, # Empty + 54, # Empty + 56, # Empty + 60, # Empty + 61, # Empty + 62, # Empty + 75, # Unbeatable Guardian + 77, # Tritoch (Vicks/Wedge/Terra) + 78, # Tritoch (Terra) + 97, # Empty + 105, # Empty + 106, # Empty + 120, # Tritoch (repeat) + 121, # Empty + 122, # Empty + 123, # Empty + 124, # Kefka (Thamasa) + 127, # Empty + 128, # Phunbaba 1 + 129 # Phunbaba 2 +] + +event_battle_groups_to_avoid = [ + 53, # Zone Eater + 83, # Kefka (final) + 101 # Face/Long Arm/Short Arm +] + +boss_event_battle_groups = [ + 6, # 'Marshal', 'Marshal' + 18, # 'Rizopas', 'Rizopas' + 46, # 'Leader', 'Leader' + 57, # 'Kefka (Narshe)', 'Kefka (Narshe)' + 63, # 'Atma', 'Atma' + 64, # 'Whelk', 'Whelk' + 66, # 'Vargas', 'Vargas' + 67, # 'TunnelArmr', 'TunnelArmr' + 68, # 'GhostTrain', 'GhostTrain' + 69, # 'Dadaluma', 'Dadaluma' + 70, # 'Ifrit/Shiva', 'Ifrit/Shiva' + 71, # 'Cranes', 'Cranes' + 72, # 'Number 024', 'Number 024' + 73, # 'Number 128', 'Number 128' + 74, # 'Umaro', 'Umaro' + 76, # 'Guardian', 'Guardian' + 79, # 'FlameEater', 'FlameEater' + 80, # 'AtmaWeapon', 'AtmaWeapon' + 81, # 'Nerapa', 'Nerapa' + 82, # 'SrBehemoth', 'SrBehemoth' + 84, # 'Tentacles', 'Tentacles' + 85, # 'Dullahan', 'Dullahan' + 86, # 'Chadarnook', 'Chadarnook' + 87, # 'Prometheus', 'Prometheus' + 88, # 'Dirt Drgn', 'Dirt Drgn' + 89, # 'Air Force', 'Air Force' + 90, # 'Stooges', 'Stooges' + 92, # 'Wrexsoul', 'Wrexsoul' + 93, # 'Doom Gaze', 'Doom Gaze' + 94, # 'Hidon', 'Hidon' + 98, # 'Doom', 'Doom' + 99, # 'Goddess', 'Goddess' + 100, # 'Poltrgeist', 'Poltrgeist' + 103, # 'Ultros 1', 'Ultros 1' + 104, # 'Ultros 2', 'Ultros 2' + 107, # 'Ultros/Chupon', 'Ultros/Chupon' + 111, # 'White Drgn', 'White Drgn' + 112, # 'Atma', 'Atma' + 114, # 'Inferno', 'Inferno' + 117, # 'Umaro', 'Umaro' + 119, # 'Tritoch', 'Tritoch' + 125, # 'Ultros 3', 'Ultros 3' + 130, # 'Phunbaba 3', 'Phunbaba 3' + 131, # 'Phunbaba 4', 'Phunbaba 4' + 132, # 'Ice Dragon', 'Ice Dragon' + 133, # 'Storm Drgn', 'Storm Drgn' + 134, # 'Dirt Drgn', 'Dirt Drgn' + 135, # 'Gold Drgn', 'Gold Drgn' + 136, # 'Skull Drgn', 'Skull Drgn' + 137, # 'Blue Drgn', 'Blue Drgn' + 138, # 'Red Dragon', 'Red Dragon' + 139, # 'White Drgn', 'White Drgn' + 140, # 'Guardian', 'Guardian' + 145 # 'MagiMaster', 'MagiMaster' +] \ No newline at end of file diff --git a/event_battle_groups.txt b/event_battle_groups.txt index b82c7378..feb75bec 100644 --- a/event_battle_groups.txt +++ b/event_battle_groups.txt @@ -23,8 +23,8 @@ Formations to avoid: 123 : ['', ''] 124 : ['Kefka (Thamasa)', 'Kefka (Thamasa)'] 127 : ['', ''] -128 : ['Phunbaba 1', 'Phunbaba 1'] 129 : ['Phunbaba 2', 'Phunbaba 2'] +128 : ['Phunbaba 1', 'Phunbaba 1'] Bosses: 6 : ['Marshal', 'Marshal'] From 96d3862dcf56edb37cb93cb680bcbadc5ab7b6b7 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 26 Mar 2023 12:36:57 -0600 Subject: [PATCH 50/60] fixing small issues, as identified by Franklin and HoxNorf --- data/character.py | 8 ++++---- data/lores.py | 2 +- event/auction_house.py | 4 ++-- event/mt_zozo.py | 2 ++ event/veldt.py | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/data/character.py b/data/character.py index cabaa67c..db012726 100644 --- a/data/character.py +++ b/data/character.py @@ -25,8 +25,8 @@ def __init__(self, id, init_data, name_data): self.init_magic_evasion = init_data[14] self.init_right_hand = init_data[15] self.init_left_hand = init_data[16] - self.init_body = init_data[17] - self.init_head = init_data[18] + self.init_body = init_data[18] # https://discord.com/channels/666661907628949504/931737764205047858/1069220818300698675 + self.init_head = init_data[17] self.init_relic1 = init_data[19] self.init_relic2 = init_data[20] self._init_run_success = init_data[21] & 0x03 @@ -51,8 +51,8 @@ def init_data(self): init_data[14] = self.init_magic_evasion init_data[15] = self.init_right_hand init_data[16] = self.init_left_hand - init_data[17] = self.init_body - init_data[18] = self.init_head + init_data[18] = self.init_body + init_data[17] = self.init_head init_data[19] = self.init_relic1 init_data[20] = self.init_relic2 init_data[21] = self._init_run_success diff --git a/data/lores.py b/data/lores.py index a9054f49..5ce4f148 100644 --- a/data/lores.py +++ b/data/lores.py @@ -15,7 +15,7 @@ class Lores: INITIAL_LORES_END = 0x26f566 NAMES_START = 0x26f9fd - NAMES_END = 0x26fb65 + NAMES_END = 0x26faec # https://discord.com/channels/666661907628949504/931737764205047858/1069100781216739328 NAME_SIZE = 10 DESC_PTRS_START = 0x2d7a70 diff --git a/event/auction_house.py b/event/auction_house.py index 82fc0a53..6d5780ba 100644 --- a/event/auction_house.py +++ b/event/auction_house.py @@ -118,9 +118,9 @@ def mod(self): def get_reward_announce_dialog(self, name, start_price, item): if item: - reward_dialog = '“' + name + '“!' + reward_dialog = '“' + name + '”!' #https://discord.com/channels/666661907628949504/666811452350398493/1085018091844554832 else: - reward_dialog = 'The Magicite, “' + name + '“!' + reward_dialog = 'The Magicite, “' + name + '”!' # keep auctioneer dialog somewhat centered with new esper/item names # looks like about 32 characters on a line (32 is just an estimate, it is not monospace) diff --git a/event/mt_zozo.py b/event/mt_zozo.py index fdbdc6fb..c0233de6 100644 --- a/event/mt_zozo.py +++ b/event/mt_zozo.py @@ -111,6 +111,8 @@ def letter_mod(self, char_name = ""): letter_text = "Dear Lola,I am writing to beg for your forgiveness. I am guilty of perpetuating a terrible lie…I have only now realized the error of my ways. I hope I can correct a great wrong.Your boyfriend, who you thought was in Mobliz, passed away some time ago. I have been writing in his stead…We humans tend to allow the past to destroy our lives.I implore you not to let this happen.It is time to look forward, to rediscover love, and embrace the beauty of life.You have so much of life left to live…" if char_name != "": letter_text += "< ><" + char_name + ">" + else: #https://discord.com/channels/666661907628949504/666811452350398493/1086426370910994493 + letter_text += "" self.dialogs.set_text(2568, letter_text) def character_mod(self, character): diff --git a/event/veldt.py b/event/veldt.py index 09327d29..031cc4571 100644 --- a/event/veldt.py +++ b/event/veldt.py @@ -376,7 +376,7 @@ def battle_events_mod(self): # overwrite step 4. of rage tutorial after sabin/cyan/gau event esper_dialog_id = 182 gau_char_arrives_dialog_id = esper_dialog_id - self.dialogs.set_multi_line_battle_text(esper_dialog_id, " Received the Magicite “" + self.espers.get_name(self.reward.id) + ".“") + self.dialogs.set_multi_line_battle_text(esper_dialog_id, " Received the Magicite “" + self.espers.get_name(self.reward.id) + ".\"") # overwrite battle event $0d, fed gau dried meat for first time space = Reserve(0x10aa21, 0x10ac5e, "veldt gau/char fed dried meat", battle_event.NOP()) From 10e155b2dfb63c140d05e40a6ab7f23f34f4cd51 Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Thu, 30 Mar 2023 17:51:54 -0400 Subject: [PATCH 51/60] Add boss% to -chests-all-monsters Now called as -cam <0--100>. The number is the percentage chance for each box to contain a boss (0 = no bosses, 100 = all bosses). --- args/chests.py | 13 ++++++++++--- data/chests.py | 15 ++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/args/chests.py b/args/chests.py index 33cfa7b3..1097849c 100644 --- a/args/chests.py +++ b/args/chests.py @@ -14,8 +14,9 @@ def parse(parser): help = "Chest contents randomized by tier. Probability of higher tiers begins low and increases as more chests are opened") chests_contents.add_argument("-cce", "--chest-contents-empty", action = "store_true", help = "Chest contents empty") - chests_contents.add_argument("-cam", "--chest-all-monsters", action="store_true", - help="Chest contents all monster-in-a-boxes") + chests_contents.add_argument("-cam", "--chest-all-monsters", default = None, type = int, + metavar = "PERCENT", choices = range(101), + help="Chest contents all monster-in-a-boxes and given percent bosses") chests.add_argument("-cms", "--chest-monsters-shuffle", action = "store_true", help = "Monsters-in-a-box shuffled but locations unchanged") @@ -24,6 +25,9 @@ def process(args): if args.chest_contents_shuffle_random is not None: args.chest_contents_shuffle_random_percent = args.chest_contents_shuffle_random args.chest_contents_shuffle_random = True + if args.chest_all_monsters is not None: + args.chest_all_monsters_boss_percent = args.chest_all_monsters + args.chest_all_monsters = True def flags(args): flags = "" @@ -37,7 +41,7 @@ def flags(args): elif args.chest_contents_empty: flags += " -cce" elif args.chest_all_monsters: - flags += " -cam" + flags += f" -cam {args.chest_all_monsters_boss_percent}" if args.chest_monsters_shuffle: flags += " -cms" @@ -62,6 +66,9 @@ def options(args): result.append(("Contents", contents_value)) if args.chest_contents_shuffle_random: result.append(("Random Percent", f"{args.chest_contents_shuffle_random_percent}%")) + elif args.chest_all_monsters: + result.append(("Boss Percent", f"{args.chest_all_monsters_boss_percent}%")) + result.append(("Monsters-In-A-Box Shuffled", args.chest_monsters_shuffle)) return result diff --git a/data/chests.py b/data/chests.py index b823ebf2..d3b7abe6 100644 --- a/data/chests.py +++ b/data/chests.py @@ -194,12 +194,17 @@ def random_scaled(self): chests_asm.scale_gold(gold_bits, self.gold_contents) - def chest_all_monsters(self): - from data.enemy_battle_groups import event_battle_groups_to_avoid - MIAB_contents = [a for a in range(256) if a not in event_battle_groups_to_avoid] + def chest_all_monsters(self, boss_percent): + from data.enemy_battle_groups import event_battle_groups_to_avoid, boss_event_battle_groups + MIAB_noboss = [a for a in range(256) if a not in event_battle_groups_to_avoid and a not in boss_event_battle_groups] + MIAB_boss = [a for a in range(256) if a in boss_event_battle_groups] for chest in self.chests: chest.type = Chest.MONSTER - chest.contents = random.choice(MIAB_contents) + is_boss = (random.random()*100 < boss_percent) + if is_boss: + chest.contents = random.choice(MIAB_boss) + else: + chest.contents = random.choice(MIAB_noboss) def clear_contents(self): for chest in self.chests: @@ -282,7 +287,7 @@ def mod(self): elif self.args.chest_contents_empty: self.clear_contents() elif self.args.chest_all_monsters: - self.chest_all_monsters() + self.chest_all_monsters(self.args.chest_all_monsters_boss_percent) else: self.remove_excluded_items() From 434d8c7915af063c4bf9f87c6947aaebfa49e66b Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sun, 2 Apr 2023 09:21:37 -0600 Subject: [PATCH 52/60] allowing 0 argument -cor --- args/coliseum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/args/coliseum.py b/args/coliseum.py index 35d0868a..133fdd22 100644 --- a/args/coliseum.py +++ b/args/coliseum.py @@ -7,7 +7,7 @@ def parse(parser): coliseum = parser.add_argument_group("Coliseum") coliseum_opponents = coliseum.add_mutually_exclusive_group() - coliseum_opponents.add_argument("-cor", "--coliseum-opponents-random", default = None, type = int, + coliseum_opponents.add_argument("-cor", "--coliseum-opponents-random", nargs='?', const=100, default = None, type = int, metavar = "PERCENT", choices = range(101), help = "Coliseum opponents original with a given percent randomized") coliseum_opponents.add_argument("-cosr", "--coliseum-opponents-shuffle-random", default = None, type = int, From 0dbbb5c286e3bdc114ce66f2d379e62787d052ca Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Sun, 2 Apr 2023 18:01:27 -0400 Subject: [PATCH 53/60] address asilverthorn's comments for PR to WC --- data/data.py | 3 -- data/item.py | 7 +++- data/items.py | 28 ++++++++------ event_battle_groups.txt | 83 ----------------------------------------- 4 files changed, 23 insertions(+), 98 deletions(-) delete mode 100644 event_battle_groups.txt diff --git a/data/data.py b/data/data.py index bd70756a..3624b986 100644 --- a/data/data.py +++ b/data/data.py @@ -40,9 +40,6 @@ def __init__(self, rom, args): self.enemies = enemies.Enemies(rom, args, self.items) self.enemies.mod(self.maps) - #for p in self.enemies.packs.packs: - # forms = [(f, self.enemies.formations.get_name(f)) for f in p.formations] - # print(p.id, ': ', forms) self.swdtechs = swdtechs.SwdTechs(rom, args, self.characters) self.swdtechs.mod() diff --git a/data/item.py b/data/item.py index 578f4048..7c0b5445 100644 --- a/data/item.py +++ b/data/item.py @@ -11,12 +11,13 @@ class Item(): ITEM_TYPE_COUNT = 7 TOOL, WEAPON, ARMOR, SHIELD, HELMET, RELIC, ITEM = range(ITEM_TYPE_COUNT) - def __init__(self, id, rom): + def __init__(self, id, rom, desc_data): self.rom = rom self.id = id self.name_addr = self.NAMES_START_ADDR + self.id * self.NAME_LENGTH self.data_addr = self.DATA_START_ADDR + self.id * self.DATA_SIZE + self.desc_data = desc_data self.read() @@ -42,6 +43,9 @@ def remove_learnable_spell(self): self.learnable_spell = 0 self.learnable_spell_rate = 0 + def get_desc_data(self): + return text.get_bytes(self.desc, text.TEXT2) + def scale_price(self, factor): self.price = int(self.price * factor) self.price = max(min(self.price, 2**16 - 1), 0) @@ -50,6 +54,7 @@ def read(self): name_bytes = self.rom.get_bytes(self.name_addr, self.NAME_LENGTH) self.icon = value_text[name_bytes[0]] self.name = text.get_string(name_bytes[1:], text.TEXT2).rstrip('\0') + self.desc = text.get_string(self.desc_data, text.TEXT2).rstrip('\0') data = self.rom.get_bytes(self.data_addr, self.DATA_SIZE) self.type = data[0] & 0x07 diff --git a/data/items.py b/data/items.py index 7dd1ad8e..a74d2917 100644 --- a/data/items.py +++ b/data/items.py @@ -1,5 +1,6 @@ import args, random from data.item import Item +from data.structures import DataList from constants.items import good_items from constants.items import id_name, name_id @@ -14,6 +15,12 @@ class Items(): BREAKABLE_RODS = range(53, 59) ELEMENTAL_SHIELDS = range(96, 99) + DESC_PTRS_START = 0x2d7aa0 + DESC_PTRS_END = 0x2d7c9f + + DESC_START = 0x2d6400 + DESC_END = 0x2d779f + GOOD = [name_id[name] for name in good_items] if args.stronger_atma_weapon: GOOD.append(name_id["Atma Weapon"]) @@ -30,6 +37,10 @@ def __init__(self, rom, args, dialogs, characters): self.dialogs = dialogs self.characters = characters + self.desc_data = DataList(self.rom, self.DESC_PTRS_START, self.DESC_PTRS_END, + self.rom.SHORT_PTR_SIZE, self.DESC_START, + self.DESC_START, self.DESC_END) + self.read() def read(self): @@ -38,7 +49,7 @@ def read(self): Item.SHIELD : [], Item.HELMET : [], Item.RELIC : [], Item.ITEM : []} for item_index in range(self.ITEM_COUNT): - item = Item(item_index, self.rom) + item = Item(item_index, self.rom, self.desc_data[item_index]) self.items.append(item) @@ -188,19 +199,12 @@ def moogle_starting_equipment(self): self.characters.characters[index].init_body = random.choice(tiers[Item.ARMOR][1]) self.characters.characters[index].init_head = random.choice(tiers[Item.HELMET][1]) - def moogle_curse_description(self): + def moogle_curse(self): # If using -no-random-encounters, the Moogle Charm becomes the Moogle Curse # (enables random encounters on world map). Update the name and description. moogle_charm = self.items[name_id["Moogle Charm"]] moogle_charm.name = "Moogle Curse" - - descr_offset = self.rom.get_bytes(0x2d7aa0 + moogle_charm.id * 2, 2) - offset = descr_offset[1] * 0x100 + descr_offset[0] - - # new_descr = "No random enemy encounters" - new_descr = "Draw monsters on world map" - name_bytes = text.get_bytes(new_descr, text.TEXT2) - self.rom.set_bytes(0x2d6400 + offset, name_bytes) + moogle_charm.desc = "Draw monsters on world map" def mod(self): not_relic_condition = lambda x : x != Item.RELIC @@ -277,11 +281,13 @@ def mod(self): self.moogle_starting_equipment() if self.args.no_random_encounters: - self.moogle_curse_description() + self.moogle_curse() def write(self): for item in self.items: item.write() + self.desc_data[item.id] = item.get_desc_data() + self.desc_data.write() def get_id(self, name): return name_id[name] diff --git a/event_battle_groups.txt b/event_battle_groups.txt deleted file mode 100644 index feb75bec..00000000 --- a/event_battle_groups.txt +++ /dev/null @@ -1,83 +0,0 @@ -Formations to avoid: -49 : ['', ''] -50 : ['', ''] -51 : ['', ''] -52 : ['', ''] -53 : ['Zone Eater', 'Zone Eater'] -54 : ['', ''] -56 : ['', ''] -60 : ['', ''] -61 : ['', ''] -62 : ['', ''] -75 : ['Guardian', 'Guardian'] -77 : ['Tritoch (Vicks/Wedge/Terra)', 'Tritoch (Vicks/Wedge/Terra)'] -78 : ['Tritoch (Terra)', 'Tritoch (Terra)'] -83 : ['Kefka', 'Kefka'] -97 : ['', ''] -101 : ['Face/Long Arm/Short Arm', 'Face/Long Arm/Short Arm'] -105 : ['', ''] -106 : ['', ''] -120 : ['Tritoch', 'Tritoch'] -121 : ['', ''] -122 : ['', ''] -123 : ['', ''] -124 : ['Kefka (Thamasa)', 'Kefka (Thamasa)'] -127 : ['', ''] -129 : ['Phunbaba 2', 'Phunbaba 2'] -128 : ['Phunbaba 1', 'Phunbaba 1'] - -Bosses: -6 : ['Marshal', 'Marshal'] -18 : ['Rizopas', 'Rizopas'] -46 : ['Leader', 'Leader'] -57 : ['Kefka (Narshe)', 'Kefka (Narshe)'] -63 : ['Atma', 'Atma'] -64 : ['Whelk', 'Whelk'] -66 : ['Vargas', 'Vargas'] -67 : ['TunnelArmr', 'TunnelArmr'] -68 : ['GhostTrain', 'GhostTrain'] -69 : ['Dadaluma', 'Dadaluma'] -70 : ['Ifrit/Shiva', 'Ifrit/Shiva'] -71 : ['Cranes', 'Cranes'] -72 : ['Number 024', 'Number 024'] -73 : ['Number 128', 'Number 128'] -74 : ['Umaro', 'Umaro'] -76 : ['Guardian', 'Guardian'] -79 : ['FlameEater', 'FlameEater'] -80 : ['AtmaWeapon', 'AtmaWeapon'] -81 : ['Nerapa', 'Nerapa'] -82 : ['SrBehemoth', 'SrBehemoth'] -84 : ['Tentacles', 'Tentacles'] -85 : ['Dullahan', 'Dullahan'] -86 : ['Chadarnook', 'Chadarnook'] -87 : ['Prometheus', 'Prometheus'] -88 : ['Dirt Drgn', 'Dirt Drgn'] -89 : ['Air Force', 'Air Force'] -90 : ['Stooges', 'Stooges'] -92 : ['Wrexsoul', 'Wrexsoul'] -93 : ['Doom Gaze', 'Doom Gaze'] -94 : ['Hidon', 'Hidon'] -98 : ['Doom', 'Doom'] -99 : ['Goddess', 'Goddess'] -100 : ['Poltrgeist', 'Poltrgeist'] -103 : ['Ultros 1', 'Ultros 1'] -104 : ['Ultros 2', 'Ultros 2'] -107 : ['Ultros/Chupon', 'Ultros/Chupon'] -111 : ['White Drgn', 'White Drgn'] -112 : ['Atma', 'Atma'] -114 : ['Inferno', 'Inferno'] -117 : ['Umaro', 'Umaro'] -119 : ['Tritoch', 'Tritoch'] -125 : ['Ultros 3', 'Ultros 3'] -130 : ['Phunbaba 3', 'Phunbaba 3'] -131 : ['Phunbaba 4', 'Phunbaba 4'] -132 : ['Ice Dragon', 'Ice Dragon'] -133 : ['Storm Drgn', 'Storm Drgn'] -134 : ['Dirt Drgn', 'Dirt Drgn'] -135 : ['Gold Drgn', 'Gold Drgn'] -136 : ['Skull Drgn', 'Skull Drgn'] -137 : ['Blue Drgn', 'Blue Drgn'] -138 : ['Red Dragon', 'Red Dragon'] -139 : ['White Drgn', 'White Drgn'] -140 : ['Guardian', 'Guardian'] -145 : ['MagiMaster', 'MagiMaster'] \ No newline at end of file From 4b91c087cc52ca572b9bae191b0ae21e196e9554 Mon Sep 17 00:00:00 2001 From: HoxNorf <45671870+HoxNorf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:13:44 -0700 Subject: [PATCH 54/60] Small Aesthetic Additions Palettes Added (10): - Alice-HoxNorf-Touhou - Alphys-LoneRedMage-Undertale - Amy-HoxNorf-Sonic - Frisk-LoneRedMage-Undertale - Gryz-HoxNorf-PS4 - Raja-HoxNorf-PS4 - Reimu (Blue)-HoxNorf-Touhou - Rune-HoxNorf-PS4 - Toriel-LoneRedMage-Undertale - Yuyuko-HoxNorf-Touhou Portraits Added (20): - Alice-HoxNorf-Touhou - Alma-Unknown-FFT - Alphys-LoneRedMage-Undertale - Amy-HoxNorf-Sonic - Cloud-JamesWhite89-FF7 - Fighter-CtrlxZ-FF1 - Frisk-LoneRedMage-Undertale - Gryz-HoxNorf-PS4 - Jessie-JamesWhite89-FF7 - Lenna (White Mage)-JamesWhite89-FF5 - Lenna-JamesWhite89-FF5 - Orlandeau-Unknown-FFT - Porom (Devout)-HoxNorf-FF4 - Raja-HoxNorf-PS4 - Ramza-Unknown-FFT - Reimu (Blue)-HoxNorf-Touhou - Rune-HoxNorf-PS4 - Sarisa-JamesWhite89-FF5 - Toriel-LoneRedMage-Undertale - Yuyuko-HoxNorf-Touhou Sprites Added (9): - Alice-HoxNorf-Touhou - Alphys-LoneRedMage-Undertale - Amy-HoxNorf-Sonic - Frisk-LoneRedMage-Undertale - Gryz-HoxNorf-PS4 - Raja-HoxNorf-PS4 - Rune-HoxNorf-PS4 - Toriel-LoneRedMage-Undertale - Yuyuko-HoxNorf-Touhou * Renamed Pirahna Plant-JamesWhite89-Mario to Piranha Plant-JamesWhite89-Mario --- .../palettes/custom/Alice-HoxNorf-Touhou.pal | Bin 0 -> 32 bytes .../custom/Alphys-LoneRedMage-Undertale.pal | Bin 0 -> 32 bytes .../palettes/custom/Amy-HoxNorf-Sonic.pal | Bin 0 -> 32 bytes .../custom/Frisk-LoneRedMage-Undertale.pal | Bin 0 -> 32 bytes graphics/palettes/custom/Gryz-HoxNorf-PS4.pal | Bin 0 -> 32 bytes ...l => Piranha Plant-JamesWhite89-Mario.pal} | Bin graphics/palettes/custom/Raja-HoxNorf-PS4.pal | Bin 0 -> 32 bytes .../custom/Reimu (Blue)-HoxNorf-Touhou.pal | Bin 0 -> 32 bytes graphics/palettes/custom/Rune-HoxNorf-PS4.pal | Bin 0 -> 32 bytes .../custom/Toriel-LoneRedMage-Undertale.pal | Bin 0 -> 32 bytes .../palettes/custom/Yuyuko-HoxNorf-Touhou.pal | Bin 0 -> 32 bytes graphics/palettes/palettes.py | 13 +++++++++- .../portraits/custom/Alice-HoxNorf-Touhou.bin | Bin 0 -> 800 bytes .../portraits/custom/Alice-HoxNorf-Touhou.pal | 1 + .../portraits/custom/Alma-Unknown-FFT.bin | Bin 0 -> 800 bytes .../portraits/custom/Alma-Unknown-FFT.pal | 1 + .../custom/Alphys-LoneRedMage-Undertale.bin | Bin 0 -> 800 bytes .../custom/Alphys-LoneRedMage-Undertale.pal | Bin 0 -> 32 bytes .../portraits/custom/Amy-HoxNorf-Sonic.bin | Bin 0 -> 800 bytes .../portraits/custom/Amy-HoxNorf-Sonic.pal | Bin 0 -> 32 bytes .../custom/Cloud-JamesWhite89-FF7.bin | Bin 0 -> 800 bytes .../custom/Cloud-JamesWhite89-FF7.pal | Bin 0 -> 32 bytes .../portraits/custom/Fighter-CtrlxZ-FF1.bin | Bin 0 -> 800 bytes .../portraits/custom/Fighter-CtrlxZ-FF1.pal | 1 + .../custom/Frisk-LoneRedMage-Undertale.bin | Bin 0 -> 800 bytes .../custom/Frisk-LoneRedMage-Undertale.pal | 1 + .../portraits/custom/Gryz-HoxNorf-PS4.bin | Bin 0 -> 800 bytes .../portraits/custom/Gryz-HoxNorf-PS4.pal | Bin 0 -> 32 bytes .../custom/Jessie-JamesWhite89-FF7.bin | Bin 0 -> 800 bytes .../custom/Jessie-JamesWhite89-FF7.pal | 1 + .../Lenna (White Mage)-JamesWhite89-FF5.bin | Bin 0 -> 800 bytes .../Lenna (White Mage)-JamesWhite89-FF5.pal | Bin 0 -> 32 bytes .../custom/Lenna-JamesWhite89-FF5.bin | Bin 0 -> 800 bytes .../custom/Lenna-JamesWhite89-FF5.pal | Bin 0 -> 32 bytes .../custom/Orlandeau-Unknown-FFT.bin | Bin 0 -> 800 bytes .../custom/Orlandeau-Unknown-FFT.pal | Bin 0 -> 32 bytes ...n => Piranha Plant-JamesWhite89-Mario.bin} | Bin ...l => Piranha Plant-JamesWhite89-Mario.pal} | Bin .../custom/Porom (Devout)-HoxNorf-FF4.bin | Bin 0 -> 800 bytes .../custom/Porom (Devout)-HoxNorf-FF4.pal | Bin 0 -> 32 bytes .../portraits/custom/Raja-HoxNorf-PS4.bin | Bin 0 -> 800 bytes .../portraits/custom/Raja-HoxNorf-PS4.pal | Bin 0 -> 32 bytes .../portraits/custom/Ramza-Unknown-FFT.bin | Bin 0 -> 800 bytes .../portraits/custom/Ramza-Unknown-FFT.pal | 1 + .../custom/Reimu (Blue)-HoxNorf-Touhou.bin | Bin 0 -> 800 bytes .../custom/Reimu (Blue)-HoxNorf-Touhou.pal | Bin 0 -> 32 bytes .../portraits/custom/Rune-HoxNorf-PS4.bin | Bin 0 -> 800 bytes .../portraits/custom/Rune-HoxNorf-PS4.pal | Bin 0 -> 32 bytes .../custom/Sarisa-JamesWhite89-FF5.bin | Bin 0 -> 800 bytes .../custom/Sarisa-JamesWhite89-FF5.pal | Bin 0 -> 32 bytes .../custom/Toriel-LoneRedMage-Undertale.bin | Bin 0 -> 800 bytes .../custom/Toriel-LoneRedMage-Undertale.pal | 1 + .../custom/Yuyuko-HoxNorf-Touhou.bin | Bin 0 -> 800 bytes .../custom/Yuyuko-HoxNorf-Touhou.pal | 1 + graphics/portraits/portraits.py | 24 +++++++++++++++++- .../sprites/custom/Alice-HoxNorf-Touhou.bin | Bin 0 -> 5792 bytes .../custom/Alphys-LoneRedMage-Undertale.bin | Bin 0 -> 5792 bytes graphics/sprites/custom/Amy-HoxNorf-Sonic.bin | Bin 0 -> 5792 bytes .../custom/Frisk-LoneRedMage-Undertale.bin | Bin 0 -> 5792 bytes graphics/sprites/custom/Gryz-HoxNorf-PS4.bin | Bin 0 -> 5792 bytes ...n => Piranha Plant-JamesWhite89-Mario.bin} | Bin graphics/sprites/custom/Raja-HoxNorf-PS4.bin | Bin 0 -> 5792 bytes graphics/sprites/custom/Rune-HoxNorf-PS4.bin | Bin 0 -> 5792 bytes .../custom/Toriel-LoneRedMage-Undertale.bin | Bin 0 -> 5792 bytes .../sprites/custom/Yuyuko-HoxNorf-Touhou.bin | Bin 0 -> 5792 bytes graphics/sprites/sprites.py | 12 ++++++++- 66 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 graphics/palettes/custom/Alice-HoxNorf-Touhou.pal create mode 100644 graphics/palettes/custom/Alphys-LoneRedMage-Undertale.pal create mode 100644 graphics/palettes/custom/Amy-HoxNorf-Sonic.pal create mode 100644 graphics/palettes/custom/Frisk-LoneRedMage-Undertale.pal create mode 100644 graphics/palettes/custom/Gryz-HoxNorf-PS4.pal rename graphics/palettes/custom/{Pirahna Plant-JamesWhite89-Mario.pal => Piranha Plant-JamesWhite89-Mario.pal} (100%) create mode 100644 graphics/palettes/custom/Raja-HoxNorf-PS4.pal create mode 100644 graphics/palettes/custom/Reimu (Blue)-HoxNorf-Touhou.pal create mode 100644 graphics/palettes/custom/Rune-HoxNorf-PS4.pal create mode 100644 graphics/palettes/custom/Toriel-LoneRedMage-Undertale.pal create mode 100644 graphics/palettes/custom/Yuyuko-HoxNorf-Touhou.pal create mode 100644 graphics/portraits/custom/Alice-HoxNorf-Touhou.bin create mode 100644 graphics/portraits/custom/Alice-HoxNorf-Touhou.pal create mode 100644 graphics/portraits/custom/Alma-Unknown-FFT.bin create mode 100644 graphics/portraits/custom/Alma-Unknown-FFT.pal create mode 100644 graphics/portraits/custom/Alphys-LoneRedMage-Undertale.bin create mode 100644 graphics/portraits/custom/Alphys-LoneRedMage-Undertale.pal create mode 100644 graphics/portraits/custom/Amy-HoxNorf-Sonic.bin create mode 100644 graphics/portraits/custom/Amy-HoxNorf-Sonic.pal create mode 100644 graphics/portraits/custom/Cloud-JamesWhite89-FF7.bin create mode 100644 graphics/portraits/custom/Cloud-JamesWhite89-FF7.pal create mode 100644 graphics/portraits/custom/Fighter-CtrlxZ-FF1.bin create mode 100644 graphics/portraits/custom/Fighter-CtrlxZ-FF1.pal create mode 100644 graphics/portraits/custom/Frisk-LoneRedMage-Undertale.bin create mode 100644 graphics/portraits/custom/Frisk-LoneRedMage-Undertale.pal create mode 100644 graphics/portraits/custom/Gryz-HoxNorf-PS4.bin create mode 100644 graphics/portraits/custom/Gryz-HoxNorf-PS4.pal create mode 100644 graphics/portraits/custom/Jessie-JamesWhite89-FF7.bin create mode 100644 graphics/portraits/custom/Jessie-JamesWhite89-FF7.pal create mode 100644 graphics/portraits/custom/Lenna (White Mage)-JamesWhite89-FF5.bin create mode 100644 graphics/portraits/custom/Lenna (White Mage)-JamesWhite89-FF5.pal create mode 100644 graphics/portraits/custom/Lenna-JamesWhite89-FF5.bin create mode 100644 graphics/portraits/custom/Lenna-JamesWhite89-FF5.pal create mode 100644 graphics/portraits/custom/Orlandeau-Unknown-FFT.bin create mode 100644 graphics/portraits/custom/Orlandeau-Unknown-FFT.pal rename graphics/portraits/custom/{Pirahna Plant-JamesWhite89-Mario.bin => Piranha Plant-JamesWhite89-Mario.bin} (100%) rename graphics/portraits/custom/{Pirahna Plant-JamesWhite89-Mario.pal => Piranha Plant-JamesWhite89-Mario.pal} (100%) create mode 100644 graphics/portraits/custom/Porom (Devout)-HoxNorf-FF4.bin create mode 100644 graphics/portraits/custom/Porom (Devout)-HoxNorf-FF4.pal create mode 100644 graphics/portraits/custom/Raja-HoxNorf-PS4.bin create mode 100644 graphics/portraits/custom/Raja-HoxNorf-PS4.pal create mode 100644 graphics/portraits/custom/Ramza-Unknown-FFT.bin create mode 100644 graphics/portraits/custom/Ramza-Unknown-FFT.pal create mode 100644 graphics/portraits/custom/Reimu (Blue)-HoxNorf-Touhou.bin create mode 100644 graphics/portraits/custom/Reimu (Blue)-HoxNorf-Touhou.pal create mode 100644 graphics/portraits/custom/Rune-HoxNorf-PS4.bin create mode 100644 graphics/portraits/custom/Rune-HoxNorf-PS4.pal create mode 100644 graphics/portraits/custom/Sarisa-JamesWhite89-FF5.bin create mode 100644 graphics/portraits/custom/Sarisa-JamesWhite89-FF5.pal create mode 100644 graphics/portraits/custom/Toriel-LoneRedMage-Undertale.bin create mode 100644 graphics/portraits/custom/Toriel-LoneRedMage-Undertale.pal create mode 100644 graphics/portraits/custom/Yuyuko-HoxNorf-Touhou.bin create mode 100644 graphics/portraits/custom/Yuyuko-HoxNorf-Touhou.pal create mode 100644 graphics/sprites/custom/Alice-HoxNorf-Touhou.bin create mode 100644 graphics/sprites/custom/Alphys-LoneRedMage-Undertale.bin create mode 100644 graphics/sprites/custom/Amy-HoxNorf-Sonic.bin create mode 100644 graphics/sprites/custom/Frisk-LoneRedMage-Undertale.bin create mode 100644 graphics/sprites/custom/Gryz-HoxNorf-PS4.bin rename graphics/sprites/custom/{Pirahna Plant-JamesWhite89-Mario.bin => Piranha Plant-JamesWhite89-Mario.bin} (100%) create mode 100644 graphics/sprites/custom/Raja-HoxNorf-PS4.bin create mode 100644 graphics/sprites/custom/Rune-HoxNorf-PS4.bin create mode 100644 graphics/sprites/custom/Toriel-LoneRedMage-Undertale.bin create mode 100644 graphics/sprites/custom/Yuyuko-HoxNorf-Touhou.bin diff --git a/graphics/palettes/custom/Alice-HoxNorf-Touhou.pal b/graphics/palettes/custom/Alice-HoxNorf-Touhou.pal new file mode 100644 index 0000000000000000000000000000000000000000..940972bf067a180895545db97edf466f62135b1d GIT binary patch literal 32 ncmZRuNap#UpQsYAEg=;j{6|)vL6|`)w8=o-@?7n9qyNbOnc@m_ literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Alphys-LoneRedMage-Undertale.pal b/graphics/palettes/custom/Alphys-LoneRedMage-Undertale.pal new file mode 100644 index 0000000000000000000000000000000000000000..803a6a3a00e679a3001a221caa823ee0e03bead9 GIT binary patch literal 32 ocmexgpUm?=Kgdg7-I7V(eWz@7{`a^o0t!r91mv{dF?`Yl0L|SC?*IS* literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Amy-HoxNorf-Sonic.pal b/graphics/palettes/custom/Amy-HoxNorf-Sonic.pal new file mode 100644 index 0000000000000000000000000000000000000000..96a3acdb0fa1d64b2fa7edd4a2a96bb6b06be099 GIT binary patch literal 32 ncmZRuNap#U|A4VRPS!Wx`;V+VgDAs)=KWgFWZZOvtfKq@wWtcp literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Frisk-LoneRedMage-Undertale.pal b/graphics/palettes/custom/Frisk-LoneRedMage-Undertale.pal new file mode 100644 index 0000000000000000000000000000000000000000..f75520f2ee48ff973c8f7e639d4b2412ec8e2af6 GIT binary patch literal 32 ocmexgpUm?=|CmG%k2}kMyPt|ZMK2b%7 literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Gryz-HoxNorf-PS4.pal b/graphics/palettes/custom/Gryz-HoxNorf-PS4.pal new file mode 100644 index 0000000000000000000000000000000000000000..19dc123d863f7db298f02933e145f6dca6f55501 GIT binary patch literal 32 ncmZRuaN_u%AH*;vsK;=Q(G)=zVHTm5zy^b7GH$v-R#E-{pnnOq literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Pirahna Plant-JamesWhite89-Mario.pal b/graphics/palettes/custom/Piranha Plant-JamesWhite89-Mario.pal similarity index 100% rename from graphics/palettes/custom/Pirahna Plant-JamesWhite89-Mario.pal rename to graphics/palettes/custom/Piranha Plant-JamesWhite89-Mario.pal diff --git a/graphics/palettes/custom/Raja-HoxNorf-PS4.pal b/graphics/palettes/custom/Raja-HoxNorf-PS4.pal new file mode 100644 index 0000000000000000000000000000000000000000..19dc123d863f7db298f02933e145f6dca6f55501 GIT binary patch literal 32 ncmZRuaN_u%AH*;vsK;=Q(G)=zVHTm5zy^b7GH$v-R#E-{pnnOq literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Reimu (Blue)-HoxNorf-Touhou.pal b/graphics/palettes/custom/Reimu (Blue)-HoxNorf-Touhou.pal new file mode 100644 index 0000000000000000000000000000000000000000..40702b76034840269e662cc1e125178086af6711 GIT binary patch literal 32 ocmZRuNap#UuPE|V$4MqW_>b(;npTfF#aqI*2*_!@WB8;A0K<b0}fEzi|%H~OCp0M*3}r2qf` literal 0 HcmV?d00001 diff --git a/graphics/palettes/palettes.py b/graphics/palettes/palettes.py index 41fbead5..cc8af236 100644 --- a/graphics/palettes/palettes.py +++ b/graphics/palettes/palettes.py @@ -176,7 +176,7 @@ 233 : "Pacman Ghost-HoxNorf-Pacman", 234 : "Palom (Adult)-HoxNorf-FF4", 235 : "Paul-HoxNorf-FF2", - 236 : "Pirahna Plant-JamesWhite89-Mario", + 236 : "Piranha Plant-JamesWhite89-Mario", 237 : "Porom (Adult)-HoxNorf-FF4", 238 : "Ramza-CtrlxZ-FFT", 239 : "Ricard-HoxNorf-FF2", @@ -236,6 +236,17 @@ # FFT 300 : "Alma-HoxNorf-FFT", 301 : "Orlandeau-ctrlxz-FFT", + + 302 : "Alice-HoxNorf-Touhou", + 303 : "Alphys-LoneRedMage-Undertale", + 304 : "Amy-HoxNorf-Sonic", + 305 : "Frisk-LoneRedMage-Undertale", + 306 : "Gryz-HoxNorf-PS4", + 307 : "Raja-HoxNorf-PS4", + 308 : "Reimu (Blue)-HoxNorf-Touhou", + 309 : "Rune-HoxNorf-PS4", + 310 : "Toriel-LoneRedMage-Undertale", + 311 : "Yuyuko-HoxNorf-Touhou", } def get_path(id_): diff --git a/graphics/portraits/custom/Alice-HoxNorf-Touhou.bin b/graphics/portraits/custom/Alice-HoxNorf-Touhou.bin new file mode 100644 index 0000000000000000000000000000000000000000..809e8d413243c88f049306bcebf50230a968811a GIT binary patch literal 800 zcmY+BZAepL6vxkfylvB7?s5`aTF(N@3iGYdl`r>>p{=5%FA@YPW-Q2ZpeSPVKAHwa z?+__O>qA5sX^>e`x?7Q23=D)`AhsK(D>8S>mvp-OG^L2nkHi1`&bPw>SSW@;QLJ`3 zw;C_%-$nEagI0^NL+n-$kF{OFsk{%gV8o7E_e8~*=RMC;Ri%`LC<$=3g^%K{LBuI* zp&)aSp_~H9vhhG)UOGLyCe-|6YQZ1BFHl!<&jX>kx~cr?GvnvSTMu3K9rT%e8$HOb zxr8pEo5&0i7@@mQJ^!xbL;GC2*irB>Fk7SpNYW-Uz;N6u&Y*Spc6&m!hmP2kz`G%v ztcP{*ReowbNxmpl@ynC^_iR7!sT7Ar|M%LvIjIoKVw|`U68wCS5AlEQjRYZ*AW8qZ zBuO`AC2dAGV7Xp)qf|UK*9>N}Uau1@P)15oLld~r@z`>MJ?qt@w(0Vd>*{o_3i1Xifoub7bi-0GnQ}{rlUPWx&Y|gM`nmDXyub^^B^}C**?u}b_?F_e zc{w>*DVD<8gSC!oYc*%UNpYz-6&G@antjN=qjSG)@S@vQb|N?_z7f53YIuVBTGbJy zm9(%e5DM|OUdDwm^uqY&2m3^tHP>Gn2bupIwu%4F-;scgDxp-qEtH=3Pbg)@IvnYrHu= z&CychUH=7SB7j7IS$rFT{$?t9BntmnnT(9F&`30(K`ax4Lb<<8Dnl1h(Y!WGTfzBp cPww9Qt_y<`TSsO%1SS!xJZoAbY$ \ No newline at end of file diff --git a/graphics/portraits/custom/Alma-Unknown-FFT.bin b/graphics/portraits/custom/Alma-Unknown-FFT.bin new file mode 100644 index 0000000000000000000000000000000000000000..9a2430f5aa777ef54ab6d9c7567ba54463204b57 GIT binary patch literal 800 zcmaiyOK1~O6o&tqJIM_8F_Tq__Ay1p>Y^l8G=g@fC>S?w(S_h9)3~sLj}#Y;ZD|IK zG+MExA{50Yf~Y$aPz;F8^#OL_t0*D`ZMtcTsGYV#0%;O&V{uvUcRBZU?l~O(!*g~{ z1kZ7Vh<1@D01pCj4I&(Ub8)PGf7j0=ty8mOiNa(PI`9G`NMJ9{p-l~ir~O0jhlzot z*syJTv!@rg@eZvxi3~pDHHuO(Ts)XpPvemze#fyjgI+wvG>TY&kJM*6u&bOmZW1;GP;C*07PYF3%qs)1ZxqR)LlqQ@QAKw@)AK{{x zMTfRy*JfW%$O%?iRy3TJY}uaILRZz8I;;#U9~7_TjZmZ+R*5XKkRs-UuCrW#$yR;! zE@*nFhbBvVs5G6ntcf^P6f+r*XW>B=lFX-pdBnYjp;N?63JQ}_HEj%2SFD|?R5f)_ znpV>;S28(2-#PD{_hvm=%94);BEg9Dz)D(QvZc)H84*M-lO+OFv-PbA?%}{#w<-P-V{g(t$5bZWmAUt7BfRgSu2&@Zu zup`okQ~XOFUDm1hAW65%pj~B)9t)5I2eBAzvo!q5F+_R|9AXG4$t&4V<^BC zf_CmY_mMk{Gh6yTtw@opg(B1ZFB+E1bHaM1t-hI|Rj7uL>T7f>btQkM1DmQ^so-BY z7L?BmxwYn%A)}6=4R%-mFlK^9|F8D1&8g#4wjODYD`>QFvSO)XNl!IJ&qo*Ci(UQ2 a-Ftd^Z{CuphwAFbTE+)1kbFz(x%LP6)c*$n literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Alma-Unknown-FFT.pal b/graphics/portraits/custom/Alma-Unknown-FFT.pal new file mode 100644 index 00000000..d4eccc51 --- /dev/null +++ b/graphics/portraits/custom/Alma-Unknown-FFT.pal @@ -0,0 +1 @@ +_)Jx62>%.72BF[g3!- \ No newline at end of file diff --git a/graphics/portraits/custom/Alphys-LoneRedMage-Undertale.bin b/graphics/portraits/custom/Alphys-LoneRedMage-Undertale.bin new file mode 100644 index 0000000000000000000000000000000000000000..ee16e7dffcf732a7b28cbc2b8f12a0740ae60b30 GIT binary patch literal 800 zcmb_a&1(}u6o0d`$s}&bM(oY%D&|lq!iq`-)2@YBul1~lfEPgz!HXZQ*{#y1L?j3Q zfE2xpM-OVZ6j3im5YdCsH3uU?$W#>9X1mWfX^Pgf@3(Ju=DmHt_c06bzl4L}mcEmX z1BVRXZ(iB+?NaN`tx>JF<=#LwZ_}V zVP~zojsYI-JY zS>^Y@Cj=mAHy2mDtsG?YZF@Ujy=|EabGgOEsi{elr*q@%CQF6Mcn&=R6-Ou*^Lfh> zK=WXXVu~m8d#%4n<`-&re{Aq5;sHaBFh5hf`aMg7rQnDA+bpDhf2`CpJHHAE|pQOoV8n~ZlzH64YYisn-$Q7iQVCttfP$ubXE(YV}eQS zP0GGX`U13`)?=GqCCCPKDauuK=do&R07DEJqt>~xCl{7o7w7?%Mp6$A7(Q-G6?%R+DU<2f`O%GDmZD{0+&_?ezcv literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Alphys-LoneRedMage-Undertale.pal b/graphics/portraits/custom/Alphys-LoneRedMage-Undertale.pal new file mode 100644 index 0000000000000000000000000000000000000000..e26c3ac5dd48c3ad6e8fd1668bc09bbc9c75020e GIT binary patch literal 32 ncmaFB?8hj=G=uA}&~83^@noLwaZ%XY>J*V11iMNh5OE2iy$8VQq+Gdl_tjPea^)qClFb_vK!tfTwmXv^ihn&5IH@-GT z&3VUbFS&)DVygJ7SaMYpJ?vveBMY{(hM zXd+?TY92~K^upz?QW6^iBB<|-e-RKx18$ND5?^`Qo^Q+FQKqP|>xDC4R>>k;Jk?lt z`S{PK1sz|46SW}Fc!CjVcA8Fy=s;*Wm{+je_;b(@Q9S^=q0A?KwXh)akdXD|U8xbn zg8@5WSM>a0-r*O_SLTATkoam(WKNbFZoirBYi((cM%V##1`Pj+XU=C_;n9}y_0Fp|^4d;j2Cil#x!9kvAT?je0mKgSFP86m4KQz>vXU!l1<<${@ht1v3svH!wV4_<+O*F&8k* z<}sJh7l={}S14vkWN=_$Vqj(90_tRDPynl4UwAiV(}6e47i10yIn3J-#>pVerq6a* zMqXjAQlOqBTmOe$t5rNE%5ONR;H_}d@bnFZFZ}BF;uowBSR2HDf&ap#oIMFY6e1Kp zG<;}y0CbxI0|(F+hlT|}yMaz>09!(H!h*rwqNCjW{+;zOt1K)6%BSC7yME*S)%Oqo z&-lJQV@~~RnLiD;@87@w|Nr;z85#BUGIJUdJ{b zC+;t7EMGa^-d6bAQs?x)dlOTsz&nMvitOyn?Gq0xbnE*2&!2BU&;BptXQuN!b~f)h zzB7Dh@Mrk<|IfdVe-8Zp{Nu-uUq8Nm5ad)6_qPqRT2nYPd6WB9J~jp(VCX9{a5IPj zS)2^*(NlKnmPWnacunohS;<=l1`NpzhZtIbX-10iE7MB0%I+oi_BlUuygx^I&%v}~ zG2sl#4CV|?60r(4PX8EkCjV%7F(EI&`p&i!G7qL?+(`%k#z6-|6GIAv2au%!3l5z0 zjjRnRYs$i`;|+67subc?c$wTe+<|szGBB};vkS8u$MMPNc1Q<`ZPI=4i;IO}Ug96- zjM;NS literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Cloud-JamesWhite89-FF7.pal b/graphics/portraits/custom/Cloud-JamesWhite89-FF7.pal new file mode 100644 index 0000000000000000000000000000000000000000..47dc3bc3da37f123295f491a9bb80e15cbaef507 GIT binary patch literal 32 ncmaDZqsTIaH(V(u{x#2UzbdEAni5tu`3)uvt_iUmifc^)x@Zd2 literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Fighter-CtrlxZ-FF1.bin b/graphics/portraits/custom/Fighter-CtrlxZ-FF1.bin new file mode 100644 index 0000000000000000000000000000000000000000..b61aca3c71892c0376b6c9dc554baa17298b1227 GIT binary patch literal 800 zcmZ8fTSyd97(R2KZP#YCqV>i2s3(UO4%84L5WJu zz@BWZm!e`9g0u&7Ol9#U=c$aW9WfHZ%Pb=}#JH!KT@dX1;ph9#e>t50KLFUEX@>(R zP^Tj!CDYCU2nQ;P2;zEJ{k3_y_WSu&=eQLT0gBN~CR@ld{v!a)%#=s=g{$$cL zt+3mNe??3P^_-!w=5RXSj86hat3(qXB>x!nz9WTZPe~b*&rEd|%fDC{vp20jbjL;dAV z6a_Lst|yb*-vU}{=xj%}crY`sfV;*^gWlKC#Oa}vPqLQCDdK*96@D2!;_lE+geFS% zaalPiiJ~Ow!il34>fCAX0V7D_?xlo<`FUNRn`>HRV_wDfqf?^n)b@u?48 zx5kQmD?VR$x(?G+ZqOvG!)> O6 zRsy&f0t7%pC7cCGMnq(AXmIEbktAJ`e*O{-PTa}hmBtvMc<#une0x{}zaekz^t28| zK_;YN^)kFh<5`qX3VUj<-mPfTYAxq1bUZs=0EB@EC``OCZ|vlBmHXlk(pi1|s5O@I zcL4-JBp{$@{jG_3Q)R7Q>iy&0?c>N;Z5PIa@qx_7&~;Bc2JW|Jzc@S^Gh5sakcCm> zDKfFoC@APF=)2?Yn()*9kq>;}!M1|O2G*Fr7WARBA2(*v9s zdPChSZ7!8AXjZ@e{`S*|J+o}oXV(`dhiO!+TU>1=owh?8`_KYy!XcSKm8?ipq{ZzN z?-CIq()|^i=K(&mf&#ZZ_5Zkz$A3z`k3Wd}%O$}u+0w-BtUXGHM z5dJF&44ANTK;h`|RM9ilpjjWXD)t^&t6bS?55OMz&}DtMH1G4e&F*f8#F6000uE-Y zlG2qI-Y@pPemI#hskDmchoJ`F4{x17oRh(Y{zYYvvZq=5pr|+h%1!q6*o3eu97u&S zRDt5$iVQ_YgjSVwJb6pm|1_Yp7%V!w%r1}p6#o6xoQ^Pnj*xNX91>@Y0E7_j_k@gS z#()se)Vf7vG|Y(b+lUW&1TJz-K6`C+t$uk#hCslOPKvHJdTn2O)}$DQ literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Frisk-LoneRedMage-Undertale.pal b/graphics/portraits/custom/Frisk-LoneRedMage-Undertale.pal new file mode 100644 index 00000000..244e4f39 --- /dev/null +++ b/graphics/portraits/custom/Frisk-LoneRedMage-Undertale.pal @@ -0,0 +1 @@ +Q!6|:F)%?[AN.^9`` \ No newline at end of file diff --git a/graphics/portraits/custom/Gryz-HoxNorf-PS4.bin b/graphics/portraits/custom/Gryz-HoxNorf-PS4.bin new file mode 100644 index 0000000000000000000000000000000000000000..1b8de6cbed637488f05b5f60c0ea8f1f3a6dceb6 GIT binary patch literal 800 zcmZ`%J#W)c6g`d|KMNdOi6ui6_a|f{2#B492~}N?*wTqkh>RUV{Yn@zAcYMi1|l&q zFwm$IOZzHNU}}(9Iv^A%A{`L8q9Tss_;GEMMilXm<@#E??mtchjx&nc1bwA{I}+Q@x#v|GrPTlb@wqx^3B(v>n@XJZBy%67Ft)So_!w z+mjtBWhCX?p56wMOGsZv&7pRLzswDIsHmYDZmd_gg(G%^(`=|vv4JWC+|#MZlFgh} zp4HBq&+@(U*ZlW|5Fs)I9ry4WCf?!@YdqGb2oRtSAN!;~#3tThnKV8INhcz)EE6wJ zNaD$M@Fjn2+-zNKH4B-7R@T<`DeDcC(ZCcFf)IY`vUx6}f_E4Lk3c!eWk^x)tL z?4f>9DS~met`wm10Wn522M=u=kk@M1pZ=Imu3Xx)Mr^FC1GH8`TutnmW2PP>tLg+s z8&4fbCa2b^J-z;Xse5hfXZyv6#`^^x!=U2nab0{W_FftzB9!SS{)C-~02SeJkE@~O zE0HzbXQRbvltR7%nu_fE5Ve77;D>cm>LaR zAZbxx)Q8G1QXj$(#Y#dMLPe2ZEGNUT52>WZaxb@YP4W-u`Eky9e$UG}59fJ+U17lN zk{ykKn#twz7)SQc6W4}r-;~V?x;P}D2WJsCnsclZ?!H*`Q}YYuoEPv@ACL#L#s!B_ zjweKf4dYlR>wcM23x&9?nY3rP_JS5wi`uHSYLBu>$U-Iz6onMkU>5DaM*FsibiQ8I zp&hM)eotz=b5Nykj77dn4Q|KA@^XzLdQOQL$#+&cIpWHk!GlK0CAmG4N4k*yB>nx7 zv4QygN@Iz|BFOLBRKePy7!+s85ZNx`zc=|-fe2(={CAaXqRc1$1#jCY)-fy~?0^Q! zza2uvHgI(%^!sZlci2AWmh&$|2NiPAjdsdx@Y$|+wUs`;l($tJmi;z#L5~(J;T`Rc zTEQ|KOkirS-8?;QW}r|oRLYod2qKDn-D|T)s+St#QZyP_mlnU6Yoxg3jJv{N7O6Bp zNtvul=CU*yOa)uX1MD``T#6=1lY;2}?%Eoo`?y$nODrU;kHrnVlKVFRGu*fsvEChN zt}E)h6F9b1ww!3%L`aMYF20&hKngg27p>kR82CrN&E9Bhn}N{!37rvXlj~_)Ngo$|3CW=`vd>~ zF#O^9%=m-h4+Bp<1A7DW4+fyXfA;^(|3Cl#|G)k}JGU9Py1K8Kzit!=>P1U&NlBTQ zn3`y5fS`s3_rAq{ABDDM`F?f$v+)1S{~H`U895k&osM5Qe4ukt>#UjUe8ksHUoJV( zjiGiyc|gsPhDQtw9WMw}I5aUd=z33iaqUWW(%emFFTPt_l{nAv8$YMI3O84Clc#59 zrl6qZRYBC-xV44g2bhOP^edIr3u%& zB8Ut~?p@fKFDg>C2? z_vh8#ud!#evvIE9|G)mig#`37zh}Sw&&>Yg12AftnH?Ms zIIyrhlriX=k$%SbnP76qMxPtY{y+X-SU;;cx9PFz1}{WKEdq= zUkBS0wl6@)qH|)#o3z^qFGZtJ5@VX xCwe{_D`_Q!5^e!Ty@`2FSnqAwYL#_jOC>LQ>30N?lxh5!Hn literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Lenna-JamesWhite89-FF5.bin b/graphics/portraits/custom/Lenna-JamesWhite89-FF5.bin new file mode 100644 index 0000000000000000000000000000000000000000..d34edd313708c1ae9bef594f6e8d8183e363a20c GIT binary patch literal 800 zcmZQzAQCV$urtar*dtUk1DSFR419cof=mqc_8(r{|E#ZmA13d>AR@xe9UjggA(4_Y z3mxcxhM@x%`M@EK#faO-0 zd%$!9!vPdNLjyxYLc@Xs8yq$`Oh^Dj2L=WQ1%?F-4GS8;z+uCK4-Y;pC|FRS@WJ54 zh8qnHAQK#b;J^Wpf()tS_NpG)8IBGN42+B}E<8L84Cd?w z&k}tOOR{}(|LFEn;M{zN%BJF$=Nx>3{sDdtiiQjJL(KxlE;B!~JdZh)&%n&U!ob5I z#2{Z`^W6Gv`t5t)_rI&XUt_Pp;KJg;(ZC=eprGL7#N4o;{<+2R|MrLf^Ug1m|2_My zzP^SA#0+2vIIys6J{SG{_S@TUzdUdL|F-V$?v9QlM_gP~RFt)91cZdRxS05vSeaN^ z_*u9)#ku9UvVcaHBI>_7A`%t>U(W-4VW70q&$V*O?ITP%C3 zXQyhX>I9Dm0t^R)HZ?IZ)o;0-Q|wu0;BVe>NYGVjo7p6xq(yFR3>)0b-@Utg2LQ0` B`Q!iq literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Lenna-JamesWhite89-FF5.pal b/graphics/portraits/custom/Lenna-JamesWhite89-FF5.pal new file mode 100644 index 0000000000000000000000000000000000000000..f82530b8e6112799a0daaf1aca074fe5cdef8399 GIT binary patch literal 32 ocmexgU*pTbU>_D`_Q!5-dA$F;_!w?~(U*)n8GkZdbrDbi0Mo||Hvj+t literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Orlandeau-Unknown-FFT.bin b/graphics/portraits/custom/Orlandeau-Unknown-FFT.bin new file mode 100644 index 0000000000000000000000000000000000000000..adc6b209bbc6b2a66168eae49aa01951fb0bbc51 GIT binary patch literal 800 zcmaixe@IhN6vxkfckMlJx()j=>FK5h{u8x1(N~S<3q&eP3I8!d7%133m9}76x_c{F z1Yrh2L4p_&^^XVz`iE>!41W+zp?@S0Clgm7OqsAdH+{Y5@LxM0?z#7#bK#utxd2C- z8Cg*Y6dfxqwwKsB`@v%bCa~ynT#T&kbDpt#JtPs@_*SJkG?3sXyao}<(8^*`)3lm| zIcN%wH!p=*-Jdhe+bMo7ds{?X{i&toQf6l7=;{E~e6`g;u`0z1R(LEHKr)iCkC&oA zo@$f;0S)w>hDb=$h+f!mglLnZV`XxHl{1lvd3mR{=~7p&DYt&WY&MoPsaziILlC@R z2Q$n8c-y8x?;`m^g9z`+7hrxFLc4nYFtgLIk*qq z;F9JXNwv|yXhj_7@4?pqb%v@eotsbdG34Gp^PYFt|6BVDqzH?bht)b z=ib07te*V)?hc0x5Fp^d#F?;hLWQ9rz#Our+=~q{J|Dgtelpn55lO*3A47TEb($M6 z_lX~8ermy~pe-e2TDC3eIq5qHRG_w(C)73?pwj=wuMedXD!FQ?0vE32s(Ag|owyw7 zaOL2{VnO7Ar;l;}v@!XD?UTq}G{n5jy)9?l!ymq+#_Oc~%#NCwW%I0)O|)OneY$s|!5n0Os6^enrCp9Tf4y<7ic;l5*o8c_Y3ft1!fa;@D^te#E8F)3(e)gB3^$c(M U7_nLUVN%(`%QZvi-K~A$e>BJufdBvi literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Orlandeau-Unknown-FFT.pal b/graphics/portraits/custom/Orlandeau-Unknown-FFT.pal new file mode 100644 index 0000000000000000000000000000000000000000..48486147baf5f3e63de5fb9ac8e3f8bb0669017f GIT binary patch literal 32 ocmbQ8xJ*RYtkmg--$u<>5(TO}vR5?qWGk)2jdnXlr!z1B0KfSPdH?_b literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Pirahna Plant-JamesWhite89-Mario.bin b/graphics/portraits/custom/Piranha Plant-JamesWhite89-Mario.bin similarity index 100% rename from graphics/portraits/custom/Pirahna Plant-JamesWhite89-Mario.bin rename to graphics/portraits/custom/Piranha Plant-JamesWhite89-Mario.bin diff --git a/graphics/portraits/custom/Pirahna Plant-JamesWhite89-Mario.pal b/graphics/portraits/custom/Piranha Plant-JamesWhite89-Mario.pal similarity index 100% rename from graphics/portraits/custom/Pirahna Plant-JamesWhite89-Mario.pal rename to graphics/portraits/custom/Piranha Plant-JamesWhite89-Mario.pal diff --git a/graphics/portraits/custom/Porom (Devout)-HoxNorf-FF4.bin b/graphics/portraits/custom/Porom (Devout)-HoxNorf-FF4.bin new file mode 100644 index 0000000000000000000000000000000000000000..504e357a997df6f463d874ff0f7f0a380df915cb GIT binary patch literal 800 zcmYk4?@Lor7{{NxGw(5(UTr~>jm;N<1cC0|z~!C2@r}?Q5cC@*5G*V-(w(b_S_v7x zu{77a2tooWjKba08W^}2zOYyAih~j9ytd)4?Cw0BO^Ci9csSp~!#U@9o-+gp=*zU> z8Rk(XKLLO+Fdu7-I?g-25)~d|z$H`BRgDFVvIkINF~+d0vJ(Nzj-pk`7xte3B*;N@ z#Kt_gkyr91DVO`hOE%|-tbw^1VU8#fQ2-$b)(aXcoD3G`5+4fR?HSx0Xd5`2S?&!C z^DrjWbFnwEPcd=pYwX6;Y!g^#BG#tyR=%mVXs=5 z0xgmv@e)ciG|`-F;gB74*p4Z=SKh7hxhw$N9vi#s%nNp19#6hNgp7ZWU+-7@!M| zZDsE`XchgpQCZf=Rgyz0ix5hvgHVZxmI_Hgz#*xY+#|npc-FsJkF9=tp6a7V7mC-h zCip(mu3s`1F6$doD>amHbSW>Cxi~ZNGpAPFL7vcl_*lw9Nr42 z=KGeqa0#~_oKoNJ;O{~XQ`m*C1~0~2lNi80gGI9s4ir CGT6vw}_Gdr{HtgftUEY9L&2oXex{gc+1JuLkpf(Ytr8Dw3mi?m@lBViJS zb+QhDQHSW@rJ#ZiJ4K{;NFf#k#hpsCL+tEQxMPkxz2B&i?=!#OFz@l+`_6kxgF?s+ z3eU?jFG;PTP2z|nflz2>W^8P7a$;hQ%=1vePMpSm97KS?(9nG0^_zS5ACEd-$unJK zu^q>74d+0{L$TyiZAo-^m#e~qDnTMI$AV-Qs0QylIC63V7lc5t(gBDFBcJc>)%8BT z&$o9en~Hd_Q6N0%FqalrSG_9XR-Nifl|}zYs6_0@)UM&y0~ zVH6jPq@`h<9Mx7ynB=II=rWjU_FodB6xS#-=cA+FN25H(riL}RoXh>? z9R3e)NfS~P{YJmx!o{Tr39HR)se9l2th49F=ikGXq%+Y+_+Q`rOZyN(2pf!7*XIaKAM^BGxzJJQ! z4(nt^_l<8`Ixs%cbN0Dd5i2D)TM&YgV5BxIH_1&(Oleobh!JtL`eg7zd(!r5@7s=? zR!GjPc9hNbCQrxTlZx_kTMDyKO==c!1&8(2*vAIpU$ME^T;t4{M}rBa{zQElUx{xV j;|I6E-#rzcZp0Ui;3PN89~oK3ty|i3dq@U>P9IM>O}llS7f#X9{1NX=J$X&~$$(%QYPsp15mfeSnRa{I zX}jnvI#QS(EeT{s>c+2HFHbH^`d2ouNXyc46>ElyD$|HzP?R9y3x=6Y!?cZ_C83L< zzM%ZFH*8un#iVzJ$7G7kkb7_=6FEpTeJDO`Xq0lK-FV;s$d@&|%jCEF?RR~)@NM7q z1IH#hzdn3WQ1B~3nOntt*+M}B_JeUv23Izcm*tv3#G254E3zC2L^gK+H7D0A`P^i! zr`lmG*=ecRw%w7d?j1Q%GfSEbj$*gbQtj3>BJ!S#5E&Jjo(VM3bAiK43&z(IF>9=5 z8qG1t}Cb6!%UB^G0AY|Z#NS4makuGQ9a9h#=CbK+XH?M>6Hk>5x;M0=wB E0L;+S`2YX_ literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Ramza-Unknown-FFT.pal b/graphics/portraits/custom/Ramza-Unknown-FFT.pal new file mode 100644 index 00000000..c097ac9c --- /dev/null +++ b/graphics/portraits/custom/Ramza-Unknown-FFT.pal @@ -0,0 +1 @@ +{-%y*G%722.BJ[g$+1lE \ No newline at end of file diff --git a/graphics/portraits/custom/Reimu (Blue)-HoxNorf-Touhou.bin b/graphics/portraits/custom/Reimu (Blue)-HoxNorf-Touhou.bin new file mode 100644 index 0000000000000000000000000000000000000000..36fce1c2657700af03005cb856bbda3f94871e73 GIT binary patch literal 800 zcmX|;Ye-XZ7{>o+N83ehD+pX-TP-QVR7!AJ?Tjpx8dz48bazCO)x6cr*3OwLmn~8& zN>Mmo!2Hm~vdg5JXY;Zd=0+E0b~!63EiGqit#)?)Ww+<|;pKTAc)vVfGrQX^hpWXC zZGubR-`#ilO+{;%rws7}9C^EsFWEkaGd%M_zJVRYNtm(o$CEc81W2Xex9Y*B;yW%6 zJucLY9b@br3QYYOfWIYBaOgRDb=*c-EH6Ad$_LR0i4%ScHP;OSyzkNa+~X*gNLG0k z($ZI1V=A^M?gd_{t@-nLl@bZ%n}JziBy%x01mAoa{vc7dDG--l;Y#2Vvi{+fNU4US zp6|+$EQv{4ow6L&ArP?O3%|)-*l)KeI`u|~KxAvu2+G{v)pD&lwr^bE=-gc)0$Ebg z+6}XlirA4Mg@V!(4-@pK>if_4_Iy35#vl%bn2BquSrArf+FEj=VBPVsW&GrFYZJML z#%*av0(2prK=}&*5JX`sI5TkNU?My)oVmBgk&OHbBLq$JV&|#WqnU!%;Iy9BN7}qY z+9A>{0p+~H!_W43E4;hCz?a}_DYB$HU1O6u2ck13%Yq&1yMsk}N7D*)qs1^1a_}k` zKEZ|9zRwp9ebn_ed;IM;BBn{Aa5tLmZ zQpBFpVWfbK=ZRzi1KJE(gF$+ry=#|C&LRvdcbvrS^sTc6G5O~vLPt??q-;U+s733> zu$%hA;3p{V2cq9o^fJ}86gA`z3~{ScTS%|pHF?qR@Q%u2H9T%wx@cD8IBN}RvaMRD zxJO=^ofaakYgn}8dP7`;szDXxCD4mrGrjKYZH>4okb2A{1VTaTYA$htASflE>L#Q1 zRu`J#=|K*I>dY*0+8{9S3}t0v1cU)2f_7N(lB;Lh30gTtyjOhz)~6-48crY`+Ho6J3%Iqtg3XdGdm+Q#RMWT@_AR)NHe>r;y0Q1{JK2>x|Iy0 literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Rune-HoxNorf-PS4.bin b/graphics/portraits/custom/Rune-HoxNorf-PS4.bin new file mode 100644 index 0000000000000000000000000000000000000000..d686cc10d7a3a9bb2f7927a84d63231cb3034a97 GIT binary patch literal 800 zcmY+CK}Zx~6vzLwGrK#+nyX$`GquIqE~VAM(uEyDO3~1ugH-4mWf5f>rE974X<{2> zEm8-A&{GEwvRW)1hb;1tbqR`67=!50V#6M6v~9lLxJLAT@I83%<9*-zzvo5{N)Qr4 zqAIE`?iAssNHK~K#39@O4{(*+_M6}2A9!LO3>nM#jU49|e@yqzef_kV+;oN5%&Yt< zUm{ij4@4ADl^Vhip2KBRTF1KdOImaE%LY2pgX`!be+~P=CsPZ{E#G`$Uxn=T#L5{K z&;lPO@DATm3*Eaer5(+soq9zS0;m@!)xXVhl>3SWj`s1!_`T!BJFDt~bNk-ru8bp2 zwU?z)NyJv{g~oKv&`l%#+*-7fe3d~#DPoAC0cFSoPw_QwT4^h3UE`F~inP)MIjA4j!cHTC-UIyS6l42(M_hjIVAK`6lyUc+3+B|9o!Z=9k z7f^>E(ad&h19t{St!(Zg8tCRo6Hed(5fhq(lX#?z$b%9)tjxq~8)j#Zjb80VpJPA> z(F{c0PHLL2?0qVaR+fYdLeO?K2AX+(Jsg+E&PBzBQNAus))_^9Y&_6HGhLFG+ z2E{1m@KQyFUROj4A}Ho|vLR=G+5Vz>xJUls Xoj9WoPK0%@R>~9_q08YBiqQWDgtQsD literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Rune-HoxNorf-PS4.pal b/graphics/portraits/custom/Rune-HoxNorf-PS4.pal new file mode 100644 index 0000000000000000000000000000000000000000..2fe95e75968a69d4886b358700f854c42e19cb27 GIT binary patch literal 32 mcmbPl!ocvqeo5qgv&{T$f(<6U3@$<`!fQ>hMb!&40098Z(F(Ny literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Sarisa-JamesWhite89-FF5.bin b/graphics/portraits/custom/Sarisa-JamesWhite89-FF5.bin new file mode 100644 index 0000000000000000000000000000000000000000..a4eb34075bbfca18e7b8ccacc3def4f5f4ce5115 GIT binary patch literal 800 zcmaiyPe>GT6vy9hezP+x>;5?ejpW+Y!7i?5QcCGGgNHm6dF&AE8i92P3IjK6nO`7m zb}8sm5N;F^o{Xd<1b1{LJ(vZ7No>w8MGzh~8qlrXnP0!(l6&p*J^bGLy~n&azxM#@ zU?d@_?lA)~*nuI8d)q#)92vh1uXr5eaJZv`QqNmkW3H2H1G{mgYc-M4vpnuNrBXI) zn#JPgCUXl`PvVjCouqz-pvvQgLb+V6dY`$sWsSlkf$;0)LkBKvSEL3Ha)TWfJYb#K{_cU< z(=&(8@ra`I^;IhQe2JmPBhwl6?w~%c+Jw^N&Q7^ZYnUb>G?|15i9p2KIQ&hiEZsbt zOh-yg9m-x2=U|n8?tft19MxKf`*W;&!jmyXK?AcUMMY(4=%F+;$*LF4ksjI5o4 z#!`#m0DHp_Q%j^>jq5XL(N`lFj7B><4F=3X=Da%iRDZ6|ks+90U*Fow=RUl*>JC>Fb9 H3q|2Cn2Yh^ literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Sarisa-JamesWhite89-FF5.pal b/graphics/portraits/custom/Sarisa-JamesWhite89-FF5.pal new file mode 100644 index 0000000000000000000000000000000000000000..ddc8d929771f32a77e7534565164eedefe56ccd3 GIT binary patch literal 32 ocmexg@6XG?P@OL?9<5a>JB#hFA3K9>cq-FMi!G@aYHq0l0IMPkL;wH) literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Toriel-LoneRedMage-Undertale.bin b/graphics/portraits/custom/Toriel-LoneRedMage-Undertale.bin new file mode 100644 index 0000000000000000000000000000000000000000..900bf6c26867b4811b9e4968a81e2a84a39ee6a1 GIT binary patch literal 800 zcma)4Ur1A76#w1bIh~;k!u$uj)pAip?Zq@-T!HN=Z!pOIfKH^YLD97!dr)_$HN!Qqm^p+xxB!Zlya?i6y`^mgvmhk_pZ{XRav@0`Oqzw@0B_#ZG} zU}9KUEPqEcaKY;syn12m)j4rEwXeBZ_cQQ7#&NXaHQf}YODwfk?!9wudA7^&OHlC@ zgD8chhf_QQmMLT{CYzDps0K3%V8z*%jT`y7_0QV4=2ykZ$p$=u)9SRkEfUpe1Y_#I z_o-!mF7lM)78kSGU*_&2$Nd8(K1&KYY~k|rs8=|YE^Ri(-~tQgFs zmvt;<(o^vn!iM2r!3I%}42Qy@dY9-^a%1_Pt#l@hG~Qzvs7DVn75z6m1!w#Ila-v! zMsH+c%W)B{aAG%XFu~09$CRnuT}{?p5$$MR)Ox9_AVeeqAFD#MJ>aVSx3zCt$*Ky@ zuKlF^L#QHcHl#RcfA;#Xv$?>!tIBRKs@>x_*x4-{rawS)DaSEz#8$BKL)pXz2R?Yt zPtVRgdYs2CY?Ac1h>;R{<@Sk*`T6TF)>=kKqtR8bgi~byf|v9=c-pIr>&ohCg)2gh zP~&OxG?n29ib>xkVgoT4lx3bTyvK)!N4C|W(ZI+^AdpCCeSPEGnf>Ua0R(U+@Ecf@ B1*8A~ literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Toriel-LoneRedMage-Undertale.pal b/graphics/portraits/custom/Toriel-LoneRedMage-Undertale.pal new file mode 100644 index 00000000..0231cea7 --- /dev/null +++ b/graphics/portraits/custom/Toriel-LoneRedMage-Undertale.pal @@ -0,0 +1 @@ +1FRJc9gV{k!k-(8z{:v`` \ No newline at end of file diff --git a/graphics/portraits/custom/Yuyuko-HoxNorf-Touhou.bin b/graphics/portraits/custom/Yuyuko-HoxNorf-Touhou.bin new file mode 100644 index 0000000000000000000000000000000000000000..606191f2ca571384b4bfe5c4411d4d3df27e49d0 GIT binary patch literal 800 zcmXX@T}V@582-*qYlqC8MTLsXbDY*P!X`y7$bHA8G?FYLihkT;6?J8!i!RK4pFe06 zOo_q^%`NI8NYc6}Bk~+m=wE{%jBGposa@oz7Ebr=e4Sa(124Qk7tixPE`(aqG$N=0 zO4hsrcd0AJ&!i^}TAd$4YqgCMq{-ok8t@qxn81s9eZy4Ww2wx_pofnc4a(L@s{)1S z#J2eyRR9wc!W>Tl1)psbnG}4pnxPAtho7ro2=P)GMMeUVFojg0thW;?LC?>On$?I| zvt((3Wu@~NLI3~^@VfC(OzFlfz>f>o^%WBrDV}zo=sx%$(e)wwXxd;& zom6$0B{Yua6Lb-#KK6F)#X3LHks2&(2GRB{#Z7bSROzpVvBuDTsaIMrUGkif_Te@h zN@+zHVJHc=TW97bSyJ!ENP%kb_nrkKVG~11Lz30b(-K{?37NvV!$mn?{#@1us7m8B z&J!$)J+bo99?Oq5PmG&M7d%xK6UQv&_!N69POaW~edwg5N+AWjJ78w6W^Qn#Ic|~r z_f`~gb_Qn%D_#CwBFJB>TgSkkJ0M}qjIpDv#vsKq4W_|kNmFPHeurPZ8{cnv-E|^x z-Ze1d__X~7GIg*|bqhKNqfISK{0Hu4{kYf&71zM_L@iAuwK(|-G=?g)UgKx!h4{Ss z4J1{I_|DVzesjxnBYi0}zZ6j43LXIGhRsfM&vyXL83(TW}0q^myMRaDcX9??Y>hO z5vKSHe6hWQeaqN5XOZugcUQQHfO8I?=xb59X3O&2%p+&}eamO9vriH-g$SY?m#~S? zFbS|0B3Y-P@zL@}?asp6OWHKHwMzS#P;ev%}~kIfO>WxJWxSyBmW1jh&t;Seb0 n_JSRT#*{%!hxFfy`G?JY$H$#x-3$X*U}DG8?qv&h7?l44m|K7^ literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Yuyuko-HoxNorf-Touhou.pal b/graphics/portraits/custom/Yuyuko-HoxNorf-Touhou.pal new file mode 100644 index 00000000..ee1caaec --- /dev/null +++ b/graphics/portraits/custom/Yuyuko-HoxNorf-Touhou.pal @@ -0,0 +1 @@ +#>g^[VM.^43{3An4Ib{Ms \ No newline at end of file diff --git a/graphics/portraits/portraits.py b/graphics/portraits/portraits.py index 9c717d33..446335c8 100644 --- a/graphics/portraits/portraits.py +++ b/graphics/portraits/portraits.py @@ -137,7 +137,7 @@ 229: "Pacman Ghost-JamesWhite89-Pacman", 230: "Palom (Adult)-HoxNorf-FF4", 231: "Paul-HoxNorf-FF2", - 232: "Pirahna Plant-JamesWhite89-Mario", + 232: "Piranha Plant-JamesWhite89-Mario", 233: "Porom (Adult)-HoxNorf-FF4", 234: "Ricard-HoxNorf-FF2", 235: "Ryu-HoxNorf-SF", @@ -192,6 +192,28 @@ 284 : "Vargas-PocoLoco-FF6", 285 : "Vincent-Twinees-FF7", 286 : "Yoshi-Badass-Mario", + + 287 : "Alice-HoxNorf-Touhou", + 288 : "Alma-Unknown-FFT", + 289 : "Alphys-LoneRedMage-Undertale", + 290 : "Amy-HoxNorf-Sonic", + 291 : "Cloud-JamesWhite89-FF7", + 292 : "Fighter-CtrlxZ-FF1", + 293 : "Frisk-LoneRedMage-Undertale", + 294 : "Gryz-HoxNorf-PS4", + 295 : "Jessie-JamesWhite89-FF7", + 296 : "Lenna (White Mage)-JamesWhite89-FF5", + 297 : "Lenna-JamesWhite89-FF5", + 298 : "Orlandeau-Unknown-FFT", + 299 : "Porom (Devout)-HoxNorf-FF4", + 300 : "Raja-HoxNorf-PS4", + 301 : "Ramza-Unknown-FFT", + 302 : "Reimu (Blue)-HoxNorf-Touhou", + 303 : "Rune-HoxNorf-PS4", + 304 : "Sarisa-JamesWhite89-FF5", + 305 : "Toriel-LoneRedMage-Undertale", + 306 : "Yuyuko-HoxNorf-Touhou", + } def get_bin_path(id_): diff --git a/graphics/sprites/custom/Alice-HoxNorf-Touhou.bin b/graphics/sprites/custom/Alice-HoxNorf-Touhou.bin new file mode 100644 index 0000000000000000000000000000000000000000..77ed3544b10504e214c2b041f2333d6678dc2e21 GIT binary patch literal 5792 zcmdT|ZEW4-6+Zrm?KIA{)3k|ia+~XZs1(`~)6(S9rip)46h>(`rj-cs;dKZ?2*nGf zjUZh;jcw6{#D@^ewrb?(ey}N%pvxdoOPf$MWm>f%mQ7*!M;L(xcEzgdfqDd;@`7-5#1LlHD zxPwt7&8I~?>7?E2pgJV=3EkRmvu|~lvb2}>P?Jo;8o?CL)2-3rs4H9!-dXck{jPb7 z#%Y@7Y0Q@0ZKQ`C)Tmg7W$2PlI>|&502pLQGR`VVcUmJ+p;#yuAvdIetPmGP921xX zv_k$BW@p<<`BshmCG%D7*j!aUxm&Q>?aqx|TUsrnQEUVgb>v76%QeKhSibm>zGG?4 zqE(!9C&9!75bF^ma3}B^kz3XO<}J54ckiysw}q<(XNCNEZFFX(d@W>Uvt*JZ+q$jU zpf*;Y+NqgZ!M_Jp$7zgoTXy8f!B1KJIhvsX@V|iu%|TeNkGiSQ+#%(`NFUy;gU!}} zO_{D8J4~an-+Ox7?C@-fj^TO{n&gQ2S4dZ5{?(QIs|*DE59^}||3SjP7A61!JK;Z- zpQp>TLUV6(RlW)NW<`F97R=nt=uB0n`z24C~T{Cp{Kc?>S0lZG>K%YGo>Ng z77W2okCPT{vo6d}VI7`%uz87c;eo;}R~6v@d)58oZpuJ5{15)GYGMxu#07nLX~$Ae zaoHUKUNiPTnfn}k+bP4(?O3@qCAmpTNRxNsxEGa=hNoH6u7bh1T zMnB06z%QM^33fNJ_ZT9BRuH^qj2>x3#8e?g(c}Cv?heHK zK{AI_(RC93zPSGVom0kH@fq)w`BVRaME^ra$QP-LzM1I%ly^L6-j)}4W-ccBYmZVU zqzm+3RsUAjU(lsg|HOYdepd<1aOxl6WrcrGY~d0eC>$u1oH-u`1~(KGa5&^w_-%o2 zHBhpC2BxVe0sN9Q@16FhqbbjUM|%{71IQ|i99THORaj4JB+IDsn@F$Wm;5+Cr2Lj0 zuEKAKwW{dmy_M(&;D1@AT}jZ%(6%1fs^~~si7vVX9fgXZNQxv10*o%Q!%2ML%mXI} z=&Yh2I`BRpnJ@9XFgfFtjz(nMK6272&IVuS(;H#zHnq<~eiWS|U%GSkjq)9I%y`w9 zQ_0TJcC566cGAOi0=gGy+@1**X)1g+916&c=p4rm4$dg2q)ytabz8d|ha3MOo}!JK zDO#e1y-2e~ShIv<#-8)0!@DNF<9$q~i_F8mZ%yPpxVk}Y)U0i4x>DXM?@`)RB3)2m z>pZPR{*_~OSr+_)UR#4!8z-nBxSv5^R_F|ChS}>ox;=Z z8L&1(kNBs@j?X`wna1ooc(Fz!K`zWhb@qYFrq!3EAHn~h@kf&Vd;d4cSFs@h|1}q~ z7h0mHy+7$^;r|_^6vZMf4pGKNd4X)j5u4N>jE&5+%m3sYvB5$+TGZ@8U!R?l6Lw(qdr8Ga#(pJs$kb`SMSe^;$vnSWqDgFZw4dH(3HuU#o$T_HcwlHf@hL`c3I zSn*xls!lnn&IOZh`hT3gz<2G=4m;l=7=YEFG-X;d8NkC^$UJQMdYW|4O?dDpt};2- zlN;C^Yc6(~5+bBKGprxhZ_}>du-V)Nwhh!bo!Fam@N-Kdyu-Wl2wbfQ!-TlzUD*tI zhpvKAs8bnHKFCa|6?_T`mTuag%q6;7`ir`Ef1j5=mq>Ax8EKKQ_AA=IRZkiIpX>{ zsc)Z)MuVrl+UM;5)#pkaWuL$P2fn%i^*(WD<4*lbi}7*&zXO(qeS{dF+Y+5|XD4T! z^VYFBj^$}OfC`8!0xL}G|Fr&w&nFc`x&9tO29N7MH>v+B`^yAHI9&FkD*EjRV2n!l z!ug|!=V|xyTogaAbT1`h^!LhD{EWV*2LCe9zh+nQGy0wy{0V>kbk-gJ`XBFk?MNkm znMdg^x*zh{kK5=YG5ft6TK;h@;SL569H+md1UTgkb`N$BweMWpEUzL}yb9TC7a}c7 znt<|0yr>^uI=s|1H~X_0`yA>YogSrUX%1P;B%84RmHVep;t#R)sr)h7S^6?f?wdFe zT^G5GALSw( zlm3JK(hR+@T7Rebll~*E|5N*`3`8ZdfKEVvFsN%Kdywqsq_y56?3|Eq+TbMT^WtE>HZ`)I|#xj)X<__qgTKCQn^`?pW> zpDKdn3kG9?@JbF~H{l3Oa{xbz_@~U_X6h6ls?ih3p)|s~1~Gu|L7@p^GyYmp zIHuo^EHRD0bbl+Lo*u2?zo!1uhxP87`ituXgIdeod8*%~{cZC7=05R`^**z9+|MB;umPN$>zL)96 z%rDB{kL33cPQ>x=Ch|{;_cztbUsaLMhWLLTX@2;;-xm|zC45g1@j1Xk>BvJ-B>^|; zG>c~eJUp;%Iv37{@icIJFXS8AENVoyNK#Ye#k|>WHkqxs$0cc=9$SjM$S?ZELa9*N zyWDY6UXmB&^KkAGeLx@4#qaK|JNf2E*(@5&WV}ST;eHkY8jt+P9T)XweK{BjMC zqk{G6igg=ut-3+I;`M!pdi!WKKsTa(mT>Mxhue>LiYT?Ydk=ANe0qT4;u<4RgjH(2 zs!9^T#E`*wNkf*C)5-ElGF#r_R}nz!=+ literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/Alphys-LoneRedMage-Undertale.bin b/graphics/sprites/custom/Alphys-LoneRedMage-Undertale.bin new file mode 100644 index 0000000000000000000000000000000000000000..ed9b8a5a5b7b22f0be65f8beaaa7ae1a5c81ed81 GIT binary patch literal 5792 zcmchbQH+#V703Ux^L2It!^~i}ble?wMrk4sRc6@+rm)=|S}iYXv8Kj{TE@^c#t?RD z%d#M4=fQ`jArxP*Mok+N`_#}EqNt=!TBCg+l&DSCW+@|9N^nER7I9lAi@$Tf@0R|OfB*NKbI-Z`$|$E2ZNHkDlhjX7fwo55`D@Y^RHko+zdgtss_Qg; zPv{DNf7Zq1oNBsVgq!y1*=6;1sFFG}&x3;Kz^ua|U52bhJvZMi%35HDlA@axroW~u(EQ2!V@ z=QS18pGE)N8J*D=^bRW<*G@V#RoPHV=!-T#Z;Y7IgP@pWypzv)v$=-e(6le_>Oa8x zAB*ZAVS-Je{<72mz*7B}`{CPG{u&*v?<;p2_rW~yaxABp~R=-&*2HfwkK$Izc{rg*z<{Y=3c&E|7x7_nD= zae4Ht6oj$L7Tz(eC)t6MPTF*PZ~#1I?e(%DR!7la(#2l%$8>DH*6Uj^R{;GA zcw_0TfX7(c-tgPk^|Y?C#zi0hG3~6ZeMsp#_^bM#_-iv;4}V!58%6UvoVClJle<0_fKa(@OMm)>O$glT0Vc*zy5oPVA1uv{p+m%;&rZnR5xn5bLG%J;5K3( zUHg1mtI%aXe}{Jx@78+PvQLXV8?dlR&FT+&SeDE1&;fL5g<5J=<_4MjZPZ%A^&@yVe_Glfd#iXBer8_O8OC?pqPsQCvozq z51G$JBfvgwa&5DV_e0XB!G4o&%HBW}h^4UjD>{Pz%-}&50kGfaDz{dXiNiqv1gY_# zM|uZX44+|NZqci+>;D-Sy}JHy-ni8N_vi;p{J**)iv7Hv?ZM8WG51Sw4Z-gO7POI5 zs_L^3|7Fli-vvq9*~e8E0TLGyScq>QNdiEImcr2f#`7p#S^i<`Ilzp0{#qjDu{IcHl3vE!?BRFRjk zfDC(CAt#+6CKlYAkniDX1{<@}vB@ltv)51Xd>D=XFUE*zeO12#XV_KD6f;#-3YH|D zV(ibg7aKHrJc&m&Ry9`D(<;+EY5JOjE-f6|ZndlwwL(1P8W z`OPRX;%$lz#>AW>U|GPZ9>e}lYPd1nDAY66Of%8&8shZ7i&Z^Ibeo2`td0^#EIX*N z;w;+mntV6ECclNVNPaylLeDs_f!3FcwxY6Y#X1wp2^eFHe=bvJT-zMg8* zDg(&d_c7*kA$N#ECTiuQ88#%Zb$rk zOkW@AWk52t@kM8fSKgv`eo%-WH<^NIsKTQ4l zW>CNGWi4-6{hI64uU{f$>^c?HuVctBiTbV7sb9_g=g;c5?5sthpnj~y#{2RvWU)QM z!p=LUjDJ@@=5h3vJMx@7#6Jdm^ZK3Jkxacngj+{!_=HPR7l<{JkU6REA#e26ncyjM z@grJR{cG|Zd;17y{}?#_p~}FP{0^RTef^{&sclD#hlr~`gxzUyye@B>S3AgasoqdA zSsi#)Kho}~{cPg%bk}~q!t4E<`yKqpd9(u;`?2+f_{(UxfGD*8eooAdidU~?-)2o# z-=>)KV1aj{Y8ktzrRpmpB9w;`wJL!{_o#kTJwI0;OZ*{>lRA?AP#~X#6cEFy%=6ec%1j$q&9i+Kw-F@6T?D6_Yi* z`2TZ1cL$(m?0gRH=a$+1+*)<+$M&bch=hz_v2ftR=z72 z*A5ToH~+stH32UoU=CM1C1}sS@c}^0=_Dq(g;l;pR@u z4rZY<+E%o^_T^6VQnx@uPb)AcmmRvXkPUPvF-egre?NW|0P1@4?nKu4uHynO_(7jz!Pi^^zo?}leZU(nYIM%(omr+UJ zE~l)CtF)qoYvG*SF2#8`PFC;=o;Sp(6KKXg533Z^?^QksqKkh!)~zo;D*XQfr+e5Y literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/Amy-HoxNorf-Sonic.bin b/graphics/sprites/custom/Amy-HoxNorf-Sonic.bin new file mode 100644 index 0000000000000000000000000000000000000000..8021d6a984542174b102c8539b9fbd3eb74248e1 GIT binary patch literal 5792 zcmcIoe~cB?75?T2GXu=NH!Rd0al3EYw77o|_LUOcz_L3OYu8kSKS*f=WvnJm3&L0x zo?*K?p|vS7(v8&yYjwAYf3!){ZlR$>8#@})YSpUIG^mJ^2Ggxle8ml2aCzJB&b)bV zW}(?Xu=noUH*@csIp?1HopZi(_u@y`jTbS72q$pZ1S*Ot-GgoD!(CX5Dr&d_-!@up zE!cxs$^fcnbR1I{z)SKzyk0oe@|ON)VLVn$jM(}Pvzv>5yBna6@9(cg%ak$kmmrafzP59m_zw(ud%Uu)C3~}%Ja1|~38CAkU z_ExOK3Jj{^iR9TS9K)+>;PhcpyaTcQq4M>~4)9WESI!vAjrJ&Ge^-sDM8p1h`F%6w zOF8)ib)ALov3vy1uJQo-+v@hG(Xkx5liFX0eAetm>v-M%G&*iTq^9vv|#Kc8PezV86t*nl74PF&AjI4oAikpZQH79655?gxG2D0+gQ1S>HT&%1D~$cW6y zJY#NBD*B4zqWL|#T`VX?KI_E40TE3U<3rpU+!|~TUc@cIL{N2aQhU+Cof^dNxEdGj zxW?$zOZ>miTE!VYq+5`Qf2Y7nwc?zz2S!g?C+)3HCw;~Va1hQ9g<8UcW@!HgMP{|{ zo}vARe8SCWf0LTkUWYkDdnj4$BX^^d*WL-*3|lV}D07H5O(H(&zto-|ReaHJkNH&$ zyThIzlzB(M`becjK;C|(S?o7@k^Mfu`0CaUDYc$P!A2}Mu^!-u7skQ^ zi&|L1e*f?FpT#@7FW}++$Gi*L%4Ge!p;0@H{i5~j7p>n!8~wAG!$)l4IzGgHHu3pK zu1Jie_B$lI6u z8*!5x_RVW2@<&!XIg3Au0Y60sy+;!JsN}Wtkk<}@4nx-<#a)&NfBq4LjrOYJgdtZl zj57Zdf1!Y20C<5XzS1^C-`;`0rMLX{bz9lqxUVu|EyWuGGHHwK@F-yvNyqVfY*X)^ zzg6FBmgw7i@Ktp3S7kn-SvtOnck#>cG4IDwsXB=%`DgMs7>`>~z!~)DZN_4=4|@ym z;^*x{G_CEU`3G_Pg|H?B;S{>f08{a z#RInukwH22*B6qdlq*05;6BYSD;oMwdOHAKP=TFyf90 z`zkh3uKNXY#0}j4acjcvjjlR9;-8aSfXE7M>KPS$a>O%u4R7dtk_A#^df&+I#c9ym+;yV{I==&Rmx+;>Z+2 z7AKf;4QI{bAu zB*CvQ&2RVnE!Dk2!mlsIZ};hUf#xj&Nr)Iw_?3-x6BJ`Nzg#LN^eKezfl4jU%X8jc4aN)uA=k+&t59nR? zBBRKaIn+_}b;7`){FnXf4{eJc-Ps#W*qv5Ycsb|2Ld1EC@>BNSupfOw&e*S$@pp_` zH0&3FGTes!KAHOC`MCb*;4UT%Jgq-c{=G1+KPs^`GW7@fe=)8-`+h%*-+jcwp_YSRIaeTlv?hL- zq3_hB1V8J?uKfFmPyBdI{P^$=ctMJvZha57GQKCdS>!*{A$8&6MC-KLqtjT$_#WqW zFg~NKvQVhc=dFM7`N0R;Up{qS|JSMkrOy!fW(C8y%H4{H$;9_;^=-C(y%c_QM%U~K zhDd98V5O%S2np=hONftl;-Bx3D^jv$U9hmd~g4d$xM-vP8|WlQtD={SR$*B>zYV*a&}7~txCL$?~| zM}wm$?Ox;kMXC2c;nKe%JU3hXT}qT>DW!`q`s+sI+p0~)?c32{}t>C&NIF=^?x#sF9H4w|3}5; literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/Frisk-LoneRedMage-Undertale.bin b/graphics/sprites/custom/Frisk-LoneRedMage-Undertale.bin new file mode 100644 index 0000000000000000000000000000000000000000..4ec4b33c6ac54c6bd2e94fec43313ca6e0f6614f GIT binary patch literal 5792 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 literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/Gryz-HoxNorf-PS4.bin b/graphics/sprites/custom/Gryz-HoxNorf-PS4.bin new file mode 100644 index 0000000000000000000000000000000000000000..cad0c61c04dd0dc3d3523d866444131675191584 GIT binary patch literal 5792 zcmdT|Z*0`|8Gd{|?hG70uSLY+F4$A22yL{vA{MUWnw(U%>a>*g!=yzm1y^NVrUrTt zMnVILHL>msqtzx=l{U3#AGU8Rsw$MNFkFd|PHJg1RaL}Na)XUV6$>7u^x!VJJwN+= zv0GZIZeM1T5dZo4ec$Kz{(0UvhrHIIcV-HPp59<|8UQp+(Pd>7(#W7Sg{(5C_fHlo zy1TB@U*U5_(R4@aLkW|3ako)PPs<YP8?i4kK;IrS8y2*c>=KtKg4|~E-5AaI(Qtr_gAaOf}`DJms;7b2IY!ZDSDxdg&ruo z?aQ}e;|-oa_C@;geI2NQkS|gHJMeqd!txjV%SHJX?SJ_e@-v^34?0mYmPRV6r?vEY zfxox~B(a=7ZGefAIbj{OTx)0|PXKtBX3;{?#01<<<7>u)-H&yS67&nS5Utwsd@`57 zQX985Cm1cus7Mu8 z)j_pR|F7fgTxKV*lm2J-#k82lSEJf{@f+5@HLATX^bctdH_$$ZC%kQsY9E8IeF_Dt zdkHD0p}nv|N8X(1)-qH#NeePGSYq~|(|7=?;tKoA*UDG$D*lKY^uQXlGCz|or^=~Z zd?F4k;+MnI*sVtJ%Sm?y_+`2F9KN-Pp9@7`xKTCKG!9@J(OWJ2RVCDtlGqSi34R_| zh~PfFkGF~6U04SkB7UdM7lvYHOsBF@`$rJ9UvAhhcJCU`gH~wwvUXPM8_jKs+MnZP z$o}J{g;LmlrG!18^*6I$>_EhR`cF4ejra#`qJ*Y@fIG7{d$F)ksP*m2cz*|&f~ zQP&+zhVi}G_xtT0`z>b9DdIbaCz&64mUvp|gYIO7{1wzIz*iREPb&Be->7!| zc2qlzMe*UHbl%v&8ZaflBM*rE zQ_;du=#WeD5LcVJ+fzBj8YpUzsDX_CG3M?$obhX*s99d+D%Bqz<{Qb9Ob;m~F@W#! zTg(epQG@uqs=mBz0cF)ziSVrI(49XNs;DrQb!&x|Q2ts*?OTHU72N?(`7h}o*Qiv) zKeEo6E*jg-$Q@yHj_~8$ffQQu{I$muRb}nohkx;`Gk$8I_D|wHbmn7LYzyCDZkLis z$f=}~Y)ua{<9oX&V~JO^QX0mznOyJDp`(SFtY7~$&CJ>6^Nd8+zboJU$F|u=>x6V8 z?nw?OmHV^z>Gu`mtb#gP%;XyPneurYrf!#+%&)`CyIHP0h-X-dAJ~>0<+BdY%Z2xt z$+d{ft0j?p#c>^fp#Qv$pEKyZJDs?Q2l%JQK5m_Vqv-$d{r@KUHsz`l{N0}&-U;K(Y zKf;yM7*6jJD@MuA?%>*zfc*`6_B&n-S268A#}#-M&(Kk7{M;OFl#CLiYlG3A>Cp39 zPSkh9OpzUBxW6obYDHyqt*dOGoYS*PO4RpCJV$q!#jOJjJaN7*-gi)Q+H1Ejq$}|t{&|apcI)^L_G2UdHLuS+~7*^;})72HxaXU4HVMf7#jk34Yre z<+uGW`njdaZ(p<{{I+N8^Pl_t^UL%9F#rBL8CaNq6N#;Y{|Eeg9M47gH~rwoNss@Z zWQO+nH!P2TTLJ%`$Gwa{k->fbO;-{8J5g@%Z;kx9o0;41UOfKB{zbXXBP6Y8Q<$b} z`u<73d+G7`v!ZDa()-xGU@T|sQ-l6N=r657vwsl!rz$U%!us1oXyPmMr>kiv`~EcW z>%qyvchm2Q{H3vS4qz_^t>yR;T~(Q1b>PfBvzgg%xKD`yQrP31VGiG!n7|8vO~09( zmg{ueM*p%y+AFQc27KS^UrOI`Z!cG!I^ACMPpluu?45QGiOh#ch+1&M!>>;EC>R*U z?`8k=M7-#CPatjrzdDZ5sf1%u?nUySSedN5L}i5juk`-*(bu+3KJ6N=sDB!I*$Qn% zf6>4;q5ns#e{lBB8<}hAO4`GZMC&YLv@=%0jei@}Q2#Lv5`{?r@myuP(SP{-pKF5t zqsjjrvj4}ary=r($PeNa#DkYXU2mtI6_gkIkD7le{C8A&Y2f%^W$?B1F&}>=jDJ>H z(Dt3Vch;D_)7|0YuPx&Li(BCT?JtD?$6f`TqC`Pb_VphdtPLJZzZ&SjTSS4i6g%j@ONIVzv)!{h+=l}FH*+7)B%a5y=hJ^l zg#Aa_iHQF!b$@Av{m0&moA}SisT+UA_%-rm6 znZ=pQ4>$YQoA-}GgMhn<9KiL$gUQ_*6AUKVmC7Y%0DpqvJ*Na^!k=J>{B=$qmv#m5 z*M<&hx3r%Xra3pYn6qHQ z9)D*3^_a6@!ndnfow}0UN#-n=@LR(El(JGkrOnX#G2{hz4)U)zzsbn!fs`BkWXJ;L}Ih&SsOx%=nNj{^D3|H`{cNPY|YTsyPk5?3%P=$nj9sUp>W*S+Mx{qwCHTs6MjjmMY_o7bzZ8Fh75UHEe?mGao~ z0*fstWKvD?uEYMh{*(UMhSo1WpAplMjcPMG4RAUlss4N>n2uaQV?NQCj^L1?s!RmU z>5^y{31cu_68+6Sb~iu8&j^cma5_Q;-so<`9YK?W>6kZP5;ueC7;(s9CRZu>(=l(p S#9QXWMQ=J5o-chl{r>|bFh^FHT&kA|e0X_}G#TyDX%CB}^gAgOW^I*eva15_WDlNoz5 z_;K;b$X5LW9dI4nf&qOCMgV8AvBc;w`tc^-UGs)CB|!s!4P}cm+fF&S?t!+F za0rvX;)?8d;4XYOe|Y}XD)tTSw=1hL4_Fo1Uy29u^X%cB!#gVWXM3N<0I=iK=6AGr zl*iE?T2DiBunmtkn2~j0#o3qrm%a6vZ5J>h5U99-6QhYyxF}TZ_v8DV-{nEr6YUe_Lm(O^Q+E(4HvS0{;J=NQr-Ns96?+;w59Hx^OdeU^LH3e zgl}f3*UJhKn6k;4y71i*|H>}E^ra0=QZ~~+4w!t9$FnTfLdi16qN8ju7gF5tZD(G`)R&(kK zwTeEBel+*`1Kt{Ax3!wSJD+d2;28A&8q&`N;RK7-T)0|k#%_+Gry7uD;s#?^;=cn1 z9mUl%h<|{+LLzbXMq%M3+iU(K;W1OH^B-H0?)4)CPg=*=)$xS?1Ol5+Cg8P5Ii#g+ z>OJ({9H{Ie4D0Grc{Ujgu3cc~{ilQPS*Os!8?Mclda#zgGxT6>{+;TudXE;H|3WMi z*4G+4t+goW=kvQW!us=^e*jnK{0H)y7Ms6@`1}v*)GoJf{sWW^_oL@8R-k_VLVu@e zi8w#BI6r`=anU{NPPrwozprBtrz~C_O)LAqf(}|7 zU5Kv^9o&SXND`SQZY8`!+6ga&fB2r``JQ2C$iD(P?2$9@MlnYI@$c$YQGx1R6xXCk9v2@nnlC%X$)~#vFc_^ z3o`N+K1Hf=<>>p~@4PShrLt;(Dqx=A3h}BS|8MmxQh*xr2uT!wwFrpfuNDC-w03Q7 zdQtLrV>>2^4lSC2w2^2>5?hIGcSb}&(LG|6a8a}m@MZ+GbD(usp}$(cu8lumyk`EC zl;@N`�)cLVx!utE=h@DvrabaJksgNg=$2AAT+^$)k7LY)7NSK*&1OjM)OCzHe2tVAc7tZV5oSE}eQ^+&1K z-@qI5$NZZ95Wf$s3<>YuoUZYgep?su5*sv z(x5bKY&V9ib=I-MX-C3BBn-^L&D`g=!5MNV-6?&$-kn^YoRu0>U2KG@;~0)}pTB@z zZ#k!(N$gb)X$Knn8)$AbHc5p9H6XD?P5}GV%jzKZdWZb|Cp$-J{wy}R+<%cWn{Bg# zCW(2;_fkL6)>ZK%X{ZmV^Hop>?!d^RSB)w2@812M=wVUcxxHSG)7TGc#wE-<`DWL< za}IPJ=n6+nnueWd+i@#*@rB9~?W@{Pwf9m>l36yAI_hGMY^X+3eESXWAGqR{{MUwp z+_1*{Y`@(KSMeF@Ig@DqFy}Y(4$o7&N|+u@1tqlYFn2jAX1T&}Davn|Vb|xk%y12* zmfwo3JeJ@3RCceD-#(>p)n8Z>Yj^43YPw=X^Yc=~%L&-CdsAm6Q{(sOL_eA~|<=-Oz7y3QQFcQH;{0-MNeoDJ(?h@Q+EM`h1 zClb0&j$KC`h33N=-E;DJfA_>ze4szYEzdSh!=MYeRg#}K8ttrl9;xAH^&kjZ!T*6i zoBq96!w>j^pcVWY`qlBK1KNJ=ZE2ER4fFSY5v6KjnuAR~I-Sam>^=a+^qs-xlj1<^P`ye?|Rv zTc!s$6a$B^)0JLHkkt%JzX=Dvet8%K5WmsP}&{2vF~T;ewd| zjsAuMjfb?o$|O!ZZ#f}*QURN=nf!TX|H(uCUT+YW)qUg-`P)o&OSvhwpPh7$ z74Eg3sPX4Ms%j@bTj9@Tbyn)7>q;+FvlF1oA z?{&@v`5<{NId;A%+Fge%2n>CVg zD`EZGgJxOOe|B<}VUk@&Lgb-n9{F(n=f~?mdOwkAoRwaYTh!PQo*yaHU)tarH3A8G z-_I0SZxwfD&gf@Ei|-b=#b$ing5iC}xPj+KvHeX3&yOnmTO0ed=^glX`TWN4SoeB{ zmn@&(#IxV4^2gNI%GG(bz76YhR_9@!jpo|<)f@WT(srT#Vf^b05~f5%zNg8g;w>axxrGQ{DIMtbJl78|B*+LmpY8MEDJGNRZJ@rj~TluFN` z!Tda!OO!9S2lwLNwHM8L4k;srU$WjYqs=9$8jRFab7`D6Hb4d*lBbfiTRu^FB& v30qW|`qPnu7mUVFM=&Z*M{1`_M8wmP+UXL5(T>+Qw(yxN29K`)HU9qq1akR< literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/Rune-HoxNorf-PS4.bin b/graphics/sprites/custom/Rune-HoxNorf-PS4.bin new file mode 100644 index 0000000000000000000000000000000000000000..150383afdae82f251197ef2094e043842e8b7a0b GIT binary patch literal 5792 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 literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/Toriel-LoneRedMage-Undertale.bin b/graphics/sprites/custom/Toriel-LoneRedMage-Undertale.bin new file mode 100644 index 0000000000000000000000000000000000000000..838e4e48cff6776c0855987c2ad14421d076e0fe GIT binary patch literal 5792 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 fHFk3>PKbX literal 0 HcmV?d00001 diff --git a/graphics/sprites/sprites.py b/graphics/sprites/sprites.py index 76127a07..b268a908 100644 --- a/graphics/sprites/sprites.py +++ b/graphics/sprites/sprites.py @@ -193,7 +193,7 @@ 233 : "Pacman Ghost-HoxNorf-Pacman", 234 : "Palom (Adult)-HoxNorf-FF4", 235 : "Paul-HoxNorf-FF2", - 236 : "Pirahna Plant-JamesWhite89-Mario", + 236 : "Piranha Plant-JamesWhite89-Mario", 237 : "Porom (Adult)-HoxNorf-FF4", 238 : "Ramza-CtrlxZ-FFT", 239 : "Ricard-HoxNorf-FF2", @@ -253,6 +253,16 @@ # FFT 300 : "Alma-HoxNorf-FFT", 301 : "Orlandeau-ctrlxz-FFT", + + 302 : "Alice-HoxNorf-Touhou", + 303 : "Alphys-LoneRedMage-Undertale", + 304 : "Amy-HoxNorf-Sonic", + 305 : "Frisk-LoneRedMage-Undertale", + 306 : "Gryz-HoxNorf-PS4", + 307 : "Raja-HoxNorf-PS4", + 308 : "Rune-HoxNorf-PS4", + 309 : "Toriel-LoneRedMage-Undertale", + 310 : "Yuyuko-HoxNorf-Touhou", } def get_path(id_): From 010887cef43fe49efc31d5ecd3fd1eb9703491cb Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Tue, 4 Apr 2023 18:24:59 -0400 Subject: [PATCH 55/60] Change -loot to -ssd Changed the flag -loot to --shuffle-steals-drops . If random% = 0, the steals and drops table is just shuffled. if random% = 100, this recreates the original -loot. --- args/steal.py | 25 +++++++++++++++++-------- data/enemies.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/args/steal.py b/args/steal.py index 2aa558cb..bd3b5c81 100644 --- a/args/steal.py +++ b/args/steal.py @@ -10,10 +10,14 @@ def parse(parser): steal_chances.add_argument("-sca", "--steal-chances-always", action = "store_true", help = "Steal will always succeed if enemy has an item") - steal.add_argument("-loot", "--loot", action="store_true", - help="Randomize items stolen and dropped") + steal.add_argument("-ssd", "--shuffle-steals-drops", default = None, type = int, + metavar = "PERCENT", choices = range(101), + help="Shuffle items stolen and dropped with randomized percent") def process(args): + if args.shuffle_steals_drops is not None: + args.shuffle_steals_drops_random_percent = args.shuffle_steals_drops + args.shuffle_steals_drops = True pass def flags(args): @@ -23,22 +27,27 @@ def flags(args): flags += " -sch" if args.steal_chances_always: flags += " -sca" - if args.loot: - flags += " -loot" + if args.shuffle_steals_drops: + flags += f" -ssd {args.shuffle_steals_drops_random_percent}" return flags def options(args): + result = [] + steal_chances = "Original" if args.steal_chances_higher: steal_chances = "Higher" if args.steal_chances_always: steal_chances = "Always" - return [ - ("Chances", steal_chances), - ("Loot", args.loot) - ] + result.append(("Chances", steal_chances)) + + result.append(("Shuffle steals & drops", args.shuffle_steals_drops)) + if args.shuffle_steals_drops: + result.append(("Random Percent:", f"{args.shuffle_steals_drops_random_percent}%")) + + return result def menu(args): return (name(), options(args)) diff --git a/data/enemies.py b/data/enemies.py index d69bd7b0..66d12256 100644 --- a/data/enemies.py +++ b/data/enemies.py @@ -313,6 +313,33 @@ def randomize_loot(self): self.set_common_drop(enemy.id, self.items.get_random()) self.set_rare_drop(enemy.id, self.items.get_random()) + def shuffle_steals_drops_random(self): + import random + + # Assemble the list of steals and drops + steals_drops = [] + for enemy in self.enemies: + if len(enemy.name) > 0: + loot_list = [enemy.steal_common, enemy.steal_rare, enemy.drop_common, enemy.drop_rare] + steals_drops.extend(loot_list) + + # Randomize the requested number + random_percent = self.args.shuffle_steals_drops_random_percent / 100.0 + number_random = int(random_percent * len(steals_drops)) + which_random = [a for a in range(len(steals_drops))] + random.shuffle(which_random) + for id in range(number_random): + steals_drops[which_random[id]] = self.items.get_random() + + # Shuffle list & reassign to enemies + random.shuffle(steals_drops) + for enemy in self.enemies: + if len(enemy.name) > 0: + self.set_common_steal(enemy.id, steals_drops.pop(0)) + self.set_rare_steal(enemy.id, steals_drops.pop(0)) + self.set_common_drop(enemy.id, steals_drops.pop(0)) + self.set_rare_drop(enemy.id, steals_drops.pop(0)) + def pad_enemy_packs(self): from data.enemy_battle_groups import unused_event_battle_groups for pack in self.packs.packs: @@ -347,8 +374,8 @@ def mod(self, maps): if self.args.boss_normalize_distort_stats: self.boss_normalize_distort_stats() - if self.args.loot: - self.randomize_loot() + if self.args.shuffle_steals_drops: + self.shuffle_steals_drops_random() if self.args.chest_all_monsters: self.pad_enemy_packs() From 5cbb56c0a5e9de6ebc9462c184d9f4e98c5b3de9 Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Tue, 4 Apr 2023 18:32:56 -0400 Subject: [PATCH 56/60] menu bugfix Fix a problem with the menu description. --- args/chests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/args/chests.py b/args/chests.py index 1097849c..e01e22c1 100644 --- a/args/chests.py +++ b/args/chests.py @@ -69,8 +69,6 @@ def options(args): elif args.chest_all_monsters: result.append(("Boss Percent", f"{args.chest_all_monsters_boss_percent}%")) - result.append(("Monsters-In-A-Box Shuffled", args.chest_monsters_shuffle)) - return result def menu(args): From 136e9d92a11cd185b00e0e94da2c2878334d05e6 Mon Sep 17 00:00:00 2001 From: Hans Rinderknecht Date: Tue, 4 Apr 2023 21:48:50 -0400 Subject: [PATCH 57/60] Fix menu bug 2 --- args/chests.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/args/chests.py b/args/chests.py index e01e22c1..3711dc03 100644 --- a/args/chests.py +++ b/args/chests.py @@ -28,6 +28,7 @@ def process(args): if args.chest_all_monsters is not None: args.chest_all_monsters_boss_percent = args.chest_all_monsters args.chest_all_monsters = True + args.chest_mosters_shuffle = False # Chest_all_monsters supercedes chest_monsters_shuffle def flags(args): flags = "" @@ -69,6 +70,9 @@ def options(args): elif args.chest_all_monsters: result.append(("Boss Percent", f"{args.chest_all_monsters_boss_percent}%")) + if not args.chest_all_monsters: + result.append(("MIAB Shuffled", args.chest_monsters_shuffle)) + return result def menu(args): @@ -79,7 +83,7 @@ def menu(args): del entries[1] # delete random percent line else: entries[0] = (entries[0][1], "") - entries[1] = ("MIAB Shuffled", entries[1][1]) + return (name(), entries) def log(args): From 967c762440b3b6243cbd70e922b1af9085fb0afe Mon Sep 17 00:00:00 2001 From: HoxNorf <45671870+HoxNorf@users.noreply.github.com> Date: Thu, 6 Apr 2023 16:42:21 -0700 Subject: [PATCH 58/60] Sprite Additions and "Restorations" - New Sprites/Palettes (11): Arthur-JamesWhite89-GnG Cait Sith-HoxNorf-FF7 Donkey Kong-Badass-Mario Gilius-JamesWhite89-GoldenAxe Kain (Holy Dragoon)-CtrlxZ-FF4TAY Lugae-Astaroth-FF4 Nitori-HoxNorf-Touhou Sanae-HoxNorf-Touhou Sherlotta-HoxNorf_ScarabEnigma-FFCC Wren-HoxNorf-PS4 X-Badass-Megaman - New Portraits (30): Arthur-JamesWhite89-GnG Banon-Laurel_Gens-FF6PR Cait Sith-HoxNorf-FF7 Celes-Laurel_Gens-FF6PR Cyan-Laurel_Gens-FF6PR Donkey Kong-Badass-Mario Edgar-Laurel_Gens-FF6PR Gau-Laurel_Gens-FF6PR General Leo-Laurel_Gens-FF6PR Gilius-JamesWhite89-GoldenAxe Ghost-Laurel_Gens-FF6PR Gogo-Laurel_Gens-FF6PR Imp-Laurel_Gens-FF6PR Kain (Holy Dragoon)-Unknown-FF4TAY Locke-Laurel_Gens-FF6PR Lugae-HoxNorf-FF4 Mog-Laurel_Gens-FF6PR Nitori-HoxNorf-Touhou Relm-Laurel_Gens-FF6PR Sabin-Laurel_Gens-FF6PR Sanae-HoxNorf-Touhou Setzer-Laurel_Gens-FF6PR Shadow-Laurel_Gens-FF6PR Sherlotta-HoxNorf-FFCC Strago-Laurel_Gens-FF6PR Terra-Laurel_Gens-FF6PR Umaro-Laurel_Gens-FF6PR WedgeVicks-Laurel_Gens-FF6PR Wren-HoxNorf-PS4 X-Unknown-Megaman - "Restored" Sprites/Palettes (32): Antlion-Astaroth-FF4 Atma-Astaroth-FF6 Boy-Zozma-FF6 Cagnazzo-Astaroth-FF4 Celes (Amano)-Astaroth-FF6 Celes (Opera)-Astaroth-FF6 Clyde-PocoLoco-FF6 Cultist-PocoLoco-FF6 Dancer-PocoLoco-FF6 Dark Elf-Astaroth-FF4 Draco-PocoLoco-FF6 Elena-Astaroth-FF7 Figaro Guard-PocoLoco-FF6 Interceptor-JamesWhite89-FF6 Katarin-Zozma-FF6 Link-FEOK-LegendOfZelda LoneWolf-PocoLoco-FF6 Lucca-FEOK-CT Lufia-JamesWhite89-Lufia Maduin-PocoLoco-FF6 Mini-JamesWhite89-FF NarsheGuard-PocoLoco-FF6 Peach-Halkel-SMRPG Rubicante-Astaroth-FF4 Scholar-PocoLoco-FF6 Siegfried-PocoLoco-FF6 Squall (Uniform)-SApprentice-FF8 Squall-PocoLoco-FF8 Tifa-Astaroth-FF7 Ultros-PocoLoco-FF6 Vargas-PocoLoco-FF6 Vincent-FEOK-FF7 - "Restored" Portraits (3): Golbez_TAY-HoxNorf-FF4 Link-JamesWhite89-LegendOfZelda Vincent-Xeblon-FF7 * Removed Duplicate Entries in Python Files * Gave proper credit to the Lufia and Vincent portraits --- .../palettes/custom/Antlion-Astaroth-FF4.pal | Bin 32 -> 32 bytes .../custom/Arthur-JamesWhite89-GnG.pal | Bin 0 -> 32 bytes .../palettes/custom/Atma-Astaroth-FF6.pal | Bin 32 -> 32 bytes graphics/palettes/custom/Boy-Zozma-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Cagnazzo-Astaroth-FF4.pal | Bin 32 -> 32 bytes .../palettes/custom/Cait Sith-HoxNorf-FF7.pal | 1 + .../custom/Celes (Amano)-Astaroth-FF6.pal | Bin 32 -> 32 bytes .../custom/Celes (Opera)-Astaroth-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Clyde-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Cultist-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Dancer-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Dark Elf-Astaroth-FF4.pal | Bin 32 -> 32 bytes .../custom/Donkey Kong-Badass-Mario.pal | Bin 0 -> 32 bytes .../palettes/custom/Draco-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Elena-Astaroth-FF7.pal | Bin 32 -> 32 bytes .../custom/Figaro Guard-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../custom/Gilius-JamesWhite89-GoldenAxe.pal | Bin 0 -> 32 bytes .../custom/Interceptor-JamesWhite89-FF6.pal | Bin 32 -> 32 bytes .../Kain (Holy Dragoon)-CtrlxZ-FF4TAY.pal | Bin 0 -> 32 bytes .../palettes/custom/Katarin-Zozma-FF6.pal | Bin 32 -> 32 bytes .../custom/Link-FEOK-LegendOfZelda.pal | Bin 32 -> 32 bytes .../palettes/custom/LoneWolf-PocoLoco-FF6.pal | Bin 32 -> 32 bytes graphics/palettes/custom/Lucca-FEOK-CT.pal | Bin 32 -> 32 bytes .../custom/Lufia-JamesWhite89-Lufia.pal | Bin 32 -> 32 bytes .../palettes/custom/Lugae-Astaroth-FF4.pal | Bin 0 -> 32 bytes .../palettes/custom/Maduin-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Mini-JamesWhite89-FF.pal | Bin 32 -> 32 bytes .../custom/NarsheGuard-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Nitori-HoxNorf-Touhou.pal | Bin 0 -> 32 bytes .../palettes/custom/Peach-Halkel-SMRPG.pal | Bin 32 -> 32 bytes .../custom/Rubicante-Astaroth-FF4.pal | Bin 32 -> 32 bytes .../palettes/custom/Sanae-HoxNorf-Touhou.pal | Bin 0 -> 32 bytes .../palettes/custom/Scholar-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../Sherlotta-HoxNorf_ScarabEnigma-FFCC.pal | Bin 0 -> 32 bytes .../custom/Siegfried-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../Squall (Uniform)-SApprentice-FF8.pal | Bin 32 -> 32 bytes .../palettes/custom/Squall-PocoLoco-FF8.pal | Bin 32 -> 32 bytes .../palettes/custom/Tifa-Astaroth-FF7.pal | Bin 32 -> 32 bytes .../palettes/custom/Ultros-PocoLoco-FF6.pal | Bin 32 -> 32 bytes .../palettes/custom/Vargas-PocoLoco-FF6.pal | Bin 32 -> 32 bytes graphics/palettes/custom/Vincent-FEOK-FF7.pal | Bin 32 -> 32 bytes graphics/palettes/custom/Wren-HoxNorf-PS4.pal | Bin 0 -> 32 bytes graphics/palettes/custom/X-Badass-Megaman.pal | Bin 0 -> 32 bytes graphics/palettes/palettes.py | 13 ++++++- .../custom/Arthur-JamesWhite89-GnG.bin | Bin 0 -> 800 bytes .../custom/Arthur-JamesWhite89-GnG.pal | Bin 0 -> 32 bytes .../custom/Banon-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Banon-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Cait Sith-HoxNorf-FF7.bin | Bin 0 -> 800 bytes .../custom/Cait Sith-HoxNorf-FF7.pal | 2 + .../custom/Celes-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Celes-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Cyan-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Cyan-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes ...roth-FF4.pal => Dark Elf-Astaroth-FF4.pal} | Bin .../custom/Donkey Kong-Badass-Mario.bin | Bin 0 -> 800 bytes .../custom/Donkey Kong-Badass-Mario.pal | Bin 0 -> 32 bytes .../custom/Edgar-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Edgar-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Gau-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Gau-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/General Leo-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/General Leo-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Ghost-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Ghost-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Gilius-JamesWhite89-GoldenAxe.bin | Bin 0 -> 800 bytes .../custom/Gilius-JamesWhite89-GoldenAxe.pal | Bin 0 -> 32 bytes .../custom/Gogo-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Gogo-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Golbez_TAY-HoxNorf-FF4.bin | Bin 800 -> 800 bytes .../custom/Golbez_TAY-HoxNorf-FF4.pal | 2 +- .../custom/Imp-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Imp-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../Kain (Holy Dragoon)-Unknown-FF4TAY.bin | Bin 0 -> 800 bytes .../Kain (Holy Dragoon)-Unknown-FF4TAY.pal | Bin 0 -> 32 bytes .../Link-JamesWhite89-LegendOfZelda.bin | Bin 800 -> 800 bytes .../Link-JamesWhite89-LegendOfZelda.pal | Bin 32 -> 32 bytes .../custom/Locke-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Locke-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes ...Lufia.bin => Lufia-JamesWhite89-Lufia.bin} | Bin ...Lufia.pal => Lufia-JamesWhite89-Lufia.pal} | Bin .../portraits/custom/Lugae-HoxNorf-FF4.bin | Bin 0 -> 800 bytes .../portraits/custom/Lugae-HoxNorf-FF4.pal | Bin 0 -> 32 bytes .../custom/Mog-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Mog-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Nitori-HoxNorf-Touhou.bin | Bin 0 -> 800 bytes .../custom/Nitori-HoxNorf-Touhou.pal | 1 + .../custom/Relm-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Relm-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Sabin-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Sabin-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../portraits/custom/Sanae-HoxNorf-Touhou.bin | Bin 0 -> 800 bytes .../portraits/custom/Sanae-HoxNorf-Touhou.pal | 1 + .../custom/Setzer-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Setzer-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Shadow-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Shadow-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Sherlotta-HoxNorf-FFCC.bin | Bin 0 -> 800 bytes .../custom/Sherlotta-HoxNorf-FFCC.pal | Bin 0 -> 32 bytes .../custom/Strago-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Strago-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Terra-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Terra-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../custom/Umaro-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/Umaro-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes ...Twinees-FF7.bin => Vincent-Xeblon-FF7.bin} | Bin ...Twinees-FF7.pal => Vincent-Xeblon-FF7.pal} | Bin .../custom/WedgeVicks-Laurel_Gens-FF6PR.bin | Bin 0 -> 800 bytes .../custom/WedgeVicks-Laurel_Gens-FF6PR.pal | Bin 0 -> 32 bytes .../portraits/custom/Wren-HoxNorf-PS4.bin | Bin 0 -> 800 bytes .../portraits/custom/Wren-HoxNorf-PS4.pal | Bin 0 -> 32 bytes .../portraits/custom/X-Unknown-Megaman.bin | Bin 0 -> 800 bytes .../portraits/custom/X-Unknown-Megaman.pal | Bin 0 -> 32 bytes graphics/portraits/portraits.py | 35 ++++++++++++++++-- .../sprites/custom/Antlion-Astaroth-FF4.bin | Bin 5792 -> 5792 bytes .../custom/Arthur-JamesWhite89-GnG.bin | Bin 0 -> 5792 bytes graphics/sprites/custom/Atma-Astaroth-FF6.bin | Bin 5792 -> 5792 bytes graphics/sprites/custom/Boy-Zozma-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Cagnazzo-Astaroth-FF4.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Cait Sith-HoxNorf-FF7.bin | Bin 0 -> 5792 bytes .../custom/Celes (Amano)-Astaroth-FF6.bin | Bin 5792 -> 5792 bytes .../custom/Celes (Opera)-Astaroth-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Clyde-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Cultist-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Dancer-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Dark Elf-Astaroth-FF4.bin | Bin 5792 -> 5792 bytes .../custom/Donkey Kong-Badass-Mario.bin | Bin 0 -> 5792 bytes .../sprites/custom/Draco-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Elena-Astaroth-FF7.bin | Bin 5792 -> 5792 bytes .../custom/Figaro Guard-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../custom/Gilius-JamesWhite89-GoldenAxe.bin | Bin 0 -> 5792 bytes .../custom/Interceptor-JamesWhite89-FF6.bin | Bin 5792 -> 5792 bytes .../Kain (Holy Dragoon)-CtrlxZ-FF4TAY.bin | Bin 0 -> 5792 bytes graphics/sprites/custom/Katarin-Zozma-FF6.bin | Bin 5792 -> 5792 bytes .../custom/Link-FEOK-LegendOfZelda.bin | Bin 5792 -> 5792 bytes .../sprites/custom/LoneWolf-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes graphics/sprites/custom/Lucca-FEOK-CT.bin | Bin 5792 -> 5792 bytes .../custom/Lufia-JamesWhite89-Lufia.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Lugae-Astaroth-FF4.bin | Bin 0 -> 5792 bytes .../sprites/custom/Maduin-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Mini-JamesWhite89-FF.bin | Bin 5792 -> 5792 bytes .../custom/NarsheGuard-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Nitori-HoxNorf-Touhou.bin | Bin 0 -> 5792 bytes .../sprites/custom/Peach-Halkel-SMRPG.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Rubicante-Astaroth-FF4.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Sanae-HoxNorf-Touhou.bin | Bin 0 -> 5792 bytes .../sprites/custom/Scholar-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../Sherlotta-HoxNorf_ScarabEnigma-FFCC.bin | Bin 0 -> 5792 bytes .../sprites/custom/Siegfried-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../Squall (Uniform)-SApprentice-FF8.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Squall-PocoLoco-FF8.bin | Bin 5792 -> 5792 bytes graphics/sprites/custom/Tifa-Astaroth-FF7.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Ultros-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes .../sprites/custom/Vargas-PocoLoco-FF6.bin | Bin 5792 -> 5792 bytes graphics/sprites/custom/Vincent-FEOK-FF7.bin | Bin 5792 -> 5792 bytes graphics/sprites/custom/Wren-HoxNorf-PS4.bin | Bin 0 -> 5792 bytes graphics/sprites/custom/X-Badass-Megaman.bin | Bin 0 -> 5792 bytes graphics/sprites/sprites.py | 13 ++++++- 158 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 graphics/palettes/custom/Arthur-JamesWhite89-GnG.pal create mode 100644 graphics/palettes/custom/Cait Sith-HoxNorf-FF7.pal create mode 100644 graphics/palettes/custom/Donkey Kong-Badass-Mario.pal create mode 100644 graphics/palettes/custom/Gilius-JamesWhite89-GoldenAxe.pal create mode 100644 graphics/palettes/custom/Kain (Holy Dragoon)-CtrlxZ-FF4TAY.pal create mode 100644 graphics/palettes/custom/Lugae-Astaroth-FF4.pal create mode 100644 graphics/palettes/custom/Nitori-HoxNorf-Touhou.pal create mode 100644 graphics/palettes/custom/Sanae-HoxNorf-Touhou.pal create mode 100644 graphics/palettes/custom/Sherlotta-HoxNorf_ScarabEnigma-FFCC.pal create mode 100644 graphics/palettes/custom/Wren-HoxNorf-PS4.pal create mode 100644 graphics/palettes/custom/X-Badass-Megaman.pal create mode 100644 graphics/portraits/custom/Arthur-JamesWhite89-GnG.bin create mode 100644 graphics/portraits/custom/Arthur-JamesWhite89-GnG.pal create mode 100644 graphics/portraits/custom/Banon-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Banon-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Cait Sith-HoxNorf-FF7.bin create mode 100644 graphics/portraits/custom/Cait Sith-HoxNorf-FF7.pal create mode 100644 graphics/portraits/custom/Celes-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Celes-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Cyan-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Cyan-Laurel_Gens-FF6PR.pal rename graphics/portraits/custom/{DarkElf-Astaroth-FF4.pal => Dark Elf-Astaroth-FF4.pal} (100%) create mode 100644 graphics/portraits/custom/Donkey Kong-Badass-Mario.bin create mode 100644 graphics/portraits/custom/Donkey Kong-Badass-Mario.pal create mode 100644 graphics/portraits/custom/Edgar-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Edgar-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Gau-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Gau-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/General Leo-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/General Leo-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Ghost-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Ghost-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Gilius-JamesWhite89-GoldenAxe.bin create mode 100644 graphics/portraits/custom/Gilius-JamesWhite89-GoldenAxe.pal create mode 100644 graphics/portraits/custom/Gogo-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Gogo-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Imp-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Imp-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Kain (Holy Dragoon)-Unknown-FF4TAY.bin create mode 100644 graphics/portraits/custom/Kain (Holy Dragoon)-Unknown-FF4TAY.pal create mode 100644 graphics/portraits/custom/Locke-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Locke-Laurel_Gens-FF6PR.pal rename graphics/portraits/custom/{Lufia-VRNocturne-Lufia.bin => Lufia-JamesWhite89-Lufia.bin} (100%) rename graphics/portraits/custom/{Lufia-VRNocturne-Lufia.pal => Lufia-JamesWhite89-Lufia.pal} (100%) create mode 100644 graphics/portraits/custom/Lugae-HoxNorf-FF4.bin create mode 100644 graphics/portraits/custom/Lugae-HoxNorf-FF4.pal create mode 100644 graphics/portraits/custom/Mog-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Mog-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Nitori-HoxNorf-Touhou.bin create mode 100644 graphics/portraits/custom/Nitori-HoxNorf-Touhou.pal create mode 100644 graphics/portraits/custom/Relm-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Relm-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Sabin-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Sabin-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Sanae-HoxNorf-Touhou.bin create mode 100644 graphics/portraits/custom/Sanae-HoxNorf-Touhou.pal create mode 100644 graphics/portraits/custom/Setzer-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Setzer-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Shadow-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Shadow-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Sherlotta-HoxNorf-FFCC.bin create mode 100644 graphics/portraits/custom/Sherlotta-HoxNorf-FFCC.pal create mode 100644 graphics/portraits/custom/Strago-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Strago-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Terra-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Terra-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Umaro-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/Umaro-Laurel_Gens-FF6PR.pal rename graphics/portraits/custom/{Vincent-Twinees-FF7.bin => Vincent-Xeblon-FF7.bin} (100%) rename graphics/portraits/custom/{Vincent-Twinees-FF7.pal => Vincent-Xeblon-FF7.pal} (100%) create mode 100644 graphics/portraits/custom/WedgeVicks-Laurel_Gens-FF6PR.bin create mode 100644 graphics/portraits/custom/WedgeVicks-Laurel_Gens-FF6PR.pal create mode 100644 graphics/portraits/custom/Wren-HoxNorf-PS4.bin create mode 100644 graphics/portraits/custom/Wren-HoxNorf-PS4.pal create mode 100644 graphics/portraits/custom/X-Unknown-Megaman.bin create mode 100644 graphics/portraits/custom/X-Unknown-Megaman.pal create mode 100644 graphics/sprites/custom/Arthur-JamesWhite89-GnG.bin create mode 100644 graphics/sprites/custom/Cait Sith-HoxNorf-FF7.bin create mode 100644 graphics/sprites/custom/Donkey Kong-Badass-Mario.bin create mode 100644 graphics/sprites/custom/Gilius-JamesWhite89-GoldenAxe.bin create mode 100644 graphics/sprites/custom/Kain (Holy Dragoon)-CtrlxZ-FF4TAY.bin create mode 100644 graphics/sprites/custom/Lugae-Astaroth-FF4.bin create mode 100644 graphics/sprites/custom/Nitori-HoxNorf-Touhou.bin create mode 100644 graphics/sprites/custom/Sanae-HoxNorf-Touhou.bin create mode 100644 graphics/sprites/custom/Sherlotta-HoxNorf_ScarabEnigma-FFCC.bin create mode 100644 graphics/sprites/custom/Wren-HoxNorf-PS4.bin create mode 100644 graphics/sprites/custom/X-Badass-Megaman.bin diff --git a/graphics/palettes/custom/Antlion-Astaroth-FF4.pal b/graphics/palettes/custom/Antlion-Astaroth-FF4.pal index 61d084a5a01e05d7a0ceeba890e97d3487af16c2..987145bc0d1e7af6a622d8222b8a8ce7bf6102ff 100644 GIT binary patch literal 32 ocmZQzNap#UKY^#3@tcIa`Aw&v}d|5F_FVK&)(Rw{(t^N*Z=7^75~>~F*7g#0GN{s$^ZZW diff --git a/graphics/palettes/custom/Arthur-JamesWhite89-GnG.pal b/graphics/palettes/custom/Arthur-JamesWhite89-GnG.pal new file mode 100644 index 0000000000000000000000000000000000000000..672f1eebf4d11cffc72831c45ff621fec11ce7aa GIT binary patch literal 32 ocmZQzNand!epn(f`Kh73eWlh}(M;Y5zLy-wWlrn(c?D+!0JjATW&i*H literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Atma-Astaroth-FF6.pal b/graphics/palettes/custom/Atma-Astaroth-FF6.pal index 0dc881a72b0327ddb52fb14c3a3a627e5fe0a8af..9125cf6ae4fff559ea69f385d9572afe4dd8832f 100644 GIT binary patch literal 32 ecmZQzNapz;@2_}^sgTj$zEbO4t-2)x5&!^-#s*jb literal 32 kcmdPYP3G}eEM%z*aR2}S diff --git a/graphics/palettes/custom/Boy-Zozma-FF6.pal b/graphics/palettes/custom/Boy-Zozma-FF6.pal index e3bdbd971fc2373d1229690fafddb57fcbdf9610..6750032c28ea573f8f3a0d791baba63480cffc0c 100644 GIT binary patch literal 32 ncmZq3cH;P-zuoAko1db+b*0w1S~knyOu>xLWZZOvtfKq@)NKph literal 32 ncmZQzaN=OIJXgEj=zqSS;!n3qEqm)=#@|f;>lqlbm>C!VuT}~( diff --git a/graphics/palettes/custom/Cagnazzo-Astaroth-FF4.pal b/graphics/palettes/custom/Cagnazzo-Astaroth-FF4.pal index e72dc51721c1de31aada73c0375d65b7ddb54d0b..01c362a2165f8381f4b3c4134d731ad703329f38 100644 GIT binary patch literal 32 gcmZQzNap#UuO0EfUOc;=KUg-eZcQwQ0s|ZX0HCG^d;kCd literal 32 lcmZQzaN^*K$*b#$HP7czsOJur{Gb27zO9r22(p+N7yyy72%Z1{ diff --git a/graphics/palettes/custom/Cait Sith-HoxNorf-FF7.pal b/graphics/palettes/custom/Cait Sith-HoxNorf-FF7.pal new file mode 100644 index 00000000..a1bcfbf8 --- /dev/null +++ b/graphics/palettes/custom/Cait Sith-HoxNorf-FF7.pal @@ -0,0 +1 @@ +)-c oDl-f_^8ESQeX3 \ No newline at end of file diff --git a/graphics/palettes/custom/Celes (Amano)-Astaroth-FF6.pal b/graphics/palettes/custom/Celes (Amano)-Astaroth-FF6.pal index 68df3bd9680c38acce4c136df8c7f2cda83508b8..bac3c623286e83ae5a8303ffe19b9ae721ccd1b6 100644 GIT binary patch literal 32 ocmZQzNap#UpQiECZX>I_`%YOKc~hy2k*_SC$++nTSw;B+0JahfH2?qr literal 32 ocmZQzaN?NA_|xit{wa%@k{4s`-Q6^7rA?)({r}fzF*7g#0Jl#HumAu6 diff --git a/graphics/palettes/custom/Celes (Opera)-Astaroth-FF6.pal b/graphics/palettes/custom/Celes (Opera)-Astaroth-FF6.pal index f9f24b0792950d9c42d0370625403683fa30f62d..f90ad7aeaed0243c90e7adf99d086223202f2062 100644 GIT binary patch literal 32 ocmZQzNap#UKOyUemOrb!`%YOm-DfgU{y|pimgj1>8~slP0JT313IG5A literal 32 mcmZ?bNalGa^8cR>0JMV)6951J literal 32 ocmZQzaN^*SysWv^>c7=Z#SVu5`Fw&$40>GW8cvn?pZ>od0I=B%#Q*>R diff --git a/graphics/palettes/custom/Cultist-PocoLoco-FF6.pal b/graphics/palettes/custom/Cultist-PocoLoco-FF6.pal index 4d396e5483b993dec881401b7fbf6931265e494d..3f12ef570550dab49db54840caf8343b4793a038 100644 GIT binary patch literal 32 ocmZQzaN_u%zmsXBn~S8q`%KBH0(=aYHP0~|kg(UXtovUN0Hs0-^8f$< literal 32 ocmZQzaN^)&m@2T7>9VGa9VH1`%FoDt*HXS41u|pbq6H=*E28x0HdS|AOHXW literal 32 ocmZQzaN;<}VDDbe^gll^cc!Gh)>Hvuh656pH7)D@*E28x0GnnCAOHXW diff --git a/graphics/palettes/custom/Dark Elf-Astaroth-FF4.pal b/graphics/palettes/custom/Dark Elf-Astaroth-FF4.pal index 2924702ffaa42db45be65a4fe5eecb1b25f2aa1d..55e35a03762d144e8476aaacdd13315bef6a382c 100644 GIT binary patch literal 32 gcmZQzNap#UpXoTgcw&-3xQ>~J>MV=@=?riH0Hb0D^8f$< literal 32 kcmZRuNamSkp=0(x-$Qj`Ql{hdVuA4g=?q}7PG|}P0H?AEe*gdg diff --git a/graphics/palettes/custom/Donkey Kong-Badass-Mario.pal b/graphics/palettes/custom/Donkey Kong-Badass-Mario.pal new file mode 100644 index 0000000000000000000000000000000000000000..773d27000078841d303150e935bcc1ae42ef2229 GIT binary patch literal 32 ocmZRuNap#U-^nn=N=x>?-A~2Oy8NO&u1^f6O6)O>^8cR>0LA?b;Q#;t literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Draco-PocoLoco-FF6.pal b/graphics/palettes/custom/Draco-PocoLoco-FF6.pal index 0b5f004f4fff21d5c00507220af80dd66c71efaa..65d6a012db40602f822551d5a113cd107941f186 100644 GIT binary patch literal 32 ocmZQzaN_u%-{G>^k4@9w{kkEC;xUOp##6D!B#o8zWT#mH0HwSN6951J literal 32 ocmZQzaN_82Vbk2~_dows>~%wXcMip45`m1zB#o8-*E28x0I56*b^rhX diff --git a/graphics/palettes/custom/Elena-Astaroth-FF7.pal b/graphics/palettes/custom/Elena-Astaroth-FF7.pal index ee5fa18bda0f7c4022ead2b8acfd6c322f2e1a3c..3ec046c8f43776a5b939521f8b1d32e252470a22 100644 GIT binary patch literal 32 ocmZQzNap#UFV45mzfSwV-A~2!mI*d0wo7!>Ezi|%H~OCp0KSzBy8r+H literal 32 ocmZQzNahjetJB`+|5Ne5U4qU3{3SXnw(Bj=)iN+-F*7g#0I;wMeE&=14^_5Up{x(3Szrml|I$u!>>0NacWh5!Hn literal 32 ncmZQzaN^)l3}!qg!KQiB@PGY&C!VlF$h% diff --git a/graphics/palettes/custom/Kain (Holy Dragoon)-CtrlxZ-FF4TAY.pal b/graphics/palettes/custom/Kain (Holy Dragoon)-CtrlxZ-FF4TAY.pal new file mode 100644 index 0000000000000000000000000000000000000000..8b4ede7611e8e69dd7af6ba3f1cbb96f73669039 GIT binary patch literal 32 icmZQzNap#UUs4{blq)FjzEjpG%gu96abOSw6aWC7O9$}) literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Katarin-Zozma-FF6.pal b/graphics/palettes/custom/Katarin-Zozma-FF6.pal index d6c6d73ba9b74b52c38abe07e9e8eeb8aed800e4..4ecb45f9680406d29faa209ec8c2af4989d46884 100644 GIT binary patch literal 32 ocmdnbkj(Qxzms8#m6q&(yPt}mb@@emT%Qb%7 diff --git a/graphics/palettes/custom/LoneWolf-PocoLoco-FF6.pal b/graphics/palettes/custom/LoneWolf-PocoLoco-FF6.pal index b4a311a129c29d052512cb9ed72f36f7051c3170..9be610875d2621a148224453cb2698893a6ddb86 100644 GIT binary patch literal 32 ocmZ?Za^m=(e@tS#6`!QN`%KAV1Z{iY$C<}rz2##6D!B!kuUWT#mH0IfI*fdBvi literal 32 ocmZQzaN_tJ#-aE>|E6Iuqpf>~%P|Qy&0~_q%BN!g*E28x0IX6A00000 diff --git a/graphics/palettes/custom/Mini-JamesWhite89-FF.pal b/graphics/palettes/custom/Mini-JamesWhite89-FF.pal index 43b974f4c47fd564366799fddf05d9dca3a98baf..6e79ea7d1b4b6a21e2d46d71c6ca7103175e42d1 100644 GIT binary patch literal 32 ocmZRuNap#U-^pN^9_01k?x*5p$u~SbhD${{T=$qp`TtJ`0LhyT82|tP literal 32 ncmZQzaN;<_Flqlbm>C!VxZ(=d diff --git a/graphics/palettes/custom/NarsheGuard-PocoLoco-FF6.pal b/graphics/palettes/custom/NarsheGuard-PocoLoco-FF6.pal index d4dfdd3ec094f20fd050f3161edc31a98ea5fc80..fb199dd87a0d28e587954d6dbb1472a55d0ad86e 100644 GIT binary patch literal 32 ocmZQzaN_u%U+h<%|2^)%)lJ2&R5sVShEpY`O6)O>^8cR>0MWw@P5=M^ literal 32 ocmZQzaN;QTtIq!(cT@3yKAUS->RiMBR#PST1plZ1uV-KY0LUE-qyPW_ diff --git a/graphics/palettes/custom/Nitori-HoxNorf-Touhou.pal b/graphics/palettes/custom/Nitori-HoxNorf-Touhou.pal new file mode 100644 index 0000000000000000000000000000000000000000..6245e0d1dfb31e95aba445c3f7ed2c4999c2e65c GIT binary patch literal 32 ocmZRuNap#Uf6!z?*|OaD;6Ji##*bwK>Jy5d$++nTSw;B+0Mu6ua{vGU literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Peach-Halkel-SMRPG.pal b/graphics/palettes/custom/Peach-Halkel-SMRPG.pal index 5e094f3af0090cfd13607c3db0a6489b998d4126..923a1bf3daf208c62891fdc2c95e431f03ce75b2 100644 GIT binary patch literal 32 ocmZQzNap#UzbazB@lGar_norwasMs;*B_90CgY|XWEJHP0L&*0od5s; literal 32 mcmZQzNai^pu`1%f#ZK9sO!0B@?(>cR=l`!~0D>%L1_l7Ta0*}m diff --git a/graphics/palettes/custom/Rubicante-Astaroth-FF4.pal b/graphics/palettes/custom/Rubicante-Astaroth-FF4.pal index 6ef0db1878f0a685661287197ffb9a0236ce6134..1e7e49fbfd0aaf3f8306d606ca0aab5a5d6e7eb3 100644 GIT binary patch literal 32 gcmZQzNap#UAHgB7D9Z5P?x*5^>8Vx@EDUe}0Fo957XSbN literal 32 mcmZRuNap!3Ew3ob@IOC-i%HTVvcuUwZ$zXLo*=be)nPv&? literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Scholar-PocoLoco-FF6.pal b/graphics/palettes/custom/Scholar-PocoLoco-FF6.pal index 94487d2b332ae5e3ceb55b45c6847b1c70b8bb80..f1d66dd3471bc69c388c61107889f53badf5eab5 100644 GIT binary patch literal 32 ocmZQzaN_u%-@&leic9jp)lJ3AntX!)(~lTTmDpn%<^Mk&0J~ibr~m)} literal 32 ocmZQzaN^*S+-mhd|EA)9tN-bjHTeWP7>*dsHJmE(zn*~s0KH@j^Z)<= diff --git a/graphics/palettes/custom/Sherlotta-HoxNorf_ScarabEnigma-FFCC.pal b/graphics/palettes/custom/Sherlotta-HoxNorf_ScarabEnigma-FFCC.pal new file mode 100644 index 0000000000000000000000000000000000000000..dc95323979ca487fba918d599f141ac9599c6c1a GIT binary patch literal 32 ncmZRuaOU}+-xeC1J~J#nSWc_QQB${GYcA(8$zXLo*=be)v78EI literal 0 HcmV?d00001 diff --git a/graphics/palettes/custom/Siegfried-PocoLoco-FF6.pal b/graphics/palettes/custom/Siegfried-PocoLoco-FF6.pal index e2a8d88f3cf3fd0d8aad67b833774bfc04c0415b..1d5956a67b3ace7355167d608a49294b73403bce 100644 GIT binary patch literal 32 ncmZQzaN_u%U#!fg*&<-?e$((&Y=_HsD?Z6%lELbFveT>pqL2z9 literal 32 ocmZQzaN=kYVADJmyWNUU@}}Yce0%p|BW9iCFUAN`TtJ`0J^#iFaQ7m literal 32 ocmZQzaN;<_(ZTTFYO>@_#sB#&0>yqkhL-7b4W~-{Pyb&J0J;F*-8>0HdJ@pa1{> literal 32 mcmZQzaN_6+X>bWlEl%6XRBs$C`9J@EeRl~15M(hkFaQ9l3JI+M diff --git a/graphics/palettes/custom/Vargas-PocoLoco-FF6.pal b/graphics/palettes/custom/Vargas-PocoLoco-FF6.pal index 6e4c0bd18626b7a568d6c520b310eafbeb7e2abd..3097b9e9fc21646233801df8862bc89e247f24da 100644 GIT binary patch literal 32 ncmZQzaN_u%AI!+6*&=Y$kWcbdY=?`z`*y2ilELbFveT>pnK%jG literal 32 ncmZQzaN=kYVAI@gbt;xm@}{A^dxy*a{9r~7#sBqL%nS?wl$;5F diff --git a/graphics/palettes/custom/Vincent-FEOK-FF7.pal b/graphics/palettes/custom/Vincent-FEOK-FF7.pal index ab402ee85fc7d30b7883b0a689210791842482cb..a69c71f89d1ef27dc05017062674dbe1092792e2 100644 GIT binary patch literal 32 ocmZQzaN_t|e~Q6J^O%Ia`%T5091}VIYWXo9lMGhZlbvP-0J821$N&HU literal 32 ocmZQzaN;;7;iLJt{wBvnj+=`1?tiuX7*8?y#q`AfuV-KY0JEtJWdHyG diff --git a/graphics/palettes/custom/Wren-HoxNorf-PS4.pal b/graphics/palettes/custom/Wren-HoxNorf-PS4.pal new file mode 100644 index 0000000000000000000000000000000000000000..f25d6b7c41be7396c84e523abb1a732a06e5999c GIT binary patch literal 32 ocmZRuNap#Uzf>e!_qog*qbY(C3_T1}f_e;(Nd~Ly$xgEZ0JN{)EM^@ZYv;X}d?feHpDpgLrL%46`X;QqlN&o0I(%`MI@&o6Gcz4$v512-Ed zJ2x{sJ3sql<_3B8AKb~i#sA;`U;lsKgFg)qq*-pR|D6xvYyS?t@AiB_{nGN)pZ`dF zk!_g5pv553z&Jr_#+;UPj#tQ56Txo4P}9KB&~TvPLBj)v1cnC;AE5FM3=RwjfcSyK zhXWsga{me&8tm=s6DBAuU{G*yXlN*`Z#clvz#xA{p+CX?gZT&RAG<5&)Xb~rvG=d< zKi_`7`8cz?L$X5xBNGEF11l3d5b`r}vxqY?0z-@$8fGjE987kA(5)bo zAQ0kkw9sc@@Tf1T@BPE_hv7d%J;Q%9JBAj94#r{z=875ydkG1SlMTkL#U1GxY-$G> z{xkmL=WjUE($g^`<0qTE!UH=yh7U}Kg+BNFXOI_R)a#oX*m{v+4?_+x?K}|qr%=eE z$6^N!FBpYkKfeGw2QvpSx;dFE*!HwmvU4!7v$8RR$~4h-onV%Q^DWov*LCgZVxsb`=9H}N9;pzw=tCtg!J5g85d z8rkoPqu!xlT_FG2<5i*XvhLvUPuVIBNDVzs!Lit`%1WO?h#>6U8*9G*JpPZnK9owR z2t*?a87Do&@b+r2i`906!MriNRMQR0Nx%kZ_!(gbqF_8-Hvl=^|0f#UCmQokW}bni zcG`FDs+%WmCR>&BbRfwy*#o)*jY#DEjLsBR7-$@AJnbzNk}AE4LV0IOXS{djn5~!X zP3R$BMvGGDH6ofqQiOQH%ve~9C)#t;Q}rq{E3aT}(IP&(%$ngWbL`C8opUIH&7I&Y zxoW9KQ2ha1?1k90$n zMFf&QhbkH!AByT~GhAV=+pbb{6Q&~yChkPTpxEEU+naxj!z=#T_1JKXZkgmy|ISKZ zktJs0llzaV{!snc<6dQ7@KJu4RXvhJ$&)b>}y}mxmlO|5oxytW+=~a5fORl+fUohjdXJHY#vTpU|5rI}4p} L7M-Dn_UHZwj1R}k literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Banon-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Banon-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..f6f60ebde4adda56be29531f9916124205a7b3c8 GIT binary patch literal 32 ocmZQzc&u8?!6n$oyGc<*Y^R!q)HlsYquW-$rDV)){pY0v0F{6WAOHXW literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Cait Sith-HoxNorf-FF7.bin b/graphics/portraits/custom/Cait Sith-HoxNorf-FF7.bin new file mode 100644 index 0000000000000000000000000000000000000000..e5802efcbbc0116e5f9c6e3c942df0dd8d66fd2b GIT binary patch literal 800 zcmZuvO-NKx6h8Of=euv5pXVPo;`}xCgAwRxKNF^Jf(f!97cR0@W3-ScnlNbynR%wD zh@?Ub!JvVltpqLFNK+#vLZP@&+W3Ybi^|^MG!E07cbgvr7kwA*<>PSA`ObF^Kmi;l zlyh8JS)IK|f#)qI!XZld5(hxNSHrELwI6#N+4@FZi$Z$osP|MTd{FDwx>Ix7T*^qD zv};N%fQpn-@!$XdzB0)6W6ML)fX~PIt7~U(!0l&-Bu*L9NS{a_T4b^#Sk4&;7yu0$NC#8(9J5O| zXvpHRyB!S(P+rOgsF`5$?oKqZUstp1LU2EK*|>}T!M+DgEv-(YGkr28t{=hvn-n!+ zPxhRt+yvQysmSE+U}WOAP9oZiUaM#s8WBb^rZ7SvKY70t{JgoiFq)$bGbkRS@NXVc z4&#E10vKT)Wt{q?QXBvymlHA&D@LK69oKIql|L0*^1QMM%_Jl~2)xp4c9nbjFxd+UQwd{h0o$U4NyhpuiT)!)ZY?W=hS)Srrqd zI3<+id(T4w6^iBZQgZd+_?HpZ_Lu*`iiKJmLJCYZ>)>U1 zAHi#G@9w;}*2~AIyOg{O@$-+mCZ(AUw-!^Cf`+0qz)Tr;VpjA;_GE!TT@FF@$$tT6 CBmJ-d literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Cait Sith-HoxNorf-FF7.pal b/graphics/portraits/custom/Cait Sith-HoxNorf-FF7.pal new file mode 100644 index 00000000..14ee0a59 --- /dev/null +++ b/graphics/portraits/custom/Cait Sith-HoxNorf-FF7.pal @@ -0,0 +1,2 @@ +|d= +%-wZ[kG16ktNI^: \ No newline at end of file diff --git a/graphics/portraits/custom/Celes-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Celes-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..45ca46d4a61a912bec73ceea5e4a19c9067a8f21 GIT binary patch literal 800 zcmYjOeMl2=7=G_|yVJStW}|JP^R5z%2qpy;j=BZ*K@pWr6BG2>*qk&;PG8SZsFOOhO5O7F;j){13f@TFy z!n4pYAA{~l{M`#!zwjdLBJTpUtFh$AukndruRq5h4J2o-!wf@Ey8|8^6T?$OijuiYz;YOW}Ym`9>3GUSSe|b|+zk|A92;&rv z;*l?@gf>2gC$+Q=w35L(k$2K8;Iv7VNC|Z7Zy0cL*kKQGCy} zhNi-E9nHm&I=KTU6p8{P(0L|s*&HS_O_2tJfi%#Jm8C7T5s2o_H6ulFPt+rpo!Z#h zTUA~C$o;7e->8aIMS7Vqll+Y{`gBnLpv@&&t>yN-%`JOX?_p;;{2|=^=3!KfOC68j pzaN=?W08uL=?CeqW{8m6ENWC_jcC@EOLtH(9||+-B?QMLgkE+Z3M_SJR8XNb zL&A-sHu?d*Lsvn;6((N3(ev&=BJJ1uBEbw159u9{xfB!?In{-C+d?Cdp?YVV!Kt9hkW2B0z#9uG~?CC!gD5+qw#VEujNw zk`6|(0p_H6-mcl!^A!?G1@P_U3pf_B$b)zuA(jPTskzH9vma(=q6xA7Rny>0uG=>_ z;bB4#rqiFOb&mWboz-PY?)$@yS?%1Qk6HU_DTG)gOI3&RG+C(=cZ<A1X+y9#18^lPy*GZWZE?q^yFgNEaI~eLJ>N`PlQETit~~ zgo|nz{NR^l+KhepB;KoBP+FDNP{TmaziuilFRrCC**@!(RhJn>2&sOpSofM9)3fOv zGXfegE+Q@EAo7ABZsk+1U>QqT_BlSMZ#+)8eT+G3zq9G$bZ?-utwV-#F+35k7z)BI zPu82NSg4tZ2Zsk(3uX9qAuX)p<}-)+qQXMzT*&X>i&&Oc2=SD87&Km;Xr$hrH4bwJF+qNS%j@Vx3ExF>9kb4e8N{(L6@6Uis zIjov)&cHGp$Iu(xZMS~tPnCG1;o9`(O_TIz+dsC>@`PYsU=hRQa+prS^zMLOW7?vgmBlQQRiNro0y{ly1kfmmSAqsb^uTu;aeSqn>sZ1fmY)1^X*P%hM- XINs0|zSbKuQl`uI_PjH0jaa_{hZ{I! literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Cyan-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Cyan-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..2c936abf6609cfa1bf23550f90b197eff78c4c0f GIT binary patch literal 32 ocmZQzSSoT%dlz03nnMbt=e2wbqdPORpPs$E#EfEi%oc7H z7<}-dy;bW3Fg1N#k&z!wTQl&5UY6-@yN2ks%kFI%W?_!8ZJ#kR8}QAI9ue>%39%t6sgiE+`0`$5%Bty8T$9Jnhvv z>brt4mJ3|K!a}&$(iTrkPy1HBJ)WFIjKx?iaNvcA<8gK-mp;TF6i-QG@sU_Sx14}2 zFlh!VtzXCS%Ytf{A;{pfqK8F_?J7d&E9rg&B@rwO)c}?=x(+-b)^Y7*pwP~Fyu!Y^ z?zXJ9f4IebHqY~-C`lNv|6>8}^>+MhD%~oc(em0I?bD?tn!W;)@XAV&6qzFTbMKAI zh5=W<;#5Am5S5_?j!?bY$8k}L)bn?92bSi4kS9H4{KSKJmQ<4pI@g}u6AA>+$5+T! z(wi$Z)b8kP$n@8*nT|8Nn?Pu#;t4|1up7Ey7r0@-d3_qO{+)h5!yuWajh0dUY*s5K w%AqyspF4z+g!q8K34*73+mO3Eqv;@S(wz)wDj4$v$$o3F&rNS`)>+Da14HhYDF6Tf literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Donkey Kong-Badass-Mario.pal b/graphics/portraits/custom/Donkey Kong-Badass-Mario.pal new file mode 100644 index 0000000000000000000000000000000000000000..b188e784ab9abec7b777d2a66a127d7e0843a374 GIT binary patch literal 32 ncmd-p`d>e%*fO2Bgn@zEB*Dl&_?}yh*>%+`niCay1v?o4y8;Tj literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Edgar-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Edgar-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..dbfa22ad1942d07223aefc4aae015b1e50379bff GIT binary patch literal 800 zcma))K}Zx)7{|Xiv%6#KeZbT4BIVrIgqTUn~9nst{VW!-2bf5Ejvv$~H|KH*9-uLl+@Bh6A@Sj3R z5`#)pNj5)GBmlosS*hr)IVKju?l^#0Oo^(Lt+E&cAj9&!Wl@!`O}A~l0vK+Yjdq1{ z&S6U2b9of-2aq3$ND=~pV0pCw9ulu2AmJd24E!1SQTt49-P`L)74`<6-&8u($9fY( zu0Wq8!{+S5;L!KNncn)%^O?+S{d+a1=5SW;+pe+{OUY(^zXBWZ6By_m4464vtW^7c zhyl?OXHc>w{M_J}JSV9o`Gyc;8irw;ovN&mi#j3dI5sWn1SKYreTZh5M#(jJYa&M?>2UL%)U9j3zqza@bsFlz zFE5S9+CM%`Kk5$V;{6|f7-(ZwTQ8_&KIspLoway`I< z74iBZKwP3I2>dvaJu$J86@1YTD)oKSB|Of|IYUGo<+ zZzsPvJLZbX9x`QnD2Hbx=Am8w*M}hnwm5-HhHo>ARv!pfNyj4%r&{g)x`p}>iZqsm zeGm!Vtua5v{7u%DJ~fok**d#mD@HG|`KSOM=!XI$+@cOdqq4|r!adIr^y;`=L?E+t zv27oKid3jDvWeBi6(eCMd8*5pJ~kF-oc=bqZM5@4L%g{snEd~fDZ6@O$C&((jc60IW(3o&W#< literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Gau-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Gau-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..5ee31b170c6cd33a35710040f431c4f309748047 GIT binary patch literal 800 zcmZQzfB{AZCI&_b2TU=t!MF?z4Ggb1oHlfXvT=eHa5VU4ux{eLs>S(DjUkU=qDl_K zUWZ$X7f$I~HlfZBQD43yep&`%e zs5FZr!}-Ik417%T5()|o4Ezj?a)%8VnV1-57=T=cy2kAu@e0>9l0*)jSh%3cp`reO zeZu@-tR>y2Oc%yE*-AdEZ#ZjI(_nZ=%`uNPS%~?M&|C8pYl1)Xq|8p3@3a4a!Q|BI z+B+Q!8-)*fCn+--od3@8pW!FlMn9o9tF9dS1GEAO95{f)0Wv`jWnfs)-~gm2I3zGU zZ~$@{z+8p{3LwUTh9eC=GUpBcGdz|#;`}nLg}JNs z%7H}+OAZ(vakt19V6W&exT38T`=tKcf9E|D;~b71JpSR|r@x>6e*f|R|NHa%+xE4c zKldL9>Ve^hAJ_}>GvsoK2s1GXfE5bxbC_$03urT1K=?q{HaHw;>Cw3IaAA3Xtt!yW z|NHBUzeg{hU*~C~3JiBKMivGmMh-?ECMnKj1|~*!4hCyx9!54kLv9uZd2xl6VJWkt zR6p}nwmp%)p>A-#qp06AVH~umu%qj1XdSZTp?<4OgiRa9J<|O0y9RouxQw4_whY61s8waxpuNYyqC0Vh^5FGb$=dE(M8`&<9N|G)Rw%;%>*6h3(Iol!~2 zRD_#df>oMbP_9j!_u#+FZ=U~n_u=`E%s=l6|2{I+V6w~)U@MGX{$Bo0&5nwj&u>59 Qet!G;`u_j&zb(oO0F0*t(*OVf literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Gau-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Gau-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..b76e47a66c5f3cc43960db55a73f956188b62886 GIT binary patch literal 32 ocmZQz&=S2@JxfB|P%+E*zKb+YMfZWY`n zI`u(OkuCb*s~{rX6dD|3FM@MVgNX|=VGqgz)-97X*T%RXA9@aa+;h%_bK!mfTY)wN z0*C*MK$TLL#>c>G>lS)S43X{RfsAD*cSV&ToboE{4O?eihsjr>5brNv(aEs{DR?M( z>r%GXAg_%Y9!o0p-g5HF^9#Mx?Qc4H0!L^0H=ZuoL-*DcI>ydNlbIb!Xn^s~?gnOB zCL)QLHCBz4s4Y>dh>1!xqL+!{%J7|;Bx)Xd3$NKmwLKr5y^62oO!O)~5nrJzHv`}e z+GiWi+)NzDcGr{jtZZ$80=n9&Mf8N`m@0q(eqh6FPz!3GhhngF&qzgdw-IKm!$Qx@ zQp)eg?BB~2P{@sZh-YM87OC>L&@LCaVHe8@JUr5T`XRNn*V0$CRc%>ifQADw4XdzR z<^n`ek#4Mhdb)nJ?lBkO=x;7Rr}1iAuK(s`&yVJL=RK8YCtQM1NDx7m3}TYsrF7}cv!D8~QDrWc2ClV^Lk_)X zjEm-)Rc|3Hm(67-WB)gA?w{@38+erAk<`>7TyUp&iPI&K%7Vb}gZ*5Af4RV6h76K+ zYosmQpZyQo?q>w5*NGyzW&40Gc@pegFUf literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Ghost-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Ghost-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..45d8d9fc9ed45986a4134fd9f62a2223d83934be GIT binary patch literal 800 zcmah{OH31C5T5O}-EC>zO-iC5fd$b9^xd0pnm$<VJ$LdK*^w(CNaC*mwSLRmN zpF=l_D{s}j7F^q>)B0wi|GFTA)hL336r_r(fHrWYV~j(b#^i{@mjSjAPNMj!53C!`<=i;#sG2zg{Sd{Z9ox?P>#KTU~4yE5c4 zPtXyv`3b)z28l#l;{50(M_Xj@N_*V4Aed9;qM0*I+@Fd96#mM_QwGYiM^a0QDpn`yFj26Mgxfck`WYwrtQ>^$^j3a=7X6Oak5{T5a-+^Te%YlJ( zB8=DN&$3|2*InIoW3(oD(K6jURmBZ0!n#$4Rxluq6$W9~!tOb5;^AcW(bB7xbH94( znL4I}>14W?7OIx|NMa#dEF9jX-bfKK@1w8zf#|zTI`clW{JOU?XjB?OqeXAYAIjIS l{M2Gs<5JtIfA;BxgHOf>Bd3?tY%=xo?i<_uPQ@d7mEX@S7t#O# literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Ghost-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Ghost-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..f4a72e66b3cf3f5bfd0743b0188e8f83b7652ea8 GIT binary patch literal 32 ncmZQz;846*y)mw`*gW6B>#U=iCtqB5qM`(Yfs;wHgO(-$q+AI| literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Gilius-JamesWhite89-GoldenAxe.bin b/graphics/portraits/custom/Gilius-JamesWhite89-GoldenAxe.bin new file mode 100644 index 0000000000000000000000000000000000000000..1748c47e4a156d7c2181b3b7e26c25a2c005b4ea GIT binary patch literal 800 zcmXZXUq}=|90%~!9`#+>mI;0U|G`nA^LVf#=$$G8ASOX3 zeq%FY6y}-yF8~x!L7NtqHmWJh?22)(D0Wp*cP%@#RqARhVz7a-f)!}6BFaKpu@MuZ zu_f54zy2x|Mv9`ws|#fE`}&eW+k|I>*ZAi}9!yqMRYv;1%)B2OZEs%IRJ_&el>M@zaPkpxQtl3S`%9z} z*_h8Qn>ZtO$zD8;!^yL`Ik`Cq#y+YuQra=b&J?~hp>2%w&yUeau(6_lsLC^37paRd zS%UB4ujpLMXZ?ZpP#SwmMqHGFO?aoVNf4=pT$QvH-TA{{^BDg)K*O zJdThgg%a+S7443y_BVxm67eg796ZC^VnHW_C4e=rcVYhbl8{ey!eR}u&LkXNK817s zCey?yeg+b!mrtBHr)H?rD${(~v5;TArMfnlg0E7Cs6BW$RR}($+2~~EiuF~8x39kU zl>5z@yM!iA=%?_*csS literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Gilius-JamesWhite89-GoldenAxe.pal b/graphics/portraits/custom/Gilius-JamesWhite89-GoldenAxe.pal new file mode 100644 index 0000000000000000000000000000000000000000..30061079a6a6ff4068874b3468c96dc24b159285 GIT binary patch literal 32 ncmZq3`JcZ{m4zYNY=Phbv-@5~g2_BA46(`a;R^D~j0s8ry_gBC literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Gogo-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Gogo-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..2926bb238784fc94a366f2f8cf9acaab1bdc634b GIT binary patch literal 800 zcma)4O=uHA6n-~4N+qCyOqHj8=@3f(k%5R2Jb#U8|p2kR{x)I%*|Lj=K)W?Sq@ zX{|zoe`!wQO+l$WlypP=L0B6K1(DRWO$9wz8xf(ln;my!XnPXpV|d@pgYUg><^lYB z5#%s|Y)%`G7~lv-b`#)W98SU_hQ#Bs*xAlh>d|PSSSUt~8}VDQC!Jj*a(d64Fb8@` z!^NTB*{jAyy=}#lKTuyvC{f&s5hfdj5m?wYKUtFm5=0lhLCY=Tu=qcEr`4!ulvT%t-Yp5KltVN z_F74@UPDWdHoY`suHt?k;<+i~@p08oj{qJcAK2fdFHZ5gSVYn*f3sbKICZ zF`HX%ekbTaQ$}4EegMO t|2T1(`@F5YA=#cBrUK9T_R0_J(HD|Z%~L`d3VG4L7*b6aTgR}P{{y;4+$sP7 literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Gogo-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Gogo-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..1d242989ff254918ff40bf0f52f74d683e944d55 GIT binary patch literal 32 ncmZQzsL|i+z0)W}Q&ZJKYLmb*iEV=KB=~fbC5_y|^z@Ygo!JR3 literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Golbez_TAY-HoxNorf-FF4.bin b/graphics/portraits/custom/Golbez_TAY-HoxNorf-FF4.bin index 2d946ebc81fbf3c62bbe27e3f5909a0b999bab9f..e0f627cbfd4c23ad48aed7243057567c5afc1fb4 100644 GIT binary patch delta 372 zcmV-)0gL{i2A~EU0}2xy9~}=7JZG`*e_Vf%0t68p91sEkD`URSksNXo5bzEG6ay&) zLp%@g4e||QA8?VNU>f+C9P|kEN%@U;Kga)x_Zb)n3hT;P+rt5F7xIfbSph&n#mT;21!h5bpqQfRIceA3G)=G>=%vAYcR>0UN(x>WuMF&HH=~fR);euyT9{_pr_I1)c+8u5npLv~njPJ!z17}x#c({eo=P`9o(zLN z)8oS2N+yu-fVw!ZdBQRub=RE@=cZf9Xo|0O?r%Ci7rst*W5wQ4Yh>7_>+NR#qMOM2 zxWyFb$1J8W1QRB&?G5{tRW+*|!i*de*aF~y7iAEpI7S<+uru993<0=c*6z}@M2-6Y zl0eqB)1i{bz%6v}Y^9lPW_Y zb-F`a${HU)8gVx*ND5B{Img!!2fr+dGeI$ODjY&)Iljw{s(2q7)mf9HW|Me(E2@fJk6?FUUe_wok`Tzg` diff --git a/graphics/portraits/custom/Golbez_TAY-HoxNorf-FF4.pal b/graphics/portraits/custom/Golbez_TAY-HoxNorf-FF4.pal index 5b653405..3ab01298 100644 --- a/graphics/portraits/custom/Golbez_TAY-HoxNorf-FF4.pal +++ b/graphics/portraits/custom/Golbez_TAY-HoxNorf-FF4.pal @@ -1 +1 @@ -AZZsf-J(-M=v>J~_{| \ No newline at end of file +|ZZsf-J(-M=v>J~_{A \ No newline at end of file diff --git a/graphics/portraits/custom/Imp-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Imp-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..4d23b8a6167434d22f0102efcb624c779cc7620f GIT binary patch literal 800 zcmb`FO=uHA6vy9YCA0X!B&X~lIDrV>G(FXiwYxWOdiG*hig>Wt90XU4-8l#;2(~9f zL7GCri-K1VY6l8dEG6FbmWerR4uMQ)X{WN?ake3%Hxd88%l|#zJl@P3fWMS!;sQQL z#?=UHR3e@`iWf>Z(ya|JHAr&;+S&DK6-E(bocmfXxS$}incE|? z&3nz!B~79x_Jer~`(pmz#5Mrr(tx-5xYPTzj}&4+uZcUVAfi04v7D?9=D% z&GqZbZMEukgf=$T{8niZLTFVQK|yXg)Z-N)g^;zkfs@3us~|vX99m>I*)zTOKb8K1 zl(D#$J{^Q%=n{mih;SmXL`vTxgaS<9GQr{5$J?)XTlH0%PDz&TFjtt`CQ7Njc%{E< q_c*5>971eJ!_UZL!iH4<&c6vRccrKWwP4E0K~GN?Yv$=Zb=Xh$oZ(LZ literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Imp-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Imp-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..8c27ac3a4c28eea05284b813b9a6734eb0e46033 GIT binary patch literal 32 ocmZQzcrLR-a;c4`>Py9QmVJ6g_L_m+iF|RT?$g6Jr z7&p%Tr~}Z7;yB14N-Wr+vpF-LY@GFW(;K&Y_t9?xu;?%DW=z_|*y7|%TUuG6H0kE` zmjgE{zU*_4**W0gk@P&=T{B?f%!r*Df7QO}1+NCN|Gg)-=IRXUG0L`hp(&@?ec@wV7><@@q-cA9mCD^{8o zH?{dDOtc{tQ`ENCc6s~O8d=Qk8TNesJywz25lX+1&|4fjaK*6a9Y^-69gV8~1t}`B zWFw{<8RK%JA&r{~IV|*pdIuhffj?R!zA2#_7^~NyOJU@mSZO4$2Gw4T42^W~%ewWP z#`9uC9Y_u+uPT{>vJ6gJFo(E!ZdaFdB5~q0lK2z44ZgLAM03v;yoo>D)z7fTSA6R? zn&LB^S6WOPSi~X$1h90b2Cp5NeD>r1r_E1qo!KeCuu*^@@p!4532y*m5O8`9cx@R9 zINKS}i-t1-M1Yim84wg@#AeK-)<)DvMI6PL!EDeSj#H|y_Ihf8EO}d|!64-P^qEsMoI1=^J$~M@_E2U(KP&00aQ|NevcYM^0jLualt<08F?Fo#-La!@l&Vygo3YhnCb#nN3@<&f;tiXPv#+XRqz^oZh+Y_R*>f zP1n1#)*rk9WMdQRPznW&w443T!AHIcbEuH{z$^eb+O$M?P=jcW^?d!tk_|i3Z?-QC zW;O5XK?T}y7260~pu#z1IqheT!8Lu?2j2e3UM0Z~2PJ314jE6d54lP%7RUwmd?78y zC8wJ>lg*?E5M&M%J6lIHM?B;rxOv=Z*jF_@=$p(7|KlfC-+RC3%b0Ssu0GPx@vh8^ zevIJ|{ZfDi9((UrHrD9T!2PQB-N#T}XF?n#x)wFT=ykSwLW!(^ z^d)3rDK^7}EO|+ST{4?2QoQJhT`np!&F3jn4b;H{7dxz7TA$Rf29uKFWD|K(12?>I z!b&oN%P6Yt^w~!cF%(vnr_;Yf0iiv~PZnL{%FmRvy|A5&X-m#vHfGml zf@ttYtiHYI#jTtK?LqBYf5oLMQ>cZF3J|HpM(Qv4w3xY^$Ck?#C3uy>xd4I3hFwQMZ(5g-dHGZ82QMtLv*~ef3i7ywhj5 o?$!e=zziA;9SpPl|M{4nXEF=%yhB_emn$>;AN(fuZy3Srzgv&?m;e9( literal 800 zcmZ{hT}V@57{{OYY@Rc>wKJ1&=GvJgS`>JM%1y;{!hu1UK@dTPGrI6XWQ(X3);U>Z zR-m@Tq$n>UGa@gvnq9b4;YVO8eUPEJ`2mI#oD^>AJa5NTy7K>fA0D3f=HcN7upWdc zlBVhZBT12@)}WyohT$m8{i7ADbSOcuf7ds>SXAV6xxy-ujSa{$#&`wCu#D^e#AS=0 z$^@1(Hl7%MF*^uhuz?u}rx=xD4jHqU#s$oH@o4cN)t7jjG!QgFAr+vt%><=VsYyU+ zeHKJc%5*a=r7fJu@d|?9Uo!V7z%h@pAaI;0^a$yTWNo0i$=>bu^A&fteos!Xm5uGT z>4uj>{`iC1ukZs!&_ZFj&vnDeM}`)TT{?f6wZ9P#$ks04!3stN4Mf2MX`&G{T`zZyx`?K zVES31rcWI@bGyP@;T5yQGAUI;5&;tm)km@{WtP-@lt-8+%oA1BA|X1!0cwSQC>LE- zgw5e|MAcC>+h1P}t_>fX^$gd}JT{lLr)*D7(PHb=kG1^bMd{ze9f+tX~t}lrVOjlhP9wLwVGIsme7$jNoNQ(T#}+}xn|`W zzAA_&-;Pt`jV`@qiCYmA0|ERIPvPHqQL*onXE0R^XkcJz+1iyDxZl+iO2-X&1~16( zWw(q>WoO}{Sz$y=O<+HvaBls0LRD+`~Uy| literal 32 ocmblET5LSQFQuiiCs>dl3!KV$o}!tla15^0KL2mN&o-= diff --git a/graphics/portraits/custom/Locke-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Locke-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..bc96ff155732c1d6226aaf4ceb922735788978da GIT binary patch literal 800 zcmZ8f&rcIk5dL0Y*|(6$R!%iR-k$WPO}Id$*|7jL0OBG5(sQ5%0ibN-P1jSpBw z6$>eqqy`(kkIz4SU214$*>j_>^$cFw{`z+DyYt@0pzh7j-!5TYUq4ms{p8wr(eL$p zzBl1cxG6W`CI(E5tD~73$CYnh{_F9rT_c$D^W~pgQ#N!z2*X;-LWM#w9XO#Kjs~%i zg`nUN(JIyZb)>5Q+&DCF)hkwyE1xORkhmUOYx<_YObx2U&CJk5^;r3Y+vz-mfX0~U zFxssi*`6D+hSFWblaH9XE2d@}XBrp0k8`qf@2qStpiXO%b}6IsWOBBefm55r(b;d( zSbhf&k+##0bPU&UZ@T*w{l)?JXpai4%*r_6AN)e1TR$GXvIY(duLsD!W-r3ZaP_97 z-U3tF7A#YmQWv_=SOpdAuw_wf-At;25=Vvkbnu=`_^(7*ITaqM2i}uzV z{d;zP@?+JiELqK)e~Wwea+iBtgqOdHCV;KfB}-YCNmCm!Tgm%4m_ro2W0| zapwQDW^W1mOsJ3Vrnaq*gE?SNPV?@4mM<8i%;3z$D9*ooZOKvrp^taRe3-!55!UwA zKM!DD1AFe61^d7`#sz#k`=LqG0-=-pH0mVn0nk?fedtrsJ^$&`sd0DX!&6%r-;h&I MQOEsr9eKa`7nG#A;{X5v literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Lugae-HoxNorf-FF4.pal b/graphics/portraits/custom/Lugae-HoxNorf-FF4.pal new file mode 100644 index 0000000000000000000000000000000000000000..e599b07b4198912c271d46342e0ea97fc6fe8362 GIT binary patch literal 32 acmZQDaN_x2ZyYzD-`lo2eSQ`L8UO%=+6Auw literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Mog-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Mog-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..08b3cfa106ff97f3894a177be9d56edd0ecde9f2 GIT binary patch literal 800 zcmbu7O=uHA6vroS>TJxK1d-Y(G;7b&P4!YxI$OL1K?r*BQnok2OFalpq{%L{U~VFK z$VnFT;Kxl5f*;)lh2HewB~b0|A<#k&T}>d9-RVqcwxp(>3jQ7p??3bA@#f8gAPvu+ z2I(^ykc0pL2o#vlTu3UCB5}#FfNKGuPfRGy8w;|~x}{sdYRHOmDiensF0Oj$9@9aN zgP3_>4swwB4~*gH_uh8jV-9m1#v!cR-`(}RSa6LHUl<`#M5*wOV%fIUv_ZM$KK)eE zTP;`M+rIC+J3-KHAG6H=3e&+ND66Gnv;4JmkVprrTFmLiMrF;Kx-|Xbnfg|}ud3n@ zE4{`8dy!v>rIsH*nUm8}YAgxFhor`{DVT*&f|8KHG!28dkTUw^PyWv5YvT2uH&>_h z+t&Jey}t2ob^giiThZO`_eu-$hs*EZ%-62hUTwYfz^|#-%gX>HPK+fw(xY>aHKSk@ zL{3S9aB0af5a;LlYIU@uSJ9|qEGM`95bqJn5huQnG2v<$CGy2sd~UdnVsVVZ%|tOX zJ)_NPvvU_`CK0-VvIP{IR+!8V^N6wFQ*s0@|L%1Ao$bwykHdg&x6|4D;XL2*0}_y4 r&kIwJr^}uIa zjSCyxYnEtH%BSuah|39S4Gf_JDH=%!V(lQnGHM7^N##h2JYdA(91M0su~LI1qwm9a zO~IjE$ljHFt@3VeYC9J_N!6d2*)v^XR2o}fwsm)Onh3#T>!vd63+oFTid#!fELrvj zAOwhgg#cjudhTs`Wx4K3(?Gk68y`b@MFgf^P%jm)asPlY>K$i+fCU!74O8%cGn~YQ z=g8uG{AniSWW|I-TX9t21zX~uhhju0m4|M}m|K!Z!bi5+6bD3y$f~9TKZV^3NeeKn zEs-VknZ={SVfE|I9O_tq{&&IBn9J~yUFUuAoWJ8Ld>v#mLt|;Xoz-?eGk9hWvkax||cnq~(!ZVWGD^P!}j1T=HVh?WNmRzq&lSrQWR!=DfIb z+t@bp>LV_#KchS;@=Xb>-|JBkoAgzye-5efo&=R=4I?;=yQF4m6#jtz75J+bQudg*KW~*;m~pDfuNmi>Cd*LZ%5XTrX17;oZi>{vY{&qo-3D2<|mmL;@2A?wjVaWfI$%@%A3;6R~aDWFR1<39e7$&ThD| z+k;u>mK>S!hWrTS=lh*PY3a^Lc=wE%#HAvdE#16T`m&KqJf}@TMUf;=TehSq9!U|c z-3;`+MZDDqPeDBxPb7x1H|gkdABJKk_oeRXWXKu6AAkH{&kmnqrAQyq^|B^kS@uaN zE-#O+ZCRP2!VOvrQ>Rxnk8PN%l%R~2$|SVafLD!1g}kwuq>Zcy2t-f=7)f18N$5e( zRYCO*VamF~A4uxI!GAKnqx08+)9QuEdvm18S#>}24Q>z_sd&vgX`p?e?0JCrl$^ZZW literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Sabin-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Sabin-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..ebb356e4654e1ce86015d56e2a663727844f3a4d GIT binary patch literal 800 zcmZ`%T}V@57=F)pwwQZkiHB-w$>N$%dkqQY!6LGr|Ny=H48tq4ot&lLJ zB(QR+H&L=~#K3AZEfc|65S0aLf{JKvDxBrE^ENZ<%ICRwzvq47<$1pE1K5O&#Hb=9 zZ1`UZ5(tw>2}KBDAVpzJnJq7jN=V>&00I(HEbENTb3Q~TGE!Z>Bdnyv;V3DIjI>$- zWZ)>s>jUh@C9xg>rtB-NVzMU9W6$0Htb1cWfeFZVy@yO!m}mW5FcbOd#wjgNxp)ht zN1Ub;DJ?}P8XACtAF+!uU$W+-9-plmik@$(nZY^1y}a%=lRu-=9OeHC<-;nJB!tyG#Ls7T$S!QVui*~N|$|VylzGf7I1~ggU2=R3HYud zCvqYi2oK19u|C!l6h#b#7!-Zz#ns^LOD%@c%x_6%ORc5$CW03Qzu*t0#O2R!!7Xg( zYpYMm2o<^}>Ys-H=Z&5SnxOzy*QAf;pMU`X14jd5-F45rp+LVekCWx7vK5yz>i78% z2oiIyf|k)S%q~{LoKP0YS!ifzYwPKmn5g7#^fv~zuT5BiJhon_ylXQznYAdF_x9>^ zWjbT0Nl5o)qkwrRT`#j62bNa+q9~RYpW+`p%B?QyK3F=IaWUe~)S2h^^)Jcz=;Gc* zwfM)whL7z~tyIm%Bpp&7CR8ekB~mY^;&hymGiF?&%V|Qq>~y@3@vtvWeoxFfmh%R2 zIrQp6=x0tkon|yu46ik>HP4P%M_3E1X7$ct92VStZnPmY$#gK`$9GtvMQ#JPa+
=T_-z1(MT)QYMaqdJ8#$9PFC)7yk&#u#qY}o0FrVFx&QzG literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Sanae-HoxNorf-Touhou.bin b/graphics/portraits/custom/Sanae-HoxNorf-Touhou.bin new file mode 100644 index 0000000000000000000000000000000000000000..2d69ef042476f4bd2b8dc70cd811c77c07f0f7a1 GIT binary patch literal 800 zcmYL{TWm{l9LInEbGEjuv~*-l+QW$JLx;>nt!IhEHFhMf$!OV&xTGW=wy|?j)&~zN z60rwVNK8C*iMU^Sx?z}b36Nl%SPqV=##sVb2Plkr4qeK#HxfO!%Xh1lKW&%4*rq2>~J~vcH9NqH=dwM_yEV-0$FA9SQndss-p7ZSo zeT($oNoUK>3=_`@ftW&UA)3h}la7_e4u_;W{qOEdUp$_X_t`9LvTCyvbVk;rO^XmLE&wHS-O0-_p&saQ0+QM8_eTahJDCBV=4l9s zX1AY;m^*oflKxAJMX7%R1vC_IrGPs?Nk>I2Mc4Rv2=K+#fU2h1u)0iZNi}hy+NkUd zTvO$2E}48&Ip&iiFJznBvP5S_)(yLBR!_HC3ZBat-CxDBSJ#D?$EU*-n9Oo~F9@Dg zQ)cG}-$m6s${&$x$3(m67_=$26JFQjv)H5HaWvfr6@F~?v2hNHAVlV|TCn0a%s998cE>DpHBJpE^FZoNPOptfuhX-^+)#KbZNeu=#5vqM}qE)n~rR zB+P3J+YC|XikB;u+}b{ePMemz@V%D&tjAF1UnItLU-Sojj_wyk;u*$iL#`w~OB$Y6 z@#w^^hdkE+O)x6XkxGLQ5}wkU8#As3myDTG8tQpOgFCu90BM z6C?{{ZpT%_vEu8vp^LV29LxP}9M8n@9MAK@c;jCXSTzMJIF03$l>L|)O%E6+8p}+E s$p&M^)N-?Bwms5u%2Z;S_ykzJD% Ee \ No newline at end of file diff --git a/graphics/portraits/custom/Setzer-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Setzer-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..115e59f8ac0437488fc1f39de5c8643f14490b3e GIT binary patch literal 800 zcmZ`%Ye-XJ82--A<~D6R)8&+s=1?R^&?Q-swEc*Mkq}{7D7h#EMVWIj(7YU$NI@{| zNBuA-6ouH2Rg$qxcA!#=QnQwZ)wUMQdE0Rpx4FyPrtW9Y1MkOs;d}U=??q?_ID>^V zSv-6I`qU>gR5#yl9OIi7WZ1`zsf*y-#< zgea=G_ml78rY9B(Z-f)~+o&KNj07W*SR}SDJ(b5#6N_YpDJ*^(|2Ch+U@nc?>zY_aQ&VGwWY#K6><(Qbk4q;@AnJKM$s zuvP9`i#-Oz*lSn)zyLTm{hk|J>J6nI-9y^kvf1Q&|96tRpyZTXPEqpY9EnJTND?Kg za+I6fTRb$ov=CfMR4J08DCNO(xdH)-{5?hr38BMmH)AUoCQoD|UHd9pV#*nE29tr9 zx)xoVPTQlM7|k;CCy!e=%sq(7L2*vm)WQ6Z+H!5R;*8?b#mnD**ngg_#wC~plcr0M zgpnsK*qc|RQ0a@nSfQ`c`(~aTo$l}Z(tCH-d)}crRI*=MQtmtjO2~n6aKvipXOZr( zGvQEY?VAG*5-13Ai8LaW;P3@vfhZ$0LyWU=b^<(tN2Kx)?|Z1h|9tp`>nSZXXDv3B zZibfq{`Ju{Bh14qaF4w6uQcUHQA3p8aXtoH?T5oJlivU(EssVWbqbfl1XhBqIor0mU!q$$USKT;A&5^}qGoqQE6i5m1{{b5|oe2N{ literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Setzer-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Setzer-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..b662b62c9ed2f5f329de3b69df22f5569d04f861 GIT binary patch literal 32 ncmZQzNYPnt!J*iv*<*N7^^@&%y<27iPNq)9e$vrDl4`O6r;7_n literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Shadow-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Shadow-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..a7a40025d9f6349c9952d87d8abdbd8e38037d53 GIT binary patch literal 800 zcma)4J!lhQ7=G?9xm-z1q8N-|lT!o*5vz3Q5Svf2P!ww190U&$TomFUwx;I3NU5Q# zOA*09GYAUprnc3bbx;@W(7~ngrcxZ5lhl~lB& z+{30&LsVUPwJ^Ceb1(1B^@)7(-NGnt%OCpsPgJ^IA5Xipy%eIuM6qK*E-h(mOdb~} z{7Vl;=U1!Fjtsc`2qTa4Z1P#pjHVgLv4m!si`pZWfJrrQFm6DDr7dbE%Q41zi<(hY ztmXaR@5Ra4f#rS0y*Adi+t}=HC-S?eA*C~MBVgUp2VUC4;?J&&)TLqQT;~PjqFD;3 z#%)+FDCGtPR7u->!J_(2DFy$=uS2J(&S;XljbETP*2d>hVw%$>u6wExbBl9x7SP>n zq>K7cK`$IByot_v?xP>w2q1#9xPmfsn4QQsv#GHg6LY72#{-q!8@>(SSHJDA2P*W+ zf`UT1yWocpGRvZ|eLms}T^*Mpp(H8s87iB~NK`X!T_!N8 zDq5FRmy%5;_KAB(kqAQGqNdwbXl3i=C?OWLerPzH;JI5fG;f?Ky`H8_lf-e} zX;YhvwL97ty1yr{5{O*lzW!Wd-0|lMoS7mew(yQ8cHU9v|2#4}6LCdcfq8aG8|{iJ z*4WpWAgk3X6jFs$N>Kzw0Kp0YGs-R&?&k)JOD2+s2=D?275+U{HDrujXv&>y?l|7z z_SM~K>Z!QxXcrU4Qo0U~rw*$JlDhRbiyHj=iDzbwx!u;w*Uvn5j%jRz<$O6mrPGJT zf}b3hiuxBrm>oaZ9zT^E3m!=-qwGm+m&xHDb@Htw29gsT7{-kUaFI* zfr(3rOQ*Dilx`f9K{{OeB?uNSIT=x&R+&0|;acZQ%k#4Hy|P4oYKba8)no7&+?nnS zdZ&)g0$OjQZS+yP3aUMTbhRUub*DSUw|UD3j>F^QpyrNoPN;wjPajUD=oFaXyQ0~wAA@r_(UXmD0@d^S{Yn-T#$k1r@)Z_-&Zgu<0YzaeZWEPHn-GXC zE0cdFV*(O3S|Y7sdBnA5T+LoJuH=L-#>&@y>r5=+9>IEZ%lb;j$6FtUL8N@mh40JO K*~NZ)AoLe`1{L@K literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Sherlotta-HoxNorf-FFCC.pal b/graphics/portraits/custom/Sherlotta-HoxNorf-FFCC.pal new file mode 100644 index 0000000000000000000000000000000000000000..5b5e8f2d4a66e610cc3e550f18278a59dc596336 GIT binary patch literal 32 ocmaE$9G}0x`hWfXbOweyN%i5&M06C_nO6D*`~CC!YAVhN0Qcez$N&HU literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Strago-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Strago-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..4ba2b3555b45d638620952f16349c1cd5aa5b7ee GIT binary patch literal 800 zcmaixK}Zx)7{|YHb!W}p9ihlA%Qr_4i9(QO?!Ju7WJehT0q0g9egC zw}@N{D$>A9x@0w@LIxdFC^eIIkj=HwcH7Z*YRCDS^&olb|AWW(=lj2p$M*m@&gF93 zdEQrFUgE0&01jN706a$uiLF|!tUES38Vm+54h=0Vq*CC32FJ^%=CHYG>ezu+epm0G zPkMKaRXDEUo_<|rckJIjc);-XP(r9c%FB4cRbnqHU_dPh2o^lp^xjtyR6Z%HYA($y z@<)agnS2PvxNpKY?Ov;&`{C383k@5CJm0e$TJ|^Q5ZMmK)L2w@l&kwS@AkrloR0DA zXIaBc3cuiMGs5<~=RZaQ$-gHeNq!YnQLPz?HigaYc+1d`qAH4VTS-d>V3aMho}N%> zW8>lDrNwkqib|4+24+m%jAkGWB7h`G;D@LxMis?!`SI3N1uT=7SixbFl!VJ^E!w!+{5VX_wxp zrY+QtL@aUtiXqcMn_VcTHNmzd3ba{{^*T8Zlv;}siNvND_=78Ef7ymRBm4~M~JEq@TRpT_+*u!N=Afc+ZrIm#iWOZNv(U|spB7-UX(pM zSkcL&Gw0IiG{v*N(?=$t>W;b6ymjwfXxuehJ#%z@609HPzV;>F&3{`>Z)N@hrG-?= literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Strago-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Strago-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..bc6fa6649dc811f194bc4cc23840f76524836547 GIT binary patch literal 32 ocmZQz@MS6F;F9cDH8s1aI$i6QS&r#B%WF~9#c|>DlkZgn0G2rmssI20 literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Terra-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/Terra-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..c425032d81f4b5dbbf8b7ac9ff332e77e0ad1109 GIT binary patch literal 800 zcmXw$dq`7Z7{EUa(eeF>|MOc{@kz_x$rcFCQPz_q@IWsgG3T;TghH z1jl;UsQ5_4iidshU7x6nwls!_6V=As1c?x@N05hP6yJnN)smOUO4H<# zp-ZB;_iOr(_R)x9Rm+n|TCdvSJ0Om*vF&fp`j&6B<}Py9%S#+~*?qNVwk?>qG(u`8qgS9J93V}NCN4)f+> zwb~N(A-m(C+_1Zh%&#NJGxcs*b~tSe*c~*OoQ*VYl+7SOP+PO3U#IbO;2>ii=Me}P zzLv|wp&|`rNosHUs#}@R`f1vlZ@s5o>J3y_bB?8#zTS3gnpt#eoeX2J8mviGO5#aT z!S!AGag>TSkb_Y&eTY%qAif~b>2xZU>Tr2^o4-M3kkL4a5J@5u84~;r?1*Ol3M7sr zoSpxNqHq@W>Oal~5rLV$Ct5o-2NrrJjg%z9FM=(G02T{aEU%7#8MdHJ=kDrqpD9Ow zL>7_*Jc)RCWYE+;+hLxx>E(>b$p=J`+B%F1nNvXm`+I?d??g@kgBT(*CosABU2^+A z`L40fmN-9od`ekOX>}Fhf@-MH{>iEU?fq*_^?TpOm__3gN_nM79jq4FM*v)|&Aba$ zqZ*w?X=}E}+o#I1e@wtf=w`Eh-~zH>-@3nLE>%xGN~_DiRzP8=!3Jjd3JX93&T3%` z#JV0jI&T;+HrS8P@5v>yi2b01L!bpIAyqCtIOc!TyC~|8Pg2wSmdyic@X2zrGCV0L zv^IcTJ1TUZuc^gBPn66QUd4@VGYvl}_T_XVgN?%+qypc~Z5x4yOZeOK&-%|Eu2s)< dz@4@Zgw7$ikQq`?3a;|NugHzqZVQw6q|25kWFwuS!o^Q0N{!6cMdbf``~lK|}>{ zDLr{<4&tE)TfDSr?G943fhX=oTGw*%OhtU7U zAPFma6EowWL^nJF-91jAmcQo-qECxD5Fk<(qkX;M~xxpV&$n3Se%@s@Vyqdui(*n zHkwl=AAPfbx#fxrRkvELdGqYD3$CYq2bZ0tr6*GZOoHW^)#SODp{%PDjCz`ZB zrh*G+k3TqV&bdDJ^o+z_{Qz*mwEanxqyfKsUH*f>n#V5utR<+!h_;sgm@k)_7C=$b z>CK7>4L|kdUj5l~jigj>srQOPV>boDyz-A)wp*Pr3I}dQjaU9~ML&JRuEg~tAX1*LJ^${X!nx-hM&82FzVbi7K)Ex^7*4vFO>%Hk4clV%J zDpU)Q^?8L(-!1IitUMm}nSw@B#8QYFKu8o37P|zgHzM&-#09uOKtux97ZK$s9-IDT z9DaC8-JN=~*&)cs68DXv)D4oe<~C1?cF4=9p(#kx30Q?##pnH6fvB6&bz{wVk#h74 b>)i>)7#OgiXIx!>sWV2PJ%~m)e<$-Bxn0;k literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Umaro-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/Umaro-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..217033a9ba94918c39bf9e6d8793365d2da46b46 GIT binary patch literal 32 ncmZQz&{WMf?KQo|X35XtsO_5<`#$tStbkWgT1eiOq&ejPlm-gV literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Vincent-Twinees-FF7.bin b/graphics/portraits/custom/Vincent-Xeblon-FF7.bin similarity index 100% rename from graphics/portraits/custom/Vincent-Twinees-FF7.bin rename to graphics/portraits/custom/Vincent-Xeblon-FF7.bin diff --git a/graphics/portraits/custom/Vincent-Twinees-FF7.pal b/graphics/portraits/custom/Vincent-Xeblon-FF7.pal similarity index 100% rename from graphics/portraits/custom/Vincent-Twinees-FF7.pal rename to graphics/portraits/custom/Vincent-Xeblon-FF7.pal diff --git a/graphics/portraits/custom/WedgeVicks-Laurel_Gens-FF6PR.bin b/graphics/portraits/custom/WedgeVicks-Laurel_Gens-FF6PR.bin new file mode 100644 index 0000000000000000000000000000000000000000..3eebbd7c3795f5327c024cc6bde5f60e053d194c GIT binary patch literal 800 zcmX|Zo(XL8zKsMso^87x36d#BF$WZ5A*oKD%YmMej+l-SmCM@)!AOpvl$<}mH;Gxu zL4!#I!%o`Unl^1-CWJFph`rCR|9!?(u5!-9hGE$ER(%GlfG`Yk8VrgXVZ%mPWn7Sn zRLoKi;)IJ}p;irx+Ky#*A_#(^s=UEjFP&`Cuj++hKp2409tgob=msaqlu8$a3FGX; z$d_Cr$lwtDQqTD2pc(dn=2T7uLV?gLe{x65-KM9VOyv;t`8+kAVo%AI(#6t%4{5f0 zJ@z{>HGbQrV80H85|>=yF7t+T3xg0UQAv_S2V@(|`X2&Rypxr9#E+wTKSuGjHc+jR z4(WpRyeBuH0zf$Y$`Cc1Y>a z2eo74@o+8QH<@KClRc@7D-E(kRJO`fYX*tvzFnKxAaLYdTM@E9U75}{Xj75L+%b2( zyGd@8hox>YBDk%meyn;%xlf-5xboz3KfaxqK*XC56m+8(i&nNTRex;hYw3$u#SdWF vnz?4C%|t4ZiWx%&=aFFhv%_;21{MkpcS`P+JBMftMvgE1+FUZn%&_?nWWgp$ literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/WedgeVicks-Laurel_Gens-FF6PR.pal b/graphics/portraits/custom/WedgeVicks-Laurel_Gens-FF6PR.pal new file mode 100644 index 0000000000000000000000000000000000000000..c196376d4bb74a9b7f7d9c6894d5a0a2c5289b4e GIT binary patch literal 32 ncmZQzNaksgSSs>d=A{Ieq>gN_pr&fCYM<&wO(C;sPCvZ>k_!pr literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/Wren-HoxNorf-PS4.bin b/graphics/portraits/custom/Wren-HoxNorf-PS4.bin new file mode 100644 index 0000000000000000000000000000000000000000..b4e12b71fb30d92c890916ee01561eca759de21f GIT binary patch literal 800 zcmZ`%PiWIn9R8AKWzQcu-Jq3R4VTCb%)wOF*P46YIflo#3p^n&&SsscS2~kNkd*eBbZidryLjG)#g8 z2NM!H7DtR<-HqO}FQ1Ke&CD6l)S_4ar`7g`_vu30vgj8Z2riytZ$)uB1BJhi&PGqGJ9SQzAR ztrCxLn#a5aBSD~DndIo=^#n0rj~&_OT#*l@ucZ{(&9V3WH8qqRsw!bK6sn!9DhR2n-mrky!sg1zMr5WswE#vC%M<(hO~o|LTH( z2;zYWBmh}0t7jPsp!R$)bKjn`=TXNgTGN(?^qy~-TjmkGfVcc?^=$P#->5dKQ^j|$ zmzF44@QAdb4FT-mUi0fh1YdYdZ|P34Qs!nXVqpepNMm~&p+MmtbVNr?N36@bz;U4( zNvX!VCWROXR(fMS@C-v6^9teE09?1`*1(-al9gm-C)wQH-2Eu!g}hL;R7zbe*SH4z zs&>`sBr@qQ_m|6MV5o!o!+VO50Qtnd!M3A$Mo%(MXjpk!Y2Z@db?z{z0yk5wrsSyW zKXmF=uPQYRfLOOA*(vo5VMG5<9=fCwbkNWYszVNP69&D(Zsv3OT(LM`nlD*dIgkSe zQSb_as}pmOetWff+B^-cfzzLhq^#Sz9S)s2D(!t;JX}1y-i_JhQgl(3c>YkbP?@7Ki)ln7D>@N-2M57Hpymh&Fe_p|oe({jKazz` F^c%N*74rZ9 literal 0 HcmV?d00001 diff --git a/graphics/portraits/custom/X-Unknown-Megaman.pal b/graphics/portraits/custom/X-Unknown-Megaman.pal new file mode 100644 index 0000000000000000000000000000000000000000..6f8ad31ad0b571ec585d2724a3140ecd07f23d84 GIT binary patch literal 32 kcmeBh&aXb=FsHbyDmjv&QjSwV@V=9P({$Bj9tJP~0KS$8=l}o! literal 0 HcmV?d00001 diff --git a/graphics/portraits/portraits.py b/graphics/portraits/portraits.py index 446335c8..31001873 100644 --- a/graphics/portraits/portraits.py +++ b/graphics/portraits/portraits.py @@ -171,7 +171,7 @@ 263 : "Link-JamesWhite89-LegendOfZelda", 264 : "LoneWolf-JamesWhite89-FF6", 265 : "Lucca-FEOK-CT", - 266 : "Lufia-VRNocturne-Lufia", + 266 : "Lufia-JamesWhite89-Lufia", 267 : "Luigi-Halkel-Mario", 268 : "Maduin-PocoLoco-FF6", 269 : "Mario-Halkel-Mario", @@ -190,7 +190,7 @@ 282 : "Tifa-JamesWhite89-FF7", 283 : "Train Conductor-Mascot1063-FF6", 284 : "Vargas-PocoLoco-FF6", - 285 : "Vincent-Twinees-FF7", + 285 : "Vincent-Xeblon-FF7", 286 : "Yoshi-Badass-Mario", 287 : "Alice-HoxNorf-Touhou", @@ -213,7 +213,36 @@ 304 : "Sarisa-JamesWhite89-FF5", 305 : "Toriel-LoneRedMage-Undertale", 306 : "Yuyuko-HoxNorf-Touhou", - + 307 : "Sanae-HoxNorf-Touhou", + 308 : "Kain (Holy Dragoon)-Unknown-FF4TAY", + 309 : "Cait Sith-HoxNorf-FF7", + 310 : "Wren-HoxNorf-PS4", + 311 : "Nitori-HoxNorf-Touhou", + 312 : "Donkey Kong-Badass-Mario", + 313 : "X-Unknown-Megaman", + 314 : "Lugae-HoxNorf-FF4", + 315 : "Gilius-JamesWhite89-GoldenAxe", + 316 : "Arthur-JamesWhite89-GnG", + 317 : "Sherlotta-HoxNorf-FFCC", + 318 : "Banon-Laurel_Gens-FF6PR", + 319 : "Celes-Laurel_Gens-FF6PR", + 320 : "Cyan-Laurel_Gens-FF6PR", + 321 : "Edgar-Laurel_Gens-FF6PR", + 322 : "Gau-Laurel_Gens-FF6PR", + 323 : "General Leo-Laurel_Gens-FF6PR", + 324 : "Ghost-Laurel_Gens-FF6PR", + 325 : "Gogo-Laurel_Gens-FF6PR", + 326 : "Imp-Laurel_Gens-FF6PR", + 327 : "Locke-Laurel_Gens-FF6PR", + 328 : "Mog-Laurel_Gens-FF6PR", + 329 : "Relm-Laurel_Gens-FF6PR", + 330 : "Sabin-Laurel_Gens-FF6PR", + 331 : "Setzer-Laurel_Gens-FF6PR", + 332 : "Shadow-Laurel_Gens-FF6PR", + 333 : "Strago-Laurel_Gens-FF6PR", + 334 : "Terra-Laurel_Gens-FF6PR", + 335 : "Umaro-Laurel_Gens-FF6PR", + 336 : "WedgeVicks-Laurel_Gens-FF6PR", } def get_bin_path(id_): diff --git a/graphics/sprites/custom/Antlion-Astaroth-FF4.bin b/graphics/sprites/custom/Antlion-Astaroth-FF4.bin index 6c9cd20a9c5efba80ee2822f3512fab18673a5f2..27b9f0077369656c57b3f3976d92c82dd07d2dcd 100644 GIT binary patch literal 5792 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 J28Ubi|396Ie1iZ0 literal 5792 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 literal 5792 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 Tzujp+r~fo|u|fUmCH4OgB$e%8 diff --git a/graphics/sprites/custom/Cagnazzo-Astaroth-FF4.bin b/graphics/sprites/custom/Cagnazzo-Astaroth-FF4.bin index 23b7f15fdf0e0fe4a17aaf2b864b75c3778983b0..fc086d784392ca12649985889bc4158f4f04e75b 100644 GIT binary patch literal 5792 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 tdYB6KVLGgK7?|MW9`YmK!|logM!ZLLnz9qwGA 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 cHjDq?@e$j{%>~=Xth9<;;-B9sOt@GQ|=sH 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 UrayQ5t>FJCKM`LBl6T|(Us&{wX8-^I diff --git a/graphics/sprites/custom/Celes (Opera)-Astaroth-FF6.bin b/graphics/sprites/custom/Celes (Opera)-Astaroth-FF6.bin index 2bc13044a821d241f545f887a7b310c5d1dce7ec..6f9d4effba129cdce1effcfbd6006e8e169ff72b 100644 GIT binary patch literal 5792 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 literal 5792 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 tOtLTAyQC4Rn;QJz|^J0YKgt%$6n&~y5F7k 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 literal 5792 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 diff --git a/graphics/sprites/custom/Cultist-PocoLoco-FF6.bin b/graphics/sprites/custom/Cultist-PocoLoco-FF6.bin index 755f8c75342e545959488363112654d9ccf67054..6f789e3d89aff89e431d3c61f6822835cf4c0082 100644 GIT binary patch literal 5792 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 literal 5792 zcmdT|eQX=$8UNjf&u8asU)th0Z4zIKn{_2^oRGyKYLlZBC@g9tRjwgQR2%Xj9Y^TErJ>n#=Qy?l zo5lJ^wdaxT_wMz3&+|U_d%j){F0RDwbC(4w14?;~j64$Hfq|qkY7UzzYrn-0b%}Y? z(2zh(T&Eo9dAM;kcHnjX5kC(_*vf2W>eUXeZT>8DVLEa)y;v>APJknhYzt*m+ z`tIcqufXHD_7rylzZI?(yh6}_k>qi8$QmvO7@IkkWuLWP$3xl!+7Yci-l3U5Q$yo` zcR(BjbYj%W+oKOQUnNfBbNrd#Bg{dRFG#mlY#{j`<5T6hwh)rG zN?$pkFMm{JcSCr7-c#an;RfO+zh7_zopL9mp;~b?0G>bgFL{r41M%(gxGjIKI9C@i zz}wPE{_5y`(W}H>l7AEj`DcTThzku2%hWGlJ0Dl#x-t0xzi!Fe+Hsu2TgtO>1w6@L zrSDb;*Ra1sFXz}lK=z*(h=eZ=V+LjHZ;}0vS&{TB=^4|gEvqZLJCg%-CO2ytQvsuvY0`K!P74A3_LA(MY3qtiT!?1tAB@0zQ{?sl&6q z7uk|nb`Nf$xghhwq2zXb|Ehnkrs_?Bilm~IPjduEeA>iO6Xg(=N!4oN`Q$`)3$%eN?AR9 zxmkOpqYdknx3mYDEv0p(JD5|~Gf=n}68ro|*(R#plj_LKZFmss@D+dDTP-QKMSv<>oTyWF=_0P4VYOnsFxz`-DETD?t z9O;H6(!*^k)IJ|Rw)V^n?N1l@uTJZi;d{#U%o5VRjaeMLdoj5AQY5QAQ2lh@@{tDq z3;sj?HT)bSdLJe*LU`#KY9AKk*uS1x#xCV97Sb3( z1YL=dDAIMo#dE@_V!UL*Qj!mWB_9yL@?J-S3PNEIdi2BkbNU$BPfo2IRtGy4Dq^Q< z%fHb3&&N-g-oOm}pvBk9H09w#CJ)QUhmFto%umcyBi}wWZuV;So&djpR#mV{k_N{r4k+Qv+tjw5 zH$O@lu#MoGp(@-=0|qL^%%q_ib{4geFsG)ceoQQ5`lDB%8|#(L%bxF_B!BiaMq5K= z+5a6fMJ<82KB`&fsF^18TaWX;Ef;MH8V`LRd`9#V?sDEx>02$MCfJpYg$fWjMyg37lPreAWIIM2k=FnqVh( z-g_72&>Z#QUb?uXV%^e%%~w$zZVPRT+|^KlR%}O$^B!bTtW&0+;{j_a6D|=lMXEPWR!^xD@jfD=!XS@OG!xT2@24E?#Vz5k@Qs1* z2in{vZWqTnW><)u0e!uiu1UXbeP~UPcO(tNxy=)0=WQQcLf-Y5_EYWs{XT|eRY`5m44EGx_k5R9>0HvY4{()?k4bYcRrS*?VK}DFGfS*ZiB{(iPqlvB zf2#jTU-OjiYG&E=_xaYh#p>To{rSJw|L3dU`PScd1(e^xOdHM_2=rap!4%GGsF8O> zAIj?=DiP(kBG*53iRYw$mNAW%AMWx<))7}y}LT$Ig)w2Mp-bfXo7Z) zV&FH%i4R`<;PVd-j(ZY6%*+cAL_aTR!UOyXyg^m+M#QKR3h*Vu8U?+JO4-ZEp}jUb z&*BeMC2yq2OR--W)fVyCvSv`-t9R-hb|7+bZg{ABnoIqY)4z}eT9)ZvXpl5aB(x5# zQ|r$3FEmff^eHCmUCEuj`oz^oS04=Q3;#M(K&pyHY=c}i@VIf}N1Zf>ZTBm`r5xx-yX_J{4KIr|N&`naI+SgcE6UZ@;`GQkvht@+2$2e;pc)4CeQ* z3hA1-;QeEs{+=3nfeeN0tJ8;~xCbJeL+?Hl*04F{@c*XGG55KwKC!|#48(tOy?xzl72*z4q3<9Qi?8W#ZU1>D)VpwpazCDm-0hqnUB|H1%=|mIcH9Z2SpHnhm7$!XZ#Mpl zGDLFE>G)gh{K)a&h5hn+IVXPu!;H6+!^i)2zL?oI3o6O&sOLmJJ2Nm; zd(tI+bURH^o0S>RwXV_)0-kP<86u9zP2}_4cOT1vbmoBva{|5ZW?|lFO diff --git a/graphics/sprites/custom/Dancer-PocoLoco-FF6.bin b/graphics/sprites/custom/Dancer-PocoLoco-FF6.bin index 7076deb105fb166688c114fcd9d6d901adcc80d1..1d54e02818d7793b0086f96ec9689c2cb894b373 100644 GIT binary patch literal 5792 zcmdT|e{37|7607%Y+r20zBF;-)Jg6u=q5L5VvDqi)5OjdRa(>qh+uSOEyZkTN;^|5 znx!F>B_5n&5zmucm>C=Q!+7 zq!LMGKCp)kOJj(rDnmp`uoO#487ZTnXy4vq?`*nF9&Whnj*@*lJg5<|6`kV?U(1eX zCoY^1?#$OO{&JA)4#q5Rnfna?A<~;ha9FoLkv%%U5E#ri>7A+*?;Kn!Be?2 znPGZr7LU-+BqC76kZBv=zCp%Dx?5o3ihp=vg$pjx%41GVu{4_xEJ|szn}MPO$RLND zl%dr?G<0zL13mjflm4wPcU70o#LUbhnIw_f!BY&5rNd_~rMFK{jLgJ+sp!x!G$POM z5yucTN%e$GZFMr-Vl(ljnm2*$*BjQ&6yG!21z#T1(j3Jrsc66+3>)?nG{2>}qvb&8 z@yq}8Toas+c8*hzBj*Iq!T{lN&ir1KM)Lr@4HwpM&!Ri2M_W&6@2>7`_WcA5~0Qf|>G!%TyzNUUW)bL;wQ z^btk4w7xq3R?)qxRH73imGLKQmHe|-qKi>IDn|#c%2HLyKMuv*rDrdSR1wNe{Hwr6 z|5Ch+EMSa{ePoP{ePoP_eoz)DWsOplz1IGkQr0k7ETNP&E{z%csFXDX#n?x|hSrA7 z^?oVL9dw6yD;g;ML87}B-8!9ThYI2t7NvK@9Z!ePUZ>neDOjWA6~qdzm+$`qnEHN@akDMZ95=w@Gmw@I$Kp`~&EsVjy+w3VkfgUtkD1DmHukQ>nh`-lrx0c(d98NDW z>|0S~X%M}F+tz4luk9j9#DTYxI;!T)*vNVG>`vN%O%I)jBsGdCt+0|Ib(B;RP*mJ? z$8f`K^5Le5wtd|Xb?@s%C$`pz9@v>xLW4<4(y9#P7J||A?uDy`XQl}qQzZ|@jkw~7 zIYze~mLCW`H+j^*xA)QRF6gR;Y7DSRsO1^()AWVu5izd?GwD5!HOGBV(Z_UgtZ7GhM_38#v)|#~#ktNubU$?Zxyj-zIZ>+d?)VCe4;8p4+^-hO(= zKh&kE)(gu5%n6?H3Wor#QqyP8AD;*QR(mCW_f7cgFIVEvl;JPg-{(60r6tEJUw-AD z8~xKxd&CmqshXDhr++WMlzj8*qd&e8KR#lK)BUjfp8kWqUEbcN4OFqJd1rtNY$JAf zNc}^AX*7Lrb~GDJF9Zjuq9zq}P#v66(loCU$DvucYEwo3LKG_RUm|K!tLR?{KzaXC z>OV$Y+5RidE6!i6m@>>)=zo|^cA-N5^OSyy8LXs#6B=dSa{W(fo3d}Q|42YjcI)Y1KbpU-pU^)( z^}_zkqnF>?@qYYV(vf>>i)vQyOx&q(5_J!hWwLA4zk~7Dv}eofUlRt)>z|bo{_p>{ z{rCFuH@~<4C-sEi*nfndSIhXj7XG#B_lNPf%BBvf&HV-HUrXl))c-c2iTA9W6m!=Vf`t;I?B%v^y*!HegM*I+|Iu{AcZ-4c$sQl=R0}NwOIF?*Q5!E57epOQrTu^8 zY~}vfZtVY3{w>qL#r(Uff7jaorTRW;+q-FZTgkqDer96`l79EOc2F(tWjsjC;~ zeJ71KgQq$>;n9BTq97@JqAne?1|&Uh<)I%?UEQV0kP zh0;|NtQ>N*mh5fZQ4(@1h<`clKy<@^Q3@|i^658@^~QP!qA@WjW>^4XG)6~rK?u^9 z8?-vDHg(sr$GRKB?xwPBw2hkRA?l~iSV{7A8lxxq=>1oB9^gCoXYV!H-KVjx3Alhm zY7@5eTgwBmww9{kc2@X9h#EUBqQ`H17K2?vT{~4 zaDQ!gW2?to+U#he?Q|;;1o3cQ;+Xw&yz%VV+QGKORHi7h;x~X_5UD-_A5U+&WAjscE_eL%$+HJ= zk@ipkQM?nM0Due{j5eoT*`{>g3q9aJ(J`z}iU#ovg?h^N)koZJyPFHRJA@!i-ImG-=;iV%2ref+iEk@r{R*Zh*GW5s0i_!PezO50Dg}zRx zZRf)$yMHk-(|=ZtDH=GSA%^{U$3L^N>IQL*%PUn$RaD9W zznnv#V~@>73@9bEgq99Pl>}(RT*zGne@r|A@moX?;623RXCYuUgy`wv<6Q@z?h1A# zFD56AU`C_iT=aujLp(}%aBj{+4$(=~A}buQ4iM~Jatb)jC=5$0c}*`N4`(0Y5B$w&{aMhmP(!{ewZc1b!05!w6G|23Cvj zi|YLTtJ9Olv@w~QL8gfsOK`#pkC76mbQgmTkFfRX9?YlYB4%dD~`WQ z$glna|Ln!+27s&(9qArhX)*s;`vUt|^;&^{MfmW*+>y_t{CPf)^5^*~kU!7okySOU zDA(CL)>u^|&tjRZs&TwKpGQ{J=miPcOQlG4q^ePFlsmX@R<`qYq>n&08GkCM1s`cL zHX{wwlzd6rIok2cHImrU=qMmznx|_;`G4f)Z}gyG<*SznsGn8-pNme@jP$FnL!gI+ z8{7)hz388%Z9=Q9(K{j!p>G9Pe=JAY^i!h}2q6EbnlQ@&WgSrJ=?#qmQcTI(0&`x14ZB;qOBJnc@k<}6ZY*NXWx!!2Kazj?eN>`D{{?=lkA9W8Qfb-J`-JVR-IL*Vulvd z%{U?!mrd|9h2=qrcQ-xa-R>Xp-M8hj>i+77{d9}aEJ|LwnVPX)Axa^(W0^VMobjW# zM&~x7sOyS?NJpU(;;xwMw(XDlduy|vz4e;EXVb0F6(#8J1GFi3H&L(AH8v=PbwiOd zG0kNB3#0Y{f{Ex@2S**;Z8g8M|C)PE&0kX5)FbK<>R@_zl>0YbtC+7o^4n)0TlhY% zsO$7dNuU!{A;t%Uk#pM`s^q=Qx!Dq_mBX8r{B1~+ctsTY#nO5`W^MK zl0T&p`sMHw<7Qx2V>@d{;9Nv+(OH|cj;9Vk`1A4TJ!5xXY0yA!XrL6$0hSF4i5;uy z+e=2()Rh8#%hY*&fxf+D{A6lqY)SvRpnj(dzPDeO{{O=={aF>*x9~^&w z{gs`M#%GRR_Fe_Q9|XfW8ZL+Pma%bdQtr363SLt+$WxONAg>D4LYS{cDl5oN} zHSzNFoH6Gc%Vd<4s=72+FR~-d<`;!@JLeLL<`>9rq{!m@V#Y&y(fmTl%7XdDnm?W_ zd86^aC0TcJU%~vLma1OWnm_tt4;{QaW)GIU&Z=i_{@{~*h7a<8DboKgewqHK-G43A z{~=Z3i}XKSKg|2(K)p#RZ5lp_7FqWmrBH#5nK-Sy0`EAaPY=Euv=zx3)Etp5{< zxx}>mdOhRsgR~R*o5TQ0)2sEr%ZUzsqOkwf^O_&?kH^NTe?gM4%4(*6NSTn$LGfMO}sAIhrwqga1m|NM7-`S%Ip?;9E`?q3r{{cD;3AJSizzGBn4 zHQz1jtA+VitlZ|=ToK_T^lfLWSj#(*f1=FGqD*1IOTt3_zp_&PTlKd<|628TQU9)% z|5kq=aoneTWs7B>?VmXm5&D|hXMFj8KCX`+AFgkf?>0f;d7i{l?ANeEv~z5OrG(3! z$0?*~Nk!4+G}|(WQ(PWUb);E_CS+eZYPE>V5^g9b@|UlXt3?6xp`8MWAD^%9rCwQ+ zLsCczm(=hyAx?^utiy2cV$hf?iQhzTV$3x@Vwh2$ANOe zn5M~lb9ejR-tPDNzCS+i`*{R(!0Bx8BK+OEiZrkQKo|g>1!jU3JURds0_rgAm-a(< z@*hliA7^CM2g5K3=j0)8Epw@|ddfEQxwuctdYa_~lYP0JWEmhXuS{xY4>?+uzaCj2 z=QI>`o-Qy9zUi%Kf0yO=2E>lC0)9|Dzp}wmWqelnuyQG9w-m!llOxG!7v#!xE-M)o zd2lv~J0uK9m$r3R4oDt23*4aKm0%kPR29(VH)o8Pm#1ELy|q-6pU4}OxOA7e6g2sZ z969wxoo5PW-3ZC=(B$8M^G%hGWjXISUaorGxx`frO^hQchd3(cIXHz%IJzg#v8?5$ zY0G5i2HNMI6-E7)4T%{y9uw{rxa8iG6c>0)<+ z1Z(A|423Y$P@hqwpDoxxHXGa!XAm~90LI^A#zZznF!M8{Oskm}z$miu z0%|3()}X8tS63G8%zb_Bwbl)3uQrX5t8!6$Zuwehg*1}OlP}nBdFFWgWx{Ugghdr^ zsfCVOewE)6w4Yn;Q6ztI7Y?C*j$Lp{>vIomeS7VS%o3PG1d~K^nXP24$$%Odhfmd< zvNl<-?0AL!v0z1wYtpTV_*)#r-)H5#2>wo_e?{JtMYD~Q z(B=%$>vUk!7a{%vOAwm~+Bb~9-5-yhzY&AKAM@v7-&cyi4XM5JFFVKKuguMjM$W&- z-|NO-mYpdsk}NbBuo=a&_+ms~Z3Hk8{zmbyA+45xLi zVuNEA*E%o#;}#>4V=TSwJ{h= z`0U#KWB1IViD3Oz27e9>{osLHfqS3d*NRR$>ko*-EKlFbJ|X^1oC6#~HKY0^@wzN} zP2)$_3Hoep$x*V|=l{&pqT1gs_FjUpaJ*x3Rum zk?=w3c6+qGwaL}4M2M+x8()5@jg8i~TibWF@M^fe#rpF<-Y2Nn86HrM{BQ@e){}~xj)1ooTOU*9*_K`PU8=oTR!)1bW;AJ@rSKyA3Gz$AEMHK zr+=YXOyya>)upI%jTJ9DGVHy$GG-C=oiLr5J z!GgkBosqCS!*W4})T6rPn#Mu-qB88GIif1EzfqM%MfL^x3&31Bs2ra+>>W}9G5q5x zSP|kM??DFhAl(*kK>m@y294+U!#^<3H2yIp4?&N(4-Uj{ESwpxzm)`^-j|E~!a(^) zd^Y<8|DtzG!(Ay_e|K>dn)}ru7K*pJoDq6 zie16(5B>r$_(ztZX(0l$6HnVPH}%M}Cb2g7DX)M}@hOdpTlUDF#A%6s=)!&&F2ZH_ z3*3ZXsCxNHNX7SR?KnIqKZqGyc^gtB3h4vfg)uxK8-)ij8KZ~tv)I$6um60e lG^YvI8EsK!sm*E3vY8kdj^T(wJD=g`nIwa|FZsXg|4+eYG(i9W literal 5792 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.Yjy4ozVyXL-Y54J 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 AdH?_b literal 5792 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 MdbN$j6`y4APvu%Bg#Z8m diff --git a/graphics/sprites/custom/Kain (Holy Dragoon)-CtrlxZ-FF4TAY.bin b/graphics/sprites/custom/Kain (Holy Dragoon)-CtrlxZ-FF4TAY.bin new file mode 100644 index 0000000000000000000000000000000000000000..c8d4681b9213b4ae92c1e9e0b888b1e8110ec637 GIT binary patch literal 5792 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 literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/Katarin-Zozma-FF6.bin b/graphics/sprites/custom/Katarin-Zozma-FF6.bin index 5d68c1de7644740a1ce58a8b7e2def89c882c595..0f9f379017d7f2a115239a44a66387c8da271100 100644 GIT binary patch literal 5792 zcmdT|eQaCR6+iF8e$V!^{qp^sFPqYjXr#e3B~2Q~eymlMjcP=+LK!OEYX@z_velw3 z4aFpP2r&yGmLaqSlu`fDO%duO3zaQmp?OqLWvm)m)v;_#+o!5@9*bJ9w81Tovvc2b zY-dB$27jO(=fme+`<~xD_uO-S=K^@3j)^$@o=C+8p-liP9;gNeBv3eoss^MX(=*YY z9m}Suj$%Lpbm$>+$R92{T(eOKRkT+5xV8oX7?D|FE9bxgJOd4}R94)X+F^vIrpO36 zFs#cUlg)|lX-I-b#Nj%rT#{_Ho+_`;>*Y8`0=LNWoW#0$7t1h^jfdjx2_@P$9o3UY zKo?0jXprcUbwPO5>g}~smCG#% zAePlaZKaLn9U@1aaVg$BvU0p#>kW=gCk+GScrWxeCDg#MtQ$C9v|qvam=?#<`d#1# zJ0m-a>pwjS;OZph}5*UUabUiL!foWaW zBJu{YMG47%M}tT4N*rG)G72Yi-~^AvaWE^+kaQxYrL?IuuAjsc0e}ERAu1ahAy8t! zq7LevMf)Py?Si|+{?rV#_Z02RplL>OgK~!)u59FMqy{hbKU-VL$TqNvZV@$rIpwSv zYZ?fog5%UasJ%@@Oh7_T%BVr9eXfeHWs3F{a0#5f#Qt=zJQ66{7a@^MPvaEm=!ke| zRm8uq`H5<(qX@p8L z%d-M-I04UP$8wi)rQW{tr2rSQVz;lYN z4yR}F2Y=!ED~s^q%)Xh)i?5_dqG{k>cCxi6m6Jpx(WnWg6n!UkSqXZ(881cj*)emx zfF635(Azi{@8U$R1U+Iw{6;o;;@JGrT(q(Ot$6=BMBj~T=*1W#UwvA5!!6V}BnA&G z?-h8uWo#|I znSU`pPbYK9^T`Xx|MJ>o-{hXi6iyI?^{@_B!XD5-x8W|b=+c6JIR@<&{hK|r2sezO z^yIBqt@!*s%d!{Y8W|u1Q4?D!y3=&ashFoHNP>C)yE)7MwtW0?7)d;$11_7KO`bpg z!O;^KMO{)>=<)&*AW|fhqc~O4KgRM!a-v)edOA!cwP(; z-lh1+V-y{sNS29rnfBi(#oF28jnxd6mDEmI2rvPfrZYX`L0UU$Ex9e26{c$G2kS;_HMiL z$<-}Nq)rM*AcDxZz-sUz0m$@Zr?Sz(Ud#aP8>j!wBlaM~;V5PRe)Hyju~ERfZFjJ4 z@Ujx_1s*&v*m;`VSc?A!xw84{JBN(PYgYd9ynqi+!bK8Y%3s!3{LZfWNL8dPk59k~ zX4W9UQj=SKv=9J}kow z`S1KGZsIS*07PT{g-6G0vTLqv8d~*a<$L?M04occpkn+3FE{QwVBG3id~WhgE`E9> zr+4-dMU~(gh!Z?Q{5#L=xi1_M`#bvAg*lJl7BFK6Inf!si$(9D#zEJIi?w@0^Px*4 znb`CQf9;hsWPc zy#M0RJChf~bN)=^8jNd+pscf+q~koggfUUoRquCFGGvMqnF)u{kyjtTG-qU{GSO^2 zcRYk2&=ssuL5%DuhH*h!VX*ZVhyK3hHxDd53RI{ykHczW>78)l_C;6vLh4tA@qi2b zdW|_ggDmt!W$tV)9h=LK_dp7oh(zS~nZ+CTWwQGZdiS@4oo*Ol$U!oown|80k-kD~ zDDuO}w3R5xx`hBy8F1;R(EX6rXXUJx38oFhnz|il9s%7_D8Fp48LSCY zex2vHf{1ygn!3Q^x1m%nl|_EN#BUAR;31_`iii9%mI_S`&Wx6f>&U;g_|o_i|NQ*? zAMsVR3ti=i&%HrG%1Zga%u;*~P-3b2RIa<;nCLFzgUtz1&Es3|uk(6AD)4Wvo<>%Y zf6q$`)g3=?EAVe3JqxE2MgEQK8H(LYh{k70_d`ngrXvk_cD^L(!H{AwbH;*{tOI~Pi&|Z2F(5?O@fK0KWaJUxu~RqVWwc_+m@|6juY`QtZKaky%u z@KpJ>j<4N4xE6W4$OLTVEZhz27+4!i#p36ib{OHQ_eNhlHh3IQj|_aGTZdz?mB8`) zI%0Q)Z1tXM=N(mb9^?-lY>?$`PLY#s9(2~V(eU%3M0>Qa=j_-F@^=_jB{e|%S21Dn zBad3(5I^`2{rCN!-G2|%ymIKxLz%tj4u7Yk%kAMDu#4TrG8#KzYsdKE8dKlA^3WCk z<<9dTj7}t@IsvS3DzWR0iF%A5T_}kkmmRmeB!29ezl@UjnbwSD@uLx3U_O4R|0CQw zH+@n#fw+-v4PoD4T}xeaU8I4=cLTBtrk1JY6sDfWH~lzsHqp^h^JFMQ*Zro-ODYu4ubKj}76l~(X9{i50bX}RmU zW4}C_8$Q=R@xs3KSes}M)(cx%cGLIS*XdIK2mUjbw{LmB2YN#OuG)bOAt9WvZUkqnncN z&dv3;qx~4#-+9-zjz1lIybCiBaJ(Sjh7HhTOn}~BH4RRM^7i}CKFF{gx(Ni#`WjIh zq4fR=F!7e&UtJ%GExW%`NVlw&++Qu(Zz)@DzYGaHwV(Ib|M9;s79VusEdF8>O#jk9 z=EaB&GY5l$DhXhOtHXp2p%4-X5HdYYh^p#3?gmyu>?DykG18Bo=QxfPc!BqdUeRB1 zqyj$+!!m)m_%`z{{}HR}21**`W+rp>t|0rvhbnLXIjRmm++Ow*G6nB zDb7z9Z{@Nmw=#zZOyKl%7H*fm*!_&3SzT9pKLgn86z*qayq^*0UPr9v{fyd-RG$^z zX!iRtnKa_=C?b$GrL~f)smXZs!*AHXbtMe zw3IrDsfQ?3OH^PLqIRj$vJ#3jc z6iBY&4!Tp0>*OuB(mtu<{+0a+Bja9fU9=A=w0}H(y}U7yXli2WL$z{<6(OSQTtk(> zDG_LV7Dy2?bt1r%9VsQR)WNH2rS|Se*zohjG4LE9v+uIl0%0&%6DC|rz4 zpyP9q0j$b#Fl_W2gU0;e{NU_V&c*pVj*$f$Uj-wNI4bN*jifGEX`dCse1(1OxRIHv zv~Ssi*&DT0)m?m?uL;F@>i=Z{jl_U}g6mJ6aYE`Fy_}uT4&>&u)c^fS$!G-wOs#-D zsI;%wgd6xu`+SgNE9^`2xgNXHzLw2R;Uho=Q|wu0z@3p>R?JF|EcvfTyGM*C~LFa%4mIZtS6u(`BKmGpA?M35~maz-) zw9E#k*=c9SD%gV_m`e1kVsvN;J@p_|peIRhU=jT&l+p8S5Q1V5eP2cogd?0tbkegm z8R*}y9CBV7fA4#F$RYZpFbImB+PbM)1o(~vzu@%1)q@v-G%?5`Tt}7 z1c}HHj<4Vc&aQ$V{(AcS9CLefejmIQmOjAG;0O7ME8J^7X6}vci9SN!UiqH!uIHDW zF=iYy2CRFmNms;}%mP{TFUO*LnWgnDzEXsDXNnIM+b`;rpJy1B&9epP(jq_0{G;f4 zO&=fKJrrH8#gZx4&S`(cR;ey;c5J%Rm0@ z@1@+iKGNHA`*LP6cdAf}xzNv@Jznmlxqv=6i8H6Uljfqkxzmxqc8_F#*8MV@r;dE)8J(PvZ`2izn@lv+>)Nn$6M*$pT6%-o;8)hJ4rzBCUP z(T8VYT+5mu4MK72o+lU9Ev(oRZm+&xsuNDaMf+{*b8J9pP~#Yl{_sMP{Yq*Y2 z=Uo?mW83yY$Vi{jrybWLP#84U2pbV?Q9p$ccNi)7_xgz->HOyFrxaK%UyDxOd*r4c zGk3$j*c*Lu_yY3g8n_8FPz)Yir)174Zy)%6>LPR-m)u95%OiUZ!XYb#`elL)OgsWz z(s1(wkuFvhRE!jzro^>Zt>-ufcLwH@OU*@nun1S;! z4|9N=2sCpVFX39yR*kO))Hmg}EfdqhlpHd+6?qJ}vZyVQ9Vr+q--6#8U?lq8PrMP6 zpDPb;U>=R$Y~EbjLN~yx#uX^MJo!}ldW#W+*We|uI}M5C)>*c7zk-{_Ybjit-{8#8 z!^`WgJTfwQ#e7u0Bm6NY1kBJoFmFE0sq7jEVEpO(#f#QYov|%jg8Obh<2(T)&bQpJ zS^;Ye99MAwNPA+IWF%KZ+_br`ifC43#VaY=>nqW;Dn!SJ!DzZb}x?B;V{Uew=8s>XTFUDDrSsII79 z5~!G9F#?OI;Q;5lRK0YU)~{T>%z}^pIlUV-N+#!|$U%Y>!!!rTT%QAHe)Y z>zCvByrueSK!5u4)c#TqxR-Vp5eUhzmjz|1emVz-cU_b(?b=&Ayy1q1MpY#b0%PwY z*Bcw44r40M-16jG+GkJRJ==Ne)%N3sf^iO=bJ93ZPC>@VSy@MA6rn-rWUi@;GgLn_ zDBF}^Q@swHr=JUsX}8#^-@2$qH7p|zn-xmK115oNDLum;o`h>ATL>-%%_ zxeK}B^lUn9=fScx%z~!WZWORb+mio3T>Y8|Zd<;p_I~z`#4Q^}+TzIG2h&eg^jZqStNt*uP4kW>@jCV9$w#p^=oZ#ZKR&4u-PHz-u+LZUCz-vPKQ;wd;r|X;AEF~={2YW}libNXR^J)y2zGh=ZiG6h zhkC7EYXr(~dViT;r}9taX&)B(rGa0+DcOdeR$4#0<@#+DT+KrM(7P)cK>JGweE(H= z(DE*OA2bFWhgEg{3C5a;A75$ApeGny>HOk=hN=y=Pkbr06u)#l!Ej6Qi$03lZ?2)$ zyXMWuw@<70vYf%JZPV&qay--cL3fabd1`sERZ4`s`rWIaV;Dc9rTX2gpOc5v3BP{# z>gQ3QvB$69Y5h!}r{YYkI%k7%&#%4sM>F(F=!NgVMtT`-*`PToS4tDp8}LR<9G!#{ z%b)7GYvoQeo8JG5dnUWux|RR3jIiA`?nZ|?mz`}{B7%i3_ktN+r_+x*Qfo1%2~ z|AGEV`u3>?ru-cRgImG&h;3DCnC4P`TL#x7{_^<|Nl$1qhmWQ%p#JgCZw7D&rak@R z$KT5Ox7CZUmG$qnLOls$bzZJ-Y3>y8U7nvIu3&r7{_V@RCVtm4g6CJh{dKtizbM=& zQ~Nvnc1ynfc|1RAg&rsArb_GIr8nUYS~|Z%L4+!7>HO+K>OQUV{3-)ipI?>juZe8G z%6_C|zwEF7-|vsckHvWtbZlvN(W4Qb$199C;&}I@0ds@vBHJCqaGa?r2gNP8crSzD zoI{ZzMw|AB8IEH)A`($jrBKx~Rj!MI-X6rZxxIaJtif;{)UHk;RrvY*#Qe+!{u#dU z^?Miq0i~BV-PE0sgEyfQm~D?dxrpV<=@X=DqJpH{`fpQGq h>A$l3%KE=FoS*}O%?=sE`eRB*igvF)9sBR&{~I0zl3D-& diff --git a/graphics/sprites/custom/Link-FEOK-LegendOfZelda.bin b/graphics/sprites/custom/Link-FEOK-LegendOfZelda.bin index 81bef75ac0613cff3118cc87ffaa26f0a10ed39a..2976fb0e6b8acf3c29e058443b85f154084766e4 100644 GIT binary patch literal 5792 zcmdT|Z){uD6+eE@^>b>+&;7H;NgUgZ(G}V?E``RaUE3N=l{QGb35iUC#ZnO|Qx{B9 zB`KKreLz};P|36jikd<`@PSWL(D;-7<*BQxPU=E5AxNnSZ;XY&Li9?r^ffW*e&;?r zaVlUE`+#<@{p@@1yZ4;)yXTyH&Q-{(gw&uKls7xJ(j!qKQbd)54j(mm*>)^Epwvr@6!aG`$X=eq^y$1sgMf7^n_zO$)Li;Nwi>ODWe;-doW5SohQ<@ z4C!LjAdRw2Kcpn8VSKwXY*x*6-syzvC}>F-4R5vFbujc6To7UMcoCm}!Rd)gAurY@<)O8M`m zcG|=8=ZwC0K2Lr=chqXGls`sa(1b!h1?a1>aC0~wexgxO<{eN$-52p0lr>`JuvxGQ z)_`?-Mo{J*$fBA~Bd#{6$(pu}_g8oM;%X4fVP*}uzm+zTi&K1Cs~+~>HLb^rtC z9#RAiC&eMZG*4Z|RT_^Bq$gCRC_!T8!tQzX9@> zsejk<*OYdkLVngD#RLDaOh4r1`ax8rkM#Go$F*+#%`;bR+Z~{jsE4VQ0%d&d^;PA@ z^>-dWH+(32(A;fg4bw0)nxp|96b*W!QyOL{dz=Sc_evWDzLGh?nQx>;11o;!)WQhSP(`7ADjq_hW!v0P;SF$boRvMHhy(fBcbe)+u^br zq-w>h^udM3qPrdF_D_#Klc;;uCI9()pgAHH#O-C#9kk)MBxQOIAQUsSE*IEs%@?|f(0nx zi;Yo@U;#D&lVavQ)+XzX;#}@(=tKv`O+Yb5dnus>Gv1Zpx<`JSUiaMRNy-Y&ZYB;Y zoe)Zv935w82aDO`GvLAaM4u+zsS#yR4LV2TR8ZwL-kYQp-Aj3f+r~dgF51owqV@3J z2eWs_{uWuF%akH2*!|!=0trCNC3}>xd^2sP2k8{=o&&RG3Oa&POwL;5wYrOM6yNK+ zlAI4Mz@ou@0T?BecL>py;`>t{QfLk*H5t)Oa`v;q3ygb$ENf-xxF`ohqweV2W! ze0!WZh^~_s0rv#{Ei|ddb93-+_iI=(0e67H97JI`8=%7|rhYzpFnb;^e+j>EP8MyS1uG1!mjp^ZY*PrZ@pH4mG9 zgT0^S*PNi*&n%$e1PD7k&NbduQkZUGbcN32=>>*$+%ygLzJB8%bpILFe}j8bvXCze zcdgbcs;iQ|f~iaN?U(r(poi$brKa751F2jBw4QD&H|*a41rxN|yT6$V|`1L z4Kh&nLDf7YCupDlY^+xw=>6iEN8U{trEwjH#URdyiI%1V>I=#VakrZnF`hR4AXip4am)=ItIl(Fv99vfDac*RG6T zpN?~URW2{lKcKRu;-(pTfAFtE0j!#$aQVIF*~g|Y zkNn-Y2HF>6@cI1Hv2(0_Xy&z?_U_;x)3@n!j63U>hIIq>>BaG7=!W=~=^(ydjBhQ| zGIAC1EtHz$>r#BX@355}t{6WI(vgaB_8)fqzuxi9@qg~%l=~~<{|3Yj&Qx7b@W`V_ z+n>6t7MtoMiTU$khvHMM0n>^UTc=|eXOB(XbS8k%gFPSk%R>G_kDR1+o_gncN>U4; zqqu{upViHYcW7GA(caNQW`G5H?4qOoeFY;e-Tf1XMng3hPyaN1;3jP(^r<_(#Dg*Mk<4<&q zS65)UJ=EoV%$HFz25mE*G=W;*G(r$N++B z6?PZ-6X!l0$t$0(`}uU|RA*r(b^-B!%=AM&1{r_|@Ph`aijO_$QvM>B@GIpnros8s z=u-Yt&YyHF1p8O0Ln){y)#325@QLsV1mGavj=Brm(Lx;tbsKT>l=*@6fpye6C6o(} zY66?l$@$BXM;~l#X=a8!Z&6-)8IuxfBoivLY`;8Elsi}63t<}!$Nb}!%kZ>7H?e{I|TeC|m5%Xjt2 zcK(R{_Y$-h`YZCc#|xh(FHYQYW?Em&AETgOph63>SC6yGwTkNDm{R1}CbXBMU*Ul} zDF^=hGY4^cd<_4q?JMz*ZNVu6BHo>4?fqK*zu^4)YwWK!pw=m|8kXQn_@u~x;lBMs zf)?626$qHEMsV`ig=^a`&TjdL`6p~WJOpdd)Gp|c4`+BM9m1iy7Q0{4AC5i(E|{iO{4MN2TC3;IIBE$S*Yrry)^ zaQydA&ijx*^E?$^M1QdX-(R(@Y;(>l4>YDq_eatOC0>W3}5$n5af;V88wS?2%z)c__w8b5SAH7%XSzqEz{EiK6;!$~P_Sk>yV6y{@* z_lmVFOwW>`W%R5L3q@|X!wgMonRG;tW)N<_>Ncw<*A4FY$&>%QR$E&lOs9~gTyxfP zge*#UGBo2gy{2yEf)aYp*UYR$(>q6@47Zo-=eNC|301B2B;*!&*wXEiTVy9mO7ZrhmW>b--#LsnK}`-}+II7`XqDIxQf9$-L4K}>}|%)OAn1W4>`4NTL}DT=<-{I2=` E4_qH>Q~&?~ literal 5792 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 I{@eWj8<+;kkN^Mx diff --git a/graphics/sprites/custom/LoneWolf-PocoLoco-FF6.bin b/graphics/sprites/custom/LoneWolf-PocoLoco-FF6.bin index 7a6b6933fe4e731b2ceadf3caa04c7b7c0754a60..1326562052a8c790aa6933ccbe7bb43c9d68697e 100644 GIT binary patch literal 5792 zcmdT|eT)=m9e!qZc6T`DZij`+0NZ7N!&U-ixI=CW7j6L*RrKmKJGMkVTu` zalMb%L>m%G!AQ)R7`Q)bC6PZa!4T3CI#JSGEVV=-05OPk`3N5LJbd1no$lPG zdZyPzoj1Gl@xC*^ujhT<-|w~XSJPH>}u|NYX%tQ*a(Sli+!MkmE#ndqkDi&g$ zE&JP1!Z1c~9{n8KjkotaYxUwST)?haEt2Ts$R&y0iLHs8vPD~FoDw&%1K0R`)H{yr zXf+p_o^#sSXKJD)SsT|?T}>rxmD!XB%89c93I3<>zIO-1b}3)-WtNa}E%pI<{~!G3 znZizG1J>g<&zGgEsH2Qy16eoYmWqR-L~Bb}jiVUg^E~}k;Q7A2!|72vunZfp z8GW&xXhNFuW5zsViEbWagoVd^|JS1x4i@l> z75~p>6f8XN`@bG(7+B}`hyEXC6jowQEBCO6VNEQF9BQFzxnwTsBy&oyaYxU% zE5+~WolErpb$)RGZMIP~N?K79i{d(TbyAG}kJn;`sxp3Y5C)8c^uYVx3+@PQml2ot zX7Dpwp669?^kRRZwV_+Nh@aZ;`SLsQkgghqR3Vj5IZ7{X)}`B43ifNS27_?zOfge5iWwoTi1CdiZC-(==g50$dyFl? zv4EhYoLDLhTAnavuYgByNPmpL)gR$ZS(u)!&t&wr@Hz{HGp8o5#?$e+@ivaRoB6;Q zbw?PzpRP*nC;Qr~v_Mx!a6NZv z+X%Ja!I|6d3LGILNbO}SU^aE8_f)=1w8WrOx+<06ox=YN{oaMFJ6ibD+UIC}iQn1B zIixX)CH4pYGx&a>LEyh6q;!=;ApJK~IN2)S9AuOJJBuN{o%dTp(NL$YP`vd$)4mHdT=yjqB5 zVma}5gE%UXd(Y!ZT6`3Dc;|%!{d2$q(MNI~g@+&u*uoLvJ%~e(Enp2-pXaZbtEU94 z(ZV}Q*XC-v&l(2hj^e&?J6odM5u$jINYru2U1hF9llFzij^%y&VLZS*tx=n#8WiI& z7Qh?k13sOF$@w2X`j+tk2i)(w2Cm{KT=5#%ALTANm&VTiY+36-i#BQ@wJvflg<7;= zjk#*dDspEJnINSour)ieL{qgT3F*v0>o?3k?7?sBQD5t5^wK8}~{8}e5prb4o> z@(SvxA9SrHk&Hl9jOisjUvxa9SK;Whfafk^6i$;$(!;sGz?o*Dp=<@yqAYAQ35 z_m~-6M7Pq~u)oj?N69lYD5O;5A-$8j-tt~&W^fTJMY`=~T`vrgU}jKPa-Ab)mNqV+ z$6HZPug}*XqYq?GBxhHO>!Vx+*U^$R`*mhX&-*@-nW5z(ML($^ZL_#7?|Z*vZ^C%)e92a-#Wn7Psq;Gxrbk?-KLfPc#3P z`cx{IWxng87Rdfe1+$~v-_A%#ob+Rl!ulbie_d7ma7eIzi0EIasvj(D#~<~xWY&J> zbr(oI7H!?duXNyB_y+H`U|$9EeKw6x1)3>M5vS1_w;njMOhKXXOCBkSB%< zJX_?^EBYXFhavjpn*U@Y^_Q9K7B&kn;1}fG`P45(Kgl;XQvdt(-hH-9_8O^v?enXU=auNEnF~bg*Xw@FCOW?+T0dGg_22t%>Tiwco${~i?=CHart z-$`Zs%w_fK*S~g9|6VKCzX!_r`68>|Pow^ovu`nZVi|G%B!27T-(&^)Fxv|39v9!T zzJ1lK;aAdJS=RgbYG-y9u=p^#s_=8U3O~`=?~q$0KEwSBxt~ypzsi;3PWk)6>>p%XY~bEs$iS@HjLaz@nuZ8+%4 z{*Zl7kSn^Y_HQPa@f+^n9ID#Cspj9P{bTd5B$3iGZhc#c|A)kCANQ|1xPMd6{iE+{ zyES{<{Uh$5ly|Q_o`1*NKa%o$lwD-uiu}3yG&E{~djhFSDJXHP^ zG7VGJ@R58>qD~H%)P0DtveaT)Qcvo}RO81US3;HoY&qp| z?sD25iY5yry@Y$(J$2*gd%))f*`}{ literal 5792 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 literal 5792 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 literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/Maduin-PocoLoco-FF6.bin b/graphics/sprites/custom/Maduin-PocoLoco-FF6.bin index da24d5805f99ed9dbd6a058388e9aaa89952a35a..e8c4fde71b046d90d6df33bd53e15f667c892df6 100644 GIT binary patch literal 5792 zcmdT|eNYtV8UO9xvb%7{-5#F`$K4W$U{E>Y%?%O6Z@ z+G*dNJKbI0dw%cpJn!@Veoq`uvj3=l-dmMc=BO{JOk@y?1;~*+uyVynL_OX_)x1Y- zXZk$BqM=QPRSe;EyeE#}gxr8bNQ-si!e>Al9T$Q%d&f)`nf!wx>+(DVw2I7vbsV}JDV?)-{?GCI(W&&XdS+|pob*~mL z?DcmjJ!HEt5X2(1Rr}Ee9V!C!{$k8<>Y1#TJBu8T=hPQ2lh@!WSmbO*5c4=WQIPxO zW_`t>wR`psZ5^sO@U9Cq_ax@1gWTb4XoE>S7UG(+K|d*%va1S9ogm8(Vzug!<1q&* zWM?ma%{B;gIhT@h)YT4pavfKhAlHK#SOp&%@Z+T7q}9duO}j7k0k426|QvQNdMjrW%}7B3=Auz-#1gKM@D*_7DBk{j@8h|D z9{7aN58^0Au|XQfVfiQ;ihN{0mXD?AXORD=Nii%RvdqBn|Aua#zsKL&XB*5_WWf4%PY!Cf{TH*iuO zq17Lf+x!PbU@3Nszfp#n7aV?1ej3Q?V>G*&$za_~Iw9!64SA!sQ~P7v8g=#nJ%Ef_ z^&Mp|D%G!1o}8DF<@tℑ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 diff --git a/graphics/sprites/custom/Mini-JamesWhite89-FF.bin b/graphics/sprites/custom/Mini-JamesWhite89-FF.bin index 3b555e3557d1584fccad8b06b2ab71767ac3d0ef..f523081c61389ff05aef3f8a2028e68eb520137c 100644 GIT binary patch literal 5792 zcmds5&2Jnv6n`_G8Si$tGb16TtwI_{MIllvv8q6V5=FiMH?$EP;MBR%1BXSuedgI(qv(?bufZ+k=H5l9j zA?MgDCdk&Q-v7cVxc`kb+OB5zpEWN45J3zqigD8nlA{Q@o;0|7e6L>0sSfQ%jpRU! zf^+QG#6RkfJ-symzfAYd%gMC--F56tz?{l546{_F0}1TH=05w-E9ATF`-&^$IT{`h z9_~GS@b$qL2gmQ-yLIo@Am4)r00)@gy$Lq~NA9BW7pDDhU=wBcFZ^H)4IK42*mys= zdt=*&Ip2tIAuBOBFx3%IVH_Ck#1TtV!xHNcex?Hcr5m7Ka5#eF-C6m&PJWDMvlKFx zv4U;%Zg+v4gp{`==! z5};A`Md$CO6j=C?*O15IQ+osOq}Ra6&~8t_68GyoZJ+-k!9}xm{y!6=@fb1Y*FO+K zfoz5eP=y8bJPb)CKR98Zv*MN2Wl$I{+=qw&oB3B~}1B6G%nbN45T%o+a??$+!- z(x1-XIW+pC=I8DoBWU!;1<9Z$s6StzzxhY>r})a*Ycu|50QOoM{I7pBZhi>8s6I?` z7Nh=ovh5nc`akg$jI>1kM`G$Z*8jp&O)0U8o9AC9zg;H!Tr;fJ1 z0_#{k@Fq=>e{}NbY=Sr-B;bLlIssjx@>Ub>V$I*b<2eb&Va3OvoNHf(^!UDZ0lpWF zLH?)vE8<7EZxJ~bKb~GR#DaD_Kic<;fM38v4b6tb1B#n{zrZV)5*kp4wWKHUiCM?wsT!%v=n_tmF&L>-Wg{9r#uk)3~iZSMUFXx3az zkyGgXYTVkt1o>@l{YCrBbSFEkG ze)jZ(7eBms_VhaT-_GJ`$dh;y|MXVp&5f;;s`lo)}kQMB~cbmM2GH*NL&#h%B^$~S0P!t>Ke`s5~T|yI0`b{ zC&)+Gk^-TW=@{_=kUs#$HlP#~J}MV?9KScS#Q!(7zpl^CY7r$-)7zvr zH7EXWP)HFHlcY@Mz;PxssT(FANr}H1Mqc9Efj;8Kr(nXNFb1P1_+#7yFaq6)BbgHURA1U*DB4l=;JD8lUBP-+8K;?k_`I9#6PUv&p4$SHk=vqXl(UU3p2c>+UaE|#QZfR|?7jJ=|(aG2r)Pk!x_HjO|9Ov{!_Y!>> zeoChyozX@k6b=oHsyg-he7|!fUGYCsO6cSEv-ZQ}S@dVPr*+;0x{l#L0}=)Q^s@pU z8c3!4c|De)#qXE1_iL8JyU)D)_cD6S=XDx79^MBSD@;xUQ|B*xA)_Q{dDZ)W0K6HD z8O~8Ul-2uR3cMMNnf~>BZ?6A38zd`%PAiXaVV&BralF+|L;yamx?>9~4@H0y5yi?VB=MptRfzSV6IQ&-EpAnp9SO0&pgpg}k zO$5z*zq5Rfpj^$qpRbv8;jZcp+zzrgaIVX1?jM$amE7QCzW>?!Rn9MG-{fy5GWKcI zr=k}=e)q?_UxsPS0hrQ&2Hw~kh_o92y#A@Oc1n0fA$9PtOZEIRH+dID{Qp&T|1B_P zdjA#O@a|im-TvXdL3~@!-^JzhYi}$DQL5|z>yMyKx+OkL9`8Tef2tSE!^YUfGI1j8 z!54I|_+|Xt%NH+S>1|e_H^9Ee=`=Y_zC}VFJ&hji@R)ay%;-aUJw6@(G=6~Z>(Q&x zi$8pNPCp|(*P|Qs!R}FVOEkzU*C58tJe<^se&5|3-z(1bsxnKAHwN#nvt&?MZ*Nqp tQb$>(K^OU7r8w%8@7xHo`327ReRERsze+eEr2-yhIhf1qr+-KPzX1uezz6^U diff --git a/graphics/sprites/custom/NarsheGuard-PocoLoco-FF6.bin b/graphics/sprites/custom/NarsheGuard-PocoLoco-FF6.bin index 34b6e0f0897f5cffe27a8056e0dd4056c2974b06..dc6632ae7ce88f5f068c9c82832db2a89ff1a164 100644 GIT binary patch literal 5792 zcmdT|ZEO_R6+JUMJG-{m-bw9}1#Iuo65$jSF9FO7jCWGC5mlqeQreWLfG1IcAZ)-1 z$T)@Aqo_itqSQYsMAMS`M^#l7YLSXklR_Aw{Gft@RS>lriZ>E!P)lJeT;t$n>A5o= zd)HBMTK{M}!!UmD&3pIboqOKB8$nE)r_RNiik14ZP%D5Csu9N=%tbxlIi$>W)*+ne zyJ4SmW!%M4uycTKL^LO(c(NyD@*)eTOTd;e~hDL{y&p8O8uYW=<>SeP(9j=4ECS{ zy=E2aIID_Y4w+_5uUOJF;--euo6AA|6bIWzna(ynaFNZM1Cu}aD(IH zIA!?quTZ|Lp=FcvRcgHganJt^)S?B;se$-kLnVd$9(ReCyyyP`YLLYeSf2k)v~%3I zzlDhV7UdtNf6t=20)L}PXUywmu-Nb!Rc*nSg*k4R+ z(z;ME3v0*Bw2_A99wMs^5ruNPBTJwbV z2DN{Pn=l7E%>vHg+bCeY=xz;RAMGB)TE{VwN@2B{q^H8PK%yr^?}Pg%7}973kTz4? zwWIXs5XP)AdMw9Y!BPrzzURMMC8;DC`$^9B{il0xphgk@6>!MdN11tan&Q8GjJ~|g zcHunY&Fj^BAI}(#6^u z{E$PT#}QH05`5?$I;QXe?o1apq3HBj1?Py^hby$VmHfO8ZbI-`OKyu36;tc${Hn@Z zKZ~cxs6*T#n;yx*N%QVRr)#=&uO)L--Is3)KJ{t%oXNlC+GVF{=cLQEi+lK+(oUbI zUCt@jj!dP76p0tPL*D=50sj*G8i)eLlv=)sLyIjE+u5)OC(WP^~0T8)~gL@i1z_fb3R`zDV*bjg}7J#Lv#T zC(;G?igA!ZlOu1EefexAo5sTx{Bjpg;n%pwx176jdb||ftagT~@NQqxx#pZEKlI>3 zYMABemnOR2N6xSt(HQqwBZHSYE|)NOg7UpcW4ni~F|yrx?Bv`w&+`uPwwyB3-VE6{ zVf%O_O6hV*Pl)e`$0K8S+G_s7?R>l@W8CqAdH|Qmv3cCYJcN{aSj0O+d`CD-a3GmW zqoR3>oLsxZ{Fr(V^NVrgK3Vl`9KZ`mId!h?N_=(Uo8-OS?BB_G3i-2zJfwLZj^aIB zC2OCfHvVO6M@m{#xbbn2iI?+l4gFktdVG?9J1U-0hLsB_M#u3dtygctV~plbyck;e z6}1~4|DMMWF>anRKeGPp`23sKt-VjFKcc=8(@E}pk~U;K6?OK+=+Svzs|Wk(|17P{ z@QlpR|6J7DL4zIxIv#yyL8<&GJzFZ@TZI2DBQ|&AjiBlEtblH&sqgFb(Fp#?^DR$C zO>hO1Sw@ze;)v&Y_Kx%YV{-5*1GXJ%3dNKs@hazSrhi1E0#C?m!dDI#CiQeXY4VrD z`<7XV<~uLMN51wNPa5!>U9`Cqi_yRqcoG}P=S)Vq%dg?ZHkX)a$rpmB&tr$#gF6fE z`yK277SKzU*FJ%7;GS}lI^K%-9~?1ivDZFmrPxeiy>ZaE*!SCgXTS5e{W8wP2EV{N zjK{~B*IctMDEIMqenqrj>Q0gFSTx zPkZ1p{=y9B`b10e%Vs<{za7D7|G&;}ySXzD8kZ<5$v`FS7)4VR0JuM;6@ZMFRsgM{ zp|(8~WZ-@du#8^tGw@dK?ua?+1{&r$yNMAr1V00xZT?-Zy|7NH_A}QnNh}UeX)pMA zh*%G_uVn08>YvhH#`l$I-%jskwRhFyIIIlow?kJv`L7V8UBm#{)y|>C++ZEXacA7V z;0gv@BIhP!tGy@=+2{e<{b=367 z+=P?16Dg;iu@F|6eNEOce*GjY?AI@O^gI0%`Lon7{lvUQ1lt@p`H22^)gRzw#U1^2 zZNaNQUcq)ei|2UuMm+sHEROfxvM-#;7{30a`86+su{Z_9PmjRaTIH z6G|~%X>J;I+t8)m;2AK60b@`N>JOKpWjj@3K}r5ECqZfcKS)jq^q2hKrq;gxe*Qn( z{V%N_hjEMOk^0r&f9?5IM6cAZK6=M_Mwix)=HB)6R708n=TduLf8YP37%21qq3QlN z8GUa)p#IhveNQ~N{@xl6qR+3tmzLmH^uIize^CGKpNziQ*S`^-L2a!57AZT>5t580 zL}qSa?;0eS%glstRn*(3chf$9rp?3GF>;ptzQFocdu%#>ChJ!pKeONe+3{9|6AA{3F_^-5jPkti*N>-HDx02tK+ULjrdUU8CFbl|d@&8$77ySJOOUS>a z@gKyG{C*Yuel!#RewyEpdKtgHl;1HS{}I$;DdjJu{QviBw)mK^U-im7D1kdvZWJ3V z{lHYof@Qgm!$0{*+kzwiEZ0(51!xgOqH)AOlYBEN)1*#dUCs0Adz`_KTsvpxmgZCW z==Gd!oB33yF_t(Uy;ohF(|_{IZ+!Ui+__xWyVkH;yQj_dC9@}by>5iZQvUQ&qJ>a&5#`b_kds;-K;iPUG3q=_BUC-vD8RxSl% zl4>yLkjmu@oD^R<4@fkUkSsA`N>NV@mevS2G&(4#XJwio zfov&bZ!ESMsv}dC$za9_(YDk&np%n{!?bZko7RXCQdymLCbfvCRlJEA?CswB#8=$oDQcWi=5;X-QbzU3@|qfmq2dX>f7&qEF7mE{KtXj zerhpCOx>9OdZnY_*#);0pZvxrCjSZ`yM`9hRjM<||IE;$_7`eT?)ep2{1tkJT4}!& zq8HS|OFPOI3Qn6_$a8w=DvTh=AencfTcqvB&NR2`p;$zZhT^mz90)R~=ABIW*DH-h zZEnbS{lf~&UjbSLt)eyRLXG7=E1f-hNo1h#nY2?6FJiL?=Q%~)M@=Q zjtu=LhK89wrGLgz>8~v)vQ^|=PTljntG<1)h24oYRP3IIMiRwCqp@duJ`SGg`>g3L z%xCN^pnIuFI%r>J%duyPPBDkar7s0r()8uPES zt;wsDx0jw;{WIWa-7UoGWwcto)#N`G8;Fi5hj)Fpt2?bVyq%S9*j z?-XW(zlfRE8WJ!P9j-bqoz_Coe@GwK;?Pb3>p@u--UU@KB-S5>KuF>c$-njl3lInm;7)3&rT59v$9j zMB}pF66PK)$vXl+D7KXd^{$rE^-4|NI`7FFV8@VQ7Dmja8b~-2-`&5A+I)xWPsDzy ze-x7`L`TitZzoMTMsI1G?H<8R9n!TI)sRbEU~&~q3{LQRcXVQGAljzxY8g@kN-gGU zuv{J3EsUAyi{ZcN=$+@GAA!K>=w&Jqid_Mr5$GGdMdB5Tg`R*O9xT~ucmEa|40d%s ztB>iUN`E*6zt_Njh)7jbg%;WU80o;y#KNH{wL&5+K=Fta>SJVIL#f% zCP}oV*@yi+`yiU6f#`|7P5P6ua(#0Pgo1sPQ$6`;EkzZ!&tjJ>z)sTEw_}vG{^|Z> z(V^IAC=%|~jaV-Q6gj8`LuxN}nQbMTa_tV2|8)!4KF{ZWA^m2eJKdj&E}&$h%Qn3y zh0acy=)!s?I#`G(3IcS(muTJrJK5O&R5J8~N&5?GlWa4P%CbKl&r@y6kEY&Jeq{Ab zMS&Du!`cd2QNz;m70Puo0R!un{m2e4wW5Y)#XEi5HxqHxu&lV9c8fPTzLUQ$f1Pr# zdO&`Hx=B_G)jV8PI2+oq()Q+UOP+~56nolg!p}rXrq-Q%k;Lr=jtb7`+9yrpO zW|Zdlefqf3Nqzc6uuE=L8Ym2%>99=p&HxVpKb-c0!zMdC4u=^!xF^*T^j!SU6Ryb* z&%HGK5A*1eO)f;2P(C6~k)7aLQ?S+S~c3 zM+5Yq9)I1ixAfEFi}80yY1g8`(iek!oTsiixo)dnhU_b0p|=UQ(^{3x_Rg$m{w?p*V;0Iz|+3nisY_kJ7NFMcR$1#TB!<&Qeb-rK!X~QQCH}13O672*x z9rWi5UcC9^#Xq4BEdubd_urFzBl=|V9npOc?pXbPcAeQb7v~&>Uov&i9giG-dCWPw z^P#RJdJS01Y2&mk^Kdq2=C_<%rsubl@`Rk8-*Rr9%5MktFQ;<^Nv{Ab;He6L5JA!_ z0E<4Afy%VhmWePI_AAl+X@tjOnFxaB-W{DrIMe()6a9Z)zxXM$e&CpyLhqymas86g zFUQXm`Vh3Y$??Y3udRIVyZs9u^Spi~>nA{qVL{i!*H)_r9XFnO_udWP`a@y+74cV& zFx`t75}*g*qf4Z}u6U&x^;1rL$@1c&>_X(M&A|#qR_2)*n3I0D^Yt>+55x7HhiXTA z`jNAj<@U*nJctaKer%9GkH59qli-=i{6|l8G1W5uzN%~q$h$y!=lMMg!vjgs~6EL2iaqK z{%;{?2{?`%RENpJKb8Nh=w9OWO~-|YSJ`u9X2 zWxpD@rpJew`=8%7UsxzFn{#b$v+d>rz@uWsI0-)BQ@!ei?AxFbW5BqpedX}1$YqJw zMtHtADIXyc9#3T2kEOem{mgv-XBJ;G_)+Hr~A)^;_p}beni~`tL@-lYVogt{%ex_ z*&qI&+f41ll3gSlmwAN0=?;G3J{>0sXN5#dP8Mo#msH~B`i$!uu66l;<;Z>dSm;o+D>xd88*!Yg5~&a$H7{}x!W=u#6DxC2 dAEnLn!n(N)Y2&?}tSs~5Y?te^RQqq^|2O+-b};|| diff --git a/graphics/sprites/custom/Nitori-HoxNorf-Touhou.bin b/graphics/sprites/custom/Nitori-HoxNorf-Touhou.bin new file mode 100644 index 0000000000000000000000000000000000000000..4b6178b1f3b62ae235d509cf19ee9a3c17816a43 GIT binary patch literal 5792 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 HfmQ!6PK#1P literal 5792 zcmdT|eQXqGdVl6)ci#1mJ+D7lk74Z{jDwwYV-L1V7BF~oIE3~{?F&(ghzXNmB^%@< zTjCg(HB+93G&qohd?IB{C#R;Z5UZ)$DfZ|cYDnr;qQC^^1D`H6LWK|l ziT@}6^Tgr49(~!o<=kFu4s&2>IjuXh@B|DoCmc~CT5qH`(xDl^K?Magh?JHF5@Z+h zZa%l1n{#B)!2(BjNqlj+U_*!<4|(#pN*zV{yd;04;O9ZU$e&CM#z}tGS$4Zo21`Rx z{vohfhV?&0t5@418F;=Q6qA;xg8~99-K14mmG7#wJPkF&=fKjTN5clhrQ9gLo2Pj6Nwt{D+}AHRDyH)V`ODytVD z3zy=9_WNKs+||*Bx)ZlALLb{DMy_zXplO?9mf_kW5xkh-K@>JXGky&%77b@0kpVj+j@6~=5%GyUNBbo}DRMy^yTA=>$t)4sn z-_K;NLj16NSpLmBJ@#2P17mp1IfGVxFoODPO0C3;@q_T=n-s+wehcuUk#mVcDxb@` zGJ01G_Gsvsf!MLhr^sO^1xvPl_9) zu%!B-g7i0(q9y8oje zj($>8_D_Bzvo(2^e^@>rH9{@dpHJsVa4>xWZo_G`@J(wO@x7q(sfnV6lfV$;Z<0@U zTaRz~Ze@JQ|JJlSnD+dytlb5*PGJ~5W$hI5KR-o}r=0}%YPXoVSG!C}zq0?Scq4kV z?0*uFc7=Q)zmyX(yN8R&Q}rm|q6||01mz~7t^TM{-piN=xMD=g`?#Hs52P1)d2eGL zFl_FQ!&5DjSJN}Q)bXy@wPVs#=wGl2qgd^NarB}0U>Zz24=ZDL^1pRox7*@W z1OX#(1y>H$*)5t{2R~|@@_oDI!3z9E$jknfoh`LuHL7X5kg~3r`)640iiyu{01GqN zOgx7tw`s))fN|mN7yl;tuqoA11c<5+DlIr6?3u&}_?PId%(UIc=VIp)Nt7Jr8AL9F z(VXC2EFw_-=+{Uw9Yj??t+Djrq6qx;#q7fJVt&>CFY*OgELy?ye*jTXj0#@__IzJg z{cRF53k^dC(S=Wyz0cJ0+`=D>)%S^Itjt9zyiJ z{D(yl9qE6QFP7vN^(}r0cr7f4>pq0k+9!wT{fE1@2Ry$3LCRlprMOFeUJnJr62+#B zHkT!xIt1;G^Uvn@?2p=h)z{-}%#KWc2%YQ)V_2P0NjcAQ&5e)7+N+I7cd(vS_#_Bp zfvm3570mLC#7`yXV#m*Q&OiRM6I1hepCl&O)Q}T3reKP6u~!wc;jiEyrPXcmKeP=) zZ}?^Pt;D%w(?fUOpkIe^sn2Y(B_REpaK3nbo6ic%-moW6|w^v)%`oFU8 zdeLX2r>jz=#9oXB&ZvI|&0{YOex`W(m)Q&B9)Bsb7w(R-_PPex(?I%<`v;*);OOYO zK>BZmTH_X9-aieer;GX*BL`!^((Rai2=%8}BmBnU?;d~ixzvm2ULg5J{`8pth013u zpY`Ogz=luXeD0sN2Y$TDpWd*3oIIO)?$69q{^Melk$oeI3b4)e85>w_(Y|<{*rgmDO5ErU*uPc_CDE$4vyWg4Hb>Rr%}F8o);t3TRu{gDqy|04fbVoPB+cKPIL|6doZ!p664egBWaervV=Z_6a@q+PeJ z|Ht&{Mp$D-Z+NZ$XHta_E?U22e?B`Wu zui5}PEOFiw>Q4Wu&Let1bSRab4I(jE(-#xBK^4DdwoG3$2d$?Me4sDEh>M&z&J9Sb z>2Q1sl9N$r-PF42`AtWay%P0*K`!aES}_w){pdypn_alEaM!);sMsN9n4{CmVs51T ztHo8Ro;8X){3j~6zS$OnN02xuB5JOg4K^qLom=Fy4-@+ynyz~Rb)VyI{2z+!8`y8J zZa*H{4UfS=^;l!;rZB{O+=r=K+^j-aL@sx5_{qd2co(bRPpye$iylt1v=I--&jYhy z%+a+X|Ni&L>)Kw)!20I(mc3a2dW#WA?7NWv@BHVv%MLMc>Ri3v&g-Ab*zc}*DE#c& z{GeV8KqCgx#;y7T;sZ77{JVi|z_se%o_`fEqmE8CU*9*o>R(tb(Z8H;@*gy|Sk{4N z7}!sI&Hgs_ZEG_Otj1rn|4_po*cpnlRvB3bZ5FaKY$`)RYC-azjs!Wdw~zDaFM^OSYTZAKf-JT?{{FP_&iN8()~>5rRpwq zk3j5skI-J-!MTR-c1WlsH|0G_G3^piJA5HwPJ+sUT<_+s^pU3|)Fp11{4Ejp^ zPs57bD0cAf;P${D)uI1l{tzp<76=7G8^n-6*iT}O?wcK&?wqd~Ci_y2o{dji^X9PK zh%pm}wfY94EMaf3THmg={P4!FoF4slX@1kfw+=vjt$vl+`wnF9H3HVg5_=aLLjg%u zSg6F#SNSdT*m;@Xdh??rZAYW8y>fI*56-V>e#Dw94e>83j9OSfKU#5j-ZMWMf-CYj z<=;E*o!j{@zg(Li`E&l9;&qUezdZR5^8W7i@}b6Odh(0@^8fg2eQf?A^F`zXf+Q&l zc6(IXGy#2*a+HF5v}Bcy@tn3fW>%^$*qkG#7$8`rAZI62R&frcFxC|5=@s^cj?)=T zA%1d3=-JC&wNh4UAkAO`zGc4~|Jb6)qTYNAdTf3f-t~Rg|6Si-!uTUQAKtSKCk=s7 z_G)lMONys)^?-K0d9eBXg`Uf^`)2pe9Dtm7^9RG1qnFivv3;=vX6|d#r9X5&qd*fF z;Vt2aK=T>!V~#_bg-sUDXFdsix%gh&6D+GQ4&>b)JH!@|AGF!iFn~Bnf+S$Wh)YD| z=rx)d(8v4ttGg?OuL_?hskC=pjaVMeAA3gQUfx` z;2TKCoUFd2zA=2c=fu?Jk14)&9FPvDdYmciW2lfA{u#Dp;7w}qeM~Gx*~Ljs+fPnT h-W~b3(Ea|=Kw(Sai=XVDlt*AYgq%M)yG8v%{vXW@c0&LF diff --git a/graphics/sprites/custom/Rubicante-Astaroth-FF4.bin b/graphics/sprites/custom/Rubicante-Astaroth-FF4.bin index eda267e3879813bb6eaf877c64816e9dbb4ab45c..90d05d7dc839b1577acf1cf1c79962d373403f32 100644 GIT binary patch literal 5792 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 T-?Uxk_HiiScg1g-E5;RcRBI)C?(th(a*b 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 literal 5792 zcmdT|TWl298UAKxygRnX-mwYsU^eyuZekJ3*&r8OFnF7g#I0yzDI{15fk_%QRz&EA zQsjbTpIj9AAr~t3ftR+cs(p!638^iLLIk6Vhcpi@50w%%2u2YhmV%NIwa`Kx)9;_X z&6)%^7b)sF+VPCfob!MG{r@M8_O#ovw);EX?JaiNPJ_dg3leDH^pOu{!tE0S#gU_C zXok@e6#wK9aCT{(TEBKcJA{2Wi;o;lcG>!%-KP!d-r!wDY*z==l)8$Yn8E`070iO= zTkcJ2!0*H8g%#7T-A z4ttoKbDAbnRJ)@)It7u|lJYp{7sh}(T{?GW$loBGgd_G-rjH9eWgh1#PobaP zMzKs_jFV#O=@{GcgW=V;*y)=aMXDyXDE8k-=8rRNGGea7x>h>P&r> zxJLWwg4aX|Q!g;xU4{+r`-!vHehWkD44xol)5!Q4A0BT-ev8mA=h1QW&5O`eWpyJ5 zLBG=IORPm3^{{(w8U0FQrO|BaZqrC%76axy=bU+jwmd4}0{66cm>4|e5xvozOjCnf z_)B3`Bs(;i8dm6}HK8U{F#`KuWI?{`+pg`NrZHuyQqr#>=ti4WmLEU_pdr}7NZ}&Z zTMNcjV~(3denj9S$Q4u$h2>^9eeiP2abpStbX!Y^ierqUHxjg)&rwvWB0nBb;4yhZ z5(&CB>fa{+&4!g|q6WHYJ&^_g-Ovp_q!X=LbId+ymZed)58w=ry?x=xl$!K={61e6 zGZ^BGqNG!j?-!rHU2RvB)R2fIPnFuec5gQ4j#5gLG5n?I2azai_DS*q4=YKR_F0c^ zr>aM1_r~SV_?xJ=KP2kW1*#q$R8k^ddCFbM{6q>Ie5}JqJ~`^}E%qRJA?`t9YB%!# zn;M0KW+Tn~u>qUxs{S;j7zjEfW{j(OB=J73aZ27C6(U8r8%WJad8T`pQffKk}U4!us{*F&tJxe0M7I}lF0z+@n+gn$pJ6lTsk*mZo z-H^LH%5De9FxKM?Ao@hQXjYNrFGdeb6L-F5rnHdk$YVf5ZL4 zy;mYmT(T;I8L+vpyQjOY)l4-m2ta zC=XhhW^n>D`TdOh3lVjR!7iha4_M~q{Hr)`TgUOCvw#!UK5GiU5OiC4!1wVO16dni zj5jVi%OtwUdajSZJ9;1Nd%x!&*(SS8)ML*+w zUSIvA#MAB$=u5?4Mu)oiE55+-KgZu4y^r+N)4#Qjz8sdpI(yib8evi%WmssX{w&rA z9D~@#{2(<#*&spt%`=8%mG>|AKlm*(^xOBns!eRTGyPZ+doiIqsqCsz(eoq5IdVRw zmVNMy>bm%H_>zYM=<=Pwda=pn`pR@?`ex3il%GFa;raRW1Oy%Wq@VVox&*t&|5F;{&%Cuc{&g)Kj z8L5Tk3$63#o$5ctsA40d_iKCfT78>>W~XBH?Vvx0^?t3s4H&)x>r}PA9rX9BV~DRS z(~GS~{uk>2N~CGV9MSzu6WZ(5{$+?Brg;sc)&3=k#AkSj{ZmPQL-a5t3r&TJ{ygM5wUyDV^e-@^ z2;waFFJtPxXjb|c#x&_(s{M=L4?14o{fFIQ`ZTBD8=-7+5(_;u!v9+M(82FsO^3~# zvYx>6{3VISP999|qjy*8{|-%ewLY){uhjp$y!+|hmHK~N`Gy*Z*Z-}W)Q+|KAIU^r z{hwDMxvtg!@N#waf4%E-v(E-s{ICV*nd8y)$pgH{>lKLWX&Qp{OC-%%=;<6ZO*l}oT7HgxDFn~C<8=U?G1djx6CN_i(> zp3AE|2L10nx7qIsl;<6u6_N1>h9hpFmHI?*f0^$~U);Yh^?hln{(Y(MOW!vB{{n%*ofQB8 diff --git a/graphics/sprites/custom/Sherlotta-HoxNorf_ScarabEnigma-FFCC.bin b/graphics/sprites/custom/Sherlotta-HoxNorf_ScarabEnigma-FFCC.bin new file mode 100644 index 0000000000000000000000000000000000000000..bce0910084e226c052b8b123e70a5d595ffd2845 GIT binary patch literal 5792 zcmdT|eQ*@#eSY>syC-S2w=zOnNvFFdc5OMRW)Ywd5Yont9pVU3V?t=KR>wx}aG4+xTn!Gx4AI{&H+#YQgPh=qa!d>4tWr|1`f};9v7JhtGxVJK!ppzs@X|Zz|>TPnzZO zL;erg2kbsPj#JJ@47D++PX?pj zq*N=~5zVW8fY8Z%oPnmt{d0)nQzQVW@ zeTP<#zVQz94^Nb%A3`bmrW*4O^iT`v(~ccXCg_z{U-0OstJJK<)~f!B5oPX|ECrRJExdAvy)%J^HZ$1BVDs}dqh{H2|gYX?jG zIebrmO0`;1h4t8McWAoI3?e9iz+b)4AOjVE#J%b(^J%UJH z|0bo+*tY7%=C8N~>(z=@H9q1hMg3hfJ>R8e3i`W@h%U@oKXG?UdWrrnGe@cZJJM++ zY3YLgz73+r?0=_!U9{<4{uAN5YfAisAItos;GbVr4m54)8cTsI^v^(<6K4RjQf}cg ze(9fsshz1el9TRCVEeA`Vl9y-$!t0oVDR(um-t-2_5JhIB{W9EPqso)l*NTUdhT9fXM^kC|7`^0Czx$8DwA@DHnaLTQ7SuM)5 z*B=`HtwzEB^V!QNiT{aUE_wc4@jq@~I5cD=80-?{uyS`M)n#Bh2n|9u=VumT|EUj0 zp7viENR#j`@0|If6`7#wPROl*c42G#2fKE69kHKXv6dRse(h41+7-2RtA$=T&3rk< z_lv1NF~7UX<6{BiDT{vgCfD$(y6}Pci_HFSgamN zWrRNPH)xJFM{A>%>Nx&#eEqHep#9}s^?)$2a&{%6?M{!=MV+$9^M9JiXX!cKq8~(| zsT!Xs^3Rwd?f+~3^YUw?a`}A2qI?p7hnxMxVF`2XPpW31>i{H6l9}o!s>D>S^4-tp_jTB2Hkvca#Ym#~k!{Va^=o)t_kJ z-g5Tg@$Pp-FPc zd;mvro6lxG;3_}FEES__d^YZMGe$?rqfd`Y@&DWMO?|_0Q|$|k>mTe&I!Li{`NN8T(3EDJbJN(S*D-JT!9{&Jhz!JY4~!~Qn(s>;RwI_z)v z2J_s#exbk3k~xF@b-usN-+!U({v_<;{R4k+|4)1sJ<>sKLu}uMu>b92|1UAdv|78$ z&f{~m;Ec0`&m`a57V&km=dWSJVst#$bS9Y)}%%qhE{Ob#Y+tECU#6C8_LC-pb7{ud3TCY_$(-wC8g8 z$2q?=lXtCvKe$5lbJl5R!8*zLVS%nSL;g15p4|LU zV>K^-xc>XwDL+3y$)Di8%lPO0W2=vJ?(H0EzAx4i)hhNtuvcRpF~q|8N%{1p1#NJC z`+UdTn_V+$d-^h=y}-;d$4qLtaMdDz%wp`Hi~Qy1Cu-~ZDQ1tt{6cs4gOd4WY5us~ zeOvh%`TL*dkK0qX-Jdby-aUVe3Wu%y{858^|Gla(f8_fAUhjmQpFdK__up3;=DGQU z_*G`FrT(9JR}@P6|A2E}idsy=&Gr8bPlx!OP!I0kGyi`5j{Mt>X8F&e*SG%aL+?1- zqU&4!)Rvj+WMU$YYNOgpyjT8xDLGn}f8B=PFaN4WssDbQ`K8oSI3x#R;+J5 zq&8WsU#0!;UadnfoqxkhS+Ca@r}I$1DAih;&Y^xo9~M4(-`>unO?m%3l(YUi+!fJk z!~9!*e)I10qow>C&c7x3cX9rGulg09U+rxum482Kng0v&N%jBhRmczj*Ym}^@-k;S zy0$=S@{4B+?ASver9%UT<9O4a=lSPyw>>(k!}#>sk9UzzyuQo$k)WvOJY|6IVI@W`vB8#lq(|Rvi7st;WW}Q0cWWsBY0ogglV=LVQ8X zYFR%sGD3uG=&-@q9cSj=b=35befA z{4d=9#`689LF)4O2Z?_Td)ez9pV9A*pN$W;J!bW@K6xF!9>jyVug&6Vyg#-wc6Yo% bFW?W!FU7x|b27aE)PZCwoM%n`T8zFy-?3}4z5GNJH_T zC0bZlG(~Zh7S&7i9E9zpC|_UX{fT(Mb9(F_=_Fm|^5JpqfTwl+pq& z-wYZG1alnHTFws#D^V3qwNF?}rg6fGNe^K4kk33HaFFKnE^1r;IV5WD} zZO&Ln#%D9~+vq)Z;gjVbCYY(xL|=Iz95jQo0{p`Yd3+SW+Y@mmH{*c;`t$k9!7VTI zbshmkLQ+i4c7x(vp;`Nuy3{|9R+=4-k9-1Ef(6qBU93`^)*b((Kd49ai{`!ZJl{oG zWMSG4$E~Fyim_vIC$wHp%V?|o^;>HT#)yg|$cm*{B!htkBwmt-)%9wtJmgDr^nt$r;48$b+36Lx->=U~Tmdnoy&^hn> zV-;%J(FUBS@D~yb{Od5+HyVO8=_)$`tpuk^Bhve*AL)K*1Yg6=!#7{M zCh#v;yBaoPJ=bn@ZuCGql18)(P#3-{xp&Dy>dYN3FshP&5Lp>mzFl9LdjU-lzlYN57Z{`eQ`I ztpR7s+=Late+aY1S78jpa28@=7H}jG(a2)ScffHJ@XQX*3Wl%&WGfKA2g(Go1X+9s zOwmiJ;6UU8U+4K3890R+BpHMUs^mJkF|&(c`y?HiCnVPb8S=?AIRC_sqtPMcw;?cf zkR-dBwSe&n+T*hW%wIxRbsy$$Al^iSO0&9iS^H5_^w6;XV%cT!{cwcLh4oXc9p)qO zuLx)0o!s5yi{rm4@P6Qh(dUGXM{z+3ewmpn`XonvoA>rXl={s_x(&U*c z%%D}&K=@?Ypu`{`pJoxT+9@1V4tRGmV$&t4RtQg6g#mC?%e zffl2|jG19B7<;QHpctB|eo01rPwpx{b)q^_R0NJ#T&gQMzYo1@@EX-&TpjMyB1Bh% z)f?)xGY<$(^T{)$DgpN{&2xKLmwHHjN#4aNz#knHDqJ-ozgEs~WAu9L(U!fj6V|I1 zzsHENWnxS|C|l1roLka8>&U&sLOBouE)CKWnnm~07}$r=F*8u?2+zx=R=XL;=maiee&a1%Jwtu*|dG{|CG`Y>}JsvRaR7ksNG4}pl z%ZS-e(Ewef)|=}>n|wXd?MN8nY`;UdPz^SNb%cN0iF#vb{Dr^)y+%d+QN0-=dYzsC zIH=FL{(w5uzl1bBlRuQ4{)M3bLAYy4YWG0!!ksVhHxl4RCVNDsgEi`Q@2g^)r(5`` zk9QFGX10DuF)5&f7_v7%bExjVqfP8QW%MU{2FdyjYPZ#s&|g84f79u{`>jonZAvvI zAs^^D>fLbjCR*#Df0_%1Z*5!J`&e(Vzo|Wgo*YF|0kpVUNY%w{ojxJ{}DRH za_WCxV02vVKW?=C|G($2j`*`jI&jXMhPqWfA+-tbh5B(ef9hx%fEe;V(EsZlX?Hn{ih1M>2{-B zGO(Xy>z8Z?A5QS~oDM#5qy4i7_#0HI-(D4q*pXFmF!G^@4y+2>Q6+^LHZzCL)8p?3 zFKl^v81*-Qg6=gTCTF`Mi-@-hRa%u?D`)#ZomYSw6$*u)*8jE8{G9%e`@iH$^`>7t zKe|@EWzLTb!3))WC(SH-le~LQN=Y&|`A?31SURcf8GYsaW;&{$4alAAv*$N& z#eZWrxApFPduI0h#&4F{^H_*2HqU0xZ$4Ik!Lr=$y#R)Mc7MB79rFHtQg``K^V`w1 zxs9LSe4D1BrQL}9KTW3VogZcM@5uSl1XG`s$-kfb{AkGg@0)sO<;ed&eG?VL75N{t zPt3RP^Yr)Q)N%f;V><08lZTSSyuU?n?eUYXB{fu9*Q@Va!LHA$?|J|CHV6aMfjtKL zKkPfaEz6Aj>f(!^FJ`}zy`tR145MHi7TRYMsZb0c+<1R*J^uc${deR1`|S9; Hp8o#|&YkS;uQjtA1S0 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 diff --git a/graphics/sprites/custom/Squall (Uniform)-SApprentice-FF8.bin b/graphics/sprites/custom/Squall (Uniform)-SApprentice-FF8.bin index fba86a6a628ebc78b8e6554e39889b2468a67c78..48e2d791177263e49aad7f24d85319f404b3ec12 100644 GIT binary patch literal 5792 zcmc&&OKcm*8UFV{?uw$gD@!(MQxew?#AzOu96zL5GItv^fDS>;Q(?45%tH$cNLndK zkO;M0%^?RJf`UgM(w0XZj6$bY1=My6{jlld2nQ-ehsB#km;*!ba~t0k*T;&QYQ&28TCzWd%k?yl1c32IVE z(E)Q3T^$z|1Gv?WH^JdFLE-Gd`LjQoyl5~F%mp2rpmX4aAcKzkfoHd0`1Qk$F8vSlbW2ih zf{PydUr~M>0jm8}w*A~i+wbFdo?)Bt#iLG}wB;tX4R=TCpOv62&?4XTbAG0xey>;b zQ7T|JYfz2EOKM4zt(CY*u(P^O3)n5vWmmQrF^EA;}ck8j|wWXK( z7Zj-7q0~Z3-y+}kGrp$iLyVF-BBdWO{bnMVehmAk>$AiA^nX0nKmAwzGYjlp0P%CpO&eLeFV*0klV&)6K_PHzrX!{5+uPAO@SU!p5BMtHMz#OUjCQc{Evfw zpT0q-p2B})AOE>b-rB=|hRhECKuSw2QiuOd5+0GLhMo(gf4mauiXD&M~cXpjp#0Y0Eypr&PjY)TFx3UGDfOjcsd7M{X;u(Tg_Hb z3%wwMexYi{CnJ=Fop|dtKYJv|G;%P5@jiqc(E6Qv!)0{h+Ul#s3+5~ z3`66{r7oU@Y@OkOemz`^TEI_)i;rLq1bD<_XQY1AnSf`pAEkr$upH^TIwdBK8|-%} zj@a)wZlronwGp_;fQaB? zhd6YS7kVMS1U>|>t7N7R+&Efw5etD1o+;8vD$^l+!yxbh=pWH{XExzwIl~#@Eo+qAx!|RC~rjE(2DxS!73v8w8s1!;D3k? zgZCo%*voygvX}cU8qZE}v%kIc!x0;MIDdFFORv(1E^$LQ8*_raAd>MnWF@o%7&3nb z7k2PI9vNJ$MPLad0{ZJA^PX}a;ECFjkfeXu+Do6Kc}l;~u0vp9SjW$@PGo9>jxvN- zoD>G|4Y3wZgfZULz$^tGYa@^Rb|6#iD-OU;dDbr=V)scaXOLi2zQXhDFX<1>FU-&Wq}CT{ zm_DI@g8HM8xAMNJ>NH*}_2eFY|M-JXfAPf+KBKq5D-+9m1-m}gDhi$vuKTr39+xs!AZc9Hql1(Qar)}jRg>SfRsD~uBtQfc5hzA1wdh(N zkJQ!vyyy2eMZ0xVu-SLs=q{LO+GtCKy_#e@ik{1a?<}FaC~JBmHv{|k*GzrXfD3v zaeQ5+2RKuJW2Ha5V01l5z4MIf;MIG#gHP8}9#FH%{{5@=&uQIySm@tjwmWE89cs!=KAL+&NS$c z7ghFXJjd0ijAJtT(krNcfQ3fb2x@Eb=7}2D&62(cO$u7+%FlK9n}w9V>VFF~=@e06 zb>-{C$+mnyKlb+c()j%FN!u=tU{Xr=! RW1G|m%NaeTKlr-w_isr0+LZtR literal 5792 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 literal 5792 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 literal 5792 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 diff --git a/graphics/sprites/custom/Ultros-PocoLoco-FF6.bin b/graphics/sprites/custom/Ultros-PocoLoco-FF6.bin index 66e061067d85063278bfd9442192e83d85b02cde..6aa818c9542766375be874135ac114e056de1649 100644 GIT binary patch literal 5792 zcmeHL-)j_C6#jN*bSLYYjN4SVnwl}D=@u&85MoxkF=MNBZ6CT6YOT_yh#-|}76cnL;E3v-udih6d(z=fP`E5sW3$Ag*f@UDI7>lp~ ziC|t$fffk#a0-X;3mn+~OQTe_?LYz~{&228b(Ziu8Go~$XtAIhDT&|RI9WbR_(P08 zX4o4Q7<&0NEF*lypB+#PdJaYWjR~B96^vUtJ%^%)e~aw@o$z-nI~YHyuTm^zjaGr* zO!ogt_~X`P#&?aQmV&I_DkoEU)NVEhIOWzYX?|2@)R64x1j z3g_U(pTtqgM=Q{Uu7!*%{lD-9DZ41(8s*1WFbvntPNm(Z!;5+`^6B18GrG}BpGA4} zSeXp{o&H>-shM9=Zv9K2XPmndYy?n*8f*h`wm7!{d28)i-wlExGFKZB__v3_TK=_{w>}P@$p8hMkRvrrzyhD z;&h0wWt_VAutkec7?Zce9RqRR1*ZCW$K&#Q?)m?8)US@vEN}nAi}J+-;)kl1 zsK4w-bfDX-zfY)s#rz-BpZD+C^&g1q&-=#{(!Uly8>A3RT81?&if=^$#75^`7O|uW zVrx~GWdWs5ukH-sII)!dv_l&y=%z)RVjr5-VzMwqyRz7u*U=7qff$NjhC$50!A;z9 zif&UI?MW+$opNRhPNaq+b`dMRMC{b@f!!>z(on2Y=h=MRN%3(IA-_cr1iudW?OV8w zl?eGQ?n3bEfZrYq#?z%ybJ83FQNX(bzl7F#;onjHAH9FsMSE^U|JTU^p?^Z^ z--e4({jZP(LVsK8KUaL&zue9Jlln;M3j3F2DWRBQ|I*WVqx`*f%?kP#H)C7ceDp6- z{oBIzeT4pFibATFmVH^s|KIgmN$S4@|85{|e4Cbif?VhKAAfnR{@bxB z70IuzRC{%PZO6%SB!8}zk5%W-T>D4TU#Dizd_ezgfSRS*`tLpUn^K?G59j*){Ht$I zwbw7fX2Xx)t(BAIk1O4cH~svp94jBI3}Q_8<98nPClk1S%=EwCpX(pc`9M{CZ`M~4 zqoMea>oSs_Lsazo@RS-uXpP zU*&9!|9JURq22Lu`)9^y9?Ir150&!l{DXNb^C#x5cu4-i_z#F*`V(H?nf~9G{%~ov z|I-~yBl}ye^BczZ`*$gdyI;R*@f#7J1ssaUC!OEO_^gFL*ZlGG!|YR{`GFF~ap&;Qsi<@r4g00bi`lida;h45X&-5T_Qvyl4 zH_-mnVAJoEgraJymeiBk2E!rVdIDe58RI4VoH(DbE&CJtRGhq1pii-A7g2_;h>6A3 tw7Rg-n1IiVKkYyS^h%?lX<%yrZ>&DJTfOvRYUcB_xMKbuj>1tI-(?B`x&SECw&^9{ zBc4k-H!{!3-o43w5RlCDP3Xrq_aK9RvFBXbMAwH#L%G7Q1<=e4e$&t2URdt>%AD+1 z_}u`)fS1imC_!17G*E5gO&gctk`O!$0xvJo`j>G13r(|K?^=EJ3Lodm;74%%-&|bl z`ofy8SNJ#&;!9%v*H~(9KLqfS#QGogjFbLTFcOQm$0HwA%)Xw0BdCA1X8<;STv`=s z4=r^p{xP?v{et+kXNnAHEmHpyfBa;^KOxSb{uREBFlGk7^dhS%O4⁣Sde#P<;^ptC954p$SIdJH~B&p;m#{ z19j+~Cz0oa4PRsZCE)A&E8|(1uR3qM&=L0W0+h+V{ zVf|l2f4%V+Wa|7^sseY@4VDE>0#x1V$Sh7VBuWj)dtGXCb;H|vpS1o&&>mz6kD!2yTA4GFrW)^4rWf=GWcm2nL+~;l}kR zjm!5r|Ezz^ep%C;e|pDhcN}=X<+aW@%9jXFbbo;PAIld;{{r2F!KqJHLnvRmce(Z^ z=6@_-FOA#9=ij;VZ@zvx-qzRR%D;1C`S;wNpK|5@eq;HI#h3Ndt-ws#aBYjHe(9x} zBwN3{l$yD)(mmy=U&2DT`&(@Nk}Ln#>W{_9^+UsTo$!24{!cT@{JUc5z=M^!Rk)Xv z|I>K!`9IB4O#M2vpXyhyf4L@{^{=7dsDAbOmutdV|0-Vp6!Gf3^*1qW86C<0C}Pfe z>u+LUh?M?!9P7_hB>xU+zac1x5Z2$OCjD;@*59v?{A1F7FN?oXFLao)=Lhd2MGoRss{eEE~aD@ye#!PrBXs z$M|yn>+`Qd`3>!VEOIK4&pr#`7wiAh{>FPw?8xyC8#3p+T;IR_zIFScGXhC7ef03r z9U1tL4C*EUlD01Jo)nH`IFKX;kq-}2P1iv;4AV+t?DXN~cfBJ36-goN5qrdpn^7xP z7TeyQ1bI0alN_JpyHuxli+knSyr?P}qlk=$v!qaUMI`iD>CNQo?kW;4g;yM+G)O@I IQ;{0@6A3J^VgLXD diff --git a/graphics/sprites/custom/Vargas-PocoLoco-FF6.bin b/graphics/sprites/custom/Vargas-PocoLoco-FF6.bin index d6197e739a67c386707eaa274827e4d3dd195203..07e23a8690b3565c1e37881c5c5c0ad8c78b89e1 100644 GIT binary patch literal 5792 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 literal 5792 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 diff --git a/graphics/sprites/custom/Vincent-FEOK-FF7.bin b/graphics/sprites/custom/Vincent-FEOK-FF7.bin index f713f982aa322f07c53da16d2c915a8510d9bd4d..816a50f81da99c745cc3e4ac9435a16feba72a58 100644 GIT binary patch literal 5792 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 diff --git a/graphics/sprites/custom/Wren-HoxNorf-PS4.bin b/graphics/sprites/custom/Wren-HoxNorf-PS4.bin new file mode 100644 index 0000000000000000000000000000000000000000..407ffda63ded9953e770005b8876f263fb94389b GIT binary patch literal 5792 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 XPhg-qt1V)DC1=mZ^3?u+um8URgJ!8r literal 0 HcmV?d00001 diff --git a/graphics/sprites/custom/X-Badass-Megaman.bin b/graphics/sprites/custom/X-Badass-Megaman.bin new file mode 100644 index 0000000000000000000000000000000000000000..72475e0d3c4aea831871a8dcbe62f9fcd7f40403 GIT binary patch literal 5792 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 Date: Sat, 8 Apr 2023 12:17:48 -0230 Subject: [PATCH 59/60] fix: Trash list update from feedback --- constants/items.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/constants/items.py b/constants/items.py index 82e67e9b..ef51fe93 100644 --- a/constants/items.py +++ b/constants/items.py @@ -299,10 +299,10 @@ ## TRASH LIST https://docs.google.com/spreadsheets/d/1Cit5Xl_TCBPFI4q1NEVQhSYGH1wKY9st_1yee3lzCP8/edit#gid=0 TRASH_WEAPONS = [ - "Dirk", "MithrilKnife", "Guardian", "MithrilBlade", "RegalCutlass", "Crystal", "Ogre Nix", "Mithril Pike", - "Stout Spear", "Gold Lance", "Partisan", "Imperial", "Kodachi", "Hardened", "Ashura", "Kotetsu", "Forged", - "Aura", "Strato", "Poison Rod", "Punisher", "Gravity Rod", "MetalKnuckle", "Mithril Claw", "Kaiser", - "Flail", "Morning Star", "Full Moon", "Boomerang", "Rising Sun", "Cards", "Darts", "Chocobo Brsh", "DaVinci Brsh" + "Dirk", "MithrilKnife", "Guardian", "MithrilBlade", "RegalCutlass", "Crystal", "Mithril Pike", + "Stout Spear", "Imperial", "Kodachi", "Hardened", "Ashura", "Kotetsu", "Forged", + "Aura", "Strato", "Punisher", "MetalKnuckle", "Mithril Claw", "Kaiser", + "Flail", "Morning Star", "Full Moon", "Boomerang", "Rising Sun", "Cards", "Darts", "Chocobo Brsh", ] TRASH_ARMOR = ["Buckler", "Mithril Shld", "Heavy Shld", "Gold Shld", "Leather Hat", "Plumed Hat", "Bandana", From 1fb6a482cc5d5938de08f84b597fca70d931eb82 Mon Sep 17 00:00:00 2001 From: asilverthorn <96998881+asilverthorn@users.noreply.github.com> Date: Sat, 8 Apr 2023 10:10:17 -0600 Subject: [PATCH 60/60] fixing duplicate -npctips arg from merging --- args/misc.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/args/misc.py b/args/misc.py index 664107a5..0c83c403 100644 --- a/args/misc.py +++ b/args/misc.py @@ -28,10 +28,6 @@ def parse(parser): misc.add_argument("-as", "--auto-sprint", action = "store_true", help = "DEPRECATED - Use `-move as` instead. Player always sprints. Sprint Shoes have no effect") - ### new option for NPC tips - misc.add_argument("-npctips", "--npc-dialog-tips", action = "store_true", - help = "NPC provide general game tips") - event_timers = misc.add_mutually_exclusive_group() event_timers.add_argument("-etr", "--event-timers-random", action = "store_true", help = "Collapsing House, Opera House, and Floating Continent timers randomized")