Add lumberjack state machine that chops down a tree
This commit is contained in:
		
							
								
								
									
										323
									
								
								bot.py
									
									
									
									
									
								
							
							
						
						
									
										323
									
								
								bot.py
									
									
									
									
									
								
							@@ -7,13 +7,85 @@ from itertools import count
 | 
			
		||||
 | 
			
		||||
import blocks
 | 
			
		||||
 | 
			
		||||
import minecraft
 | 
			
		||||
from minecraft import authentication
 | 
			
		||||
from minecraft.exceptions import YggdrasilError
 | 
			
		||||
from minecraft.networking.connection import Connection
 | 
			
		||||
from minecraft.networking.packets import Packet, clientbound, serverbound
 | 
			
		||||
from minecraft.networking.types import BlockFace, VarInt, Position, Boolean, Byte
 | 
			
		||||
from minecraft.compat import input
 | 
			
		||||
from minecraft.managers import ChunksManager
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#class AcknowledgePlayerDiggingPacket(Packet):
 | 
			
		||||
#    @staticmethod
 | 
			
		||||
#    def get_id(context):
 | 
			
		||||
#        return 0x08
 | 
			
		||||
#
 | 
			
		||||
#    packet_name = 'acknowledge player digging'
 | 
			
		||||
#    definition = [
 | 
			
		||||
#        {'status': VarInt},
 | 
			
		||||
#        {'location': Position},
 | 
			
		||||
#        {'face': VarInt},
 | 
			
		||||
#        {'successful': Boolean},
 | 
			
		||||
#    ]
 | 
			
		||||
#
 | 
			
		||||
#class BlockBreakAnimationPacket(Packet):
 | 
			
		||||
#    @staticmethod
 | 
			
		||||
#    def get_id(context):
 | 
			
		||||
#        return 0x09
 | 
			
		||||
#
 | 
			
		||||
#    packet_name = 'block break animation'
 | 
			
		||||
#    definition = [
 | 
			
		||||
#        {'entity_id': VarInt},
 | 
			
		||||
#        {'location': Position},
 | 
			
		||||
#        {'destroy_stage': Byte},
 | 
			
		||||
#    ]
 | 
			
		||||
#
 | 
			
		||||
#def get_packets(old_get_packets):
 | 
			
		||||
#    def wrapper(func, context):
 | 
			
		||||
#        packets = func(context)
 | 
			
		||||
#        packets.add(AcknowledgePlayerDiggingPacket)
 | 
			
		||||
#        packets.add(BlockBreakAnimationPacket)
 | 
			
		||||
#        print(packets)
 | 
			
		||||
#        return packets
 | 
			
		||||
#    return lambda x: wrapper(old_get_packets, x)
 | 
			
		||||
#
 | 
			
		||||
#minecraft.networking.packets.clientbound.play.get_packets = get_packets(minecraft.networking.packets.clientbound.play.get_packets)
 | 
			
		||||
#
 | 
			
		||||
#def qot(x):
 | 
			
		||||
#    print('qot.')
 | 
			
		||||
#    return set()
 | 
			
		||||
#
 | 
			
		||||
#minecraft.networking.packets.clientbound.play.get_packets = qot
 | 
			
		||||
 | 
			
		||||
