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 blocks
|
||||||
|
|
||||||
|
import minecraft
|
||||||
from minecraft import authentication
|
from minecraft import authentication
|
||||||
from minecraft.exceptions import YggdrasilError
|
from minecraft.exceptions import YggdrasilError
|
||||||
from minecraft.networking.connection import Connection
|
from minecraft.networking.connection import Connection
|
||||||
from minecraft.networking.packets import Packet, clientbound, serverbound
|
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.compat import input
|
||||||
from minecraft.managers import ChunksManager
|
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):
|
class AStarTimeout(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -142,6 +214,9 @@ HYPOT_LUT = {
|
||||||
def padd(p1, p2):
|
def padd(p1, p2):
|
||||||
return (p1[0] + p2[0], p1[1] + p2[1], p1[2] + p2[2])
|
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):
|
def pint(p):
|
||||||
return (int(p[0]), int(p[1]), int(p[2]))
|
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
|
sign = 1 if n % 2 else -1
|
||||||
return (0, ceil(n/2) * sign * amount, 0)
|
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_ABOVE = (0, +1, 0)
|
||||||
BLOCK_BELOW = (0, -1, 0)
|
BLOCK_BELOW = (0, -1, 0)
|
||||||
|
|
||||||
|
@ -391,11 +488,13 @@ class MCWorld:
|
||||||
|
|
||||||
for log in logs:
|
for log in logs:
|
||||||
# crawl to the top log
|
# crawl to the top log
|
||||||
|
log_count = 1
|
||||||
while self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LOG_IDS:
|
while self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LOG_IDS:
|
||||||
log = padd(log, BLOCK_ABOVE)
|
log = padd(log, BLOCK_ABOVE)
|
||||||
|
log_count += 1
|
||||||
|
|
||||||
# make sure it's a tree
|
# make sure it's a good tree
|
||||||
if self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LEAF_IDS:
|
if self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LEAF_IDS and log_count > 2:
|
||||||
break
|
break
|
||||||
else: # for
|
else: # for
|
||||||
return None
|
return None
|
||||||
|
@ -420,14 +519,155 @@ class MCWorld:
|
||||||
result.append(padd(tree, offset))
|
result.append(padd(tree, offset))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def navigate_to_opening(self, start, opening):
|
def path_to_opening(self, start, opening):
|
||||||
maze_solver = MazeSolver(self.chunks)
|
maze_solver = MazeSolver(self.chunks)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return list(maze_solver.astar(start, opening))
|
s = maze_solver.astar(start, opening)
|
||||||
|
return list(s) if s else None
|
||||||
except AStarTimeout:
|
except AStarTimeout:
|
||||||
return None
|
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
|
TICK = 0.05
|
||||||
|
@ -454,6 +694,8 @@ def cap(x, amount):
|
||||||
|
|
||||||
|
|
||||||
def tick(connection, player_info):
|
def tick(connection, player_info):
|
||||||
|
s['jobstate'].run()
|
||||||
|
|
||||||
target = None
|
target = None
|
||||||
|
|
||||||
p = player_info.pos
|
p = player_info.pos
|
||||||
|
@ -494,12 +736,15 @@ def tick(connection, player_info):
|
||||||
else:
|
else:
|
||||||
s['y_a'] = -36.0
|
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])
|
look_at = LPoint3f(s['path'][YAW_LOOK_AHEAD])
|
||||||
elif len(s['path']):
|
elif len(s['path']):
|
||||||
look_at = LPoint3f(s['path'][-1])
|
look_at = LPoint3f(s['path'][-1])
|
||||||
|
else:
|
||||||
|
look_at = None
|
||||||
|
|
||||||
if look_at:
|
if look_at:
|
||||||
look_at.x += 0.5
|
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)
|
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)
|
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):
|
def init(connection, player_info):
|
||||||
p = player_info.pos
|
p = player_info.pos
|
||||||
|
|
||||||
s['path'] = []
|
s['path'] = []
|
||||||
|
s['look_at'] = None
|
||||||
s['y_v'] = 0
|
s['y_v'] = 0
|
||||||
s['y_a'] = 0
|
s['y_a'] = 0
|
||||||
s['yaw'] = 360
|
s['yaw'] = 360
|
||||||
s['pitch'] = 0
|
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 main(connection, player_info):
|
||||||
def handle_join_game(join_game_packet):
|
def handle_join_game(join_game_packet):
|
||||||
print('Connected.')
|
print('Connected.')
|
||||||
|
@ -556,7 +826,7 @@ def main(connection, player_info):
|
||||||
solution = MazeSolver(player_info.chunks).astar(pint(player_info.pos), pint(s['goal']))
|
solution = MazeSolver(player_info.chunks).astar(pint(player_info.pos), pint(s['goal']))
|
||||||
if solution:
|
if solution:
|
||||||
solution = list(solution)
|
solution = list(solution)
|
||||||
#s['path'] = solution
|
s['path'] = solution
|
||||||
print(len(solution))
|
print(len(solution))
|
||||||
print(round(time.time() - start, 3), 'seconds')
|
print(round(time.time() - start, 3), 'seconds')
|
||||||
else:
|
else:
|
||||||
|
@ -573,6 +843,18 @@ def main(connection, player_info):
|
||||||
connection.register_packet_listener(
|
connection.register_packet_listener(
|
||||||
x, clientbound.play.BlockChangePacket)
|
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):
|
def print_chat(chat_packet):
|
||||||
print("Message (%s): %s" % (
|
print("Message (%s): %s" % (
|
||||||
chat_packet.field_string('position'), chat_packet.json_data))
|
chat_packet.field_string('position'), chat_packet.json_data))
|
||||||
|
@ -603,7 +885,7 @@ def main(connection, player_info):
|
||||||
start = time.time()
|
start = time.time()
|
||||||
solution = MazeSolver(player_info.chunks).astar(pint(player_info.pos), pint(s['goal']))
|
solution = MazeSolver(player_info.chunks).astar(pint(player_info.pos), pint(s['goal']))
|
||||||
solution = list(solution)
|
solution = list(solution)
|
||||||
#s['path'] = solution
|
s['path'] = solution
|
||||||
print(len(solution))
|
print(len(solution))
|
||||||
print(round(time.time() - start, 3), 'seconds')
|
print(round(time.time() - start, 3), 'seconds')
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
|
@ -623,6 +905,31 @@ def main(connection, player_info):
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
import traceback
|
import traceback
|
||||||
print(traceback.format_exc())
|
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(
|
connection.register_packet_listener(
|
||||||
|
|
Loading…
Reference in New Issue
Block a user