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 json
import logging import logging
import os import os
import pickle
import random import random
import time import time
import zlib
from BaseClasses import World, CollectionState, Item, Region, Location, Shop from BaseClasses import World, CollectionState, Item, Region, Location, Shop
from Regions import create_regions, mark_light_world_regions 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) player_names = parse_names_string(args.names)
outfilebase = 'ER_%s' % (args.outputname if args.outputname else world.seed) outfilebase = 'ER_%s' % (args.outputname if args.outputname else world.seed)
rom_names = []
jsonout = {} jsonout = {}
if not args.suppress_rom: if not args.suppress_rom:
from MultiServer import MultiWorld
multidata = MultiWorld()
multidata.players = world.players
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none' 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' or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
@ -161,16 +158,12 @@ def main(args, seed=None):
else: else:
rom = LocalRom(args.rom) rom = LocalRom(args.rom)
patch_rom(world, player, rom, use_enemizer) patch_rom(world, player, rom, use_enemizer)
rom_names.append((player, list(rom.name)))
enemizer_patch = [] enemizer_patch = []
if use_enemizer and (args.enemizercli or not args.jsonout): 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]) 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: if args.jsonout:
jsonout[f'patch{player}'] = rom.patches jsonout[f'patch{player}'] = rom.patches
if use_enemizer: if use_enemizer:
@ -209,7 +202,10 @@ def main(args, seed=None):
rom.write_to_file(output_path(f'{outfilebase}{playername}{outfilesuffix}.sfc')) rom.write_to_file(output_path(f'{outfilebase}{playername}{outfilesuffix}.sfc'))
with open(output_path('%s_multidata' % outfilebase), 'wb') as f: 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: if args.create_spoiler and not args.jsonout:
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))

View File

@ -4,10 +4,10 @@ import asyncio
import functools import functools
import json import json
import logging import logging
import pickle
import re import re
import urllib.request import urllib.request
import websockets import websockets
import zlib
import Items import Items
import Regions import Regions
@ -22,18 +22,14 @@ class Client:
self.slot = None self.slot = None
self.send_index = 0 self.send_index = 0
class MultiWorld:
def __init__(self):
self.players = None
self.rom_names = {}
self.locations = {}
class Context: class Context:
def __init__(self, host, port, password): def __init__(self, host, port, password):
self.data_filename = None self.data_filename = None
self.save_filename = None self.save_filename = None
self.disable_save = False self.disable_save = False
self.world = MultiWorld() self.players = 0
self.rom_names = {}
self.locations = {}
self.host = host self.host = host
self.port = port self.port = port
self.password = password self.password = password
@ -44,7 +40,7 @@ class Context:
def get_room_info(ctx : Context): def get_room_info(ctx : Context):
return { return {
'password': ctx.password is not None, '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] '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): def register_location_checks(ctx : Context, name, team, slot, locations):
found_items = False found_items = False
for location in locations: for location in locations:
if (location, slot) in ctx.world.locations: if (location, slot) in ctx.locations:
target_item, target_player = ctx.world.locations[(location, slot)] target_item, target_player = ctx.locations[(location, slot)]
if target_player != slot: if target_player != slot:
found = False found = False
recvd_items = get_received_items(ctx, team, target_player) 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: if found_items and not ctx.disable_save:
try: try:
with open(ctx.save_filename, "wb") as f: 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: except Exception as e:
logging.exception(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)]): 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') errors.add('SlotAlreadyTaken')
elif 'slot' not in args or not args['slot']: 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)]: if slot not in [c.slot for c in ctx.clients if c.auth and same_team(c.team, client.team)]:
client.slot = slot client.slot = slot
break break
elif slot == ctx.world.players: elif slot == ctx.players:
errors.add('SlotAlreadyTaken') 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') errors.add('InvalidSlot')
else: else:
client.slot = args['slot'] 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)]]) await send_msgs(client.socket, [['ConnectionRefused', list(errors)]])
else: else:
client.auth = True 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) items = get_received_items(ctx, client.team, client.slot)
if items: if items:
reply.append(['ReceivedItems', (0, tuplize_received_items(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"),)) ctx.data_filename = tkinter.filedialog.askopenfilename(filetypes=(("Multiworld data","*multidata"),))
with open(ctx.data_filename, 'rb') as f: 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: except Exception as e:
print('Failed to read multiworld data (%s)' % e) print('Failed to read multiworld data (%s)' % e)
return return
ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8') if not ctx.host else ctx.host 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 ctx.disable_save = args.disable_save
if not ctx.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' ctx.save_filename = (ctx.data_filename[:-9] if ctx.data_filename[-9:] == 'multidata' else (ctx.data_filename + '_')) + 'multisave'
try: try:
with open(ctx.save_filename, 'rb') as f: with open(ctx.save_filename, 'rb') as f:
players, rom_names, received_items = pickle.load(f) jsonobj = json.loads(zlib.decompress(f.read()).decode("utf-8"))
if players != ctx.world.players or rom_names != ctx.world.rom_names: 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') raise Exception('Save file mismatch, will start a new game')
ctx.received_items = received_items 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))) print('Loaded save file with %d received items for %d players' % (sum([len(p) for p in received_items.values()]), len(received_items)))