Compare commits

...

5 Commits

8 changed files with 305 additions and 56 deletions

View File

@ -237,12 +237,31 @@ SAPLINGS = [
'dark_oak_sapling', 'dark_oak_sapling',
] ]
BEDS = [
'white_bed',
'orange_bed',
'magenta_bed',
'light_blue_bed',
'yellow_bed',
'lime_bed',
'pink_bed',
'gray_bed',
'light_gray_bed',
'cyan_bed',
'purple_bed',
'blue_bed',
'brown_bed',
'green_bed',
'red_bed',
'black_bed',
]
INDEXED = [ INDEXED = [
'chest', 'chest',
'trapped_chest', 'trapped_chest',
'emerald_block', 'emerald_block',
'barrel', 'barrel',
] ] + BEDS
NON_SOLID_IDS = set([SINGLE_SNOW]) NON_SOLID_IDS = set([SINGLE_SNOW])
@ -310,16 +329,23 @@ for block_name in ['beetroots']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']: for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
BEETROOT_IDS.add(state['id']) BEETROOT_IDS.add(state['id'])
INDEXED_IDS = set()
for block_name in INDEXED:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
INDEXED_IDS.add(state['id'])
SAPLING_IDS = set() SAPLING_IDS = set()
for block_name in SAPLINGS: for block_name in SAPLINGS:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']: for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
SAPLING_IDS.add(state['id']) SAPLING_IDS.add(state['id'])
BED_IDS = set()
for block_name in BEDS:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
BED_IDS.add(state['id'])
INDEXED_IDS = set()
for block_name in INDEXED:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
INDEXED_IDS.add(state['id'])
MATURE_WHEAT_ID = max(WHEAT_IDS) MATURE_WHEAT_ID = max(WHEAT_IDS)
MATURE_POTATO_ID = max(POTATO_IDS) MATURE_POTATO_ID = max(POTATO_IDS)
MATURE_CARROT_ID = max(CARROT_IDS) MATURE_CARROT_ID = max(CARROT_IDS)

20
bot.py
View File

@ -24,7 +24,7 @@ from minecraft.networking.packets import Packet, clientbound, serverbound
from protocol.managers import DataManager, ChunksManager, ChatManager, ChunkNotLoadedException from protocol.managers import DataManager, ChunksManager, ChatManager, ChunkNotLoadedException
from munch import Munch from munch import Munch
from panda3d.core import LPoint3f, LVector3f from vector import Point3D, Vector3D
import game import game
importlib.reload(game) importlib.reload(game)
@ -41,9 +41,9 @@ importlib.reload(mcdata)
last_tick = time.time() last_tick = time.time()
PITCH_ANGLE_DIR = LVector3f(x=0, y=1, z=0) PITCH_ANGLE_DIR = Vector3D((0, 1, 0))
YAW_ANGLE_DIR = LVector3f(x=0, y=0, z=-1) YAW_ANGLE_DIR = Vector3D((0, 0, -1))
YAW_ANGLE_REF = LVector3f(x=0, y=1, z=0) YAW_ANGLE_REF = Vector3D((0, 1, 0))
YAW_LOOK_AHEAD = 4 YAW_LOOK_AHEAD = 4
@ -105,7 +105,7 @@ def tick(global_state):
########## player physics ########## ########## player physics ##########
if g.path and len(g.path): if g.path and len(g.path):
target = LPoint3f(g.path[0]) target = Point3D(g.path[0])
target.x += 0.5 target.x += 0.5
target.z += 0.5 target.z += 0.5
@ -142,9 +142,11 @@ def tick(global_state):
g.y_v += g.y_a * utils.TICK g.y_v += g.y_a * utils.TICK
block_below = g.chunks.get_block_at(floor(p.x), ceil(p.y-1), floor(p.z)) block_below = g.chunks.get_block_at(floor(p.x), ceil(p.y-1), floor(p.z))
block_above = g.chunks.get_block_at(floor(p.x), ceil(p.y+1), floor(p.z))
in_void = p.y < 0 in_void = p.y < 0
in_air = block_below in blocks.NON_SOLID_IDS or in_void in_air = block_below in blocks.NON_SOLID_IDS or in_void
in_water = block_below in blocks.WATER_IDS in_water = block_below in blocks.WATER_IDS
g.crawling = block_above not in blocks.NON_SOLID_IDS
if in_air: if in_air:
g.y_a = -36.0 g.y_a = -36.0
@ -156,11 +158,11 @@ def tick(global_state):
g.y_a = 0 g.y_a = 0
if g.look_at: if g.look_at:
look_at = LPoint3f(g.look_at) look_at = Point3D(g.look_at)
elif g.path and len(g.path) > YAW_LOOK_AHEAD: elif g.path and len(g.path) > YAW_LOOK_AHEAD:
look_at = LPoint3f(g.path[YAW_LOOK_AHEAD]) look_at = Point3D(g.path[YAW_LOOK_AHEAD])
elif g.path and len(g.path): elif g.path and len(g.path):
look_at = LPoint3f(g.path[-1]) look_at = Point3D(g.path[-1])
else: else:
look_at = None look_at = None
@ -178,6 +180,7 @@ def tick(global_state):
# remove vertical component for yaw calculation # remove vertical component for yaw calculation
look_at_d.y = 0 look_at_d.y = 0
if look_at_d.length() > 0.6:
target_yaw = look_at_d.normalized().signedAngleDeg(other=YAW_ANGLE_DIR, ref=YAW_ANGLE_REF) target_yaw = look_at_d.normalized().signedAngleDeg(other=YAW_ANGLE_DIR, ref=YAW_ANGLE_REF)
target_yaw_d = target_yaw - g.yaw target_yaw_d = target_yaw - g.yaw
target_yaw_d = (target_yaw_d + 180) % 360 - 180 target_yaw_d = (target_yaw_d + 180) % 360 - 180
@ -204,6 +207,7 @@ def init(global_state):
g.y_a = 0 g.y_a = 0
g.yaw = 360 g.yaw = 360
g.pitch = 0 g.pitch = 0
g.crawling = False
g.breaking = None g.breaking = None
g.break_time = 0 g.break_time = 0

