Files
dockipelago/worlds/apeescape3/data/Items.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

323 lines
11 KiB
Python

from collections.abc import Sequence
from dataclasses import dataclass
from abc import ABC
import random
from BaseClasses import Item, ItemClassification
from .Strings import Itm, Game, Meta, APHelper
from .Addresses import NTSCU
### [< --- HELPERS --- >]
class AE3Item(Item):
"""
Defines an Item in Ape Escape 3. These include but are not limited to the Gadgets, Morphs and select buyable items
in the Shopping District.
"""
game : str = Meta.game
@dataclass
class AE3ItemMeta(ABC):
"""Base Data Class for all Items in Ape Escape 3."""
name : str
item_id : int
address : int
def to_item(self, player : int) -> AE3Item:
return AE3Item(self.name, ItemClassification.filler, self.item_id, player)
@dataclass
class EquipmentItem(AE3ItemMeta):
"""
Base Data Class for any Item that the player can only have one of. They can only be either locked or unlocked.
Parameters:
name : Name of Item from Strings.py
"""
def __init__(self, name : str):
self.name = name
# Equipment can be assumed to always be in Addresses.Items. NTSCU version will be used as basis for the ID.
self.item_id = NTSCU.Items[name]
self.address = self.item_id
def to_item(self, player : int, classification : ItemClassification = ItemClassification.progression) -> AE3Item:
return AE3Item(self.name, classification, self.item_id, player)
@dataclass
class CollectableItem(AE3ItemMeta):
"""
Base Data Class for any Item that the player can obtain multiples of continuously regardless of whether the player
is allowed to collect more.
Parameters:
name : Name of Item from Strings.py
resource : Name of resource affected by Item from Strings.py
amount : Amount of the Item to give
weight : How often to be chosen to fill a location
id_offset : (default : 0) Added Offset to ID for Items that target the same Memory Address
"""
resource : str
amount : int | float
capacity : int
weight : int
def __init__(self, name : str, resource : str, amount : int | float, weight : int, id_offset : int = 0):
self.name = name
# Collectables can be assumed to always be in Addresses.Items. NTSCU version will be used as basis for the ID.
self.address = NTSCU.GameStates[resource]
self.item_id = self.address + id_offset
self.resource = resource
self.amount = amount
self.capacity = Capacities[resource]
self.weight = weight
class UpgradeableItem(AE3ItemMeta):
"""
Base class for any item the player can obtain multiples of but only exists in specific amounts.
Parameters:
name : Name of Item from Strings.py
resource : Name of resource affected by Item from Strings.py
limit : Maximum amount of the item that is expected to exist in the game
id_offset : (default : 0) Added Offset to ID for Items that target the same Memory Address
"""
resource : str
amount : int | float
limit : int
def __init__(self, name : str, resource : str, amount : int | float, limit : int, id_offset : int = 0):
self.name = name
# Upgradeables can be assumed to always be in Addresses.Items. NTSCU version will be used as basis for the ID.
self.address = NTSCU.GameStates[resource]
self.item_id = self.address + id_offset
self.resource = resource
self.amount = amount
self.limit = limit
def to_item(self, player : int, classification : ItemClassification = ItemClassification.useful) -> AE3Item:
return AE3Item(self.name, classification, self.item_id, player)
def to_items(self, player : int, classification : ItemClassification = ItemClassification.useful) -> list[AE3Item]:
return [self.to_item(player, classification) for _ in range(self.limit)]
class ArchipelagoItem(AE3ItemMeta):
"""Base class for any non in-game item"""
def __init__(self, name : str):
self.name = name
self.item_id = AP[name]
def to_item(self, player : int) -> AE3Item:
return AE3Item(self.name, ItemClassification.progression, self.item_id, player)
def to_items(self, player : int, amount : int):
return [self.to_item(player) for _ in range(amount)]
### [< --- DATA --->]
Capacities : dict[str, int | float] = {
Game.morph_duration.value : 30.0,
Game.nothing.value : 0x0,
Game.cookies.value : 100.0,
Game.jackets.value : 0x63,
Game.chips.value : 0x270F,
Game.morph_gauge_active.value : 30.0,
Game.morph_stocks.value : 1100.0,
Game.ammo_boom.value : 0x9,
Game.ammo_homing.value : 0x9,
}
HUD_OFFSETS : dict[str, int] = {
Game.jackets.value : 0x0,
Game.cookies.value : 0x4,
Game.chips.value : 0x6,
}
AP : dict[str, int] = {
APHelper.channel_key.value : 0x3E8,
APHelper.victory.value : 0x3E9,
APHelper.shop_stock.value : 0x3EA,
APHelper.hint_book.value : 0x3EB,
}
### [< --- ITEMS --- >]
# Gadgets
Gadget_Club = EquipmentItem(Itm.gadget_club.value)
Gadget_Net = EquipmentItem(Itm.gadget_net.value)
Gadget_Radar = EquipmentItem(Itm.gadget_radar.value)
Gadget_Hoop = EquipmentItem(Itm.gadget_hoop.value)
Gadget_Sling = EquipmentItem(Itm.gadget_sling.value)
Gadget_Swim = EquipmentItem(Itm.gadget_swim.value)
Gadget_RCC = EquipmentItem(Itm.gadget_rcc.value)
Gadget_Fly = EquipmentItem(Itm.gadget_fly.value)
# Morphs
Morph_Knight = EquipmentItem(Itm.morph_knight.value)
Morph_Cowboy = EquipmentItem(Itm.morph_cowboy.value)
Morph_Ninja = EquipmentItem(Itm.morph_ninja.value)
Morph_Magician = EquipmentItem(Itm.morph_magician.value)
Morph_Kungfu = EquipmentItem(Itm.morph_kungfu.value)
Morph_Hero = EquipmentItem(Itm.morph_hero.value)
Morph_Monkey = EquipmentItem(Itm.morph_monkey.value)
# Accessories
Chassis_Twin = EquipmentItem(Itm.chassis_twin.value)
Chassis_Black = EquipmentItem(Itm.chassis_black.value)
Chassis_Pudding = EquipmentItem(Itm.chassis_pudding.value)
# Upgradeables
Acc_Morph_Stock = UpgradeableItem(Itm.acc_morph_stock.value, Game.morph_stocks.value, 100.0, 10)
Acc_Morph_Ext = UpgradeableItem(Itm.acc_morph_ext.value, Game.morph_duration.value, 2.0, 10)
# Collectables
Nothing = CollectableItem(Itm.nothing.value, Game.nothing.value,0, 1)
Cookie = CollectableItem(Itm.cookie.value, Game.cookies.value, 20.0, 20)
Cookie_Giant = CollectableItem(Itm.cookie_giant.value, Game.cookies.value, 100.0, 10, 0x01)
Jacket = CollectableItem(Itm.jacket.value, Game.jackets.value, 1, 5)
Chip_1x = CollectableItem(Itm.chip_1x.value, Game.chips.value, 1, 40)
Chip_5x = CollectableItem(Itm.chip_5x.value, Game.chips.value, 5, 35, 0x01)
Chip_10x = CollectableItem(Itm.chip_10x.value, Game.chips.value, 10, 30, 0x02)
Energy = CollectableItem(Itm.energy.value, Game.morph_gauge_active.value, 3.0, 40,0x0)
Energy_Mega = CollectableItem(Itm.energy_mega.value, Game.morph_gauge_active.value, 30.0, 10,
0x01)
Ammo_Boom = CollectableItem(Itm.ammo_boom.value, Game.ammo_boom.value, 1, 25)
Ammo_Homing = CollectableItem(Itm.ammo_homing.value, Game.ammo_homing.value, 1, 25)
# Archipelago
Channel_Key = ArchipelagoItem(APHelper.channel_key.value)
Shop_Stock = ArchipelagoItem(APHelper.shop_stock.value)
Hint_Book = ArchipelagoItem(APHelper.hint_book.value)
Victory = ArchipelagoItem(APHelper.victory.value)
### [< --- ITEM GROUPS --- >]
GADGETS : Sequence[EquipmentItem] = [
Gadget_Swim, Gadget_Club, Gadget_Net, Gadget_Radar, Gadget_Hoop, Gadget_Sling, Gadget_RCC, Gadget_Fly
]
MORPHS : Sequence[EquipmentItem] = [
Morph_Knight, Morph_Cowboy, Morph_Ninja, Morph_Magician, Morph_Kungfu, Morph_Hero, Morph_Monkey
]
EQUIPMENT : Sequence[EquipmentItem] = [
*GADGETS, *MORPHS
]
ACCESSORIES : Sequence[EquipmentItem] = [
Chassis_Twin, Chassis_Black, Chassis_Pudding
]
UPGRADEABLES : Sequence[UpgradeableItem] = [
Acc_Morph_Stock, Acc_Morph_Ext
]
COLLECTABLES : Sequence[CollectableItem] = [
Nothing, Cookie, Cookie_Giant, Jacket, Chip_1x, Chip_5x, Chip_10x, Energy, Energy_Mega, Ammo_Boom, Ammo_Homing
]
ARCHIPELAGO : Sequence[ArchipelagoItem] = [
Channel_Key, Shop_Stock, Hint_Book, Victory
]
ITEMS_MASTER : Sequence[AE3ItemMeta] = [
*GADGETS, *MORPHS, *ACCESSORIES, *UPGRADEABLES, *COLLECTABLES, *ARCHIPELAGO
]
ITEMS_INDEX : Sequence[Sequence] = [
ITEMS_MASTER, GADGETS, MORPHS, EQUIPMENT, ACCESSORIES, UPGRADEABLES, COLLECTABLES, ARCHIPELAGO
]
### [< --- METHODS --- >]
def from_id(item_id = int, category : int = 0):
"""Get Item by its ID"""
ref : Sequence = ITEMS_INDEX[category]
i : AE3ItemMeta = next((i for i in ref if i.item_id == item_id), None)
return i
def generate_name_to_id() -> dict[str : int]:
"""Get a Dictionary of all Items in Name-ID pairs"""
i : AE3ItemMeta
return {i.name : i.item_id for i in ITEMS_MASTER}
def generate_item_groups() -> dict[str : set[str]]:
"""Get a Dictionary of Item Groups"""
groups : dict[str : set[str]] = {}
i : AE3ItemMeta
# Gadgets
for i in GADGETS:
groups.setdefault(APHelper.gadgets.value, set()).add(i.name)
# Morphs
for i in MORPHS:
groups.setdefault(APHelper.morphs.value, set()).add(i.name)
# Equipment
groups.setdefault(APHelper.equipment.value, set()).update(groups[APHelper.gadgets.value])
groups.setdefault(APHelper.equipment.value, set()).update(groups[APHelper.morphs.value])
# Morphs (without Super Monkey)
groups.setdefault(APHelper.morphs_no_monkey.value, groups[APHelper.morphs.value]).remove(Itm.morph_monkey.value)
# RC Cars
groups.setdefault(APHelper.rc_cars.value, set()).update([
Itm.gadget_rcc.value, Itm.chassis_twin.value, Itm.chassis_black.value, Itm.chassis_pudding.value
])
# Catch (Long)
groups.setdefault(APHelper.catch_long.value, set()).update([
Itm.morph_cowboy.value, Itm.morph_ninja.value, Itm.morph_magician.value, Itm.morph_hero.value
])
# Attack
groups.setdefault(APHelper.attack.value, set()).update([
Itm.gadget_club.value, Itm.gadget_sling.value, *groups[APHelper.morphs_no_monkey.value]
])
# Hit
groups.setdefault(APHelper.hit.value, set()).update([
Itm.gadget_hoop.value, Itm.morph_monkey.value, Itm.gadget_fly.value, *groups[APHelper.rc_cars.value],
*groups[APHelper.attack.value]
])
# Dash
groups.setdefault(APHelper.dash.value, set()).update([
Itm.gadget_hoop.value, Itm.morph_ninja.value, Itm.morph_hero.value
])
# Shoot
groups.setdefault(APHelper.shoot.value, set()).update([
Itm.gadget_sling.value, Itm.morph_cowboy.value, Itm.morph_hero.value
])
# Fly
groups.setdefault(APHelper.fly.value, set()).update([
Itm.gadget_fly.value, Itm.morph_ninja.value
])
# Glide
groups.setdefault(APHelper.glide.value, set()).update([
Itm.morph_hero.value, *groups[APHelper.fly.value]
])
# Archipelago
groups.setdefault(APHelper.archipelago.value, set()).update([
APHelper.channel_key.value, APHelper.shop_stock.value
])
return groups
def generate_collectables(rand : random, player : int, amt : int) -> list[AE3Item]:
"""Get a list of Items of the specified Archipelago"""
weights : list[int] = [w.weight for w in COLLECTABLES]
result : list[CollectableItem] = rand.choices([*COLLECTABLES], [*weights], k = amt)
items : list[AE3Item] = [c.to_item(player) for c in result]
return items