big upgrades to upgrade system

This commit is contained in:
2025-10-05 11:11:54 -07:00
parent 0edb4b50d2
commit 537bdf1ad7
20 changed files with 3310 additions and 97 deletions

View File

@@ -1,3 +1,4 @@
import glob
import logging
import os
import pathlib
@@ -5,6 +6,7 @@ import sys
import flask
from flask_cors import CORS
import lxml.etree as xmltree
log = logging.getLogger()
pipe_stderr = logging.StreamHandler(sys.stderr)
@@ -30,6 +32,7 @@ CORS(app)
orig_url_for = app.url_for
xml_namespaces = {
"rule": "seagull:rules",
"items": "seagull:rules/items",
"upgrades": "seagull:rules/upgrades"
}
@@ -58,6 +61,25 @@ def url_for_override(endpoint, *posargs, _anchor=None, _method=None, _scheme=Non
app.url_for = url_for_override
schemas = []
pth_common_rule_schema = path_appdir / "basepak/rules/schemas/rules.xsd"
doc_common_rule_schema = xmltree.parse(pth_common_rule_schema.as_posix())
common_rule_schema = xmltree.XMLSchema(doc_common_rule_schema)
## \brief Validates all XML files in gamedata.
# \internal
#
# This is typically run on program startup.
def validate_xml_files():
for schema in schemas:
for file in gamedata.vfs.vfs.glob(schema[1]):
pth_xml = file.path[1:]
fd_xml = gamedata.vfs.open(pth_xml)
doc = xmltree.parse(fd_xml)
if not schema[0].validate(doc):
log.error(f"Bogus XML document: {pth_xml}")
## \internal
# \brief Base Flask rendering context. Generated with render_base_context().
base_context = {}
@@ -86,7 +108,7 @@ def render_base_context():
domain_components = flask.request.host.split(".")
base_domain = ".".join(domain_components[-2:])
gamedata.vfs.copy_out("static/js/mermaid.esm.min.mjs", dest=path_appdir.as_posix())
gamedata.vfs.copy_dir("static", dest=path_appdir.as_posix())
# all this wind up for...
if base_domain == "otl-hga.net": # production, use assets from S3

View File

@@ -85,6 +85,19 @@ class GameVFSHandler(object):
osfs_dest.makedirs(pth_file.parent.as_posix(), recreate=True)
fs.copy.copy_file(self.vfs, filepath, dest, filepath)
return (pth_dest / filepath).as_posix()
def copy_dir(self, dirpath, dest=None):
if not dest:
self.osfs_temp.makedirs(pathlib.Path(dirpath).parent.as_posix(), recreate=True)
fs.copy.copy_dir(self.vfs, dirpath, self.osfs_temp, dirpath)
return self.pth_temp / dirpath
else:
pth_dest = pathlib.Path(dest)
pth_file = pathlib.Path(dirpath)
osfs_dest = fs.osfs.OSFS(dest)
osfs_dest.makedirs(pth_file.parent.as_posix(), recreate=True)
fs.copy.copy_dir(self.vfs, dirpath, dest, dirpath)
return (pth_dest / dirpath).as_posix()
## \brief Primary VFS handler.
# \internal

View File

@@ -1,3 +1,5 @@
import colorsys
import json
import io
import logging
import textwrap
@@ -7,14 +9,16 @@ import flask
import lxml.etree as xmltree
import mermaid
from . import core, gamedata
from . import core, gamedata, jsonizer, util
pth_upgrade_schema = core.path_appdir / "basepak/rules/schemas/upgrades.xsd"
doc_upgrade_schema = xmltree.parse(pth_upgrade_schema.as_posix())
upgrade_schema = xmltree.XMLSchema(doc_upgrade_schema)
upgrade_schema_parser = xmltree.XMLParser(schema=upgrade_schema)
UpgradeData = namedtuple("UpgradeData", ["id", "name", "desc", "requires"])
core.schemas.append((upgrade_schema, "rules/upgrades/**.xml"))
UpgradeData = namedtuple("UpgradeData", ["id", "name", "desc", "requires", "food", "shinies"])
## \brief Renders an upgrade tree.
# \param tree The upgrade tree to retrieve.
@@ -26,19 +30,40 @@ def get_upgrade_tree(tree):
rulefile = xmltree.parse(gamedata.vfs.open(f"/rules/upgrades/{tree}.xml"), upgrade_schema_parser)
ruleset = rulefile.getroot()
unlocked = []
if len(flask.request.query_string) > 0:
for upgrade in flask.request.args.get("unlocked", "").split(","):
if len(upgrade) > 0:
unlocked.append(upgrade)
hnd_treedata = ruleset.xpath("./upgrades:TreeData", namespaces=core.xml_namespaces)[0]
hnd_name = hnd_treedata.xpath("./upgrades:Name", namespaces=core.xml_namespaces)[0]
hnd_primary_color = hnd_treedata.xpath("./upgrades:PrimaryColor", namespaces=core.xml_namespaces)[0]
buf_mmd.write(textwrap.dedent(f"""
----
---
title: {hnd_name.text}
theme: base
themeVariables:
primaryColor: {hnd_primary_color.text}
----
securityLevel: loose
---
flowchart LR
"""))
""").lstrip())
rgb_primary_color = util.hex_to_rgb(hnd_primary_color.text)
hsv_primary_color = colorsys.rgb_to_hsv(*rgb_primary_color)
print(f"{rgb_primary_color} ----> {hsv_primary_color}")
hsv_fill_open_color = (hsv_primary_color[0], hsv_primary_color[1] / 2, hsv_primary_color[2])
rgb_fill_open_color = colorsys.hsv_to_rgb(*hsv_fill_open_color)
hsv_stroke_unlock_color = (hsv_primary_color[0], 1.0, 55)
rgb_stroke_unlock_color = colorsys.hsv_to_rgb(*hsv_stroke_unlock_color)
hsv_fill_lock_color = (hsv_primary_color[0], 0.1, min(hsv_primary_color[2] * 1.5, 255))
rgb_fill_lock_color = colorsys.hsv_to_rgb(*hsv_fill_lock_color)
print(f"{hsv_fill_lock_color}, {rgb_fill_lock_color}")
hsv_stroke_lock_color = (hsv_primary_color[0], 0.1, 55)
rgb_stroke_lock_color = colorsys.hsv_to_rgb(*hsv_stroke_lock_color)
buf_mmd.write(f" classDef open fill:{util.rgb_to_hex(*rgb_fill_open_color)},stroke:{util.rgb_to_hex(*rgb_primary_color)}\n")
buf_mmd.write(f" classDef unlocked fill:{util.rgb_to_hex(*rgb_primary_color)},stroke:{util.rgb_to_hex(*rgb_stroke_unlock_color)}\n")
buf_mmd.write(f" classDef locked fill:{util.rgb_to_hex(*rgb_fill_lock_color)},stroke:{util.rgb_to_hex(*rgb_stroke_lock_color)}\n")
tree_upgrades = []
for hnd_upgrade in ruleset.iter("{seagull:rules/upgrades}Upgrade"):
@@ -48,14 +73,27 @@ def get_upgrade_tree(tree):
try:
hnd_requires = hnd_upgrade.xpath("./upgrades:Requirements", namespaces=core.xml_namespaces)[0]
require_list = [elem.text for elem in hnd_requires.iter("{seagull:rules/upgrades}Require")]
try:
hnd_food = hnd_requires.xpath("./upgrades:Food", namespaces=core.xml_namespaces)[0]
except IndexError:
hnd_food = None
try:
hnd_shinies = hnd_requires.xpath("./upgrades:Shinies", namespaces=core.xml_namespaces)[0]
except IndexError:
hnd_shinies = None
except IndexError:
require_list = []
hnd_food = None
hnd_shinies = None
upgrade = UpgradeData(
id=hnd_id.text,
name=hnd_upgrade_name.text,
desc=hnd_desc.text,
requires=require_list
requires=require_list,
food=int(hnd_food.text) if hnd_food is not None else 0,
shinies=int(hnd_shinies.text) if hnd_shinies is not None else 0
)
tree_upgrades.append(upgrade)
@@ -64,7 +102,9 @@ def get_upgrade_tree(tree):
dependency_lines = []
for upgrade in tree_upgrades:
deptier = 0
buf_mmd.write(f" {upgrade.id}@{{label: \"{upgrade.name}\"}}\n")
upgrade_food_costs = f"🍏{upgrade.food}" if upgrade.food > 0 else ""
upgrade_shinies_costs = f"🪙{upgrade.shinies}" if upgrade.shinies > 0 else ""
buf_mmd.write(f" {upgrade.id}@{{label: \"{upgrade.name}\n<span style='font-size:0.6em'>{upgrade_food_costs}{upgrade_shinies_costs}</span>\"}}\n")
collected_tiers = []
for require in upgrade.requires:
if require in tiers:
@@ -75,14 +115,72 @@ def get_upgrade_tree(tree):
if len(collected_tiers) > 0:
deptier = max(collected_tiers)
tiers[upgrade.id] = deptier + 1
tier = deptier + 1
tiers[upgrade.id] = tier
all_requirements_met = True
if tier > 1:
while all_requirements_met:
for require in upgrade.requires:
all_requirements_met = require in unlocked
if not all_requirements_met:
buf_mmd.write(f" class {upgrade.id} locked\n")
elif upgrade in unlocked:
buf_mmd.write(f" class {upgrade.id} unlocked\n")
else:
buf_mmd.write(f" class {upgrade.id} open\n")
buf_mmd.write(f" click {upgrade.id} call purchase_upgrade('{tree}','{upgrade.id}')\n")
for line in dependency_lines:
buf_mmd.write(line)
buf_mmd.seek(0)
print(buf_mmd.read())
return get_upgrade_tree_mmd(tree) # TEMP
mmd_str = buf_mmd.read()
mmd_upgradetree = mermaid.Mermaid(mmd_str, height=400)
#print(mmd_str)
return mmd_upgradetree.svg_response.content
@core.app.route("/upgrades/<tree>/<upgrade>")
def get_upgrade_data(tree, upgrade):
rulefile = xmltree.parse(gamedata.vfs.open(f"/rules/upgrades/{tree}.xml"), upgrade_schema_parser)
ruleset = rulefile.getroot()
target_upgrade = None
hnd_id = None
for hnd_upgrade in ruleset.iter("{seagull:rules/upgrades}Upgrade"):
hnd_id = hnd_upgrade.xpath("./upgrades:Id", namespaces=core.xml_namespaces)[0]
if hnd_id.text == upgrade:
target_upgrade = hnd_upgrade
break
hnd_name = hnd_upgrade.xpath("./upgrades:Name", namespaces=core.xml_namespaces)[0]
hnd_desc = hnd_upgrade.xpath("./upgrades:Desc", namespaces=core.xml_namespaces)[0]
try:
hnd_requires = hnd_upgrade.xpath("./upgrades:Requirements", namespaces=core.xml_namespaces)[0]
require_list = [elem.text for elem in hnd_requires.iter("{seagull:rules/upgrades}Require")]
try:
hnd_food = hnd_requires.xpath("./upgrades:Food", namespaces=core.xml_namespaces)[0]
except IndexError:
hnd_food = None
try:
hnd_shinies = hnd_requires.xpath("./upgrades:Shinies", namespaces=core.xml_namespaces)[0]
except IndexError:
hnd_shinies = None
except IndexError:
hnd_requires = None
hnd_shinies = None
hnd_food = None
require_list = []
return flask.make_response(json.dumps({
"id": hnd_id.text,
"name": hnd_name.text,
"desc": hnd_desc.text,
"food": int(hnd_food.text) if hnd_food else 0,
"shinies": int(hnd_shinies.text) if hnd_shinies else 0,
"requires": require_list
}, cls=jsonizer.JSONizer), 200)
def get_upgrade_tree_mmd(tree):
if not gamedata.vfs.exists(f"upgrades/{tree}.mmd"):

6
app/pylocal/util.py Normal file
View File

@@ -0,0 +1,6 @@
def hex_to_rgb(hex: str):
hex = hex.lstrip("#")
return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4))
def rgb_to_hex(r: int, g: int, b: int):
return f"#{int(r):0>2x}{int(g):0>2x}{int(b):0>2x}"