Files
Archipelago/worlds/earthbound/modules/hint_data.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

366 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from ..game_data.local_data import item_id_table, character_item_table, party_id_nums
from ..game_data.text_data import text_encoder
from ..game_data.static_location_data import location_groups
from ..modules.shopsanity import shop_locations
from ..Options import ShopRandomizer, MagicantMode
import struct
from BaseClasses import Location
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .. import EarthBoundWorld
from ..Rom import LocalRom
def setup_hints(world: "EarthBoundWorld") -> None:
hint_types = [
# gives a hint for a specific out of the way location in this player's world, regardless of what item it is
"item_at_location",
"region_progression_check", # woth or foolish hint, checks specific location groups of this world so as to be more helpful.
"hint_for_good_item", # gives the exact location and sender of a good item for the local player
"item_in_local_region", # Hints a random item that can be found in a specific local location group
"prog_item_at_region", # Hints the region that a good item can be found for this player
"joke_hint", # Doesn't hint anything
"dungeon_location" # Hints what dungeon can be found at a specific entrance
]
world.in_game_hint_types = []
world.hinted_locations = {}
world.hinted_items = {}
world.hinted_regions = {}
world.hinted_dungeons = {}
# may not need to be world.
world.local_hintable_locations = [
"Onett - Mayor Pirkle",
"Onett - South Road Present",
"Onett - Meteor Item",
"Onett - Treehouse Guy",
"Twoson - Orange Kid Donation",
"Twoson - Everdred Meeting",
"Twoson - Apple Kid Invention",
"Fourside - Post-Moonside Delivery",
"Lost Underworld - Talking Rock",
"Dungeon Man - 2F Hole Present",
"Poo - Starting Item",
"Summers - Magic Cake",
"Deep Darkness - Teleporting Monkey",
"Twoson - Insignificant Location",
"Peaceful Rest Valley - North Side Present",
"Twoson - Paula's Mother",
"Happy-Happy Village - Prisoner",
"Threed - Boogey Tent Trashcan",
"Threed - Zombie Prisoner",
"Grapefruit Falls - Saturn Cave Present",
"Saturn Valley - Saturn Coffee",
"Dusty Dunes - South Side Present",
"Stonehenge - Tony Item",
"Stonehenge - Kidnapped Mr. Saturn",
"Stonehenge - Dead End Present",
"Stonehenge - Near End of the Maze Present",
"Stonehenge - Bridge Room East Balcony Present",
"Gold Mine - B1F Isolated Present",
"Fourside - Venus Gift",
"Fourside - Bakery 2F Gift",
"Fourside - Department Store Blackout",
"Monotoli Building - Monotoli Character",
"Dungeon Man - 1F Exit Ledge Present",
"Deep Darkness - Barf Character",
"Lumine Hall - B1F West Alcove Present",
"Cave of the Present - Star Master",
"Cave of the Present - Broken Phase Distorter",
"Fire Spring - 1st Cave Present",
"Tenda Village - Tenda Tea",
"Deep Darkness - Barf Character",
"Dalaam - Trial of Mu",
"Pyramid - Northwest Door Sarcophagus"
]
world.local_hintable_items = [
"Franklin Badge",
"Key to the Shack",
"Key to the Cabin",
"Key to the Tower",
"Key to the Locker",
"Bad Key Machine",
"Pencil Eraser",
"Eraser Eraser",
"UFO Engine",
"Yogurt Dispenser",
"Zombie Paper",
"King Banana",
"Signed Banana",
"Tendakraut",
"Jar of Fly Honey",
"Wad of Bills",
"Tiny Ruby",
"Diamond",
"Meteorite Piece",
"Hieroglyph Copy",
"Piggy Nose",
"Carrot Key",
"Police Badge",
"Letter For Tony",
"Mining Permit",
"Contact Lens",
"Insignificant Item",
"Pak of Bubble Gum",
"Sea Pendant",
"Shyness Book",
"Hawk Eye",
"Ness",
"Paula",
"Jeff",
"Poo",
"Onett Teleport",
"Twoson Teleport",
"Happy-Happy Village Teleport",
"Threed Teleport",
"Saturn Valley Teleport",
"Dusty Dunes Teleport",
"Fourside Teleport",
"Winters Teleport",
"Summers Teleport",
"Scaraba Teleport",
"Dalaam Teleport",
"Deep Darkness Teleport",
"Tenda Village Teleport",
"Lost Underworld Teleport"
]
world.joke_hints = [
"you can find 6 hint shops around the world.",
"if you want to hint for an item, use !hint in your text client!",
"the mouse at the Fourside Department Store knows what the Spook has.",
"my business may be a scam.",
"there's a guy near Threed's hotel who saw the Zombies take someone away.",
"you can use the Y button to run or mash through text.",
"you can submit custom window flavors in the game's Archipelago thread.",
"you can buy a ruler at the Fourside Department Store.",
"you can store a lot of items in your backpack with the R button.",
"Giygas's guard may actually be from another seed...",
"if you have multiple of something important, you can throw away the extras.",
"the chicken crossed the road because it was trying to use Teleport α.",
"Figment's Ice Palace can probably be found in Ice Palace.",
"you can talk to the hint shop owners for a hint.",
"deliveryman are bad at using keys.",
"PK scramble is a pretty good time.",
"Apple Kid researched the Power of the Earth with Dr. Andonuts.",
"you can find beta Archipelago games on the Archipelago discord.",
"you can randomize EarthBound with Archipelago.",
"hint prices double with each one bought.",
"you probably should have kept your money.",
"there's a secret option to plando Lumine Hall's text.",
"this isn't a very good hint.",
"some hints are good hints.\n@But not this one."
]
hintable_location_groups = location_groups.copy()
if world.options.shop_randomizer != ShopRandomizer.option_shopsanity:
hintable_location_groups["Onett"] = hintable_location_groups["Onett"] - shop_locations
hintable_location_groups["Twoson"] = hintable_location_groups["Twoson"] - shop_locations
hintable_location_groups["Happy-Happy Village"] = hintable_location_groups["Happy-Happy Village"] - shop_locations
hintable_location_groups["Threed"] = hintable_location_groups["Threed"] - shop_locations
hintable_location_groups["Grapefruit Falls"] = hintable_location_groups["Grapefruit Falls"] - shop_locations
hintable_location_groups["Saturn Valley"] = hintable_location_groups["Saturn Valley"] - shop_locations
hintable_location_groups["Dusty Dunes Desert"] = hintable_location_groups["Dusty Dunes Desert"] - shop_locations
hintable_location_groups["Winters"] = hintable_location_groups["Winters"] - shop_locations
hintable_location_groups["Dr. Andonuts's Lab"] = hintable_location_groups["Dr. Andonuts's Lab"] - shop_locations
hintable_location_groups["Fourside"] = hintable_location_groups["Fourside"] - shop_locations
hintable_location_groups["Moonside"] = hintable_location_groups["Moonside"] - shop_locations
hintable_location_groups["Summers"] = hintable_location_groups["Summers"] - shop_locations
hintable_location_groups["Dalaam"] = hintable_location_groups["Dalaam"] - shop_locations
hintable_location_groups["Scaraba"] = hintable_location_groups["Scaraba"] - shop_locations
hintable_location_groups["Deep Darkness"] = hintable_location_groups["Deep Darkness"] - shop_locations
hintable_location_groups["Lost Underworld"] = hintable_location_groups["Lost Underworld"] - shop_locations
hintable_location_groups["Magicant"] = hintable_location_groups["Magicant"] - shop_locations
del hintable_location_groups["Burglin Park"]
del hintable_location_groups["the Scaraba Bazaar"]
del hintable_location_groups["the Twoson Department Store"]
del hintable_location_groups["the Fourside Department Store"]
del hintable_location_groups["the Saturn Valley Shop"]
if world.options.magicant_mode >= 2:
del hintable_location_groups["Magicant"]
if not world.options.giygas_required:
del hintable_location_groups["Cave of the Past"]
if world.options.magicant_mode.value in [0, 3]:
world.local_hintable_items.append("Magicant Teleport")
for item in world.multiworld.precollected_items[world.player]:
if item.name in world.local_hintable_items:
world.local_hintable_items.remove(item.name)
for item in world.options.start_hints.value:
if item in world.local_hintable_items:
world.local_hintable_items.remove(item)
if world.starting_area_teleport in world.local_hintable_items:
world.local_hintable_items.remove(world.starting_area_teleport)
if world.local_hintable_items == []:
hint_types.remove("hint_for_good_item")
hint_types.remove("prog_item_at_region")
if not world.options.dungeon_shuffle:
hint_types.remove("dungeon_location")
if world.options.giygas_required:
world.local_hintable_locations.append("Cave of the Past - Present")
if world.options.magicant_mode == MagicantMode.option_psi_location:
world.local_hintable_locations.append("Magicant - Ness's Nightmare")
for i in range(6):
world.in_game_hint_types.append(world.random.choice(hint_types))
for index, hint in enumerate(world.in_game_hint_types):
if hint == "item_at_location":
location = world.random.choice(world.local_hintable_locations)
world.hinted_locations[index] = location
elif hint == "region_progression_check":
group, group_locs = world.random.choice(list(hintable_location_groups.items()))
world.hinted_regions[index] = group
elif hint == "hint_for_good_item" or hint == "prog_item_at_region":
item = world.random.choice(world.local_hintable_items)
world.hinted_items[index] = item
elif hint == "item_in_local_region":
group, group_locs = world.random.choice(list(hintable_location_groups.items()))
location = world.random.choice(sorted(group_locs))
world.hinted_regions[index] = group
world.hinted_locations[index] = location
elif hint == "dungeon_location":
dungeon = world.random.choice(list(world.dungeon_connections.keys()))
world.hinted_dungeons[index] = dungeon
def parse_hint_data(world: "EarthBoundWorld", location: Location, rom: "LocalRom", hint: str, index: int) -> None:
if hint == "item_at_location":
if world.player == location.item.player and location.item.name in character_item_table and location.item.name != "Photograph":
player_text = "your friend "
item_text = bytearray([0x1C, 0x02, party_id_nums[location.item.name]]) # In-game text command to display party member names
elif world.player == location.item.player:
player_text = "your "
if location.item.name in item_id_table:
item_text = bytearray([0x1C, 0x05, item_id_table[location.item.name]]) # In-game text command to display item names
else:
# if the item doesn't have a name (e.g it's PSI)
item_text = text_encoder(location.item.name, 128)
else:
player_text = f"{world.multiworld.get_player_name(location.item.player)}'s "
item_text = text_encoder(location.item.name, 128)
player_text = text_encoder(player_text, 255)
location_text = text_encoder(f" can be found at\n@{location.name}.", 255)
text = player_text + item_text + location_text
# [player]'s [item] can be found at [location].
text.append(0x02)
elif hint == "region_progression_check":
if world.progression_count == 1:
text = f"you can find {world.progression_count} important item at {world.hinted_area}."
else:
text = f"you can find {world.progression_count} important items at {world.hinted_area}."
text = text_encoder(text, 255)
text.append(0x02)
elif hint == "hint_for_good_item" or hint == "prog_item_at_region" or hint == "item_in_local_region":
if location.item.name in character_item_table and location.item.player == world.player and location.item.name != "Photograph":
item_text = text_encoder("your friend ", 255)
item_text.extend([0x1C, 0x02, party_id_nums[location.item.name]])
elif location.item.name in item_id_table and location.item.player == world.player:
item_text = text_encoder("your ", 255)
item_text.extend([0x1C, 0x05, item_id_table[location.item.name]])
elif location.item.player == world.player:
item_text = text_encoder(f"your {location.item.name}", 128)
else:
item_text = f"{world.multiworld.get_player_name(location.item.player)}'s {location.item.name}"
item_text = text_encoder(item_text, 255)
item_text.extend(text_encoder(" can be found ", 255))
if location.player != world.player:
player_text = text_encoder(f"by {world.multiworld.get_player_name(location.player)}\n", 255)
else:
player_text = text_encoder("\n", 255)
if hint == "hint_for_good_item":
location_text = text_encoder(f"@at {location.name}.", 255)
# your [item] can be found by [player] at [location]
else:
location_name_groups = world.multiworld.worlds[location.player].location_name_groups
possible_location_groups = [
group_name for group_name, group_locations in location_name_groups.items()
if location.name in group_locations and group_name != "Everywhere"
]
if not possible_location_groups:
if location.parent_region.name == "Menu":
area = ""
else:
area = f" near {location.parent_region.name}"
else:
area = f" near {world.random.choice(possible_location_groups)}"
location_text = text_encoder(f"@somewhere{area}.", 255)
# your [item] can be found by [player] somewhere near [location group]
text = item_text + player_text + location_text
text.append(0x02)
elif hint == "joke_hint":
text = world.random.choice(world.joke_hints)
text = text_encoder(text, 255)
text.append(0x02)
elif hint == "dungeon_location":
dungeon = world.hinted_dungeons[index]
text = f"{dungeon} leads to {world.dungeon_connections[dungeon]}."
text = text_encoder(text, 255)
text.append(0x02)
else:
text = 0x00
hint_addresses = [
0x070376,
0x0703A8,
0x0703DA,
0x07040C,
0x07043E,
0x070470
]
scoutable_hint_addresses = [
0x2EF3D5,
0x2EF3EB,
0x2EF401,
0x2EF417,
0x2EF42D,
0x2EF443
]
rom.write_bytes(0x310000 + world.hint_pointer, text)
rom.write_bytes(hint_addresses[world.hint_number], struct.pack("I", 0xF10000 + world.hint_pointer))
if hint in ["item_at_location", "hint_for_good_item"]:
rom.write_bytes(scoutable_hint_addresses[world.hint_number], struct.pack("I", 0xEEF451))
rom.write_bytes(0x310252 + (world.hint_number * 3), bytearray([0x01]))
world.hint_man_hints.append((location.address, location.player))
else:
rom.write_bytes(scoutable_hint_addresses[world.hint_number], struct.pack("I", 0xEEF4B2))
world.hint_man_hints.append(("NULL", 0))
world.hint_pointer = world.hint_pointer + len(text)
world.hint_number += 1
# Word on the street is that PLAYER's ITEM can be found at LOCATION
# Word on the street is that REGION has X important items
# Word on the street is that your ITEM can be found by PLAYER at LOCATION
# Word on the street is that PLAYER's ITEM can be found somewhere near REGION...
# Word on the street is that your ITEM can be found somewhere near REGION...
# char item hint?
# That's all for today.
# Like text part 1, extend 0x1C 0x05 0xItem Item, extend (the rest of the string)