Compare commits
101 Commits
v1.0.1
...
artamis-mo
| Author | SHA1 | Date | |
|---|---|---|---|
| d0a8df25f6 | |||
|
|
6f552949fa | ||
|
|
ca1eb82ce1 | ||
|
|
414a155323 | ||
|
|
b9d8d9174f | ||
|
|
8e58f9662e | ||
|
|
6f4597398f | ||
|
|
5e71874446 | ||
|
|
1870dd24ba | ||
|
|
f70b6c4c9c | ||
|
|
79d4d5b10b | ||
|
|
7fea34adc3 | ||
|
|
a3f9e6cbc9 | ||
|
|
bccc83f864 | ||
|
|
6409721841 | ||
|
|
d3a7b014bd | ||
|
|
3ec8631203 | ||
|
|
2081912a39 | ||
|
|
67bbde2556 | ||
|
|
0503c2ead3 | ||
|
|
7a642cc1a9 | ||
|
|
eb8d44e975 | ||
|
|
1f35f5fa93 | ||
|
|
6bc819f4bc | ||
|
|
bb0c5f5b9a | ||
|
|
0e397c7079 | ||
|
|
2572a25089 | ||
|
|
dcdf168618 | ||
|
|
8a8136a267 | ||
|
|
5f158497f9 | ||
|
|
bc74e67e07 | ||
|
|
dbc592dad0 | ||
|
|
afe1345e34 | ||
|
|
410df2a948 | ||
|
|
7c6eada7b2 | ||
|
|
8e1217d1a5 | ||
|
|
5615277705 | ||
|
|
2de0f9d766 | ||
|
|
4da88cf794 | ||
|
|
e2cc1b5de7 | ||
|
|
103a6f79d1 | ||
|
|
17861c1050 | ||
|
|
a63c33a711 | ||
|
|
c84ef117c8 | ||
|
|
834096f282 | ||
|
|
0a31d96ee4 | ||
|
|
3edb733dcb | ||
|
|
9c01cc31e0 | ||
|
|
ea8262855e | ||
|
|
98ea11887e | ||
|
|
18aefcd3f2 | ||
|
|
3a13332533 | ||
|
|
c279ef7bc6 | ||
|
|
55ef0cc8c9 | ||
|
|
babc4f441c | ||
|
|
92d932da55 | ||
|
|
b1b65a3adf | ||
|
|
d6f5e87ccf | ||
|
|
079239ea70 | ||
|
|
93bac29e8c | ||
|
|
c9fea29d92 | ||
|
|
e17895902e | ||
|
|
1596550111 | ||
|
|
c888e17845 | ||
|
|
3dee611b51 | ||
|
|
d6c7a04316 | ||
|
|
900c8a519a | ||
|
|
43acc9f003 | ||
|
|
96eb8fcd9a | ||
|
|
d4bd682ac9 | ||
|
|
61d4783f61 | ||
|
|
00fff466ff | ||
|
|
d8483bef6e | ||
|
|
56a198fcfd | ||
|
|
4e362dc722 | ||
|
|
cfcfc9ecfd | ||
|
|
a3a415adfd | ||
|
|
14c95aa85b | ||
|
|
8d941dad6f | ||
|
|
8628f6637a | ||
|
|
17b7914c35 | ||
|
|
b8dfd5ce4c | ||
|
|
b3749b7fe3 | ||
|
|
3aaf625282 | ||
|
|
9df2360b8b | ||
|
|
d61ac9a135 | ||
|
|
c8fc56d7c4 | ||
|
|
51aad167cc | ||
|
|
e2def66522 | ||
|
|
73e9d9d577 | ||
|
|
a5d7ff65c1 | ||
|
|
05bf60abf7 | ||
|
|
7f627e2c07 | ||
|
|
19e0fe1286 | ||
|
|
b390974019 | ||
|
|
9da65fab09 | ||
|
|
02d2eab5a4 | ||
|
|
985c8b681b | ||
|
|
cf5a4012c0 | ||
|
|
c59e75ef7b | ||
|
|
2dbe344348 |
@@ -1,9 +1,18 @@
|
|||||||
from typing import TYPE_CHECKING
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Sequence
|
||||||
import asyncio
|
import asyncio
|
||||||
import NetUtils
|
import NetUtils
|
||||||
import copy
|
import copy
|
||||||
|
import uuid
|
||||||
|
import Utils
|
||||||
from .Locations import grinch_locations, GrinchLocation
|
from .Locations import grinch_locations, GrinchLocation
|
||||||
from .Items import ALL_ITEMS_TABLE, MISSION_ITEMS_TABLE, GADGETS_TABLE, KEYS_TABLE, GrinchItemData #, SLEIGH_PARTS_TABLE
|
from .Items import (
|
||||||
|
ALL_ITEMS_TABLE,
|
||||||
|
MISSION_ITEMS_TABLE,
|
||||||
|
GADGETS_TABLE,
|
||||||
|
KEYS_TABLE,
|
||||||
|
GrinchItemData,
|
||||||
|
) # , SLEIGH_PARTS_TABLE
|
||||||
import worlds._bizhawk as bizhawk
|
import worlds._bizhawk as bizhawk
|
||||||
from worlds._bizhawk.client import BizHawkClient
|
from worlds._bizhawk.client import BizHawkClient
|
||||||
|
|
||||||
@@ -23,75 +32,146 @@ MAX_DEMO_MODE_CHECK = 30
|
|||||||
# List of Menu Map IDs
|
# List of Menu Map IDs
|
||||||
MENU_MAP_IDS: list[int] = [0x00, 0x02, 0x35, 0x36, 0x37]
|
MENU_MAP_IDS: list[int] = [0x00, 0x02, 0x35, 0x36, 0x37]
|
||||||
|
|
||||||
|
MAX_EGGS: int = 200
|
||||||
|
EGG_COUNT_ADDR: int = 0x010058
|
||||||
|
EGG_ADDR_BYTESIZE: int = 2
|
||||||
|
|
||||||
|
|
||||||
class GrinchClient(BizHawkClient):
|
class GrinchClient(BizHawkClient):
|
||||||
game = "The Grinch"
|
game = "The Grinch"
|
||||||
system = "PSX"
|
system = "PSX"
|
||||||
patch_suffix = ".apgrinch"
|
patch_suffix = ".apgrinch"
|
||||||
items_handling = 0b111
|
items_handling = 0b111
|
||||||
demo_mode_buffer = 0
|
demo_mode_buffer: int = 0
|
||||||
last_map_location = -1
|
last_map_location: int = -1
|
||||||
ingame_log = False
|
ingame_log: bool = False
|
||||||
|
previous_egg_count: int = 0
|
||||||
|
send_ring_link: bool = False
|
||||||
|
unique_client_id: int = 0
|
||||||
|
ring_link_enabled = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.last_received_index = 0
|
self.last_received_index = 0
|
||||||
self.loading_bios_msg = False
|
self.loading_bios_msg = False
|
||||||
self.loc_unlimited_eggs = False
|
self.loc_unlimited_eggs = False
|
||||||
|
self.unique_client_id = 0
|
||||||
|
|
||||||
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
|
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
|
||||||
from CommonClient import logger
|
from CommonClient import logger
|
||||||
|
|
||||||
# TODO Check the ROM data to see if it matches against bytes expected
|
# TODO Check the ROM data to see if it matches against bytes expected
|
||||||
grinch_identifier_ram_address: int = 0x00928C
|
grinch_identifier_ram_address: int = 0x00928C
|
||||||
bios_identifier_ram_address: int = 0x097F30
|
bios_identifier_ram_address: int = 0x097F30
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bytes_actual: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(
|
bytes_actual: bytes = (
|
||||||
grinch_identifier_ram_address, 11, "MainRAM")]))[0]
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx, [(grinch_identifier_ram_address, 11, "MainRAM")]
|
||||||
|
)
|
||||||
|
)[0]
|
||||||
|
|
||||||
psx_rom_name = bytes_actual.decode("ascii")
|
psx_rom_name = bytes_actual.decode("ascii")
|
||||||
if psx_rom_name != "SLUS_011.97":
|
if psx_rom_name != "SLUS_011.97":
|
||||||
bios_bytes_check: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(
|
bios_bytes_check: bytes = (
|
||||||
bios_identifier_ram_address, 24, "MainRAM")]))[0]
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx, [(bios_identifier_ram_address, 24, "MainRAM")]
|
||||||
|
)
|
||||||
|
)[0]
|
||||||
|
|
||||||
if "System ROM Version" in bios_bytes_check.decode("ascii"):
|
if "System ROM Version" in bios_bytes_check.decode("ascii"):
|
||||||
if not self.loading_bios_msg:
|
if not self.loading_bios_msg:
|
||||||
self.loading_bios_msg = True
|
self.loading_bios_msg = True
|
||||||
logger.error("BIOS is currently loading. Will wait up to 5 seconds before retrying.")
|
logger.error(
|
||||||
|
"BIOS is currently loading. Will wait up to 5 seconds before retrying."
|
||||||
|
)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.error("Invalid rom detected. You are not playing Grinch USA Version.")
|
logger.error(
|
||||||
raise Exception("Invalid rom detected. You are not playing Grinch USA Version.")
|
"Invalid rom detected. You are not playing Grinch USA Version."
|
||||||
|
)
|
||||||
|
raise Exception(
|
||||||
|
"Invalid rom detected. You are not playing Grinch USA Version."
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.command_processor.commands["ringlink"] = _cmd_ringlink
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
ctx.game = self.game
|
ctx.game = self.game
|
||||||
ctx.items_handling = self.items_handling
|
ctx.items_handling = self.items_handling
|
||||||
ctx.want_slot_data = True
|
ctx.want_slot_data = True
|
||||||
ctx.watcher_timeout = 0.25
|
ctx.watcher_timeout = 0.125
|
||||||
self.loading_bios_msg = False
|
self.loading_bios_msg = False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None:
|
def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None:
|
||||||
from CommonClient import logger
|
from CommonClient import logger
|
||||||
|
|
||||||
super().on_package(ctx, cmd, args)
|
super().on_package(ctx, cmd, args)
|
||||||
|
|
||||||
match cmd:
|
match cmd:
|
||||||
case "Connected": # On Connect
|
case "Connected": # On Connect
|
||||||
self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"])
|
self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"])
|
||||||
logger.info("You are now connected to the client. "+
|
self.unique_client_id = self._get_uuid()
|
||||||
"There may be a slight delay to check you are not in demo mode before locations start to send.")
|
logger.info(
|
||||||
|
"You are now connected to the client. "
|
||||||
|
+ "There may be a slight delay to check you are not in demo mode before locations start to send."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ring_link_enabled = bool(ctx.slot_data["ring_link"])
|
||||||
|
|
||||||
|
tags = copy.deepcopy(ctx.tags)
|
||||||
|
|
||||||
|
if self.ring_link_enabled:
|
||||||
|
ctx.tags.add("RingLink")
|
||||||
|
|
||||||
|
else:
|
||||||
|
ctx.tags -= {"RingLink"}
|
||||||
|
|
||||||
|
if tags != ctx.tags:
|
||||||
|
Utils.async_start(
|
||||||
|
ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]),
|
||||||
|
"Update RingLink Tags",
|
||||||
|
)
|
||||||
|
|
||||||
|
case "Bounced":
|
||||||
|
if "tags" not in args:
|
||||||
|
return
|
||||||
|
|
||||||
|
if (
|
||||||
|
"RingLink" in ctx.tags
|
||||||
|
and "RingLink" in args["tags"]
|
||||||
|
and args["data"]["source"] != self.unique_client_id
|
||||||
|
):
|
||||||
|
Utils.async_start(
|
||||||
|
self.ring_link_input(args["data"]["amount"], ctx), "SyncEggs"
|
||||||
|
)
|
||||||
|
|
||||||
async def set_auth(self, ctx: "BizHawkClientContext") -> None:
|
async def set_auth(self, ctx: "BizHawkClientContext") -> None:
|
||||||
await ctx.get_username()
|
await ctx.get_username()
|
||||||
|
|
||||||
async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
|
async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
|
||||||
from CommonClient import logger
|
from CommonClient import logger
|
||||||
#If the player is not connected to an AP Server, or their connection was disconnected.
|
|
||||||
if ctx.server is None or ctx.server.socket.closed or ctx.slot_data is None:
|
# If the player is not connected to an AP Server, or their connection was disconnected.
|
||||||
|
if not ctx.slot:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not await self.ingame_checker(ctx):
|
if not await self.ingame_checker(ctx):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not any(
|
||||||
|
task.get_name() == "Grinch EggLink" for task in asyncio.all_tasks()
|
||||||
|
):
|
||||||
|
print("EggLink")
|
||||||
|
self.send_ring_link = True
|
||||||
|
Utils.async_start(self.ring_link_output(ctx), name="Grinch EggLink")
|
||||||
|
|
||||||
await self.location_checker(ctx)
|
await self.location_checker(ctx)
|
||||||
await self.receiving_items_handler(ctx)
|
await self.receiving_items_handler(ctx)
|
||||||
await self.goal_checker(ctx)
|
await self.goal_checker(ctx)
|
||||||
@@ -100,16 +180,46 @@ class GrinchClient(BizHawkClient):
|
|||||||
|
|
||||||
except bizhawk.RequestFailedError as ex:
|
except bizhawk.RequestFailedError as ex:
|
||||||
# The connector didn't respond. Exit handler and return to main loop to reconnect
|
# The connector didn't respond. Exit handler and return to main loop to reconnect
|
||||||
logger.error("Failure to connect / authenticate the grinch. Error details: " + str(ex))
|
logger.error(
|
||||||
|
"Failure to connect / authenticate the grinch. Error details: "
|
||||||
|
+ str(ex)
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as genericEx:
|
||||||
|
# For all other errors, catch this and let the client gracefully disconnect
|
||||||
|
logger.error(
|
||||||
|
"Unknown error occurred while playing the grinch. Error details: "
|
||||||
|
+ str(genericEx)
|
||||||
|
)
|
||||||
|
await ctx.disconnect(False)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def location_checker(self, ctx: "BizHawkClientContext"):
|
async def location_checker(self, ctx: "BizHawkClientContext"):
|
||||||
from CommonClient import logger
|
from CommonClient import logger
|
||||||
|
|
||||||
# Update the AP Server to know what locations are not checked yet.
|
# Update the AP Server to know what locations are not checked yet.
|
||||||
local_locations_checked: list[int] = []
|
local_locations_checked: list[int] = []
|
||||||
|
addr_list_to_read: list[tuple[int, int, str]] = []
|
||||||
local_ap_locations: set[int] = copy.deepcopy(ctx.missing_locations)
|
local_ap_locations: set[int] = copy.deepcopy(ctx.missing_locations)
|
||||||
|
|
||||||
|
# Loop through the first time of everything left to create the list of RAM addresses to read / monitor.
|
||||||
|
for missing_location in local_ap_locations:
|
||||||
|
grinch_loc_name = ctx.location_names.lookup_in_game(missing_location)
|
||||||
|
grinch_loc_ram_data = grinch_locations[grinch_loc_name]
|
||||||
|
missing_addr_list: list[tuple[int, int, str]] = [
|
||||||
|
(read_addr.ram_address, read_addr.byte_size, "MainRAM")
|
||||||
|
for read_addr in grinch_loc_ram_data.update_ram_addr
|
||||||
|
]
|
||||||
|
addr_list_to_read = [*addr_list_to_read, *missing_addr_list]
|
||||||
|
|
||||||
|
returned_bytes: list[bytes] = await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx, addr_list_to_read
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now loop through everything again and this time get the byte value from the above read, convert to int,
|
||||||
|
# and check to see if that ram address has our expected value.
|
||||||
for missing_location in local_ap_locations:
|
for missing_location in local_ap_locations:
|
||||||
# local_location = ctx.location_names.lookup_in_game(missing_location)
|
|
||||||
# Missing location is the AP ID & we need to convert it back to a location name within our game.
|
# Missing location is the AP ID & we need to convert it back to a location name within our game.
|
||||||
# Using the location name, we can then get the Grinch ram data from there.
|
# Using the location name, we can then get the Grinch ram data from there.
|
||||||
grinch_loc_name = ctx.location_names.lookup_in_game(missing_location)
|
grinch_loc_name = ctx.location_names.lookup_in_game(missing_location)
|
||||||
@@ -118,22 +228,41 @@ class GrinchClient(BizHawkClient):
|
|||||||
# Grinch ram data may have more than one address to update, so we are going to loop through all addresses in a location
|
# Grinch ram data may have more than one address to update, so we are going to loop through all addresses in a location
|
||||||
# We use a list here to keep track of all our checks. If they are all true, then and only then do we mark that location as checked.
|
# We use a list here to keep track of all our checks. If they are all true, then and only then do we mark that location as checked.
|
||||||
ram_checked_list: list[bool] = []
|
ram_checked_list: list[bool] = []
|
||||||
|
|
||||||
for addr_to_update in grinch_loc_ram_data.update_ram_addr:
|
for addr_to_update in grinch_loc_ram_data.update_ram_addr:
|
||||||
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
||||||
current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
|
orig_index: int = addr_list_to_read.index(
|
||||||
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
|
(addr_to_update.ram_address, addr_to_update.byte_size, "MainRAM")
|
||||||
|
)
|
||||||
|
value_read_from_bizhawk: int = int.from_bytes(
|
||||||
|
returned_bytes[orig_index], "little"
|
||||||
|
)
|
||||||
|
|
||||||
if is_binary:
|
if is_binary:
|
||||||
ram_checked_list.append((current_ram_address_value & (1 << addr_to_update.binary_bit_pos)) > 0)
|
ram_checked_list.append(
|
||||||
|
(value_read_from_bizhawk & (1 << addr_to_update.binary_bit_pos))
|
||||||
|
> 0
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
expected_int_value = addr_to_update.value
|
expected_int_value = addr_to_update.value
|
||||||
ram_checked_list.append(expected_int_value == current_ram_address_value)
|
ram_checked_list.append(
|
||||||
|
expected_int_value == value_read_from_bizhawk
|
||||||
|
)
|
||||||
|
|
||||||
if all(ram_checked_list):
|
if all(ram_checked_list):
|
||||||
local_locations_checked.append(GrinchLocation.get_apid(grinch_loc_ram_data.id))
|
local_locations_checked.append(
|
||||||
|
GrinchLocation.get_apid(grinch_loc_ram_data.id)
|
||||||
|
)
|
||||||
|
|
||||||
# Update the AP server with the locally checked list of locations (In other words, locations I found in Grinch)
|
# Update the AP server with the locally checked list of locations (In other words, locations I found in Grinch)
|
||||||
locations_sent_to_ap: set[int] = await ctx.check_locations(local_locations_checked)
|
locations_sent_to_ap: set[int] = await ctx.check_locations(
|
||||||
|
local_locations_checked
|
||||||
|
)
|
||||||
|
|
||||||
if len(locations_sent_to_ap) > 0:
|
if len(locations_sent_to_ap) > 0:
|
||||||
await self.remove_physical_items(ctx)
|
await self.remove_physical_items(ctx)
|
||||||
|
|
||||||
ctx.locations_checked = set(local_locations_checked)
|
ctx.locations_checked = set(local_locations_checked)
|
||||||
|
|
||||||
async def receiving_items_handler(self, ctx: "BizHawkClientContext"):
|
async def receiving_items_handler(self, ctx: "BizHawkClientContext"):
|
||||||
@@ -141,13 +270,21 @@ class GrinchClient(BizHawkClient):
|
|||||||
# If the list says that we have 3 items and we already received items, we will ignore and continue.
|
# If the list says that we have 3 items and we already received items, we will ignore and continue.
|
||||||
# Otherwise, we will get the new items and give them to the player.
|
# Otherwise, we will get the new items and give them to the player.
|
||||||
|
|
||||||
self.last_received_index = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
|
self.last_received_index = int.from_bytes(
|
||||||
RECV_ITEM_ADDR, RECV_ITEM_BITSIZE, "MainRAM")]))[0], "little")
|
(
|
||||||
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx, [(RECV_ITEM_ADDR, RECV_ITEM_BITSIZE, "MainRAM")]
|
||||||
|
)
|
||||||
|
)[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
|
||||||
if len(ctx.items_received) == self.last_received_index:
|
if len(ctx.items_received) == self.last_received_index:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ensures we only get the new items that we want to give the player
|
# Ensures we only get the new items that we want to give the player
|
||||||
new_items_only = ctx.items_received[self.last_received_index:]
|
new_items_only = ctx.items_received[self.last_received_index :]
|
||||||
|
ram_addr_dict: dict[int, list[int]] = {}
|
||||||
|
|
||||||
for item_received in new_items_only:
|
for item_received in new_items_only:
|
||||||
local_item = ctx.item_names.lookup_in_game(item_received.item)
|
local_item = ctx.item_names.lookup_in_game(item_received.item)
|
||||||
@@ -155,103 +292,260 @@ class GrinchClient(BizHawkClient):
|
|||||||
|
|
||||||
for addr_to_update in grinch_item_ram_data.update_ram_addr:
|
for addr_to_update in grinch_item_ram_data.update_ram_addr:
|
||||||
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
||||||
current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
|
|
||||||
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
|
if addr_to_update.ram_address in ram_addr_dict.keys():
|
||||||
|
current_ram_address_value = ram_addr_dict[
|
||||||
|
addr_to_update.ram_address
|
||||||
|
][0]
|
||||||
|
else:
|
||||||
|
current_ram_address_value = int.from_bytes(
|
||||||
|
(
|
||||||
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
addr_to_update.ram_address,
|
||||||
|
addr_to_update.byte_size,
|
||||||
|
"MainRAM",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
|
||||||
if is_binary:
|
if is_binary:
|
||||||
current_ram_address_value = (current_ram_address_value | (1 << addr_to_update.binary_bit_pos))
|
current_ram_address_value = current_ram_address_value | (
|
||||||
|
1 << addr_to_update.binary_bit_pos
|
||||||
|
)
|
||||||
|
|
||||||
elif addr_to_update.update_existing_value:
|
elif addr_to_update.update_existing_value:
|
||||||
# Grabs minimum value of a list of numbers and makes sure it does not go above max count possible
|
# Grabs minimum value of a list of numbers and makes sure it does not go above max count possible
|
||||||
current_ram_address_value += addr_to_update.value
|
current_ram_address_value += addr_to_update.value
|
||||||
current_ram_address_value = min(current_ram_address_value, addr_to_update.max_count)
|
current_ram_address_value = min(
|
||||||
|
current_ram_address_value, addr_to_update.max_count
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
current_ram_address_value = addr_to_update.value
|
current_ram_address_value = addr_to_update.value
|
||||||
|
|
||||||
# Write the updated value back into RAM
|
# Write the updated value back into RAM
|
||||||
await self.update_and_validate_address(ctx, addr_to_update.ram_address, current_ram_address_value, addr_to_update.bit_size)
|
ram_addr_dict[addr_to_update.ram_address] = [
|
||||||
# await bizhawk.write(ctx.bizhawk_ctx, [(addr_to_update.ram_address,
|
current_ram_address_value,
|
||||||
# current_ram_address_value.to_bytes(addr_to_update.bit_size, "little"), "MainRAM")])
|
addr_to_update.byte_size,
|
||||||
|
]
|
||||||
|
|
||||||
self.last_received_index += 1
|
self.last_received_index += 1
|
||||||
await self.update_and_validate_address(ctx, RECV_ITEM_ADDR, self.last_received_index, RECV_ITEM_BITSIZE)
|
|
||||||
|
# Update the latest received item index to ram as well.
|
||||||
|
ram_addr_dict[RECV_ITEM_ADDR] = [self.last_received_index, RECV_ITEM_BITSIZE]
|
||||||
|
|
||||||
|
await bizhawk.write(
|
||||||
|
ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)
|
||||||
|
)
|
||||||
|
|
||||||
async def goal_checker(self, ctx: "BizHawkClientContext"):
|
async def goal_checker(self, ctx: "BizHawkClientContext"):
|
||||||
if not ctx.finished_game:
|
if not ctx.finished_game:
|
||||||
goal_loc = grinch_locations["Neutralizing Santa"]
|
goal_loc = grinch_locations["MC - Sleigh Ride - Neutralizing Santa"]
|
||||||
goal_ram_address = goal_loc.update_ram_addr[0]
|
goal_ram_address = goal_loc.update_ram_addr[0]
|
||||||
current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
|
current_ram_address_value = int.from_bytes(
|
||||||
goal_ram_address.ram_address, goal_ram_address.bit_size, "MainRAM")]))[0], "little")
|
(
|
||||||
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
goal_ram_address.ram_address,
|
||||||
|
goal_ram_address.byte_size,
|
||||||
|
"MainRAM",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
|
||||||
if (current_ram_address_value & (1 << goal_ram_address.binary_bit_pos)) > 0:
|
if (current_ram_address_value & (1 << goal_ram_address.binary_bit_pos)) > 0:
|
||||||
|
# if current_ram_address_value == goal_ram_address.value:
|
||||||
ctx.finished_game = True
|
ctx.finished_game = True
|
||||||
await ctx.send_msgs([{
|
await ctx.send_msgs(
|
||||||
"cmd": "StatusUpdate",
|
[
|
||||||
"status": NetUtils.ClientStatus.CLIENT_GOAL,
|
{
|
||||||
}])
|
"cmd": "StatusUpdate",
|
||||||
|
"status": NetUtils.ClientStatus.CLIENT_GOAL,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# This function's entire purpose is to take away items we physically received ingame, but have not received from AP
|
# This function's entire purpose is to take away items we physically received ingame, but have not received from AP
|
||||||
async def remove_physical_items(self, ctx: "BizHawkClientContext"):
|
async def remove_physical_items(self, ctx: "BizHawkClientContext"):
|
||||||
|
ram_addr_dict: dict[int, list[int]] = {}
|
||||||
|
|
||||||
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
|
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
|
||||||
items_to_check: dict[str, GrinchItemData] = {**GADGETS_TABLE} #, **SLEIGH_PARTS_TABLE
|
items_to_check: dict[str, GrinchItemData] = {
|
||||||
heart_count = len(list(item_id for item_id in list_recv_itemids if item_id == 42570))
|
**GADGETS_TABLE
|
||||||
|
} # , **SLEIGH_PARTS_TABLE
|
||||||
|
heart_count = len(
|
||||||
|
list(item_id for item_id in list_recv_itemids if item_id == 42570)
|
||||||
|
)
|
||||||
heart_item_data = ALL_ITEMS_TABLE["Heart of Stone"]
|
heart_item_data = ALL_ITEMS_TABLE["Heart of Stone"]
|
||||||
await self.update_and_validate_address(ctx, heart_item_data.update_ram_addr[0].ram_address, min(heart_count, 4), 1)
|
ram_addr_dict[heart_item_data.update_ram_addr[0].ram_address] = [
|
||||||
|
min(heart_count, 4),
|
||||||
|
1,
|
||||||
|
]
|
||||||
|
|
||||||
# Setting Who Lake Mission Count back to 0 to prevent warping after completing 3 missions
|
# Setting mission count for all accesses back to 0 to prevent warping/unlocking after completing 3 missions
|
||||||
await self.update_and_validate_address(ctx,0x0100F0, 0, 4)
|
ram_addr_dict[0x0100F0] = [0, 4]
|
||||||
|
|
||||||
for (item_name, item_data) in items_to_check.items():
|
for item_name, item_data in items_to_check.items():
|
||||||
# If item is an event or already been received, ignore.
|
# If item is an event or already been received, ignore.
|
||||||
if item_data.id is None or GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
|
if (
|
||||||
|
item_data.id is None
|
||||||
|
or GrinchLocation.get_apid(item_data.id) in list_recv_itemids
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# This assumes we don't have the item so we must set all the data to 0
|
# This assumes we don't have the item so we must set all the data to 0
|
||||||
for addr_to_update in item_data.update_ram_addr:
|
for addr_to_update in item_data.update_ram_addr:
|
||||||
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
||||||
|
|
||||||
if is_binary:
|
if is_binary:
|
||||||
current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
|
if addr_to_update.ram_address in ram_addr_dict.keys():
|
||||||
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
|
current_bin_value = ram_addr_dict[addr_to_update.ram_address][0]
|
||||||
|
|
||||||
|
else:
|
||||||
|
current_bin_value = int.from_bytes(
|
||||||
|
(
|
||||||
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
addr_to_update.ram_address,
|
||||||
|
addr_to_update.byte_size,
|
||||||
|
"MainRAM",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
current_bin_value &= ~(1 << addr_to_update.binary_bit_pos)
|
current_bin_value &= ~(1 << addr_to_update.binary_bit_pos)
|
||||||
await self.update_and_validate_address(ctx, addr_to_update.ram_address, current_bin_value, 1)
|
ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
await self.update_and_validate_address(ctx, addr_to_update.ram_address, 0, 1)
|
ram_addr_dict[addr_to_update.ram_address] = [
|
||||||
|
0,
|
||||||
|
addr_to_update.byte_size,
|
||||||
|
]
|
||||||
|
|
||||||
|
await bizhawk.write(
|
||||||
|
ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)
|
||||||
|
)
|
||||||
|
|
||||||
|
def convert_dict_to_ram_list(
|
||||||
|
self, addr_dict: dict[int, list[int]]
|
||||||
|
) -> list[tuple[int, Sequence[int], str]]:
|
||||||
|
addr_list_to_update: list[tuple[int, Sequence[int], str]] = []
|
||||||
|
|
||||||
|
for key, val in addr_dict.items():
|
||||||
|
addr_list_to_update.append(
|
||||||
|
(key, val[0].to_bytes(val[1], "little"), "MainRAM")
|
||||||
|
)
|
||||||
|
|
||||||
|
return addr_list_to_update
|
||||||
|
|
||||||
# Removes the regional access until you actually received it from AP.
|
# Removes the regional access until you actually received it from AP.
|
||||||
async def constant_address_update(self, ctx: "BizHawkClientContext"):
|
async def constant_address_update(self, ctx: "BizHawkClientContext"):
|
||||||
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
|
ram_addr_dict: dict[int, list[int]] = {}
|
||||||
items_to_check: dict[str, GrinchItemData] = {**KEYS_TABLE, **MISSION_ITEMS_TABLE}
|
|
||||||
|
|
||||||
for (item_name, item_data) in items_to_check.items():
|
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
|
||||||
|
items_to_check: dict[str, GrinchItemData] = {
|
||||||
|
**KEYS_TABLE,
|
||||||
|
**MISSION_ITEMS_TABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
for item_name, item_data in items_to_check.items():
|
||||||
# If item is an event or already been received, ignore.
|
# If item is an event or already been received, ignore.
|
||||||
if item_data.id is None or GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
|
if (
|
||||||
|
item_data.id is None
|
||||||
|
): # or GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# This assumes we don't have the item so we must set all the data to 0
|
# This will either constantly update the item to ensure you still have it or take it away if you don't deserve it
|
||||||
for addr_to_update in item_data.update_ram_addr:
|
for addr_to_update in item_data.update_ram_addr:
|
||||||
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
||||||
|
|
||||||
if is_binary:
|
if is_binary:
|
||||||
current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
|
if addr_to_update.ram_address in ram_addr_dict.keys():
|
||||||
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
|
current_bin_value = ram_addr_dict[addr_to_update.ram_address][0]
|
||||||
current_bin_value &= ~(1 << addr_to_update.binary_bit_pos)
|
|
||||||
await self.update_and_validate_address(ctx, addr_to_update.ram_address, current_bin_value, 1)
|
else:
|
||||||
|
current_bin_value = int.from_bytes(
|
||||||
|
(
|
||||||
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
addr_to_update.ram_address,
|
||||||
|
addr_to_update.byte_size,
|
||||||
|
"MainRAM",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
|
||||||
|
if GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
|
||||||
|
current_bin_value |= 1 << addr_to_update.binary_bit_pos
|
||||||
|
|
||||||
|
else:
|
||||||
|
current_bin_value &= ~(1 << addr_to_update.binary_bit_pos)
|
||||||
|
|
||||||
|
ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
await self.update_and_validate_address(ctx, addr_to_update.ram_address, 0, 1)
|
if GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
|
||||||
|
ram_addr_dict[addr_to_update.ram_address] = [
|
||||||
|
addr_to_update.value,
|
||||||
|
addr_to_update.byte_size,
|
||||||
|
]
|
||||||
|
|
||||||
|
else:
|
||||||
|
ram_addr_dict[addr_to_update.ram_address] = [
|
||||||
|
0,
|
||||||
|
addr_to_update.byte_size,
|
||||||
|
]
|
||||||
|
|
||||||
|
await bizhawk.write(
|
||||||
|
ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)
|
||||||
|
)
|
||||||
|
|
||||||
async def ingame_checker(self, ctx: "BizHawkClientContext"):
|
async def ingame_checker(self, ctx: "BizHawkClientContext"):
|
||||||
from CommonClient import logger
|
from CommonClient import logger
|
||||||
|
|
||||||
ingame_map_id = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
|
ingame_map_id = int.from_bytes(
|
||||||
0x010000, 1, "MainRAM")]))[0], "little")
|
(await bizhawk.read(ctx.bizhawk_ctx, [(0x010000, 1, "MainRAM")]))[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
initial_cutscene_checker = int.from_bytes(
|
||||||
|
(await bizhawk.read(ctx.bizhawk_ctx, [(0x010094, 1, "MainRAM")]))[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
|
||||||
#If not in game or at a menu, or loading the publisher logos
|
# If not in game or at a menu, or loading the publisher logos
|
||||||
if ingame_map_id <= 0x04 or ingame_map_id >= 0x35:
|
if ingame_map_id <= 0x04 or ingame_map_id >= 0x35:
|
||||||
|
self.ingame_log = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#If grinch has changed maps
|
# If grinch has changed maps
|
||||||
if not ingame_map_id == self.last_map_location:
|
if not ingame_map_id == self.last_map_location:
|
||||||
# If the last "map" we were on was a menu or a publisher logo
|
# If the last "map" we were on was a menu or a publisher logo
|
||||||
if self.last_map_location in MENU_MAP_IDS:
|
if self.last_map_location in MENU_MAP_IDS:
|
||||||
# Reset our demo mode checker just in case the game is in demo mode.
|
# Reset our demo mode checker just in case the game is in demo mode.
|
||||||
self.demo_mode_buffer = 0
|
self.demo_mode_buffer = 0
|
||||||
self.ingame_log = False
|
self.ingame_log = False
|
||||||
|
|
||||||
|
if initial_cutscene_checker != 1:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Update the previous map we were on to be the current map.
|
# Update the previous map we were on to be the current map.
|
||||||
@@ -263,25 +557,129 @@ class GrinchClient(BizHawkClient):
|
|||||||
self.demo_mode_buffer += 1
|
self.demo_mode_buffer += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
demo_mode = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
|
demo_mode = int.from_bytes(
|
||||||
0x01008A, 1, "MainRAM")]))[0], "little")
|
(await bizhawk.read(ctx.bizhawk_ctx, [(0x01008A, 1, "MainRAM")]))[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
|
||||||
if demo_mode == 1:
|
if demo_mode == 1:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.ingame_log:
|
if not self.ingame_log:
|
||||||
logger.info("You can now start sending locations from the Grinch!")
|
logger.info("You can now start sending locations from the Grinch!")
|
||||||
self.ingame_log = True
|
self.ingame_log = True
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def option_handler(self, ctx: "BizHawkClientContext"):
|
async def option_handler(self, ctx: "BizHawkClientContext"):
|
||||||
if self.loc_unlimited_eggs:
|
if self.loc_unlimited_eggs:
|
||||||
max_eggs: int = 200
|
await bizhawk.write(
|
||||||
await bizhawk.write(ctx.bizhawk_ctx, [(0x010058, max_eggs.to_bytes(2,"little"), "MainRAM")])
|
ctx.bizhawk_ctx,
|
||||||
|
[(EGG_COUNT_ADDR, MAX_EGGS.to_bytes(2, "little"), "MainRAM")],
|
||||||
|
)
|
||||||
|
|
||||||
async def update_and_validate_address(self, ctx: "BizHawkClientContext", address_to_validate: int, expected_value: int, byte_size: int):
|
async def ring_link_output(self, ctx: "BizHawkClientContext"):
|
||||||
await bizhawk.write(ctx.bizhawk_ctx, [(address_to_validate, expected_value.to_bytes(byte_size, "little"), "MainRAM")])
|
from CommonClient import logger
|
||||||
current_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(address_to_validate, byte_size, "MainRAM")]))[0], "little")
|
|
||||||
if not current_value == expected_value:
|
while self.send_ring_link and ctx.slot:
|
||||||
if address_to_validate == 0x010000 or address_to_validate == 0x08FB94: # TODO Temporairly skips teleportation addresses; to be changed later on.
|
|
||||||
return
|
try:
|
||||||
raise Exception("Unable to update address as expected. Address: "+ str(address_to_validate)+"; Expected Value: "+str(expected_value))
|
current_egg_count = int.from_bytes(
|
||||||
|
(
|
||||||
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")],
|
||||||
|
)
|
||||||
|
)[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
|
||||||
|
if (current_egg_count - self.previous_egg_count) != 0:
|
||||||
|
msg = {
|
||||||
|
"cmd": "Bounce",
|
||||||
|
"data": {
|
||||||
|
"time": time.time(),
|
||||||
|
"source": self.unique_client_id,
|
||||||
|
"amount": current_egg_count - self.previous_egg_count,
|
||||||
|
},
|
||||||
|
"tags": ["RingLink"],
|
||||||
|
}
|
||||||
|
await ctx.send_msgs([msg])
|
||||||
|
self.previous_egg_count = current_egg_count
|
||||||
|
# logger.info(f"RingLink: You sent {str(current_egg_count - self.previous_egg_count)} rotten eggs.")
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
logger.error(
|
||||||
|
"While monitoring grinch's egg count ingame, an error occurred. Details:"
|
||||||
|
+ str(ex)
|
||||||
|
)
|
||||||
|
self.send_ring_link = False
|
||||||
|
|
||||||
|
if not ctx.slot:
|
||||||
|
logger.info(
|
||||||
|
"You must be connected to the multi-world in order for RingLink to work properly."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def ring_link_input(self, egg_amount: int, ctx: "BizHawkClientContext"):
|
||||||
|
from CommonClient import logger
|
||||||
|
|
||||||
|
game_egg_count = int.from_bytes(
|
||||||
|
(
|
||||||
|
await bizhawk.read(
|
||||||
|
ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]
|
||||||
|
)
|
||||||
|
)[0],
|
||||||
|
"little",
|
||||||
|
)
|
||||||
|
non_neg_eggs = (
|
||||||
|
game_egg_count + egg_amount if game_egg_count + egg_amount > 0 else 0
|
||||||
|
)
|
||||||
|
current_egg_count = min(non_neg_eggs, MAX_EGGS)
|
||||||
|
|
||||||
|
await bizhawk.write(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
EGG_COUNT_ADDR,
|
||||||
|
int(current_egg_count).to_bytes(EGG_ADDR_BYTESIZE, "little"),
|
||||||
|
"MainRAM",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.previous_egg_count = current_egg_count
|
||||||
|
# logger.info(f"RingLink: You received {str(egg_amount)} rotten eggs.")
|
||||||
|
|
||||||
|
def _get_uuid(self) -> int:
|
||||||
|
string_id = str(uuid.uuid4())
|
||||||
|
uid: int = 0
|
||||||
|
for char in string_id:
|
||||||
|
uid += ord(char)
|
||||||
|
return uid
|
||||||
|
|
||||||
|
|
||||||
|
def _cmd_ringlink(self):
|
||||||
|
"""Toggle ringling from client. Overrides default setting."""
|
||||||
|
if not self.ctx.slot:
|
||||||
|
return
|
||||||
|
|
||||||
|
Utils.async_start(
|
||||||
|
_update_ring_link(self.ctx, not "RingLink" in self.ctx.tags),
|
||||||
|
name="Update RingLink",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _update_ring_link(ctx: "BizHawkClientContext", ring_link: bool):
|
||||||
|
"""Helper function to set Ring Link connection tag on/off and update the connection if already connected."""
|
||||||
|
old_tags = copy.deepcopy(ctx.tags)
|
||||||
|
|
||||||
|
if ring_link:
|
||||||
|
ctx.tags.add("RingLink")
|
||||||
|
|
||||||
|
else:
|
||||||
|
ctx.tags -= {"RingLink"}
|
||||||
|
|
||||||
|
if old_tags != ctx.tags and ctx.server and not ctx.server.socket.closed:
|
||||||
|
await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}])
|
||||||
|
|||||||
@@ -1,242 +1,609 @@
|
|||||||
from typing import NamedTuple, Optional
|
from typing import NamedTuple, Optional
|
||||||
|
|
||||||
from .RamHandler import GrinchRamData
|
from .RamHandler import GrinchRamData, UpdateMethod
|
||||||
from BaseClasses import Item
|
from BaseClasses import Item
|
||||||
from BaseClasses import ItemClassification as IC #IC can be any name, saves having to type the whole word in code
|
from BaseClasses import (
|
||||||
|
ItemClassification as IC,
|
||||||
|
) # IC can be any name, saves having to type the whole word in code
|
||||||
|
|
||||||
|
|
||||||
class GrinchItemData(NamedTuple):
|
class GrinchItemData(NamedTuple):
|
||||||
item_group: str #arbituary that can be whatever it can be, basically the field/property for item groups
|
item_group: list[
|
||||||
|
str
|
||||||
|
] # arbituary that can be whatever it can be, basically the field/property for item groups
|
||||||
id: Optional[int]
|
id: Optional[int]
|
||||||
classification: IC
|
classification: IC
|
||||||
update_ram_addr: list[GrinchRamData]
|
update_ram_addr: list[GrinchRamData]
|
||||||
second_item_group: Optional[str] = None
|
|
||||||
|
|
||||||
class GrinchItem(Item):
|
class GrinchItem(Item):
|
||||||
game: str = "The Grinch"
|
game: str = "The Grinch"
|
||||||
|
|
||||||
#Tells server what item id it is
|
# Tells server what item id it is
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_apid(id: int):
|
def get_apid(id: int):
|
||||||
#If you give me an input id, I will return the Grinch equivalent server/ap id
|
# If you give me an input id, I will return the Grinch equivalent server/ap id
|
||||||
base_id: int = 42069
|
base_id: int = 42069
|
||||||
return base_id + id if id is not None else None
|
return base_id + id if id is not None else None
|
||||||
|
|
||||||
def __init__(self, name: str, player: int, data: GrinchItemData):
|
def __init__(self, name: str, player: int, data: GrinchItemData):
|
||||||
super(GrinchItem, self).__init__(name,data.classification, GrinchItem.get_apid(data.id), player)
|
super(GrinchItem, self).__init__(
|
||||||
|
name, data.classification, GrinchItem.get_apid(data.id), player
|
||||||
|
)
|
||||||
|
|
||||||
self.type = data.item_group
|
self.type = data.item_group
|
||||||
self.item_id = data.id
|
self.item_id = data.id
|
||||||
|
|
||||||
#allows hinting of items via category
|
|
||||||
|
# allows hinting of items via category
|
||||||
def get_item_names_per_category() -> dict[str, set[str]]:
|
def get_item_names_per_category() -> dict[str, set[str]]:
|
||||||
categories: dict[str, set[str]] = {}
|
categories: dict[str, set[str]] = {}
|
||||||
|
|
||||||
for name, data in ALL_ITEMS_TABLE.items():
|
for name, data in ALL_ITEMS_TABLE.items():
|
||||||
categories.setdefault(data.item_group, set()).add(name)
|
for group in data.item_group: # iterate over each category
|
||||||
|
categories.setdefault(group, set()).add(name)
|
||||||
|
|
||||||
return categories
|
return categories
|
||||||
|
|
||||||
#Gadgets
|
|
||||||
#All gadgets require at least 4 different blueprints to be unlocked in the computer in Mount Crumpit.
|
class grinch_items:
|
||||||
|
class gadgets:
|
||||||
|
BINOCULARS: str = "Binoculars"
|
||||||
|
ROCKET_EGG_LAUNCHER: str = "Rotten Egg Launcher"
|
||||||
|
ROCKET_SPRING: str = "Rocket Spring"
|
||||||
|
SLIME_SHOOTER: str = "Slime Shooter"
|
||||||
|
OCTOPUS_CLIMBING_DEVICE: str = "Octopus Climbing Device"
|
||||||
|
MARINE_MOBILE: str = "Marine Mobile"
|
||||||
|
GRINCH_COPTER: str = "Grinch Copter"
|
||||||
|
|
||||||
|
class keys:
|
||||||
|
WHOVILLE: str = "Whoville Vacuum Tube"
|
||||||
|
WHO_FOREST: str = "Who Forest Vacuum Tube"
|
||||||
|
WHO_DUMP: str = "Who Dump Vacuum Tube"
|
||||||
|
WHO_LAKE: str = "Who Lake Vacuum Tube"
|
||||||
|
PROGRESSIVE_VACUUM_TUBE: str = "Progressive Vacuum Tube"
|
||||||
|
SLEIGH_ROOM_KEY: str = "Sleigh Room Key"
|
||||||
|
|
||||||
|
class moves:
|
||||||
|
PANCAKE: str = "Pancake"
|
||||||
|
BAD_BREATH: str = "Bad Breath"
|
||||||
|
SIEZE: str = "Seize"
|
||||||
|
MAX: str = "Max"
|
||||||
|
SNEAK: str = "Sneak"
|
||||||
|
|
||||||
|
class level_items:
|
||||||
|
WV_WHO_CLOAK: str = "Who Cloak"
|
||||||
|
WV_PAINT_BUCKET: str = "Painting Bucket"
|
||||||
|
WV_HAMMER: str = "Hammer"
|
||||||
|
WV_SCULPTIN_TOOLS: str = "Sculpting Tools"
|
||||||
|
WF_GLUE_BUCKET: str = "Glue Bucket"
|
||||||
|
WF_CABLE_CAR_ACCESS_CARD: str = "Cable Car Access Card"
|
||||||
|
WD_SCISSORS: str = "Scissors"
|
||||||
|
WL_ROPE: str = "Rope"
|
||||||
|
WL_HOOK: str = "Hook"
|
||||||
|
WL_DRILLL: str = "Drill"
|
||||||
|
WL_SCOUT_CLOTHES: str = "Scout Clothes"
|
||||||
|
|
||||||
|
class useful_items:
|
||||||
|
HEART_OF_STONE: str = "Heart of Stone"
|
||||||
|
|
||||||
|
class trap_items:
|
||||||
|
DEPLETION_TRAP: str = "Depletion Trap"
|
||||||
|
DUMP_IT_TO_CRUMPIT: str = "Dump it to Crumpit"
|
||||||
|
WHO_SENT_ME_BACK: str = "Who sent me back?"
|
||||||
|
|
||||||
|
|
||||||
|
class grinch_categories:
|
||||||
|
FILLER: str = "Filler"
|
||||||
|
GADGETS: str = "Gadgets"
|
||||||
|
HEALING_ITEMS: str = "Healing Items"
|
||||||
|
MISSION_SPECIFIC_ITEMS: str = "Mission Specific Items"
|
||||||
|
MOVES: str = "Moves"
|
||||||
|
REQUIRED_ITEM: str = "Required Items"
|
||||||
|
ROTTEN_EGG_BUNDLES: str = "Rotten Egg Bundles"
|
||||||
|
SLEIGH_ROOM: str = "Sleigh Room"
|
||||||
|
TRAPS: str = "Traps"
|
||||||
|
USEFUL_ITEMS: str = "Useful Items"
|
||||||
|
VACUUM_TUBES: str = "Vacuum Tubes"
|
||||||
|
|
||||||
|
|
||||||
|
# Gadgets
|
||||||
|
# All gadgets require at least 4 different blueprints to be unlocked in the computer in Mount Crumpit.
|
||||||
GADGETS_TABLE: dict[str, GrinchItemData] = {
|
GADGETS_TABLE: dict[str, GrinchItemData] = {
|
||||||
"Binoculars": GrinchItemData("Gadgets", 100, IC.useful,
|
grinch_items.gadgets.BINOCULARS: GrinchItemData(
|
||||||
[GrinchRamData(0x0102B6, value=0x40), GrinchRamData(0x0102B7, value=0x41),
|
[grinch_categories.GADGETS],
|
||||||
GrinchRamData(0x0102B8, value=0x44), GrinchRamData(0x0102B9, value=0x45),
|
100,
|
||||||
# GrinchRamData(0x0100BC, binary_bit_pos=0)
|
IC.useful,
|
||||||
]),
|
[
|
||||||
"Rotten Egg Launcher": GrinchItemData("Gadgets", 101, IC.progression,
|
GrinchRamData(0x0102B6, value=0x40),
|
||||||
[GrinchRamData(0x0102BA, value=0x40), GrinchRamData(0x0102BB, value=0x41),
|
GrinchRamData(0x0102B7, value=0x41),
|
||||||
GrinchRamData(0x0102BC, value=0x44), GrinchRamData(0x0102BD, value=0x45),
|
GrinchRamData(0x0102B8, value=0x44),
|
||||||
# GrinchRamData(0x0100BC, binary_bit_pos=1)
|
GrinchRamData(0x0102B9, value=0x45),
|
||||||
]),
|
# GrinchRamData(0x0100BC, binary_bit_pos=0)
|
||||||
"Rocket Spring": GrinchItemData("Gadgets", 102, IC.progression,
|
],
|
||||||
[GrinchRamData(0x0102BE, value=0x40), GrinchRamData(0x0102BF, value=0x41),
|
),
|
||||||
GrinchRamData(0x0102C0, value=0x42), GrinchRamData(0x0102C1, value=0x44),
|
grinch_items.gadgets.ROCKET_EGG_LAUNCHER: GrinchItemData(
|
||||||
GrinchRamData(0x0102C2, value=0x45), GrinchRamData(0x0102C3, value=0x46),
|
[grinch_categories.GADGETS],
|
||||||
GrinchRamData(0x0102C4, value=0x48), GrinchRamData(0x0102C5, value=0x49),
|
101,
|
||||||
GrinchRamData(0x0102C6, value=0x4A),
|
IC.progression,
|
||||||
# GrinchRamData(0x0100BC, binary_bit_pos=2)
|
[
|
||||||
]),
|
GrinchRamData(0x0102BA, value=0x40),
|
||||||
"Slime Shooter": GrinchItemData("Gadgets", 103, IC.progression,
|
GrinchRamData(0x0102BB, value=0x41),
|
||||||
[GrinchRamData(0x0102C7, value=0x40), GrinchRamData(0x0102C8, value=0x41),
|
GrinchRamData(0x0102BC, value=0x44),
|
||||||
GrinchRamData(0x0102C9, value=0x42), GrinchRamData(0x0102CA, value=0x44),
|
GrinchRamData(0x0102BD, value=0x45),
|
||||||
GrinchRamData(0x0102CB, value=0x45), GrinchRamData(0x0102CC, value=0x46),
|
# GrinchRamData(0x0100BC, binary_bit_pos=1)
|
||||||
GrinchRamData(0x0102CD, value=0x48), GrinchRamData(0x0102CE, value=0x49),
|
],
|
||||||
GrinchRamData(0x0102CF, value=0x4A),
|
),
|
||||||
# GrinchRamData(0x0100BC, binary_bit_pos=3)
|
grinch_items.gadgets.ROCKET_SPRING: GrinchItemData(
|
||||||
]),
|
[grinch_categories.GADGETS],
|
||||||
"Octopus Climbing Device": GrinchItemData("Gadgets", 104, IC.progression,
|
102,
|
||||||
[GrinchRamData(0x0102D0, value=0x40), GrinchRamData(0x0102D1, value=0x41),
|
IC.progression,
|
||||||
GrinchRamData(0x0102D2, value=0x42), GrinchRamData(0x0102D3, value=0x44),
|
[
|
||||||
GrinchRamData(0x0102D4, value=0x45), GrinchRamData(0x0102D5, value=0x46),
|
GrinchRamData(0x0102BE, value=0x40),
|
||||||
GrinchRamData(0x0102D6, value=0x48), GrinchRamData(0x0102D7, value=0x49),
|
GrinchRamData(0x0102BF, value=0x41),
|
||||||
GrinchRamData(0x0102D8, value=0x4A),
|
GrinchRamData(0x0102C0, value=0x42),
|
||||||
# GrinchRamData(0x0100BC, binary_bit_pos=4)
|
GrinchRamData(0x0102C1, value=0x44),
|
||||||
]),
|
GrinchRamData(0x0102C2, value=0x45),
|
||||||
"Marine Mobile": GrinchItemData("Gadgets", 105, IC.progression,
|
GrinchRamData(0x0102C3, value=0x46),
|
||||||
[GrinchRamData(0x0102D9, value=0x40), GrinchRamData(0x0102DA, value=0x41),
|
GrinchRamData(0x0102C4, value=0x48),
|
||||||
GrinchRamData(0x0102DB, value=0x42), GrinchRamData(0x0102DC, value=0x43),
|
GrinchRamData(0x0102C5, value=0x49),
|
||||||
GrinchRamData(0x0102DD, value=0x44), GrinchRamData(0x0102DE, value=0x45),
|
GrinchRamData(0x0102C6, value=0x4A),
|
||||||
GrinchRamData(0x0102DF, value=0x46), GrinchRamData(0x0102E0, value=0x47),
|
# GrinchRamData(0x0100BC, binary_bit_pos=2)
|
||||||
GrinchRamData(0x0102E1, value=0x48), GrinchRamData(0x0102E2, value=0x49),
|
],
|
||||||
GrinchRamData(0x0102E3, value=0x4A), GrinchRamData(0x0102E4, value=0x4B),
|
),
|
||||||
GrinchRamData(0x0102E5, value=0x4C), GrinchRamData(0x0102E6, value=0x4D),
|
grinch_items.gadgets.SLIME_SHOOTER: GrinchItemData(
|
||||||
GrinchRamData(0x0102E7, value=0x4E), GrinchRamData(0x0102E8, value=0x4F),
|
[
|
||||||
# GrinchRamData(0x0100BC, binary_bit_pos=5)
|
grinch_categories.GADGETS,
|
||||||
]),
|
"Slime Gun", # For canon --MarioSpore
|
||||||
"Grinch Copter": GrinchItemData("Gadgets", 106, IC.progression,
|
],
|
||||||
[GrinchRamData(0x0102E9, value=0x40), GrinchRamData(0x0102EA, value=0x41),
|
103,
|
||||||
GrinchRamData(0x0102EB, value=0x42), GrinchRamData(0x0102EC, value=0x43),
|
IC.progression,
|
||||||
GrinchRamData(0x0102ED, value=0x44), GrinchRamData(0x0102EE, value=0x45),
|
[
|
||||||
GrinchRamData(0x0102EF, value=0x46), GrinchRamData(0x0102F0, value=0x47),
|
GrinchRamData(0x0102C7, value=0x40),
|
||||||
GrinchRamData(0x0102F1, value=0x48), GrinchRamData(0x0102F2, value=0x49),
|
GrinchRamData(0x0102C8, value=0x41),
|
||||||
GrinchRamData(0x0102F3, value=0x4A), GrinchRamData(0x0102F4, value=0x4B),
|
GrinchRamData(0x0102C9, value=0x42),
|
||||||
GrinchRamData(0x0102F5, value=0x4C), GrinchRamData(0x0102F6, value=0x4D),
|
GrinchRamData(0x0102CA, value=0x44),
|
||||||
GrinchRamData(0x0102F7, value=0x4E), GrinchRamData(0x0102F8, value=0x4F),
|
GrinchRamData(0x0102CB, value=0x45),
|
||||||
# GrinchRamData(0x0100BC, binary_bit_pos=6)
|
GrinchRamData(0x0102CC, value=0x46),
|
||||||
])
|
GrinchRamData(0x0102CD, value=0x48),
|
||||||
|
GrinchRamData(0x0102CE, value=0x49),
|
||||||
|
GrinchRamData(0x0102CF, value=0x4A),
|
||||||
|
# GrinchRamData(0x0100BC, binary_bit_pos=3)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
grinch_items.gadgets.OCTOPUS_CLIMBING_DEVICE: GrinchItemData(
|
||||||
|
[grinch_categories.GADGETS],
|
||||||
|
104,
|
||||||
|
IC.progression,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x0102D0, value=0x40),
|
||||||
|
GrinchRamData(0x0102D1, value=0x41),
|
||||||
|
GrinchRamData(0x0102D2, value=0x42),
|
||||||
|
GrinchRamData(0x0102D3, value=0x44),
|
||||||
|
GrinchRamData(0x0102D4, value=0x45),
|
||||||
|
GrinchRamData(0x0102D5, value=0x46),
|
||||||
|
GrinchRamData(0x0102D6, value=0x48),
|
||||||
|
GrinchRamData(0x0102D7, value=0x49),
|
||||||
|
GrinchRamData(0x0102D8, value=0x4A),
|
||||||
|
# GrinchRamData(0x0100BC, binary_bit_pos=4)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
grinch_items.gadgets.MARINE_MOBILE: GrinchItemData(
|
||||||
|
[grinch_categories.GADGETS],
|
||||||
|
105,
|
||||||
|
IC.progression,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x0102D9, value=0x40),
|
||||||
|
GrinchRamData(0x0102DA, value=0x41),
|
||||||
|
GrinchRamData(0x0102DB, value=0x42),
|
||||||
|
GrinchRamData(0x0102DC, value=0x43),
|
||||||
|
GrinchRamData(0x0102DD, value=0x44),
|
||||||
|
GrinchRamData(0x0102DE, value=0x45),
|
||||||
|
GrinchRamData(0x0102DF, value=0x46),
|
||||||
|
GrinchRamData(0x0102E0, value=0x47),
|
||||||
|
GrinchRamData(0x0102E1, value=0x48),
|
||||||
|
GrinchRamData(0x0102E2, value=0x49),
|
||||||
|
GrinchRamData(0x0102E3, value=0x4A),
|
||||||
|
GrinchRamData(0x0102E4, value=0x4B),
|
||||||
|
GrinchRamData(0x0102E5, value=0x4C),
|
||||||
|
GrinchRamData(0x0102E6, value=0x4D),
|
||||||
|
GrinchRamData(0x0102E7, value=0x4E),
|
||||||
|
GrinchRamData(0x0102E8, value=0x4F),
|
||||||
|
# GrinchRamData(0x0100BC, binary_bit_pos=5)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
grinch_items.gadgets.GRINCH_COPTER: GrinchItemData(
|
||||||
|
[grinch_categories.GADGETS],
|
||||||
|
106,
|
||||||
|
IC.progression,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x0102E9, value=0x40),
|
||||||
|
GrinchRamData(0x0102EA, value=0x41),
|
||||||
|
GrinchRamData(0x0102EB, value=0x42),
|
||||||
|
GrinchRamData(0x0102EC, value=0x43),
|
||||||
|
GrinchRamData(0x0102ED, value=0x44),
|
||||||
|
GrinchRamData(0x0102EE, value=0x45),
|
||||||
|
GrinchRamData(0x0102EF, value=0x46),
|
||||||
|
GrinchRamData(0x0102F0, value=0x47),
|
||||||
|
GrinchRamData(0x0102F1, value=0x48),
|
||||||
|
GrinchRamData(0x0102F2, value=0x49),
|
||||||
|
GrinchRamData(0x0102F3, value=0x4A),
|
||||||
|
GrinchRamData(0x0102F4, value=0x4B),
|
||||||
|
GrinchRamData(0x0102F5, value=0x4C),
|
||||||
|
GrinchRamData(0x0102F6, value=0x4D),
|
||||||
|
GrinchRamData(0x0102F7, value=0x4E),
|
||||||
|
GrinchRamData(0x0102F8, value=0x4F),
|
||||||
|
# GrinchRamData(0x0100BC, binary_bit_pos=6)
|
||||||
|
],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
#Mission Specific Items
|
# Mission Specific Items
|
||||||
MISSION_ITEMS_TABLE: dict[str, GrinchItemData] = {
|
MISSION_ITEMS_TABLE: dict[str, GrinchItemData] = {
|
||||||
"Who Cloak": GrinchItemData("Mission Specific Items", 200, IC.progression,
|
grinch_items.level_items.WV_WHO_CLOAK: GrinchItemData(
|
||||||
[GrinchRamData(0x0101F9, binary_bit_pos=0)], second_item_group="Useful Items"),
|
[
|
||||||
"Painting Bucket": GrinchItemData("Mission Specific Items", 201, IC.progression_deprioritized,
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
[GrinchRamData(0x0101F9, binary_bit_pos=1)], second_item_group="Useful Items"),
|
grinch_categories.USEFUL_ITEMS,
|
||||||
"Scissors": GrinchItemData("Mission Specific Items", 202, IC.progression_deprioritized,
|
],
|
||||||
[GrinchRamData(0x0101F9, binary_bit_pos=6), GrinchRamData(0x0100C2, binary_bit_pos=1)],
|
200,
|
||||||
second_item_group="Useful Items"),
|
IC.progression,
|
||||||
"Glue Bucket": GrinchItemData("Mission Specific Items", 203, IC.progression_deprioritized,
|
[GrinchRamData(0x0101F9, binary_bit_pos=0)],
|
||||||
[GrinchRamData(0x0101F9, binary_bit_pos=4)], second_item_group="Useful Items"),
|
),
|
||||||
"Cable Car Access Card": GrinchItemData("Mission Specific Items", 204, IC.progression,
|
grinch_items.level_items.WV_PAINT_BUCKET: GrinchItemData(
|
||||||
[GrinchRamData(0x0101F9, binary_bit_pos=5)], second_item_group="Useful Items"),
|
[
|
||||||
"Drill": GrinchItemData("Mission Specific Items", 205, IC.progression_deprioritized,
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
[GrinchRamData(0x0101FA, binary_bit_pos=2)], second_item_group="Useful Items"),
|
grinch_categories.USEFUL_ITEMS,
|
||||||
"Rope": GrinchItemData("Mission Specific Items", 206, IC.progression_deprioritized,
|
],
|
||||||
[GrinchRamData(0x0101FA, binary_bit_pos=1)], second_item_group="Useful Items"),
|
201,
|
||||||
"Hook": GrinchItemData("Mission Specific Items", 207, IC.progression_deprioritized,
|
IC.progression_deprioritized,
|
||||||
[GrinchRamData(0x0101FA, binary_bit_pos=0)], second_item_group="Useful Items"),
|
[GrinchRamData(0x0101F9, binary_bit_pos=1)],
|
||||||
"Sculpting Tools": GrinchItemData("Mission Specific Items", 208, IC.progression_deprioritized,
|
),
|
||||||
[GrinchRamData(0x0101F9, binary_bit_pos=2)], second_item_group="Useful Items"),
|
grinch_items.level_items.WD_SCISSORS: GrinchItemData(
|
||||||
"Hammer": GrinchItemData("Mission Specific Items", 209, IC.progression_deprioritized,
|
[
|
||||||
[GrinchRamData(0x0101F9, binary_bit_pos=3)], second_item_group="Useful Items"),
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
"Scout Clothes": GrinchItemData("Mission Specific Items", 210, IC.progression,
|
grinch_categories.USEFUL_ITEMS,
|
||||||
[GrinchRamData(0x0101F9, binary_bit_pos=7)], second_item_group="Useful Items")
|
],
|
||||||
|
202,
|
||||||
|
IC.progression_deprioritized,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x0101F9, binary_bit_pos=6),
|
||||||
|
GrinchRamData(0x0100C2, binary_bit_pos=1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
grinch_items.level_items.WF_GLUE_BUCKET: GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
],
|
||||||
|
203,
|
||||||
|
IC.progression_deprioritized,
|
||||||
|
[GrinchRamData(0x0101F9, binary_bit_pos=4)],
|
||||||
|
),
|
||||||
|
grinch_items.level_items.WF_CABLE_CAR_ACCESS_CARD: GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
],
|
||||||
|
204,
|
||||||
|
IC.progression,
|
||||||
|
[GrinchRamData(0x0101F9, binary_bit_pos=5)],
|
||||||
|
),
|
||||||
|
grinch_items.level_items.WL_DRILLL: GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
],
|
||||||
|
205,
|
||||||
|
IC.progression_deprioritized,
|
||||||
|
[GrinchRamData(0x0101FA, binary_bit_pos=2)],
|
||||||
|
),
|
||||||
|
grinch_items.level_items.WL_ROPE: GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
],
|
||||||
|
206,
|
||||||
|
IC.progression_deprioritized,
|
||||||
|
[GrinchRamData(0x0101FA, binary_bit_pos=1)],
|
||||||
|
),
|
||||||
|
grinch_items.level_items.WL_HOOK: GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
],
|
||||||
|
207,
|
||||||
|
IC.progression_deprioritized,
|
||||||
|
[GrinchRamData(0x0101FA, binary_bit_pos=0)],
|
||||||
|
),
|
||||||
|
grinch_items.level_items.WV_SCULPTIN_TOOLS: GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
],
|
||||||
|
208,
|
||||||
|
IC.progression_deprioritized,
|
||||||
|
[GrinchRamData(0x0101F9, binary_bit_pos=2)],
|
||||||
|
),
|
||||||
|
grinch_items.level_items.WV_HAMMER: GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
],
|
||||||
|
209,
|
||||||
|
IC.progression_deprioritized,
|
||||||
|
[GrinchRamData(0x0101F9, binary_bit_pos=3)],
|
||||||
|
),
|
||||||
|
grinch_items.level_items.WL_SCOUT_CLOTHES: GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.MISSION_SPECIFIC_ITEMS,
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
],
|
||||||
|
210,
|
||||||
|
IC.progression,
|
||||||
|
[GrinchRamData(0x0101F9, binary_bit_pos=7)],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
#Sleigh Parts
|
# Sleigh Parts
|
||||||
# SLEIGH_PARTS_TABLE: dict[str, GrinchItemData] = {
|
# SLEIGH_PARTS_TABLE: dict[str, GrinchItemData] = {
|
||||||
# "Exhaust Pipes": GrinchItemData("Sleigh Parts", 300, IC.progression_skip_balancing,
|
# "Exhaust Pipes": GrinchItemData(["Sleigh Parts"], 300, IC.progression_skip_balancing,
|
||||||
# [GrinchRamData(0x0101FB, binary_bit_pos=2)]),
|
# [GrinchRamData(0x0101FB, binary_bit_pos=2)]),
|
||||||
# "GPS": GrinchItemData("Sleigh Parts", 301, IC.useful,
|
# "GPS": GrinchItemData(["Sleigh Parts"], 301, IC.useful,
|
||||||
# [GrinchRamData(0x0101FB, binary_bit_pos=5)]),
|
# [GrinchRamData(0x0101FB, binary_bit_pos=5)]),
|
||||||
# "Tires": GrinchItemData("Sleigh Parts", 302, IC.progression_skip_balancing,
|
# "Tires": GrinchItemData(["Sleigh Parts"], 302, IC.progression_skip_balancing,
|
||||||
# [GrinchRamData(0x0101FB, binary_bit_pos=4)]),
|
# [GrinchRamData(0x0101FB, binary_bit_pos=4)]),
|
||||||
# "Skis": GrinchItemData("Sleigh Parts", 303, IC.progression_skip_balancing,
|
# "Skis": GrinchItemData(["Sleigh Parts"], 303, IC.progression_skip_balancing,
|
||||||
# [GrinchRamData(0x0101FB, binary_bit_pos=3)]),
|
# [GrinchRamData(0x0101FB, binary_bit_pos=3)]),
|
||||||
# "Twin-End Tuba": GrinchItemData("Sleigh Parts", 304, IC.progression_skip_balancing,
|
# "Twin-End Tuba": GrinchItemData(["Sleigh Parts"], 304, IC.progression_skip_balancing,
|
||||||
# [GrinchRamData(0x0101FB, binary_bit_pos=6)])
|
# [GrinchRamData(0x0101FB, binary_bit_pos=6)])
|
||||||
# }
|
# }
|
||||||
|
|
||||||
#Access Keys
|
# Access Keys
|
||||||
KEYS_TABLE: dict[str, GrinchItemData] = {
|
KEYS_TABLE: dict[str, GrinchItemData] = {
|
||||||
# "Whoville Vacuum Access": GrinchItemData("Vacuum Access", 400, IC.progression,
|
grinch_items.keys.WHOVILLE: GrinchItemData(
|
||||||
|
[grinch_categories.VACUUM_TUBES],
|
||||||
|
400,
|
||||||
|
IC.progression,
|
||||||
|
[GrinchRamData(0x010200, binary_bit_pos=1)],
|
||||||
|
),
|
||||||
|
grinch_items.keys.WHO_FOREST: GrinchItemData(
|
||||||
|
[grinch_categories.VACUUM_TUBES],
|
||||||
|
401,
|
||||||
|
IC.progression,
|
||||||
|
[GrinchRamData(0x0100AA, binary_bit_pos=2)],
|
||||||
|
),
|
||||||
|
grinch_items.keys.WHO_DUMP: GrinchItemData(
|
||||||
|
[grinch_categories.VACUUM_TUBES],
|
||||||
|
402,
|
||||||
|
IC.progression,
|
||||||
|
[GrinchRamData(0x0100AA, binary_bit_pos=3)],
|
||||||
|
),
|
||||||
|
grinch_items.keys.WHO_LAKE: GrinchItemData(
|
||||||
|
[grinch_categories.VACUUM_TUBES],
|
||||||
|
403,
|
||||||
|
IC.progression,
|
||||||
|
[GrinchRamData(0x0100AA, binary_bit_pos=4)],
|
||||||
|
),
|
||||||
|
# "Progressive Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 404, IC.progression,
|
||||||
# [GrinchRamData()]),
|
# [GrinchRamData()]),
|
||||||
"Who Forest Vacuum Access": GrinchItemData("Vacuum Access", 401, IC.progression,
|
# "Spin N' Win Door Unlock": GrinchItemData(["Supadow Door Unlocks"], 405, IC.progression,
|
||||||
[GrinchRamData(0x0100AA, binary_bit_pos=2)]),
|
|
||||||
"Who Dump Vacuum Access": GrinchItemData("Vacuum Access", 402, IC.progression,
|
|
||||||
[GrinchRamData(0x0100AA, binary_bit_pos=3)]),
|
|
||||||
"Who Lake Vacuum Access": GrinchItemData("Vacuum Access", 403, IC.progression,
|
|
||||||
[GrinchRamData(0x0100AA, binary_bit_pos=4)]),
|
|
||||||
# "Progressive Vacuum Access": GrinchItemData("Vacuum Access", 404, IC.progression,
|
|
||||||
# [GrinchRamData()]),
|
# [GrinchRamData()]),
|
||||||
# "Spin N' Win Door Unlock": GrinchItemData("Supadow Door Unlocks", 405, IC.progression,
|
# "Dankamania Door Unlock": GrinchItemData(["Supadow Door Unlocks"], 406, IC.progression,
|
||||||
# [GrinchRamData()]),
|
|
||||||
# "Dankamania Door Unlock": GrinchItemData("Supadow Door Unlocks", 406, IC.progression,
|
|
||||||
# [GrinchRamData()]),
|
# [GrinchRamData()]),
|
||||||
# "The Copter Race Contest Door Unlock": GrinchItemData("Supadow Door Unlocks", 407, IC.progression,
|
# "The Copter Race Contest Door Unlock": GrinchItemData("Supadow Door Unlocks", 407, IC.progression,
|
||||||
# [GrinchRamData()]),
|
# [GrinchRamData()]),
|
||||||
# "Progressive Supadow Door Unlock": GrinchItemData("Supadow Door Unlocks", 408, IC.progression,
|
# "Progressive Supadow Door Unlock": GrinchItemData("Supadow Door Unlocks", 408, IC.progression,
|
||||||
# [GrinchRamData()]),
|
# [GrinchRamData()]),
|
||||||
# "Bike Race Access": GrinchItemData("Supadow Door Unlocks", 409, IC.progression,
|
# "Bike Race Access": GrinchItemData(["Supadow Door Unlocks", 409, IC.progression,
|
||||||
# [GrinchRamData()])
|
# [GrinchRamData()])
|
||||||
"Sleigh Room Key": GrinchItemData("Sleigh Room", 410, IC.progression,
|
grinch_items.keys.SLEIGH_ROOM_KEY: GrinchItemData(
|
||||||
[GrinchRamData(0x010200, binary_bit_pos=6), GrinchRamData(0x0100AA, binary_bit_pos=5)])
|
[
|
||||||
|
grinch_categories.SLEIGH_ROOM,
|
||||||
|
grinch_categories.REQUIRED_ITEM,
|
||||||
|
],
|
||||||
|
410,
|
||||||
|
IC.progression,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x010200, binary_bit_pos=6),
|
||||||
|
GrinchRamData(0x0100AA, binary_bit_pos=5),
|
||||||
|
],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
#Misc Items
|
# Misc Items
|
||||||
MISC_ITEMS_TABLE: dict[str, GrinchItemData] = {
|
MISC_ITEMS_TABLE: dict[str, GrinchItemData] = {
|
||||||
# This item may not function properly if you receive it during a loading screen or in Mount Crumpit
|
# This item may not function properly if you receive it during a loading screen or in Mount Crumpit
|
||||||
# "Fully Healed Grinch": GrinchItemData("Health Items", 500, IC.filler,
|
# "Fully Healed Grinch": GrinchItemData(["Health Items", "Filler"], 500, IC.filler,
|
||||||
# [GrinchRamData(0x0E8FDC, value=120)]),
|
# [GrinchRamData(0x0E8FDC, value=120)]),
|
||||||
"5 Rotten Eggs": GrinchItemData("Rotten Egg Bundles", 502, IC.filler,
|
"5 Rotten Eggs": GrinchItemData(
|
||||||
[GrinchRamData(0x010058, value=5, update_existing_value=True, max_count=200, bit_size=2)]),
|
[
|
||||||
"10 Rotten Eggs": GrinchItemData("Rotten Egg Bundles", 503, IC.filler,
|
grinch_categories.ROTTEN_EGG_BUNDLES,
|
||||||
[GrinchRamData(0x010058, value=10, update_existing_value=True, max_count=200, bit_size=2)]),
|
grinch_categories.FILLER,
|
||||||
"20 Rotten Eggs": GrinchItemData("Rotten Egg Bundles", 504, IC.filler,
|
],
|
||||||
[GrinchRamData(0x010058, value=20, update_existing_value=True, max_count=200, bit_size=2)])
|
502,
|
||||||
|
IC.filler,
|
||||||
|
[
|
||||||
|
GrinchRamData(
|
||||||
|
0x010058,
|
||||||
|
value=5,
|
||||||
|
update_method=UpdateMethod.ADD,
|
||||||
|
max_count=200,
|
||||||
|
byte_size=2,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"10 Rotten Eggs": GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.ROTTEN_EGG_BUNDLES,
|
||||||
|
grinch_categories.FILLER,
|
||||||
|
],
|
||||||
|
503,
|
||||||
|
IC.filler,
|
||||||
|
[
|
||||||
|
GrinchRamData(
|
||||||
|
0x010058,
|
||||||
|
value=10,
|
||||||
|
update_method=UpdateMethod.ADD,
|
||||||
|
max_count=200,
|
||||||
|
byte_size=2,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"20 Rotten Eggs": GrinchItemData(
|
||||||
|
[
|
||||||
|
grinch_categories.ROTTEN_EGG_BUNDLES,
|
||||||
|
grinch_categories.FILLER,
|
||||||
|
],
|
||||||
|
504,
|
||||||
|
IC.filler,
|
||||||
|
[
|
||||||
|
GrinchRamData(
|
||||||
|
0x010058,
|
||||||
|
value=20,
|
||||||
|
update_method=UpdateMethod.ADD,
|
||||||
|
max_count=200,
|
||||||
|
byte_size=2,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
USEFUL_IC_TABLE: dict[str, GrinchItemData] = {
|
USEFUL_ITEMS_TABLE: dict[str, GrinchItemData] = {
|
||||||
"Heart of Stone": GrinchItemData("Health Items", 501, IC.useful,
|
grinch_items.useful_items.HEART_OF_STONE: GrinchItemData(
|
||||||
[GrinchRamData(0x0100ED, value=1, update_existing_value=True, max_count=4)])
|
[
|
||||||
|
grinch_categories.USEFUL_ITEMS,
|
||||||
|
grinch_categories.HEALING_ITEMS,
|
||||||
|
],
|
||||||
|
501,
|
||||||
|
IC.useful,
|
||||||
|
[
|
||||||
|
GrinchRamData(
|
||||||
|
0x0100ED,
|
||||||
|
value=1,
|
||||||
|
update_method=UpdateMethod.ADD,
|
||||||
|
max_count=4,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#Traps
|
# Traps
|
||||||
TRAPS_TABLE: dict[str, GrinchItemData] = {
|
TRAPS_TABLE: dict[str, GrinchItemData] = {
|
||||||
# alias to Ice Trap for traplink
|
# alias to Ice Trap for traplink
|
||||||
# "Freeze Trap": GrinchItemData("Traps", 600, IC.trap, [GrinchRamData()]),
|
# "Freeze Trap": GrinchItemData(["Traps"], 600, IC.trap, [GrinchRamData()]),
|
||||||
# "Bee Trap": GrinchItemData("Traps", 601, IC.trap, [GrinchRamData()]),
|
# "Bee Trap": GrinchItemData(["Traps"], 601, IC.trap, [GrinchRamData()]),
|
||||||
# "Electrocution Trap": GrinchItemData("Traps", 602, IC.trap, [GrinchRamData()]),
|
# "Electrocution Trap": GrinchItemData(["Traps"], 602, IC.trap, [GrinchRamData()]),
|
||||||
# alias to Slowness Trap for traplink
|
# alias to Slowness Trap for traplink
|
||||||
# "Tip Toe Trap": GrinchItemData("Traps", 603, IC.trap, [GrinchRamData()]),
|
# "Tip Toe Trap": GrinchItemData(["Traps"], 603, IC.trap, [GrinchRamData()]),
|
||||||
# This item may not function properly if you receive it during a loading screen or in Mount Crumpit
|
# This item may not function properly if you receive it during a loading screen or in Mount Crumpit
|
||||||
# "Damage Trap": GrinchItemData("Traps", 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_existing_value=True)]),
|
# alias to Exhaustion Trap
|
||||||
"Depletion Trap": GrinchItemData("Traps", 605, IC.trap, [GrinchRamData(0x010058, value=0, bit_size=2)]),
|
# "Damage Trap": GrinchItemData(["Traps"], 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_method=UpdateMethod.ADD)]),
|
||||||
"Dump it to Crumpit": GrinchItemData("Traps", 606, IC.trap, #Alias to Home Trap for traplink
|
grinch_items.trap_items.DEPLETION_TRAP: GrinchItemData(
|
||||||
[GrinchRamData(0x010000, value=0x05), GrinchRamData(0x08FB94, value=1)]),
|
[grinch_categories.TRAPS],
|
||||||
#alias to Spring Trap for traplink
|
605,
|
||||||
# "Rocket Spring Trap": GrinchItemData("Traps", 607, IC.trap, [GrinchRamData()]),
|
IC.trap,
|
||||||
#alias to Home Trap for traplink
|
[GrinchRamData(0x010058, value=0, byte_size=2)],
|
||||||
"Who sent me back?": GrinchItemData("Traps", 608, IC.trap, [GrinchRamData(0x08FB94, value=1)]),
|
),
|
||||||
# "Cutscene Trap": GrinchItemData("Traps", 609, IC.trap, [GrinchRamData()]),
|
grinch_items.trap_items.DUMP_IT_TO_CRUMPIT: GrinchItemData(
|
||||||
# "No Vac Trap": GrinchItemData("Traps", 610, IC.trap, [GrinchRamData(0x0102DA, value=0]),
|
[grinch_categories.TRAPS],
|
||||||
# "Invisible Trap": GrinchItemData("Traps", 611, IC.trap, [GrinchRamData(0x0102DA, value=0, bit_size=4)])
|
606,
|
||||||
# "Child Trap": GrinchItemData("Traps", 612, IC.trap,[GrinchRamData()])
|
IC.trap, # Alias to Home Trap for traplink
|
||||||
|
[
|
||||||
|
GrinchRamData(0x010000, value=0x05),
|
||||||
|
GrinchRamData(0x08FB94, value=1),
|
||||||
|
GrinchRamData(0x0100B4, value=0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
# alias to Spring Trap for traplink
|
||||||
|
# "Rocket Spring Trap": GrinchItemData(["Traps"], 607, IC.trap, [GrinchRamData()]),
|
||||||
|
# alias to Home Trap for traplink
|
||||||
|
grinch_items.trap_items.WHO_SENT_ME_BACK: GrinchItemData(
|
||||||
|
[grinch_categories.TRAPS],
|
||||||
|
608,
|
||||||
|
IC.trap,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x08FB94, value=1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
# "Cutscene Trap": GrinchItemData(["Traps"], 609, IC.trap, [GrinchRamData()]),
|
||||||
|
# "No Vac Trap": GrinchItemData(["Traps"], 610, IC.trap, [GrinchRamData(0x0102DA, value=0]),
|
||||||
|
# "Invisible Trap": GrinchItemData(["Traps"], 611, IC.trap, [GrinchRamData(0x0102DA, value=0, byte_size=4)])
|
||||||
|
# "Child Trap": GrinchItemData(["Traps"], 612, IC.trap,[GrinchRamData()])
|
||||||
|
# "Disable Jump Trap": GrinchItemData(["Traps"], 613, IC.trap,[GrinchRamData(0x010026, binary_bit_pos=6)])
|
||||||
}
|
}
|
||||||
|
|
||||||
#Movesets
|
# Movesets
|
||||||
# MOVES_TABLE: dict[str, GrinchItemData] = {
|
MOVES_TABLE: dict[str, GrinchItemData] = {
|
||||||
# "Bad Breath": GrinchItemData("Movesets", 700, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=1)]),
|
grinch_items.moves.BAD_BREATH: GrinchItemData(
|
||||||
# "Pancake": GrinchItemData("Movesets", 701, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=2)]),
|
[grinch_categories.MOVES],
|
||||||
# "Push & Pull": GrinchItemData("Movesets", 702, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=3)]),
|
700,
|
||||||
# "Max": GrinchItemData("Movesets", 703, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=4)]),
|
IC.progression,
|
||||||
# "Tip Toe": GrinchItemData("Movesets", 704, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=5)])
|
[
|
||||||
# }
|
GrinchRamData(0x0100BB, binary_bit_pos=1),
|
||||||
#Double star combines all dictionaries from each individual list together
|
],
|
||||||
|
),
|
||||||
|
grinch_items.moves.PANCAKE: GrinchItemData(
|
||||||
|
[grinch_categories.MOVES],
|
||||||
|
701,
|
||||||
|
IC.progression,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x0100BB, binary_bit_pos=2),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
grinch_items.moves.SIEZE: GrinchItemData(
|
||||||
|
[grinch_categories.MOVES],
|
||||||
|
702,
|
||||||
|
IC.progression,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x0100BB, binary_bit_pos=3),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
grinch_items.moves.MAX: GrinchItemData(
|
||||||
|
[grinch_categories.MOVES],
|
||||||
|
703,
|
||||||
|
IC.progression,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x0100BB, binary_bit_pos=4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
grinch_items.moves.SNEAK: GrinchItemData(
|
||||||
|
[grinch_categories.MOVES],
|
||||||
|
704,
|
||||||
|
IC.progression,
|
||||||
|
[
|
||||||
|
GrinchRamData(0x0100BB, binary_bit_pos=5),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Double star combines all dictionaries from each individual list together
|
||||||
ALL_ITEMS_TABLE: dict[str, GrinchItemData] = {
|
ALL_ITEMS_TABLE: dict[str, GrinchItemData] = {
|
||||||
**GADGETS_TABLE,
|
**GADGETS_TABLE,
|
||||||
**MISSION_ITEMS_TABLE,
|
**MISSION_ITEMS_TABLE,
|
||||||
**KEYS_TABLE,
|
**KEYS_TABLE,
|
||||||
**MISC_ITEMS_TABLE,
|
**MISC_ITEMS_TABLE,
|
||||||
**TRAPS_TABLE,
|
**TRAPS_TABLE,
|
||||||
**USEFUL_IC_TABLE,
|
**USEFUL_ITEMS_TABLE,
|
||||||
# **SLEIGH_PARTS_TABLE,
|
# **SLEIGH_PARTS_TABLE,
|
||||||
# **MOVES_TABLE,
|
**MOVES_TABLE,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Psuedocoding traplink table
|
# Psuedocoding traplink table
|
||||||
# BEE_TRAP_EQUIV = ["Army Trap", "Buyon Trap", "Ghost", "Gooey Bag", "OmoTrap", "Police Trap"]
|
# BEE_TRAP_EQUIV = ["Army Trap", "Buyon Trap", "Ghost", "Gooey Bag", "OmoTrap", "Police Trap"]
|
||||||
# ICE_TRAP_EQUIV = ["Chaos Control Trap", "Freeze Trap", "Frozen Trap", "Honey Trap", "Paralyze Trap", "Stun Trap", "Bubble Trap"]
|
# ICE_TRAP_EQUIV = ["Chaos Control Trap", "Freeze Trap", "Frozen Trap", "Honey Trap", "Paralyze Trap", "Stun Trap", "Bubble Trap"]
|
||||||
# DAMAGE_TRAP_EQUIV = ["Banana Trap", "Bomb", "Bonk Trap", "Fire Trap", "Laughter Trap", "Nut Trap", "Push Trap", "Squash Trap", "Thwimp Trap", "TNT Barrel Trap", "Meteor Trap"]
|
# DAMAGE_TRAP_EQUIV = ["Banana Trap", "Bomb", "Bonk Trap", "Fire Trap", "Laughter Trap", "Nut Trap", "Push Trap",
|
||||||
|
# "Squash Trap", "Thwimp Trap", "TNT Barrel Trap", "Meteor Trap", "Double Damage", "Spike Ball Trap"]
|
||||||
|
|
||||||
# SPRING_TRAP_EQUIV = ["Eject Ability", "Hiccup Trap", "Jump Trap", "Jumping Jacks Trap", "Whoops! Trap"]
|
# SPRING_TRAP_EQUIV = ["Eject Ability", "Hiccup Trap", "Jump Trap", "Jumping Jacks Trap", "Whoops! Trap"]
|
||||||
# HOME_TRAP_EQUIV = ["Blue Balls Curse", "Instant Death Trap"]
|
# HOME_TRAP_EQUIV = ["Blue Balls Curse", "Instant Death Trap", "Get Out Trap"]
|
||||||
# SLOWNESS_TRAP_EQUIV = ["Iron Boots Trap", "Slow Trap", "Sticky Floor Trap"]
|
# SLOWNESS_TRAP_EQUIV = ["Iron Boots Trap", "Slow Trap", "Sticky Floor Trap"]
|
||||||
# CUTSCENE_TRAP_EQUIV = ["Phone Trap"]
|
# CUTSCENE_TRAP_EQUIV = ["Phone Trap"]
|
||||||
# ELEC_TRAP_EQUIV = []
|
# ELEC_TRAP_EQUIV = []
|
||||||
|
# DEPL_TRAP_EQUIV = ["Dry Trap"]
|
||||||
|
|
||||||
|
|
||||||
def grinch_items_to_id() -> dict[str, int]:
|
def grinch_items_to_id() -> dict[str, int]:
|
||||||
item_mappings: dict[str, int] = {}
|
item_mappings: dict[str, int] = {}
|
||||||
for ItemName, ItemData in ALL_ITEMS_TABLE.items():
|
for ItemName, ItemData in ALL_ITEMS_TABLE.items():
|
||||||
item_mappings.update({ItemName: GrinchItem.get_apid(ItemData.id)})
|
item_mappings.update({ItemName: GrinchItem.get_apid(ItemData.id)})
|
||||||
return item_mappings
|
return item_mappings
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,26 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from Options import FreeText, NumericOption, Toggle, DefaultOnToggle, Choice, TextChoice, Range, NamedRange, OptionList, \
|
from Options import (
|
||||||
PerGameCommonOptions
|
FreeText,
|
||||||
|
NumericOption,
|
||||||
|
Toggle,
|
||||||
|
DefaultOnToggle,
|
||||||
|
Choice,
|
||||||
|
TextChoice,
|
||||||
|
Range,
|
||||||
|
NamedRange,
|
||||||
|
OptionList,
|
||||||
|
PerGameCommonOptions,
|
||||||
|
OptionSet,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StartingArea(Choice):
|
class StartingArea(Choice):
|
||||||
"""
|
"""
|
||||||
Here, you can select which area you'll start the game with. [NOT IMPLEMENTED]
|
Here, you can select which area you'll start the game with. [NOT IMPLEMENTED]
|
||||||
Whichever one you pick is the region you'll have access to at the start of the Multiworld.
|
Whichever one you pick is the region you'll have access to at the start of the Multiworld.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
option_whoville = 0
|
option_whoville = 0
|
||||||
option_who_forest = 1
|
option_who_forest = 1
|
||||||
option_who_dump = 2
|
option_who_dump = 2
|
||||||
@@ -15,13 +28,16 @@ class StartingArea(Choice):
|
|||||||
default = 0
|
default = 0
|
||||||
display_name = "Starting Area"
|
display_name = "Starting Area"
|
||||||
|
|
||||||
class ProgressiveVacuum(Toggle):#DefaultOnToggle
|
|
||||||
|
class ProgressiveVacuum(Toggle): # DefaultOnToggle
|
||||||
"""
|
"""
|
||||||
Determines whether you get access to main areas progressively [NOT IMPLEMENTED]
|
Determines whether you get access to main areas progressively [NOT IMPLEMENTED]
|
||||||
|
|
||||||
Enabled: Whoville > Who Forest > Who Dump > Who Lake
|
Enabled: Whoville > Who Forest > Who Dump > Who Lake
|
||||||
"""
|
"""
|
||||||
display_name = "Progressive Vacuum Access"
|
|
||||||
|
display_name = "Progressive Vacuum Tubes"
|
||||||
|
|
||||||
|
|
||||||
class Missionsanity(Choice):
|
class Missionsanity(Choice):
|
||||||
"""
|
"""
|
||||||
@@ -32,6 +48,7 @@ class Missionsanity(Choice):
|
|||||||
Individual: Individual tasks for one mission, such as individual snowmen squashed, are checks.
|
Individual: Individual tasks for one mission, such as individual snowmen squashed, are checks.
|
||||||
Both: Both individual tasks and mission completion are randomized.
|
Both: Both individual tasks and mission completion are randomized.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
display_name = "Mission Locations"
|
display_name = "Mission Locations"
|
||||||
option_none = 0
|
option_none = 0
|
||||||
option_completion = 1
|
option_completion = 1
|
||||||
@@ -39,52 +56,101 @@ class Missionsanity(Choice):
|
|||||||
option_both = 3
|
option_both = 3
|
||||||
default = 1
|
default = 1
|
||||||
|
|
||||||
class AnnoyingLocations(DefaultOnToggle):
|
|
||||||
"""Makes certain long, annoying, and tedious checks to be excluded [NOT IMPLEMENTED]"""
|
|
||||||
display_name = "Annoying Locations"
|
|
||||||
|
|
||||||
class ProgressiveGadget(Toggle):#DefaultOnToggle
|
class ExcludeEnvironments(OptionSet):
|
||||||
"""
|
"""
|
||||||
Determines whether you get access to a gadget as individual blueprint count [NOT IMPLEMENTED]
|
Allows entire environments to be an excluded location to ensure you are not logically required to enter the environment along
|
||||||
|
with any and all checks that are in that environment too.
|
||||||
|
|
||||||
|
WARNING: Excluding too many environments may cause generation to fail.
|
||||||
|
[NOT IMPLEMENTED]
|
||||||
|
|
||||||
|
Valid keys: "Whoville", "Who Forest", "Who Dump", "Who Lake", "Post Office", "Clock Tower", "City Hall",
|
||||||
|
"Ski Resort", "Civic Center", "Minefield", "Power Plant", "Generator Building", "Scout's Hut",
|
||||||
|
"North Shore", "Mayor's Villa", "Sleigh Ride"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
display_name = "Exclude Environments"
|
||||||
|
valid_keys = {
|
||||||
|
"Whoville",
|
||||||
|
"Who Forest",
|
||||||
|
"Who Dump",
|
||||||
|
"Who Lake",
|
||||||
|
"Post Office",
|
||||||
|
"Clock Tower",
|
||||||
|
"City Hall",
|
||||||
|
"Ski Resort",
|
||||||
|
"Civic Center",
|
||||||
|
"Minefield",
|
||||||
|
"Power Plant",
|
||||||
|
"Generator Building",
|
||||||
|
"Scout's Hut",
|
||||||
|
"North Shore",
|
||||||
|
"Mayor's Villa",
|
||||||
|
"Sleigh Ride",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressiveGadget(Toggle): # DefaultOnToggle
|
||||||
|
"""
|
||||||
|
Determines whether you get access to a gadget as individual blueprint count. [NOT IMPLEMENTED]
|
||||||
|
"""
|
||||||
|
|
||||||
display_name = "Progressive Gadgets"
|
display_name = "Progressive Gadgets"
|
||||||
|
|
||||||
|
|
||||||
class Supadow(Toggle):
|
class Supadow(Toggle):
|
||||||
"""Enables completing minigames through the Supadows in Mount Crumpit as checks. (9 locations) [NOT IMPLEMENTED]"""
|
"""Enables completing minigames through the Supadows in Mount Crumpit as checks. NOT IMPLEMENTED]"""
|
||||||
display_name = "Supadow Minigame Locations"
|
|
||||||
|
display_name = "Supadow Minigames"
|
||||||
|
|
||||||
|
|
||||||
class Gifts(Toggle):
|
class Gifts(Range):
|
||||||
"""Missions that require you to squash every present in a level. (4 locations) [NOT IMPLEMENTED]"""
|
"""
|
||||||
display_name = "Gift Collection Locations"
|
Considers how many gifts must be squashed per check.
|
||||||
|
Enabling this will also enable squashing all gifts in a region mission along side this. [NOT IMPLEMENTED]
|
||||||
|
"""
|
||||||
|
|
||||||
|
display_name = "Gifts Squashed per Check"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 300
|
||||||
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class Movesanity(Toggle):
|
class Moverando(Toggle):
|
||||||
"""Randomizes Grinch's moveset along with randomizing max into the pool. [NOT IMPLEMENTED]"""
|
"""Randomizes Grinch's moveset along with randomizing max into the pool. [NOT IMPLEMENTED]"""
|
||||||
display_name = "Movesanity"
|
|
||||||
|
display_name = "Moves Randomized"
|
||||||
|
|
||||||
|
|
||||||
class UnlimitedEggs(Toggle):
|
class UnlimitedEggs(Toggle):
|
||||||
"""Determine whether or not you run out of rotten eggs when you utilize your gadgets."""
|
"""Determine whether or not you run out of rotten eggs when you utilize your gadgets."""
|
||||||
|
|
||||||
display_name = "Unlimited Rotten Eggs"
|
display_name = "Unlimited Rotten Eggs"
|
||||||
|
|
||||||
|
|
||||||
class RingLinkOption(Toggle):
|
class RingLinkOption(Toggle):
|
||||||
"""Whenever this is toggled, your ammo is linked with other ringlink-compatible games that also have this enabled. [NOT IMPLEMENTED]"""
|
"""Whenever this is toggled, your ammo is linked with other ringlink-compatible games that also have this enabled."""
|
||||||
|
|
||||||
|
display_name = "Ring Link"
|
||||||
|
|
||||||
|
|
||||||
class TrapLinkOption(Toggle):
|
class TrapLinkOption(Toggle):
|
||||||
"""If a trap is sent from Grinch, traps that are compatible with other games are triggered aswell. [NOT IMPLEMENTED]"""
|
"""If a trap is sent from Grinch, traps that are compatible with other games are triggered as well. [NOT IMPLEMENTED]"""
|
||||||
|
|
||||||
|
display_name = "Trap Link"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GrinchOptions(PerGameCommonOptions):#DeathLinkMixin
|
class GrinchOptions(PerGameCommonOptions): # DeathLinkMixin
|
||||||
starting_area: StartingArea
|
starting_area: StartingArea
|
||||||
progressive_vacuum: ProgressiveVacuum
|
progressive_vacuum: ProgressiveVacuum
|
||||||
missionsanity: Missionsanity
|
missionsanity: Missionsanity
|
||||||
annoying_locations: AnnoyingLocations
|
exclude_environments: ExcludeEnvironments
|
||||||
minigamesanity: Supadow
|
|
||||||
progressive_gadget: ProgressiveGadget
|
progressive_gadget: ProgressiveGadget
|
||||||
supadow_minigames: Supadow
|
supadow_minigames: Supadow
|
||||||
giftsanity: Gifts
|
giftsanity: Gifts
|
||||||
movesanity: Movesanity
|
move_rando: Moverando
|
||||||
unlimited_eggs: UnlimitedEggs
|
unlimited_eggs: UnlimitedEggs
|
||||||
ring_link: RingLinkOption
|
ring_link: RingLinkOption
|
||||||
trap_link: TrapLinkOption
|
trap_link: TrapLinkOption
|
||||||
|
|||||||
@@ -1,12 +1,66 @@
|
|||||||
|
from enum import STRICT, IntEnum
|
||||||
from typing import NamedTuple, Optional
|
from typing import NamedTuple, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateMethod(IntEnum, boundary=STRICT):
|
||||||
|
SET = 1
|
||||||
|
ADD = 2
|
||||||
|
SUBTRACT = 3
|
||||||
|
FREEZE = 4
|
||||||
|
|
||||||
|
|
||||||
class GrinchRamData(NamedTuple):
|
class GrinchRamData(NamedTuple):
|
||||||
|
"""A Representation of an update to RAM data for The Grinch.
|
||||||
|
|
||||||
|
ram_address (int): The RAM address that we are updating, usually passed in with hex, representation (0x11111111)
|
||||||
|
value (int; Optional): The value we are using to set, add, or subtract from the RAM Address Value. Defaults to 1 if binary_bit_pos is passed in
|
||||||
|
binary_bit_pos: (int; Optional): If passed in, we are looking for a specific bit within the byte of the ram_address. This is represented as a small-endian bit position, meaning the right-most bit is 0, and the left-most bit is 7
|
||||||
|
byte_size: (int: Default: 1): The size of the RAM Address address we are looking for.
|
||||||
|
update_method (UpdateMethod; Default: SET): Determines what we are doing to the RAM Address. We can either SET the address, simply assigning a value. We can ADD or SUBTRACT, modifying hte existing value by a set amount. And we can FREEZE the address, preventing it from updating in the future
|
||||||
|
min_count: The minimum amount that a value can go down to using SUBTRACT
|
||||||
|
max_count: The maximum amount that a value can go down to using ADD
|
||||||
|
"""
|
||||||
|
|
||||||
ram_address: int
|
ram_address: int
|
||||||
value: Optional[int] = None #none is empty/null
|
value: Optional[int] = None # none is empty/null
|
||||||
# Either set or add either hex or unsigned values through Client.py
|
# Either set or add either hex or unsigned values through Client.py
|
||||||
# Hex uses 0x00, unsigned are base whole numbers
|
# Hex uses 0x00, unsigned are base whole numbers
|
||||||
binary_bit_pos: Optional[int] = None
|
binary_bit_pos: Optional[int] = None
|
||||||
bit_size: int = 1
|
byte_size: int = 1
|
||||||
update_existing_value: bool = False
|
update_method: UpdateMethod = UpdateMethod.SET
|
||||||
max_count: int = 0
|
min_count: int = 0
|
||||||
|
max_count: int = 255
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
ram_address: int,
|
||||||
|
value: Optional[int],
|
||||||
|
byte_size: int,
|
||||||
|
update_method: UpdateMethod,
|
||||||
|
min_count: int,
|
||||||
|
max_count: int,
|
||||||
|
):
|
||||||
|
self.ram_address = ram_address
|
||||||
|
|
||||||
|
if value:
|
||||||
|
self.value = value
|
||||||
|
else:
|
||||||
|
self.value = 1
|
||||||
|
|
||||||
|
if byte_size:
|
||||||
|
self.byte_size = byte_size
|
||||||
|
|
||||||
|
if update_method:
|
||||||
|
self.update_method = update_method
|
||||||
|
|
||||||
|
if min_count and min_count > -1:
|
||||||
|
self.min_count = min_count
|
||||||
|
|
||||||
|
if max_count and max_count > -1:
|
||||||
|
self.max_count = max_count
|
||||||
|
elif max_count and max_count > ((2 ** (self.byte_size * 8)) - 1):
|
||||||
|
raise ValueError(
|
||||||
|
"max_count cannot be larger than the RAM addresses max possible value"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.max_count = (2 ** (self.byte_size * 8)) - 1
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ mainareas_list = [
|
|||||||
"Whoville",
|
"Whoville",
|
||||||
"Who Forest",
|
"Who Forest",
|
||||||
"Who Dump",
|
"Who Dump",
|
||||||
"Who Lake"
|
"Who Lake",
|
||||||
]
|
]
|
||||||
|
|
||||||
subareas_list = [
|
subareas_list = [
|
||||||
"Post Office",
|
"Post Office",
|
||||||
"City Hall",
|
"City Hall",
|
||||||
"Countdown to X-Mas Tower",
|
"Clock Tower",
|
||||||
"Ski Resort",
|
"Ski Resort",
|
||||||
"Civic Center",
|
"Civic Center",
|
||||||
"Minefield",
|
"Minefield",
|
||||||
@@ -29,29 +29,35 @@ subareas_list = [
|
|||||||
"Scout's Hut",
|
"Scout's Hut",
|
||||||
"North Shore",
|
"North Shore",
|
||||||
"Mayor's Villa",
|
"Mayor's Villa",
|
||||||
"Sleigh Room"
|
"Sleigh Room",
|
||||||
]
|
]
|
||||||
|
|
||||||
supadow_list = [
|
supadow_list = [
|
||||||
"Spin N' Win Supadow",
|
"Spin N' Win Supadow",
|
||||||
"Dankamania Supadow",
|
"Dankamania Supadow",
|
||||||
"The Copter Race Contest Supadow",
|
"The Copter Race Contest Supadow",
|
||||||
"Bike Race"
|
"Bike Race",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def create_regions(world: "GrinchWorld"):
|
def create_regions(world: "GrinchWorld"):
|
||||||
for mainarea in mainareas_list:
|
for mainarea in mainareas_list:
|
||||||
#Each area in mainarea, create a region for the given player
|
# Each area in mainarea, create a region for the given player
|
||||||
world.multiworld.regions.append(Region(mainarea, world.player, world.multiworld))
|
world.multiworld.regions.append(
|
||||||
|
Region(mainarea, world.player, world.multiworld)
|
||||||
|
)
|
||||||
for subarea in subareas_list:
|
for subarea in subareas_list:
|
||||||
#Each area in subarea, create a region for the given player
|
# Each area in subarea, create a region for the given player
|
||||||
world.multiworld.regions.append(Region(subarea, world.player, world.multiworld))
|
world.multiworld.regions.append(Region(subarea, world.player, world.multiworld))
|
||||||
for supadow in supadow_list:
|
for supadow in supadow_list:
|
||||||
#Each area in supadow, create a region for the given player
|
# Each area in supadow, create a region for the given player
|
||||||
world.multiworld.regions.append(Region(supadow, world.player, world.multiworld))
|
world.multiworld.regions.append(Region(supadow, world.player, world.multiworld))
|
||||||
|
|
||||||
|
|
||||||
# TODO Optimize this function
|
# TODO Optimize this function
|
||||||
def grinchconnect(world: "GrinchWorld", current_region_name: str, connected_region_name: str):
|
def grinchconnect(
|
||||||
|
world: "GrinchWorld", current_region_name: str, connected_region_name: str
|
||||||
|
):
|
||||||
current_region = world.get_region(current_region_name)
|
current_region = world.get_region(current_region_name)
|
||||||
connected_region = world.get_region(connected_region_name)
|
connected_region = world.get_region(connected_region_name)
|
||||||
required_items: list[list[str]] = access_rules_dict[connected_region.name]
|
required_items: list[list[str]] = access_rules_dict[connected_region.name]
|
||||||
@@ -62,33 +68,38 @@ def grinchconnect(world: "GrinchWorld", current_region_name: str, connected_regi
|
|||||||
connected_region.connect(current_region)
|
connected_region.connect(current_region)
|
||||||
for access_rule in rule_list:
|
for access_rule in rule_list:
|
||||||
for region_entrance in current_region.entrances:
|
for region_entrance in current_region.entrances:
|
||||||
if region_entrance.connected_region.name == current_region_name and \
|
if (
|
||||||
region_entrance.parent_region.name == connected_region_name:
|
region_entrance.connected_region.name == current_region_name
|
||||||
|
and region_entrance.parent_region.name == connected_region_name
|
||||||
|
):
|
||||||
if rule_list.index(access_rule) == 0:
|
if rule_list.index(access_rule) == 0:
|
||||||
add_rule(region_entrance, access_rule)
|
add_rule(region_entrance, access_rule)
|
||||||
else:
|
else:
|
||||||
add_rule(region_entrance, access_rule, combine="or")
|
add_rule(region_entrance, access_rule, combine="or")
|
||||||
for region_entrance in connected_region.entrances:
|
for region_entrance in connected_region.entrances:
|
||||||
if region_entrance.connected_region.name == connected_region_name and \
|
if (
|
||||||
region_entrance.parent_region.name == current_region_name:
|
region_entrance.connected_region.name == connected_region_name
|
||||||
|
and region_entrance.parent_region.name == current_region_name
|
||||||
|
):
|
||||||
if rule_list.index(access_rule) == 0:
|
if rule_list.index(access_rule) == 0:
|
||||||
add_rule(region_entrance, access_rule)
|
add_rule(region_entrance, access_rule)
|
||||||
else:
|
else:
|
||||||
add_rule(region_entrance, access_rule, combine="or")
|
add_rule(region_entrance, access_rule, combine="or")
|
||||||
|
|
||||||
#What regions are connected to each other
|
|
||||||
|
# What regions are connected to each other
|
||||||
def connect_regions(world: "GrinchWorld"):
|
def connect_regions(world: "GrinchWorld"):
|
||||||
grinchconnect(world, "Mount Crumpit", "Whoville")
|
grinchconnect(world, "Mount Crumpit", "Whoville")
|
||||||
grinchconnect(world, "Mount Crumpit", "Who Forest")
|
grinchconnect(world, "Mount Crumpit", "Who Forest")
|
||||||
grinchconnect(world, "Mount Crumpit", "Who Dump")
|
grinchconnect(world, "Mount Crumpit", "Who Dump")
|
||||||
grinchconnect(world, "Mount Crumpit", "Who Lake")
|
grinchconnect(world, "Mount Crumpit", "Who Lake")
|
||||||
grinchconnect(world, "Mount Crumpit", "Sleigh Room")
|
grinchconnect(world, "Mount Crumpit", "Sleigh Room")
|
||||||
grinchconnect(world, "Mount Crumpit", "Spin N' Win Supadow")
|
grinchconnect(world, "Mount Crumpit", "Spin N' Win")
|
||||||
grinchconnect(world, "Mount Crumpit", "Dankamania Supadow")
|
grinchconnect(world, "Mount Crumpit", "Dankamania")
|
||||||
grinchconnect(world, "Mount Crumpit", "The Copter Race Contest Supadow")
|
grinchconnect(world, "Mount Crumpit", "The Copter Race Contest")
|
||||||
grinchconnect(world, "Whoville", "Post Office")
|
grinchconnect(world, "Whoville", "Post Office")
|
||||||
grinchconnect(world, "Whoville", "City Hall")
|
grinchconnect(world, "Whoville", "City Hall")
|
||||||
grinchconnect(world, "Whoville", "Countdown to X-Mas Clock Tower")
|
grinchconnect(world, "Whoville", "Clock Tower")
|
||||||
grinchconnect(world, "Who Forest", "Ski Resort")
|
grinchconnect(world, "Who Forest", "Ski Resort")
|
||||||
grinchconnect(world, "Who Forest", "Civic Center")
|
grinchconnect(world, "Who Forest", "Civic Center")
|
||||||
grinchconnect(world, "Who Dump", "Minefield")
|
grinchconnect(world, "Who Dump", "Minefield")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
from BaseClasses import Region, Item, ItemClassification
|
from BaseClasses import Region, Item, ItemClassification
|
||||||
from .Locations import grinch_locations_to_id, grinch_locations, GrinchLocation
|
from .Locations import grinch_locations_to_id, grinch_locations, GrinchLocation, get_location_names_per_category
|
||||||
from .Items import grinch_items_to_id, GrinchItem, ALL_ITEMS_TABLE, MISC_ITEMS_TABLE
|
from .Items import grinch_items_to_id, GrinchItem, ALL_ITEMS_TABLE, MISC_ITEMS_TABLE, get_item_names_per_category
|
||||||
from .Regions import connect_regions
|
from .Regions import connect_regions
|
||||||
from .Rules import set_location_rules
|
from .Rules import set_location_rules
|
||||||
|
|
||||||
@@ -8,27 +8,32 @@ from .Client import *
|
|||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
from worlds.AutoWorld import World
|
from worlds.AutoWorld import World
|
||||||
|
from Options import OptionError
|
||||||
|
|
||||||
from . import Options
|
from .Options import GrinchOptions
|
||||||
from .Rules import access_rules_dict
|
from .Rules import access_rules_dict
|
||||||
|
|
||||||
|
|
||||||
class GrinchWorld(World):
|
class GrinchWorld(World):
|
||||||
game: ClassVar[str] = "The Grinch"
|
game: ClassVar[str] = "The Grinch"
|
||||||
options_dataclass = Options.GrinchOptions
|
options_dataclass = Options.GrinchOptions
|
||||||
options = Options.GrinchOptions
|
options: Options.GrinchOptions
|
||||||
topology_present = True #not an open world game, very linear
|
topology_present = True #not an open world game, very linear
|
||||||
item_name_to_id: ClassVar[dict[str,int]] = grinch_items_to_id()
|
item_name_to_id: ClassVar[dict[str,int]] = grinch_items_to_id()
|
||||||
location_name_to_id: ClassVar[dict[str,int]] = grinch_locations_to_id()
|
location_name_to_id: ClassVar[dict[str,int]] = grinch_locations_to_id()
|
||||||
required_client_version = (0, 6, 3)
|
required_client_version = (0, 6, 3)
|
||||||
|
item_name_groups = get_item_names_per_category()
|
||||||
|
location_name_groups = get_location_names_per_category()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs): #Pulls __init__ function and takes control from there in BaseClasses.py
|
def __init__(self, *args, **kwargs): #Pulls __init__ function and takes control from there in BaseClasses.py
|
||||||
self.origin_region_name: str = "Mount Crumpit"
|
self.origin_region_name: str = "Mount Crumpit"
|
||||||
super(GrinchWorld, self).__init__(*args, **kwargs)
|
super(GrinchWorld, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def generate_early(self) -> None: #Special conditions changed before generation occurs
|
def generate_early(self) -> None: #Special conditions changed before generation occurs
|
||||||
pass
|
if self.options.ring_link == 1 and self.options.unlimited_eggs == 1:
|
||||||
|
raise OptionError("Cannot enable both unlimited rotten eggs and ring links. You can only enable one of these at a time." +
|
||||||
|
f"The following player's YAML needs to be fixed: {self.player_name}")
|
||||||
|
|
||||||
|
|
||||||
def create_regions(self): #Generates all regions for the multiworld
|
def create_regions(self): #Generates all regions for the multiworld
|
||||||
for region_name in access_rules_dict.keys():
|
for region_name in access_rules_dict.keys():
|
||||||
@@ -37,7 +42,7 @@ class GrinchWorld(World):
|
|||||||
for location, data in grinch_locations.items():
|
for location, data in grinch_locations.items():
|
||||||
region = self.get_region(data.region)
|
region = self.get_region(data.region)
|
||||||
entry = GrinchLocation(self.player, location, region, data)
|
entry = GrinchLocation(self.player, location, region, data)
|
||||||
if location == "Neutralizing Santa":
|
if location == "MC - Sleigh Ride - Neutralizing Santa":
|
||||||
entry.place_locked_item(Item("Goal", ItemClassification.progression, None, self.player))
|
entry.place_locked_item(Item("Goal", ItemClassification.progression, None, self.player))
|
||||||
region.locations.append(entry)
|
region.locations.append(entry)
|
||||||
connect_regions(self)
|
connect_regions(self)
|
||||||
@@ -72,7 +77,7 @@ class GrinchWorld(World):
|
|||||||
def fill_slot_data(self):
|
def fill_slot_data(self):
|
||||||
return {
|
return {
|
||||||
"give_unlimited_eggs": self.options.unlimited_eggs.value,
|
"give_unlimited_eggs": self.options.unlimited_eggs.value,
|
||||||
|
"ring_link": self.options.ring_link.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
def generate_output(self, output_directory: str) -> None:
|
def generate_output(self, output_directory: str) -> None:
|
||||||
|
|||||||
6
worlds/grinch/archipelago.json
Normal file
6
worlds/grinch/archipelago.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"minimum_ap_version": "0.6.3",
|
||||||
|
"world_version": "1.2.3",
|
||||||
|
"authors": ["MarioSpore"],
|
||||||
|
"game": "The Grinch"
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
- Credit to Raven-187 & Hacc on gamehacking.org for providing the addresses for various cheat codes. Without them, this
|
- Credit to Raven-187 & Hacc on gamehacking.org for providing the addresses for various cheat codes. Without them, this
|
||||||
would of made RAM searching much more tedious.
|
would of made RAM searching much more tedious.
|
||||||
- Shoutouts to SomeJakeGuy for basically teaching me how to code in general.
|
- Shoutouts to SomeJakeGuy for basically teaching me how to code in general along with starting decompilation of the game
|
||||||
|
into motion
|
||||||
- Shoutouts to BootsinSoots for helping with the implementation of the logic rules code itself.
|
- Shoutouts to BootsinSoots for helping with the implementation of the logic rules code itself.
|
||||||
- Thanks to the Grinch PS1 speedrunning discord community server for encouraging the production of this randomizer.
|
- Thanks to the Grinch PS1 speedrunning discord community server for encouraging the production of this randomizer.
|
||||||
|
- Credit to Artamiss for the vast majority of BZZ decompilation and general backend coding.
|
||||||
@@ -1,31 +1,45 @@
|
|||||||
# The Grinch - Setup Guide
|
# The Grinch (PS1) - Setup Guide
|
||||||
|
|
||||||
## Required Software
|
## Required Software
|
||||||
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Please use version 0.6.2 or later for integrated
|
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Please use version 0.6.3 or later for integrated
|
||||||
BizHawk support.
|
BizHawk support.
|
||||||
- Legally obtained NTSC Bin ROM file, probably named something like `Grinch, The (USA) (En,Fr,Es).bin`.
|
- Legally obtained NTSC Bin ROM file, probably named something like `Grinch, The (USA) (En,Fr,Es).bin`.
|
||||||
- CUE files may work, but I have not tested this.
|
The game's CUE file should also work aswell along side the BIN file if you have troubles opening the BIN file.
|
||||||
- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) Version 2.9.1 is supported, but I can't promise if any version is stable or not.
|
- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) Version 2.9.1 is required to play. Any version is NOT compatible and may have unintended behavior in
|
||||||
|
the game as well as not being able to connect to the client through the LUA Console.
|
||||||
- The latest `grinch.apworld` file. You can find this on the [Releases page](https://github.com/MarioSpore/Grinch-AP/releases/latest). Put this in your `Archipelago/custom_worlds` folder.
|
- The latest `grinch.apworld` file. You can find this on the [Releases page](https://github.com/MarioSpore/Grinch-AP/releases/latest). Put this in your `Archipelago/custom_worlds` folder.
|
||||||
|
- PSX BIOS Firmware bin file, which is required to run the game through Bizhawk. The file you need should be
|
||||||
|
named something like `SCPH-5501.BIN`.
|
||||||
|
|
||||||
## Configuring your Config (.yaml) file
|
## Configuring BizHawk
|
||||||
|
Once you have installed BizHawk, open `EmuHawk.exe` and change the following settings:
|
||||||
|
|
||||||
### What is a config file and why do I need one?
|
- If you're using BizHawk 2.7 or 2.8, go to `Config > Customize`. On the Advanced tab, switch the Lua Core from
|
||||||
|
`NLua+KopiLua` to `Lua+LuaInterface`, then restart EmuHawk. (If you're using BizHawk 2.9, you can skip this step.)
|
||||||
|
- Under `Config > Customize`, check the "Run in background" option to prevent disconnecting from the client while you're
|
||||||
|
tabbed out of EmuHawk.
|
||||||
|
- Under `Config > Preferred Cores > PSX`, select NymaShock.
|
||||||
|
- Open any PlayStation game in EmuHawk and go to `Config > Controllers…` to configure your inputs. If you can't click
|
||||||
|
`Controllers…`, it's because you need to load a game first.
|
||||||
|
You may need to invert Sensitivity for the up/down axis to -100%.
|
||||||
|
This can be found under Analog Controls through `Config > Controllers…`.
|
||||||
|
Depending on your controller, you may also want to tweak the Deadzone. Something like 6% is recommended for a DualShock 4.
|
||||||
|
- Consider clearing keybinds in `Config > Hotkeys…` if you don't intend to use them. Select the keybind and press Esc to
|
||||||
|
clear it.
|
||||||
|
- You are required to legally obtain a PSX Bios BIN firmware file for the game to be opened. To import this, you go to
|
||||||
|
`Config > Firmware... > Tools` and scrolling until you see the PlayStation tab. You might right click on the bios region
|
||||||
|
and click `Set Customization` or `Import` on the top of the window. Then a window should open, telling you what file to
|
||||||
|
import, which is the BIN file required. The bios should be recognized if Bizhawk displays a checkmark beside it, saying
|
||||||
|
the bios version you have is stable and should run without issues. If a bios is already been imported and the game
|
||||||
|
runs fine by itself, you may skip this step.
|
||||||
|
|
||||||
See the guide on setting up a basic YAML at the Archipelago setup
|
## Generating a Game
|
||||||
guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en)
|
|
||||||
|
|
||||||
### Where do I get a config file?
|
1. Create your options file (YAML). After installing the `grinch.apworld` file, you can generate a template within the Archipelago Launcher by clicking `Generate Template Settings`.
|
||||||
|
2. Follow the general Archipelago instructions for [generating a game](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game).
|
||||||
The Player options page on the website allows you to configure your personal
|
3. Open `ArchipelagoLauncher.exe`
|
||||||
options and export a config file from them: [The Grinch Player Options Page](../player-options)
|
4. Select "BizHawk Client" in the right-side column. On your first time opening BizHawk Client, you will also be asked to
|
||||||
|
locate `EmuHawk.exe` in your BizHawk install.
|
||||||
### Verifying your config file
|
|
||||||
|
|
||||||
If you would like to validate your config file to make sure it works, you may do
|
|
||||||
so on the YAML Validator page: [YAML Validation page](/check)
|
|
||||||
|
|
||||||
## Joining a MultiWorld Game
|
|
||||||
|
|
||||||
### Connect to the Multiserver
|
### Connect to the Multiserver
|
||||||
|
|
||||||
@@ -36,19 +50,4 @@ script. Navigate to your Archipelago install folder and open `data/lua/connector
|
|||||||
|
|
||||||
To connect the client to the multiserver simply put `<address>:<port>` on the text field on top and
|
To connect the client to the multiserver simply put `<address>:<port>` on the text field on top and
|
||||||
press enter (if the server uses a password, type in the bottom text field
|
press enter (if the server uses a password, type in the bottom text field
|
||||||
`/connect <address>:<port> [password]`)
|
`/connect <address>:<port> [password]`)
|
||||||
|
|
||||||
## Hosting a MultiWorld game
|
|
||||||
|
|
||||||
The recommended way to host a game is to use our hosting service. The process is relatively simple:
|
|
||||||
|
|
||||||
1. Collect config files from your players.
|
|
||||||
2. Upload the config files to the Generate page above.
|
|
||||||
- Generate page: [WebHost Seed Generation Page](/generate)
|
|
||||||
3. Wait a moment while the seed is generated.
|
|
||||||
4. When the seed is generated, you will be redirected to a "Seed Info" page.
|
|
||||||
5. Click "Create New Room". This will take you to the server page. Provide the link to this page to
|
|
||||||
your players, so they may download their patch files from there.
|
|
||||||
6. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the
|
|
||||||
progress of all players in the game. Any observers may also be given the link to this page.
|
|
||||||
7. Once all players have joined, you may begin playing.
|
|
||||||
Reference in New Issue
Block a user