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/
# Auto generated version file
src/treepuncher/__version__.py

View file

@ -1,7 +1,30 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
requires = ["setuptools", "setuptools-scm"]
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 .system import ConnectedEvent, DisconnectedEvent
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 .world import GameWorld
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 datetime
import json
#from aiocraft.client import MinecraftClient
from aiocraft.mc.definitions import Gamemode, Dimension, Difficulty
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
@ -37,9 +39,9 @@ class GameState(Scaffold):
super().__init__(*args, **kwargs)
self.in_game = False
self.gamemode = Gamemode.SURVIVAL
self.dimension = Dimension.OVERWORLD
self.difficulty = Difficulty.HARD
self.gamemode = Gamemode.UNKNOWN
self.dimension = Dimension.UNKNOWN
self.difficulty = Difficulty.UNKNOWN
self.join_time = datetime.datetime(2011, 11, 18)
self.hp = 20.0
@ -56,7 +58,8 @@ class GameState(Scaffold):
async def on_player_respawning(packet:PacketRespawn):
self.gamemode = Gamemode(packet.gamemode)
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:
self.dimension = Dimension(packet.dimension)
self.difficulty = Difficulty(packet.difficulty)
@ -72,12 +75,19 @@ class GameState(Scaffold):
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)
async def player_joining_cb(packet:PacketLogin):
self.entity_id = packet.entityId
self.gamemode = Gamemode(packet.gameMode)
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:
self.dimension = Dimension(packet.dimension)
self.difficulty = Difficulty(packet.difficulty)

View file

@ -1,21 +1,21 @@
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 import (
PacketMapChunk, PacketBlockChange, PacketMultiBlockChange, PacketSetPassengers, PacketEntityTeleport,
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 ..scaffold import Scaffold
from ..events import BlockUpdateEvent
class GameWorld(Scaffold):
position : BlockPos
vehicle_id : Optional[int]
vehicle_id : int | None
world : World
_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
self.world = World()
@self.on_packet(PacketMapChunk)
async def map_chunk_cb(packet:PacketMapChunk):
assert isinstance(packet.bitMap, int)
@ -105,18 +104,31 @@ class GameWorld(Scaffold):
@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])
self.run_callbacks(BlockUpdateEvent, BlockUpdateEvent(pos, packet.type))
@self.on_packet(PacketMultiBlockChange)
async def multi_block_change_cb(packet:PacketMultiBlockChange):
return # holy shit this is LAME!
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:
if self.dispatcher.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'])
for entry in packet.records:
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.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 .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
@ -223,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())