AutoWorld: basic Item handling

This commit is contained in:
Fabian Dill 2021-07-12 13:54:47 +02:00
parent babd809fa6
commit 31c550d410
11 changed files with 84 additions and 51 deletions

View File

@ -272,6 +272,8 @@ class MultiWorld():
return next(location for location in self.get_locations() if
location.item and location.item.name == item and location.item.player == player)
def create_item(self, item_name: str, player: int) -> Item:
return self.worlds[player].create_item(item_name)
def push_precollected(self, item: Item):
item.world = self
@ -859,7 +861,6 @@ class CollectionState(object):
(self.has('Progressive Weapons', player, 1) and self.has('Bed', player)))
return respawn_dragon and self.has('Progressive Weapons', player, 2) and self.has('Progressive Armor', player) and self.has('Archery', player)
def collect(self, item: Item, event: bool = False, location: Location = None) -> bool:
if location:
self.locations_checked.add(location)

View File

@ -9,7 +9,7 @@ import pickle
from typing import Dict, Tuple
from BaseClasses import MultiWorld, CollectionState, Region, Item
from worlds.alttp.Items import ItemFactory, item_name_groups
from worlds.alttp.Items import item_name_groups
from worlds.alttp.Regions import create_regions, mark_light_world_regions, \
lookup_vanilla_location_to_entrance
from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions
@ -453,8 +453,8 @@ def main(args, seed=None):
for index, take_any in enumerate(takeanyregions):
for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if
world.retro[player]]:
item = ItemFactory(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'],
region.player)
item = world.create_item(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'],
region.player)
player = region.player
location_id = SHOP_ID_START + total_shop_slots + index

View File

@ -516,10 +516,11 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
ret.game = get_choice("game", weights)
if ret.game not in weights:
raise Exception(f"No game options for selected game \"{ret.game}\" found.")
world_type = AutoWorldRegister.world_types[ret.game]
game_weights = weights[ret.game]
ret.local_items = set()
for item_name in game_weights.get('local_items', []):
items = item_name_groups.get(item_name, {item_name})
items = world_type.item_name_groups.get(item_name, {item_name})
for item in items:
if item in lookup_any_item_name_to_id:
ret.local_items.add(item)
@ -528,7 +529,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
ret.non_local_items = set()
for item_name in game_weights.get('non_local_items', []):
items = item_name_groups.get(item_name, {item_name})
items = world_type.item_name_groups.get(item_name, {item_name})
for item in items:
if item in lookup_any_item_name_to_id:
ret.non_local_items.add(item)
@ -556,7 +557,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
else:
setattr(ret, option_name, option.from_any(get_choice(option_name, game_weights)))
except Exception as e:
raise Exception(f"Error generating option {option_name} in {ret.game}")
raise Exception(f"Error generating option {option_name} in {ret.game}") from e
else:
setattr(ret, option_name, option(option.default))
if ret.game == "Minecraft":
@ -778,7 +779,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
get_choice("direction", placement, "both")
))
ret.sprite_pool = weights.get('sprite_pool', [])
ret.sprite = get_choice('sprite', weights, "Link")
if 'random_sprite_on_event' in weights:

View File

@ -1,4 +1,6 @@
from BaseClasses import MultiWorld
from typing import Dict, Set, Tuple
from BaseClasses import MultiWorld, Item, CollectionState
class AutoWorldRegister(type):
@ -29,6 +31,9 @@ class World(metaclass=AutoWorldRegister):
player: int
options: dict = {}
topology_present: bool = False # indicate if world type has any meaningful layout/pathing
item_names: Set[str] = frozenset()
# maps item group names to sets of items. Example: "Weapons" -> {"Sword", "Bow"}
item_name_groups: Dict[str, Set[str]] = {}
def __init__(self, world: MultiWorld, player: int):
self.world = world
@ -49,15 +54,19 @@ class World(metaclass=AutoWorldRegister):
If you need any last-second randomization, use MultiWorld.slot_seeds[slot] instead."""
pass
def get_required_client_version(self) -> tuple:
def get_required_client_version(self) -> Tuple[int, int, int]:
return 0, 0, 3
# end of Main.py calls
def collect(self, state, item) -> bool:
def collect(self, state: CollectionState, item: Item) -> bool:
"""Collect an item into state. For speed reasons items that aren't logically useful get skipped."""
if item.advancement:
state.prog_items[item.name, item.player] += 1
return True # indicate that a logical state change has occured
return False
def create_item(self, name: str) -> Item:
"""Create an item for this world type and player.
Warning: this may be called with self.world = None, for example by MultiServer"""
raise NotImplementedError

