desktop support
This commit is contained in:
47
app/desktop.py
Executable file
47
app/desktop.py
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import threading
|
||||
import webview
|
||||
|
||||
import flask
|
||||
|
||||
from pylocal import core, desktop, dev, tick
|
||||
|
||||
core.desktop_mode = True
|
||||
sig_exit = threading.Event()
|
||||
|
||||
argp = argparse.ArgumentParser("seagull")
|
||||
argp.add_argument("-d", "--debug", action="store_true")
|
||||
argo = argp.parse_args()
|
||||
|
||||
@core.app.route("/")
|
||||
def index():
|
||||
if not core.base_context_live:
|
||||
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(1, core.app.url_for("static", filename="js/seagull-desktop.js"))
|
||||
return flask.render_template("main_page.j2", **core.base_context)
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
if sys.platform.startswith("win"):
|
||||
storage_dir = pathlib.Path(os.environ["APPDATA"]) / "seagull"
|
||||
elif sys.platform.startswith("darwin"): # macos
|
||||
storage_dir = pathlib.Path(os.environ["HOME"]) / "Library/Application Support/seagull"
|
||||
else:
|
||||
storage_dir = pathlib.Path(os.environ["HOME"]) / ".local/share/seagull"
|
||||
desktop.path_storagedir = pathlib.Path(storage_dir)
|
||||
|
||||
if argo.debug:
|
||||
desktop.api.debug_mode = True
|
||||
storage_dir.mkdir(exist_ok=True, parents=True)
|
||||
webview.create_window("Seagull Game", core.app, js_api=desktop.api)
|
||||
webview.start(private_mode=False, storage_path=storage_dir.as_posix(), debug=True if argo.debug else False)
|
||||
except KeyboardInterrupt:
|
||||
core.log.info("Goodnight, moon ...")
|
||||
sig_exit.set()
|
||||
sys.exit(0)
|
10
app/index.wsgi
Normal file → Executable file
10
app/index.wsgi
Normal file → Executable file
@@ -3,17 +3,22 @@
|
||||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import flask
|
||||
from gevent.pywsgi import WSGIServer
|
||||
|
||||
from pylocal import core, tick
|
||||
from pylocal import core, dev, tick
|
||||
|
||||
sig_exit = threading.Event()
|
||||
|
||||
@core.app.route("/")
|
||||
def index():
|
||||
if not core.base_context_live:
|
||||
core.render_base_context()
|
||||
core.base_context["scripts"].append(core.app.url_for("static", filename="js/seagull-web.js"))
|
||||
return flask.render_template("main_page.j2", **core.base_context)
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -22,5 +27,6 @@ if __name__ == "__main__":
|
||||
http_server = WSGIServer(('', 80), core.app)
|
||||
http_server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("Goodnight, moon ...")
|
||||
core.log.info("Goodnight, moon ...")
|
||||
sig_exit.set()
|
||||
sys.exit(0)
|
@@ -1,10 +1,24 @@
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
import flask
|
||||
import redis
|
||||
|
||||
app = flask.Flask("seagull-game", root_path="/app")
|
||||
log = logging.getLogger()
|
||||
pipe_stderr = logging.StreamHandler(sys.stderr)
|
||||
pipe_stderr.setLevel(logging.DEBUG)
|
||||
log.addHandler(pipe_stderr)
|
||||
|
||||
if getattr(sys, "frozen", False):
|
||||
path_appdir = pathlib.Path(sys._MEIPASS)
|
||||
else:
|
||||
path_appdir = pathlib.Path.cwd()
|
||||
log.critical(path_appdir)
|
||||
|
||||
desktop_mode = False
|
||||
|
||||
app = flask.Flask("seagull-game", root_path=path_appdir)
|
||||
orig_url_for = app.url_for
|
||||
|
||||
#REDIS_HOST="stub-implementation.example.net"
|
||||
@@ -16,9 +30,8 @@ orig_url_for = app.url_for
|
||||
def url_for_override(endpoint, *posargs, _anchor=None, _method=None, _scheme=None, _external=None, self=app, **values):
|
||||
if endpoint == "static":
|
||||
# bandaid for #1
|
||||
if not os.path.exists("/app/static/" + values["filename"]):
|
||||
sys.stderr.write("WARN:: requested {0} from local file, but it doesn't exist in this container. Redirecting to CDN...\n".format(values["filename"]))
|
||||
sys.stderr.flush()
|
||||
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"]))
|
||||
return "https://cdn.otl-hga.net/seagull/" + values["filename"]
|
||||
|
||||
return orig_url_for(endpoint, *posargs, _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external, **values)
|
||||
@@ -51,5 +64,5 @@ def render_base_context():
|
||||
base_context_live = True
|
||||
|
||||
@app.route("/core/ping")
|
||||
def aws_healthcheck_ping():
|
||||
def healthcheck_ping():
|
||||
return flask.Response("OK", content_type="text/plain")
|
32
app/pylocal/desktop.py
Normal file
32
app/pylocal/desktop.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import pathlib
|
||||
|
||||
from . import core
|
||||
|
||||
path_storagedir = pathlib.Path()
|
||||
|
||||
class JS_API:
|
||||
debug_mode = False
|
||||
|
||||
def load_data(self, key):
|
||||
if not (path_storagedir / key).exists():
|
||||
return None
|
||||
|
||||
with open(path_storagedir / key) as fd_datafile:
|
||||
try:
|
||||
return fd_datafile.read()
|
||||
except Exception as exc:
|
||||
core.log.error(f"problem loading {key} (from {path_storagedir}): {exc}")
|
||||
return None
|
||||
|
||||
def save_data(self, key, data):
|
||||
with open(path_storagedir / key, "w") as fd_datafile:
|
||||
try:
|
||||
fd_datafile.write(data)
|
||||
except Exception as exc:
|
||||
core.log.error(f"problem saving {key} (to {path_storagedir}): {exc}")
|
||||
|
||||
def delete_data(self, key):
|
||||
if (path_storagedir / key).exists():
|
||||
(path_storagedir / key).unlink()
|
||||
|
||||
api = JS_API()
|
7
app/pylocal/dev.py
Normal file
7
app/pylocal/dev.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import flask
|
||||
|
||||
from . import core
|
||||
|
||||
@core.app.route("/dev/get-toolbox")
|
||||
def dev_toolbox():
|
||||
return flask.render_template("dev_toolbox.j2", ipaddr=flask.request.remote_addr, desktop=core.desktop_mode)
|
@@ -7,7 +7,11 @@ import flask
|
||||
from . import core
|
||||
|
||||
def generate_flavor_text():
|
||||
proc_rant = subprocess.run(["rant", "/app/rant/flavor.rant"], capture_output=True)
|
||||
if core.desktop_mode:
|
||||
rant_path = core.path_appdir / "opt/rant/bin/rant"
|
||||
else:
|
||||
rant_path = "rant" # rely on OS PATH
|
||||
proc_rant = subprocess.run([rant_path, (core.path_appdir / "rant/flavor.rant").as_posix()], capture_output=True)
|
||||
return proc_rant.stdout.decode()
|
||||
|
||||
class TickEvent(object):
|
||||
@@ -36,11 +40,13 @@ def tick():
|
||||
result["event_type"] = random.choices(ticktypes, weights=tickweights)[0]
|
||||
|
||||
match result["event_type"]:
|
||||
case 0:
|
||||
pass
|
||||
case 1: # FLAVOR
|
||||
result["log"] = generate_flavor_text()
|
||||
case 10: # ENCHUMAN
|
||||
result["items"] = [] # TODO: implement items
|
||||
result["items"] = {} # TODO: implement items
|
||||
case _:
|
||||
print("undefined tick: {0}".format(result["event_type"]))
|
||||
core.log.warning("undefined tick: {0}".format(result["event_type"]))
|
||||
|
||||
return flask.Response(json.dumps(result), status=200, content_type="application/json")
|
@@ -18,9 +18,7 @@
|
||||
You have a polite conversation about birdly affairs. |
|
||||
It scoffs and flies away.
|
||||
}
|
||||
|
|
||||
You encounter a human and attempt to steal their `{
|
||||
[pick: <wordlist/nouns/food>] |
|
||||
[pick: <wordlist/nouns/fast_food>]
|
||||
}.
|
||||
} |
|
||||
{
|
||||
A nearby {`[pick: <wordlist/nouns/birds>]|colony of `[pick: <wordlist/nouns/birds>]s} seems to be harassing a human.
|
||||
}
|
1
app/requirements-build-desktop.txt
Normal file
1
app/requirements-build-desktop.txt
Normal file
@@ -0,0 +1 @@
|
||||
pyinstaller>=6.14.2
|
3
app/requirements-desktop-linux.txt
Normal file
3
app/requirements-desktop-linux.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
pyqt6>=6.9.1
|
||||
pyqtwebengine>=5.15.7
|
||||
pywebview[qt]>=5.4
|
1
app/requirements-desktop.txt
Normal file
1
app/requirements-desktop.txt
Normal file
@@ -0,0 +1 @@
|
||||
pywebview>=5.4
|
@@ -1,4 +1,4 @@
|
||||
Flask==2.2.2
|
||||
gevent==22.10.2
|
||||
Flask==3.1.1
|
||||
gevent==25.5.1
|
||||
hiredis>=1.0.0
|
||||
redis==4.5.1
|
||||
redis==6.2.0
|
2
app/templates/dev_toolbox.j2
Normal file
2
app/templates/dev_toolbox.j2
Normal file
@@ -0,0 +1,2 @@
|
||||
{% if not desktop %}IP: {{ipaddr}}<br />{% endif %}
|
||||
<button id="dev-reset" onClick="reset_game()">Reset Game</button>
|
@@ -12,8 +12,10 @@
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div style="background:yellow;border:2px red;">
|
||||
<h1>This doesn't work without JavaScript.</h1><br />
|
||||
<h2>You're probably using a browser extension or privacy tool that disables it.</h2>
|
||||
</div>
|
||||
</noscript>
|
||||
<div id="root">
|
||||
|
||||
@@ -22,12 +24,28 @@
|
||||
<div id="side-seagull-name"><span id="lbl-seagull-name">Nameless</span> <a href="javascript:change_seagull_name()">✏️</a></div>
|
||||
<div id="side-seagull-name-editor"><input type="text" id="edt-seagull-name"> <a href="javascript:confirm_seagull_name()">✅</a><a href="javascript:cancel_seagull_name()">❌</a></div>
|
||||
<div id="side-seagull-stats">
|
||||
<p id="side-seagull-lvl">Lv 1 LoadError</p>
|
||||
<p id="side-seagull-lvl">Lv <span id="lbl-seagull-lvl">1</span> <span id="lbl-seagull-class">LoadError</span></p>
|
||||
<p id="side-seagull-xp">XP: <span id="lbl-seagull-xp-current">0</span>/<span id="lbl-seagull-xp-next">0</span></p>
|
||||
<p id="side-seagull-misc">
|
||||
Colony: <span id="lbl-seagull-colony">1337</span><br />
|
||||
Shinies: <span id="lbl-seagull-shinies">420</span><br />
|
||||
Food: <span id="lbl-seagull-food">69</span>
|
||||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
<div id="side-action-bar">
|
||||
<p>Human encounters: <select id="menu-enc-human">
|
||||
<option value="pause">Stop and ask</option>
|
||||
<option value="steal-food">Steal food</option>
|
||||
<option value="steal-shinies">Steal shiny things</option>
|
||||
</select></p>
|
||||
<p>Seagull encounters: <select id="menu-enc-seagull">
|
||||
<option value="pause">Stop and ask</option>
|
||||
<option value="recruit">Attempt recruiting</option>
|
||||
<option value="steal-food">Steal food</option>
|
||||
<option value="steal-shinies">Steal shiny things</option>
|
||||
</select></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-content">
|
||||
|
Reference in New Issue
Block a user