36
game.py
View File

@ -8,7 +8,7 @@ from itertools import count
from munch import Munch from munch import Munch
from copy import copy from copy import copy
from panda3d.core import LPoint3f from vector import Point3D
from minecraft.networking.packets import Packet, clientbound, serverbound from minecraft.networking.packets import Packet, clientbound, serverbound
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@ -21,7 +21,7 @@ from protocol.packets import (
ClientWindowConfirmationPacket, EntityMetadataPacket, ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket, SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket, EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket,
SelectTradePacket, SelectTradePacket, DisconnectPacket,
) )
from protocol.types import Slot from protocol.types import Slot
@ -117,9 +117,7 @@ class MCWorld:
log_count += 1 log_count += 1
for offset in path.CHECK_DIRECTIONS: for offset in path.CHECK_DIRECTIONS:
if self.block_at(*utils.padd(log, offset)) not in blocks.LEAF_IDS: if self.block_at(*utils.padd(log, offset)) in blocks.LEAF_IDS:
break
else: # for:
good_leaves = True good_leaves = True
# make sure it's a good tree # make sure it's a good tree
@ -132,7 +130,7 @@ class MCWorld:
def find_tree_openings(self, tree): def find_tree_openings(self, tree):
# returns coords in a cardinal direction where we can stand by tree # returns coords in a cardinal direction where we can stand by tree
maze_solver = path.Pathfinder(self.g.chunks) maze_solver = path.Pathfinder(self.g)
result = [] result = []
# TODO: make sure only non-solid and leaves between # TODO: make sure only non-solid and leaves between
@ -146,7 +144,7 @@ class MCWorld:
return result return result
def path_to_place(self, start, place): def path_to_place(self, start, place):
maze_solver = path.Pathfinder(self.g.chunks) maze_solver = path.Pathfinder(self.g)
try: try:
s = maze_solver.astar(start, place) s = maze_solver.astar(start, place)
@ -155,8 +153,8 @@ class MCWorld:
return None return None
def find_bed_areas(self, center, distance): def find_bed_areas(self, center, distance):
bed_clearance = 25 # 5x5 area bed_clearance = 9 # 5x5 area
clear_distance = 3 clear_distance = 2
for a in self.find_blocks_3d(center, [0], distance, 50): for a in self.find_blocks_3d(center, [0], distance, 50):
# check for air around the area # check for air around the area
@ -324,7 +322,7 @@ class MCWorld:
def find_villager_openings(self, villager): def find_villager_openings(self, villager):
# returns coords in a cardinal direction where we can stand by a villager # returns coords in a cardinal direction where we can stand by a villager
maze_solver = path.Pathfinder(self.g.chunks) maze_solver = path.Pathfinder(self.g)
result = [] result = []
for distance in range(3): for distance in range(3):
@ -374,6 +372,7 @@ class Game:
register(self.handle_update_health, clientbound.play.UpdateHealthPacket) register(self.handle_update_health, clientbound.play.UpdateHealthPacket)
#register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket) #register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket)
register(self.handle_trade_list, TradeListPacket) register(self.handle_trade_list, TradeListPacket)
register(self.handle_disconnect, DisconnectPacket)
#register(self.handle_packet, Packet, early=True) #register(self.handle_packet, Packet, early=True)
@ -393,11 +392,11 @@ class Game:
def handle_block_change(self, packet): def handle_block_change(self, packet):
if packet.block_state_id == blocks.SOUL_TORCH: if packet.block_state_id == blocks.SOUL_TORCH:
try: try:
self.g.goal = LPoint3f(x=packet.location[0], y=packet.location[1], z=packet.location[2]) self.g.goal = Point3D((packet.location[0], packet.location[1], packet.location[2]))
print('new waypoint:', self.g.goal) print('new waypoint:', self.g.goal)
start = time.time() start = time.time()
solution = path.Pathfinder(self.g.chunks).astar(utils.pint(self.g.pos), utils.pint(self.g.goal)) solution = path.Pathfinder(self.g).astar(utils.pint(self.g.pos), utils.pint(self.g.goal))
if solution: if solution:
solution = list(solution) solution = list(solution)
self.g.path = solution self.g.path = solution
@ -420,7 +419,7 @@ class Game:
def handle_position_and_look(self, packet): def handle_position_and_look(self, packet):
print(packet) print(packet)
p = LPoint3f(x=packet.x, y=packet.y, z=packet.z) p = Point3D((packet.x, packet.y, packet.z))
self.g.pos = p self.g.pos = p
confirm_packet = serverbound.play.TeleportConfirmPacket() confirm_packet = serverbound.play.TeleportConfirmPacket()
@ -851,6 +850,11 @@ class Game:
data = data.replace('^', '.') data = data.replace('^', '.')
reply = str(eval(data)) reply = str(eval(data))
if command == 'exit':
import os
os._exit(0)
except BaseException as e: except BaseException as e:
import traceback import traceback
@ -1230,6 +1234,12 @@ class Game:
packet.selected_slot = num packet.selected_slot = num
self.g.connection.write_packet(packet) self.g.connection.write_packet(packet)
def handle_disconnect(self, packet):
print(packet)
print('Client disconnected!')
import os
os._exit(1)
def tick(self): def tick(self):
if self.g.breaking: if self.g.breaking:
self.animate() self.animate()