class PlayerDiggingPacket(Packet):
 | 
			
		||||
    # used when player mines / breaks blocks
 | 
			
		||||
    # https://wiki.vg/Protocol#Player_Digging
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_id(context):
 | 
			
		||||
        return 0x1A
 | 
			
		||||
 | 
			
		||||
    packet_name = 'player digging'
 | 
			
		||||
 | 
			
		||||
    definition = [
 | 
			
		||||
        {'status': VarInt},
 | 
			
		||||
        {'location': Position},
 | 
			
		||||
        {'face': VarInt},
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    STARTED = 0
 | 
			
		||||
    CANCELLED = 1
 | 
			
		||||
    FINISHED = 2
 | 
			
		||||
 | 
			
		||||
    # PlayerBlockPlacementPacket.Face is an alias for BlockFace.
 | 
			
		||||
    Face = BlockFace
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AStarTimeout(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
@@ -142,6 +214,9 @@ HYPOT_LUT = {
 | 
			
		||||
def padd(p1, p2):
 | 
			
		||||
    return (p1[0] + p2[0], p1[1] + p2[1], p1[2] + p2[2])
 | 
			
		||||
 | 
			
		||||
def psub(p1, p2):
 | 
			
		||||
    return (p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2])
 | 
			
		||||
 | 
			
		||||
def pint(p):
 | 
			
		||||
    return (int(p[0]), int(p[1]), int(p[2]))
 | 
			
		||||
 | 
			
		||||
@@ -347,6 +422,28 @@ def alternate(n, amount):
 | 
			
		||||
    sign = 1 if n % 2 else -1
 | 
			
		||||
    return (0, ceil(n/2) * sign * amount, 0)
 | 
			
		||||
 | 
			
		||||
def diffrange(n):
 | 
			
		||||
    # same as range(n+1) but can go negative
 | 
			
		||||
    sign = 1 if n >= 0 else -1
 | 
			
		||||
    return range(0, n+sign, sign)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def break_block(connection, coords, time):
 | 
			
		||||
    packet = PlayerDiggingPacket()
 | 
			
		||||
    packet.status = 0
 | 
			
		||||
    packet.location = coords
 | 
			
		||||
    packet.face = 1
 | 
			
		||||
    connection.write_packet(packet)
 | 
			
		||||
 | 
			
		||||
    s['break_finished_packet'] = PlayerDiggingPacket()
 | 
			
		||||
    s['break_finished_packet'].status = 2
 | 
			
		||||
    s['break_finished_packet'].location = coords
 | 
			
		||||
    s['break_finished_packet'].face = 1
 | 
			
		||||
 | 
			
		||||
    s['break_time'] = time
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BLOCK_ABOVE = (0, +1, 0)
 | 
			
		||||
BLOCK_BELOW = (0, -1, 0)
 | 
			
		||||
 | 
			
		||||
@@ -391,11 +488,13 @@ class MCWorld:
 | 
			
		||||
 | 
			
		||||
        for log in logs:
 | 
			
		||||
            # crawl to the top log
 | 
			
		||||
            log_count = 1
 | 
			
		||||
            while self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LOG_IDS:
 | 
			
		||||
                log = padd(log, BLOCK_ABOVE)
 | 
			
		||||
                log_count += 1
 | 
			
		||||
 | 
			
		||||
            # make sure it's a tree
 | 
			
		||||
            if self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LEAF_IDS:
 | 
			
		||||
            # make sure it's a good tree
 | 
			
		||||
            if self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LEAF_IDS and log_count > 2:
 | 
			
		||||
                break
 | 
			
		||||
        else: # for
 | 
			
		||||
            return None
 | 
			
		||||
@@ -420,14 +519,155 @@ class MCWorld:
 | 
			
		||||
                    result.append(padd(tree, offset))
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def navigate_to_opening(self, start, opening):
 | 
			
		||||
    def path_to_opening(self, start, opening):
 | 
			
		||||
        maze_solver = MazeSolver(self.chunks)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            return list(maze_solver.astar(start, opening))
 | 
			
		||||
            s = maze_solver.astar(start, opening)
 | 
			
		||||
            return list(s) if s else None
 | 
			
		||||
        except AStarTimeout:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def path_to_base(self, start, base):
 | 
			
		||||
        maze_solver = MazeSolver(self.chunks)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            s = maze_solver.astar(start, base)
 | 
			
		||||
            return list(s) if s else None
 | 
			
		||||
        except AStarTimeout:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LumberjackStates:
 | 
			
		||||
    def bair(self, p):
 | 
			
		||||
        return self.player_info.chunks.get_block_at(*p) in blocks.NON_SOLID_IDS
 | 
			
		||||
 | 
			
		||||
    def blog(self, p):
 | 
			
		||||
        return self.player_info.chunks.get_block_at(*p) in blocks.LOG_IDS
 | 
			
		||||
 | 
			
		||||
    def idle(self):
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def find_new_tree(self):
 | 
			
		||||
        print('Finding new tree...')
 | 
			
		||||
        w = MCWorld(self.player_info.chunks)
 | 
			
		||||
        p = pint(self.player_info.pos)
 | 
			
		||||
 | 
			
		||||
        self.tree = w.find_tree(p, 100)
 | 
			
		||||
        print('Found tree at:', self.tree)
 | 
			
		||||
 | 
			
		||||
        openings = w.find_tree_openings(self.tree)
 | 
			
		||||
 | 
			
		||||
        for o in openings:
 | 
			
		||||
            path = w.path_to_opening(p, o)
 | 
			
		||||
            self.opening = o
 | 
			
		||||
            if path: break
 | 
			
		||||
        else: # for
 | 
			
		||||
            print('Unable to get to tree')
 | 
			
		||||
            self.state = self.finished
 | 
			
		||||
 | 
			
		||||
        s['path'] = path
 | 
			
		||||
        self.state = self.going_to_tree
 | 
			
		||||
 | 
			
		||||
    def going_to_tree(self):
 | 
			
		||||
        d = self.player_info.pos - LPoint3f(*self.opening)
 | 
			
		||||
        if d.length() < 1:
 | 
			
		||||
            s['look_at'] = LPoint3f(*self.tree)
 | 
			
		||||
            self.state = self.clear_leaves
 | 
			
		||||
 | 
			
		||||
    def clear_leaves(self):
 | 
			
		||||
        if not s['break_finished_packet']:
 | 
			
		||||
            p = pint(self.player_info.pos)
 | 
			
		||||
            diff = psub(self.tree, p)
 | 
			
		||||
 | 
			
		||||
            for x in diffrange(diff[0]):
 | 
			
		||||
                for z in diffrange(diff[2]):
 | 
			
		||||
                    for y in range(2):
 | 
			
		||||
                        check = padd(p, (x, y, z))
 | 
			
		||||
                        if self.blog(check):
 | 
			
		||||
                            self.state = self.clear_trunk_base
 | 
			
		||||
                            return
 | 
			
		||||
                        if not self.bair(check):
 | 
			
		||||
                            s['break'] = (check, 0.5)
 | 
			
		||||
                            return
 | 
			
		||||
 | 
			
		||||
    def clear_trunk_base(self):
 | 
			
		||||
        if not s['break_finished_packet']:
 | 
			
		||||
            base = self.tree
 | 
			
		||||
            above = padd(self.tree, BLOCK_ABOVE)
 | 
			
		||||
 | 
			
		||||
            if self.blog(base):
 | 
			
		||||
                s['break'] = (base, 2)
 | 
			
		||||
                return
 | 
			
		||||
            elif self.blog(above):
 | 
			
		||||
                s['break'] = (above, 2)
 | 
			
		||||
                return
 | 
			
		||||
            else:
 | 
			
		||||
                w = MCWorld(self.player_info.chunks)
 | 
			
		||||
                p = pint(self.player_info.pos)
 | 
			
		||||
                path = w.path_to_base(p, self.tree)
 | 
			
		||||
 | 
			
		||||
                s['path'] = path
 | 
			
		||||
                self.state = self.going_to_trunk_base
 | 
			
		||||
 | 
			
		||||
    def going_to_trunk_base(self):
 | 
			
		||||
        d = self.player_info.pos - LPoint3f(*self.opening)
 | 
			
		||||
        if d.length() < 1:
 | 
			
		||||
            s['pitch'] = -90
 | 
			
		||||
            self.state = self.clear_trunk
 | 
			
		||||
 | 
			
		||||
    def clear_trunk(self):
 | 
			
		||||
        if not s['break_finished_packet']:
 | 
			
		||||
            check = self.tree
 | 
			
		||||
 | 
			
		||||
            count = 0
 | 
			
		||||
            while self.bair(check) and count < 6:
 | 
			
		||||
                check = padd(check, BLOCK_ABOVE)
 | 
			
		||||
                count += 1
 | 
			
		||||
 | 
			
		||||
            if self.blog(check):
 | 
			
		||||
                s['break'] = (check, 2)
 | 
			
		||||
            else:
 | 
			
		||||
                print('Finished clearing tree')
 | 
			
		||||
                self.state = self.finished
 | 
			
		||||
 | 
			
		||||
    def finished(self):
 | 
			
		||||
        s['pitch'] = 0 # todo, calc with look_at
 | 
			
		||||
        s['look_at'] = None
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __init__(self, player_info):
 | 
			
		||||
        self.player_info = player_info
 | 
			
		||||
        self.state = self.idle
 | 
			
		||||
 | 
			
		||||
        self.tree = None
 | 
			
		||||
        self.opening = None
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        self.state()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JobStates:
 | 
			
		||||
    def idle(self):
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def night_shelter(self):
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def lumberjack(self):
 | 
			
		||||
        l = self.lumberjack_states
 | 
			
		||||
        if l.state == l.idle:
 | 
			
		||||
            l.state = l.find_new_tree
 | 
			
		||||
        l.run()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, player_info):
 | 
			
		||||
        self.player_info = player_info
 | 
			
		||||
        self.state = self.idle
 | 
			
		||||
        self.lumberjack_states = LumberjackStates(player_info)
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        self.state()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TICK = 0.05
 | 
			
		||||
@@ -454,6 +694,8 @@ def cap(x, amount):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def tick(connection, player_info):
 | 
			
		||||
    s['jobstate'].run()
 | 
			
		||||
 | 
			
		||||
    target = None
 | 
			
		||||
 | 
			
		||||
    p = player_info.pos
 | 
			
		||||
@@ -494,12 +736,15 @@ def tick(connection, player_info):
 | 
			
		||||
    else:
 | 
			
		||||
        s['y_a'] = -36.0
 | 
			
		||||
 | 
			
		||||
    look_at = None
 | 
			
		||||
 | 
			
		||||
    if len(s['path']) > YAW_LOOK_AHEAD:
 | 
			
		||||
    if s['look_at']:
 | 
			
		||||
        look_at = LPoint3f(s['look_at'])
 | 
			
		||||
    elif len(s['path']) > YAW_LOOK_AHEAD:
 | 
			
		||||
        look_at = LPoint3f(s['path'][YAW_LOOK_AHEAD])
 | 
			
		||||
    elif len(s['path']):
 | 
			
		||||
        look_at = LPoint3f(s['path'][-1])
 | 
			
		||||
    else:
 | 
			
		||||
        look_at = None
 | 
			
		||||
 | 
			
		||||
    if look_at:
 | 
			
		||||
        look_at.x += 0.5
 | 
			
		||||
@@ -517,15 +762,40 @@ def tick(connection, player_info):
 | 
			
		||||
    packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=s['pitch'], yaw=s['yaw'], on_ground=True)
 | 
			
		||||
    connection.write_packet(packet, force=True)
 | 
			
		||||
 | 
			
		||||
    if s['break']:
 | 
			
		||||
        break_block(connection, *s['break'])
 | 
			
		||||
        s['break'] = None
 | 
			
		||||
 | 
			
		||||
    if s['break_time'] > 0:
 | 
			
		||||
        packet = serverbound.play.AnimationPacket()
 | 
			
		||||
        packet.hand = packet.HAND_MAIN
 | 
			
		||||
        connection.write_packet(packet)
 | 
			
		||||
 | 
			
		||||
        s['break_time'] -= TICK
 | 
			
		||||
    elif s['break_finished_packet']:
 | 
			
		||||
        connection.write_packet(s['break_finished_packet'])
 | 
			
		||||
        s['break_finished_packet'] = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def init(connection, player_info):
 | 
			
		||||
    p = player_info.pos
 | 
			
		||||
 | 
			
		||||
    s['path'] = []
 | 
			
		||||
    s['look_at'] = None
 | 
			
		||||
    s['y_v'] = 0
 | 
			
		||||
    s['y_a'] = 0
 | 
			
		||||
    s['yaw'] = 360
 | 
			
		||||
    s['pitch'] = 0
 | 
			
		||||
 | 
			
		||||
    s['break'] = None
 | 
			
		||||
    s['break_time'] = 0
 | 
			
		||||
    s['break_finished_packet'] = None
 | 
			
		||||
 | 
			
		||||
    s['jobstate'] = JobStates(player_info)
 | 
			
		||||
    s['jobstate'].run()
 | 
			
		||||
 | 
			
		||||
