Files
dockipelago/worlds/earthbound/modules/equipamizer.py
PinkSwitch 55c70a5ba8 EarthBound: Implement New Game (#5159)
* Add the world

* doc update

* docs

* Fix Blast/Missile not clearing Reflect

* Update worlds/earthbound/__init__.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/earthbound/__init__.py

remove unused import

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/earthbound/__init__.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/earthbound/modules/dungeon_er.py

make bool optional

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/earthbound/modules/boss_shuffle.py

typing update

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/earthbound/modules/boss_shuffle.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Filter events out of item name to id

* we call it a glorp

* Update worlds/earthbound/Regions.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/earthbound/__init__.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/earthbound/Items.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/earthbound/Regions.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Fix missing optional import

* hint stuff

* -Fix Apple Kid text being wrong
-Fix Slimy Pile text being wrong

* -Fix some sprite corruption if PSI was used when an enemy loaded another enemy
-Fixed a visible artifact tile during some cutscenes

* Update ver

* Update docs

* Fix some money scripting issues

* Add argument to PSI fakeout attack

* Updated monkey caves shop description

* Remove closing markdown from doc

* Add new flavors

* Make flavors actually work

* Update platforms

* Fix common gear getting duplicated

* Split region initialization

* Condense checks for start inventory + some other junk

* Fix some item groups - change receiver phone to warp pad

* wow that one was really bad :glorp:

* blah

* Fix cutoff option text

* switch start inventory concatenation to itertools

* Fix sky runner scripting bug - added some new comm suggestions

* Fix crash when generating with spoiler_only

* Fix happy-happy teleport not unlocking after beating carpainter

* Hint man hints can now use CreateHint packets to create hints in other games

* Adjust some filler rarity

* Update world to use CreateHints and deprecate old method

* Fix epilogue skip being offset

* Rearrange a couple regions

* Fix tendapants getting deleted in battle

* update doc

* i got scared and forgot i had multiple none checks and am worried about this triggering but tested and it works

* Fix mostly typing errors from silvris

* More type checks

* More typing

* Typema

* Type

* Fix enemy levels overwriting music

* Fix gihugic blunder

* Fix Lumine Hall enabling OSS

* del world

* Rel 4.2.7

* Remove some debug logs

* Fix vanilla bug with weird ambush detection

* Fix Starman Junior having an unscaled Freeze

* Change shop scaling

* Fix shops using the wrong thankful script

* Update some bosses in boss shuffle

* Loc group adjustment

* Update some boss shuffle stuff | Fix Enemizer attacks getting overwritten by Shuffle data | Fix flunkies not updating and still being used with enemizer

* Get rid of some debug stuff

* Get boss shuffle running, dont merge

* Fix json and get boss shuffle no plando back up

* Fix Magicant Boost not initializing to Ness if party count = 4

* Fix belch shop using wrong logic

* Don't re-send goal status

* EBitem

* remove :

* idk if this is whatvi wanted

* All client messagesnow only send when relevant instead of constantly

* Patch up the rest of boss plando

* Fix Giygas being not excluded from enemizer

* Fix epilogue again

* adjust the sphere scaling name

* add the things

* Fix Ness being placed onto monotoli when monotoli was in sea of eden

* Fix prefill properly

* Fix boss shuffle on vanilla slots.

* rename this, apparently

* Update archipelago.json

---------

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2025-12-19 14:52:27 +01:00

995 lines
31 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from dataclasses import dataclass
import copy
from ..game_data.text_data import text_encoder, calc_pixel_width
from ..Options import Armorizer, Weaponizer
from operator import attrgetter
import struct
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .. import EarthBoundWorld
from ..Rom import LocalRom
@dataclass
class EBArmor:
address: int
equip_type: str
defense: int = 0
aux_stat: int = 0
poo_def: int = 0
flash_res: int = 0
freeze_res: int = 0
fire_res: int = 0
par_res: int = 0
sleep_res: int = 0
name: str = "None"
can_equip: str = "All"
total_resistance = 0
double_price_item: str = "None"
@dataclass
class EBWeapon:
can_equip: str
equip_type: str
address: int
name: str = "None"
offense: int = 0
aux_stat: int = 0
poo_off: int = 0
miss_rate: int = 0
double_price_item: str = "None"
def roll_resistances(world: "EarthBoundWorld", element: str, armor: EBArmor) -> None:
chance = world.random.randint(0, 100)
if chance < world.options.armorizer_resistance_chance.value:
setattr(armor, element, world.random.randint(1, 3))
else:
setattr(armor, element, 0)
def price_weapons(world: "EarthBoundWorld", weapons: list[EBWeapon], rom: "LocalRom") -> None:
for index, weapon in enumerate(weapons):
if weapon.can_equip == "Poo":
price = 10 * weapon.poo_off
else:
price = 10 * weapon.offense
if price > 300:
price = price * 2
price += (20 * weapon.aux_stat)
price -= (50 * weapon.miss_rate)
price += world.random.randint(-20, 20)
price = max(5, price)
rom.write_bytes((weapon.address + 26), struct.pack("H", price))
if weapon.double_price_item in summers_addresses:
price = min(0xFFFF, price * 2)
rom.write_bytes((summers_addresses[weapon.double_price_item] + 26), struct.pack("H", price))
def price_armors(world: "EarthBoundWorld", armor_pricing_list: list[EBArmor], rom: "LocalRom") -> None:
for index, armor in enumerate(armor_pricing_list):
if armor.can_equip == "Poo":
price = 10 * armor.poo_def
else:
price = 10 * armor.defense
if price > 300:
price = price * 2
price += (20 * armor.aux_stat)
price += (50 * armor.fire_res)
price += (50 * armor.freeze_res)
price += (50 * armor.flash_res)
price += (50 * armor.par_res)
price += world.random.randint(-20, 20)
price = max(5, price)
rom.write_bytes((armor.address + 26), struct.pack("H", price))
if armor.double_price_item in summers_addresses:
price = min(0xFFFF, price * 2)
rom.write_bytes((summers_addresses[armor.double_price_item] + 26), struct.pack("H", price))
def apply_progressive_weapons(world: "EarthBoundWorld", weapons: list[str], progressives: list[str], rom: "LocalRom") -> None:
for index, item in enumerate(weapons):
weapon = world.weapon_list[item]
weapon.offense = progressives[index].offense
rom.write_bytes(weapon.address + 31, bytearray([weapon.offense]))
def apply_progressive_armor(world: "EarthBoundWorld", armors: list[str], progressives: list[str], rom: "LocalRom") -> None:
for index, item in enumerate(armors):
armor = world.armor_list[item]
armor.defense = progressives[index].defense
rom.write_bytes(armor.address + 31, bytearray([armor.defense]))
adjectives = [
"Hard",
"Wild",
"Boring",
"Lavish",
"Grouchy",
"Elastic",
"Unsightly",
"Long",
"Wide",
"Cheap",
"Copper",
"Silver",
"Gold",
"Platinum",
"Diamond",
"Jade",
"Ruby",
"Sapphire",
"Pearl",
"Dull",
"Cold",
"Fair",
"Awful",
"Bad",
"Dry",
"Wet",
"Shiny",
"Damp",
"Elite",
"Beefy",
"Better",
"Alright",
"Okay",
"Metal",
"Pixie's",
"Cherub's",
"Demon's",
"Goddess",
"Sprite's",
"Fairy's",
"Devil's",
"Best",
"Spiteful",
"Travel",
"Great",
"Crystal",
"Baseball",
"Holmes",
"Red",
"Talisman",
"Defense",
"Mr. Saturn",
"Slumber",
"Lucky",
"Shiny",
"Souvenir",
"Silence",
"Ultimate",
"Charm",
"Saturn",
"Tenda",
"Sturdy",
"Sleek",
"Green",
"Blue",
"White",
"Yellow",
"Azure",
"Emerald",
"Handmade",
"Hank's",
"Real",
"Peace",
"Magic",
"Protect",
"Brass",
"Cursed",
"Rabbit's",
"Odd",
"Cheese",
"Casual",
"Silk",
"Gutsy",
"Hyper",
"Crusher",
"Thick",
"Deluxe",
"Chef's",
"Cracked",
"Plastic",
"Cotton",
"Mr. Baseball",
"Razor",
"Gilded",
"Master",
"Fighter's",
"Worn",
"Magicant",
"Happy",
"Well-Done",
"Rare",
"Gnarly",
"Wicked",
"Bionic",
"Combat",
"Tee ball",
"Sand lot",
"Minor league",
"Big league",
"Hall of fame",
"Famous",
"Legendary",
"Casey",
"French",
"Holy",
"Pop",
"Zip",
"Gaia",
"Baddest",
"Death",
"Spectrum",
"Laser",
"Moon",
"Toy",
"Magnum",
"Stun",
"Trick",
"Dirty",
"Washed",
"Laundered",
"Fresh",
"New",
"Old",
"Alien",
"T-rex's",
"Double",
"Non-stick",
"Football",
"Tennis",
"Golf",
"Hockey",
"Burnt",
"Boiled"
]
char_nums = {
"Ness": 0x01,
"Paula": 0x02,
"Jeff": 0x03,
"Poo": 0x04
}
usage_bytes = {
"All": 0x0F,
"Ness": 0x01,
"Paula": 0x02,
"Jeff": 0x04,
"Poo": 0x08
}
type_bytes = {
"body": 0x14,
"arm": 0x18,
"other": 0x1C,
"Bash": 0x10,
"Shoot": 0x11
}
summers_addresses = {
"Platinum Band": 0x155A5C,
"Diamond Band": 0x155A83,
"Big League Bat": 0x15535A
}
royal_names = [
"of kings",
"of dukes",
"of princes",
"of barons",
"of lords",
"of sultans",
"of counts",
"of England"
]
def randomize_armor(world: "EarthBoundWorld", rom: "LocalRom") -> None:
if world.options.equipamizer_cap_stats:
armor_caps = {
"body": 30,
"arm": 80,
"other": 110
}
else:
armor_caps = {
"body": 127,
"arm": 127,
"other": 127
}
other_adjectives = adjectives.copy()
arm_adjectives = adjectives.copy()
body_adjectives = adjectives.copy()
taken_names = []
armor_dict = {
"arm": arm_adjectives,
"body": body_adjectives,
"other": other_adjectives
}
equalized_names = [
"Mild",
"Earth",
"Sea"
]
ult_names = [
"Day",
"Sun",
"Star"
]
elemental_names = {
"Flash": [
"Dark",
"Cloud",
"Night"
],
"Freeze": [
"Puddle",
"Drizzle",
"Rain"
],
"Fire": [
"Smoke",
"Ember",
"Flame"
]
}
plain_elemental_names = [
"Dark",
"Cloud",
"Night",
"Puddle",
"Drizzle",
"Rain",
"Smoke",
"Ember",
"Flame",
"Mild",
"Earth",
"Sea",
"Day",
"Sun",
"Star"
]
all_armor = [
"Travel Charm",
"Great Charm",
"Crystal Charm",
"Rabbit's Foot",
"Flame Pendant",
"Rain Pendant",
"Night Pendant",
"Sea Pendant",
"Star Pendant",
"Cloak of Kings",
"Cheap Bracelet",
"Copper Bracelet",
"Silver Bracelet",
"Gold Bracelet",
"Platinum Band",
"Diamond Band",
"Pixie's Bracelet",
"Cherub's Band",
"Goddess Band",
"Bracer of Kings",
"Baseball Cap",
"Mr. Baseball Cap",
"Holmes Hat",
"Hard Hat",
"Coin of Slumber",
"Coin of Defense",
"Coin of Silence",
"Mr. Saturn Coin",
"Charm Coin",
"Lucky Coin",
"Talisman Coin",
"Shiny Coin",
"Souvenir Coin",
"Diadem of Kings",
"Earth Pendant",
"Saturn Ribbon",
"Ribbon",
"Red Ribbon",
"Defense Ribbon",
"Talisman Ribbon",
"Goddess Ribbon",
]
char_armor_names = {
"Ness": {
"body": "tee",
"arm": "mitt",
"other": "pack"
},
"Paula": {
"body": "dress",
"arm": "ring",
"other": "ribbon"
},
"Jeff": {
"body": "tie",
"arm": "watch",
"other": "glasses"
},
}
aux_stat = {
"arm": "Luck",
"body": "Speed",
"other": "Luck"
}
armor_names = {
"body": ["pendant", "charm", "foot", "brooch", "shirt",
"amulet", "cloak", "suit", "plate", "vest", "coat", "jersey", "poncho"],
"arm": ["bracelet", "band", "bracer", "gauntlet", "sleeve", "glove", "bangle", "armlet", "sweatband"],
"other": ["cap", "hat", "coin", "crown", "diadem", "helmet", "mask", "wig", "pants", "jeans", "greaves", "boot"]
}
res_strength = [
", just a little bit",
" somewhat",
""
]
progressive_bracelets = [
]
progressive_others = [
]
world.armor_list = {
"Travel Charm": EBArmor(0x15583A, "body"),
"Great Charm": EBArmor(0x155861, "body"),
"Crystal Charm": EBArmor(0x155888, "body"),
"Rabbit's Foot": EBArmor(0x1558AF, "body"),
"Flame Pendant": EBArmor(0x1558D6, "body"),
"Rain Pendant": EBArmor(0x1558FD, "body"),
"Night Pendant": EBArmor(0x155924, "body"),
"Sea Pendant": EBArmor(0x15594B, "body"),
"Star Pendant": EBArmor(0x155972, "body"),
"Cloak of Kings": EBArmor(0x155999, "body"),
"Cheap Bracelet": EBArmor(0x1559C0, "arm"),
"Copper Bracelet": EBArmor(0x1559E7, "arm"),
"Silver Bracelet": EBArmor(0x155A0E, "arm"),
"Gold Bracelet": EBArmor(0x155A35, "arm"),
"Platinum Band": EBArmor(0x1570E8, "arm"),
"Diamond Band": EBArmor(0x15710F, "arm"),
"Pixie's Bracelet": EBArmor(0x155AAA, "arm"),
"Cherub's Band": EBArmor(0x155AD1, "arm"),
"Goddess Band": EBArmor(0x155AF8, "arm"),
"Bracer of Kings": EBArmor(0x155B1F, "arm"),
"Baseball Cap": EBArmor(0x155B46, "other"),
"Mr. Baseball Cap": EBArmor(0x155B94, "other"),
"Holmes Hat": EBArmor(0x155B6D, "other"),
"Hard Hat": EBArmor(0x155BBB, "other"),
"Ribbon": EBArmor(0x155BE2, "other"),
"Red Ribbon": EBArmor(0x155C09, "other"),
"Goddess Ribbon": EBArmor(0x155C30, "other"),
"Coin of Slumber": EBArmor(0x155C57, "other"),
"Coin of Defense": EBArmor(0x155C7E, "other"),
"Coin of Silence": EBArmor(0x1571AB, "other"),
"Mr. Saturn Coin": EBArmor(0x1575EF, "other"),
"Lucky Coin": EBArmor(0x155CA5, "other"),
"Charm Coin": EBArmor(0x1571D2, "other"),
"Talisman Coin": EBArmor(0x155CCC, "other"),
"Shiny Coin": EBArmor(0x155CF3, "other"),
"Souvenir Coin": EBArmor(0x155D1A, "other"),
"Diadem of Kings": EBArmor(0x155D41, "other"),
"Earth Pendant": EBArmor(0x156D8E, "body"),
"Defense Ribbon": EBArmor(0x157136, "other"),
"Talisman Ribbon": EBArmor(0x15715D, "other"),
"Saturn Ribbon": EBArmor(0x157184, "other"),
}
for item in all_armor:
armor = world.armor_list[item]
if world.options.armorizer == Armorizer.option_chaos:
armor.equip_type = world.random.choice(["arm", "body", "other"])
if armor.equip_type == "arm":
progressive_bracelets.append(item)
elif armor.equip_type == "other" and armor.can_equip == "All":
progressive_others.append(item)
if item in summers_addresses:
armor.double_price_item = item
armor.defense = world.random.randint(1, armor_caps[armor.equip_type])
chance = world.random.randint(0, 100)
if chance < 8:
armor.aux_stat = world.random.randint(1, 127)
else:
armor.aux_stat = 0
if armor.equip_type != "arm":
roll_resistances(world, "flash_res", armor)
roll_resistances(world, "freeze_res", armor)
roll_resistances(world, "fire_res", armor)
roll_resistances(world, "par_res", armor)
armor.sleep_res = 0
else:
armor.flash_res = 0
armor.freeze_res = 0
armor.fire_res = 0
armor.par_res = 0
# Only Arm gear can have sleep resistance; arm gear cannot have elemental resistance
roll_resistances(world, "sleep_res", armor)
if armor.flash_res + armor.freeze_res + armor.fire_res == 0:
# If no resistances are active use a normal name
front_name = world.random.choice(armor_dict[armor.equip_type])
armor_dict[armor.equip_type].remove(front_name)
elif armor.flash_res == armor.freeze_res == armor.fire_res:
# Get a combined name for the level
front_name = equalized_names[armor.flash_res - 1]
elif armor.par_res == armor.flash_res == armor.freeze_res == armor.fire_res:
# Should be used if Paralysis + the others all succeed
front_name = ult_names[armor.flash_res - 1]
else:
# If resistances are inequal, use the strongest as the name and pull a name from its strength
# If 2 are equal pick a random of them
names = ("Flash", "Freeze", "Fire")
strengths = (armor.flash_res, armor.freeze_res, armor.fire_res)
best_elements = [(name, strength) for name, strength in zip(names, strengths) if strength == max(strengths)]
best_name, best_strength = world.random.choice(best_elements)
front_name = elemental_names[best_name][best_strength - 1]
chance = world.random.randint(0, 100)
if chance < 10:
armor.can_equip = world.random.choice(["Ness", "Paula", "Jeff", "Poo"])
else:
armor.can_equip = "All"
if armor.can_equip == "Poo":
back_name = world.random.choice(royal_names)
front_name = world.random.choice(armor_names[armor.equip_type]).capitalize()
elif armor.can_equip in ["Ness", "Paula", "Jeff"]:
back_name = char_armor_names[armor.can_equip][armor.equip_type]
else:
back_name = world.random.choice(armor_names[armor.equip_type])
armor.name = front_name + " " + back_name
if armor.name in taken_names:
front_name = world.random.choice(armor_dict[armor.equip_type])
armor_dict[armor.equip_type].remove(front_name)
armor.name = front_name + " " + back_name
pixel_length = calc_pixel_width(armor.name)
first_armor = False
if armor.can_equip == "Poo":
names_to_try = royal_names.copy()
else:
names_to_try = armor_names[armor.equip_type].copy()
while pixel_length > 70 or armor.name in taken_names:
# First we replace any spaces with half-width spaces, a common tech used in vanilla to fix long names
if first_armor is False:
armor.name = armor.name.replace(" ", "")
first_armor = True
else:
if names_to_try and front_name not in plain_elemental_names:
# If it's still too long, change the second part of the name to try and roll a shorter name
back_name = world.random.choice(names_to_try)
names_to_try.remove(back_name)
else:
# If it's *STILL* too long, chop a letter off the end of the front
front_name = front_name[:-1]
if front_name == "":
# we ran out of letters rip
front_name = "Long"
first_armor = False
armor.name = front_name + " " + back_name
pixel_length = calc_pixel_width(armor.name)
taken_names.append(armor.name)
armor.total_resistance = (1 * armor.fire_res) + (4 * armor.freeze_res) + (16 * armor.flash_res) + (64 * armor.par_res)
rom.write_bytes(armor.address + 28, bytearray([usage_bytes[armor.can_equip]]))
rom.write_bytes(armor.address + 25, bytearray([type_bytes[armor.equip_type]]))
sortable_armor = copy.deepcopy(world.armor_list)
sorted_armor = sorted(sortable_armor.values(), key=attrgetter("defense"))
sorted_arm_gear = [armor for armor in sorted_armor if armor.equip_type == "arm"]
sorted_body_gear = [armor for armor in sorted_armor if armor.equip_type == "body"]
sorted_other_gear = [armor for armor in sorted_armor if armor.equip_type == "other"]
sorts = [
sorted_arm_gear,
sorted_other_gear,
sorted_body_gear,
]
prog_armors = [
progressive_bracelets,
progressive_others
]
if world.options.progressive_armor:
for i in range(2):
apply_progressive_armor(world, prog_armors[i], sorts[i], rom)
for i in range(3):
price_armors(world, sorts[i], rom)
for item in all_armor:
armor = world.armor_list[item]
if armor.can_equip != "Poo":
armor.poo_def = 216 # defense is signed, all non-kings equipment has this value
else:
armor.poo_def = armor.defense
rom.write_bytes(armor.address + 31, bytearray([armor.defense, armor.poo_def, armor.aux_stat, armor.total_resistance]))
item_name = text_encoder(armor.name, 25)
item_name.extend([0x00])
description = f"{armor.name}\n"
if armor.can_equip != "All":
description += f"@♪'s {armor.equip_type} equipment.\n"
else:
if armor.equip_type == "other":
description += "@Must be equipped as “other”.\n"
else:
description += f"@Must be equipped on your {armor.equip_type}.\n"
if armor.can_equip == "Poo":
description += f"@+{armor.poo_def} Defense.\n"
else:
description += f"@+{armor.defense} Defense.\n"
if armor.aux_stat > 0:
description += f"@+{armor.aux_stat} {aux_stat[armor.equip_type]}. \n"
if armor.flash_res > 0:
description += f"@Protects against Flash attacks{res_strength[armor.flash_res - 1]}.\n"
if armor.freeze_res > 0:
description += f"@Protects against Freeze attacks{res_strength[armor.freeze_res - 1]}.\n"
if armor.fire_res > 0:
description += f"@Protects against Fire attacks{res_strength[armor.fire_res - 1]}.\n"
if armor.par_res > 0:
description += f"@Protects against Paralysis{res_strength[armor.par_res - 1]}.\n"
if armor.sleep_res > 0:
description += f"@Protects against Sleep{res_strength[armor.sleep_res - 1]}.\n"
description = text_encoder(description, 0x100)
description = description[:-2]
description.extend([0x13, 0x02])
if armor.can_equip != "All":
index = description.index(0xAC)
description[index:index + 1] = bytearray([0x1C, 0x02, char_nums[armor.can_equip]])
rom.write_bytes(armor.address, item_name)
rom.write_bytes((0x310000 + world.description_pointer), description)
rom.write_bytes((armor.address + 35), struct.pack("I", (0xF10000 + world.description_pointer)))
if item in ["Platinum Band", "Diamond Band"]:
rom.write_bytes(summers_addresses[item] + 28, bytearray([usage_bytes[armor.can_equip]]))
rom.write_bytes(summers_addresses[item] + 31, bytearray([armor.defense, armor.poo_def, armor.aux_stat, armor.total_resistance]))
rom.write_bytes(summers_addresses[item] + 25, bytearray([type_bytes[armor.equip_type]]))
rom.write_bytes(summers_addresses[item], item_name)
rom.write_bytes((summers_addresses[item] + 35), struct.pack("I", (0xF10000 + world.description_pointer)))
world.description_pointer += len(description)
def randomize_weapons(world: "EarthBoundWorld", rom: "LocalRom") -> None:
if world.options.equipamizer_cap_stats:
weapon_cap = 120
else:
weapon_cap = 127
weapon_names = {
"Ness": ["bat", "stick", "club", "board", "racket", "cue", "pole", "paddle"],
"Paula": ["fry pan", "frypan", "skillet", "whisk", "saucepan", "pin"],
"Jeff": ["gun", "beam", "air gun", "beam gun", "cannon", "blaster", "pistol", "revolver", "shotgun", "rifle"],
"Poo": ["Sword", "Katana", "Knife", "Scissor", "Cutter", "Blade", "Chisel", "Saw", "Axe", "Scalpel", "Sabre"],
"All": ["yo-yo", "slingshot", "boomerang", "chakram", "bow"]
}
taken_names = []
miss_rates = {
"Ness": 1,
"Paula": 1,
"Jeff": 0,
"Poo": 0,
"All": 3
}
progressive_bats = [
]
progressive_pans = [
]
progressive_guns = [
]
progressive_alls = [
]
starting_weapons = {
"Ness": "Tee Ball Bat",
"Paula": "Fry Pan",
"Jeff": "Pop Gun",
"Poo": "None"
}
starting_weapon = starting_weapons[world.starting_character]
world.weapon_list = {
"Cracked Bat": EBWeapon("Ness", "Bash", 0x155297),
"Tee Ball Bat": EBWeapon("Ness", "Bash", 0x1552BE),
"Sand Lot Bat": EBWeapon("Ness", "Bash", 0x1552E5),
"Minor League Bat": EBWeapon("Ness", "Bash", 0x15530C),
"Mr. Baseball Bat": EBWeapon("Ness", "Bash", 0x155333),
"Big League Bat": EBWeapon("Ness", "Bash", 0x157073),
"Hall of Fame Bat": EBWeapon("Ness", "Bash", 0x155381),
"Magicant Bat": EBWeapon("Ness", "Bash", 0x1553A8),
"Legendary Bat": EBWeapon("Ness", "Bash", 0x1553CF),
"Gutsy Bat": EBWeapon("Ness", "Bash", 0x1553F6),
"Casey Bat": EBWeapon("Ness", "Bash", 0x15541D),
"Fry Pan": EBWeapon("Paula", "Bash", 0x155444),
"Thick Fry Pan": EBWeapon("Paula", "Bash", 0x15546B),
"Deluxe Fry Pan": EBWeapon("Paula", "Bash", 0x155492),
"Chef's Fry Pan": EBWeapon("Paula", "Bash", 0x1554B9),
"French Fry Pan": EBWeapon("Paula", "Bash", 0x1554E0),
"Magic Fry Pan": EBWeapon("Paula", "Bash", 0x155507),
"Holy Fry Pan": EBWeapon("Paula", "Bash", 0x15552E),
"Sword of Kings": EBWeapon("Poo", "Bash", 0x155555),
"Pop Gun": EBWeapon("Jeff", "Shoot", 0x15557C),
"Stun Gun": EBWeapon("Jeff", "Shoot", 0x1555A3),
"Toy Air Gun": EBWeapon("Jeff", "Shoot", 0x1555CA),
"Magnum Air Gun": EBWeapon("Jeff", "Shoot", 0x1555F1),
"Zip Gun": EBWeapon("Jeff", "Shoot", 0x155618),
"Laser Gun": EBWeapon("Jeff", "Shoot", 0x15563F),
"Hyper Beam": EBWeapon("Jeff", "Shoot", 0x155666),
"Crusher Beam": EBWeapon("Jeff", "Shoot", 0x15568D),
"Spectrum Beam": EBWeapon("Jeff", "Shoot", 0x1556B4),
"Death Ray": EBWeapon("Jeff", "Shoot", 0x1556DB),
"Baddest Beam": EBWeapon("Jeff", "Shoot", 0x155702),
"Moon Beam Gun": EBWeapon("Jeff", "Shoot", 0x155729),
"Gaia Beam": EBWeapon("Jeff", "Shoot", 0x155750),
"Yo-yo": EBWeapon("All", "Shoot", 0x155777),
"Slingshot": EBWeapon("All", "Shoot", 0x15579E),
"Bionic Slingshot": EBWeapon("All", "Shoot", 0x1557C5),
"Trick Yo-yo": EBWeapon("All", "Shoot", 0x1557EC),
"Combat Yo-yo": EBWeapon("All", "Shoot", 0x155813),
"T-Rex's Bat": EBWeapon("Ness", "Bash", 0x15704C),
"Ultimate Bat": EBWeapon("Ness", "Bash", 0x15709A),
"Double Beam": EBWeapon("Jeff", "Shoot", 0x1570C1),
"Non-stick Frypan": EBWeapon("Paula", "Bash", 0x1575C8)
}
all_weapons = [
"Cracked Bat",
"Tee Ball Bat",
"Sand Lot Bat",
"Minor League Bat",
"Mr. Baseball Bat",
"T-Rex's Bat",
"Big League Bat",
"Hall of Fame Bat",
"Ultimate Bat",
"Casey Bat",
"Magicant Bat",
"Legendary Bat",
"Gutsy Bat",
"Fry Pan",
"Thick Fry Pan",
"Deluxe Fry Pan",
"Chef's Fry Pan",
"Non-stick Frypan",
"French Fry Pan",
"Holy Fry Pan",
"Magic Fry Pan",
"Sword of Kings",
"Pop Gun",
"Stun Gun",
"Toy Air Gun",
"Magnum Air Gun",
"Zip Gun",
"Laser Gun",
"Hyper Beam",
"Double Beam",
"Crusher Beam",
"Spectrum Beam",
"Death Ray",
"Baddest Beam",
"Moon Beam Gun",
"Gaia Beam",
"Yo-yo",
"Slingshot",
"Bionic Slingshot",
"Trick Yo-yo",
"Combat Yo-yo"
]
for item in all_weapons:
weapon = world.weapon_list[item]
if world.options.weaponizer == Weaponizer.option_chaos:
chance = world.random.randint(1, 100)
if chance < 8:
weapon.can_equip = "All"
else:
weapon.can_equip = world.random.choice(["Ness", "Paula", "Jeff", "Poo"])
if item == starting_weapon:
weapon.can_equip = world.starting_character
if item in summers_addresses:
weapon.double_price_item = item
if weapon.can_equip == "Ness":
progressive_bats.append(item)
elif weapon.can_equip == "Paula":
progressive_pans.append(item)
elif weapon.can_equip == "Jeff":
progressive_guns.append(item)
if item == starting_weapon and not world.options.progressive_weapons: # Todo; remove not progressive weapons
weapon.offense = 10
else:
if world.options.progressive_weapons:
weapon.offense = world.random.randint(10, weapon_cap)
else:
weapon.offense = world.random.randint(1, weapon_cap)
if weapon.can_equip == "Poo":
front_name = world.random.choice(weapon_names[weapon.can_equip])
back_name = world.random.choice(royal_names)
else:
front_name = world.random.choice(adjectives)
back_name = world.random.choice(weapon_names[weapon.can_equip])
chance = world.random.randint(0, 100)
if chance < 8:
weapon.aux_stat = world.random.randint(1, 127)
else:
weapon.aux_stat = 0
if weapon.can_equip in ["Jeff", "All"]:
weapon.equip_type = "Shoot"
else:
weapon.equip_type = "Bash"
chance = world.random.randint(1, 100)
if chance < 4 and item != starting_weapon:
weapon.miss_rate = 12
else:
weapon.miss_rate = miss_rates[weapon.can_equip]
weapon.name = front_name + " " + back_name
pixel_length = calc_pixel_width(weapon.name)
half_space = False
if weapon.can_equip == "Poo":
names_to_try = royal_names.copy()
else:
names_to_try = weapon_names[weapon.can_equip].copy()
while pixel_length > 70 or weapon.name in taken_names:
# First we replace any spaces with half-width spaces, a common tech used in vanilla to fix long names
if half_space is False:
weapon.name = weapon.name.replace(" ", "")
half_space = True
else:
if names_to_try:
# If it's still too long, change the second part of the name to try and roll a shorter name
back_name = world.random.choice(names_to_try)
names_to_try.remove(back_name)
else:
# If it's *STILL* too long, chop a letter off the end of the front
front_name = front_name[:-1]
if front_name == "":
# we ran out of letters rip
front_name = "Long"
half_space = False
weapon.name = front_name + " " + back_name
pixel_length = calc_pixel_width(weapon.name)
rom.write_bytes(weapon.address + 28, bytearray([usage_bytes[weapon.can_equip]]))
rom.write_bytes(weapon.address + 25, bytearray([type_bytes[weapon.equip_type]]))
taken_names.append(weapon.name)
sortable_weapons = copy.deepcopy(world.weapon_list)
sorted_weapons = sorted(sortable_weapons.values(), key=attrgetter("offense"))
sorted_bats = [weapon for weapon in sorted_weapons if weapon.can_equip == "Ness"]
sorted_pans = [weapon for weapon in sorted_weapons if weapon.can_equip == "Paula"]
sorted_guns = [weapon for weapon in sorted_weapons if weapon.can_equip == "Jeff"]
sorted_swords = [weapon for weapon in sorted_weapons if weapon.can_equip == "Poo"]
sorted_alls = [weapon for weapon in sorted_weapons if weapon.can_equip == "All"]
sorts = [
sorted_bats,
sorted_pans,
sorted_guns,
sorted_alls,
sorted_swords
]
prog_weapons = [
progressive_bats,
progressive_pans,
progressive_guns,
progressive_alls
]
for i in range(5):
price_weapons(world, sorts[i], rom)
if world.options.progressive_weapons:
for i in range(4):
apply_progressive_weapons(world, prog_weapons[i], sorts[i], rom)
for item in all_weapons:
weapon = world.weapon_list[item]
if weapon.can_equip == "Poo":
weapon.poo_off = weapon.offense
else:
weapon.poo_off = 250
rom.write_bytes(weapon.address + 31, bytearray([
weapon.offense, weapon.poo_off, weapon.aux_stat, weapon.miss_rate]))
item_name = text_encoder(weapon.name, 25)
item_name.extend([0x00])
description = f"{weapon.name}\n"
if weapon.can_equip != "All":
description += f"@♪ can equip this weapon.\n"
description += f"@+{weapon.offense} Offense.\n"
if weapon.aux_stat > 0:
description += f"@+{weapon.aux_stat} Guts.\n"
if weapon.miss_rate == 12:
description += "@If you use this, you might just whiff.\n"
description = text_encoder(description, 0x100)
description = description[:-2]
description.extend([0x13, 0x02])
if weapon.can_equip != "All":
index = description.index(0xAC)
description[index:index + 1] = bytearray([0x1C, 0x02, char_nums[weapon.can_equip]])
rom.write_bytes(weapon.address, item_name)
rom.write_bytes((0x310000 + world.description_pointer), description)
rom.write_bytes((weapon.address + 35), struct.pack("I", (0xF10000 + world.description_pointer)))
if item == "Big League Bat":
rom.write_bytes(summers_addresses[item] + 28, bytearray([usage_bytes[weapon.can_equip]]))
rom.write_bytes(summers_addresses[item] + 31, bytearray([weapon.offense, weapon.poo_off, weapon.aux_stat, weapon.miss_rate]))
rom.write_bytes(summers_addresses[item] + 25, bytearray([type_bytes[weapon.equip_type]]))
rom.write_bytes(summers_addresses[item], item_name)
rom.write_bytes((summers_addresses[item] + 35), struct.pack("I", (0xF10000 + world.description_pointer)))
world.description_pointer += len(description)