WebHost: add hint cost and forfeit mode to webgen page

This commit is contained in:
Fabian Dill 2021-10-11 00:46:18 +02:00
parent f7bd637073
commit a8b105267c
6 changed files with 55 additions and 35 deletions

30
Main.py
View File

@ -7,7 +7,7 @@ import concurrent.futures
import pickle import pickle
import tempfile import tempfile
import zipfile import zipfile
from typing import Dict, Tuple from typing import Dict, Tuple, Optional
from BaseClasses import MultiWorld, CollectionState, Region, RegionType from BaseClasses import MultiWorld, CollectionState, Region, RegionType
from worlds.alttp.Items import item_name_groups from worlds.alttp.Items import item_name_groups
@ -19,7 +19,16 @@ from worlds.generic.Rules import locality_rules, exclusion_rules
from worlds import AutoWorld from worlds import AutoWorld
def main(args, seed=None): ordered_areas = (
'Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace',
'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total"
)
def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = None):
if not baked_server_options:
baked_server_options = get_options()["server_options"]
if args.outputpath: if args.outputpath:
os.makedirs(args.outputpath, exist_ok=True) os.makedirs(args.outputpath, exist_ok=True)
output_path.cached_path = args.outputpath output_path.cached_path = args.outputpath
@ -159,15 +168,15 @@ def main(args, seed=None):
output = tempfile.TemporaryDirectory() output = tempfile.TemporaryDirectory()
with output as temp_dir: with output as temp_dir:
with concurrent.futures.ThreadPoolExecutor(world.players+2) as pool: with concurrent.futures.ThreadPoolExecutor(world.players + 2) as pool:
check_accessibility_task = pool.submit(world.fulfills_accessibility) check_accessibility_task = pool.submit(world.fulfills_accessibility)
output_file_futures = [pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir)] output_file_futures = [pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir)]
for player in world.player_ids: for player in world.player_ids:
# skip starting a thread for methods that say "pass". # skip starting a thread for methods that say "pass".
if AutoWorld.World.generate_output.__code__ is not world.worlds[player].generate_output.__code__: if AutoWorld.World.generate_output.__code__ is not world.worlds[player].generate_output.__code__:
output_file_futures.append(pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir)) output_file_futures.append(
pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir))
def get_entrance_to_region(region: Region): def get_entrance_to_region(region: Region):
for entrance in region.entrances: for entrance in region.entrances:
@ -188,9 +197,7 @@ def main(args, seed=None):
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name: if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
er_hint_data[region.player][location.address] = main_entrance.name er_hint_data[region.player][location.address] = main_entrance.name
ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace',
'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total")
checks_in_area = {player: {area: list() for area in ordered_areas} checks_in_area = {player: {area: list() for area in ordered_areas}
for player in range(1, world.players + 1)} for player in range(1, world.players + 1)}
@ -219,8 +226,9 @@ def main(args, seed=None):
for index, take_any in enumerate(takeanyregions): for index, take_any in enumerate(takeanyregions):
for region in [world.get_region(take_any, player) for player in for region in [world.get_region(take_any, player) for player in
world.get_game_players("A Link to the Past") if world.retro[player]]: world.get_game_players("A Link to the Past") if world.retro[player]]:
item = world.create_item(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'], item = world.create_item(
region.player) region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'],
region.player)
player = region.player player = region.player
location_id = SHOP_ID_START + total_shop_slots + index location_id = SHOP_ID_START + total_shop_slots + index
@ -286,7 +294,7 @@ def main(args, seed=None):
world.worlds[player].remote_start_inventory}, world.worlds[player].remote_start_inventory},
"locations": locations_data, "locations": locations_data,
"checks_in_area": checks_in_area, "checks_in_area": checks_in_area,
"server_options": get_options()["server_options"], "server_options": baked_server_options,
"er_hint_data": er_hint_data, "er_hint_data": er_hint_data,
"precollected_items": precollected_items, "precollected_items": precollected_items,
"precollected_hints": precollected_hints, "precollected_hints": precollected_hints,

View File

@ -89,7 +89,7 @@ def launch_generator(pool: multiprocessing.pool.Pool, generation: Generation):
options = restricted_loads(generation.options) options = restricted_loads(generation.options)
logging.info(f"Generating {generation.id} for {len(options)} players") 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"], {"meta": meta,
"sid": generation.id, "sid": generation.id,
"owner": generation.owner}, "owner": generation.owner},
handle_generation_success, handle_generation_failure) handle_generation_success, handle_generation_failure)

View File

