Add lumberjack state machine that chops down a tree

This commit is contained in:
Tanner Collin 2020-05-23 01:06:06 -06:00
parent b0c27a98c1
commit 4e2efeb604

323
bot.py
View File

@ -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(