Skip to content

Commit

Permalink
Merge branch 'ladx/deathlink-ram-checks' into ladx/enable-deathlink
Browse files Browse the repository at this point in the history
  • Loading branch information
threeandthreee committed Jan 27, 2025
2 parents 94627c8 + a9e7901 commit a8d866c
Showing 1 changed file with 31 additions and 58 deletions.
89 changes: 31 additions & 58 deletions LinksAwakeningClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class LAClientConstants:
# Unused
# ROMWorldID = 0x0055
# ROMConnectorVersion = 0x0056
wInventoryAppearing = 0xC14F
wDialogState = 0xC19F
wAddHealthBuffer = 0xDB93
wSubtractHealthBuffer = 0xDB94
# RO: We should only act if this is higher then 6, as it indicates that the game is running normally
Expand Down Expand Up @@ -307,6 +309,7 @@ class LinksAwakeningClient():
auth = None
game_crc = None
deathlink_status = None
no_kill_room_id = None
slot = None
recvd_checks = {}
retroarch_address = None
Expand Down Expand Up @@ -410,69 +413,11 @@ async def wait_for_game_ready(self):
async def is_victory(self):
return (await self.gameboy.read_memory_cache([LAClientConstants.wGameplayType]))[LAClientConstants.wGameplayType] == 1

def fix_trade_items(self):
"""
If we kill the player after they give up a trade item but before they
complete the related check, they are locked out of the check. This
looks at items collected and compares against locations checked to
give unused trade items back to the player.
"""
trade_items = (
{ 'destination': '0x2A6-Trade', 'name': 'TRADING_ITEM_YOSHI_DOLL' },
{ 'destination': '0x2B2-Trade', 'name': 'TRADING_ITEM_RIBBON' },
{ 'destination': '0x2FE-Trade', 'name': 'TRADING_ITEM_DOG_FOOD' },
{ 'destination': '0x07B-Trade', 'name': 'TRADING_ITEM_BANANAS' },
{ 'destination': '0x087-Trade', 'name': 'TRADING_ITEM_STICK' },
{ 'destination': '0x2D7-Trade', 'name': 'TRADING_ITEM_HONEYCOMB' },
{ 'destination': '0x019-Trade', 'name': 'TRADING_ITEM_PINEAPPLE' },
{ 'destination': '0x2D9-Trade', 'name': 'TRADING_ITEM_HIBISCUS' },
{ 'destination': '0x2A8-Trade', 'name': 'TRADING_ITEM_LETTER' },
{ 'destination': '0x0CD-Trade', 'name': 'TRADING_ITEM_BROOM' },
{ 'destination': '0x2F5-Trade', 'name': 'TRADING_ITEM_FISHING_HOOK' },
{ 'destination': '0x0C9-Trade', 'name': 'TRADING_ITEM_NECKLACE' },
)
trade1 = 0
trade2 = 0
for i, item in enumerate(trade_items):
item_id = [x.item_id for x in links_awakening_items if x.ladxr_id == item['name']][0] + LABaseID
item_has_been_received = [x for x in self.recvd_checks.values() if x.item == item_id]
if not item_has_been_received:
continue
destination_has_been_checked = [x for x in self.tracker.all_checks if x.value and x.id == item['destination']]
if destination_has_been_checked:
continue
if i <= 7: # trade item inventory is tracked by bits across 2 addresses
trade1 += 2**i
else:
trade2 += 2**(i-8)
self.gameboy.write_memory(LAClientConstants.wTradeSequenceItem, [trade1])
self.gameboy.write_memory(LAClientConstants.wTradeSequenceItem2, [trade2])

async def main_tick(self, item_get_cb, win_cb, deathlink_cb):
await self.tracker.readChecks(item_get_cb)
await self.item_tracker.readItems()
await self.gps_tracker.read_location()

current_health = (await self.gameboy.read_memory_cache([LAClientConstants.wLinkHealth]))[LAClientConstants.wLinkHealth]
health_to_remove = (await self.gameboy.read_memory_cache([LAClientConstants.wSubtractHealthBuffer]))[LAClientConstants.wSubtractHealthBuffer]

if self.deathlink_status == 'pending':
self.gameboy.write_memory(LAClientConstants.wAddHealthBuffer, [0]) # Stop any health gain
self.gameboy.write_memory(LAClientConstants.wLinkHealth, [1]) # Almost dead
self.gameboy.write_memory(LAClientConstants.wSubtractHealthBuffer, [1]) # Deal remaining damage this way to trigger medicine
self.deathlink_status = 'in_progress'
elif self.deathlink_status == 'in_progress':
if not current_health: # Died from deathlink
self.deathlink_status = 'complete'
self.fix_trade_items()
elif not health_to_remove: # Survived deathlink (medicine)
self.deathlink_status = None
elif not self.deathlink_status and not current_health: # Died naturally
await deathlink_cb()
self.deathlink_status = 'complete'
elif self.deathlink_status == 'complete' and current_health:
self.deathlink_status = None

if await self.is_victory():
await win_cb()

Expand All @@ -483,6 +428,34 @@ async def main_tick(self, item_get_cb, win_cb, deathlink_cb):
item = self.recvd_checks[recv_index]
await self.recved_item_from_ap(item.item, item.location, item.player, recv_index)

mem = await self.gameboy.read_memory_cache([
LAClientConstants.wLinkHealth,
LAClientConstants.wSubtractHealthBuffer,
])

if self.deathlink_status == 'pending':
room_id = '0x' + hex(self.gps_tracker.room)[2:].zfill(3).upper()
checks = [x for x in self.tracker.remaining_checks
if x.id.startswith(room_id) and not x.id.endswith('Owl')]
if checks:
self.no_kill_room_id = room_id
elif room_id != self.no_kill_room_id:
self.gameboy.write_memory(LAClientConstants.wAddHealthBuffer, [0]) # Stop any health gain
self.gameboy.write_memory(LAClientConstants.wLinkHealth, [1]) # Almost dead
self.gameboy.write_memory(LAClientConstants.wSubtractHealthBuffer, [1]) # Deal remaining damage this way to trigger medicine
self.deathlink_status = 'in_progress'
self.no_kill_room_id = None
elif self.deathlink_status == 'in_progress':
if not mem[LAClientConstants.wLinkHealth]: # Died from deathlink
self.deathlink_status = 'complete'
elif not mem[LAClientConstants.wSubtractHealthBuffer]: # Survived deathlink (medicine)
self.deathlink_status = None
elif not self.deathlink_status and not mem[LAClientConstants.wLinkHealth]: # Died naturally
await deathlink_cb()
self.deathlink_status = 'complete'
elif self.deathlink_status == 'complete' and mem[LAClientConstants.wLinkHealth]:
self.deathlink_status = None


all_tasks = set()

Expand Down

0 comments on commit a8d866c

Please sign in to comment.