From cb934e82201d858d344764e8d997f9b5e5cb937c Mon Sep 17 00:00:00 2001 From: Dominic Zimmer Date: Fri, 17 Jul 2020 19:18:17 +0200 Subject: [PATCH] Create waschmarken as clone of webpnp --- .gitignore | 126 ++++++++++ index.html | 27 +++ main.py | 125 ++++++++++ model.py | 354 +++++++++++++++++++++++++++++ requirements.txt | 1 + static/empty.jpg | Bin 0 -> 2005 bytes static/renderer.js | 34 +++ static/unmined.leaflet.patched.js | 75 ++++++ static/views/lobby/script.js | 143 ++++++++++++ static/views/lobby/template.html | 21 ++ static/views/master/script.js | 135 +++++++++++ static/views/master/template.html | 14 ++ static/views/session/script.js | 80 +++++++ static/views/session/template.html | 13 ++ tehsession.json | 1 + ui.html | 30 +++ 16 files changed, 1179 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 main.py create mode 100644 model.py create mode 100644 requirements.txt create mode 100644 static/empty.jpg create mode 100644 static/renderer.js create mode 100644 static/unmined.leaflet.patched.js create mode 100644 static/views/lobby/script.js create mode 100644 static/views/lobby/template.html create mode 100644 static/views/master/script.js create mode 100644 static/views/master/template.html create mode 100644 static/views/session/script.js create mode 100644 static/views/session/template.html create mode 100644 tehsession.json create mode 100644 ui.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..805d7ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,126 @@ + +# Created by https://www.gitignore.io/api/python,virtualenv +# Edit at https://www.gitignore.io/?templates=python,virtualenv + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +### VirtualEnv ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +pyvenv.cfg +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pip-selfcheck.json + +# End of https://www.gitignore.io/api/python,virtualenv + +tehmodel.json +backups \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..d8ad6f2 --- /dev/null +++ b/index.html @@ -0,0 +1,27 @@ + + + + + leafblade Minecraft Server + + + + + +this is the base site + +
+ + + + + + + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..c7fc51e --- /dev/null +++ b/main.py @@ -0,0 +1,125 @@ +import aiohttp.web +import traceback +import urllib.parse +import os +import json + +from model import Model + +routes = aiohttp.web.RouteTableDef() + +routes.static('/static', 'static') + +admintoken = "ae33fd8cc4fdcb1ff50ba42ff48046a7" + +CLIENT_REGEX = r'/{client:[a-zA-Z0-9]{16}}' + + +def get_client(request: aiohttp.web.Request): + client = request.match_info.get("client", None) + model = request.app['model'] + + if not client: + print('[get_client] client is not set, wtf') + raise aiohttp.web.HTTPBadRequest() + + if not model.exists_client(client): + print(f'[get_client] model does not know {client=}') + raise aiohttp.web.HTTPBadRequest() + + return client + + +@routes.get(CLIENT_REGEX) +async def handler(request: aiohttp.web.Request): + # this handler prevents people missing trailing slashes + client = get_client(request) + raise aiohttp.web.HTTPFound(f"{client}/") + + +@routes.get(CLIENT_REGEX + '/') +async def handler(request: aiohttp.web.Request): + client = get_client(request) + print(f"{client=} accessed") + return aiohttp.web.FileResponse('ui.html') + + +@routes.post(CLIENT_REGEX + '/api/{method}') +async def handler(request: aiohttp.web.Request): + client = get_client(request) + method = request.match_info.get('method', None) + model = request.app['model'] + + data = await request.json() + + print(f'{method=} {client=} {data=}') + + try: + assert method in model.ApiMethod.dict + await model.ApiMethod.dict[method](model, client, **data) + return aiohttp.web.Response(status=200) + except Exception as e: + del e # unused? + traceback.print_exc() + return aiohttp.web.Response(status=400) + finally: + await model.send_state(client) + +@routes.get(CLIENT_REGEX + '/ws') +async def _(request: aiohttp.web.Request): + client = get_client(request) + model = request.app['model'] + + ws = aiohttp.web.WebSocketResponse(heartbeat=10) + await ws.prepare(request) + + print(f'[WS] client {client} connected, {ws=}') + await model.subscribe(client, ws) + + async for msg in ws: + print(f'[WS] incoming message from client {client}, {ws=}, {msg=}') + + print(f'[WS] client {client} disconnected, {ws=}') + model.unsubscribe(ws) + + return ws + + +@routes.get(f"/api/{admintoken}") +async def handler(request: aiohttp.web.Request): + model = request.app['model'] + + return aiohttp.web.json_response(model.model) + + +@routes.get('/') +async def handler(request): + del request # unused + + return aiohttp.web.FileResponse('index.html') + + +@routes.get('/register/{username:.+}') +async def handler(request): + model = request.app['model'] + username = request.match_info.get('username', 'Joe') + username = urllib.parse.unquote(username) + + client = model.create_client(username) + raise aiohttp.web.HTTPFound(f"/{client}/") + + +if __name__ == '__main__': + app = aiohttp.web.Application() + app.add_routes(routes) + + data = {} + filename = "tehsession.json" + if os.path.isfile(filename): + with open(filename) as f: + data = json.load(f) + + app['model'] = Model(model = data) + + aiohttp.web.run_app(app, port=42042) + app['model'].save() diff --git a/model.py b/model.py new file mode 100644 index 0000000..bb7fd85 --- /dev/null +++ b/model.py @@ -0,0 +1,354 @@ +import random +import base64 +import json +import os +import datetime + + +def generate_random_id(_s=set()): + while (new_id := base64.b32encode(bytearray(random.randint(0, 0xFF) for _ in range(10)))[:16].decode().lower()) in _s: + pass + _s.add(new_id) + return new_id + + +class Model(object): + class ApiMethod: + dict = dict() + + def __init__(self, fun): + self.dict[fun.__name__] = fun + + def __contains__(self, item): + return item in self.dict + + def __init__(self, model = {}): + if "sessions" not in model: + model["sessions"] = [] + if "clients" not in model: + model["clients"] = [] + if "items" not in model: + model["items"] = [] + self.sockets = {} # mapping: clientid -> sockets + self.sessions = { session: Session(model = model["sessions"][session]) for session in model["sessions"] } + self.clients = { client: Client(model = model["clients"][client]) for client in model["clients"] } + self.items = { item: Item(model = model["items"][item]) for item in model["items"] } + self.filename = "tehsession.json" + + def to_json(self): + model = { + "sessions": {session: self.sessions[session].to_json() for session in self.sessions }, + "clients": {client: self.clients[client].to_json() for client in self.clients }, + "items": {item: self.items[item].to_json() for item in self.items }, + } + return model + + @ApiMethod + async def test_api(self, clientid): + print(f'test_api {clientid=}') + + @ApiMethod + async def test_yeet(self, clientid): + raise Exception('yeet') + + @ApiMethod + async def change_username(self, clientid, username) -> str: + self.clients[clientid].name = username + + @ApiMethod + async def create_session(self, clientid, sessionname) -> str: + if not sessionname: + raise Exception(f"Sessionname cant be empty!") + session = Session(name = sessionname, owner = clientid) + self.sessions[session.id] = session + print("create_session was called") + + @ApiMethod + async def change_sessionname(self, clientid, sessionid, sessionname) -> str: + if not sessionname: + raise Exception("Cant be empty!") + session = self.sessions[sessionid] + if not (session.owner == clientid): + raise Exception("Ownly owner can change sessionname") + session.name = sessionname + + @ApiMethod + async def create_item(self, clientid, name, description, image): + client = self.clients[clientid] + if not client.session: + raise Exception("create_item requires the client to be host. But client is in no session") + session = self.sessions[client.session] + if (session.owner != client.id): + raise Exception("create_item requires the client to be host. But client is not the owner") + if not name: + raise Exception("create_item requires an item name") + item = Item(name = name, description = description, image = image) + self.items[item.id] = item + + + @ApiMethod + async def leave_session(self, clientid): + client = self.clients[clientid] + sessionid = client.session + if sessionid: + session = self.sessions[sessionid] + session.clients.remove(clientid) + self.clients[clientid].session = "" + + @ApiMethod + async def join_session(self, clientid, sessionid): + client = self.clients[clientid] + old_sessionid = client.session + # leave old session + if old_sessionid: + old_session = self.sessions[old_sessionid] + old_session.clients.remove(clientid) + session = self.sessions[sessionid] + client.session = sessionid + session.clients.append(client.id) + + + @ApiMethod + async def move_item(self, clientid, fromplayer, toplayer, itemid, toslot, fromslot): + client = self.clients[clientid] + fromclient = next((client for client in self.clients.values() if client.name == fromplayer), None) + toclient = next((client for client in self.clients.values() if client.name == toplayer), None) + if fromplayer == "master": + # create item + if not toclient: + raise Exception("to-client is illegal") + session = self.sessions[client.session] + if not session: + raise Exception("move item must be used in session") + if client.id != session.owner: + raise Exception("Only owner can move items") + toslot -= 1 + if toslot > len(session.inventories[toclient.id]): + raise Exception("Index out of toplayers range") + session.inventories[toclient.id].insert(toslot, itemid) + elif toplayer == "master": + if not fromclient: + raise Exception("from-client is illegal") + session = self.sessions[client.session] + if not session: + raise Exception("move item must be used in session") + if client.id != session.owner: + raise Exception("Only owner can move items") + if itemid not in session.inventories[fromclient.id]: + raise Exception("he does not have that item") + fromslot -= 1 + if fromslot > len(session.inventories[fromclient.id]): + raise Exception("Index out of fromplayer range") + session.inventories[fromclient.id].pop(fromslot) + else: + if not fromclient and not toclient: + raise Exception("from- or to-client are illegal") + session = self.sessions[client.session] + if not session: + raise Exception("move item must be used in session") + if client.id != session.owner: + raise Exception("Only owner can move items") + if itemid not in session.inventories[fromclient.id]: + raise Exception("he does not have that item") + toslot -= 1 + if toslot > len(session.inventories[toclient.id]): + raise Exception("Index out of toplayers range") + fromslot -= 1 + if fromslot > len(session.inventories[fromclient.id]): + raise Exception("Index out of fromplayer range") + session.inventories[fromclient.id].pop(fromslot) + session.inventories[toclient.id].insert(toslot, itemid) + + + + @ApiMethod + async def draw(self, clientid): + await self.send_state(clientid) + + async def send_lobby_view(self, clientid): + data = {} + client = self.clients[clientid] + + data["view"] = "lobby" + data["username"] = client.name + data["sessions"] = { + session.id: { + "id": session.id, + "name": session.name, + "owned": session.owner == client.id, + } for session in self.sessions.values() + } + for socket in self.sockets[clientid]: + await socket.send_json(data) + + async def send_master_view(self, clientid): + data = {} + client = self.clients[clientid] + session = self.sessions[client.session] + + data["view"] = "master" + data["username"] = client.name + data["session"] = session.to_json() + data["items"] = self.to_json()["items"] + data["inventories"] = {} + for _client in session.inventories: + _client = self.clients[_client] + inventory = session.get_items(_client.id) + inventory = list(map(lambda itemid: self.to_json()["items"][itemid], inventory)) + data["inventories"][_client.name] = inventory + + for socket in self.sockets[clientid]: + await socket.send_json(data) + + + async def send_session_view(self, clientid): + data = {} + client = self.clients[clientid] + session = self.sessions[client.session] + + data["view"] = "session" + data["username"] = client.name + data["session"] = session.name + data["inventories"] = {} + for _client in session.inventories: + _client = self.clients[_client] + inventory = session.get_items(_client.id) + inventory = list(map(lambda itemid: self.to_json()["items"][itemid], inventory)) + data["inventories"][_client.name] = inventory + + for socket in self.sockets[clientid]: + await socket.send_json(data) + + async def send_state(self, clientid): + # TODO: compute state, send to client + data = {} + client = self.clients[clientid] + session = self.sessions[client.session] if client.session else None + if session: + if session.owner == client.id: + await self.send_master_view(clientid) + else: + await self.send_session_view(clientid) + else: + await self.send_lobby_view(clientid) + + def save(self): + with open(self.filename, "w") as f: + json.dump(self.to_json(), f) + + def exists_client(self, clientid: str) -> bool: + return clientid in self.clients + + def create_client(self, name="Joe") -> str: + if not name: + raise Exception("Username cannot be empty!") + client = Client() + client.name = name + self.clients[client.id] = client + return client.id + + async def subscribe(self, clientid, socket): + if not clientid in self.sockets: + self.sockets[clientid] = [] + self.sockets[clientid].append(socket) + await self.send_state(clientid) + + def unsubscribe(self, socket): + for client in self.sockets: + if socket in self.sockets[client]: + self.sockets[client].remove(socket) + +class Session: + + def __init__(self, model = None, owner = None, name = None): + if model: + self.id = model["id"] + self.name = model["name"] + self.clients = model["clients"] + self.owner = model["owner"] + if "inventories" in model: + self.inventories = model["inventories"] + else: + self.inventories = {} + elif owner and name: + self.id = generate_random_id() + self.clients = [] + self.owner = owner + self.name = name + self.inventories = {} + else: + raise Exception("Illegal session constructor") + + def get_items(self, playerid): + if playerid in self.inventories: + return self.inventories[playerid] + else: + return [] + + def give_item(self, playerid, itemid): + if not playerid in self.inventories: + self.inventories[playerid] = [] + self.inventories[playerid].append(itemid) + + def to_json(self): + model = { + "id": self.id, + "name": self.name, + "clients": self.clients, + "owner": self.owner, + "inventories": self.inventories + } + return model + + + +class Client: + + def __init__(self, model = None): + if model: + self.id = model["id"] + self.name = model["name"] + self.session = model["session"] + else: + self.id = generate_random_id() + self.name = "Default Client Name" + self.session = "" + + def to_json(self): + model = { + "id": self.id, + "name": self.name, + "session": self.session, + } + return model + +class Item: + + def __init__(self, model = None, name = None, description = "", image = "", tags = {}): + if model: + self.id = model["id"] + self.name = model["name"] + self.description = model["description"] + self.image = model["image"] + if "tags" in model: + self.tags = model["tags"] + else: + self.tags = {} + elif name: + self.id = generate_random_id() + self.name = name + self.description = description + self.image = image + self.tags = tags + else: + raise Exception("Illegal Item Constructor") + + def to_json(self): + model = { + "id": self.id, + "name": self.name, + "description": self.description, + "image": self.image, + "tags": self.tags, + } + return model diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f179af2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +aiohttp==3.6.2 \ No newline at end of file diff --git a/static/empty.jpg b/static/empty.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e227ee896fc08344069fc43d5d11a2ac24ada08 GIT binary patch literal 2005 zcmeHGO=uHA6#iy6+ia6fyW4JRkbq=6i2;zW0VN^AFHZ zB-2SSvKWg2{3iwzPBGUg7OaK2#;lb}Ph|K<=}|h7IG@R$Os2<=6C03PWxG=M?FTB= zhLcT=SyR(zEdM9;Vkdw-P;z#?b~KY2r!Not{tA={csU|+d62x^p+d241I!}As9kd! zgf9?2ywIo#e1ouQ&*ln*9}J8@=H@~ToCx8z?E{7{>?RULakUmeTwENJ;O*LjR|C6A&(~JP(}kIG*`(} zN0!#dJmQ72!xY)1$TCIP6mbmms37OYL)3scWL<6RGY1d2E~bqE%6rbg?gjkUfYn9L zf861G^)4X41D;m9@$)U<$T#vYc74&?K>uan@lw~9zYaXP0z_77IVb1tBhy=&nE}4s z2SU@p;0qvdwyhhjiFMq24NQ_&m%6-n`+$sgE>9#4SqaS~Z1Y}7PgUm-m zQ5D7KQv!Z}kE#VUEf@#}Lwy5!sL$vN2K6D`7&OD-u-3mLGGs;u%&;kvFqvX}O0S~y znxSCG{A=USV5p4xTxJF&gUJTtO9;DnNa(O;D!EN@vo;!k15IX-bXlk7eK5Llb^Gfd zD{x6vN&me{Evk}AwPT8eU4mi!nISV4XPYvlF2i$|o_J>$PX>$crd>Ln region.x * 512) + && (tileBlockPoint.z < (region.z + 1) * 512) + && (tileBlockPoint.z + tileBlockSize > region.z * 512); + }; + + if (tileX >= minTileX + && tileY >= minTileY + && tileX <= maxTileX + && tileY <= maxTileY + && ((regions === undefined) || regions.some(intersectsWithTile))) { + return ('/static/unmined/tiles/zoom.{z}/{xd}/{yd}/tile.{x}.{y}.' + options.imageFormat) + .replace('{z}', zoom) + .replace('{yd}', '' + Math.floor(tileY / 10)) + .replace('{xd}', '' + Math.floor(tileX / 10)) + .replace('{y}', view.tile.row) + .replace('{x}', view.tile.column); + } else { + return "/static/empty.jpg"; + } + }, + { + detectRetina: false, + bounds: [[minMapX, minMapY], [minMapX + mapWidth, minMapY + mapHeight]] + }); + + let map = L.map(mapId, { + crs: L.CRS.Simple, + minZoom: options.minZoom + zoomOffset, + maxZoom: options.maxZoom + zoomOffset, + layers: [unminedLayer], + maxBoundsViscosity: 1.0 + }).setView([0, 0], options.defaultZoom + zoomOffset); + + let northWest = map.unproject([minMapX, minMapY], map.getMaxZoom()); + let southEast = map.unproject([minMapX + mapWidth, minMapY + mapHeight], map.getMaxZoom()); + map.setMaxBounds(new L.LatLngBounds(northWest, southEast)); + + return map; + + } + +} \ No newline at end of file diff --git a/static/views/lobby/script.js b/static/views/lobby/script.js new file mode 100644 index 0000000..b647bc5 --- /dev/null +++ b/static/views/lobby/script.js @@ -0,0 +1,143 @@ +var text = document.getElementById("msg").innerText +msg = JSON.parse(text) + +if (msg.hasOwnProperty('username')) { + document.getElementById('label-username').innerText = msg.username; + } + + // username management + document.getElementById("btn-edit-username").onclick = async function (e) { + document.getElementById("btn-confirm-username").style.display = "inline-block"; + document.getElementById("btn-discard-username").style.display = "inline-block"; + document.getElementById("btn-edit-username").style.display = "none"; + document.getElementById("label-username").style.display = "none"; + document.getElementById("input-set-username").style.display = "inline-block"; + document.getElementById("input-set-username").value = document.getElementById("label-username").innerText; + } + document.getElementById("btn-discard-username").onclick = async function (e) { + document.getElementById("btn-confirm-username").style.display = "none"; + document.getElementById("btn-discard-username").style.display = "none"; + document.getElementById("btn-edit-username").style.display = "inline-block"; + document.getElementById("label-username").style.display = "inline-block"; + document.getElementById("input-set-username").style.display = "none"; + } + document.getElementById("btn-confirm-username").onclick = async function (e) { + var text = document.getElementById("input-set-username").value; + if (Boolean(text)) { + let data = {"username": text}; + + await fetch('api/change_username', { + method: 'POST', + body: JSON.stringify(data), + }) + document.getElementById("btn-confirm-username").style.display = "none"; + document.getElementById("btn-discard-username").style.display = "none"; + document.getElementById("btn-edit-username").style.display = "inline-block"; + document.getElementById("label-username").style.display = "inline-block"; + document.getElementById("input-set-username").style.display = "none"; + } else { + console.log("cant be empty"); + document.getElementById("btn-confirm-username").style.display = "none"; + document.getElementById("btn-discard-username").style.display = "none"; + document.getElementById("btn-edit-username").style.display = "inline-block"; + document.getElementById("label-username").style.display = "inline-block"; + document.getElementById("input-set-username").style.display = "none"; + } + } + + + + document.getElementById("btn-create-session").onclick = async function (e) { + var text = document.getElementById("input-create-session").value; + if (Boolean(text)) { + let data = {"sessionname": text}; + + await fetch('api/create_session', { + method: 'POST', + body: JSON.stringify(data), + }) + } + }; + + if (msg.hasOwnProperty('sessions')) { + const sessions = document.getElementById('sessions'); + + while (sessions.children.length) sessions.lastChild.remove(); + + Object.keys(msg.sessions).forEach( session => { + session = msg.sessions[session]; + var sessionid = session["id"]; + var sessionname = session["name"]; + var owned = session["owned"]; + + const tehsession = document.createElement('div'); + const labelname = document.createElement('span'); + labelname.innerText = sessionname; + + tehsession.appendChild(labelname); + + if (owned) { + const inputname = document.createElement('input'); + inputname.style.display = "none"; + const btnedit = document.createElement('button'); + btnedit.innerText = '🖉' + const btnconfirm = document.createElement('button'); + btnconfirm.innerText = '✔' + btnconfirm.style.display = "none" + const btndiscard = document.createElement('button'); + btndiscard.innerText = '✘' + btndiscard.style.display = "none" + + btnedit.onclick = async function (e) { + inputname.style.display = "inline-block"; + inputname.value = sessionname; + btnedit.style.display = "none"; + btnconfirm.style.display = "inline-block"; + btndiscard.style.display = "inline-block"; + labelname.style.display = "none"; + } + btndiscard.onclick = async function (e) { + inputname.style.display = "none"; + btnedit.style.display = "inline-block"; + btnconfirm.style.display = "none"; + btndiscard.style.display = "none"; + labelname.style.display = "inline-block"; + } + btnconfirm.onclick = async function (e) { + text = inputname.value; + if (Boolean(text)) { + let data = {"sessionid": sessionid, "sessionname": text}; + await fetch('api/change_sessionname', { + method: 'POST', + body: JSON.stringify(data), + }); + inputname.style.display = "none"; + btnedit.style.display = "inline-block"; + btnconfirm.style.display = "none"; + btndiscard.style.display = "none"; + labelname.style.display = "inline-block"; + } else { + console.log("cant be empty"); + } + } + + tehsession.appendChild(inputname); + tehsession.appendChild(btnedit); + tehsession.appendChild(btnconfirm); + tehsession.appendChild(btndiscard); + } + + if ((! msg.hasOwnProperty('session')) || msg.session["id"] != sessionid) { + const btnjoin = document.createElement('button'); + btnjoin.innerText = "Join"; + btnjoin.onclick = async (e) => await fetch('api/join_session', { + method: 'POST', body: JSON.stringify({"sessionid": sessionid}) + }); + + tehsession.appendChild(btnjoin); + } + + sessions.appendChild(tehsession); + }) + } + diff --git a/static/views/lobby/template.html b/static/views/lobby/template.html new file mode 100644 index 0000000..c0ee059 --- /dev/null +++ b/static/views/lobby/template.html @@ -0,0 +1,21 @@ +
+
+ Hello, + _ + + + + +
+
+ + +Available sessions
+

