Add lumberjack state machine that chops down a tree
This commit is contained in:
parent
b0c27a98c1
commit
4e2efeb604
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(
|
||||
|
|
Loading…
Reference in New Issue
Block a user