regain basic WebHost functionality

This commit is contained in:
Fabian Dill 2021-05-13 21:57:11 +02:00
parent c5ff962ea1
commit b82d6cec31
9 changed files with 92 additions and 135 deletions

View File

@ -7,7 +7,7 @@ from concurrent.futures import ThreadPoolExecutor
import colorama import colorama
import asyncio import asyncio
from queue import Queue, Empty from queue import Queue
from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, logger from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, logger
from MultiServer import mark_raw from MultiServer import mark_raw

View File

@ -1,4 +1,6 @@
import json
import pickle import pickle
from uuid import UUID from uuid import UUID
from . import api_endpoints from . import api_endpoints
@ -46,7 +48,7 @@ def generate_api():
gen = Generation( gen = Generation(
options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}), options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}),
# convert to json compatible # convert to json compatible
meta=pickle.dumps({"race": race}), state=STATE_QUEUED, meta=json.dumps({"race": race}), state=STATE_QUEUED,
owner=session["_id"]) owner=session["_id"])
commit() commit()
return {"text": f"Generation of seed {gen.id} started successfully.", return {"text": f"Generation of seed {gen.id} started successfully.",
@ -55,7 +57,8 @@ def generate_api():
"wait_api_url": url_for("api.wait_seed_api", seed=gen.id, _external=True), "wait_api_url": url_for("api.wait_seed_api", seed=gen.id, _external=True),
"url": url_for("wait_seed", seed=gen.id, _external=True)}, 201 "url": url_for("wait_seed", seed=gen.id, _external=True)}, 201
except Exception as e: except Exception as e:
return {"text": "Uncaught Exception:" + str(e)}, 500 raise
@api_endpoints.route('/status/<suuid:seed>') @api_endpoints.route('/status/<suuid:seed>')

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import json
import multiprocessing import multiprocessing
from datetime import timedelta, datetime from datetime import timedelta, datetime
import concurrent.futures import concurrent.futures
@ -9,6 +10,8 @@ import time
from pony.orm import db_session, select, commit from pony.orm import db_session, select, commit
from Utils import restricted_loads
class CommonLocker(): class CommonLocker():
"""Uses a file lock to signal that something is already running""" """Uses a file lock to signal that something is already running"""
@ -78,14 +81,21 @@ def handle_generation_failure(result: BaseException):
def launch_generator(pool: multiprocessing.pool.Pool, generation: Generation): def launch_generator(pool: multiprocessing.pool.Pool, generation: Generation):
options = generation.options try:
logging.info(f"Generating {generation.id} for {len(options)} players") meta = json.loads(generation.meta)
options = restricted_loads(generation.options)
meta = generation.meta logging.info(f"Generating {generation.id} for {len(options)} players")
pool.apply_async(gen_game, (options,), pool.apply_async(gen_game, (options,),
{"race": meta["race"], "sid": generation.id, "owner": generation.owner}, {"race": meta["race"],
handle_generation_success, handle_generation_failure) "sid": generation.id,
generation.state = STATE_STARTED "owner": generation.owner},
handle_generation_success, handle_generation_failure)
except:
generation.state = STATE_ERROR
commit()
raise
else:
generation.state = STATE_STARTED
def init_db(pony_config: dict): def init_db(pony_config: dict):

View File

@ -1,6 +1,7 @@
import os import os
import tempfile import tempfile
import random import random
import json
from collections import Counter from collections import Counter
from flask import request, flash, redirect, url_for, session, render_template from flask import request, flash, redirect, url_for, session, render_template
@ -94,10 +95,6 @@ def gen_game(gen_options, race=False, owner=None, sid=None):
erargs.names = ",".join(erargs.name[i] for i in range(1, playercount + 1)) erargs.names = ",".join(erargs.name[i] for i in range(1, playercount + 1))
del (erargs.name) del (erargs.name)
erargs.skip_progression_balancing = {player: not balanced for player, balanced in
erargs.progression_balancing.items()}
del (erargs.progression_balancing)
ERmain(erargs, seed) ERmain(erargs, seed)
return upload_to_db(target.name, owner, sid, race) return upload_to_db(target.name, owner, sid, race)
@ -107,7 +104,11 @@ def gen_game(gen_options, race=False, owner=None, sid=None):
gen = Generation.get(id=sid) gen = Generation.get(id=sid)
if gen is not None: if gen is not None:
gen.state = STATE_ERROR gen.state = STATE_ERROR
gen.meta = (e.__class__.__name__ + ": "+ str(e)).encode() meta = json.loads(gen.meta)
meta["error"] = (e.__class__.__name__ + ": "+ str(e))
gen.meta = json.dumps(meta)
commit()
raise raise
@ -122,7 +123,7 @@ def wait_seed(seed: UUID):
if not generation: if not generation:
return "Generation not found." return "Generation not found."
elif generation.state == STATE_ERROR: elif generation.state == STATE_ERROR:
return render_template("seedError.html", seed_error=generation.meta.decode()) return render_template("seedError.html", seed_error=generation.meta)
return render_template("waitSeed.html", seed_id=seed_id) return render_template("waitSeed.html", seed_id=seed_id)
@ -147,10 +148,10 @@ def upload_to_db(folder, owner, sid, race:bool):
with db_session: with db_session:
if sid: if sid:
seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=owner, seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=owner,
id=sid, meta={"tags": ["generated"]}) id=sid, meta=json.dumps({"tags": ["generated"]}))
else: else:
seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=owner, seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=owner,
meta={"tags": ["generated"]}) meta=json.dumps({"tags": ["generated"]}))
for patch in patches: for patch in patches:
patch.seed = seed patch.seed = seed
if sid: if sid:

