treepuncher/treepuncher/treepuncher.py
2021-11-30 16:31:42 +01:00

202 lines
5.3 KiB
Python

import re
import logging
import asyncio
from typing import List, Dict, Union, Optional, Any, Type
from enum import Enum
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from aiocraft.client import MinecraftClient
from aiocraft.mc.packet import Packet
from aiocraft.mc.definitions import Difficulty, Dimension, Gamemode, BlockPos
from aiocraft.mc.proto.play.clientbound import (
PacketRespawn, PacketLogin, PacketPosition, PacketUpdateHealth, PacketExperience,
PacketAbilities, PacketChat as PacketChatMessage
)
from aiocraft.mc.proto.play.serverbound import PacketTeleportConfirm, PacketClientCommand, PacketChat
from .events import ChatEvent
from .events.chat import MessageType
from .modules.module import LogicModule
REMOVE_COLOR_FORMATS = re.compile(r"§[0-9a-z]")
class TreepuncherEvents(Enum):
DIED = 0
IN_GAME = 1
class Treepuncher(MinecraftClient):
in_game : bool
gamemode : Gamemode
dimension : Dimension
difficulty : Difficulty
hp : float
food : float
xp : float
lvl : int
total_xp : int
slot : int
# TODO inventory
position : BlockPos
# TODO world
# TODO player abilities
# walk_speed : float
# fly_speed : float
# flags : int
scheduler : AsyncIOScheduler
modules : List[LogicModule]
ctx : Dict[Any, Any]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ctx = dict()
self.in_game = False
self.gamemode = Gamemode.SURVIVAL
self.dimension = Dimension.OVERWORLD
self.difficulty = Difficulty.HARD
self.hp = 20.0
self.food = 20.0
self.xp = 0.0
self.lvl = 0
self.slot = 0
self.position = BlockPos(0, 0, 0)
self._register_handlers()
self.modules = []
self.scheduler = AsyncIOScheduler()
logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING) # So it's way less spammy
self.scheduler.start(paused=True)
async def start(self):
await super().start()
self.scheduler.resume()
for m in self.modules:
await m.initialize(self)
async def stop(self, force:bool=False):
self.scheduler.pause()
for m in self.modules:
await m.cleanup(self)
await super().stop(force=force)
def add(self, module:LogicModule):
module.register(self)
self.modules.append(module)
def on_chat(self, msg_type:Union[str, MessageType] = None):
if isinstance(msg_type, str):
msg_type = MessageType(msg_type)
def wrapper(fun):
async def process_chat_packet(packet:PacketChatMessage):
msg = ChatEvent(packet.message)
if not msg_type or msg.type == msg_type:
return await fun(msg)
return self.register(PacketChatMessage, process_chat_packet)
return wrapper
def on_death(self):
def wrapper(fun):
return self.register(TreepuncherEvents.DIED, fun)
return wrapper
def on_joined_world(self):
def wrapper(fun):
return self.register(TreepuncherEvents.IN_GAME, fun)
return wrapper
async def write(self, packet:Packet, wait:bool=False):
await self.dispatcher.write(packet, wait)
async def chat(self, message:str, wait:bool=False):
await self.dispatcher.write(
PacketChat(
self.dispatcher.proto,
message=message
),
wait=wait
)
def _register_handlers(self):
@self.on_disconnected()
async def on_disconnected():
self.in_game = False
@self.on_packet(PacketRespawn)
async def on_player_respawning(packet:PacketRespawn):
self.gamemode = Gamemode(packet.gamemode)
self.dimension = Dimension(packet.dimension)
self.difficulty = Difficulty(packet.difficulty)
if self.difficulty != Difficulty.PEACEFUL \
and self.gamemode != Gamemode.SPECTATOR:
self.in_game = True
else:
self.in_game = False
self._logger.info(
"Reloading world: %s (%s) in %s",
self.dimension.name,
self.difficulty.name,
self.gamemode.name
)
@self.on_packet(PacketLogin)
async def player_joining_cb(packet:PacketLogin):
self.gamemode = Gamemode(packet.gameMode)
self.dimension = Dimension(packet.dimension)
self.difficulty = Difficulty(packet.difficulty)
if self.difficulty != Difficulty.PEACEFUL \
and self.gamemode != Gamemode.SPECTATOR:
self.in_game = True
else:
self.in_game = False
self._logger.info(
"Joined world: %s (%s) in %s",
self.dimension.name,
self.difficulty.name,
self.gamemode.name
)
self.run_callbacks(TreepuncherEvents.IN_GAME)
@self.on_packet(PacketPosition)
async def player_rubberband_cb(packet:PacketPosition):
self._logger.info("Position synchronized")
self.position = BlockPos(packet.x, packet.y, packet.z)
await self.dispatcher.write(
PacketTeleportConfirm(
self.dispatcher.proto,
teleportId=packet.teleportId
)
)
@self.on_packet(PacketUpdateHealth)
async def player_hp_cb(packet:PacketUpdateHealth):
died = packet.health != self.hp and packet.health <= 0
self.hp = packet.health
self.food = packet.food + packet.foodSaturation
if died:
self.run_callbacks(TreepuncherEvents.DIED)
self._logger.info("Dead, respawning...")
await asyncio.sleep(0.5)
await self.dispatcher.write(
PacketClientCommand(self.dispatcher.proto, actionId=0) # respawn
)
@self.on_packet(PacketExperience)
async def player_xp_cb(packet:PacketExperience):
if packet.level != self.lvl:
self._logger.info("Level up : %d", packet.level)
self.xp = packet.experienceBar
self.lvl = packet.level
self.total_xp = packet.totalExperience