mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 15:13:52 -08:00
Fix(undertale): prevent massive bounce msg spam for position updates (#5990)
* fix(undertale): prevent massive bounce msg spam for position updates * make sure player is removed on leaving / timing out * do not check for tags: online, as bounce evaluation is or'd
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import asyncio
|
||||
import typing
|
||||
import bsdiff4
|
||||
@@ -15,6 +16,9 @@ from CommonClient import CommonContext, server_loop, \
|
||||
gui_enabled, ClientCommandProcessor, logger, get_base_parser
|
||||
from Utils import async_start
|
||||
|
||||
# Heartbeat for position sharing via bounces, in seconds
|
||||
UNDERTALE_STATUS_INTERVAL = 30.0
|
||||
UNDERTALE_ONLINE_TIMEOUT = 60.0
|
||||
|
||||
class UndertaleCommandProcessor(ClientCommandProcessor):
|
||||
def __init__(self, ctx):
|
||||
@@ -109,6 +113,11 @@ class UndertaleContext(CommonContext):
|
||||
self.completed_routes = {"pacifist": 0, "genocide": 0, "neutral": 0}
|
||||
# self.save_game_folder: files go in this path to pass data between us and the actual game
|
||||
self.save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE")
|
||||
self.last_sent_position: typing.Optional[tuple] = None
|
||||
self.last_room: typing.Optional[str] = None
|
||||
self.last_status_write: float = 0.0
|
||||
self.other_undertale_status: dict[int, dict] = {}
|
||||
|
||||
|
||||
def patch_game(self):
|
||||
with open(Utils.user_path("Undertale", "data.win"), "rb") as f:
|
||||
@@ -219,6 +228,9 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
||||
await ctx.send_msgs([{"cmd": "SetNotify", "keys": [str(ctx.slot)+" RoutesDone neutral",
|
||||
str(ctx.slot)+" RoutesDone pacifist",
|
||||
str(ctx.slot)+" RoutesDone genocide"]}])
|
||||
if any(info.game == "Undertale" and slot != ctx.slot
|
||||
for slot, info in ctx.slot_info.items()):
|
||||
ctx.set_notify("undertale_room_status")
|
||||
if args["slot_data"]["only_flakes"]:
|
||||
with open(os.path.join(ctx.save_game_folder, "GenoNoChest.flag"), "w") as f:
|
||||
f.close()
|
||||
@@ -263,6 +275,12 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
||||
if str(ctx.slot)+" RoutesDone pacifist" in args["keys"]:
|
||||
if args["keys"][str(ctx.slot) + " RoutesDone pacifist"] is not None:
|
||||
ctx.completed_routes["pacifist"] = args["keys"][str(ctx.slot)+" RoutesDone pacifist"]
|
||||
if "undertale_room_status" in args["keys"] and args["keys"]["undertale_room_status"]:
|
||||
status = args["keys"]["undertale_room_status"]
|
||||
ctx.other_undertale_status = {
|
||||
int(key): val for key, val in status.items()
|
||||
if int(key) != ctx.slot
|
||||
}
|
||||
elif cmd == "SetReply":
|
||||
if args["value"] is not None:
|
||||
if str(ctx.slot)+" RoutesDone pacifist" == args["key"]:
|
||||
@@ -271,6 +289,11 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
||||
ctx.completed_routes["genocide"] = args["value"]
|
||||
elif str(ctx.slot)+" RoutesDone neutral" == args["key"]:
|
||||
ctx.completed_routes["neutral"] = args["value"]
|
||||
if args.get("key") == "undertale_room_status" and args.get("value"):
|
||||
ctx.other_undertale_status = {
|
||||
int(key): val for key, val in args["value"].items()
|
||||
if int(key) != ctx.slot
|
||||
}
|
||||
elif cmd == "ReceivedItems":
|
||||
start_index = args["index"]
|
||||
|
||||
@@ -368,9 +391,8 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
||||
f.close()
|
||||
|
||||
elif cmd == "Bounced":
|
||||
tags = args.get("tags", [])
|
||||
if "Online" in tags:
|
||||
data = args.get("data", {})
|
||||
data = args.get("data", {})
|
||||
if "x" in data and "room" in data:
|
||||
if data["player"] != ctx.slot and data["player"] is not None:
|
||||
filename = f"FRISK" + str(data["player"]) + ".playerspot"
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
@@ -381,21 +403,63 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
||||
|
||||
async def multi_watcher(ctx: UndertaleContext):
|
||||
while not ctx.exit_event.is_set():
|
||||
path = ctx.save_game_folder
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if "spots.mine" in file and "Online" in ctx.tags:
|
||||
with open(os.path.join(root, file), "r") as mine:
|
||||
this_x = mine.readline()
|
||||
this_y = mine.readline()
|
||||
this_room = mine.readline()
|
||||
this_sprite = mine.readline()
|
||||
this_frame = mine.readline()
|
||||
mine.close()
|
||||
message = [{"cmd": "Bounce", "tags": ["Online"],
|
||||
"data": {"player": ctx.slot, "x": this_x, "y": this_y, "room": this_room,
|
||||
"spr": this_sprite, "frm": this_frame}}]
|
||||
await ctx.send_msgs(message)
|
||||
if "Online" in ctx.tags and any(
|
||||
info.game == "Undertale" and slot != ctx.slot
|
||||
for slot, info in ctx.slot_info.items()):
|
||||
now = time.time()
|
||||
path = ctx.save_game_folder
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if "spots.mine" in file:
|
||||
with open(os.path.join(root, file), "r") as mine:
|
||||
this_x = mine.readline()
|
||||
this_y = mine.readline()
|
||||
this_room = mine.readline()
|
||||
this_sprite = mine.readline()
|
||||
this_frame = mine.readline()
|
||||
|
||||
if this_room != ctx.last_room or \
|
||||
now - ctx.last_status_write >= UNDERTALE_STATUS_INTERVAL:
|
||||
ctx.last_room = this_room
|
||||
ctx.last_status_write = now
|
||||
await ctx.send_msgs([{
|
||||
"cmd": "Set",
|
||||
"key": "undertale_room_status",
|
||||
"default": {},
|
||||
"want_reply": False,
|
||||
"operations": [{"operation": "update",
|
||||
"value": {str(ctx.slot): {"room": this_room,
|
||||
"time": now}}}]
|
||||
}])
|
||||
|
||||
# If player was visible but timed out (heartbeat) or left the room, remove them.
|
||||
for slot, entry in ctx.other_undertale_status.items():
|
||||
if entry.get("room") != this_room or \
|
||||
now - entry.get("time", now) > UNDERTALE_ONLINE_TIMEOUT:
|
||||
playerspot = os.path.join(ctx.save_game_folder,
|
||||
f"FRISK{slot}.playerspot")
|
||||
if os.path.exists(playerspot):
|
||||
os.remove(playerspot)
|
||||
|
||||
current_position = (this_x, this_y, this_room, this_sprite, this_frame)
|
||||
if current_position == ctx.last_sent_position:
|
||||
continue
|
||||
|
||||
# Empty status dict = no data yet → send to bootstrap.
|
||||
online_in_room = any(
|
||||
entry.get("room") == this_room and
|
||||
now - entry.get("time", now) <= UNDERTALE_ONLINE_TIMEOUT
|
||||
for entry in ctx.other_undertale_status.values()
|
||||
)
|
||||
if ctx.other_undertale_status and not online_in_room:
|
||||
continue
|
||||
|
||||
message = [{"cmd": "Bounce", "games": ["Undertale"],
|
||||
"data": {"player": ctx.slot, "x": this_x, "y": this_y,
|
||||
"room": this_room, "spr": this_sprite,
|
||||
"frm": this_frame}}]
|
||||
await ctx.send_msgs(message)
|
||||
ctx.last_sent_position = current_position
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user