+
+
+ +Create Session
+ + +
diff --git a/static/views/master/script.js b/static/views/master/script.js new file mode 100644 index 0000000..ea06eae --- /dev/null +++ b/static/views/master/script.js @@ -0,0 +1,135 @@ +var text = document.getElementById("msg").innerText +msg = JSON.parse(text) + +function onDrag (evt) { + var item = evt.item; // dragged HTMLElement + fromplayer = evt.from.getAttribute("inventory") + toplayer = evt.to.getAttribute("inventory") + itemid = item.getAttribute("itemid") + toslot = evt.newIndex + fromslot = evt.oldIndex + data = { + "fromplayer": fromplayer, + "toplayer": toplayer, + "itemid": itemid, + "fromslot": fromslot, + "toslot": toslot, + } + fetch('api/move_item', { + method: 'POST', + body: JSON.stringify(data), + }); +} +function draw_item(item) { + const itemdiv = document.createElement('itemdiv'); + var name = item["name"] + var description = item["description"] + var image = item["image"] || "../static/empty.jpg" + + namespan = document.createElement('b'); + namespan.innerText = name; + namespan.style.display = "block" + namespan.style.textAlign = "center" + + descriptionspan = document.createElement('span'); + descriptionspan.innerText = description; + descriptionspan.style.display = "block" + descriptionspan.style.width = "8rem"; + + imageimg = document.createElement('img'); + imageimg.src = image; + imageimg.style.width = "8rem"; + imageimg.style.display = "block" + + itemdiv.appendChild(namespan); + itemdiv.appendChild(imageimg); + itemdiv.appendChild(descriptionspan); + itemdiv.style.display = "inline-block" + itemdiv.style.background = "lightcoral" + itemdiv.setAttribute("itemid", item["id"]); + return itemdiv +} + +document.getElementById("btn-leave-session").onclick = async (e) => await fetch('api/leave_session', { + method: 'POST', body: JSON.stringify({}) + }); +var session = msg.session; +document.getElementById("session").innerText = session["name"]; +document.getElementById('label-username').innerText = msg.username; + +document.getElementById("btn-gen-item").onclick = async (e) => { + name = document.getElementById("input-gen-item-name").value; + description = document.getElementById("input-gen-item-description").value; + image = document.getElementById("input-gen-item-image").value; + if (Boolean(name)) { + await fetch('api/create_item', { method: 'POST', body: JSON.stringify( + {'name': name, 'description': description, 'image': image} + )}); + } +}; +var wrapper = document.getElementById("items"); +var items = msg.items; +var itemsdiv = document.createElement('div'); +var itemsheading = document.createElement('h3'); +itemsheading.innerText = "All Items"; +wrapper.style.background = "lightblue"; +wrapper.style.display = "table"; + +while (wrapper.children.length) wrapper.lastChild.remove(); + +Object.keys(items).forEach( item => { + + var item = items[item]; + itemdiv = draw_item(item); + itemsdiv.appendChild(itemdiv); + +}); +Sortable.create(itemsdiv, + { + "group": "items", + "sort": "false", + "draggable": "itemdiv", + "onEnd": onDrag, + +}); +itemsdiv.setAttribute("inventory", "master"); +wrapper.appendChild(itemsheading); +wrapper.appendChild(itemsdiv); + + +var inventories = document.getElementById("inventories"); +while (inventories.children.length) inventories.lastChild.remove(); + +Object.keys(msg.inventories).forEach( name => { + inventory = msg.inventories[name]; + inventorydiv = document.createElement('div'); + inventorydiv.style.display = "block"; + titlespan = document.createElement('h4'); + titlespan.innerText = name; + titlespan.style.textAlign = "center"; + inventorydiv.appendChild(titlespan); + + Object.keys(inventory).forEach( item => { + item = inventory[item]; + var thediv = draw_item(item); + //thediv.style.display = "block"; + inventorydiv.appendChild(thediv); + inventorydiv.style.display = "inline-block"; + }); + + inventorydiv.style.background = "lightgreen"; + + Sortable.create(inventorydiv, + { + "group": "items", + "sort": "false", + "draggable": "itemdiv", + "onEnd": onDrag, + }); + inventorydiv.setAttribute("inventory", name); + + inventories.appendChild(inventorydiv); + +}); + +inventories.style.display = "inline-block"; diff --git a/static/views/master/template.html b/static/views/master/template.html new file mode 100644 index 0000000..edebe27 --- /dev/null +++ b/static/views/master/template.html @@ -0,0 +1,14 @@ +Welcome to , lobbymaster +

+ + +
+ Name:
+ Description:
+ Image:
+ +
+
+
+
+
diff --git a/static/views/session/script.js b/static/views/session/script.js new file mode 100644 index 0000000..41b4042 --- /dev/null +++ b/static/views/session/script.js @@ -0,0 +1,80 @@ +function draw_item(item) { + const itemdiv = document.createElement('itemdiv'); + var name = item["name"] + var description = item["description"] + var image = item["image"] || "../static/empty.jpg" + + namespan = document.createElement('b'); + namespan.innerText = name; + namespan.style.display = "block" + namespan.style.textAlign = "center" + + descriptionspan = document.createElement('span'); + descriptionspan.innerText = description; + descriptionspan.style.display = "block" + descriptionspan.style.width = "8rem"; + + imageimg = document.createElement('img'); + imageimg.src = image; + imageimg.style.width = "8rem"; + imageimg.style.display = "block" + + itemdiv.appendChild(namespan); + itemdiv.appendChild(imageimg); + itemdiv.appendChild(descriptionspan); + itemdiv.style.display = "inline-block" + itemdiv.style.background = "lightcoral" + itemdiv.setAttribute("itemid", item["id"]); + return itemdiv +} + +var text = document.getElementById("msg").innerText +msg = JSON.parse(text) + +document.getElementById("btn-leave-session").onclick = async (e) => await fetch('api/leave_session', { + method: 'POST', body: JSON.stringify({}) + }); +document.getElementById("session").innerText = msg.session; +document.getElementById('label-username').innerText = msg.username; +//fill inventory and inventories +inventorydiv = document.getElementById("inventory"); +inventoriesdiv = document.getElementById("inventories"); + +while (inventorydiv.children.length) inventorydiv.lastChild.remove(); +while (inventoriesdiv.children.length) inventoriesdiv.lastChild.remove(); + +username = msg.username; +inventories = msg.inventories; + +if (username in inventories) { + inventory = inventories[username] + + Object.keys(inventory).forEach( item => { + + var item = inventory[item]; + itemdiv = draw_item(item); + inventorydiv.appendChild(itemdiv); + + }); +} + Object.keys(inventories).forEach( inventory => { + if (!(inventory in inventories)) + return; + if (inventory == username) + return; + var wrapper = document.createElement('div') + var usernamespan = document.createElement('span') + usernamespan.innerText = inventory; + var inventorydiv = document.createElement('div') + Object.keys(inventories[inventory]).forEach( item => { + var item = inventories[inventory][item]; + itemdiv = draw_item(item); + inventorydiv.appendChild(itemdiv); + }); + //inventorydiv.appendChild(itemdiv); + wrapper.appendChild(usernamespan) + wrapper.appendChild(inventorydiv) + inventoriesdiv.appendChild(wrapper); + + }); + diff --git a/static/views/session/template.html b/static/views/session/template.html new file mode 100644 index 0000000..354c928 --- /dev/null +++ b/static/views/session/template.html @@ -0,0 +1,13 @@ +
+ Hello +

+ Welkome to !
+ +

+
+

Your items

+
+
+

Other players' inventories

+
+
diff --git a/tehsession.json b/tehsession.json new file mode 100644 index 0000000..121ff74 --- /dev/null +++ b/tehsession.json @@ -0,0 +1 @@ +{"sessions": {"2b7jrklcn6eulwbw": {"id": "2b7jrklcn6eulwbw", "name": "Die coole session von Player 1", "clients": ["hmrcjnvo2ngs7265", "bkrqopf5j6q3tpta", "cho2o2ntflm4mq3u"], "owner": "bkrqopf5j6q3tpta", "inventories": {"cho2o2ntflm4mq3u": ["hr3gqfed4zelzdcz"], "bkrqopf5j6q3tpta": ["tuqjjll2cltlw6i6"]}}}, "clients": {"bkrqopf5j6q3tpta": {"id": "bkrqopf5j6q3tpta", "name": "Player 1", "session": "2b7jrklcn6eulwbw"}, "cho2o2ntflm4mq3u": {"id": "cho2o2ntflm4mq3u", "name": "Player 2", "session": "2b7jrklcn6eulwbw"}, "hmrcjnvo2ngs7265": {"id": "hmrcjnvo2ngs7265", "name": "Joe", "session": "2b7jrklcn6eulwbw"}}, "items": {"hr3gqfed4zelzdcz": {"id": "hr3gqfed4zelzdcz", "name": "Revive", "description": "Revives a KO pokemon and brings it to 50% health", "image": "https://1.bp.blogspot.com/-Hz8sIf0g7cY/VsyVIWiLsnI/AAAAAAAAnII/DjW20xLr0R4/s1600/max_revive_by_peetzaahhh2010-d8oki1o.png", "tags": {}}, "7htnswtslkla6rqh": {"id": "7htnswtslkla6rqh", "name": "Potion", "description": "Heals your Pokey-Man by 20 hp", "image": "https://cdn.bulbagarden.net/upload/thumb/4/45/PotionBaseSet94.jpg/200px-PotionBaseSet94.jpg", "tags": {}}, "tuqjjll2cltlw6i6": {"id": "tuqjjll2cltlw6i6", "name": "Revive\u2122", "description": "When mom tells you that there is Max-Revive at home", "image": "https://www.pokewiki.de/images/5/58/Vitalkraut_Traumwelt.png", "tags": {}}}} \ No newline at end of file diff --git a/ui.html b/ui.html new file mode 100644 index 0000000..116ace5 --- /dev/null +++ b/ui.html @@ -0,0 +1,30 @@ + + + + + leafblade Minecraft Server + + + + + + +
+
+ + + + + + +