mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
The Messenger: implement new game (#1494)
* initial commit of messenger integration * setup no_logic and needed slot_data * fix some typos and determinism * make all of it deterministic * add documentation * swapped to non local items so change the fed data * ~~deathlink~~ * satisfy the docs test * update doc test to show expected name * split custom classes into a separate file and fix an errant rule * make access dependency test give more useful errors * implement tests * remove some unneccessary back entrances and make names clearer * fix some big dumbs * successful unit tests are good also some slight reorganizing * add astral tea quest line, and potentially power seals as items * if TYPE_CHECKING... aahhhhhh * oop forgot to remove legacy code * having the seed and leaves as actual items doesn't seem to do anything so remove them. locations still work though * update setup guide with some changes * Tower HQ was creating duplicate locations * allow self locking items * cleanup * move self_locking_items function to core * docstring * implement choice of notes needed for music box * test the default value * don't create any starting inventory items * make item creation faster * change default accessibility and power seals options * improve documentation * precollected_items is a dict of Items... * implement shop chest goal * tests * always assign total and required seals * add new goals and set music box as requiring shop chest on shop chest goals instead of just setting it as the completion * fix dumb test quirk * implement music box skip as an option * world rewrite/cleanup * default to apworld and add game to readme * revert bleeding commits from other PRs * more bleeds * fix some errors in options docstrings * ??? * make my set rules method not have an awful name * test cleanup * add a test for item accessibility * fix issues with tests * make the self locking item behavior work correctly * misc cleanup * more general cleanup to be a good example * quick rules rewrite * more general cleanup and typing * more speed, more clean * bump data version * make sure the locked item belongs to current player * fix bad name and indent. call MessengerItem directly for events * add poptracker pack to docs * doc cleanup and "known issues" section that I probably won't be able to fix any time soon. * missed some spots * add another bug i forgot about * be consistently wrong
This commit is contained in:
125
worlds/messenger/__init__.py
Normal file
125
worlds/messenger/__init__.py
Normal file
@@ -0,0 +1,125 @@
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from BaseClasses import Tutorial, ItemClassification
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Constants import NOTES, PROG_ITEMS, PHOBEKINS, USEFUL_ITEMS, ALWAYS_LOCATIONS, SEALS, ALL_ITEMS
|
||||
from .Options import messenger_options, NotesNeeded, Goal, PowerSeals
|
||||
from .Regions import REGIONS, REGION_CONNECTIONS
|
||||
from .Rules import MessengerRules
|
||||
from .SubClasses import MessengerRegion, MessengerItem
|
||||
|
||||
|
||||
class MessengerWeb(WebWorld):
|
||||
theme = "ocean"
|
||||
|
||||
bug_report_page = "https://github.com/minous27/TheMessengerRandomizerMod/issues"
|
||||
|
||||
tut_en = Tutorial(
|
||||
"Multiworld Setup Tutorial",
|
||||
"A guide to setting up The Messenger randomizer on your computer.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["alwaysintreble"]
|
||||
)
|
||||
|
||||
tutorials = [tut_en]
|
||||
|
||||
|
||||
class MessengerWorld(World):
|
||||
"""
|
||||
As a demon army besieges his village, a young ninja ventures through a cursed world, to deliver a scroll paramount
|
||||
to his clan’s survival. What begins as a classic action platformer soon unravels into an expansive time-traveling
|
||||
adventure full of thrills, surprises, and humor.
|
||||
"""
|
||||
game = "The Messenger"
|
||||
|
||||
item_name_groups = {
|
||||
"Notes": set(NOTES),
|
||||
"Keys": set(NOTES),
|
||||
"Crest": {"Sun Crest", "Moon Crest"},
|
||||
"Phobe": set(PHOBEKINS),
|
||||
"Phobekin": set(PHOBEKINS),
|
||||
"Shuriken": {"Windmill Shuriken"},
|
||||
}
|
||||
|
||||
option_definitions = messenger_options
|
||||
|
||||
base_offset = 0xADD_000
|
||||
item_name_to_id = {item: item_id
|
||||
for item_id, item in enumerate(ALL_ITEMS, base_offset)}
|
||||
location_name_to_id = {location: location_id
|
||||
for location_id, location in enumerate([*ALWAYS_LOCATIONS, *SEALS], base_offset)}
|
||||
|
||||
data_version = 1
|
||||
|
||||
web = MessengerWeb()
|
||||
|
||||
total_seals: Optional[int] = None
|
||||
required_seals: Optional[int] = None
|
||||
|
||||
def generate_early(self) -> None:
|
||||
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
||||
self.multiworld.shuffle_seals[self.player].value = PowerSeals.option_true
|
||||
self.total_seals = self.multiworld.total_seals[self.player].value
|
||||
self.required_seals = int(self.multiworld.percent_seals_required[self.player].value / 100 * self.total_seals)
|
||||
|
||||
def create_regions(self) -> None:
|
||||
for region in [MessengerRegion(reg_name, self) for reg_name in REGIONS]:
|
||||
if region.name in REGION_CONNECTIONS:
|
||||
region.add_exits(REGION_CONNECTIONS[region.name])
|
||||
|
||||
def create_items(self) -> None:
|
||||
itempool: List[MessengerItem] = []
|
||||
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
||||
seals = [self.create_item("Power Seal") for _ in range(self.total_seals)]
|
||||
for i in range(self.required_seals):
|
||||
seals[i].classification = ItemClassification.progression_skip_balancing
|
||||
itempool += seals
|
||||
else:
|
||||
notes = self.multiworld.random.sample(NOTES, k=len(NOTES))
|
||||
precollected_notes_amount = NotesNeeded.range_end - self.multiworld.notes_needed[self.player]
|
||||
if precollected_notes_amount:
|
||||
for note in notes[:precollected_notes_amount]:
|
||||
self.multiworld.push_precollected(self.create_item(note))
|
||||
itempool += [self.create_item(note) for note in notes[precollected_notes_amount:]]
|
||||
|
||||
itempool += [self.create_item(item)
|
||||
for item in self.item_name_to_id
|
||||
if item not in
|
||||
{
|
||||
"Power Seal", "Time Shard", *NOTES,
|
||||
*{collected_item.name for collected_item in self.multiworld.precollected_items[self.player]}
|
||||
# this is a set and currently won't create items for anything that appears in here at all
|
||||
# if we get in a position where this can have duplicates of items that aren't Power Seals
|
||||
# or Time shards, this will need to be redone.
|
||||
}]
|
||||
itempool += [self.create_filler()
|
||||
for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool))]
|
||||
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self) -> None:
|
||||
MessengerRules(self).set_messenger_rules()
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
locations: Dict[int, List[str]] = {}
|
||||
for loc in self.multiworld.get_filled_locations(self.player):
|
||||
if loc.item.code:
|
||||
locations[loc.address] = [loc.item.name, self.multiworld.player_name[loc.item.player]]
|
||||
|
||||
return {
|
||||
"deathlink": self.multiworld.death_link[self.player].value,
|
||||
"goal": self.multiworld.goal[self.player].current_key,
|
||||
"music_box": self.multiworld.music_box[self.player].value,
|
||||
"required_seals": self.required_seals,
|
||||
"locations": locations,
|
||||
"settings": {"Difficulty": "Basic" if not self.multiworld.shuffle_seals[self.player] else "Advanced"}
|
||||
}
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "Time Shard"
|
||||
|
||||
def create_item(self, name: str) -> MessengerItem:
|
||||
item_id: Optional[int] = self.item_name_to_id.get(name, None)
|
||||
return MessengerItem(name, self.player, item_id)
|
Reference in New Issue
Block a user