View File

@ -39,7 +39,7 @@ class Seed(db.Entity):
creation_time = Required(datetime, default=lambda: datetime.utcnow()) creation_time = Required(datetime, default=lambda: datetime.utcnow())
patches = Set(Patch) patches = Set(Patch)
spoiler = Optional(LongStr, lazy=True) spoiler = Optional(LongStr, lazy=True)
meta = Required(Json, lazy=True, default=lambda: {}) # additional meta information/tags meta = Required(str, default=lambda: "{\"race\": false}") # additional meta information/tags
class Command(db.Entity): class Command(db.Entity):
@ -51,6 +51,6 @@ class Command(db.Entity):
class Generation(db.Entity): class Generation(db.Entity):
id = PrimaryKey(UUID, default=uuid4) id = PrimaryKey(UUID, default=uuid4)
owner = Required(UUID) owner = Required(UUID)
options = Required(Json, lazy=True) options = Required(buffer, lazy=True)
meta = Required(Json, lazy=True) meta = Required(str, default=lambda: "{\"race\": false}")
state = Required(int, default=0, index=True) state = Required(int, default=0, index=True)

View File

@ -39,7 +39,7 @@
</tr> </tr>
<tr> <tr>
<td><img src="{{ icons["Pegasus Boots"] }}" class="{{ 'acquired' if "Pegasus Boots" in acquired_items }}" /></td> <td><img src="{{ icons["Pegasus Boots"] }}" class="{{ 'acquired' if "Pegasus Boots" in acquired_items }}" /></td>
<td><img src="{{ gloves_url }}" class="{{ 'acquired' if gloves_acquired }}" /></td> <td><img src="{{ glove_url }}" class="{{ 'acquired' if glove_acquired }}" /></td>
<td><img src="{{ icons["Flippers"] }}" class="{{ 'acquired' if "Flippers" in acquired_items }}" /></td> <td><img src="{{ icons["Flippers"] }}" class="{{ 'acquired' if "Flippers" in acquired_items }}" /></td>
<td><img src="{{ icons["Moon Pearl"] }}" class="{{ 'acquired' if "Moon Pearl" in acquired_items }}" /></td> <td><img src="{{ icons["Moon Pearl"] }}" class="{{ 'acquired' if "Moon Pearl" in acquired_items }}" /></td>
<td><img src="{{ icons["Mushroom"] }}" class="{{ 'acquired' if "Mushroom" in acquired_items }}" /></td> <td><img src="{{ icons["Mushroom"] }}" class="{{ 'acquired' if "Mushroom" in acquired_items }}" /></td>

View File

