diff --git a/src/treepuncher/events/__init__.py b/src/treepuncher/events/__init__.py index eca99b5..d14b5c0 100644 --- a/src/treepuncher/events/__init__.py +++ b/src/treepuncher/events/__init__.py @@ -3,3 +3,4 @@ from .join_game import JoinGameEvent from .death import DeathEvent from .system import ConnectedEvent, DisconnectedEvent from .connection import PlayerJoinEvent, PlayerLeaveEvent +from .block_update import BlockUpdateEvent diff --git a/src/treepuncher/events/block_update.py b/src/treepuncher/events/block_update.py new file mode 100644 index 0000000..1ad4947 --- /dev/null +++ b/src/treepuncher/events/block_update.py @@ -0,0 +1,13 @@ +from aiocraft.mc.definitions import BlockPos + +from .base import BaseEvent + +class BlockUpdateEvent(BaseEvent): + SENTINEL = object() + + location : BlockPos + state : int + + def __init__(self, location: BlockPos, state: int): + self.location = location + self.state = state diff --git a/src/treepuncher/game/__init__.py b/src/treepuncher/game/__init__.py index f66ad18..dd57fd4 100644 --- a/src/treepuncher/game/__init__.py +++ b/src/treepuncher/game/__init__.py @@ -4,3 +4,4 @@ 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/position.py b/src/treepuncher/game/position.py new file mode 100644 index 0000000..b3330d6 --- /dev/null +++ b/src/treepuncher/game/position.py @@ -0,0 +1,53 @@ +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/world.py b/src/treepuncher/game/world.py index e259477..02a338b 100644 --- a/src/treepuncher/game/world.py +++ b/src/treepuncher/game/world.py @@ -1,98 +1,27 @@ import json -from time import time -from typing import Optional - from aiocraft.mc.definitions import BlockPos -from aiocraft.mc.proto.play.clientbound import ( - PacketPosition, PacketMapChunk, PacketBlockChange, PacketMultiBlockChange, PacketSetPassengers, - PacketEntityTeleport, PacketRelEntityMove -) -from aiocraft.mc.proto.play.serverbound import PacketTeleportConfirm, PacketSteerVehicle +from aiocraft.mc.proto import PacketMapChunk, PacketBlockChange, PacketMultiBlockChange +from aiocraft.mc.types import twos_comp + from aiocraft import Chunk, World # TODO these imports will hopefully change! from ..scaffold import Scaffold +from ..events import BlockUpdateEvent class GameWorld(Scaffold): - position : BlockPos - vehicle_id : Optional[int] world : World - _last_steer_vehicle : float - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.position = BlockPos(0, 0, 0) - self.vehicle_id = None - self._last_steer_vehicle = time() + self.world = World() - @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(PacketRelEntityMove) - async def entity_relative_move_cb(packet:PacketRelEntityMove): - if self.vehicle_id is None: - return - if self.vehicle_id != packet.entityId: - return - self.position = BlockPos( - self.position.x + packet.dX, - self.position.y + packet.dY, - self.position.z + packet.dZ - ) - self.logger.debug( - "Position synchronized : (x:%.0f,y:%.0f,z:%.0f) (relMove vehicle)", - self.position.x, self.position.y, self.position.z - ) - 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 - ) - ) - - @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 - ) - ) - - if self.cfg.getboolean("process_world", fallback=False): - self.world = World() + # Since this might require more resources, allow to disable it + if not self.cfg.getboolean("process_world", fallback=True): + return + if self.proto == 340: # Chunk parsing is only implemented for 1.12 @self.on_packet(PacketMapChunk) async def map_chunk_cb(packet:PacketMapChunk): assert isinstance(packet.bitMap, int) @@ -100,15 +29,34 @@ class GameWorld(Scaffold): c.read(packet.chunkData) self.world.put(c, packet.x, packet.z, not packet.groundUp) - @self.on_packet(PacketBlockChange) - async def block_change_cb(packet:PacketBlockChange): - self.world.put_block(packet.location[0], packet.location[1], packet.location[2], packet.type) + @self.on_packet(PacketBlockChange) + async def block_change_cb(packet:PacketBlockChange): + self.world.put_block(packet.location[0], packet.location[1], packet.location[2], packet.type) + pos = BlockPos(packet.location[0], packet.location[1], packet.location[2]) + await self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, packet.type)) - @self.on_packet(PacketMultiBlockChange) - async def multi_block_change_cb(packet:PacketMultiBlockChange): + @self.on_packet(PacketMultiBlockChange) + async def multi_block_change_cb(packet:PacketMultiBlockChange): + if self.proto < 751: chunk_x_off = packet.chunkX * 16 chunk_z_off = packet.chunkZ * 16 for entry in packet.records: x_off = (entry['horizontalPos'] >> 4 ) & 15 z_off = entry['horizontalPos'] & 15 - self.world.put_block(x_off + chunk_x_off, entry['y'], z_off + chunk_z_off, entry['blockId']) + 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']) + await self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, entry['blockId'])) + elif self.proto < 760: + x = twos_comp((packet.chunkCoordinates >> 42) & 0x3FFFFF, 22) + z = twos_comp((packet.chunkCoordinates >> 20) & 0x3FFFFF, 22) + y = twos_comp((packet.chunkCoordinates ) & 0xFFFFF , 20) + for loc in packet.records: + state = loc >> 12 + dx = ((loc & 0x0FFF) >> 8 ) & 0x0F + 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) + await self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, state)) + else: + self.logger.error("Cannot process MultiBlockChange for protocol %d", self.proto) diff --git a/src/treepuncher/treepuncher.py b/src/treepuncher/treepuncher.py index ef64f7e..59bf706 100644 --- a/src/treepuncher/treepuncher.py +++ b/src/treepuncher/treepuncher.py @@ -15,7 +15,7 @@ from aiocraft.mc.auth import AuthInterface, AuthException, MojangAuthenticator, from aiocraft.mc.auth.microsoft import InvalidStateError from .storage import StorageDriver, SystemState, AuthenticatorState -from .game import GameState, GameChat, GameInventory, GameTablist, GameWorld, GameContainer +from .game import GameState, GameChat, GameInventory, GameTablist, GameWorld, GameContainer, GamePosition from .addon import Addon from .notifier import Notifier, Provider @@ -34,6 +34,7 @@ class Treepuncher( GameInventory, GameContainer, GameTablist, + GamePosition, GameWorld ): name: str @@ -222,7 +223,7 @@ class Treepuncher( except AuthException as e: self.logger.error("Auth exception : [%s|%d] %s (%s)", e.endpoint, e.code, e.data, e.kwargs) - except InvalidStateError as e: + except InvalidStateError: self.logger.error("Invalid authenticator state") if isinstance(self.authenticator, MicrosoftAuthenticator): self.logger.info("Obtain an auth code by visiting %s", self.authenticator.url())