Gather sand and sleep at night

This commit is contained in:
Tanner Collin 2020-08-19 14:00:57 -06:00
parent ebc9c5ef1a
commit 49412e0253
3 changed files with 475 additions and 62 deletions

View File

@ -43,12 +43,6 @@ AVOID = [
NON_SOLID = [
'minecraft:air',
'minecraft:oak_sapling',
'minecraft:spruce_sapling',
'minecraft:birch_sapling',
'minecraft:jungle_sapling',
'minecraft:acacia_sapling',
'minecraft:dark_oak_sapling',
'minecraft:powered_rail',
'minecraft:detector_rail',
'minecraft:grass',

493
bot.py
View File

@ -13,6 +13,7 @@ from minecraft import authentication
from minecraft.exceptions import YggdrasilError
from minecraft.networking.connection import Connection
from minecraft.networking.packets import clientbound, serverbound
from minecraft.networking.types import BlockFace
from minecraft.compat import input
from minecraft.managers import chunks, ChunksManager
@ -37,6 +38,7 @@ BLOCK_ABOVE2 = (0, +2, 0)
BLOCK_ABOVE3 = (0, +3, 0)
BLOCK_ABOVE4 = (0, +4, 0)
BLOCK_BELOW = (0, -1, 0)
BLOCK_BELOW2 = (0, -2, 0)
TRAVERSE_NORTH = (0, 0, -1)
TRAVERSE_SOUTH = (0, 0, +1)
@ -152,6 +154,12 @@ def pmul(p, s):
def phyp(p1, p2):
return hypot(p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2])
def phyp_bias(p1, p2, origin):
origin_distance = phyp(origin, p2)
height_diff = p2[1] - p1[1]
height_diff = height_diff*8 if height_diff < 0 else height_diff*0.5
return hypot(p1[0] - p2[0], height_diff, p1[2] - p2[2]) + origin_distance*1.5
def pint(p):
return (floor(p[0]), floor(p[1]), floor(p[2]))
@ -380,11 +388,39 @@ def break_block(connection, coords, time):
s['break_time'] = time
s['break_timeout'] = 0.25
def place_block(connection, pos, face):
packet = serverbound.play.PlayerBlockPlacementPacket()
packet.hand = 0
packet.location = pos
packet.face = face
packet.x = 0.5
packet.y = 0.5
packet.z = 0.5
packet.inside_block = False
connection.write_packet(packet)
def say(connection, message):
packet = serverbound.play.ChatPacket()
packet.message = message
connection.write_packet(packet)
def pick(connection, slot):
packet = custom_packets.PickItemPacket()
packet.slot_to_use = slot
connection.write_packet(packet)
def hold(connection, slot):
packet = custom_packets.HeldItemChangePacket()
packet.slot = slot
connection.write_packet(packet)
def choose_slot(connection, slot):
if slot >= 36:
slot -= 36
hold(connection, slot)
else:
pick(connection, slot)
BLOCK_ABOVE = (0, +1, 0)
BLOCK_BELOW = (0, -1, 0)
@ -459,32 +495,90 @@ class MCWorld:
maze_solver = MazeSolver(self.chunks)
result = []
# TODO: make sure only non-solid and leaves between
# make sure traversable too
for distance in range(5):
for direction in CHECK_DIRECTIONS:
offset = (0, 0, 0)
for _ in range(distance):
offset = padd(offset, direction)
offset = pmul(direction, distance+1)
if maze_solver.check_traverse(tree, offset):
result.append(padd(tree, offset))
return result
def path_to_opening(self, start, opening):
def path_to_place(self, start, place):
maze_solver = MazeSolver(self.chunks)
try:
s = maze_solver.astar(start, opening)
s = maze_solver.astar(start, place)
return list(s) if s else None
except AStarTimeout:
return None
def path_to_base(self, start, base):
maze_solver = MazeSolver(self.chunks)
def find_bed_areas(self, center, distance):
air = []
for i in range(5):
check = padd(center, alternate(i, 1))
air.extend(self.find_blocks(check, distance, [0], 200))
try:
s = maze_solver.astar(start, base)
return list(s) if s else None
except AStarTimeout:
return None
areas = []
for a in air:
# check for ground around the area
if len(self.find_blocks(padd(a, BLOCK_BELOW), 2, blocks.NON_SOLID_IDS, 9)):
continue
# check for air around the area
if len(self.find_blocks(a, 2, [0], 9)) < 9:
continue
# check for air above the area
if len(self.find_blocks(padd(a, BLOCK_ABOVE), 2, [0], 9)) < 9:
continue
areas.append(a)
areas.sort(key=lambda x: phyp(center, x))
return areas
def sand_adjacent_safe(self, sand):
for direction in CHECK_DIRECTIONS:
if self.block_at(*padd(sand, direction)) in blocks.AVOID_IDS:
return False
return True
def find_sand(self, center, distance, origin):
sand = []
for i in range(10):
check = padd(center, alternate(i, 1))
sand.extend(self.find_blocks(check, distance, [66], 20))
safe_sand = []
for s in sand:
# make sure it has solid below
if self.block_at(*padd(s, BLOCK_BELOW)) in blocks.NON_SOLID_IDS:
continue
# make sure it has solid two below - prevent hanging sand
if self.block_at(*padd(s, BLOCK_BELOW2)) in blocks.NON_SOLID_IDS:
continue
# and walkable air above
if self.block_at(*padd(s, BLOCK_ABOVE)) not in blocks.NON_SOLID_IDS:
continue
if not self.sand_adjacent_safe(s):
continue
safe_sand.append(s)
safe_sand.sort(key=lambda x: phyp_bias(center, x, origin))
return safe_sand
def find_bed_openings(self, area):
# returns coords in a cardinal direction where we can stand by bed
result = []
for direction in CHECK_DIRECTIONS:
result.append(padd(area, direction))
return result
class LumberjackStates:
@ -512,24 +606,31 @@ class LumberjackStates:
trees.pop(0)
self.tree = trees[0]
openings = w.find_tree_openings(self.tree)
self.openings = w.find_tree_openings(self.tree)
self.state = self.choose_opening
for o in openings:
path = w.path_to_opening(p, o)
self.opening = o
if path: break
else: # for
def choose_opening(self):
w = MCWorld(self.player_info.chunks)
p = pint(self.player_info.pos)
print('openings:', self.openings)
if not len(self.openings):
print('Unable to get to tree', self.tree)
self.bad_trees.append(self.tree)
self.state = self.done
self.state = self.cleanup
return
path = w.path_to_place(p, self.openings[0])
if path:
s['path'] = path
self.state = self.going_to_tree
else:
self.openings.pop(0)
def going_to_tree(self):
d = self.player_info.pos - LPoint3f(*self.opening)
if d.length() < 1:
if pint(self.player_info.pos) == self.openings[0]:
s['look_at'] = self.tree
self.state = self.clear_leaves
@ -542,13 +643,15 @@ class LumberjackStates:
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 check == self.tree:
break
if not self.bair(check):
print('Breaking leaf')
s['break'] = (check, 0.5)
return
self.state = self.clear_trunk_base
def clear_trunk_base(self):
if not s['break_timeout']:
base = self.tree
@ -563,14 +666,17 @@ class LumberjackStates:
else:
w = MCWorld(self.player_info.chunks)
p = pint(self.player_info.pos)
path = w.path_to_base(p, self.tree)
path = w.path_to_place(p, self.tree)
if path:
s['path'] = path
self.state = self.going_to_trunk_base
else:
self.openings.pop(0)
self.state = self.choose_opening
def going_to_trunk_base(self):
d = self.player_info.pos - LPoint3f(*self.opening)
if d.length() < 1:
if pint(self.player_info.pos) == self.tree:
s['look_at'] = padd(self.tree, BLOCK_ABOVE2)
self.state = self.clear_trunk
@ -589,7 +695,6 @@ class LumberjackStates:
else:
print('Finished clearing tree')
self.wait_time = 0.5
s['look_at'] = None
self.state = self.wait
def wait(self):
@ -597,9 +702,14 @@ class LumberjackStates:
if self.wait_time > 0:
self.wait_time -= TICK
else:
self.state = self.cleanup
def cleanup(self):
s['look_at'] = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
@ -608,7 +718,7 @@ class LumberjackStates:
self.state = self.idle
self.tree = None
self.opening = None
self.openings = []
self.bad_trees = []
self.wait_time = 0
@ -616,32 +726,283 @@ class LumberjackStates:
self.state()
class GatherSandStates:
def bair(self, p):
return self.player_info.chunks.get_block_at(*p) in blocks.NON_SOLID_IDS
def bsand(self, p):
return self.player_info.chunks.get_block_at(*p) == 66
def idle(self):
return None
def init(self):
self.state = self.find_new_sand
def find_new_sand(self):
print('Finding new sand...')
w = MCWorld(self.player_info.chunks)
p = pint(self.player_info.pos)
sand = w.find_sand(p, 150, self.origin)
print('Found sand:', sand)
while sand[0] in self.bad_sand:
sand.pop(0)
self.sand = sand[0]
self.state = self.nav_to_sand
def nav_to_sand(self):
w = MCWorld(self.player_info.chunks)
p = pint(self.player_info.pos)
w.chunks.set_block_at(*self.sand, 0)
path = w.path_to_place(p, self.sand)
w.chunks.set_block_at(*self.sand, 66)
if path:
s['path'] = path[:-1]
self.state = self.going_to_sand
else:
self.bad_sand.append(self.sand)
self.state = self.find_new_sand
def going_to_sand(self):
if not len(s['path']):
s['look_at'] = self.sand
self.state = self.dig_sand
def dig_sand(self):
if not s['break_timeout']:
if self.bsand(self.sand):
s['break'] = (self.sand, 0.75)
print('digging sand')
else:
self.state = self.get_sand
def get_sand(self):
w = MCWorld(self.player_info.chunks)
p = pint(self.player_info.pos)
path = w.path_to_place(p, self.sand)
if path:
s['path'] = path
self.state = self.going_to_item
else:
self.bad_sand.append(self.sand)
self.state = self.find_new_sand
def going_to_item(self):
if pint(self.player_info.pos) == self.sand:
s['look_at'] = self.sand
self.state = self.cleanup
def cleanup(self):
s['look_at'] = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, player_info):
self.player_info = player_info
self.state = self.idle
self.origin = pint(self.player_info.pos)
self.sand = None
self.bad_sand = []
self.wait_time = 0
def run(self):
self.state()
class SleepWithBedStates:
def idle(self):
return None
def init(self):
if s['time'] >= 12000:
self.state = self.find_bed_spot
else:
print('Aborting sleep, not night')
self.state = self.cleanup
def find_bed_spot(self):
print('Finding a bed spot...')
w = MCWorld(self.player_info.chunks)
p = pint(self.player_info.pos)
areas = w.find_bed_areas(p, 100)
print('Found areas:', areas)
if len(areas):
while areas[0] in self.bad_areas:
areas.pop(0)
self.area = areas[0]
elif self.last_area:
self.area = self.last_area
else:
print('Unable to find area, and no last area')
self.state = self.cleanup
return
openings = w.find_bed_openings(self.area)
for o in openings:
path = w.path_to_place(p, o)
self.opening = o
if path: break
else: # for
print('Unable to get to bed area', self.area)
self.bad_areas.append(self.area)
self.state = self.cleanup
return
s['path'] = path
self.state = self.going_to_area
self.last_area = self.area
def going_to_area(self):
if pint(self.player_info.pos) == self.opening:
s['look_at'] = self.area
self.state = self.select_bed
def select_bed(self):
for slot, item in self.player_info.inv.items():
if item.item_id in items.BED_IDS:
print('Found bed in slot', slot)
s['look_at'] = padd(self.area, BLOCK_BELOW)
choose_slot(self.connection, slot)
self.state = self.place_bed
break
else: # for
say(self.connection, 'I need a bed')
self.state = self.cleanup
def place_bed(self):
place_block(self.connection, self.area, BlockFace.TOP)
self.state = self.use_bed
def use_bed(self):
if s['time'] >= 12542:
print('Sleeping')
place_block(self.connection, self.area, BlockFace.TOP)
say(self.connection, 'zzz')
self.state = self.sleep_bed
def sleep_bed(self):
if s['time'] < 100:
print('Woke up')
self.state = self.break_bed
def break_bed(self):
s['break'] = (self.area, 0.4)
self.state = self.collect_bed
def collect_bed(self):
if not s['break_timeout']:
s['path'] = [padd(self.area, spiral(n)) for n in range(9)]
self.wait_time = 4
self.state = self.wait
def wait(self):
# wait to pick up bed
if self.wait_time > 0:
self.wait_time -= TICK
else:
self.state = self.cleanup
def cleanup(self):
s['look_at'] = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, player_info, connection):
self.player_info = player_info
self.connection = connection
self.state = self.idle
self.area = None
self.opening = None
self.bad_areas = []
self.last_area = None
self.wait_time = 0
def run(self):
self.state()
class JobStates:
def idle(self):
return None
def night_shelter(self):
return None
def sleep_with_bed(self):
s = self.sleep_with_bed_states
if s.state == s.idle:
s.state = s.init
elif s.state == s.done:
s.state = s.init
# check time, etc
if self.prev_state:
print('Reverting to prev state')
self.state = self.prev_state
return
s.run()
def gather_sand(self):
s = self.gather_sand_states
if s.state == s.idle:
s.state = s.init
elif s.state == s.done:
s.state = s.init
# check time, etc
if self.survive:
self.prev_state = self.gather_sand
self.state = self.sleep_with_bed
return
s.run()
def lumberjack(self):
l = self.lumberjack_states
if l.state == l.idle:
l.state = l.init
elif l.state == l.done:
s = self.lumberjack_states
if s.state == s.idle:
s.state = s.init
elif s.state == s.done:
s.state = s.init
# check time, etc
l.state = l.init
l.run()
if self.survive:
self.prev_state = self.lumberjack
self.state = self.sleep_with_bed
return
s.run()
def stop(self):
self.lumberjack_states = LumberjackStates(self.player_info)
self.gather_sand_states = GatherSandStates(self.player_info)
self.sleep_with_bed_states = SleepWithBedStates(self.player_info, self.connection)
self.state = self.idle
def __init__(self, connection, player_info):
# TODO: watch dog if it gets stuck
self.connection = connection
self.player_info = player_info
self.state = self.idle
self.prev_state = None
self.lumberjack_states = LumberjackStates(self.player_info)
self.gather_sand_states = GatherSandStates(self.player_info)
self.sleep_with_bed_states = SleepWithBedStates(self.player_info, self.connection)
self.survive = False
def run(self):
@ -744,15 +1105,18 @@ def tick(connection, player_info):
look_at_d = p - look_at
if look_at_d.length() > 0.6:
target_yaw = look_at_d.normalized().signedAngleDeg(other=YAW_ANGLE_DIR, ref=YAW_ANGLE_REF)
target_yaw_d = target_yaw - s['yaw']
target_yaw_d = (target_yaw_d + 180) % 360 - 180
s['yaw'] += cap(target_yaw_d, 30)
target_pitch = look_at_d.normalized().angleDeg(PITCH_ANGLE_DIR)
target_pitch = (target_pitch - 90) * -1
target_pitch_d = target_pitch - s['pitch']
s['pitch'] += cap(target_pitch_d, 10)
# remove vertical component for yaw calculation
look_at_d.y = 0
target_yaw = look_at_d.normalized().signedAngleDeg(other=YAW_ANGLE_DIR, ref=YAW_ANGLE_REF)
target_yaw_d = target_yaw - s['yaw']
target_yaw_d = (target_yaw_d + 180) % 360 - 180
s['yaw'] += cap(target_yaw_d, 30)
else:
target_pitch_d = 0 - s['pitch']
s['pitch'] += cap(target_pitch_d, 10)
@ -788,6 +1152,8 @@ def tick(connection, player_info):
def init(connection, player_info):
p = player_info.pos
s['time'] = 0
s['path'] = []
s['look_at'] = None
s['y_v'] = 0
@ -803,6 +1169,7 @@ def init(connection, player_info):
s['jobstate'] = JobStates(connection, player_info)
s['jobstate'].run()
def main(connection, player_info):
def handle_join_game(join_game_packet):
print('Connected.')
@ -850,11 +1217,11 @@ def main(connection, player_info):
connection.register_packet_listener(
x, clientbound.play.BlockChangePacket)
#def y(p):
# print(p)
def handle_time_update(p):
s['time'] = p.time_of_day % 24000
#connection.register_packet_listener(
# y, AcknowledgePlayerDiggingPacket)
connection.register_packet_listener(
handle_time_update, custom_packets.TimeUpdatePacket)
def handle_set_slot(p):
print(p)
@ -872,6 +1239,9 @@ def main(connection, player_info):
if '!reload' in chat_packet.json_data:
global running
running = False
elif '!misc' in chat_packet.json_data:
for i in range(9):
print(i, spiral(i))
elif '!afk' in chat_packet.json_data:
say(connection, '/afk')
elif '!respawn' in chat_packet.json_data:
@ -919,11 +1289,29 @@ def main(connection, player_info):
for i in player_info.inv.values():
if i.present:
print(items.ITEM_NAMES[i.item_id], 'x', i.item_count)
elif '!spot' in chat_packet.json_data:
mc_world = MCWorld(player_info.chunks)
start = time.time()
coords = mc_world.find_bed_areas(pint(player_info.pos), 100)
print(coords)
print(len(coords))
print(round(time.time() - start, 3), 'seconds')
elif '!echo' in chat_packet.json_data:
parts = chat_packet.json_data.split('\'')
say(connection, parts[1])
elif '!sleep' in chat_packet.json_data:
#for i in player_info.inv.values():
# if i.item_id in items.BED_IDS:
# break
#else: # for
# say(connection, 'I need a bed')
# return
s['jobstate'].state = s['jobstate'].sleep_with_bed
s['jobstate'].sleep_with_bed_states.state = s['jobstate'].sleep_with_bed_states.find_bed_spot
elif 'get wood and survive' in chat_packet.json_data:
for i in player_info.inv.values():
print(i.item_id)
if i.item_id in items.BED_IDS:
break
else: # for
@ -932,8 +1320,21 @@ def main(connection, player_info):
s['jobstate'].state = s['jobstate'].lumberjack
s['jobstate'].survive = True
elif 'get sand and survive' in chat_packet.json_data:
for i in player_info.inv.values():
print(i.item_id)
if i.item_id in items.BED_IDS:
break
else: # for
say(connection, 'I need a bed')
return
s['jobstate'].state = s['jobstate'].gather_sand
s['jobstate'].survive = True
elif 'get wood' in chat_packet.json_data:
s['jobstate'].state = s['jobstate'].lumberjack
elif 'get sand' in chat_packet.json_data:
s['jobstate'].state = s['jobstate'].gather_sand
elif 'stop job' in chat_packet.json_data:
say(connection, 'ok')
s['jobstate'].state = s['jobstate'].stop