@ -31,10 +31,7 @@
<tr> <tr>
<td><a href="{{ url_for("viewSeed", seed=room.seed.id) }}">{{ room.seed.id|suuid }}</a></td> <td><a href="{{ url_for("viewSeed", seed=room.seed.id) }}">{{ room.seed.id|suuid }}</a></td>
<td><a href="{{ url_for("hostRoom", room=room.id) }}">{{ room.id|suuid }}</a></td> <td><a href="{{ url_for("hostRoom", room=room.id) }}">{{ room.id|suuid }}</a></td>
<td <td>>={{ room.seed.patches|length }}</td>
class="center"
data-tooltip="{{ room.seed.multidata.names[0]|join(", ")|truncate(256, False, " ...") }}"
>{{ room.seed.multidata.names[0]|length }}</td>
<td>{{ room.creation_time.strftime("%Y-%m-%d %H:%M") }}</td> <td>{{ room.creation_time.strftime("%Y-%m-%d %H:%M") }}</td>
<td>{{ room.last_activity.strftime("%Y-%m-%d %H:%M") }}</td> <td>{{ room.last_activity.strftime("%Y-%m-%d %H:%M") }}</td>
</tr> </tr>
@ -59,11 +56,7 @@
{% for seed in seeds %} {% for seed in seeds %}
<tr> <tr>
<td><a href="{{ url_for("viewSeed", seed=seed.id) }}">{{ seed.id|suuid }}</a></td> <td><a href="{{ url_for("viewSeed", seed=seed.id) }}">{{ seed.id|suuid }}</a></td>
<td class="center" <td>{% if seed.multidata %}>={{ seed.patches|length }}{% else %}1{% endif %}
{% if seed.multidata %}
data-tooltip="{{ seed.multidata.names[0]|join(", ")|truncate(256, False, " ...") }}"
{% endif %}
>{% if seed.multidata %}{{ seed.multidata.names[0]|length }}{% else %}1{% endif %}
</td> </td>
<td>{{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }}</td> <td>{{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }}</td>
</tr> </tr>

View File

