190 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from typing import Any, Dict, List
 | |
| 
 | |
| from BaseClasses import Item, Location, Tutorial, ItemClassification, MultiWorld
 | |
| from worlds.AutoWorld import WebWorld, World
 | |
| from . import Items, Locations, Regions, Rules
 | |
| from .Options import FaxanaduOptions
 | |
| from worlds.generic.Rules import set_rule
 | |
| 
 | |
| 
 | |
| DAXANADU_VERSION = "0.3.0"
 | |
| 
 | |
| 
 | |
| class FaxanaduLocation(Location):
 | |
|     game: str = "Faxanadu"
 | |
| 
 | |
| 
 | |
| class FaxanaduItem(Item):
 | |
|     game: str = "Faxanadu"
 | |
| 
 | |
| 
 | |
| class FaxanaduWeb(WebWorld):
 | |
|     tutorials = [Tutorial(
 | |
|         "Multiworld Setup Guide",
 | |
|         "A guide to setting up the Faxanadu randomizer connected to an Archipelago Multiworld",
 | |
|         "English",
 | |
|         "setup_en.md",
 | |
|         "setup/en",
 | |
|         ["Daivuk"]
 | |
|     )]
 | |
|     theme = "dirt"
 | |
| 
 | |
| 
 | |
| class FaxanaduWorld(World):
 | |
|     """
 | |
|     Faxanadu is an action role-playing platform video game developed by Hudson Soft for the Nintendo Entertainment System
 | |
|     """
 | |
|     options_dataclass = FaxanaduOptions
 | |
|     options: FaxanaduOptions
 | |
|     game = "Faxanadu"
 | |
|     web = FaxanaduWeb()
 | |
| 
 | |
|     item_name_to_id = {item.name: item.id for item in Items.items if item.id is not None}
 | |
|     item_name_to_item = {item.name: item for item in Items.items}
 | |
|     location_name_to_id = {loc.name: loc.id for loc in Locations.locations if loc.id is not None}
 | |
| 
 | |
|     def __init__(self, world: MultiWorld, player: int):
 | |
|         self.filler_ratios: Dict[str, int] = {
 | |
|             item.name: item.count
 | |
|             for item in Items.items
 | |
|             if item.classification in [ItemClassification.filler, ItemClassification.trap]
 | |
|         }
 | |
|         # Remove poison by default to respect itemlinking
 | |
|         self.filler_ratios["Poison"] = 0
 | |
|         super().__init__(world, player)
 | |
| 
 | |
|     def create_regions(self):
 | |
|         Regions.create_regions(self)
 | |
| 
 | |
|         # Add locations into regions
 | |
|         for region in self.multiworld.get_regions(self.player):
 | |
|             for loc in [location for location in Locations.locations if location.region == region.name]:
 | |
|                 location = FaxanaduLocation(self.player, loc.name, loc.id, region)
 | |
| 
 | |
|                 # In Faxanadu, Poison hurts you when picked up. It makes no sense to sell them in shops
 | |
|                 if loc.type == Locations.LocationType.shop:
 | |
|                     location.item_rule = lambda item, player=self.player: not (player == item.player and item.name == "Poison")
 | |
|                 
 | |
|                 region.locations.append(location)
 | |
| 
 | |
|     def set_rules(self):
 | |
|         Rules.set_rules(self)
 | |
|         self.multiworld.completion_condition[self.player] = lambda state: state.has("Killed Evil One", self.player)
 | |
| 
 | |
|     def create_item(self, name: str) -> FaxanaduItem:
 | |
|         item: Items.ItemDef = self.item_name_to_item[name]
 | |
|         return FaxanaduItem(name, item.classification, item.id, self.player)
 | |
| 
 | |
|     # Returns how many red potions were prefilled into shops
 | |
|     def prefill_shop_red_potions(self) -> int:
 | |
|         red_potion_in_shop_count = 0
 | |
|         if self.options.keep_shop_red_potions:
 | |
|             red_potion_item = self.item_name_to_item["Red Potion"]
 | |
|             red_potion_shop_locations = [
 | |
|                 loc
 | |
|                 for loc in Locations.locations
 | |
|                 if loc.type == Locations.LocationType.shop and loc.original_item == Locations.ItemType.red_potion
 | |
|             ]
 | |
|             for loc in red_potion_shop_locations:
 | |
|                 location = self.get_location(loc.name)
 | |
|                 location.place_locked_item(FaxanaduItem(red_potion_item.name, red_potion_item.classification, red_potion_item.id, self.player))
 | |
|                 red_potion_in_shop_count += 1
 | |
|         return red_potion_in_shop_count
 | |
| 
 | |
|     def put_wingboot_in_shop(self, shops, region_name):
 | |
|         item = self.item_name_to_item["Wingboots"]
 | |
|         shop = shops.pop(region_name)
 | |
|         slot = self.random.randint(0, len(shop) - 1)
 | |
|         loc = shop[slot]
 | |
|         location = self.get_location(loc.name)
 | |
|         location.place_locked_item(FaxanaduItem(item.name, item.classification, item.id, self.player))
 | |
