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']}")
|
logger.info(f"DeathLink: Received from {data['source']}")
|
||||||
|
|
||||||
async def send_death(self, death_text: str = ""):
|
async def send_death(self, death_text: str = ""):
|
||||||
|
logger.info("Sending death to your friends...")
|
||||||
self.last_death_link = time.time()
|
self.last_death_link = time.time()
|
||||||
await self.send_msgs([{
|
await self.send_msgs([{
|
||||||
"cmd": "Bounce", "tags": ["DeathLink"],
|
"cmd": "Bounce", "tags": ["DeathLink"],
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import atexit
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
@ -32,6 +31,12 @@ snes_logger = logging.getLogger("SNES")
|
||||||
from MultiServer import mark_raw
|
from MultiServer import mark_raw
|
||||||
|
|
||||||
|
|
||||||
|
class DeathState(enum.IntEnum):
|
||||||
|
killing_player = 1
|
||||||
|
alive = 2
|
||||||
|
dead = 3
|
||||||
|
|
||||||
|
|
||||||
class LttPCommandProcessor(ClientCommandProcessor):
|
class LttPCommandProcessor(ClientCommandProcessor):
|
||||||
ctx: Context
|
ctx: Context
|
||||||
|
|
||||||
|
@ -88,6 +93,10 @@ class LttPCommandProcessor(ClientCommandProcessor):
|
||||||
self.output("Data Sent")
|
self.output("Data Sent")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _cmd_test_death(self):
|
||||||
|
self.ctx.on_deathlink({"source": "Console",
|
||||||
|
"time": time.time()})
|
||||||
|
|
||||||
|
|
||||||
class Context(CommonContext):
|
class Context(CommonContext):
|
||||||
command_processor = LttPCommandProcessor
|
command_processor = LttPCommandProcessor
|
||||||
|
@ -106,7 +115,7 @@ class Context(CommonContext):
|
||||||
self.snes_request_lock = asyncio.Lock()
|
self.snes_request_lock = asyncio.Lock()
|
||||||
self.snes_write_buffer = []
|
self.snes_write_buffer = []
|
||||||
self.snes_connector_lock = threading.Lock()
|
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.awaiting_rom = False
|
||||||
self.rom = None
|
self.rom = None
|
||||||
|
@ -140,13 +149,17 @@ class Context(CommonContext):
|
||||||
}])
|
}])
|
||||||
|
|
||||||
def on_deathlink(self, data: dict):
|
def on_deathlink(self, data: dict):
|
||||||
snes_buffered_write(self, WRAM_START + 0xF36D, bytes([0])) # set current health to 0
|
asyncio.create_task(deathlink_kill_player(self))
|
||||||
snes_buffered_write(self, WRAM_START + 0x0373, bytes([8])) # deal 1 full heart of damage at next opportunity
|
self.death_state = DeathState.killing_player
|
||||||
asyncio.create_task(snes_flush_writes(self))
|
|
||||||
self.death_state = True
|
|
||||||
super(Context, self).on_deathlink(data)
|
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:
|
def color_item(item_id: int, green: bool = False) -> str:
|
||||||
item_name = get_item_name_from_id(item_id)
|
item_name = get_item_name_from_id(item_id)
|
||||||
item_colors = ['green' if green else 'cyan']
|
item_colors = ['green' if green else 'cyan']
|
||||||
|
@ -842,7 +855,7 @@ async def game_watcher(ctx: Context):
|
||||||
ctx.rom = rom
|
ctx.rom = rom
|
||||||
death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1)
|
death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1)
|
||||||
if death_link:
|
if death_link:
|
||||||
death_link = bool(death_link[0])
|
death_link = bool(death_link[0] & 0b1)
|
||||||
old_tags = ctx.tags.copy()
|
old_tags = ctx.tags.copy()
|
||||||
if death_link:
|
if death_link:
|
||||||
ctx.tags.add("DeathLink")
|
ctx.tags.add("DeathLink")
|
||||||
|
@ -864,12 +877,24 @@ async def game_watcher(ctx: Context):
|
||||||
|
|
||||||
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
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 "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time():
|
||||||
if gamemode[0] in DEATH_MODES:
|
currently_dead = gamemode[0] in DEATH_MODES
|
||||||
if not ctx.death_state: # new death
|
# 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()
|
await ctx.send_death()
|
||||||
ctx.death_state = True
|
# in this state we care about confirming a kill, to move state to dead
|
||||||
else:
|
elif DeathState.killing_player:
|
||||||
ctx.death_state = False # reset death state, so next death can trigger
|
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)
|
gameend = await snes_read(ctx, SAVEDATA_START + 0x443, 1)
|
||||||
game_timer = await snes_read(ctx, SAVEDATA_START + 0x42E, 4)
|
game_timer = await snes_read(ctx, SAVEDATA_START + 0x42E, 4)
|
||||||
if gamemode is None or gameend is None or game_timer is None or \
|
if gamemode is None or gameend is None or game_timer is None or \
|
||||||
|
|
|
@ -431,6 +431,17 @@ class Context:
|
||||||
else:
|
else:
|
||||||
return self.player_names[team, slot]
|
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]):
|
def notify_hints(ctx: Context, team: int, hints: typing.List[NetUtils.Hint]):
|
||||||
concerns = collections.defaultdict(list)
|
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]
|
current = ctx.client_game_state[client.team, client.slot]
|
||||||
if current != ClientStatus.CLIENT_GOAL: # can't undo goal completion
|
if current != ClientStatus.CLIENT_GOAL: # can't undo goal completion
|
||||||
if new_status == ClientStatus.CLIENT_GOAL:
|
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.on_goal_achieved(client)
|
||||||
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.client_game_state[client.team, client.slot] = new_status
|
ctx.client_game_state[client.team, client.slot] = new_status
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue