MultiServer: add network commands Store, Retrieve, Modify and ModifyNotify
This commit is contained in:
parent
f38b970ea2
commit
5faf1f27de
|
@ -15,6 +15,7 @@ import random
|
|||
import pickle
|
||||
import itertools
|
||||
import time
|
||||
import operator
|
||||
|
||||
import ModuleUpdate
|
||||
|
||||
|
@ -38,6 +39,16 @@ from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, Networ
|
|||
|
||||
colorama.init()
|
||||
|
||||
# functions callable on storable data on the server by clients
|
||||
modify_functions = {
|
||||
"add": operator.add,
|
||||
"mul": operator.mul,
|
||||
"max": max,
|
||||
"min": min,
|
||||
"replace": lambda old, new: new,
|
||||
"deplete": lambda value, change: max(0, value + change)
|
||||
}
|
||||
|
||||
|
||||
class Client(Endpoint):
|
||||
version = Version(0, 0, 0)
|
||||
|
@ -100,6 +111,8 @@ class Context:
|
|||
locations: typing.Dict[int, typing.Dict[int, typing.Tuple[int, int, int]]]
|
||||
groups: typing.Dict[int, typing.Set[int]]
|
||||
save_version = 2
|
||||
stored_data: typing.Dict[str, object]
|
||||
stored_data_notification_clients: typing.Dict[str, typing.Set[Client]]
|
||||
|
||||
def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int,
|
||||
hint_cost: int, item_cheat: bool, forfeit_mode: str = "disabled", collect_mode="disabled",
|
||||
|
@ -161,6 +174,8 @@ class Context:
|
|||
self.seed_name = ""
|
||||
self.groups = {}
|
||||
self.random = random.Random()
|
||||
self.stored_data = {}
|
||||
self.stored_data_notification_clients = collections.defaultdict(weakref.WeakSet)
|
||||
|
||||
# General networking
|
||||
|
||||
|
@ -408,7 +423,8 @@ class Context:
|
|||
(key, value.timestamp()) for key, value in self.client_activity_timers.items()),
|
||||
"client_connection_timers": tuple(
|
||||
(key, value.timestamp()) for key, value in self.client_connection_timers.items()),
|
||||
"random_state": self.random.getstate()
|
||||
"random_state": self.random.getstate(),
|
||||
"stored_data": self.stored_data
|
||||
}
|
||||
|
||||
return d
|
||||
|
@ -444,11 +460,14 @@ class Context:
|
|||
{tuple(key): datetime.datetime.fromtimestamp(value, datetime.timezone.utc) for key, value
|
||||
in savedata["client_activity_timers"]})
|
||||
self.location_checks.update(savedata["location_checks"])
|
||||
if "random_state" in savedata:
|
||||
self.random.setstate(savedata["random_state"])
|
||||
self.random.setstate(savedata["random_state"])
|
||||
|
||||
if "stored_data" in savedata:
|
||||
self.stored_data = savedata["stored_data"]
|
||||
# count items and slots from lists for item_handling = remote
|
||||
logging.info(f'Loaded save file with {sum([len(v) for k,v in self.received_items.items() if k[2]])} received items '
|
||||
f'for {sum(k[2] for k in self.received_items)} players')
|
||||
logging.info(
|
||||
f'Loaded save file with {sum([len(v) for k, v in self.received_items.items() if k[2]])} received items '
|
||||
f'for {sum(k[2] for k in self.received_items)} players')
|
||||
|
||||
# rest
|
||||
|
||||
|
@ -1506,6 +1525,48 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
|||
bounceclient.slot in slots):
|
||||
await ctx.send_encoded_msgs(bounceclient, msg)
|
||||
|
||||
elif cmd == "Store":
|
||||
if "data" not in args or type(args["data"]) != dict:
|
||||
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "arguments",
|
||||
"text": 'Store', "original_cmd": cmd}])
|
||||
return
|
||||
for key, value in args["data"].items():
|
||||
ctx.stored_data[key] = value
|
||||
|
||||
elif cmd == "Retrieve":
|
||||
if "data" not in args or type(args["data"]) != list:
|
||||
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "arguments",
|
||||
"text": 'Retrieve', "original_cmd": cmd}])
|
||||
return
|
||||
args["cmd"] = "Retrieved"
|
||||
keys = args["data"]
|
||||
args["data"] = {key: ctx.stored_data.get(key, None) for key in keys}
|
||||
await ctx.send_msgs(client, [args])
|
||||
|
||||
elif cmd == "Modify":
|
||||
if "key" not in args or "value" not in args:
|
||||
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "arguments",
|
||||
"text": 'Modify', "original_cmd": cmd}])
|
||||
return
|
||||
args["cmd"] = "Modified"
|
||||
value = ctx.stored_data.get(args["key"], args.get("default", 0))
|
||||
args["original_value"] = value
|
||||
operation = args.get("operation", "add")
|
||||
func = modify_functions[operation]
|
||||
value = func(value, args.get("value"))
|
||||
ctx.stored_data[args["key"]] = args["value"] = value
|
||||
targets = set(ctx.stored_data_notification_clients[args["key"]])
|
||||
targets.add(client)
|
||||
ctx.broadcast(targets, [args])
|
||||
|
||||
elif cmd == "ModifyNotify":
|
||||
if "data" not in args or type(args["data"]) != list:
|
||||
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "arguments",
|
||||
"text": 'ModifyNotify', "original_cmd": cmd}])
|
||||
return
|
||||
for key in args["data"]:
|
||||
ctx.stored_data_notification_clients[key].add(client)
|
||||
|
||||
|
||||
def update_client_status(ctx: Context, client: Client, new_status: ClientStatus):
|
||||
current = ctx.client_game_state[client.team, client.slot]
|
||||
|
|
|
@ -77,10 +77,10 @@ class Filler(object):
|
|||
if aboveMaxDiffStr != '[ ]':
|
||||
self.errorMsg += "\nMaximum difficulty could not be applied everywhere. Affected locations: {}".format(aboveMaxDiffStr)
|
||||
isStuck = False
|
||||
print('\n%d step(s) in %dms' % (self.nSteps, int((date-self.startDate)*1000)))
|
||||
|
||||
if self.vcr != None:
|
||||
self.vcr.dump()
|
||||
return (isStuck, self.container.itemLocations, self.getProgressionItemLocations())
|
||||
return isStuck, self.container.itemLocations, self.getProgressionItemLocations()
|
||||
|
||||
# helper method to collect in item/location with logic. updates self.ap and VCR
|
||||
def collect(self, itemLoc, container=None, pickup=True):
|
||||
|
|
|
@ -168,7 +168,7 @@ class SoEWorld(World):
|
|||
self.world.get_location(wings_location, self.player).place_locked_item(wings_item)
|
||||
self.world.itempool.remove(wings_item)
|
||||
# generate stuff for later
|
||||
self.evermizer_seed = self.world.random.randint(0, 2**16-1) # TODO: make this an option for "full" plando?
|
||||
self.evermizer_seed = self.world.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando?
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
player_name = self.world.get_player_name(self.player)
|
||||
|
@ -223,7 +223,7 @@ class SoEWorld(World):
|
|||
try:
|
||||
os.unlink(placement_file)
|
||||
os.unlink(out_file)
|
||||
os.unlink(out_file[:-4]+'_SPOILER.log')
|
||||
os.unlink(out_file[:-4] + '_SPOILER.log')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
@ -236,7 +236,6 @@ class SoEWorld(World):
|
|||
multidata["connect_names"][self.connect_name] = payload
|
||||
|
||||
|
||||
|
||||
class SoEItem(Item):
|
||||
game: str = "Secret of Evermore"
|
||||
|
||||
|
|
Loading…
Reference in New Issue