def main(connection, player_info):
 | 
			
		||||
    def handle_join_game(join_game_packet):
 | 
			
		||||
        print('Connected.')
 | 
			
		||||
@@ -556,7 +826,7 @@ def main(connection, player_info):
 | 
			
		||||
                solution = MazeSolver(player_info.chunks).astar(pint(player_info.pos), pint(s['goal']))
 | 
			
		||||
                if solution:
 | 
			
		||||
                    solution = list(solution)
 | 
			
		||||
                    #s['path'] = solution
 | 
			
		||||
                    s['path'] = solution
 | 
			
		||||
                    print(len(solution))
 | 
			
		||||
                    print(round(time.time() - start, 3), 'seconds')
 | 
			
		||||
                else:
 | 
			
		||||
@@ -573,6 +843,18 @@ def main(connection, player_info):
 | 
			
		||||
    connection.register_packet_listener(
 | 
			
		||||
        x, clientbound.play.BlockChangePacket)
 | 
			
		||||
 | 
			
		||||
    #def y(p):
 | 
			
		||||
    #    print(p)
 | 
			
		||||
 | 
			
		||||
    #connection.register_packet_listener(
 | 
			
		||||
    #    y, AcknowledgePlayerDiggingPacket)
 | 
			
		||||
 | 
			
		||||
    #def z(p):
 | 
			
		||||
    #    print(p)
 | 
			
		||||
 | 
			
		||||
    #connection.register_packet_listener(
 | 
			
		||||
    #    z, BlockBreakAnimationPacket)
 | 
			
		||||
 | 
			
		||||
    def print_chat(chat_packet):
 | 
			
		||||
        print("Message (%s): %s" % (
 | 
			
		||||
            chat_packet.field_string('position'), chat_packet.json_data))
 | 
			
		||||
