Initial commit
Build and Push Docker Image / build-and-push (push) Successful in 18s

This commit is contained in:
ciomek committed 2026-08-16 11:41:07 +02:00
commit 279b2a7e90
49 files changed
+2664

No files matched your search

View File
Whitespace-only changes.
View File
Whitespace-only changes.
@@ -0,0 +1,35 @@
import json
from sqlmodel import Session, select
from loon.web.db import engine
from loon.web.users.models import User
from loon.web.utils.password import hash_password
async def change_password_handler(client, userdata, msg):
data = json.loads(msg.payload.decode())
if msg.topic != "loon/register/change_password":
return
uuid = data["uuid"]
password = data["password"]
with Session(engine) as session:
statement = select(User).where(User.uuid == uuid)
user = session.exec(statement).first()
if user is None:
client.publish(f"loon/register/{uuid}/response",
json.dumps({"success": False, "error": "The user is not registered!"}))
return
with Session(engine) as session:
user = session.exec(
select(User).where(User.uuid == uuid)
).first()
user.password = hash_password(password)
session.commit()
client.publish(f"loon/register/{uuid}/response", json.dumps({"success": True, "error": None}))
+105
View File
@@ -0,0 +1,105 @@
import json
import random
from loon.web.users.state import user_threads
PLAYER_EVENT_SUFFIXES = ("died", "joined", "left")
DEATH_BY_DAMAGE = {
"fall": (
"{username} fell from a high place",
"{username} forgot to tie their shoes",
),
"fire": (
"{username} forgot fire burns",
"{username} was engulfed in flames",
),
"drown": (
"{username} drowned",
),
"blast": (
"{username} blew up",
),
"combat": (
"{username} was slain",
"{username} was pummeled to death",
),
"generic": (
"{username} died",
"{username} withered away",
),
}
DEATH_BY_KILLER = {
"fall": (
"{username} was doomed to fall by {killer}",
),
"fire": (
"{username} walked into fire whilst fighting {killer}",
),
"drown": (
"{username} drowned whilst trying to escape {killer}",
),
"blast": (
"{username} was blown up by {killer}",
),
"combat": (
"{username} was slain by {killer}",
"{username} was killed by {killer}",
"{username} was slain by {killer} using magic",
"{username} got finished off by {killer}",
),
"generic": (
"{username} was slain by {killer}",
"{username} was killed by {killer}",
),
}
DEATH_BY_KILLER_DEFAULT = DEATH_BY_KILLER["combat"]
def get_random(templates, **kwargs):
return random.choice(templates).format(**kwargs)
async def marquee_handler(client, userdata, msg):
prefix = "loon/player/"
if not msg.topic.startswith(prefix):
return
rest = msg.topic[len(prefix):]
_, _, subtopic = rest.partition("/")
if subtopic not in PLAYER_EVENT_SUFFIXES:
return
try:
data = json.loads(msg.payload.decode())
except json.JSONDecodeError:
return
username = data["player"]["username"]
if subtopic == "died":
killer = data.get("killer")
damage_type = data.get("damageType")
if killer:
templates = DEATH_BY_KILLER.get(damage_type, DEATH_BY_KILLER_DEFAULT)
message = get_random(templates, username=username, killer=killer)
elif damage_type in DEATH_BY_DAMAGE:
message = get_random(DEATH_BY_DAMAGE[damage_type], username=username)
elif damage_type:
message = f"{username} died of {damage_type}"
else:
message = f"{username} died"
elif subtopic == "joined":
message = f"{username} joined the game"
else:
message = f"{username} left the game"
envelope = json.dumps({"topic": "server/marquee", "payload": {"message": message}})
for queue in list(user_threads.values()):
await queue.put(envelope)
+19
View File
@@ -0,0 +1,19 @@
import json
from loon.web.users.state import user_threads
async def player_handler(client, userdata, msg):
data = json.loads(msg.payload.decode())
prefix = "loon/player/"
if not msg.topic.startswith(prefix):
return
rest = msg.topic[len(prefix):]
player_uuid, _, subtopic = rest.partition("/")
queue = user_threads.get(player_uuid)
if queue is not None:
await queue.put(json.dumps({"topic": subtopic, "payload": data}))
+34
View File
@@ -0,0 +1,34 @@
import json
from sqlmodel import Session, select
from loon.web.db import engine
from loon.web.users.models import User
from loon.web.utils.password import hash_password
async def register_request_handler(client, userdata, msg):
data = json.loads(msg.payload.decode())
if msg.topic != "loon/register/request":
return
uuid = data["uuid"]
password = data["password"]
internal_username = data["internalUsername"]
with Session(engine) as session:
statement = select(User).where(User.uuid == uuid)
user = session.exec(statement).first()
if user is not None:
client.publish(f"loon/register/{uuid}/response",
json.dumps({"success": False, "error": "The user is already registered!"}))
return
with Session(engine) as session:
user = User(uuid=uuid, password=hash_password(password), internal_username=internal_username, admin=False)
session.add(user)
session.commit()
client.publish(f"loon/register/{uuid}/response", json.dumps({"success": True, "error": None}))
+18
View File
@@ -0,0 +1,18 @@
import json
from loon.web.users.state import user_threads
async def server_handler(client, userdata, msg):
if msg.topic != "loon/server/info":
return
try:
data = json.loads(msg.payload.decode())
except json.JSONDecodeError:
return
envelope = json.dumps({"topic": "server/info", "payload": data})
for queue in list(user_threads.values()):
await queue.put(envelope)
+34
View File
@@ -0,0 +1,34 @@
import json
import re
import redis
from config import settings
from loon.redis.chunk import set_chunk_cache
from loon.web.users.state import user_threads
async def world_handler(client, userdata, msg):
chunk_topic = re.compile(r"^loon/world/chunk/(-?\d+)/(-?\d+)$")
match = chunk_topic.match(msg.topic)
if not match:
return
try:
data = json.loads(msg.payload.decode())
except json.JSONDecodeError:
return
x, z = match.group(1), match.group(2)
envelope = json.dumps({"topic": f"world/chunk/{x}/{z}", "payload": data})
if settings.redis.enabled:
try:
set_chunk_cache(int(x), int(z), data)
except redis.RedisError:
pass
for queue in list(user_threads.values()):
await queue.put(envelope)
+74
View File
@@ -0,0 +1,74 @@
import asyncio
import json
import threading
import paho.mqtt.client as mqtt
from config import settings
from loon import logger
from loon.mqtt.handlers.change_password_request import change_password_handler
from loon.mqtt.handlers.marquee import marquee_handler
from loon.mqtt.handlers.player import player_handler
from loon.mqtt.handlers.register_request import register_request_handler
from loon.mqtt.handlers.server import server_handler
from loon.mqtt.handlers.world import world_handler
class MQTTManager:
BASE_HANDLERS = [player_handler, register_request_handler, world_handler, server_handler, marquee_handler, change_password_handler]
def __init__(self, inject_handlers = None):
if inject_handlers is None:
inject_handlers = []
self.client = mqtt.Client()
self.thread = None
self.loop = asyncio.get_running_loop()
self.handlers = self.BASE_HANDLERS + inject_handlers
@staticmethod
def setup_and_start():
mqtt_client = MQTTManager()
mqtt_client.connect()
mqtt_client.start()
return mqtt_client
def connect(self):
self.client.on_connect = self.on_connect
self.client.on_disconnect = self.on_disconnect
self.client.on_message = self.on_topic
self.client.username_pw_set(username=settings.mqtt.username, password=settings.mqtt.password)
self.client.reconnect_delay_set(min_delay=1, max_delay=60)
self.client.connect_async(settings.mqtt.host, settings.mqtt.port)
def start(self):
self.thread = threading.Thread(target=self.client.loop_forever, daemon=True)
self.thread.start()
return self.thread
@staticmethod
def on_connect(client, userdata, flags, reason_code):
logger.info(f"MQTT connected (rc={reason_code})")
client.subscribe("loon/#")
@staticmethod
def on_disconnect(client, userdata, reason_code):
logger.warning(f"MQTT disconnected (rc={reason_code}). Reconnecting...")
def on_topic(self, client, userdata, msg):
async def handle_message():
try:
data = json.loads(msg.payload.decode())
except json.decoder.JSONDecodeError:
return
logger.info(f"[Received] {data}.")
for handler in self.handlers:
await handler(client, userdata, msg)
asyncio.run_coroutine_threadsafe(handle_message(), self.loop)
def publish(self, topic: str, payload: mqtt.PayloadType = None, qos: int = 0, retain: bool = False) -> mqtt.MQTTMessageInfo:
return self.client.publish(topic, payload, qos, retain)