LttP: refine DeathLink handling.

This commit is contained in:
Fabian Dill 2021-11-11 16:09:08 +01:00
parent 82d3e4bc92
commit 650fd5d792
3 changed files with 50 additions and 20 deletions

View File

@ -271,6 +271,7 @@ class CommonContext():
logger.info(f"DeathLink: Received from {data['source']}")
async def send_death(self, death_text: str = ""):
logger.info("Sending death to your friends...")
self.last_death_link = time.time()
await self.send_msgs([{
"cmd": "Bounce", "tags": ["DeathLink"],

View File

@ -1,6 +1,5 @@
from __future__ import annotations
import atexit
import threading
import time
import multiprocessing
@ -32,6 +31,12 @@ snes_logger = logging.getLogger("SNES")
from MultiServer import mark_raw
class DeathState(enum.IntEnum):
killing_player = 1
alive = 2
dead = 3
class LttPCommandProcessor(ClientCommandProcessor):
ctx: Context
@ -88,6 +93,10 @@ class LttPCommandProcessor(ClientCommandProcessor):
self.output("Data Sent")
return True
def _cmd_test_death(self):
self.ctx.on_deathlink({"source": "Console",
"time": time.time()})
class Context(CommonContext):
command_processor = LttPCommandProcessor
@ -106,7 +115,7 @@ class Context(CommonContext):
self.snes_request_lock = asyncio.Lock()
self.snes_write_buffer = []
self.snes_connector_lock = threading.Lock()
self.death_state = False # for death link flop behaviour
self.death_state = DeathState.alive # for death link flop behaviour
self.awaiting_rom = False
self.rom = None
@ -140,13 +149,17 @@ class Context(CommonContext):
}])
def on_deathlink(self, data: dict):
snes_buffered_write(self, WRAM_START + 0xF36D, bytes([0])) # set current health to 0
snes_buffered_write(self, WRAM_START + 0x0373, bytes([8])) # deal 1 full heart of damage at next opportunity
asyncio.create_task(snes_flush_writes(self))
self.death_state = True
asyncio.create_task(deathlink_kill_player(self))
self.death_state = DeathState.killing_player
super(Context, self).on_deathlink(data)
async def deathlink_kill_player(ctx: Context):
snes_buffered_write(ctx, WRAM_START + 0xF36D, bytes([0])) # set current health to 0
snes_buffered_write(ctx, WRAM_START + 0x0373, bytes([8])) # deal 1 full heart of damage at next opportunity
await snes_flush_writes(ctx)
def color_item(item_id: int, green: bool = False) -> str:
item_name = get_item_name_from_id(item_id)
item_colors = ['green' if green else 'cyan']
@ -842,7 +855,7 @@ async def game_watcher(ctx: Context):
ctx.rom = rom
death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1)
if death_link:
death_link = bool(death_link[0])
death_link = bool(death_link[0] & 0b1)
old_tags = ctx.tags.copy()
if death_link:
ctx.tags.add("DeathLink")
@ -864,12 +877,24 @@ async def game_watcher(ctx: Context):
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time():
if gamemode[0] in DEATH_MODES:
if not ctx.death_state: # new death
currently_dead = gamemode[0] in DEATH_MODES
# in this state we only care about triggering a death send
if ctx.death_state == DeathState.alive:
if currently_dead:
ctx.death_state = DeathState.dead
await ctx.send_death()
ctx.death_state = True
else:
ctx.death_state = False # reset death state, so next death can trigger
# in this state we care about confirming a kill, to move state to dead
elif DeathState.killing_player:
if currently_dead:
ctx.death_state = DeathState.dead
else:
await deathlink_kill_player(ctx) # try again
ctx.last_death_link = time.time() # delay handling
# in this state we wait until the player is alive again
elif DeathState.dead:
if not currently_dead:
ctx.death_state = DeathState.alive
gameend = await snes_read(ctx, SAVEDATA_START + 0x443, 1)
game_timer = await snes_read(ctx, SAVEDATA_START + 0x42E, 4)
if gamemode is None or gameend is None or game_timer is None or \

View File

@ -431,6 +431,17 @@ class Context:
else:
return self.player_names[team, slot]
def on_goal_achieved(self, client: Client):
finished_msg = f'{self.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1})' \
f' has completed their goal.'
self.notify_all(finished_msg)
if "auto" in self.forfeit_mode:
forfeit_player(self, client.team, client.slot)
elif proxy_worlds[self.games[client.slot]].forced_auto_forfeit:
forfeit_player(self, client.team, client.slot)
if "auto" in self.collect_mode:
collect_player(self, client.team, client.slot)
def notify_hints(ctx: Context, team: int, hints: typing.List[NetUtils.Hint]):
concerns = collections.defaultdict(list)
@ -1356,14 +1367,7 @@ def update_client_status(ctx: Context, client: Client, new_status: ClientStatus)
current = ctx.client_game_state[client.team, client.slot]
if current != ClientStatus.CLIENT_GOAL: # can't undo goal completion
if new_status == ClientStatus.CLIENT_GOAL:
finished_msg = f'{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) has completed their goal.'
ctx.notify_all(finished_msg)
if "auto" in ctx.forfeit_mode:
forfeit_player(ctx, client.team, client.slot)
elif proxy_worlds[ctx.games[client.slot]].forced_auto_forfeit:
forfeit_player(ctx, client.team, client.slot)
if "auto" in ctx.collect_mode:
collect_player(ctx, client.team, client.slot)
ctx.on_goal_achieved(client)
ctx.client_game_state[client.team, client.slot] = new_status