Multidata/save: moved away from pickle and store a compressed json instead

This commit is contained in:
Bonta-kun 2020-01-04 22:08:13 +01:00
parent 1be0d62d4f
commit a3657c02aa
2 changed files with 32 additions and 31 deletions

18
Main.py
View File

@ -4,9 +4,9 @@ from itertools import zip_longest
import json
import logging
import os
import pickle
import random
import time
import zlib
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
from Regions import create_regions, mark_light_world_regions
@ -140,12 +140,9 @@ def main(args, seed=None):
player_names = parse_names_string(args.names)
outfilebase = 'ER_%s' % (args.outputname if args.outputname else world.seed)
rom_names = []
jsonout = {}
if not args.suppress_rom:
from MultiServer import MultiWorld
multidata = MultiWorld()
multidata.players = world.players
for player in range(1, world.players + 1):
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none'
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
@ -161,16 +158,12 @@ def main(args, seed=None):
else:
rom = LocalRom(args.rom)
patch_rom(world, player, rom, use_enemizer)
rom_names.append((player, list(rom.name)))
enemizer_patch = []
if use_enemizer and (args.enemizercli or not args.jsonout):
enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shufflepalette[player], args.shufflepots[player])
multidata.rom_names[player] = list(rom.name)
for location in world.get_filled_locations(player):
if type(location.address) is int:
multidata.locations[(location.address, player)] = (location.item.code, location.item.player)
if args.jsonout:
jsonout[f'patch{player}'] = rom.patches
if use_enemizer:
@ -209,7 +202,10 @@ def main(args, seed=None):
rom.write_to_file(output_path(f'{outfilebase}{playername}{outfilesuffix}.sfc'))
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
pickle.dump(multidata, f, pickle.HIGHEST_PROTOCOL)
jsonstr = json.dumps((world.players,
rom_names,
[((location.address, location.player), (location.item.code, location.item.player)) for location in world.get_filled_locations() if type(location.address) is int]))
f.write(zlib.compress(jsonstr.encode("utf-8")))
if args.create_spoiler and not args.jsonout:
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))

View File

@ -4,10 +4,10 @@ import asyncio
import functools
import json
import logging
import pickle
import re
import urllib.request
import websockets
import zlib
import Items
import Regions
@ -22,18 +22,14 @@ class Client:
self.slot = None
self.send_index = 0
class MultiWorld:
def __init__(self):
self.players = None
self.rom_names = {}
self.locations = {}
class Context:
def __init__(self, host, port, password):
self.data_filename = None
self.save_filename = None
self.disable_save = False
self.world = MultiWorld()
self.players = 0
self.rom_names = {}
self.locations = {}
self.host = host
self.port = port
self.password = password
@ -44,7 +40,7 @@ class Context:
def get_room_info(ctx : Context):
return {
'password': ctx.password is not None,
'slots': ctx.world.players,
'slots': ctx.players,
'players': [(client.name, client.team, client.slot) for client in ctx.clients if client.auth]
}
@ -175,8 +171,8 @@ def forfeit_player(ctx : Context, team, slot, name):
def register_location_checks(ctx : Context, name, team, slot, locations):
found_items = False
for location in locations:
if (location, slot) in ctx.world.locations:
target_item, target_player = ctx.world.locations[(location, slot)]
if (location, slot) in ctx.locations:
target_item, target_player = ctx.locations[(location, slot)]
if target_player != slot:
found = False
recvd_items = get_received_items(ctx, team, target_player)
@ -196,7 +192,10 @@ def register_location_checks(ctx : Context, name, team, slot, locations):
if found_items and not ctx.disable_save:
try:
with open(ctx.save_filename, "wb") as f:
pickle.dump((ctx.world.players, ctx.world.rom_names, ctx.received_items), f, pickle.HIGHEST_PROTOCOL)
jsonstr = json.dumps((ctx.players,
[(k, v) for k, v in ctx.rom_names.items()],
[(k, [i.__dict__ for i in v]) for k, v in ctx.received_items.items()]))
f.write(zlib.compress(jsonstr.encode("utf-8")))
except Exception as e:
logging.exception(e)
@ -233,13 +232,13 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
if 'slot' in args and any([c.slot == args['slot'] for c in ctx.clients if c.auth and same_team(c.team, client.team)]):
errors.add('SlotAlreadyTaken')
elif 'slot' not in args or not args['slot']:
for slot in range(1, ctx.world.players + 1):
for slot in range(1, ctx.players + 1):
if slot not in [c.slot for c in ctx.clients if c.auth and same_team(c.team, client.team)]:
client.slot = slot
break
elif slot == ctx.world.players:
elif slot == ctx.players:
errors.add('SlotAlreadyTaken')
elif args['slot'] not in range(1, ctx.world.players + 1):
elif args['slot'] not in range(1, ctx.players + 1):
errors.add('InvalidSlot')
else:
client.slot = args['slot']
@ -251,7 +250,7 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
await send_msgs(client.socket, [['ConnectionRefused', list(errors)]])
else:
client.auth = True
reply = [['Connected', ctx.world.rom_names[client.slot]]]
reply = [['Connected', ctx.rom_names[client.slot]]]
items = get_received_items(ctx, client.team, client.slot)
if items:
reply.append(['ReceivedItems', (0, tuplize_received_items(items))])
@ -358,13 +357,16 @@ async def main():
ctx.data_filename = tkinter.filedialog.askopenfilename(filetypes=(("Multiworld data","*multidata"),))
with open(ctx.data_filename, 'rb') as f:
ctx.world = pickle.load(f)
jsonobj = json.loads(zlib.decompress(f.read()).decode("utf-8"))
ctx.players = jsonobj[0]
ctx.rom_names = {k: v for k, v in jsonobj[1]}
ctx.locations = {tuple(k): tuple(v) for k, v in jsonobj[2]}
except Exception as e:
print('Failed to read multiworld data (%s)' % e)
return
ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8') if not ctx.host else ctx.host
print('Hosting game of %d players (%s) at %s:%d' % (ctx.world.players, 'No password' if not ctx.password else 'Password: %s' % ctx.password, ip, ctx.port))
print('Hosting game of %d players (%s) at %s:%d' % (ctx.players, 'No password' if not ctx.password else 'Password: %s' % ctx.password, ip, ctx.port))
ctx.disable_save = args.disable_save
if not ctx.disable_save:
@ -372,8 +374,11 @@ async def main():
ctx.save_filename = (ctx.data_filename[:-9] if ctx.data_filename[-9:] == 'multidata' else (ctx.data_filename + '_')) + 'multisave'
try:
with open(ctx.save_filename, 'rb') as f:
players, rom_names, received_items = pickle.load(f)
if players != ctx.world.players or rom_names != ctx.world.rom_names:
jsonobj = json.loads(zlib.decompress(f.read()).decode("utf-8"))
players = jsonobj[0]
rom_names = {k: v for k, v in jsonobj[1]}
received_items = {tuple(k): [ReceivedItem(**i) for i in v] for k, v in jsonobj[2]}
if players != ctx.players or rom_names != ctx.rom_names:
raise Exception('Save file mismatch, will start a new game')
ctx.received_items = received_items
print('Loaded save file with %d received items for %d players' % (sum([len(p) for p in received_items.values()]), len(received_items)))