LttP: refine DeathLink handling.
This commit is contained in:
parent
82d3e4bc92
commit
650fd5d792
|
@ -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"],
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue