WebHost: add hint cost and forfeit mode to webgen page
This commit is contained in:
parent
f7bd637073
commit
a8b105267c
28
Main.py
28
Main.py
|
@ -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,7 +226,8 @@ 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.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'],
|
||||||
region.player)
|
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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -25,6 +25,6 @@
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#generate-game-form{
|
#file-input{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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: </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: </td>
|
<td>Rooms: </td>
|
||||||
<td>
|
<td>
|
||||||
|
|
Loading…
Reference in New Issue