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
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
import typing
|
import typing
|
||||||
import bsdiff4
|
import bsdiff4
|
||||||
@@ -15,6 +16,9 @@ from CommonClient import CommonContext, server_loop, \
|
|||||||
gui_enabled, ClientCommandProcessor, logger, get_base_parser
|
gui_enabled, ClientCommandProcessor, logger, get_base_parser
|
||||||
from Utils import async_start
|
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):
|
class UndertaleCommandProcessor(ClientCommandProcessor):
|
||||||
def __init__(self, ctx):
|
def __init__(self, ctx):
|
||||||
@@ -109,6 +113,11 @@ class UndertaleContext(CommonContext):
|
|||||||
self.completed_routes = {"pacifist": 0, "genocide": 0, "neutral": 0}
|
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: 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.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):
|
def patch_game(self):
|
||||||
with open(Utils.user_path("Undertale", "data.win"), "rb") as f:
|
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",
|
await ctx.send_msgs([{"cmd": "SetNotify", "keys": [str(ctx.slot)+" RoutesDone neutral",
|
||||||
str(ctx.slot)+" RoutesDone pacifist",
|
str(ctx.slot)+" RoutesDone pacifist",
|
||||||
str(ctx.slot)+" RoutesDone genocide"]}])
|
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"]:
|
if args["slot_data"]["only_flakes"]:
|
||||||
with open(os.path.join(ctx.save_game_folder, "GenoNoChest.flag"), "w") as f:
|
with open(os.path.join(ctx.save_game_folder, "GenoNoChest.flag"), "w") as f:
|
||||||
f.close()
|
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 str(ctx.slot)+" RoutesDone pacifist" in args["keys"]:
|
||||||
if args["keys"][str(ctx.slot) + " RoutesDone pacifist"] is not None:
|
if args["keys"][str(ctx.slot) + " RoutesDone pacifist"] is not None:
|
||||||
ctx.completed_routes["pacifist"] = args["keys"][str(ctx.slot)+" RoutesDone pacifist"]
|
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":
|
elif cmd == "SetReply":
|
||||||
if args["value"] is not None:
|
if args["value"] is not None:
|
||||||
if str(ctx.slot)+" RoutesDone pacifist" == args["key"]:
|
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"]
|
ctx.completed_routes["genocide"] = args["value"]
|
||||||
elif str(ctx.slot)+" RoutesDone neutral" == args["key"]:
|
elif str(ctx.slot)+" RoutesDone neutral" == args["key"]:
|
||||||
ctx.completed_routes["neutral"] = args["value"]
|
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":
|
elif cmd == "ReceivedItems":
|
||||||
start_index = args["index"]
|
start_index = args["index"]
|
||||||
|
|
||||||
@@ -368,9 +391,8 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
|||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
elif cmd == "Bounced":
|
elif cmd == "Bounced":
|
||||||
tags = args.get("tags", [])
|
data = args.get("data", {})
|
||||||
if "Online" in tags:
|
if "x" in data and "room" in data:
|
||||||
data = args.get("data", {})
|
|
||||||
if data["player"] != ctx.slot and data["player"] is not None:
|
if data["player"] != ctx.slot and data["player"] is not None:
|
||||||
filename = f"FRISK" + str(data["player"]) + ".playerspot"
|
filename = f"FRISK" + str(data["player"]) + ".playerspot"
|
||||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
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):
|
async def multi_watcher(ctx: UndertaleContext):
|
||||||
while not ctx.exit_event.is_set():
|
while not ctx.exit_event.is_set():
|
||||||
path = ctx.save_game_folder
|
if "Online" in ctx.tags and any(
|
||||||
for root, dirs, files in os.walk(path):
|
info.game == "Undertale" and slot != ctx.slot
|
||||||
for file in files:
|
for slot, info in ctx.slot_info.items()):
|
||||||
if "spots.mine" in file and "Online" in ctx.tags:
|
now = time.time()
|
||||||
with open(os.path.join(root, file), "r") as mine:
|
path = ctx.save_game_folder
|
||||||
this_x = mine.readline()
|
for root, dirs, files in os.walk(path):
|
||||||
this_y = mine.readline()
|
for file in files:
|
||||||
this_room = mine.readline()
|
if "spots.mine" in file:
|
||||||
this_sprite = mine.readline()
|
with open(os.path.join(root, file), "r") as mine:
|
||||||
this_frame = mine.readline()
|
this_x = mine.readline()
|
||||||
mine.close()
|
this_y = mine.readline()
|
||||||
message = [{"cmd": "Bounce", "tags": ["Online"],
|
this_room = mine.readline()
|
||||||
"data": {"player": ctx.slot, "x": this_x, "y": this_y, "room": this_room,
|
this_sprite = mine.readline()
|
||||||
"spr": this_sprite, "frm": this_frame}}]
|
this_frame = mine.readline()
|
||||||
await ctx.send_msgs(message)
|
|
||||||
|
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)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user