51
jobs.py
View File

@ -732,7 +732,51 @@ class SleepWithBedStates:
self.state = self.cleanup self.state = self.cleanup
return return
self.state = self.find_beds
def find_beds(self):
print('Finding beds...')
w = self.g.world
p = utils.pint(self.g.pos)
self.beds = w.find_blocks_indexed(p, blocks.BED_IDS)
print('Found:', self.beds)
self.state = self.choose_bed
def choose_bed(self):
print('Choosing a bed...')
w = self.g.world
p = utils.pint(self.g.pos)
c = self.g.chunks
if not len(self.beds):
print('No beds')
self.state = self.select_bed self.state = self.select_bed
return
bed = self.beds[0]
tmp = c.get_block_at(*bed)
c.set_block_at(*bed, blocks.AIR)
navpath = w.path_to_place(p, bed)
c.set_block_at(*bed, tmp)
print('navpath:', navpath)
if navpath:
self.g.path = navpath[:-1]
self.opening = self.g.path[-1]
self.area = bed
self.state = self.going_to_bed
return
else:
self.beds.pop(0)
def going_to_bed(self):
if utils.pint(self.g.pos) == self.opening:
self.my_bed = False
self.g.look_at = self.area
self.state = self.use_bed
def select_bed(self): def select_bed(self):
if self.g.game.select_item(items.BED_IDS): if self.g.game.select_item(items.BED_IDS):
@ -778,6 +822,7 @@ class SleepWithBedStates:
def place_bed(self): def place_bed(self):
self.g.game.place_block(self.area, BlockFace.TOP) self.g.game.place_block(self.area, BlockFace.TOP)
self.my_bed = True
self.state = self.use_bed self.state = self.use_bed
def use_bed(self): def use_bed(self):
@ -803,8 +848,12 @@ class SleepWithBedStates:
self.state = self.break_bed self.state = self.break_bed
def break_bed(self): def break_bed(self):
if self.my_bed:
self.g.game.break_block(self.area) self.g.game.break_block(self.area)
self.state = self.collect_bed self.state = self.collect_bed
else:
self.state = self.cleanup
def collect_bed(self): def collect_bed(self):
if not self.g.breaking: if not self.g.breaking:
@ -833,6 +882,8 @@ class SleepWithBedStates:
self.silent = False self.silent = False
self.my_bed = False
self.beds = []
self.area = None self.area = None
self.opening = None self.opening = None
self.bad_areas = [] self.bad_areas = []

