diff --git a/src/treepuncher/__main__.py b/src/treepuncher/__main__.py index 08f08ca..73e8669 100644 --- a/src/treepuncher/__main__.py +++ b/src/treepuncher/__main__.py @@ -1,8 +1,5 @@ #!/usr/bin/env python import os -import re -import sys -import asyncio import logging import argparse import inspect @@ -20,9 +17,9 @@ from .scaffold import ConfigObject from .helpers import configure_logging def main(): - root = Path(os.getcwd()) # TODO would be cool if it was possible to configure addons path, but we need to load addons before doing argparse so we can do helptext - # addon_path = Path(args.path) if args.addon_path else ( root/'addons' ) + #root = Path(os.getcwd()) + #addon_path = Path(args.path) if args.addon_path else ( root/'addons' ) addon_path = Path('addons') addons : Set[Type[Addon]] = set() @@ -34,7 +31,7 @@ def main(): obj = getattr(m, obj_name) if obj != Addon and inspect.isclass(obj) and issubclass(obj, Addon): addons.add(obj) - except Exception as e: + except Exception: print(f"Exception importing addon {py_path}") traceback.print_exc() pass diff --git a/src/treepuncher/events/block_update.py b/src/treepuncher/events/block_update.py index 1ad4947..549f041 100644 --- a/src/treepuncher/events/block_update.py +++ b/src/treepuncher/events/block_update.py @@ -1,4 +1,4 @@ -from aiocraft.mc.definitions import BlockPos +from aiocraft.types import BlockPos from .base import BaseEvent diff --git a/src/treepuncher/events/connection.py b/src/treepuncher/events/connection.py index c4bdd57..b517dc2 100644 --- a/src/treepuncher/events/connection.py +++ b/src/treepuncher/events/connection.py @@ -1,4 +1,4 @@ -from aiocraft.mc.definitions import Player +from aiocraft.types import Player from .base import BaseEvent class PlayerJoinEvent(BaseEvent): diff --git a/src/treepuncher/events/join_game.py b/src/treepuncher/events/join_game.py index 9b37ee5..0f6b823 100644 --- a/src/treepuncher/events/join_game.py +++ b/src/treepuncher/events/join_game.py @@ -1,4 +1,4 @@ -from aiocraft.mc.definitions import Dimension, Difficulty, Gamemode +from aiocraft.types import Dimension, Difficulty, Gamemode from .base import BaseEvent diff --git a/src/treepuncher/events/packet.py b/src/treepuncher/events/packet.py index ecce2d2..6d248fb 100644 --- a/src/treepuncher/events/packet.py +++ b/src/treepuncher/events/packet.py @@ -1,4 +1,4 @@ -from aiocraft.mc.packet import Packet +from aiocraft.packet import Packet from .base import BaseEvent diff --git a/src/treepuncher/game/__init__.py b/src/treepuncher/game/__init__.py index dd57fd4..f66ad18 100644 --- a/src/treepuncher/game/__init__.py +++ b/src/treepuncher/game/__init__.py @@ -4,4 +4,3 @@ from .tablist import GameTablist from .chat import GameChat from .world import GameWorld from .container import GameContainer -from .position import GamePosition diff --git a/src/treepuncher/game/chat.py b/src/treepuncher/game/chat.py index 3ac638e..8050a7a 100644 --- a/src/treepuncher/game/chat.py +++ b/src/treepuncher/game/chat.py @@ -1,9 +1,7 @@ -from typing import Union +from aiocraft.proto.play.clientbound import PacketChat as PacketChatMessage +from aiocraft.proto.play.serverbound import PacketChat -from aiocraft.mc.proto.play.clientbound import PacketChat as PacketChatMessage -from aiocraft.mc.proto.play.serverbound import PacketChat - -from ..events.chat import ChatEvent, MessageType +from ..events.chat import ChatEvent from ..scaffold import Scaffold class GameChat(Scaffold): @@ -15,14 +13,11 @@ class GameChat(Scaffold): async def chat_event_callback(packet:PacketChatMessage): self.run_callbacks(ChatEvent, ChatEvent(packet.message)) - async def chat(self, message:str, whisper:str=None, wait:bool=False): + async def chat(self, message:str, whisper:str="", wait:bool=False): if whisper: message = f"/w {whisper} {message}" await self.dispatcher.write( - PacketChat( - self.dispatcher.proto, - message=message - ), + PacketChat(message=message), wait=wait ) diff --git a/src/treepuncher/game/container.py b/src/treepuncher/game/container.py index 537cc3d..55b4de2 100644 --- a/src/treepuncher/game/container.py +++ b/src/treepuncher/game/container.py @@ -1,27 +1,22 @@ -import asyncio -import datetime -from typing import List, Optional - -#from aiocraft.client import MinecraftClient -from aiocraft.mc.definitions import Item -from aiocraft.mc.proto.play.clientbound import PacketTransaction -from aiocraft.mc.proto.play.serverbound import PacketTransaction as PacketTransactionServerbound -from aiocraft.mc.proto import ( +from aiocraft.types import Item +from aiocraft.proto.play.clientbound import PacketTransaction +from aiocraft.proto.play.serverbound import PacketTransaction as PacketTransactionServerbound +from aiocraft.proto import ( PacketOpenWindow, PacketCloseWindow, PacketSetSlot ) -from ..events import JoinGameEvent, DeathEvent, ConnectedEvent, DisconnectedEvent +from ..events import DisconnectedEvent from ..scaffold import Scaffold class WindowContainer: id: int title: str type: str - entity_id: Optional[int] + entity_id: int | None transaction_id: int - inventory: List[Optional[Item]] + inventory: list[Item | None] - def __init__(self, id:int, title: str, type: str, entity_id:int = None, slot_count:int = 27): + def __init__(self, id:int, title: str, type: str, entity_id:int | None = None, slot_count:int = 27): self.id = id self.title = title self.type = type @@ -37,7 +32,7 @@ class WindowContainer: return self.transaction_id class GameContainer(Scaffold): - window: Optional[WindowContainer] + window: WindowContainer | None @property def is_container_open(self) -> bool: @@ -85,7 +80,6 @@ class GameContainer(Scaffold): if not packet.accepted: # apologize to server automatically await self.dispatcher.write( PacketTransactionServerbound( - self.dispatcher.proto, windowId=packet.windowId, action=packet.action, accepted=packet.accepted, diff --git a/src/treepuncher/game/inventory.py b/src/treepuncher/game/inventory.py index 01e57b1..0d6b85b 100644 --- a/src/treepuncher/game/inventory.py +++ b/src/treepuncher/game/inventory.py @@ -1,8 +1,8 @@ from typing import List -from aiocraft.mc.definitions import Item -from aiocraft.mc.proto.play.clientbound import PacketSetSlot, PacketHeldItemSlot as PacketHeldItemChange -from aiocraft.mc.proto.play.serverbound import PacketHeldItemSlot +from aiocraft.types import Item +from aiocraft.proto.play.clientbound import PacketSetSlot, PacketHeldItemSlot as PacketHeldItemChange +from aiocraft.proto.play.serverbound import PacketHeldItemSlot from ..scaffold import Scaffold @@ -13,7 +13,7 @@ class GameInventory(Scaffold): async def set_slot(self, slot:int): self.slot = slot - await self.dispatcher.write(PacketHeldItemSlot(self.dispatcher.proto, slotId=slot)) + await self.dispatcher.write(PacketHeldItemSlot(slotId=slot)) @property def hotbar(self) -> List[Item]: diff --git a/src/treepuncher/game/position.py b/src/treepuncher/game/position.py deleted file mode 100644 index b3330d6..0000000 --- a/src/treepuncher/game/position.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Optional - -from aiocraft.mc.definitions import BlockPos -from aiocraft.mc.proto import PacketPosition, PacketSetPassengers, PacketEntityTeleport, PacketTeleportConfirm - -from ..scaffold import Scaffold - -class GamePosition(Scaffold): - position : BlockPos - vehicle_id : Optional[int] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.position = BlockPos(0, 0, 0) - self.vehicle_id = None - - @self.on_packet(PacketSetPassengers) - async def player_enters_vehicle_cb(packet:PacketSetPassengers): - if self.vehicle_id is None: # might get mounted on a vehicle - for entity_id in packet.passengers: - if entity_id == self.entity_id: - self.vehicle_id = packet.entityId - else: # might get dismounted from vehicle - if packet.entityId == self.vehicle_id: - if self.entity_id not in packet.passengers: - self.vehicle_id = None - - @self.on_packet(PacketEntityTeleport) - async def entity_rubberband_cb(packet:PacketEntityTeleport): - if self.vehicle_id is None: - return - if self.vehicle_id != packet.entityId: - return - self.position = BlockPos(packet.x, packet.y, packet.z) - self.logger.info( - "Position synchronized : (x:%.0f,y:%.0f,z:%.0f) (vehicle)", - self.position.x, self.position.y, self.position.z - ) - - @self.on_packet(PacketPosition) - async def player_rubberband_cb(packet:PacketPosition): - self.position = BlockPos(packet.x, packet.y, packet.z) - self.logger.info( - "Position synchronized : (x:%.0f,y:%.0f,z:%.0f)", - self.position.x, self.position.y, self.position.z - ) - await self.dispatcher.write( - PacketTeleportConfirm( - self.dispatcher.proto, - teleportId=packet.teleportId - ) - ) diff --git a/src/treepuncher/game/state.py b/src/treepuncher/game/state.py index 93f13c9..1f80c6d 100644 --- a/src/treepuncher/game/state.py +++ b/src/treepuncher/game/state.py @@ -3,8 +3,8 @@ import datetime import json #from aiocraft.client import MinecraftClient -from aiocraft.mc.definitions import Gamemode, Dimension, Difficulty -from aiocraft.mc.proto import ( +from aiocraft.types import Gamemode, Dimension, Difficulty +from aiocraft.proto import ( PacketRespawn, PacketLogin, PacketUpdateHealth, PacketExperience, PacketSettings, PacketClientCommand, PacketAbilities, PacketDifficulty ) @@ -58,7 +58,7 @@ class GameState(Scaffold): async def on_player_respawning(packet:PacketRespawn): self.gamemode = Gamemode(packet.gamemode) if isinstance(packet.dimension, dict): - self.logger.info("Received dimension data: %s", json.dumps(packet.dimensionCodec, indent=2)) + self.logger.info("Received dimension data: %s", json.dumps(packet.dimension, indent=2)) self.dimension = Dimension.from_str(packet.dimension['effects']) else: self.dimension = Dimension(packet.dimension) @@ -106,7 +106,6 @@ class GameState(Scaffold): self.run_callbacks(JoinGameEvent, JoinGameEvent(self.dimension, self.difficulty, self.gamemode)) await self.dispatcher.write( PacketSettings( - self.dispatcher.proto, locale="en_US", viewDistance=4, chatFlags=0, @@ -115,7 +114,7 @@ class GameState(Scaffold): mainHand=0, ) ) - await self.dispatcher.write(PacketClientCommand(self.dispatcher.proto, actionId=0)) + await self.dispatcher.write(PacketClientCommand(actionId=0)) @self.on_packet(PacketUpdateHealth) async def player_hp_cb(packet:PacketUpdateHealth): @@ -132,7 +131,7 @@ class GameState(Scaffold): self.logger.warning("Died, attempting to respawn") await asyncio.sleep(0.5) # TODO make configurable await self.dispatcher.write( - PacketClientCommand(self.dispatcher.proto, actionId=0) # respawn + PacketClientCommand(actionId=0) # respawn ) @self.on_packet(PacketExperience) diff --git a/src/treepuncher/game/tablist.py b/src/treepuncher/game/tablist.py index 143bb9d..0af08e0 100644 --- a/src/treepuncher/game/tablist.py +++ b/src/treepuncher/game/tablist.py @@ -2,10 +2,9 @@ import uuid import datetime from enum import Enum -from typing import Dict, List -from aiocraft.mc.definitions import Player -from aiocraft.mc.proto import PacketPlayerInfo +from aiocraft.types import Player +from aiocraft.proto import PacketPlayerInfo from ..scaffold import Scaffold from ..events import ConnectedEvent, PlayerJoinEvent, PlayerLeaveEvent @@ -18,7 +17,7 @@ class ActionType(Enum): # TODO move this in aiocraft REMOVE_PLAYER = 4 class GameTablist(Scaffold): - tablist : Dict[uuid.UUID, Player] + tablist : dict[uuid.UUID, Player] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/src/treepuncher/game/world.py b/src/treepuncher/game/world.py index cfdf990..19d24ea 100644 --- a/src/treepuncher/game/world.py +++ b/src/treepuncher/game/world.py @@ -1,13 +1,13 @@ import json from time import time -from aiocraft.mc.definitions import BlockPos -from aiocraft.mc.proto import ( +from aiocraft.types import BlockPos +from aiocraft.proto import ( PacketMapChunk, PacketBlockChange, PacketMultiBlockChange, PacketSetPassengers, PacketEntityTeleport, PacketSteerVehicle, PacketRelEntityMove, PacketTeleportConfirm ) -from aiocraft.mc.proto.play.clientbound import PacketPosition -from aiocraft.mc.types import twos_comp +from aiocraft.proto.play.clientbound import PacketPosition +from aiocraft.primitives import twos_comp from aiocraft import Chunk, World # TODO these imports will hopefully change! @@ -70,12 +70,7 @@ class GameWorld(Scaffold): if time() - self._last_steer_vehicle >= 5: self._last_steer_vehicle = time() await self.dispatcher.write( - PacketSteerVehicle( - self.dispatcher.proto, - forward=0, - sideways=0, - jump=0 - ) + PacketSteerVehicle(forward=0, sideways=0, jump=0) ) @self.on_packet(PacketPosition) @@ -86,10 +81,7 @@ class GameWorld(Scaffold): self.position.x, self.position.y, self.position.z ) await self.dispatcher.write( - PacketTeleportConfirm( - self.dispatcher.proto, - teleportId=packet.teleportId - ) + PacketTeleportConfirm(teleportId=packet.teleportId) ) # Since this might require more resources, allow to disable it @@ -118,7 +110,7 @@ class GameWorld(Scaffold): x_off = (entry['horizontalPos'] >> 4 ) & 15 z_off = entry['horizontalPos'] & 15 pos = BlockPos(x_off + chunk_x_off, entry['y'], z_off + chunk_z_off) - self.world.put_block(pos.x,pos.y, pos.z, entry['blockId']) + self.world.put_block(pos.i_x, pos.i_y, pos.i_z, entry['blockId']) self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, entry['blockId'])) elif self.dispatcher.proto < 760: x = twos_comp((packet.chunkCoordinates >> 42) & 0x3FFFFF, 22) @@ -130,7 +122,7 @@ class GameWorld(Scaffold): dz = ((loc & 0x0FFF) >> 4 ) & 0x0F dy = ((loc & 0x0FFF) ) & 0x0F pos = BlockPos(16*x + dx, 16*y + dy, 16*z + dz) - self.world.put_block(pos.x, pos.y, pos.z, state) + self.world.put_block(pos.i_x, pos.i_y, pos.i_z, state) self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, state)) else: self.logger.error("Cannot process MultiBlockChange for protocol %d", self.dispatcher.proto) diff --git a/src/treepuncher/scaffold.py b/src/treepuncher/scaffold.py index 93afb39..eb0aa3f 100644 --- a/src/treepuncher/scaffold.py +++ b/src/treepuncher/scaffold.py @@ -2,13 +2,13 @@ from configparser import ConfigParser, SectionProxy from typing import Type, Any -from aiocraft.client import MinecraftClient +from aiocraft.client import AbstractMinecraftClient from aiocraft.util import helpers -from aiocraft.mc.packet import Packet -from aiocraft.mc.definitions import ConnectionState -from aiocraft.mc.proto import PacketKickDisconnect, PacketSetCompression -from aiocraft.mc.proto.play.clientbound import PacketKeepAlive -from aiocraft.mc.proto.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse +from aiocraft.packet import Packet +from aiocraft.types import ConnectionState +from aiocraft.proto import PacketKickDisconnect, PacketSetCompression +from aiocraft.proto.play.clientbound import PacketKeepAlive +from aiocraft.proto.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse from .traits import CallbacksHolder, Runnable from .events import ConnectedEvent, DisconnectedEvent @@ -21,7 +21,7 @@ class ConfigObject: class Scaffold( CallbacksHolder, Runnable, - MinecraftClient, + AbstractMinecraftClient, ): entity_id : int @@ -43,16 +43,17 @@ class Scaffold( #Override async def _play(self) -> bool: - self.dispatcher.set_state(ConnectionState.PLAY) + assert self.dispatcher is not None + self.dispatcher.promote(ConnectionState.PLAY) self.run_callbacks(ConnectedEvent, ConnectedEvent()) async for packet in self.dispatcher.packets(): self.logger.debug("[ * ] Processing %s", packet.__class__.__name__) if isinstance(packet, PacketSetCompression): self.logger.info("Compression updated") - self.dispatcher.set_compression(packet.threshold) + self.dispatcher.update_compression_threshold(packet.threshold) elif isinstance(packet, PacketKeepAlive): if self.cfg.getboolean("send_keep_alive", fallback=True): - keep_alive_packet = PacketKeepAliveResponse(self.dispatcher._proto, keepAliveId=packet.keepAliveId) + keep_alive_packet = PacketKeepAliveResponse(keepAliveId=packet.keepAliveId) await self.dispatcher.write(keep_alive_packet) elif isinstance(packet, PacketKickDisconnect): self.logger.error("Kicked while in game : %s", helpers.parse_chat(packet.reason)) diff --git a/src/treepuncher/traits/callbacks.py b/src/treepuncher/traits/callbacks.py index cf5fd37..fe3a4ff 100644 --- a/src/treepuncher/traits/callbacks.py +++ b/src/treepuncher/traits/callbacks.py @@ -5,8 +5,6 @@ import logging from inspect import isclass from typing import Dict, List, Set, Any, Callable, Type -from ..events.base import BaseEvent - class CallbacksHolder: _callbacks : Dict[Any, List[Callable]] @@ -17,7 +15,7 @@ class CallbacksHolder: self._callbacks = {} self._tasks = {} - def callback_keys(self, filter:Type = None) -> Set[Any]: + def callback_keys(self, filter:Type | None = None) -> Set[Any]: return set(x for x in self._callbacks.keys() if not filter or (isclass(x) and issubclass(x, filter))) def register(self, key:Any, callback:Callable): diff --git a/src/treepuncher/treepuncher.py b/src/treepuncher/treepuncher.py index 6a2bbff..05ff571 100644 --- a/src/treepuncher/treepuncher.py +++ b/src/treepuncher/treepuncher.py @@ -4,18 +4,18 @@ import asyncio import datetime import pkg_resources -from typing import List, Dict, Any, Type, Optional +from typing import Any, Type from time import time from configparser import ConfigParser from apscheduler.schedulers.asyncio import AsyncIOScheduler -from aiocraft.mc.packet import Packet -from aiocraft.mc.auth import AuthInterface, AuthException, MojangAuthenticator, MicrosoftAuthenticator, OfflineAuthenticator -from aiocraft.mc.auth.microsoft import InvalidStateError +from aiocraft.packet import Packet +from aiocraft.auth import AuthInterface, AuthException, MojangAuthenticator, MicrosoftAuthenticator, OfflineAuthenticator +from aiocraft.auth.microsoft import InvalidStateError from .storage import StorageDriver, SystemState, AuthenticatorState -from .game import GameState, GameChat, GameInventory, GameTablist, GameWorld, GameContainer, GamePosition +from .game import GameState, GameChat, GameInventory, GameTablist, GameWorld, GameContainer from .addon import Addon from .notifier import Notifier, Provider @@ -42,10 +42,13 @@ class Treepuncher( notifier: Notifier scheduler: AsyncIOScheduler - modules: List[Addon] - ctx: Dict[Any, Any] + modules: list[Addon] + ctx: dict[Any, Any] _processing: bool + _proto_override: int + _host: str + _port: int def __init__( self, @@ -61,7 +64,7 @@ class Treepuncher( authenticator : AuthInterface - def opt(k:str, required=False, default=None, t:type=str) -> Optional[Any]: + def opt(k, required=False, default=None, t=str): v = kwargs.get(k) if v is None: v = self.cfg.get(k) @@ -95,13 +98,19 @@ class Treepuncher( ) super().__init__( - opt('server', required=True), authenticator=authenticator, online_mode=opt('online_mode', default=True, t=bool), - force_port=opt('force_port', default=0, t=int), - resolve_srv=opt('resolve_srv', default=True, t=bool), ) + self._proto_override = opt('force_proto', t=int) + self._host = opt('server', required=True) + if ":" in self._host: + h, p = self._host.split(":", 1) + self._host = h + self._port = int(p) + else: + self._host, self._port = self.resolve_srv(self._host) + self.storage = StorageDriver(opt('session_file') or f"data/{name}.session") # TODO wrap with pathlib self.notifier = Notifier(self) @@ -199,22 +208,21 @@ class Treepuncher( async def _work(self): self.logger.debug("Worker started") try: - if "force_proto" in self.cfg: - self.dispatcher.set_proto(self.cfg.getint('force_proto')) + log_ignored_packets = self.cfg.getboolean('log_ignored_packets', fallback=False) + whitelist = self.callback_keys(filter=Packet) + if self._proto_override: + proto = self._proto_override else: try: - server_data = await self.info() + server_data = await self.info(self._host, self._port, whitelist=whitelist, log_ignored_packets=log_ignored_packets) if "version" in server_data and "protocol" in server_data["version"]: - self.dispatcher.set_proto(server_data['version']['protocol']) + proto = server_data['version']['protocol'] except OSError as e: self.logger.error("Connection error : %s", str(e)) - self.dispatcher.whitelist(self.callback_keys(filter=Packet)) - self.dispatcher.log_ignored_packets(self.cfg.getboolean('log_ignored_packets', fallback=False)) - while self._processing: try: - await self.join() + await self.join(self._host, self._port, proto, whitelist=whitelist, log_ignored_packets=log_ignored_packets) except OSError as e: self.logger.error("Connection error : %s", str(e))