state sync, many changes:
* separated css/js/rule files to pak file (glorified zip) to reduce full rebuilds * implemented build cache * some frontend UI spiffing up
This commit is contained in:
		
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,11 @@
 | 
				
			|||||||
.vscode/**
 | 
					.vscode/**
 | 
				
			||||||
scratch.ipynb
 | 
					scratch.ipynb
 | 
				
			||||||
.docker-login-credentials
 | 
					.docker-login-credentials
 | 
				
			||||||
 | 
					
 | 
				
			||||||
seagull
 | 
					seagull
 | 
				
			||||||
build/**
 | 
					seagull.pak
 | 
				
			||||||
 | 
					build/**
 | 
				
			||||||
 | 
					build_cache/**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**/__pycache__/**
 | 
				
			||||||
 | 
					**.pyc
 | 
				
			||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
app/**
 | 
					app/**
 | 
				
			||||||
ext/imsky/wordlists/**
 | 
					ext/imsky/wordlists/**
 | 
				
			||||||
static/**
 | 
					 | 
				
			||||||
seagull.spec
 | 
					seagull.spec
 | 
				
			||||||
@@ -8,8 +8,9 @@ import threading
 | 
				
			|||||||
import webview
 | 
					import webview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import flask
 | 
					import flask
 | 
				
			||||||
 | 
					import fs.tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from pylocal import core, actions, desktop, dev, items, tick
 | 
					from pylocal import core, actions, desktop, dev, gamedata, items, tick
 | 
				
			||||||
 | 
					
 | 
				
			||||||
core.desktop_mode = True
 | 
					core.desktop_mode = True
 | 
				
			||||||
sig_exit = threading.Event()
 | 
					sig_exit = threading.Event()
 | 
				
			||||||
@@ -24,6 +25,7 @@ def index():
 | 
				
			|||||||
        core.render_base_context()
 | 
					        core.render_base_context()
 | 
				
			||||||
        core.base_context["scripts"].insert(0, core.app.url_for("static", filename="js/desktop-structuredclone.js"))
 | 
					        core.base_context["scripts"].insert(0, core.app.url_for("static", filename="js/desktop-structuredclone.js"))
 | 
				
			||||||
        core.base_context["scripts"].insert(1, core.app.url_for("static", filename="js/seagull-desktop.js"))
 | 
					        core.base_context["scripts"].insert(1, core.app.url_for("static", filename="js/seagull-desktop.js"))
 | 
				
			||||||
 | 
					    gamedata.vfs.copy_out("templates/main_page.j2", dest=core.path_appdir.as_posix())
 | 
				
			||||||
    return flask.render_template("main_page.j2", **core.base_context)
 | 
					    return flask.render_template("main_page.j2", **core.base_context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
@@ -37,6 +39,9 @@ if __name__ == "__main__":
 | 
				
			|||||||
            storage_dir = pathlib.Path(os.environ["HOME"]) / ".local/share/seagull"
 | 
					            storage_dir = pathlib.Path(os.environ["HOME"]) / ".local/share/seagull"
 | 
				
			||||||
        desktop.path_storagedir = storage_dir
 | 
					        desktop.path_storagedir = storage_dir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gamedata.vfs.load_data_source("basepak")
 | 
				
			||||||
 | 
					        gamedata.vfs.load_data_source("seagull.pak", proto="zip")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if argo.debug:
 | 
					        if argo.debug:
 | 
				
			||||||
            desktop.api.debug_mode = True
 | 
					            desktop.api.debug_mode = True
 | 
				
			||||||
        storage_dir.mkdir(exist_ok=True, parents=True)
 | 
					        storage_dir.mkdir(exist_ok=True, parents=True)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ import threading
 | 
				
			|||||||
import flask
 | 
					import flask
 | 
				
			||||||
from gevent.pywsgi import WSGIServer
 | 
					from gevent.pywsgi import WSGIServer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from pylocal import core, actions, dev, items, tick
 | 
					from pylocal import core, actions, dev, gamedata, items, tick
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sig_exit = threading.Event()
 | 
					sig_exit = threading.Event()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,9 @@ log.critical(path_appdir)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
desktop_mode = False
 | 
					desktop_mode = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app = flask.Flask("seagull-game", root_path=path_appdir)
 | 
					from . import gamedata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app = flask.Flask("seagull-game", root_path=path_appdir, template_folder="templates", static_folder="static")
 | 
				
			||||||
orig_url_for = app.url_for
 | 
					orig_url_for = app.url_for
 | 
				
			||||||
 | 
					
 | 
				
			||||||
xml_namespaces = {
 | 
					xml_namespaces = {
 | 
				
			||||||
@@ -34,12 +36,15 @@ xml_namespaces = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def url_for_override(endpoint, *posargs, _anchor=None, _method=None, _scheme=None, _external=None, self=app, **values):
 | 
					def url_for_override(endpoint, *posargs, _anchor=None, _method=None, _scheme=None, _external=None, self=app, **values):
 | 
				
			||||||
    if endpoint == "static":
 | 
					    if endpoint == "static":
 | 
				
			||||||
        # bandaid for #1
 | 
					        if not gamedata.vfs.exists(f"static/{values["filename"]}"):
 | 
				
			||||||
        if not os.path.exists(path_appdir / "static" / values["filename"]):
 | 
					 | 
				
			||||||
            log.warning("requested {0} from local file, but it doesn't exist in this container. Redirecting to CDN...\n".format(values["filename"]))
 | 
					            log.warning("requested {0} from local file, but it doesn't exist in this container. Redirecting to CDN...\n".format(values["filename"]))
 | 
				
			||||||
            return "https://cdn.otl-hga.net/seagull/" + values["filename"]
 | 
					            return "https://cdn.otl-hga.net/seagull/" + values["filename"]
 | 
				
			||||||
    
 | 
					        else:
 | 
				
			||||||
    return orig_url_for(endpoint, *posargs, _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external, **values)
 | 
					            gamedata.vfs.copy_out(f"static/{values["filename"]}", dest=path_appdir.as_posix())
 | 
				
			||||||
 | 
					            return orig_url_for(endpoint, *posargs, _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external, **values)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print(endpoint)
 | 
				
			||||||
 | 
					        return orig_url_for(endpoint, *posargs, _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external, **values)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.url_for = url_for_override
 | 
					app.url_for = url_for_override
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,7 +53,8 @@ base_context_live = False
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@app.route("/dialog/<dialog>")
 | 
					@app.route("/dialog/<dialog>")
 | 
				
			||||||
def render_dialog(dialog):
 | 
					def render_dialog(dialog):
 | 
				
			||||||
    if os.path.exists(path_appdir / f"templates/{dialog}.j2"):
 | 
					    if gamedata.vfs.exists(f"templates/{dialog}.j2"):
 | 
				
			||||||
 | 
					        gamedata.vfs.copy_out(f"templates/{dialog}.j2", dest=path_appdir.as_posix())
 | 
				
			||||||
        return flask.render_template(f"{dialog}.j2")
 | 
					        return flask.render_template(f"{dialog}.j2")
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return "", 404
 | 
					        return "", 404
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,10 @@ from . import core
 | 
				
			|||||||
path_storagedir = pathlib.Path()
 | 
					path_storagedir = pathlib.Path()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class JS_API:
 | 
					class JS_API:
 | 
				
			||||||
    def __init__(self):
 | 
					    debug_mode = False
 | 
				
			||||||
        self.debug_mode = False
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, debug_mode=False):
 | 
				
			||||||
 | 
					        self.debug_mode = debug_mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def load_data(self, key):
 | 
					    def load_data(self, key):
 | 
				
			||||||
        if not (path_storagedir / key).exists():
 | 
					        if not (path_storagedir / key).exists():
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										79
									
								
								app/pylocal/gamedata.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								app/pylocal/gamedata.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import pathlib
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					import typing
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import fs
 | 
				
			||||||
 | 
					import fs.base
 | 
				
			||||||
 | 
					import fs.copy
 | 
				
			||||||
 | 
					import fs.osfs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GameVFSHandler(object):
 | 
				
			||||||
 | 
					    vfs = None
 | 
				
			||||||
 | 
					    log = logging.getLogger().getChild("vfs")
 | 
				
			||||||
 | 
					    proto_handlers = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _osfs_handle(self, path):
 | 
				
			||||||
 | 
					        if self.osfs_cwd.exists(path):
 | 
				
			||||||
 | 
					            return self.osfs_cwd.opendir(path)
 | 
				
			||||||
 | 
					        elif self.osfs_appdir.exists(path):
 | 
				
			||||||
 | 
					            return self.osfs_appdir.opendir(path)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise FileNotFoundError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self) -> None:
 | 
				
			||||||
 | 
					        self.vfs = fs.open_fs("mem://")
 | 
				
			||||||
 | 
					        self.proto_handlers["osfs"] = self._osfs_handle
 | 
				
			||||||
 | 
					        self.osfs_cwd = fs.osfs.OSFS(os.getcwd())
 | 
				
			||||||
 | 
					        self.osfs_appdir = fs.osfs.OSFS(core.path_appdir.as_posix())
 | 
				
			||||||
 | 
					        self.pth_temp = pathlib.Path(tempfile.mkdtemp())
 | 
				
			||||||
 | 
					        self.osfs_temp = fs.osfs.OSFS(self.pth_temp.as_posix())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __del__(self):
 | 
				
			||||||
 | 
					        if self.pth_temp and self.pth_temp.exists():
 | 
				
			||||||
 | 
					            shutil.rmtree(self.pth_temp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getattr__(self, name: str) -> typing.Any:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return getattr(self.vfs, name)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def load_data_source(self, source: pathlib.Path | fs.base.FS | typing.Text, proto="osfs"):
 | 
				
			||||||
 | 
					        print(f"data source: {source} ({proto})")
 | 
				
			||||||
 | 
					        assert self.vfs is not None
 | 
				
			||||||
 | 
					        if isinstance(source, pathlib.Path):
 | 
				
			||||||
 | 
					            assert isinstance(source, pathlib.Path) # for linter
 | 
				
			||||||
 | 
					            self.log.info(f"loading vfs source: {source.as_posix()}")
 | 
				
			||||||
 | 
					            if proto in self.proto_handlers:
 | 
				
			||||||
 | 
					                fs_source = self.proto_handlers[proto](source.as_posix())
 | 
				
			||||||
 | 
					                fs.copy.copy_fs(fs_source, self.vfs)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                fs_source = fs.open_fs(f"{proto}:/{source.as_posix()}")
 | 
				
			||||||
 | 
					                fs.copy.copy_fs(fs_source, self.vfs)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if proto in self.proto_handlers:
 | 
				
			||||||
 | 
					                fs_source = self.proto_handlers[proto](source)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                fs_source = fs.open_fs(f"{proto}://{source}")
 | 
				
			||||||
 | 
					            self.log.info(f"loading vfs source: {fs_source} (pyfilesystem2 handler)")
 | 
				
			||||||
 | 
					            fs.copy.copy_fs(fs_source, self.vfs)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def copy_out(self, filepath, dest=None):
 | 
				
			||||||
 | 
					        if not dest:
 | 
				
			||||||
 | 
					            self.osfs_temp.makedirs(pathlib.Path(filepath).parent.as_posix(), recreate=True)
 | 
				
			||||||
 | 
					            fs.copy.copy_file(self.vfs, filepath, self.osfs_temp, filepath)
 | 
				
			||||||
 | 
					            return self.pth_temp / filepath
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            pth_dest = pathlib.Path(dest)
 | 
				
			||||||
 | 
					            pth_file = pathlib.Path(filepath)
 | 
				
			||||||
 | 
					            osfs_dest = fs.osfs.OSFS(dest)
 | 
				
			||||||
 | 
					            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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					vfs = GameVFSHandler()
 | 
				
			||||||
@@ -4,17 +4,18 @@ import subprocess
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import lxml.etree as xmltree
 | 
					import lxml.etree as xmltree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import core
 | 
					from . import core, gamedata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
valid_resources = [
 | 
					valid_resources = [
 | 
				
			||||||
    "food", "shinies", "psi" # early game
 | 
					    "food", "shinies", "psi" # early game
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rant_env = os.environ.copy()
 | 
					rant_env = os.environ.copy()
 | 
				
			||||||
rant_env["RANT_MODULES_PATH"] = (core.path_appdir / "rant").as_posix()
 | 
					rant_env["RANT_MODULES_PATH"] = (core.path_appdir / "basepak/rant").as_posix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fd_item_schema = xmltree.parse(core.path_appdir / "rules/schemas/items.xsd")
 | 
					pth_item_schema = core.path_appdir / "basepak/rules/schemas/items.xsd"
 | 
				
			||||||
item_schema = xmltree.XMLSchema(fd_item_schema)
 | 
					doc_item_schema = xmltree.parse(pth_item_schema.as_posix())
 | 
				
			||||||
 | 
					item_schema = xmltree.XMLSchema(doc_item_schema)
 | 
				
			||||||
item_schema_parser = xmltree.XMLParser(schema=item_schema)
 | 
					item_schema_parser = xmltree.XMLParser(schema=item_schema)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_item_description(resource, target):
 | 
					def generate_item_description(resource, target):
 | 
				
			||||||
@@ -22,7 +23,8 @@ def generate_item_description(resource, target):
 | 
				
			|||||||
        rant_path = core.path_appdir / "opt/rant/bin/rant"
 | 
					        rant_path = core.path_appdir / "opt/rant/bin/rant"
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        rant_path = "rant" # rely on OS PATH
 | 
					        rant_path = "rant" # rely on OS PATH
 | 
				
			||||||
    proc_rant = subprocess.run([rant_path, (core.path_appdir / f"rant/{resource}/{target}.rant").as_posix()], env=rant_env, capture_output=True)
 | 
					    pth_rantfile = gamedata.vfs.copy_out(f"rant/{resource}/{target}.rant")
 | 
				
			||||||
 | 
					    proc_rant = subprocess.run([rant_path, (core.path_appdir / pth_rantfile).as_posix()], env=rant_env, capture_output=True)
 | 
				
			||||||
    if proc_rant.stderr:
 | 
					    if proc_rant.stderr:
 | 
				
			||||||
        core.log.warning("rant is throwing up:\n" + proc_rant.stderr.decode())
 | 
					        core.log.warning("rant is throwing up:\n" + proc_rant.stderr.decode())
 | 
				
			||||||
    return proc_rant.stdout.decode().strip()
 | 
					    return proc_rant.stdout.decode().strip()
 | 
				
			||||||
@@ -30,7 +32,7 @@ def generate_item_description(resource, target):
 | 
				
			|||||||
def generate_item_list(resource, target, min, max, storybeat=0):
 | 
					def generate_item_list(resource, target, min, max, storybeat=0):
 | 
				
			||||||
    count = random.randint(min, max)
 | 
					    count = random.randint(min, max)
 | 
				
			||||||
    result = []
 | 
					    result = []
 | 
				
			||||||
    rulefile = xmltree.parse(core.path_appdir / f"rules/items/{target}.xml", item_schema_parser)
 | 
					    rulefile = xmltree.parse(gamedata.vfs.open(f"/rules/items/{target}.xml"), item_schema_parser)
 | 
				
			||||||
    ruleset = rulefile.getroot()
 | 
					    ruleset = rulefile.getroot()
 | 
				
			||||||
    resource_rules = []
 | 
					    resource_rules = []
 | 
				
			||||||
    for res_rule in ruleset.iter(f"{{seagull:rules/items}}{resource.title()}"):
 | 
					    for res_rule in ruleset.iter(f"{{seagull:rules/items}}{resource.title()}"):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import random
 | 
					import random
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
@@ -6,12 +7,15 @@ import flask
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from . import core, items, jsonizer
 | 
					from . import core, items, jsonizer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rant_env = os.environ.copy()
 | 
				
			||||||
 | 
					rant_env["RANT_MODULES_PATH"] = (core.path_appdir / "basepak/rant").as_posix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_flavor_text():
 | 
					def generate_flavor_text():
 | 
				
			||||||
    if core.desktop_mode:
 | 
					    if core.desktop_mode:
 | 
				
			||||||
        rant_path = core.path_appdir / "opt/rant/bin/rant"
 | 
					        rant_path = core.path_appdir / "opt/rant/bin/rant"
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        rant_path = "rant" # rely on OS PATH
 | 
					        rant_path = "rant" # rely on OS PATH
 | 
				
			||||||
    proc_rant = subprocess.run([rant_path, (core.path_appdir / "rant/flavor.rant").as_posix()], capture_output=True)
 | 
					    proc_rant = subprocess.run([rant_path, (core.path_appdir / "basepak/rant/flavor.rant").as_posix()], env=rant_env, capture_output=True)
 | 
				
			||||||
    return proc_rant.stdout.decode()
 | 
					    return proc_rant.stdout.decode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TickEvent(object):
 | 
					class TickEvent(object):
 | 
				
			||||||
@@ -61,4 +65,12 @@ def tick():
 | 
				
			|||||||
        case _:
 | 
					        case _:
 | 
				
			||||||
            core.log.warning("undefined tick: {0}".format(result["event_type"]))
 | 
					            core.log.warning("undefined tick: {0}".format(result["event_type"]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return flask.Response(json.dumps(result, cls=jsonizer.JSONizer), status=200, content_type="application/json")
 | 
					    return flask.Response(json.dumps(result, cls=jsonizer.JSONizer), status=200, content_type="application/json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@core.app.route("/tick/colony", methods=["POST"])
 | 
				
			||||||
 | 
					def tick_colony() -> flask.Response:
 | 
				
			||||||
 | 
					    req = flask.request.json
 | 
				
			||||||
 | 
					    if not req:
 | 
				
			||||||
 | 
					        return flask.make_response("Bad request", 400)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
Flask==3.1.1
 | 
					Flask>=3.1.1
 | 
				
			||||||
gevent==25.5.1
 | 
					gevent>=25.5.1
 | 
				
			||||||
lxml>=6.0.0
 | 
					lxml>=6.0.0
 | 
				
			||||||
 | 
					fs>=2.4.16
 | 
				
			||||||
@@ -49,7 +49,14 @@
 | 
				
			|||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div id="main-content">
 | 
					<div id="main-content">
 | 
				
			||||||
    <div id="main-day-stats">It has been <span id="main-day-counter">a cosmically unknowable number of</span> days.</div>
 | 
					    <div id="main-header">
 | 
				
			||||||
 | 
					        <div id="main-day-stats">It has been <span id="main-day-counter">a cosmically unknowable number of</span> days.</div>
 | 
				
			||||||
 | 
					        <div id="main-button-bar">
 | 
				
			||||||
 | 
					            <button id="button-charsheet" class="main-bar">📊</button>
 | 
				
			||||||
 | 
					            <button id="button-settings" class="main-bar">⚙️</button>
 | 
				
			||||||
 | 
					            <button id="button-about" class="main-bar">ℹ️</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
    <div id="main-log"></div>
 | 
					    <div id="main-log"></div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
srcdir=$(pwd)
 | 
					srcdir=$(pwd)
 | 
				
			||||||
BUILD_DIR=${BUILD_DIR:-$srcdir/build}
 | 
					BUILD_DIR=${BUILD_DIR:-$srcdir/build}
 | 
				
			||||||
 | 
					CACHE_DIR=${CACHE_DIR:-$srcdir/build_cache}
 | 
				
			||||||
echo "$srcdir => $BUILD_DIR"
 | 
					echo "$srcdir => $BUILD_DIR"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
die () {
 | 
					die () {
 | 
				
			||||||
@@ -18,21 +19,33 @@ findcmd python
 | 
				
			|||||||
findcmd rsync
 | 
					findcmd rsync
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mkdir -p $BUILD_DIR && cd $BUILD_DIR
 | 
					mkdir -p $BUILD_DIR && cd $BUILD_DIR
 | 
				
			||||||
 | 
					mkdir -p $CACHE_DIR
 | 
				
			||||||
rsync -rv --include-from=$srcdir/.rsync-include $srcdir/ $BUILD_DIR/
 | 
					rsync -rv --include-from=$srcdir/.rsync-include $srcdir/ $BUILD_DIR/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# rant
 | 
					# rant
 | 
				
			||||||
mkdir -p opt/rant
 | 
					if [[ -f $CACHE_DIR/rant.tar.zst ]]; then
 | 
				
			||||||
cargo install rant --version 4.0.0-alpha.33 --root $BUILD_DIR/opt/rant --features cli
 | 
					    pv -bprt -N "unpacking: rant" $CACHE_DIR/rant.tar.zst | tar -x --zstd -f -
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    mkdir -p opt/rant
 | 
				
			||||||
 | 
					    cargo install rant --version 4.0.0-alpha.33 --root $BUILD_DIR/opt/rant --features cli
 | 
				
			||||||
 | 
					    tar -c -f - --zstd opt/rant | pv -bprt -N "caching: rant" > $CACHE_DIR/rant.tar.zst
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
python $srcdir/render-wordlists.py -i $srcdir/ext/imsky/wordlists -o rant $BUILD_DIR/app/rant/wordlist.rant
 | 
					python $srcdir/render-wordlists.py -i $srcdir/ext/imsky/wordlists -o rant $BUILD_DIR/app/rant/wordlist.rant
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# python venv
 | 
					# python venv
 | 
				
			||||||
python -m venv pyvenv
 | 
					if [[ -f $CACHE_DIR/pyvenv.tar.zst ]]; then
 | 
				
			||||||
source pyvenv/bin/activate
 | 
					    pv -bprt -N "unpacking: pyvenv" $CACHE_DIR/pyvenv.tar.zst | tar -x --zstd -f -
 | 
				
			||||||
pip install -r app/requirements.txt
 | 
					    source pyvenv/bin/activate
 | 
				
			||||||
pip install -r app/requirements-build-desktop.txt
 | 
					else
 | 
				
			||||||
pip install -r app/requirements-desktop.txt
 | 
					    python -m venv pyvenv
 | 
				
			||||||
[[ $(uname -s) == "Linux" ]] && pip install -r app/requirements-desktop-linux.txt
 | 
					    source pyvenv/bin/activate
 | 
				
			||||||
#[[ $(uname -s) == "Darwin" ]] && pip install -r app/requirements-desktop-macos.txt
 | 
					    pip install -r app/requirements.txt
 | 
				
			||||||
 | 
					    pip install -r app/requirements-build-desktop.txt
 | 
				
			||||||
 | 
					    pip install -r app/requirements-desktop.txt
 | 
				
			||||||
 | 
					    [[ $(uname -s) == "Linux" ]] && pip install -r app/requirements-desktop-linux.txt
 | 
				
			||||||
 | 
					    #[[ $(uname -s) == "Darwin" ]] && pip install -r app/requirements-desktop-macos.txt
 | 
				
			||||||
 | 
					    tar -c -f - --zstd pyvenv | pv -bprt -N "caching: pyvenv" > $CACHE_DIR/pyvenv.tar.zst
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pyinstaller seagull.spec
 | 
					pyinstaller seagull.spec
 | 
				
			||||||
deactivate
 | 
					deactivate
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								build-pak.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										10
									
								
								build-pak.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					#        ('app/templates', './templates'),
 | 
				
			||||||
 | 
					#        ('app/rules', './rules'),
 | 
				
			||||||
 | 
					#        ('static', './static'),
 | 
				
			||||||
 | 
					#        ('app/rant', './rant'),
 | 
				
			||||||
 | 
					#        ('opt', './opt')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					zip -7rv seagull.pak static
 | 
				
			||||||
 | 
					cd app
 | 
				
			||||||
 | 
					zip -7rv ../seagull.pak templates rules rant
 | 
				
			||||||
@@ -6,10 +6,8 @@ a = Analysis(
 | 
				
			|||||||
    pathex=[],
 | 
					    pathex=[],
 | 
				
			||||||
    binaries=[],
 | 
					    binaries=[],
 | 
				
			||||||
    datas=[
 | 
					    datas=[
 | 
				
			||||||
        ('app/templates', './templates'),
 | 
					        ('app/rant', 'basepak/rant'),
 | 
				
			||||||
        ('app/rules', './rules'),
 | 
					        ('app/rules/schemas', 'basepak/rules/schemas'),
 | 
				
			||||||
        ('static', './static'),
 | 
					 | 
				
			||||||
        ('app/rant', './rant'),
 | 
					 | 
				
			||||||
        ('opt', './opt')
 | 
					        ('opt', './opt')
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    hiddenimports=[],
 | 
					    hiddenimports=[],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,13 +35,30 @@ div#main-content {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    padding-left: 5px;
 | 
					    padding-left: 5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
div#main-day-stats {
 | 
					
 | 
				
			||||||
 | 
					div#main-header {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: row;
 | 
				
			||||||
    min-height: 100px;
 | 
					    min-height: 100px;
 | 
				
			||||||
    vertical-align: middle;
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    border-bottom: 0.125em solid rgb(192,192,192);
 | 
					    border-bottom: 0.125em solid rgb(192,192,192);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div#main-day-stats {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    margin-top: auto;
 | 
				
			||||||
 | 
					    margin-bottom: auto;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div#main-button-bar {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: row;
 | 
				
			||||||
 | 
					    min-height: 125px;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div#main-log {
 | 
					div#main-log {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    flex-direction: column-reverse;
 | 
					    flex-direction: column-reverse;
 | 
				
			||||||
@@ -109,4 +126,12 @@ div#modal {
 | 
				
			|||||||
    height: 90%;
 | 
					    height: 90%;
 | 
				
			||||||
    border: 1.25em double rgba(192, 192, 192, 255);
 | 
					    border: 1.25em double rgba(192, 192, 192, 255);
 | 
				
			||||||
    background-color: rgba(255, 255, 255, 255);
 | 
					    background-color: rgba(255, 255, 255, 255);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button.main-bar {
 | 
				
			||||||
 | 
					    width: 2.5em;
 | 
				
			||||||
 | 
					    height: 2.5em;
 | 
				
			||||||
 | 
					    background-color: rgba(0,0,0,0);
 | 
				
			||||||
 | 
					    border: 1px solid black;
 | 
				
			||||||
 | 
					    font-size: 2em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -24,7 +24,8 @@ const gamestate_default = {
 | 
				
			|||||||
    "enc_human": "pause",
 | 
					    "enc_human": "pause",
 | 
				
			||||||
    "enc_seagull": "pause",
 | 
					    "enc_seagull": "pause",
 | 
				
			||||||
    "agility": 0,
 | 
					    "agility": 0,
 | 
				
			||||||
    "instinct": 0
 | 
					    "instinct": 0,
 | 
				
			||||||
 | 
					    "leadership": 0
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var bool_log_alt = false
 | 
					var bool_log_alt = false
 | 
				
			||||||
@@ -109,7 +110,8 @@ async function open_modal_dialog(dialog) {
 | 
				
			|||||||
    if (!modal_dialog_open) {
 | 
					    if (!modal_dialog_open) {
 | 
				
			||||||
        tick_meter_running = false;
 | 
					        tick_meter_running = false;
 | 
				
			||||||
        modal_dialog_open = true;
 | 
					        modal_dialog_open = true;
 | 
				
			||||||
        modal_background.style = "z-index: 10 !important; visibility: visible !important;";
 | 
					        modal_background.style.zIndex = "10 !important";
 | 
				
			||||||
 | 
					        modal_background.style.visibility = "visible !important";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dialog_data = await fetch(`/dialog/${dialog}`)
 | 
					    dialog_data = await fetch(`/dialog/${dialog}`)
 | 
				
			||||||
@@ -162,6 +164,15 @@ function reward_xp(amount) {
 | 
				
			|||||||
        gamestate["xp"] -= old_xp_next;
 | 
					        gamestate["xp"] -= old_xp_next;
 | 
				
			||||||
        gamestate["level"] += 1;
 | 
					        gamestate["level"] += 1;
 | 
				
			||||||
        gamestate["xp_next"] = (old_xp_next * 1.5) + (gamestate["level"] * 5);
 | 
					        gamestate["xp_next"] = (old_xp_next * 1.5) + (gamestate["level"] * 5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (gamestate["level"] == 2) {
 | 
				
			||||||
 | 
					            gamestate["story_beat"] = 1;
 | 
				
			||||||
 | 
					            record_log("The humans have fired off some sort of large rocket from a nearby platform. You watch it as it pierces the sky above you and fades into the heavens.");
 | 
				
			||||||
 | 
					        } else if (gamestate["level"] == 3) {
 | 
				
			||||||
 | 
					            gamestate["story_beat"] = 2;
 | 
				
			||||||
 | 
					            gamestate["class"] = "Seagull";
 | 
				
			||||||
 | 
					            record_log("You have grown up from a young, eager seaglet to a full blown Seagull. As your colony participates in the ritual honoring your coming of age, you begin to detect a shift in the winds, though you're not certain exactly how.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -180,6 +191,10 @@ async function steal_resource(resource, target, amount, itemstr) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function recruit(amount) {
 | 
					async function recruit(amount) {
 | 
				
			||||||
 | 
					    if (gamestate["shinies"] < amount) {
 | 
				
			||||||
 | 
					        record_log("You do not have enough shinies to recruit this seagull.");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var stealdata = await fetch("/act/recruit", {method: "POST", body: JSON.stringify({gamestate: gamestate})})
 | 
					    var stealdata = await fetch("/act/recruit", {method: "POST", body: JSON.stringify({gamestate: gamestate})})
 | 
				
			||||||
    .then(res => { return res.json(); })
 | 
					    .then(res => { return res.json(); })
 | 
				
			||||||
    .catch(e => { throw e; });
 | 
					    .catch(e => { throw e; });
 | 
				
			||||||
@@ -375,9 +390,13 @@ target.addEventListener(start_event, function (ev) {
 | 
				
			|||||||
    page_elements["lbl_level"] = document.querySelector("#lbl-seagull-lvl");
 | 
					    page_elements["lbl_level"] = document.querySelector("#lbl-seagull-lvl");
 | 
				
			||||||
    page_elements["menu_enc_human"] = document.querySelector("#menu-enc-human");
 | 
					    page_elements["menu_enc_human"] = document.querySelector("#menu-enc-human");
 | 
				
			||||||
    page_elements["menu_enc_seagull"] = document.querySelector("#menu-enc-seagull");
 | 
					    page_elements["menu_enc_seagull"] = document.querySelector("#menu-enc-seagull");
 | 
				
			||||||
 | 
					    page_elements["btn_charsheet"] = document.querySelector("#button-charsheet");
 | 
				
			||||||
 | 
					    page_elements["btn_settings"] = document.querySelector("#button-settings");
 | 
				
			||||||
 | 
					    page_elements["btn_about"] = document.querySelector("#button-about");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    page_elements["menu_enc_human"].addEventListener("change", (ev) => {update_action("human", ev.target.value)})
 | 
					    page_elements["menu_enc_human"].addEventListener("change", (ev) => {update_action("human", ev.target.value)});
 | 
				
			||||||
    page_elements["menu_enc_seagull"].addEventListener("change", (ev) => {update_action("seagull", ev.target.value)})
 | 
					    page_elements["menu_enc_seagull"].addEventListener("change", (ev) => {update_action("seagull", ev.target.value)});
 | 
				
			||||||
 | 
					    page_elements["btn_charsheet"].addEventListener("click", (ev) => {open_modal_dialog("charsheet")});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prepare_gamestate();
 | 
					    prepare_gamestate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user