| 
 | |
|         # Put a rule right away that we need to have to unlocked.
 | |
|         set_rule(location, lambda state: state.has("Unlock Wingboots", self.player))
 | |
| 
 | |
|     # Returns how many wingboots were prefilled into shops
 | |
|     def prefill_shop_wingboots(self) -> int:
 | |
|         # Collect shops
 | |
|         shops: Dict[str, List[Locations.LocationDef]] = {}
 | |
|         for loc in Locations.locations:
 | |
|             if loc.type == Locations.LocationType.shop:
 | |
|                 if self.options.keep_shop_red_potions and loc.original_item == Locations.ItemType.red_potion:
 | |
|                     continue # Don't override our red potions
 | |
|                 shops.setdefault(loc.region, []).append(loc)
 | |
|         
 | |
|         shop_count = len(shops)
 | |
|         wingboots_count = round(shop_count / 2.5) # On 10 shops, we should have about 4 shops with wingboots
 | |
| 
 | |
|         # At least one should be in the first 4 shops. Because we require wingboots to progress past that point.
 | |
|         must_have_regions = [region for i, region in enumerate(shops) if i < 4]
 | |
|         self.put_wingboot_in_shop(shops, self.random.choice(must_have_regions))
 | |
| 
 | |
|         # Fill in the rest randomly in remaining shops
 | |
|         for i in range(wingboots_count - 1): # -1 because we added one already
 | |
|             region = self.random.choice(list(shops.keys()))
 | |
|             self.put_wingboot_in_shop(shops, region)
 | |
| 
 | |
|         return wingboots_count
 | |
| 
 | |
|     def create_items(self) -> None:
 | |
|         itempool: List[FaxanaduItem] = []
 | |
| 
 | |
|         # Prefill red potions in shops if option is set
 | |
|         red_potion_in_shop_count = self.prefill_shop_red_potions()
 | |
| 
 | |
|         # Prefill wingboots in shops
 | |
|         wingboots_in_shop_count = self.prefill_shop_wingboots()
 | |
| 
 | |
|         # Create the item pool, excluding fillers.
 | |
|         prefilled_count = red_potion_in_shop_count + wingboots_in_shop_count
 | |
|         for item in Items.items:
 | |
|             # Ignore pendant if turned off
 | |
|             if item.name == "Pendant" and not self.options.include_pendant:
 | |
|                 continue
 | |
| 
 | |
|             # ignore fillers for now, we will fill them later
 | |
|             if item.classification in [ItemClassification.filler, ItemClassification.trap] and \
 | |
|                item.progression_count == 0:
 | |
|                 continue
 | |
| 
 | |
|             prefill_loc = None
 | |
|             if item.prefill_location:
 | |
|                 prefill_loc = self.get_location(item.prefill_location)
 | |
| 
 | |
|             # if require dragon slayer is turned on, we need progressive shields to be progression
 | |
|             item_classification = item.classification
 | |
|             if self.options.require_dragon_slayer and item.name == "Progressive Shield":
 | |
|                 item_classification = ItemClassification.progression
 | |
| 
 | |
|             if prefill_loc:
 | |
|                 prefill_loc.place_locked_item(FaxanaduItem(item.name, item_classification, item.id, self.player))
 | |
|                 prefilled_count += 1
 | |
|             else:
 | |
|                 for i in range(item.count - item.progression_count):
 | |
|                     itempool.append(FaxanaduItem(item.name, item_classification, item.id, self.player))
 | |
|                 for i in range(item.progression_count):
 | |
|                     itempool.append(FaxanaduItem(item.name, ItemClassification.progression, item.id, self.player))
 | |
| 
 | |
|         # Adjust filler ratios
 | |
|         # If red potions are locked in shops, remove the count from the ratio.
 | |
|         self.filler_ratios["Red Potion"] -= red_potion_in_shop_count
 | |
| 
 | |
|         # Add poisons if desired
 | |
|         if self.options.include_poisons:
 | |
|             self.filler_ratios["Poison"] = self.item_name_to_item["Poison"].count
 | |
| 
 | |
|         # Randomly add fillers to the pool with ratios based on og game occurrence counts.
 | |
|         filler_count = len(Locations.locations) - len(itempool) - prefilled_count
 | |
|         for i in range(filler_count):
 | |
|             itempool.append(self.create_item(self.get_filler_item_name()))
 | |
|                 
 | |
|         self.multiworld.itempool += itempool
 | |
| 
 | |
|     def get_filler_item_name(self) -> str:
 | |
|         return self.random.choices(list(self.filler_ratios.keys()), weights=list(self.filler_ratios.values()))[0]
 | |
|     
 | |
|     def fill_slot_data(self) -> Dict[str, Any]:
 | |
|         slot_data = self.options.as_dict("keep_shop_red_potions", "random_musics", "random_sounds", "random_npcs", "random_monsters", "random_rewards")
 | |
|         slot_data["daxanadu_version"] = DAXANADU_VERSION
 | |
|         return slot_data
 | 