View File

@ -19,6 +19,7 @@ def get_packets(old_get_packets):
mc_packets.add(packets.DestroyEntitiesPacket) mc_packets.add(packets.DestroyEntitiesPacket)
mc_packets.add(packets.EntityTeleport) mc_packets.add(packets.EntityTeleport)
mc_packets.add(packets.TradeListPacket) mc_packets.add(packets.TradeListPacket)
mc_packets.add(packets.DisconnectPacket)
return mc_packets return mc_packets

27
path.py
View File

@ -121,8 +121,9 @@ HALF_PARKOUR = {
BLOCK_CACHE_SIZE = 2**14 BLOCK_CACHE_SIZE = 2**14
class Pathfinder(AStar): class Pathfinder(AStar):
def __init__(self, chunks): def __init__(self, g):
self.chunks = chunks self.g = g
self.chunks = g.chunks
self.start_time = time.time() self.start_time = time.time()
@functools.lru_cache(maxsize=BLOCK_CACHE_SIZE) @functools.lru_cache(maxsize=BLOCK_CACHE_SIZE)
@ -133,6 +134,23 @@ class Pathfinder(AStar):
def bavoid(self, p): def bavoid(self, p):
return self.chunks.get_block_at(*p) in blocks.AVOID_IDS or p[1] < 0 return self.chunks.get_block_at(*p) in blocks.AVOID_IDS or p[1] < 0
def check_traverse_crawling(self, node, offset):
dest = utils.padd(node, offset)
if not self.bair(dest):
return False
if self.bair(utils.padd(dest, BLOCK_BELOW)):
return False
if self.bavoid(dest):
return False
if self.bavoid(utils.padd(dest, BLOCK_BELOW)):
return False
return True
def check_traverse(self, node, offset): def check_traverse(self, node, offset):
dest = utils.padd(node, offset) dest = utils.padd(node, offset)
@ -254,6 +272,11 @@ class Pathfinder(AStar):
def neighbors(self, node): def neighbors(self, node):
results = [] results = []
if self.g.crawling:
for offset in TRAVERSE:
if self.check_traverse_crawling(node, offset):
results.append(utils.padd(node, offset))
else:
for offset in TRAVERSE: for offset in TRAVERSE:
if self.check_traverse(node, offset): if self.check_traverse(node, offset):
results.append(utils.padd(node, offset)) results.append(utils.padd(node, offset))

View File

@ -447,3 +447,14 @@ class SelectTradePacket(Packet):
definition = [ definition = [
{'selected_slot': VarInt}, {'selected_slot': VarInt},
] ]
class DisconnectPacket(Packet):
# Sent by the server before it disconnects a client
# https://wiki.vg/Protocol#Disconnect_.28play.29
id = 0x19
packet_name = 'disconnect'
definition = [
{'reason': String},
]

123
vector.py Normal file
View File

@ -0,0 +1,123 @@
import math
class Vector3D:
def __init__(self, vector):
self.x = vector[0]
self.y = vector[1]
self.z = vector[2]
@property
def xz(self):
return Vector3D((self.x, 0, self.z))
def tuple(self):
return (self.x, self.y, self.z)
def __getitem__(self, key):
return self.tuple()[key]
def length(self):
return math.hypot(self.x, self.y, self.z)
def normalized(self):
x = self.x / self.length()
y = self.y / self.length()
z = self.z / self.length()
return Vector3D((x, y, z))
def dot(self, other):
return self.x * other.x + self.y * other.y + self.z * other.z
def cross(self, other):
a1, a2, a3 = self.tuple()
b1, b2, b3 = other.tuple()
return Vector3D((a2*b3-a3*b2, a3*b1-a1*b3, a1*b2-a2*b1))
def angleDeg(self, other):
ratio = self.dot(other) / (self.length() * other.length())
rads = math.acos(ratio)
return math.degrees(rads)
def signedAngleDeg(self, other, ref):
angle1 = self.angleDeg(other)
cross = self.cross(ref)
angle2 = other.angleDeg(cross)
if angle2 < 90:
return -angle1
else:
return angle1
def __repr__(self):
return 'Vector3D(x={}, y={}, z={})'.format(self.x, self.y, self.z)
def __str__(self):
return '[{}, {}, {}]'.format(self.x, self.y, self.z)
class Point3D:
def __init__(self, point):
self.x = point[0]
self.y = point[1]
self.z = point[2]
def __sub__(self, other):
#x = other.x - self.x
#y = other.y - self.y
#z = other.z - self.z
x = self.x - other.x
y = self.y - other.y
z = self.z - other.z
return Vector3D((x, y, z))
def tuple(self):
return (self.x, self.y, self.z)
def __getitem__(self, key):
return self.tuple()[key]
def __repr__(self):
return 'Point3D(x={}, y={}, z={})'.format(self.x, self.y, self.z)
def __str__(self):
return '({}, {}, {})'.format(self.x, self.y, self.z)
if __name__ == '__main__':
# test to make sure our Vector module is the same as Panda3D
from panda3d.core import LPoint3f, LVector3f
import random
pPITCH_ANGLE_DIR = LVector3f(x=0, y=1, z=0)
pYAW_ANGLE_DIR = LVector3f(x=0, y=0, z=-1)
pYAW_ANGLE_REF = LVector3f(x=0, y=1, z=0)
PITCH_ANGLE_DIR = Vector3D((0, 1, 0))
YAW_ANGLE_DIR = Vector3D((0, 0, -1))
YAW_ANGLE_REF = Vector3D((0, 1, 0))
for _ in range(1000):
r = lambda: random.uniform(-10, 10)
a, b, c = r(), r(), r()
plook_at_d = LVector3f(x=a, y=b, z=c)
look_at_d = Vector3D((a, b, c))
ptarget_pitch = plook_at_d.normalized().angleDeg(pPITCH_ANGLE_DIR)
target_pitch = look_at_d.normalized().angleDeg(PITCH_ANGLE_DIR)
if round(ptarget_pitch) != round(target_pitch):
print('mismatch:', ptarget_pitch, target_pitch)
break
else: # for
print('no mismatches')
for _ in range(1000):
r = lambda: random.uniform(-10, 10)
a, b, c = r(), r(), r()
plook_at_d = LVector3f(x=a, y=b, z=c)
look_at_d = Vector3D((a, b, c))
ptarget_yaw = plook_at_d.normalized().signedAngleDeg(other=pYAW_ANGLE_DIR, ref=pYAW_ANGLE_REF)
target_yaw = look_at_d.normalized().signedAngleDeg(other=YAW_ANGLE_DIR, ref=YAW_ANGLE_REF)
if round(ptarget_yaw) != round(target_yaw):
print('mismatch:', ptarget_yaw, target_yaw)
break
else: # for
print('no mismatches')