View File

@ -6,17 +6,19 @@ def GetBeemizerItem(world, player, item):
if world.beemizer[player] and item_name in trap_replaceable:
if world.random.random() < world.beemizer[player] * 0.25:
if world.random.random() < (0.5 + world.beemizer[player] * 0.1):
return "Bee Trap" if isinstance(item, str) else ItemFactory("Bee Trap", player)
return "Bee Trap" if isinstance(item, str) else world.create_item("Bee Trap", player)
else:
return "Bee" if isinstance(item, str) else ItemFactory("Bee", player)
return "Bee" if isinstance(item, str) else world.create_item("Bee", player)
else:
return item
else:
return item
def ItemFactory(items, player):
from worlds.alttp import ALttPItem
# should be replaced with direct world.create_item(item) call in the future
def ItemFactory(items, player: int):
from worlds.alttp import ALTTPWorld
world = ALTTPWorld(None, player)
ret = []
singleton = False
if isinstance(items, str):
@ -24,7 +26,7 @@ def ItemFactory(items, player):
singleton = True
for item in items:
if item in item_table:
ret.append(ALttPItem(item, *item_table[item], player))
ret.append(world.create_item(item))
else:
raise Exception(f"Unknown item {item}")
@ -211,6 +213,8 @@ item_table = {'Bow': ItemData(True, None, 0x0B, 'You have\nchosen the\narcher cl
'Open Floodgate': ItemData(True, 'Event', None, None, None, None, None, None, None, None),
}
as_dict_item_table = {name: data._asdict() for name, data in item_table.items()}
progression_mapping = {
"Golden Sword": ("Progressive Sword", 4),
"Tempered Sword": ("Progressive Sword", 3),
@ -268,8 +272,8 @@ for basename, substring in _simple_groups:
del (_simple_groups)
progression_items = {name for name, data in item_table.items() if type(data[2]) == int and data[0]}
item_name_groups['Everything'] = {name for name, data in item_table.items() if type(data[2]) == int}
progression_items = {name for name, data in item_table.items() if type(data.item_code) == int and data.advancement}
item_name_groups['Everything'] = {name for name, data in item_table.items() if type(data.item_code) == int}
item_name_groups['Progression Items'] = progression_items
item_name_groups['Non Progression Items'] = item_name_groups['Everything'] - progression_items

View File

@ -3,11 +3,15 @@ from typing import Optional
from BaseClasses import Location, Item, CollectionState
from ..AutoWorld import World
from .Options import alttp_options
from .Items import as_dict_item_table, item_name_groups, item_table
class ALTTPWorld(World):
game: str = "A Link to the Past"
options = alttp_options
topology_present = True
item_name_groups = item_name_groups
item_names = frozenset(item_table)
def collect(self, state: CollectionState, item: Item) -> bool:
if item.name.startswith('Progressive '):
@ -66,6 +70,9 @@ class ALTTPWorld(World):
def get_required_client_version(self) -> tuple:
return max((0, 1, 4), super(ALTTPWorld, self).get_required_client_version())
def create_item(self, name: str) -> Item:
return ALttPItem(name, self.player, **as_dict_item_table[name])
class ALttPLocation(Location):
game: str = "A Link to the Past"
@ -80,16 +87,16 @@ class ALttPLocation(Location):
class ALttPItem(Item):
game: str = "A Link to the Past"
def __init__(self, name='', advancement=False, type=None, code=None, pedestal_hint=None, pedestal_credit=None, sickkid_credit=None, zora_credit=None, witch_credit=None, fluteboy_credit=None, hint_text=None, player=None):
super(ALttPItem, self).__init__(name, advancement, code, player)
def __init__(self, name, player, advancement=False, type=None, item_code=None, pedestal_hint=None, pedestal_credit=None,
sick_kid_credit=None, zora_credit=None, witch_credit=None, flute_boy_credit=None, hint_text=None):
super(ALttPItem, self).__init__(name, advancement, item_code, player)
self.type = type
self._pedestal_hint_text = pedestal_hint
self.pedestal_credit_text = pedestal_credit
self.sickkid_credit_text = sickkid_credit
self.sickkid_credit_text = sick_kid_credit
self.zora_credit_text = zora_credit
self.magicshop_credit_text = witch_credit
self.fluteboy_credit_text = fluteboy_credit
self.fluteboy_credit_text = flute_boy_credit
self._hint_text = hint_text

View File

@ -4,29 +4,30 @@ from BaseClasses import Region, Entrance, Location, Item
from .Technologies import base_tech_table, recipe_sources, base_technology_table, advancement_technologies, \
all_ingredient_names, required_technologies, get_rocket_requirements, rocket_recipes, \
progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table, \
get_science_pack_pools, Recipe, recipes, technology_table
get_science_pack_pools, Recipe, recipes, technology_table, tech_table
from .Shapes import get_shapes
from .Mod import generate_mod
from .Options import factorio_options
class FactorioItem(Item):
game = "Factorio"
class Factorio(World):
game: str = "Factorio"
static_nodes = {"automation", "logistics", "rocket-silo"}
custom_recipes = {}
additional_advancement_technologies = set()
item_names = frozenset(tech_table)
def generate_basic(self):
for tech_name, tech_id in base_tech_table.items():
if self.world.progressive and tech_name in tech_to_progressive_lookup:
item_name = tech_to_progressive_lookup[tech_name]
tech_id = progressive_tech_table[item_name]
for tech_name in base_tech_table:
if self.world.progressive:
item_name = tech_to_progressive_lookup.get(tech_name, tech_name)
else:
item_name = tech_name
tech_item = Item(item_name, item_name in advancement_technologies or
item_name in self.additional_advancement_technologies,
tech_id, self.player)
tech_item.game = "Factorio"
item_name = item_name
tech_item = self.create_item(item_name)
if tech_name in self.static_nodes:
self.world.get_location(tech_name, self.player).place_locked_item(tech_item)
else:
@ -157,3 +158,9 @@ class Factorio(World):
if tech in tech_to_progressive_lookup:
prog_add.add(tech_to_progressive_lookup[tech])
self.additional_advancement_technologies |= prog_add
def create_item(self, name: str) -> Item:
assert name in tech_table
return FactorioItem(name, name in advancement_technologies or
name in self.additional_advancement_technologies,
tech_table[name], self.player)

View File

@ -1,6 +1,7 @@
from typing import NamedTuple, Union
import logging
class PlandoItem(NamedTuple):
item: str
location: str
@ -25,7 +26,3 @@ class PlandoConnection(NamedTuple):
entrance: str
exit: str
direction: str # entrance, exit or both
class World():
pass

View File

@ -1,4 +1,5 @@
import logging
from typing import Set
logger = logging.getLogger("Hollow Knight")
@ -14,6 +15,7 @@ from ..AutoWorld import World
class HKWorld(World):
game: str = "Hollow Knight"
options = hollow_knight_options
item_names: Set[str] = frozenset(item_table)
def generate_basic(self):
# Link regions
@ -22,8 +24,7 @@ class HKWorld(World):
# Generate item pool
pool = []
for item_name, item_data in item_table.items():
item = HKItem(item_name, item_data.advancement, item_data.id, item_data.type, player=self.player)
item = self.create_item(item_name)
if item_data.type == "Event":
event_location = self.world.get_location(item_name, self.player)
@ -83,6 +84,9 @@ class HKWorld(World):
slot_data[option_name] = int(option.value)
return slot_data
def create_item(self, name: str) -> Item:
item_data = item_table[name]
return HKItem(name, item_data.advancement, item_data.id, item_data.type, self.player)
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
ret = Region(name, None, name, player)

View File

@ -1,14 +1,15 @@
from BaseClasses import Item
import typing
class ItemData(typing.NamedTuple):
code: int
progression: bool
class MinecraftItem(Item):
game: str = "Minecraft"
def __init__(self, name: str, progression: bool, code: int, player: int):
super().__init__(name, progression, code if code else None, player)
item_table = {
"Archery": ItemData(45000, True),

View File

@ -1,9 +1,12 @@
from typing import Dict, Set
from .Items import MinecraftItem, item_table, item_frequencies
from .Locations import MinecraftAdvancement, advancement_table, exclusion_table, events_table
from .Regions import mc_regions, link_minecraft_structures
from .Rules import set_rules
from BaseClasses import Region, Entrance
from BaseClasses import Region, Entrance, Item
from .Options import minecraft_options
from ..AutoWorld import World
@ -13,6 +16,7 @@ class MinecraftWorld(World):
game: str = "Minecraft"
options = minecraft_options
topology_present = True
item_names = frozenset(item_table)
def _get_mc_data(self):
exits = ["Overworld Structure 1", "Overworld Structure 2", "Nether Structure 1", "Nether Structure 2",
@ -27,7 +31,6 @@ class MinecraftWorld(World):
'structures': {exit: self.world.get_entrance(exit, self.player).connected_region.name for exit in exits}
}
def generate_basic(self):
link_minecraft_structures(self.world, self.player)
@ -35,9 +38,9 @@ class MinecraftWorld(World):
pool_counts = item_frequencies.copy()
if getattr(self.world, "bee_traps")[self.player]:
pool_counts.update({"Rotten Flesh": 0, "Bee Trap (Minecraft)": 4})
for item_name, item_data in item_table.items():
for item_name in item_table:
for count in range(pool_counts.get(item_name, 1)):
pool.append(MinecraftItem(item_name, item_data.progression, item_data.code, self.player))
pool.append(self.create_item(item_name))
prefill_pool = {}
prefill_pool.update(events_table)
@ -49,7 +52,7 @@ class MinecraftWorld(World):
for loc_name, item_name in prefill_pool.items():
item_data = item_table[item_name]
location = self.world.get_location(loc_name, self.player)
item = MinecraftItem(item_name, item_data.progression, item_data.code, self.player)
item = self.create_item(item_name)
self.world.push_item(location, item, collect=False)
pool.remove(item)
location.event = item_data.progression
@ -57,11 +60,9 @@ class MinecraftWorld(World):
self.world.itempool += pool
def set_rules(self):
set_rules(self.world, self.player)
def create_regions(self):
def MCRegion(region_name: str, exits=[]):
ret = Region(region_name, None, region_name, self.player)
@ -75,7 +76,6 @@ class MinecraftWorld(World):
self.world.regions += [MCRegion(*r) for r in mc_regions]
def generate_output(self):
import json
from base64 import b64encode
@ -86,10 +86,13 @@ class MinecraftWorld(World):
with open(output_path(filename), 'wb') as f:
f.write(b64encode(bytes(json.dumps(data), 'utf-8')))
def fill_slot_data(self):
slot_data = self._get_mc_data()
for option_name in minecraft_options:
option = getattr(self.world, option_name)[self.player]
slot_data[option_name] = int(option.value)
return slot_data
def create_item(self, name: str) -> Item:
item_data = item_table[name]
return MinecraftItem(name, item_data.progression, item_data.code, self.player)