From 201844c57faffb8d8de7e931f3650b2783415eda Mon Sep 17 00:00:00 2001 From: nonchris Date: Sat, 22 Aug 2026 00:02:58 +0200 Subject: [PATCH 1/4] further centralize entity edit --- st_minecraft/en/main.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/st_minecraft/en/main.py b/st_minecraft/en/main.py index c749661..c444ba8 100644 --- a/st_minecraft/en/main.py +++ b/st_minecraft/en/main.py @@ -484,6 +484,12 @@ def _set_player_property(type: str, player: Player, value: float): _send_command(command) +def _edit_entity_command(entity: Entity, *args) -> None: + """internal edit entity function""" + command = _build_command("editEntity", entity.id, *args) + _send_command(command, validate=True) + + def set_entity_name(entity: Entity, name: str) -> Entity: """ Set the name of an entity @@ -493,8 +499,7 @@ def set_entity_name(entity: Entity, name: str) -> Entity: Returns: An updated version of the entity (state after the change) """ - command = _build_command("editEntity", entity.id, f"name:{name}") - _send_command(command) + _edit_entity_command(entity, f"name:{name}") return get_entity(entity) @@ -511,8 +516,7 @@ def set_entity_position(entity: Entity, x: float, y: float, z: float, dimension: Returns: An updated version of the entity (state after the change) """ - command = _build_command("editEntity", entity.id, f"position:{x};{y};{z};{dimension.value}") - _send_command(command) + _edit_entity_command(entity, f"position:{x};{y};{z};{dimension.value}") return get_entity(entity) @@ -527,8 +531,7 @@ def set_entity_ai(entity: Entity, status: bool) -> Entity: Returns: An updated version of the entity (state after the change) """ - command = _build_command("editEntity", entity.id, f"ai:{status}") - _send_command(command) + _edit_entity_command(entity, f"ai:{status}") return get_entity(entity) @@ -540,8 +543,7 @@ def set_entity_health(entity: Entity, health: float) -> Entity: entity: The entity to be edited, not EntitySammlung! health: How many health points the entity should have (0=dead). """ - command = _build_command("editEntity", entity.id, f"health:{health}") - _send_command(command) + _edit_entity_command(entity, f"health:{health}") return get_entity(entity) From 8a92fa3b7f81e2e1bb6916ae50710bc6deac15d9 Mon Sep 17 00:00:00 2001 From: nonchris Date: Sat, 22 Aug 2026 00:24:36 +0200 Subject: [PATCH 2/4] typo --- st_minecraft/en/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/st_minecraft/en/main.py b/st_minecraft/en/main.py index c444ba8..7fe3b9d 100644 --- a/st_minecraft/en/main.py +++ b/st_minecraft/en/main.py @@ -51,7 +51,7 @@ def set_block( def get_block(x: float, y: float, z: float, dimension: Dimension = Dimension.World) -> Material: """ Query what type of block is at the coordinate - You get a block object back that contains the type under .typ + You get a block object back that contains the type under .type Note: An "empty" block is treated as an air block. Args: From 1b7d4e686e70be48aa870496a24362e143930552 Mon Sep 17 00:00:00 2001 From: nonchris Date: Sat, 22 Aug 2026 00:25:26 +0200 Subject: [PATCH 3/4] add validation logic for commands that have no explicit response --- st_minecraft/core/core.py | 29 ++++++++++++++++++++++++++++- st_minecraft/en/boss_bar.py | 8 +++++--- st_minecraft/en/main.py | 12 ++++++------ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/st_minecraft/core/core.py b/st_minecraft/core/core.py index 91d265e..ee3935f 100644 --- a/st_minecraft/core/core.py +++ b/st_minecraft/core/core.py @@ -16,6 +16,8 @@ connection: Optional[socket.socket] = None ARG_SEPARATOR = "𝇉" +_SUCCESS_MESSAGE = f"{ARG_SEPARATOR}success{ARG_SEPARATOR}" +"""used to signal that a command was successful (when no other data response is expected)""" DEFAULT_PORT = 25595 @@ -146,6 +148,28 @@ def _receive(timeout: float = 2.0) -> bytes | None: return data +def _receive_raise_if_command_error() -> None: + """ + all commands that expect no value in return, instead receive a success/failure message + this is "success" for success and else a failure message. + + this function is meant to be called after a command is sent that doesn't expect a return value + + Returns: + nothing on success + + Raises: + ConnectionError or ValueError on failure + """ + data = _receive() + if data is None: + raise ConnectionError(f"Did not receive any data in time, as response to command") + + response = _bytes_to_text(data) + if response != _SUCCESS_MESSAGE: + raise ValueError(f"Server received command but responded with error: {response}") + + def _to_int(*args) -> tuple[int, ...]: """args to int""" return tuple(map(int, args)) @@ -173,12 +197,13 @@ def _bytes_to_text(b: bytes) -> str: return b.decode("utf-8").strip() -def _send_command(command: str) -> None: +def _send_command(command: str, validate: bool = False) -> None: """ Sends a command over the global connection. Args: command (str): The command to send. + validate: if True _receive_raise_if_command_error() is called, a function to validate a success response """ # needed internally if connection is None: @@ -186,6 +211,8 @@ def _send_command(command: str) -> None: command = f"{command}\n" connection.sendall(command.encode("utf-8")) + if validate: + _receive_raise_if_command_error() E = TypeVar("E", bound=Enum) diff --git a/st_minecraft/en/boss_bar.py b/st_minecraft/en/boss_bar.py index b93f155..e6c8269 100644 --- a/st_minecraft/en/boss_bar.py +++ b/st_minecraft/en/boss_bar.py @@ -8,6 +8,8 @@ from st_minecraft.core.core import _build_command from st_minecraft.core.core import _send_command +# TODO: there is a GetBossBar command in the backend that seems to be unused in the frontend + class BossBarStyle(Enum): """Ways in which the style of a boss bar can be displayed""" @@ -58,7 +60,7 @@ def __repr__(self): def _send_boss_bar_command(sub_command: str): # needed internally command = f"editBossBar{ARG_SEPARATOR}{sub_command}" - _send_command(command) + _send_command(command, validate=True) def create_bar(name: str, display_text: str) -> BossBar: @@ -72,7 +74,7 @@ def create_bar(name: str, display_text: str) -> BossBar: A BossBar object with which you can further configure the bar """ command = _build_command("spawnBossBar", name, display_text) - _send_command(command) + _send_command(command, validate=True) # some of the values are set when creating. return BossBar( @@ -126,4 +128,4 @@ def delete_bar(boss_bar: BossBar): def _delete_bar_str(boss_bar_name: str): command = _build_command("deleteBossBar", boss_bar_name) - _send_command(command) + _send_command(command, validate=True) diff --git a/st_minecraft/en/main.py b/st_minecraft/en/main.py index 7fe3b9d..9f5ce72 100644 --- a/st_minecraft/en/main.py +++ b/st_minecraft/en/main.py @@ -171,7 +171,7 @@ def send_to_chat(message: str): message: The message you want to send """ command = _build_command("postChat", message) - _send_command(command) + _send_command(command, validate=True) def get_chat() -> list[Message]: @@ -236,7 +236,7 @@ def show_title( seconds_to_ticks(display_time), seconds_to_ticks(fade_out_time), ) - _send_command(command) + _send_command(command, validate=True) def send_command(command: str): @@ -249,7 +249,7 @@ def send_command(command: str): if command.startswith("/"): print("Warning: You entered a '/' at the beginning of the command. This is probably not necessary!") command = _build_command("chatCommand", command) - _send_command(command) + _send_command(command, validate=True) def spawn_entity( @@ -272,7 +272,7 @@ def spawn_entity( """ command = _build_command("spawnEntity", x, y, z, dimension.value, entity.value) print(command) - _send_command(command) + _send_command(command, validate=True) data = _receive() entity = Entity.from_api_format(_bytes_to_text(data)) return entity @@ -318,7 +318,7 @@ def give_item( args.append("unbreakable") command = _build_command(*args) - _send_command(command) + _send_command(command, validate=True) return get_inventory(player) @@ -408,7 +408,7 @@ def set_player_velocity(player: Player, direction: DirectionCollection, value: f """ command = _build_command("setPlayerVelocity", direction.value, player.id, value) - _send_command(command) + _send_command(command, validate=True) return get_player(index=player.id) From 1844b6627f23292da3c6e871a52e2afe7e1aec1e Mon Sep 17 00:00:00 2001 From: nonchris Date: Sat, 22 Aug 2026 00:27:23 +0200 Subject: [PATCH 4/4] add more extensive testing --- demo/test.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/demo/test.py b/demo/test.py index c4c24f8..ce21e63 100644 --- a/demo/test.py +++ b/demo/test.py @@ -1,12 +1,24 @@ +""" +this file aims to call nearly all functions at least once +there are not many assertions. it's more a "do we pass all signatures correctly" test +""" + +# TODO: von chat auslesen is untested + import time import st_minecraft.de as st_minecraft +import st_minecraft.de.boss_leiste as boss_bar from st_minecraft.en import Dimension st_minecraft.verbinden() +""" test title""" + st_minecraft.zeige_titel("Hallo Welt!") +"""test player getters""" + spieler = st_minecraft.hole_spieler() spieler_durch_name = st_minecraft.hole_spieler_durch_name(spieler.name) @@ -17,14 +29,14 @@ assert spieler == spieler_durch_index -cmd = f"op {spieler.name}" -st_minecraft.sende_befehl(cmd) -print(cmd) +""" test player positioning""" st_minecraft.spieler_position_setzen(spieler, spieler.x, spieler.y - 20, spieler.z, dimension=Dimension.Nether) st_minecraft.spieler_position_setzen(spieler, spieler.x, spieler.y + 20, spieler.z, dimension=Dimension.World) +""" test player attributes""" + st_minecraft.spieler_leben_setzen(spieler, 20) st_minecraft.spieler_max_leben_setzten(spieler, 40) @@ -35,15 +47,28 @@ st_minecraft.spieler_xp_fortschritt_setzen(spieler, 0.5) +st_minecraft.spieler_geschwindigkeit_setzen(spieler, st_minecraft.RichtungSammlung.Hoch, 10) + +""" +entity tests +""" + entity = st_minecraft.erzeuge_entity(spieler.x, spieler.y, spieler.z, st_minecraft.EntitySammlung.Kuh) entity = st_minecraft.entity_name_setzen(entity, "Test") +entity = st_minecraft.entity_position_setzen(entity, entity.x, entity.y, entity.z) print(entity.name) entity = st_minecraft.entity_ai_setzen(entity, False) +entity = st_minecraft.entity_leben_setzen(entity, 1) -st_minecraft.gebe_item(spieler, st_minecraft.MaterialSammlung.Holzspitzhacke, 1, name="Test") -print(st_minecraft.hole_inventar(spieler)) +e2 = st_minecraft.hole_entity(entity) + +assert entity.id == e2.id +""" +block tests +""" + for i in range(1, 10, 2): p = st_minecraft.hole_spieler() offset = i * 0.01 @@ -52,6 +77,9 @@ time.sleep(0.2) print(p.x) +""" +float test +""" b = st_minecraft.hole_block(0, 100.34, 0) if b.typ == st_minecraft.MaterialSammlung.Goldblock: @@ -69,4 +97,33 @@ else: raise AssertionError("Set block doesnt match") +""" +command test +""" + +st_minecraft.sende_befehl("time set day") + +""" +chat test +""" +st_minecraft.sende_an_chat("Test!") + +"""inventory tests""" + +st_minecraft.gebe_item(spieler, st_minecraft.MaterialSammlung.Holzspitzhacke, 1, name="Test") +inv = st_minecraft.hole_inventar(spieler) + +print(inv) + +""" +boss bar tests +""" + +bb = boss_bar.erzeuge_leiste("test", "test") + +boss_bar.setze_farbe(bb, boss_bar.BossLeisteFarben.PINK) + +boss_bar.loesche_leiste(bb) + + print(f"SUCCESS")