mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Donkey Kong Country 3 Implementation (#798)
* Baseline patching and logic for DKC3 * Client can send, but not yet receive * Alpha Test Baseline * Bug Fixes and Starting Lives Option * Finish BBH, add world hints * Add music shuffle * Boomer Costs Text * Stubbed in Collect behaviour * Adjust Gyrocopter option * Add Bonus Coin junk replacement and tracker support * Delete bad logs * Undo host.yaml change * Refactored SNIClient * Make Swanky Free * Fix Typo * Undo SNIClient run_game hack * Fix Typo * Remove Bosses from Level Shuffle * Remove duplicate kivy Data * Add DKC3 Docs and increment Data version * Remove dead code * Fix mislabeled region * Add Dark Souls 3 to README * Always force Cog on Rocket Rush Flag * Fix Single Ski lock and too many DK Coins * Update Retroarch version number * Don't send DKC3 through LttP Adjuster * Comment Location ROM Table * Change ROM Hash prefix to D3 * Remove redundant constructor * Add ROM Change Safeguards * Properly mark WRAM accesses * Remove outdated region connect * Fix syntax error * Fix Game description * Fix SNES Bank Access * Add isso_setup for DKC3 * Double Quote strings * Escape single quotes I guess
This commit is contained in:
220
worlds/dkc3/Client.py
Normal file
220
worlds/dkc3/Client.py
Normal file
@@ -0,0 +1,220 @@
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from NetUtils import ClientStatus, color
|
||||
from SNIClient import Context, snes_buffered_write, snes_flush_writes, snes_read
|
||||
from Patch import GAME_DKC3
|
||||
|
||||
snes_logger = logging.getLogger("SNES")
|
||||
|
||||
# DKC3 - DKC3_TODO: Check these values
|
||||
ROM_START = 0x000000
|
||||
WRAM_START = 0xF50000
|
||||
WRAM_SIZE = 0x20000
|
||||
SRAM_START = 0xE00000
|
||||
|
||||
SAVEDATA_START = WRAM_START + 0xF000
|
||||
SAVEDATA_SIZE = 0x500
|
||||
|
||||
DKC3_ROMNAME_START = 0x00FFC0
|
||||
DKC3_ROMHASH_START = 0x7FC0
|
||||
ROMNAME_SIZE = 0x15
|
||||
ROMHASH_SIZE = 0x15
|
||||
|
||||
DKC3_RECV_PROGRESS_ADDR = WRAM_START + 0x632 # DKC3_TODO: Find a permanent home for this
|
||||
DKC3_FILE_NAME_ADDR = WRAM_START + 0x5D9
|
||||
DEATH_LINK_ACTIVE_ADDR = DKC3_ROMNAME_START + 0x15 # DKC3_TODO: Find a permanent home for this
|
||||
|
||||
|
||||
async def deathlink_kill_player(ctx: Context):
|
||||
pass
|
||||
#if ctx.game == GAME_DKC3:
|
||||
# DKC3_TODO: Handle Receiving Deathlink
|
||||
|
||||
|
||||
async def dkc3_rom_init(ctx: Context):
|
||||
if not ctx.rom:
|
||||
ctx.finished_game = False
|
||||
ctx.death_link_allow_survive = False
|
||||
game_name = await snes_read(ctx, DKC3_ROMNAME_START, 0x15)
|
||||
if game_name is None or game_name != b"DONKEY KONG COUNTRY 3":
|
||||
return False
|
||||
else:
|
||||
ctx.game = GAME_DKC3
|
||||
ctx.items_handling = 0b111 # remote items
|
||||
|
||||
rom = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE)
|
||||
if rom is None or rom == bytes([0] * ROMHASH_SIZE):
|
||||
return False
|
||||
|
||||
ctx.rom = rom
|
||||
|
||||
#death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1)
|
||||
## DKC3_TODO: Handle Deathlink
|
||||
#if death_link:
|
||||
# ctx.allow_collect = bool(death_link[0] & 0b100)
|
||||
# await ctx.update_death_link(bool(death_link[0] & 0b1))
|
||||
return True
|
||||
|
||||
|
||||
async def dkc3_game_watcher(ctx: Context):
|
||||
if ctx.game == GAME_DKC3:
|
||||
# DKC3_TODO: Handle Deathlink
|
||||
save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5)
|
||||
if save_file_name is None or save_file_name[0] == 0x00:
|
||||
# We haven't loaded a save file
|
||||
return
|
||||
|
||||
new_checks = []
|
||||
from worlds.dkc3.Rom import location_rom_data, item_rom_data
|
||||
for loc_id, loc_data in location_rom_data.items():
|
||||
if loc_id not in ctx.locations_checked:
|
||||
data = await snes_read(ctx, WRAM_START + loc_data[0], 1)
|
||||
masked_data = data[0] & (1 << loc_data[1])
|
||||
bit_set = (masked_data != 0)
|
||||
invert_bit = ((len(loc_data) >= 3) and loc_data[2])
|
||||
if bit_set != invert_bit:
|
||||
# DKC3_TODO: Handle non-included checks
|
||||
new_checks.append(loc_id)
|
||||
|
||||
save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5)
|
||||
if save_file_name is None or save_file_name[0] == 0x00:
|
||||
# We have somehow exited the save file
|
||||
return
|
||||
|
||||
rom = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE)
|
||||
if rom != ctx.rom:
|
||||
ctx.rom = None
|
||||
# We have somehow loaded a different ROM
|
||||
return
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names[new_check_id]
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||
|
||||
# DKC3_TODO: Make this actually visually display new things received (ASM Hook required)
|
||||
recv_count = await snes_read(ctx, DKC3_RECV_PROGRESS_ADDR, 1)
|
||||
recv_index = recv_count[0]
|
||||
|
||||
if recv_index < len(ctx.items_received):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names[item.item], 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names[item.location], recv_index, len(ctx.items_received)))
|
||||
|
||||
snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index]))
|
||||
if item.item in item_rom_data:
|
||||
item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1)
|
||||
new_item_count = item_count[0] + 1
|
||||
for address in item_rom_data[item.item]:
|
||||
snes_buffered_write(ctx, WRAM_START + address, bytes([new_item_count]))
|
||||
|
||||
# Handle Coin Displays
|
||||
current_level = await snes_read(ctx, WRAM_START + 0x5E3, 0x5)
|
||||
if item.item == 0xDC3002 and (current_level[0] == 0x0A and current_level[2] == 0x00 and current_level[4] == 0x03):
|
||||
# Bazaar and Barter
|
||||
item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1)
|
||||
new_item_count = item_count[0] + 1
|
||||
snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count]))
|
||||
elif item.item == 0xDC3002 and current_level[0] == 0x04:
|
||||
# Swanky
|
||||
item_count = await snes_read(ctx, WRAM_START + 0xA26, 0x1)
|
||||
new_item_count = item_count[0] + 1
|
||||
snes_buffered_write(ctx, WRAM_START + 0xA26, bytes([new_item_count]))
|
||||
elif item.item == 0xDC3003 and (current_level[0] == 0x0A and current_level[2] == 0x08 and current_level[4] == 0x01):
|
||||
# Boomer
|
||||
item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1)
|
||||
new_item_count = item_count[0] + 1
|
||||
snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count]))
|
||||
else:
|
||||
# Handle Patch and Skis
|
||||
if item.item == 0xDC3007:
|
||||
num_upgrades = 1
|
||||
inventory = await snes_read(ctx, WRAM_START + 0x605, 0xF)
|
||||
|
||||
if (inventory[0] & 0x02):
|
||||
num_upgrades = 3
|
||||
elif (inventory[13] & 0x08) or (inventory[0] & 0x01):
|
||||
num_upgrades = 2
|
||||
|
||||
if num_upgrades == 1:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x01]))
|
||||
if inventory[4] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x01]))
|
||||
elif inventory[6] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x01]))
|
||||
elif inventory[8] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x01]))
|
||||
elif inventory[10] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x01]))
|
||||
|
||||
cove_mekanos_progress = await snes_read(ctx, WRAM_START + 0x691, 0x2)
|
||||
snes_buffered_write(ctx, WRAM_START + 0x691, bytes([cove_mekanos_progress[0] | 0x01]))
|
||||
snes_buffered_write(ctx, WRAM_START + 0x692, bytes([cove_mekanos_progress[1] | 0x01]))
|
||||
elif num_upgrades == 2:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x02]))
|
||||
if inventory[4] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x02]))
|
||||
elif inventory[6] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x02]))
|
||||
elif inventory[8] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x02]))
|
||||
elif inventory[10] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x02]))
|
||||
elif num_upgrades == 3:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x606, bytes([inventory[1] | 0x20]))
|
||||
|
||||
k3_ridge_progress = await snes_read(ctx, WRAM_START + 0x693, 0x2)
|
||||
snes_buffered_write(ctx, WRAM_START + 0x693, bytes([k3_ridge_progress[0] | 0x01]))
|
||||
snes_buffered_write(ctx, WRAM_START + 0x694, bytes([k3_ridge_progress[1] | 0x01]))
|
||||
elif item.item == 0xDC3000:
|
||||
# Handle Victory
|
||||
if not ctx.finished_game:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
else:
|
||||
print("Item Not Recognized: ", item.item)
|
||||
pass
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
# DKC3_TODO: This method of collect should work, however it does not unlock the next level correctly when previous is flagged
|
||||
# Handle Collected Locations
|
||||
#for loc_id in ctx.checked_locations:
|
||||
# if loc_id not in ctx.locations_checked:
|
||||
# loc_data = location_rom_data[loc_id]
|
||||
# data = await snes_read(ctx, WRAM_START + loc_data[0], 1)
|
||||
# invert_bit = ((len(loc_data) >= 3) and loc_data[2])
|
||||
# if not invert_bit:
|
||||
# masked_data = data[0] | (1 << loc_data[1])
|
||||
# print("Collected Location: ", hex(loc_data[0]), " | ", loc_data[1])
|
||||
# snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
|
||||
# await snes_flush_writes(ctx)
|
||||
# else:
|
||||
# masked_data = data[0] & ~(1 << loc_data[1])
|
||||
# print("Collected Inverted Location: ", hex(loc_data[0]), " | ", loc_data[1])
|
||||
# snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
|
||||
# await snes_flush_writes(ctx)
|
||||
# ctx.locations_checked.add(loc_id)
|
||||
|
||||
# Calculate Boomer Cost Text
|
||||
boomer_cost_text = await snes_read(ctx, WRAM_START + 0xAAFD, 2)
|
||||
if boomer_cost_text[0] == 0x31 and boomer_cost_text[1] == 0x35:
|
||||
boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1)
|
||||
boomer_cost_tens = int(boomer_cost[0]) // 10
|
||||
boomer_cost_ones = int(boomer_cost[0]) % 10
|
||||
snes_buffered_write(ctx, WRAM_START + 0xAAFD, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones]))
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
boomer_final_cost_text = await snes_read(ctx, WRAM_START + 0xAB9B, 2)
|
||||
if boomer_final_cost_text[0] == 0x32 and boomer_final_cost_text[1] == 0x35:
|
||||
boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1)
|
||||
boomer_cost_tens = boomer_cost[0] // 10
|
||||
boomer_cost_ones = boomer_cost[0] % 10
|
||||
snes_buffered_write(ctx, WRAM_START + 0xAB9B, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones]))
|
||||
await snes_flush_writes(ctx)
|
Reference in New Issue
Block a user