@ -266,6 +266,7 @@ def attribute_item(inventory, team, recipient, item):
def attribute_item_solo(inventory, item): def attribute_item_solo(inventory, item):
"""Adds item to inventory counter, converts everything to progressive."""
target_item = links.get(item, item) target_item = links.get(item, item)
if item in levels: # non-progressive if item in levels: # non-progressive
inventory[target_item] = max(inventory[target_item], levels[item]) inventory[target_item] = max(inventory[target_item], levels[item])
@ -319,11 +320,12 @@ def get_static_room_data(room: Room):
player_big_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1)} player_big_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1)}
player_small_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1)} player_small_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1)}
for _, (item_id, item_player) in locations.items(): for loc_data in locations.values():
if item_id in ids_big_key: for item_id, item_player in loc_data.values():
player_big_key_locations[item_player].add(ids_big_key[item_id]) if item_id in ids_big_key:
if item_id in ids_small_key: player_big_key_locations[item_player].add(ids_big_key[item_id])
player_small_key_locations[item_player].add(ids_small_key[item_id]) elif item_id in ids_small_key:
player_small_key_locations[item_player].add(ids_small_key[item_id])
result = locations, names, use_door_tracker, player_checks_in_area, player_location_to_area, \ result = locations, names, use_door_tracker, player_checks_in_area, player_location_to_area, \
player_big_key_locations, player_small_key_locations, multidata["precollected_items"] player_big_key_locations, player_small_key_locations, multidata["precollected_items"]
@ -332,7 +334,7 @@ def get_static_room_data(room: Room):
@app.route('/tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>') @app.route('/tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>')
@cache.memoize(timeout=15) @cache.memoize(timeout=60) # multisave is currently created at most every minute
def getPlayerTracker(tracker: UUID, tracked_team: int, tracked_player: int): def getPlayerTracker(tracker: UUID, tracked_team: int, tracked_player: int):
# Team and player must be positive and greater than zero # Team and player must be positive and greater than zero
if tracked_team < 0 or tracked_player < 1: if tracked_team < 0 or tracked_player < 1:
@ -362,35 +364,25 @@ def getPlayerTracker(tracker: UUID, tracked_team: int, tracked_player: int):
multisave = {} multisave = {}
# Add items to player inventory # Add items to player inventory
for (ms_team, ms_player), locations_checked in multisave.get("location_checks", {}): for (ms_team, ms_player), locations_checked in multisave.get("location_checks", {}).items():
# logging.info(f"{ms_team}, {ms_player}, {locations_checked}") # logging.info(f"{ms_team}, {ms_player}, {locations_checked}")
# Skip teams and players not matching the request # Skip teams and players not matching the request
player_locations = locations[ms_player]
if ms_team == tracked_team: if ms_team == tracked_team:
# If the player does not have the item, do nothing # If the player does not have the item, do nothing
for location in locations_checked: for location in locations_checked:
if (location, ms_player) not in locations: if location in player_locations:
continue item, recipient = player_locations[location]
if recipient == tracked_player: # a check done for the tracked player
item, recipient = locations[location, ms_player] attribute_item_solo(inventory, item)
if recipient == tracked_player: # a check done for the tracked player if ms_player == tracked_player: # a check done by the tracked player
attribute_item_solo(inventory, item) checks_done[location_to_area[location]] += 1
if ms_player == tracked_player: # a check done by the tracked player checks_done["Total"] += 1
checks_done[location_to_area[location]] += 1
checks_done["Total"] += 1
# Note the presence of the triforce item # Note the presence of the triforce item
for (ms_team, ms_player), game_state in multisave.get("client_game_state", []): game_state = multisave.get("client_game_state", {}).get((tracked_team, tracked_player), 0)
# Skip teams and players not matching the request if game_state == 30:
if ms_team != tracked_team or ms_player != tracked_player: inventory[106] = 1 # Triforce
continue
if game_state:
inventory[106] = 1 # Triforce
acquired_items = []
for itm in inventory:
acquired_items.append(get_item_name_from_id(itm))
# Progressive items need special handling for icons and class # Progressive items need special handling for icons and class
progressive_items = { progressive_items = {
@ -400,86 +392,42 @@ def getPlayerTracker(tracker: UUID, tracked_team: int, tracked_player: int):
"Progressive Mail": 96, "Progressive Mail": 96,
"Progressive Shield": 95, "Progressive Shield": 95,
} }
progressive_names = {
"Progressive Sword": [None, 'Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword'],
"Progressive Glove": [None, 'Power Glove', 'Titan Mitts'],
"Progressive Bow": [None, "Bow", "Silver Bow"],
"Progressive Mail": ["Green Mail", "Blue Mail", "Red Mail"],
"Progressive Shield": [None, "Blue Shield", "Red Shield", "Mirror Shield"]
}
# Determine which icon to use for the sword # Determine which icon to use
sword_url = icons["Fighter Sword"] display_data = {}
sword_acquired = False for item_name, item_id in progressive_items.items():
sword_names = ['Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword'] level = min(inventory[item_id], len(progressive_names[item_name]))
if "Progressive Sword" in acquired_items: display_name = progressive_names[item_name][level]
sword_url = icons[sword_names[min(inventory[progressive_items["Progressive Sword"]], 4) - 1]] acquired = True
sword_acquired = True if not display_name:
else: acquired = False
for sword in reversed(sword_names): display_name = progressive_names[item_name][level+1]
if sword in acquired_items: base_name = item_name.split(maxsplit=1)[1].lower()
sword_url = icons[sword] display_data[base_name+"_acquired"] = acquired
sword_acquired = True display_data[base_name+"_url"] = icons[display_name]
break
gloves_url = icons["Power Glove"]
gloves_acquired = False
glove_names = ["Power Glove", "Titan Mitts"]
if "Progressive Glove" in acquired_items:
gloves_url = icons[glove_names[min(inventory[progressive_items["Progressive Glove"]], 2) - 1]]
gloves_acquired = True
else:
for glove in reversed(glove_names):
if glove in acquired_items:
gloves_url = icons[glove]
gloves_acquired = True
break
bow_url = icons["Bow"]
bow_acquired = False
bow_names = ["Bow", "Silver Bow"]
if "Progressive Bow" in acquired_items:
bow_url = icons[bow_names[min(inventory[progressive_items["Progressive Bow"]], 2) - 1]]
bow_acquired = True
else:
for bow in reversed(bow_names):
if bow in acquired_items:
bow_url = icons[bow]
bow_acquired = True
break
mail_url = icons["Green Mail"]
mail_names = ["Blue Mail", "Red Mail"]
if "Progressive Mail" in acquired_items:
mail_url = icons[mail_names[min(inventory[progressive_items["Progressive Mail"]], 2) - 1]]
else:
for mail in reversed(mail_names):
if mail in acquired_items:
mail_url = icons[mail]
break
shield_url = icons["Blue Shield"]
shield_acquired = False
shield_names = ["Blue Shield", "Red Shield", "Mirror Shield"]
if "Progressive Shield" in acquired_items:
shield_url = icons[shield_names[min(inventory[progressive_items["Progressive Shield"]], 3) - 1]]
shield_acquired = True
else:
for shield in reversed(shield_names):
if shield in acquired_items:
shield_url = icons[shield]
shield_acquired = True
break
# The single player tracker doesn't care about overworld, underworld, and total checks. Maybe it should? # The single player tracker doesn't care about overworld, underworld, and total checks. Maybe it should?
sp_areas = ordered_areas[2:15] sp_areas = ordered_areas[2:15]
return render_template("playerTracker.html", inventory=inventory, get_item_name_from_id=get_item_name_from_id, return render_template("playerTracker.html", inventory=inventory, get_item_name_from_id=get_item_name_from_id,
player_name=player_name, room=room, icons=icons, checks_done=checks_done, player_name=player_name, room=room, icons=icons, checks_done=checks_done,
checks_in_area=seed_checks_in_area, acquired_items=acquired_items, checks_in_area=seed_checks_in_area, acquired_items={Items.lookup_id_to_name[id] for id in inventory},
sword_url=sword_url, sword_acquired=sword_acquired, gloves_url=gloves_url,
gloves_acquired=gloves_acquired, bow_url=bow_url, bow_acquired=bow_acquired,
small_key_ids=small_key_ids, big_key_ids=big_key_ids, sp_areas=sp_areas, small_key_ids=small_key_ids, big_key_ids=big_key_ids, sp_areas=sp_areas,
key_locations=player_small_key_locations[tracked_player], key_locations=player_small_key_locations[tracked_player],
big_key_locations=player_big_key_locations[tracked_player], big_key_locations=player_big_key_locations[tracked_player],
mail_url=mail_url, shield_url=shield_url, shield_acquired=shield_acquired) **display_data)
@app.route('/tracker/<suuid:tracker>') @app.route('/tracker/<suuid:tracker>')
@cache.memoize(timeout=30) # update every 30 seconds @cache.memoize(timeout=60) # multisave is currently created at most every minute
def getTracker(tracker: UUID): def getTracker(tracker: UUID):
room = Room.get(tracker=tracker) room = Room.get(tracker=tracker)
if not room: if not room:
@ -500,26 +448,27 @@ def getTracker(tracker: UUID):
else: else:
multisave = {} multisave = {}
if "hints" in multisave: if "hints" in multisave:
for key, hintdata in multisave["hints"]:
for hint in hintdata:
hints[key[0]].add(Hint(*hint))
for (team, player), locations_checked in multisave.get("location_checks", {}): for (team, slot), slot_hints in multisave["hints"].items():
hints[team] |= set(slot_hints)
for (team, player), locations_checked in multisave.get("location_checks", {}).items():
player_locations = locations[player]
if precollected_items: if precollected_items:
precollected = precollected_items[player] precollected = precollected_items[player]
for item_id in precollected: for item_id in precollected:
attribute_item(inventory, team, player, item_id) attribute_item(inventory, team, player, item_id)
for location in locations_checked: for location in locations_checked:
if (location, player) not in locations or location not in player_location_to_area[player]: if location not in player_locations or location not in player_location_to_area[player]:
continue continue
item, recipient = locations[location, player] item, recipient = player_locations[location]
attribute_item(inventory, team, recipient, item) attribute_item(inventory, team, recipient, item)
checks_done[team][player][player_location_to_area[player][location]] += 1 checks_done[team][player][player_location_to_area[player][location]] += 1
checks_done[team][player]["Total"] += 1 checks_done[team][player]["Total"] += 1
for (team, player), game_state in multisave.get("client_game_state", []): for (team, player), game_state in multisave.get("client_game_state", {}).items():
if game_state: if game_state == 30:
inventory[team][player][106] = 1 # Triforce inventory[team][player][106] = 1 # Triforce
group_big_key_locations = set() group_big_key_locations = set()

View File

@ -362,6 +362,7 @@ def parse_arguments(argv, no_defaults=False):
create a binary patch file from which the randomized rom can be recreated using MultiClient.''') create a binary patch file from which the randomized rom can be recreated using MultiClient.''')
parser.add_argument('--disable_glitch_boots', default=defval(False), action='store_true', help='''\ parser.add_argument('--disable_glitch_boots', default=defval(False), action='store_true', help='''\
turns off starting with Pegasus Boots in glitched modes.''') turns off starting with Pegasus Boots in glitched modes.''')
parser.add_argument('--start_hints')
if multiargs.multi: if multiargs.multi:
for player in range(1, multiargs.multi + 1): for player in range(1, multiargs.multi + 1):
parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS) parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)
@ -405,7 +406,7 @@ def parse_arguments(argv, no_defaults=False):
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'heartbeep', "progression_balancing", "triforce_pieces_available", 'heartbeep', "progression_balancing", "triforce_pieces_available",
"triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots", "triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots",
"required_medallions", "required_medallions", "start_hints",
"plando_items", "plando_texts", "plando_connections", "er_seeds", "plando_items", "plando_texts", "plando_connections", "er_seeds",
'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', 'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',