Merge branch 'dev' of fantabos.co:treepuncher into dev

This commit is contained in:
əlemi 2023-11-02 05:39:51 +01:00
commit caeb5215cc
Signed by: alemi
GPG key ID: A4895B84D311642C
10 changed files with 148 additions and 62 deletions

3
.gitignore vendored
View file

@ -127,3 +127,6 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
# Auto generated version file
src/treepuncher/__version__.py

View file

@ -1,7 +1,30 @@
[build-system] [build-system]
requires = [ requires = ["setuptools", "setuptools-scm"]
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project]
name = "treepuncher"
authors = [
{name = "alemi", email = "me@alemi.dev"},
]
description = "An hackable Minecraft client, built with aiocraft"
readme = "README.md"
requires-python = ">=3.7"
keywords = ["minecraft", "client", "bot", "hackable"]
# license = {text = "MIT"}
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = [
"setproctitle",
"termcolor",
"apscheduler",
"aioconsole",
"aiocraft",
]
dynamic = ["version"]
[tool.setuptools_scm]
write_to = "src/treepuncher/__version__.py"

View file

@ -1,30 +0,0 @@
[metadata]
name = treepuncher
version = 0.1.0
author = alemi
author_email = me@alemi.dev
description = An hackable Minecraft client, built with aiocraft
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/alemidev/treepuncher
project_urls =
Bug Tracker = https://github.com/alemidev/treepuncher/issues
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Operating System :: OS Independent
[options]
install_requires =
setproctitle
termcolor
apscheduler
aioconsole
aiocraft
package_dir =
= src
packages = find:
python_requires = >=3.6
[options.packages.find]
where = src

View file

@ -3,3 +3,4 @@ from .join_game import JoinGameEvent
from .death import DeathEvent from .death import DeathEvent
from .system import ConnectedEvent, DisconnectedEvent from .system import ConnectedEvent, DisconnectedEvent
from .connection import PlayerJoinEvent, PlayerLeaveEvent from .connection import PlayerJoinEvent, PlayerLeaveEvent
from .block_update import BlockUpdateEvent

View file

@ -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

View file

@ -4,3 +4,4 @@ from .tablist import GameTablist
from .chat import GameChat from .chat import GameChat
from .world import GameWorld from .world import GameWorld
from .container import GameContainer from .container import GameContainer
from .position import GamePosition

View file

@ -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
)
)

View file

@ -1,10 +1,12 @@
import asyncio import asyncio
import datetime import datetime
import json
#from aiocraft.client import MinecraftClient #from aiocraft.client import MinecraftClient
from aiocraft.mc.definitions import Gamemode, Dimension, Difficulty from aiocraft.mc.definitions import Gamemode, Dimension, Difficulty
from aiocraft.mc.proto import ( from aiocraft.mc.proto import (
PacketRespawn, PacketLogin, PacketUpdateHealth, PacketExperience, PacketSettings, PacketClientCommand, PacketAbilities PacketRespawn, PacketLogin, PacketUpdateHealth, PacketExperience, PacketSettings,
PacketClientCommand, PacketAbilities, PacketDifficulty
) )
from ..events import JoinGameEvent, DeathEvent, DisconnectedEvent from ..events import JoinGameEvent, DeathEvent, DisconnectedEvent
@ -37,9 +39,9 @@ class GameState(Scaffold):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.in_game = False self.in_game = False
self.gamemode = Gamemode.SURVIVAL self.gamemode = Gamemode.UNKNOWN
self.dimension = Dimension.OVERWORLD self.dimension = Dimension.UNKNOWN
self.difficulty = Difficulty.HARD self.difficulty = Difficulty.UNKNOWN
self.join_time = datetime.datetime(2011, 11, 18) self.join_time = datetime.datetime(2011, 11, 18)
self.hp = 20.0 self.hp = 20.0
@ -56,7 +58,8 @@ class GameState(Scaffold):
async def on_player_respawning(packet:PacketRespawn): async def on_player_respawning(packet:PacketRespawn):
self.gamemode = Gamemode(packet.gamemode) self.gamemode = Gamemode(packet.gamemode)
if isinstance(packet.dimension, dict): if isinstance(packet.dimension, dict):
self.dimension = Dimension.OVERWORLD # TODO wtf??? self.logger.info("Received dimension data: %s", json.dumps(packet.dimensionCodec, indent=2))
self.dimension = Dimension.from_str(packet.dimension['effects'])
else: else:
self.dimension = Dimension(packet.dimension) self.dimension = Dimension(packet.dimension)
self.difficulty = Difficulty(packet.difficulty) self.difficulty = Difficulty(packet.difficulty)
@ -72,12 +75,19 @@ class GameState(Scaffold):
self.gamemode.name self.gamemode.name
) )
@self.on_packet(PacketDifficulty)
async def on_set_difficulty(packet:PacketDifficulty):
self.difficulty = Difficulty(packet.difficulty)
self.logger.info("Difficulty set to %s", self.difficulty.name)
@self.on_packet(PacketLogin) @self.on_packet(PacketLogin)
async def player_joining_cb(packet:PacketLogin): async def player_joining_cb(packet:PacketLogin):
self.entity_id = packet.entityId self.entity_id = packet.entityId
self.gamemode = Gamemode(packet.gameMode) self.gamemode = Gamemode(packet.gameMode)
if isinstance(packet.dimension, dict): if isinstance(packet.dimension, dict):
self.dimension = Dimension.OVERWORLD # TODO wtf??? with open('world_codec.json', 'w') as f:
json.dump(packet.dimensionCodec, f)
self.dimension = Dimension.from_str(packet.dimension['effects'])
else: else:
self.dimension = Dimension(packet.dimension) self.dimension = Dimension(packet.dimension)
self.difficulty = Difficulty(packet.difficulty) self.difficulty = Difficulty(packet.difficulty)

