From 49412e02530dda0d4afd90906c91037ab9b8a3da Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Wed, 19 Aug 2020 14:00:57 -0600 Subject: [PATCH] Gather sand and sleep at night --- blocks.py | 6 - bot.py | 503 +++++++++++++++++++++++++++++++++++++++++----- custom_packets.py | 28 ++- 3 files changed, 475 insertions(+), 62 deletions(-) diff --git a/blocks.py b/blocks.py index 9f2d953..88adbaa 100644 --- a/blocks.py +++ b/blocks.py @@ -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', diff --git a/bot.py b/bot.py index dff7960..2a23372 100644 --- a/bot.py +++ b/bot.py @@ -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 - s['path'] = path - self.state = self.going_to_tree + 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) - s['path'] = path - self.state = self.going_to_trunk_base + 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.done + 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 diff --git a/custom_packets.py b/custom_packets.py index f9ab829..498d79e 100644 --- a/custom_packets.py +++ b/custom_packets.py @@ -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}, + ]