Files
dockipelago/worlds/earthbound/modules/enemizer/randomize_enemy_attributes.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

144 lines
5.9 KiB
Python

import struct
from .enemy_attributes import (enemy_species, enemy_adjectives, battle_sprites, field_sprites, excluded_enemies,
insects, robots, movement_patterns, start_texts, death_texts, weakness_table)
from ...game_data.text_data import calc_pixel_width, text_encoder
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ... import EarthBoundWorld
from ...Rom import LocalRom
shield_statuses = [
"phys_1",
"phys_2",
"psi_1",
"psi_2"
]
battle_songs = [
0x60,
0x61,
0x62,
0x63,
0x64,
0x65,
0x66,
0x67,
0x68,
0x69,
0x8D,
0x94
]
def randomize_enemy_attributes(world: "EarthBoundWorld", rom: "LocalRom") -> None:
"""Randomizes various attributes of enemies. This includes the name,
gender, sprite, color, etc. Data can be found in enemy_attributes."""
taken_names = []
for enemy in world.enemies:
if enemy not in excluded_enemies and " (" not in enemy:
new_name = "FFFFFFFFFFFFFFFFFFFFFFFFFF"
pixel_width = calc_pixel_width(new_name)
species = "Null"
while not (len(new_name) <= 25 and new_name not in taken_names and pixel_width <= 95):
species = world.random.choice(enemy_species)
adjective = world.random.choice(enemy_adjectives)
new_name = adjective + species
pixel_width = calc_pixel_width(new_name)
taken_names.append(new_name)
sprite = world.random.choice(battle_sprites[species])
field_sprite = field_sprites[sprite]
world.enemy_sprites[enemy] = field_sprite
movement_pattern = movement_patterns[field_sprite]
palette = world.random.randint(1, 31)
gender = world.random.randint(1, 3)
if species in robots:
enemy_type = 2
elif species in insects:
enemy_type = 1
else:
enemy_type = 0
row = world.random.randint(0, 1)
mirror_chance = world.random.randint(0, 100)
start_text = world.random.choice(start_texts)
death_text = world.random.choice(death_texts)
if species in ["Power Robot", "Reactor Robot", "Sphere"]:
death_action = 0x0040
elif species == "Oak":
death_action = 0x0041
else:
death_action = 0x0000
music = world.random.choice(battle_songs)
drop_rate = world.random.randint(0, 7)
base_drop = world.random.choice(world.filler_drops)
if world.random.randint(1, 100) < 6:
status = world.random.randint(1, 7)
if status < 5:
world.enemies[enemy].has_shield = shield_statuses[status - 1]
else:
status = 0
if species in ["Party Man", "Reveler"]:
miss_rate = 6
elif species == "Boy":
miss_rate = 8
elif species == "Bot":
miss_rate = 5
else:
miss_rate = world.random.randint(0, 4)
fire_weakness = get_weakness("Fire", species)
freeze_weakness = get_weakness("Freeze", species)
flash_weakness = get_weakness("Flash", species)
paralysis_weakness = get_weakness("Paralysis", species)
hypnosis_weakness = get_weakness("Hypnosis", species)
address = world.enemies[enemy].address
new_name = text_encoder(new_name, 0x18)
if len(new_name) < 0x18:
new_name.extend([0x00])
if world.enemies[enemy].attack_extensions > 1:
num_enemies = world.enemies[enemy].attack_extensions
else:
num_enemies = 1
for i in range(num_enemies):
rom.write_bytes(address, bytearray([0x01]))
rom.write_bytes(address + 1, new_name)
rom.write_bytes(address + 0x1A, bytearray([gender]))
rom.write_bytes(address + 0x1B, bytearray([enemy_type]))
rom.write_bytes(address + 0x1C, struct.pack("H", sprite))
rom.write_bytes(address + 0x1E, struct.pack("H", field_sprite))
rom.write_bytes(address + 0x2B, struct.pack("H", movement_pattern))
rom.write_bytes(address + 0x2D, struct.pack("I", start_text))
rom.write_bytes(address + 0x31, struct.pack("I", death_text))
rom.write_bytes(address + 0x35, bytearray([palette]))
rom.write_bytes(address + 0x37, bytearray([music]))
rom.write_bytes(address + 0x3F, bytearray([fire_weakness]))
rom.write_bytes(address + 0x40, bytearray([freeze_weakness]))
rom.write_bytes(address + 0x41, bytearray([flash_weakness]))
rom.write_bytes(address + 0x42, bytearray([paralysis_weakness]))
rom.write_bytes(address + 0x43, bytearray([hypnosis_weakness]))
rom.write_bytes(address + 0x43, bytearray([miss_rate]))
rom.write_bytes(address + 0x4E, struct.pack("H", death_action))
rom.write_bytes(address + 0x57, bytearray([drop_rate]))
rom.write_bytes(address + 0x58, bytearray([base_drop]))
rom.write_bytes(address + 0x59, bytearray([status]))
rom.write_bytes(address + 0x5B, bytearray([row]))
rom.write_bytes(address + 0x5D, bytearray([mirror_chance]))
if world.enemies[enemy].attack_extensions > 1:
address = world.enemies[f"{enemy} ({i + 2})"].address
enemy = f"{enemy} ({i + 2})"
def get_weakness(element: str, species: str) -> int:
"""Returns a weakness to given element, given the enemy's base species."""
if species in weakness_table[element]:
weakness = weakness_table[element][species]
else:
weakness = 1
return weakness