@@ -603,7 +885,7 @@ def main(connection, player_info):
 | 
			
		||||
                start = time.time()
 | 
			
		||||
                solution = MazeSolver(player_info.chunks).astar(pint(player_info.pos), pint(s['goal']))
 | 
			
		||||
                solution = list(solution)
 | 
			
		||||
                #s['path'] = solution
 | 
			
		||||
                s['path'] = solution
 | 
			
		||||
                print(len(solution))
 | 
			
		||||
                print(round(time.time() - start, 3), 'seconds')
 | 
			
		||||
            except BaseException as e:
 | 
			
		||||
@@ -623,6 +905,31 @@ def main(connection, player_info):
 | 
			
		||||
            except BaseException as e:
 | 
			
		||||
                import traceback
 | 
			
		||||
                print(traceback.format_exc())
 | 
			
		||||
        elif '!break' in chat_packet.json_data:
 | 
			
		||||
            try:
 | 
			
		||||
                coords = pint(player_info.pos)
 | 
			
		||||
                coords = padd(coords, CHECK_NORTH)
 | 
			
		||||
 | 
			
		||||
                break_block(connection, coords, 2.5)
 | 
			
		||||
                #break_block(connection, coords, 0.35)
 | 
			
		||||
            except BaseException as e:
 | 
			
		||||
                import traceback
 | 
			
		||||
                print(traceback.format_exc())
 | 
			
		||||
        elif '!echo' in chat_packet.json_data:
 | 
			
		||||
            try:
 | 
			
		||||
                parts = chat_packet.json_data.split('\'')
 | 
			
		||||
                packet = serverbound.play.ChatPacket()
 | 
			
		||||
                packet.message = parts[1]
 | 
			
		||||
                connection.write_packet(packet)
 | 
			
		||||
            except BaseException as e:
 | 
			
		||||
                import traceback
 | 
			
		||||
                print(traceback.format_exc())
 | 
			
		||||
        elif 'get wood' in chat_packet.json_data:
 | 
			
		||||
            print('setting job state to lumberjack')
 | 
			
		||||
            s['jobstate'].state = s['jobstate'].lumberjack
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
    connection.register_packet_listener(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user