diff --git a/worlds/crosscode/codegen/lists.py b/worlds/crosscode/codegen/lists.py index 1b5f254a35c8..8e3afa90304c 100644 --- a/worlds/crosscode/codegen/lists.py +++ b/worlds/crosscode/codegen/lists.py @@ -15,8 +15,13 @@ from ..types.items import ItemData, ProgressiveItemChainSingle, SingleItemData, ItemPoolEntry, ProgressiveItemChain from ..types.locations import AccessInfo, LocationData -from ..types.condition import Condition, NeverCondition, RegionCondition, OrCondition, AndCondition, ShopSlotCondition +from ..types.condition import ( + Condition, NeverCondition, RegionCondition, + OrCondition, AndCondition, ShopSlotCondition, + SingleTradeCondition, +) from ..types.shops import ShopData +from ..types.traders import TraderData, SingleTrade class LocationCategory(StrEnum): """ @@ -66,6 +71,14 @@ class ListInfo: shop_unlock_by_shop_and_id: dict[tuple[str, int], ItemPoolEntry] global_slot_region_conditions_list: dict[str, list[Condition]] + trader_data: dict[str, TraderData] + per_trader_locations: dict[str, dict[int, LocationData]] + global_trader_locations: dict[int, LocationData] + trader_unlock_by_id: dict[int, ItemPoolEntry] + trader_unlock_by_trader: dict[str, ItemPoolEntry] + trader_unlock_by_trader_and_id: dict[tuple[str, int], ItemPoolEntry] + global_trade_region_conditions_list: dict[str, list[Condition]] + region_botanics_amounts: dict[str, dict[str, int]] # { mode => { region => number of plants } } botanics_internal_names_to_ids: dict[str, int] @@ -112,6 +125,13 @@ def __init__(self, ctx: Context): self.shop_unlock_by_shop_and_id = {} self.global_slot_region_conditions_list = {} + self.trader_data = {} + self.per_trader_locations = defaultdict(dict) + self.trader_unlock_by_id = {} # unused? + self.trader_unlock_by_trader = {} + self.trader_unlock_by_trader_and_id = {} + self.global_trade_region_conditions_list = {} # ? + self.region_botanics_amounts = defaultdict(lambda: defaultdict(lambda: 0)) self.botanics_internal_names_to_ids = {} @@ -462,6 +482,90 @@ def __add_shop_list(self, loc_list: dict[str, dict[str, typing.Any]]): for name, raw_shop in loc_list.items(): self.__add_shop(name, raw_shop) + def __add_trader(self, internal_name: str, raw_trader: dict[str, typing.Any]): + # is it possible to simplify this code by combining it with the shop code? + trader_name: str = raw_trader["location"]["trader"] + area = raw_trader["location"]["area"] + area_name = self.ctx.area_names[area] + + metadata = raw_trader["metadata"] + metadata["trader"] = True + + access_info = self.json_parser.parse_location_access_info(raw_trader) + + # it's empty, but since it's a defaultdict[list], we get an empty list + # so we can modify it in-place and it'll get reflected + locs = self.per_trader_locations[trader_name] + + unlock = f"Trader Unlock: {trader_name}" + # the desired behaviour is identical for shops and traders, so can reuse + unlock_item = self.__add_shop_unlock_item(unlock) + if trader_name not in self.trader_unlock_by_trader: + self.trader_unlock_by_trader[trader_name] = ItemPoolEntry(unlock_item, 1, metadata) + self.descriptions[unlock_item.combo_id] = { + # oh no the french are invading + "en_US": fr"Unlocks \c[3]all trades\c[0] for trader \c[3]{trader_name}\c[0]." + } + + trader_unlocks = self.item_groups.setdefault("Trader Unlocks", []) + if unlock_item not in trader_unlocks: + trader_unlocks.append(unlock_item) + + global_item_group = self.item_groups.setdefault("Global Trader Unlocks", []) + trader_area_group = self.item_groups.setdefault(f"Trader Unlocks: {area_name}", []) + + global_item_group.append(unlock_item) + trader_area_group.append(unlock_item) + + trade_unlocks_group = self.item_groups.setdefault("Trade Unlocks", []) + this_trader_unlocks = self.item_groups.setdefault(f"Trade Unlocks: {trader_name}", []) + + for trade_name, trade in raw_trader["trades"].items(): + t_, name, count = trade["reward"] + item_data = self.ctx.rando_data["items"][name] + if t_ != "item": # all trades should give an item + continue + + trade_loc_name = f"Trade: {trade_name} ({trader_name})" + locid = self.__get_or_allocate_location_id(trade_loc_name) + + item_id = item_data["id"] + + trade_location = LocationData( + name=trade_loc_name, + code=locid, + area=area, + metadata=metadata, + access=AccessInfo( + region={rn: trader_name for rn in access_info.region}, + cond=[SingleTradeCondition(trader_name, item_id)] + ) + ) + + self.location_groups[area_name].append(trade_location) + self.location_groups[f"{area_name} Traders"].append(trade_location) + + trade_unlocks_group.append(trade_location) + this_trader_unlocks.append(trade_location) + + locs[item_id] = trade_location + self.locations_data[trade_location.name] = trade_location + + by_trader_and_id_name = f"Trade Unlock: {trade_name} ({trader_name})" + by_trader_and_id_item = self.__add_shop_unlock_item(by_trader_and_id_name) + self.trader_unlock_by_trader_and_id[internal_name, item_id] + + self.descriptions[by_trader_and_id_item.combo_id] = { + "en_US": fr"Unlocks the trade \c[3]{trade_name}\c[0] from trader \c[3]{trader_name}\c[0]." + } + + self.trader_data[trader_name] = TraderData( + internal_name=internal_name, + name=trader_name, + access=access_info, + metadata=metadata, + ) + def __add_item_data_list(self, item_list: dict[str, dict[str, typing.Any]]): """ Add a list of items to the list. diff --git a/worlds/crosscode/types/condition.py b/worlds/crosscode/types/condition.py index dc383b4396fa..a9d4c7bcb1bd 100644 --- a/worlds/crosscode/types/condition.py +++ b/worlds/crosscode/types/condition.py @@ -167,6 +167,15 @@ def satisfied(self, state: CollectionState, player: int, location: int | None, a return state.has(args["shop_unlock_by_shop_and_id"][self.shop_name, self.item_id].item.name, player) return True +@dataclass +class SingleTradeCondition(Condition): + trader_name: str + item_id: int + + def satisfied(self, state: CollectionState, player: int, location: int | None, args: LogicDict) -> bool: + # XXX: need to implement this as tradesanity gets further implemented + return True + @dataclass class BotanicsCompletionCondition(Condition): amount: float diff --git a/worlds/crosscode/types/traders.py b/worlds/crosscode/types/traders.py new file mode 100644 index 000000000000..4d65774cdfb5 --- /dev/null +++ b/worlds/crosscode/types/traders.py @@ -0,0 +1,21 @@ +import typing +from dataclasses import dataclass + +from .locations import AccessInfo +from .metadata import IncludeOptions +from .items import ItemData + +@dataclass +class TraderData: + internal_name: str + name: str + access: AccessInfo + metadata: typing.Optional[IncludeOptions] = None + +@dataclass +class SingleTrade: + display_name: str + reward: ItemData + cost: list[ItemData] + index: int +