View file

@ -1,21 +1,21 @@
import json import json
from time import time from time import time
from typing import Optional
from aiocraft.mc.definitions import BlockPos from aiocraft.mc.definitions import BlockPos
from aiocraft.mc.proto.play.clientbound import ( from aiocraft.mc.proto import (
PacketPosition, PacketMapChunk, PacketBlockChange, PacketMultiBlockChange, PacketSetPassengers, PacketMapChunk, PacketBlockChange, PacketMultiBlockChange, PacketSetPassengers, PacketEntityTeleport,
PacketEntityTeleport, PacketRelEntityMove PacketSteerVehicle, PacketRelEntityMove, PacketTeleportConfirm, PacketPosition
) )
from aiocraft.mc.proto.play.serverbound import PacketTeleportConfirm, PacketSteerVehicle from aiocraft.mc.types import twos_comp
from aiocraft import Chunk, World # TODO these imports will hopefully change! from aiocraft import Chunk, World # TODO these imports will hopefully change!
from ..scaffold import Scaffold from ..scaffold import Scaffold
from ..events import BlockUpdateEvent
class GameWorld(Scaffold): class GameWorld(Scaffold):
position : BlockPos position : BlockPos
vehicle_id : Optional[int] vehicle_id : int | None
world : World world : World
_last_steer_vehicle : float _last_steer_vehicle : float
@ -90,11 +90,10 @@ class GameWorld(Scaffold):
) )
) )
if not self.cfg.getboolean("process_world", fallback=False): # Since this might require more resources, allow to disable it
if not self.cfg.getboolean("process_world", fallback=True):
return return
self.world = World()
@self.on_packet(PacketMapChunk) @self.on_packet(PacketMapChunk)
async def map_chunk_cb(packet:PacketMapChunk): async def map_chunk_cb(packet:PacketMapChunk):
assert isinstance(packet.bitMap, int) assert isinstance(packet.bitMap, int)
@ -105,18 +104,31 @@ class GameWorld(Scaffold):
@self.on_packet(PacketBlockChange) @self.on_packet(PacketBlockChange)
async def block_change_cb(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.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])
self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, packet.type))
@self.on_packet(PacketMultiBlockChange) @self.on_packet(PacketMultiBlockChange)
async def multi_block_change_cb(packet:PacketMultiBlockChange): async def multi_block_change_cb(packet:PacketMultiBlockChange):
return # holy shit this is LAME! if self.dispatcher.proto < 751:
if hasattr(packet, "chunkCoordinates"):
chunk_x_off = (packet.chunkCoordinates & 0b1111111111111111111111) * 16
chunk_z_off = ((packet.chunkCoordinates >> 22) & 0b1111111111111111111111) * 16
chunk_y_off = ((packet.chunkCoordinates >> 44) & 0b11111111111111111111) * 16
else:
chunk_x_off = packet.chunkX * 16 chunk_x_off = packet.chunkX * 16
chunk_z_off = packet.chunkZ * 16 chunk_z_off = packet.chunkZ * 16
for entry in packet.records: for entry in packet.records:
x_off = (entry['horizontalPos'] >> 4 ) & 15 x_off = (entry['horizontalPos'] >> 4 ) & 15
z_off = entry['horizontalPos'] & 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'])
self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, entry['blockId']))
elif self.dispatcher.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)
self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, state))
else:
self.logger.error("Cannot process MultiBlockChange for protocol %d", self.dispatcher.proto)

View file

@ -15,7 +15,7 @@ from aiocraft.mc.auth import AuthInterface, AuthException, MojangAuthenticator,
from aiocraft.mc.auth.microsoft import InvalidStateError from aiocraft.mc.auth.microsoft import InvalidStateError
from .storage import StorageDriver, SystemState, AuthenticatorState 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 .addon import Addon
from .notifier import Notifier, Provider from .notifier import Notifier, Provider
@ -223,7 +223,7 @@ class Treepuncher(
except AuthException as e: except AuthException as e:
self.logger.error("Auth exception : [%s|%d] %s (%s)", e.endpoint, e.code, e.data, e.kwargs) 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") self.logger.error("Invalid authenticator state")
if isinstance(self.authenticator, MicrosoftAuthenticator): if isinstance(self.authenticator, MicrosoftAuthenticator):
self.logger.info("Obtain an auth code by visiting %s", self.authenticator.url()) self.logger.info("Obtain an auth code by visiting %s", self.authenticator.url())