@ -4,6 +4,7 @@ import random
import json import json
import zipfile import zipfile
from collections import Counter from collections import Counter
from typing import Dict, Optional as TypeOptional
from flask import request, flash, redirect, url_for, session, render_template from flask import request, flash, redirect, url_for, session, render_template
@ -33,6 +34,14 @@ def generate(race=False):
flash(options) flash(options)
else: else:
results, gen_options = roll_options(options) results, gen_options = roll_options(options)
# get form data -> server settings
hint_cost = int(request.form.get("hint_cost", 10))
forfeit_mode = request.form.get("forfeit_mode", "goal")
meta = {"race": race, "hint_cost": hint_cost, "forfeit_mode": forfeit_mode}
if race:
meta["item_cheat"] = False
meta["remaining"] = False
if any(type(result) == str for result in results.values()): if any(type(result) == str for result in results.values()):
return render_template("checkResult.html", results=results) return render_template("checkResult.html", results=results)
elif len(gen_options) > app.config["MAX_ROLL"]: elif len(gen_options) > app.config["MAX_ROLL"]:
@ -42,7 +51,8 @@ def generate(race=False):
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=json.dumps({"race": race}), state=STATE_QUEUED, meta=json.dumps(meta),
state=STATE_QUEUED,
owner=session["_id"]) owner=session["_id"])
commit() commit()
@ -50,18 +60,24 @@ def generate(race=False):
else: else:
try: try:
seed_id = gen_game({name: vars(options) for name, options in gen_options.items()}, seed_id = gen_game({name: vars(options) for name, options in gen_options.items()},
race=race, owner=session["_id"].int) meta=meta, owner=session["_id"].int)
except BaseException as e: except BaseException as e:
from .autolauncher import handle_generation_failure from .autolauncher import handle_generation_failure
handle_generation_failure(e) handle_generation_failure(e)
return render_template("seedError.html", seed_error=(e.__class__.__name__ + ": "+ str(e))) return render_template("seedError.html", seed_error=(e.__class__.__name__ + ": " + str(e)))
return redirect(url_for("viewSeed", seed=seed_id)) return redirect(url_for("viewSeed", seed=seed_id))
return render_template("generate.html", race=race) return render_template("generate.html", race=race)
def gen_game(gen_options, race=False, owner=None, sid=None): def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=None, sid=None):
if not meta:
meta: Dict[str, object] = {}
meta.setdefault("hint_cost", 10)
race = meta.get("race", False)
del (meta["race"])
try: try:
target = tempfile.TemporaryDirectory() target = tempfile.TemporaryDirectory()
playercount = len(gen_options) playercount = len(gen_options)
@ -95,7 +111,7 @@ def gen_game(gen_options, race=False, owner=None, sid=None):
erargs.name[player] = os.path.splitext(os.path.split(playerfile)[-1])[0] erargs.name[player] = os.path.splitext(os.path.split(playerfile)[-1])[0]
erargs.name[player] = handle_name(erargs.name[player], player, name_counter) erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
ERmain(erargs, seed) ERmain(erargs, seed, baked_server_options=meta)
return upload_to_db(target.name, sid, owner, race) return upload_to_db(target.name, sid, owner, race)
except BaseException as e: except BaseException as e:
@ -105,7 +121,7 @@ def gen_game(gen_options, race=False, owner=None, sid=None):
if gen is not None: if gen is not None:
gen.state = STATE_ERROR gen.state = STATE_ERROR
meta = json.loads(gen.meta) meta = json.loads(gen.meta)
meta["error"] = (e.__class__.__name__ + ": "+ str(e)) meta["error"] = (e.__class__.__name__ + ": " + str(e))
gen.meta = json.dumps(meta) gen.meta = json.dumps(meta)
commit() commit()

View File

@ -25,6 +25,6 @@
margin-bottom: 1rem; margin-bottom: 1rem;
} }
#generate-game-form{ #file-input{
display: none; display: none;
} }

View File

@ -37,6 +37,18 @@
<div id="generate-game-form-wrapper"> <div id="generate-game-form-wrapper">
<form id="generate-game-form" method="post" enctype="multipart/form-data"> <form id="generate-game-form" method="post" enctype="multipart/form-data">
<input id="file-input" type="file" name="file"> <input id="file-input" type="file" name="file">
<label data-tooltip="After gathering this many checks, players can !hint <itemname> to get the location of that item." for="hint_cost">Hint Cost:</label><select name="hint_cost" id="hint_cost">
{% for n in range(0, 110, 5) %}
<option {% if n == 10 %}selected="selected" {% endif %} value="{{ n }}">{% if n > 100 %}Off{% else %}{{ n }}%{% endif %}</option>
{% endfor %}
</select>
<label for="forfeit_mode">Forfeit Permission:</label><select name="forfeit_mode" id="forfeit_mode">
<option value="auto">Automatic on goal completion</option>
<option value="goal">Allow !forfeit after goal completion</option>
<option value="auto-enabled">Automatic on goal completion and manual !forfeit</option>
<option value="enabled">Manual !forfeit</option>
<option value="disabled">Disabled</option>
</select>
</form> </form>
<button id="generate-game-button">Upload</button> <button id="generate-game-button">Upload</button>
</div> </div>

View File

@ -12,10 +12,6 @@
<div id="view-seed-wrapper"> <div id="view-seed-wrapper">
<div id="view-seed" class="grass-island"> <div id="view-seed" class="grass-island">
<h1>Seed Info</h1> <h1>Seed Info</h1>
{% if not seed.multidata and not seed.spoiler %}
<p>Single Player Race Rom: No spoiler or multidata exists, parts of the rom are encrypted and rooms
cannot be created.</p>
{% endif %}
<table> <table>
<tbody> <tbody>
<tr> <tr>
@ -33,18 +29,6 @@
</tr> </tr>
{% endif %} {% endif %}
{% if seed.multidata %} {% if seed.multidata %}
<tr>
<td>Players:&nbsp;</td>
<td>
<ul>
{% for patch in seed.slots|sort(attribute='player_id') %}
<li>
<a href="{{ url_for("download_raw_patch", seed_id=seed.id, player_id=patch.player_id) }}">{{ patch.player_name }}</a>
</li>
{% endfor %}
</ul>
</td>
</tr>
<tr> <tr>
<td>Rooms:&nbsp;</td> <td>Rooms:&nbsp;</td>
<td> <td>