Merge branch 'dev' of fantabos.co:treepuncher into dev
This commit is contained in:
commit
caeb5215cc
10 changed files with 148 additions and 62 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -127,3 +127,6 @@ dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# Auto generated version file
|
||||||
|
src/treepuncher/__version__.py
|
||||||
|
|
|
@ -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"
|
||||||
|
|
30
setup.cfg
30
setup.cfg
|
@ -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
|
|
|
@ -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
|
||||||
|
|
13
src/treepuncher/events/block_update.py
Normal file
13
src/treepuncher/events/block_update.py
Normal 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
|
|
@ -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
|
||||||
|
|
53
src/treepuncher/game/position.py
Normal file
53
src/treepuncher/game/position.py
Normal 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
|
||||||
|
)
|
||||||
|
)
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Reference in a new issue