From 4e2efeb604e0eb9c065a8e22aedb27037db74eb9 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 23 May 2020 01:06:06 -0600 Subject: [PATCH] Add lumberjack state machine that chops down a tree --- bot.py | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 315 insertions(+), 8 deletions(-) diff --git a/bot.py b/bot.py index 2d62a94..fb305bc 100644 --- a/bot.py +++ b/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(