From e0c5c3a6b986625a23a6dd019f8b6b7e013ae9fb Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 5 Dec 2020 10:56:56 +0000 Subject: [PATCH] Add job for filling in a volume of blocks --- blocks.py | 1 + bot.py | 2 + game.py | 54 +++++++++-- jobs.py | 212 ++++++++++++++++++++++++++++++++++++++++++++ main.py | 1 + monkey_patch.py | 3 +- protocol/packets.py | 18 +++- utils.py | 10 +++ 8 files changed, 294 insertions(+), 7 deletions(-) diff --git a/blocks.py b/blocks.py index b345436..99a41f5 100644 --- a/blocks.py +++ b/blocks.py @@ -19,6 +19,7 @@ for name, data in JSON_BLOCKS.items(): BREAK_DISTANCE = 6 AIR = 0 +STONE = 1 SAND = 66 SINGLE_SNOW = 3921 SOUL_TORCH = 4008 diff --git a/bot.py b/bot.py index 02e6ec0..9a8d4c3 100644 --- a/bot.py +++ b/bot.py @@ -205,6 +205,8 @@ def init(global_state): g.queue_afk = False + g.filling = False + def bot(global_state): g = global_state diff --git a/game.py b/game.py index 612c461..fdb7f1e 100644 --- a/game.py +++ b/game.py @@ -20,7 +20,7 @@ from protocol.packets import ( ClickWindowPacket, CloseWindowPacket, ServerWindowConfirmationPacket, ClientWindowConfirmationPacket, EntityMetadataPacket, SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket, - EntityActionPacket, + EntityActionPacket, SpawnPlayerPacket, ) from protocol.types import Slot @@ -319,6 +319,7 @@ class Game: register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket) register(self.handle_entity_position_rotation, EntityPositionRotationPacket) register(self.handle_destroy_entities, DestroyEntitiesPacket) + register(self.handle_spawn_player, SpawnPlayerPacket) #register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket) #register(self.handle_packet, Packet, early=True) @@ -356,6 +357,8 @@ class Game: import traceback print(traceback.format_exc()) + #print(packet) + def handle_position_and_look(self, packet): print(packet) p = LPoint3f(x=packet.x, y=packet.y, z=packet.z) @@ -612,6 +615,33 @@ class Game: data = data.replace('^', '.') reply = str(eval(data)) + if command == 'fill': + try: + data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ') + x1, y1, z1, x2, y2, z2 = [int(x) for x in data.split()] + except (AttributeError, ValueError): + reply = 'usage: !fill x1 y1 z1 x2 y2 z2' + + if not reply: + coord1 = (x1, y1, z1) + coord2 = (x2, y2, z2) + block = self.g.world.block_at(*coord1) + + if not reply and y1 > y2: + reply = 'can only fill upwards' + + if not reply and block is None: + reply = 'first coord out of range' + + if not reply and block == 0: + reply = 'can\'t fill with air' + + if not reply: + self.g.filling = Munch(coord1=coord1, coord2=coord2, block=block) + self.g.job.state = self.g.job.fill_blocks + reply = 'filling ' + str(utils.pvolume(coord1, coord2)) + ' with ' + blocks.BLOCKS[block] + + except BaseException as e: import traceback print(traceback.format_exc()) @@ -794,6 +824,18 @@ class Game: packet2.accepted = packet.accepted self.g.connection.write_packet(packet2) + def handle_spawn_player(self, packet): + print(packet) + self.g.players[packet.entity_id] = Munch( + entity_id=packet.entity_id, + player_uuid=packet.player_uuid, + x=packet.x, + y=packet.y, + z=packet.z, + yaw=packet.yaw, + pitch=packet.pitch, + ) + def handle_spawn_object(self, packet): #return if packet.type_id != 37: return @@ -809,9 +851,6 @@ class Game: ) def check_gapple(self, packet): - if not self.g.job: - return - current_gapple_chest = self.g.job.find_gapple_states.current_chest if current_gapple_chest: for entry in packet.metadata: @@ -826,7 +865,8 @@ class Game: if not packet.metadata: return - self.check_gapple(packet) + if self.g.job and self.g.job.state == self.g.job.find_gapple_states: + self.check_gapple(packet) obj = self.g.objects.get(packet.entity_id, None) if obj: @@ -836,6 +876,10 @@ class Game: obj.item_id = entry.value.item_id obj.item_count = entry.value.item_count + player = self.g.players.get(packet.entity_id, None) + if player: + return + def handle_spawn_living(self, packet): self.g.mobs[packet.entity_id] = Munch( entity_id=packet.entity_id, diff --git a/jobs.py b/jobs.py index 2a7ac25..c30efa0 100644 --- a/jobs.py +++ b/jobs.py @@ -2,6 +2,7 @@ import re import time import importlib import random +from itertools import count from math import hypot from panda3d.core import LPoint3f @@ -1107,6 +1108,209 @@ class CheckThreatsStates: self.state() +class FillBlocksStates: + def idle(self): + return None + + def init(self): + f = self.g.filling + + if not f: + self.state = self.cleanup + print('Aborting, nothing to fill') + return + + if self.last_block: + self.state = self.select_block + else: + self.state = self.find_last_block + + def find_last_block(self): + w = self.g.world + f = self.g.filling + print('Finding last block') + + b1, b2 = utils.pboundingbox(f.coord1, f.coord2) + y_start = f.coord1[1] + y_end = f.coord2[1] + distance = utils.phyp(f.coord1, f.coord2) + + self.next_block = f.coord1 + + for offset in utils.search_3d(distance): + check = utils.padd(f.coord1, offset) + + # ensure block is within fill area + if check[0] < b1[0] or check[0] > b2[0]: + continue + if check[2] < b1[2] or check[2] > b2[2]: + continue + + if w.block_at(*check) == blocks.AIR: + self.state = self.select_block + return + + self.last_block = check + + def select_block(self): + f = self.g.filling + name = blocks.BLOCKS[f.block] + item = items.ITEMS['minecraft:'+name]['protocol_id'] + + if self.g.game.select_item([item]): + #self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW) + self.state = self.find_next_block + else: + print('No blocks, aborting') + self.state = self.cleanup + + def find_next_block(self): + w = self.g.world + f = self.g.filling + print('Finding next block, last:', self.last_block) + + b1, b2 = utils.pboundingbox(f.coord1, f.coord2) + box = utils.psub(b2, b1) + xz_distance = hypot(box[0]+1, box[2]+1) + y_start = f.coord1[1] + y_end = f.coord2[1] + + print('distance', xz_distance) + + for y in range(y_start, y_end+1): + for i in count(): + offset = utils.spiral(i) + check = utils.padd(self.last_block, offset) + check = (check[0], y, check[2]) + + print('layer', y) + print('offset', offset) + print('hypot', hypot(offset[0], offset[2])) + + if hypot(offset[0], offset[2]) > xz_distance: + break + + # ensure block is within fill area + if check[0] < b1[0] or check[0] > b2[0]: + continue + if check[2] < b1[2] or check[2] > b2[2]: + continue + + if w.block_at(*check) == blocks.AIR: + print('Found next block:', check) + self.next_block = check + self.state = self.check_block_distance + return + + # if there's nothing left to fill + self.g.filling = None + self.state = self.cleanup + + def check_block_distance(self): + w = self.g.world + p = utils.pint(self.g.pos) + head = utils.padd(p, path.BLOCK_ABOVE) + + if utils.phyp(head, self.next_block) < 4: + self.state = self.fill_block + else: + self.state = self.nav_to_block + + def nav_to_block(self): + w = self.g.world + p = utils.pint(self.g.pos) + c = self.g.chunks + + tmp = c.get_block_at(*self.next_block) + c.set_block_at(*self.next_block, blocks.STONE) + pos = utils.padd(self.next_block, path.BLOCK_ABOVE) + navpath = w.path_to_place(p, pos) + c.set_block_at(*self.next_block, tmp) + + if navpath: + self.g.path = navpath[:-1] + self.state = self.going_to_block + else: + print('Cant get to that block') + self.state = self.cleanup + #self.bad_sand.append(self.sand) + #self.state = self.find_new_sand + + def going_to_block(self): + if not len(self.g.path): + self.state = self.fill_block + + def fill_block(self): + print('Filling block', self.next_block) + + self.g.game.place_block(self.next_block, BlockFace.TOP) + self.g.look_at = self.next_block + self.wait_time = 0.25 + self.state = self.wait_for_block + + def wait_for_block(self): + if self.wait_time > 0: + self.wait_time -= utils.TICK + else: + self.state = self.check_block + + def check_block(self): + w = self.g.world + if w.block_at(*self.next_block) != blocks.AIR: + self.last_block = self.next_block + else: + print('Block didnt appear') + + self.state = self.check_obstruction + + def check_obstruction(self): + p = utils.pint(self.g.pos) + print('last', self.last_block) + print('p', p) + if self.last_block[1] >= p[1]: + print('Obstructed, going to last block') + self.state = self.nav_to_last_block + else: + self.state = self.cleanup + + def nav_to_last_block(self): + w = self.g.world + p = utils.pint(self.g.pos) + c = self.g.chunks + + pos = utils.padd(self.last_block, path.BLOCK_ABOVE) + navpath = w.path_to_place(p, pos) + + if navpath: + self.g.path = navpath + self.state = self.going_to_last_block + else: + print('Cant get to that block') + self.state = self.cleanup + + def going_to_last_block(self): + if not len(self.g.path): + self.state = self.cleanup + + def cleanup(self): + self.state = self.done + + def done(self): + # never gets ran, placeholder + return None + + def __init__(self, global_state): + self.g = global_state + self.state = self.idle + + self.wait_time = 0 + self.last_block = None + self.next_block = None + + def run(self): + self.state() + + class JobStates: def idle(self): return [] @@ -1121,6 +1325,7 @@ class JobStates: self.clear_leaves_states = ClearLeavesStates(self.g) self.grab_sapling_states = GrabSaplingStates(self.g) self.grab_sand_states = GrabSandStates(self.g) + self.fill_blocks_states = FillBlocksStates(self.g) self.check_threats_states = CheckThreatsStates(self.g) def run_machines(self, machines): @@ -1190,6 +1395,13 @@ class JobStates: self.cache_items_states.silent = True return machines + def fill_blocks(self): + machines = [ + self.fill_blocks_states, + self.sleep_with_bed_states, + ] + return machines + def stop(self): self.init_machines() self.state = self.idle diff --git a/main.py b/main.py index 5aee0cb..37ceca0 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,7 @@ g.pos = False g.inv = {} g.objects = {} g.mobs = {} +g.players = {} g.window = None g.job = None g.correction_count = 0 diff --git a/monkey_patch.py b/monkey_patch.py index 6a7eeb5..c9d833b 100644 --- a/monkey_patch.py +++ b/monkey_patch.py @@ -23,7 +23,8 @@ def get_packets(old_get_packets): mc_packets.add(packets.SpawnLivingEntityPacket) mc_packets.add(packets.EntityPositionRotationPacket) mc_packets.add(packets.DestroyEntitiesPacket) - mc_packets.add(packets.EntityActionPacket) + #mc_packets.add(packets.EntityActionPacket) + mc_packets.add(packets.SpawnPlayerPacket) return mc_packets return lambda x: wrapper(old_get_packets, x) diff --git a/protocol/packets.py b/protocol/packets.py index 93740f4..4ab60a0 100644 --- a/protocol/packets.py +++ b/protocol/packets.py @@ -5,7 +5,7 @@ from minecraft.networking.types import ( VarInt, Integer, UnsignedByte, Position, Vector, MutableRecord, attribute_alias, multi_attribute_alias, Long, Boolean, VarLong, Short, UnsignedLong, Byte, BlockFace, String, UUID, Angle, Double, - Float, + Float, Direction, PositionAndLook ) from protocol.types import Nbt, Slot, Entry @@ -374,3 +374,19 @@ class EntityActionPacket(Packet): {'action_id': VarInt}, {'jump_boost': VarInt}, ] + +class SpawnPlayerPacket(Packet): + # https://wiki.vg/Protocol#Spawn_Player + + id = 0x04 + packet_name = 'spawn player' + + definition = [ + {'entity_id': VarInt}, + {'player_uuid': UUID}, + {'x': Double}, + {'y': Double}, + {'z': Double}, + {'yaw': Angle}, + {'pitch': Angle}, + ] diff --git a/utils.py b/utils.py index e9b7b5b..87197de 100644 --- a/utils.py +++ b/utils.py @@ -33,6 +33,16 @@ def phyp_king(p1, p2): def pint(p): return (floor(p[0]), floor(p[1]), floor(p[2])) +def pboundingbox(p1, p2): + b1 = (min(p1[0], p2[0]), min(p1[1], p2[1]), min(p1[2], p2[2])) + b2 = (max(p1[0], p2[0]), max(p1[1], p2[1]), max(p1[2], p2[2])) + return b1, b2 + +def pvolume(p1, p2): + b1, b2 = pboundingbox(p1, p2) + box = psub(b2, b1) + return (box[0]+1) * (box[1]+1) * (box[2]+1) + def cap(x, amount): sign = 1 if x >= 0 else -1 return sign * min(abs(x), amount)