View File

@ -1,7 +1,7 @@
import minecraft.networking.packets
from minecraft.networking.packets import Packet
from minecraft.networking.types import BlockFace, VarInt, Position, Boolean, Byte, UnsignedByte, Short, TrailingByteArray
from minecraft.networking.types import BlockFace, VarInt, Position, Boolean, Byte, UnsignedByte, Short, TrailingByteArray, Long
from minecraft.networking.types.basic import Type
#def qot(x):
@ -15,9 +15,9 @@ class AcknowledgePlayerDiggingPacket(Packet):
id = 0x08
packet_name = 'acknowledge player digging'
definition = [
{'status': VarInt},
{'location': Position},
{'face': VarInt},
{'block': VarInt},
{'status': VarInt},
{'successful': Boolean},
]
@ -72,8 +72,6 @@ class Slot(Type):
# TODO
pass
class SetSlotPacket(Packet):
id = 0x17
packet_name = 'set slot'
@ -83,6 +81,14 @@ class SetSlotPacket(Packet):
{'slot_data': Slot},
]
class TimeUpdatePacket(Packet):
id = 0x4F
packet_name = 'time update'
definition = [
{'world_age': Long},
{'time_of_day': Long},
]
@ -93,6 +99,7 @@ def get_packets(old_get_packets):
packets.add(AcknowledgePlayerDiggingPacket)
packets.add(BlockBreakAnimationPacket)
packets.add(SetSlotPacket)
packets.add(TimeUpdatePacket)
return packets
return lambda x: wrapper(old_get_packets, x)
@ -129,3 +136,14 @@ class PickItemPacket(Packet):
definition = [
{'slot_to_use': VarInt},
]
class HeldItemChangePacket(Packet):
# Sent when the player changes the slot selection
# https://wiki.vg/Protocol#Held_Item_Change_.28serverbound.29
id = 0x23
packet_name = 'held item change'
definition = [
{'slot': Short},
]