Compare commits

...

5 Commits

8 changed files with 305 additions and 56 deletions

View File

@ -237,12 +237,31 @@ SAPLINGS = [
'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 = [
'chest',
'trapped_chest',
'emerald_block',
'barrel',
]
] + BEDS
NON_SOLID_IDS = set([SINGLE_SNOW])
@ -310,16 +329,23 @@ for block_name in ['beetroots']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
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()
for block_name in SAPLINGS:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
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_POTATO_ID = max(POTATO_IDS)
MATURE_CARROT_ID = max(CARROT_IDS)

24
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 munch import Munch
from panda3d.core import LPoint3f, LVector3f
from vector import Point3D, Vector3D
import game
importlib.reload(game)
@ -41,9 +41,9 @@ importlib.reload(mcdata)
last_tick = time.time()
PITCH_ANGLE_DIR = LVector3f(x=0, y=1, z=0)
YAW_ANGLE_DIR = LVector3f(x=0, y=0, z=-1)
YAW_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))
YAW_LOOK_AHEAD = 4
@ -105,7 +105,7 @@ def tick(global_state):
########## player physics ##########
if g.path and len(g.path):
target = LPoint3f(g.path[0])
target = Point3D(g.path[0])
target.x += 0.5
target.z += 0.5
@ -142,9 +142,11 @@ def tick(global_state):
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_above = g.chunks.get_block_at(floor(p.x), ceil(p.y+1), floor(p.z))
in_void = p.y < 0
in_air = block_below in blocks.NON_SOLID_IDS or in_void
in_water = block_below in blocks.WATER_IDS
g.crawling = block_above not in blocks.NON_SOLID_IDS
if in_air:
g.y_a = -36.0
@ -156,11 +158,11 @@ def tick(global_state):
g.y_a = 0
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:
look_at = LPoint3f(g.path[YAW_LOOK_AHEAD])
look_at = Point3D(g.path[YAW_LOOK_AHEAD])
elif g.path and len(g.path):
look_at = LPoint3f(g.path[-1])
look_at = Point3D(g.path[-1])
else:
look_at = None
@ -175,9 +177,10 @@ def tick(global_state):
target_pitch_d = target_pitch - g.pitch
g.pitch += utils.cap(target_pitch_d, 10)
# remove vertical component for yaw calculation
look_at_d.y = 0
# remove vertical component for yaw calculation
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_d = target_yaw - g.yaw
target_yaw_d = (target_yaw_d + 180) % 360 - 180
@ -204,6 +207,7 @@ def init(global_state):
g.y_a = 0
g.yaw = 360
g.pitch = 0
g.crawling = False
g.breaking = None
g.break_time = 0

38
game.py
View File

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

57
jobs.py
View File

@ -732,7 +732,51 @@ class SleepWithBedStates:
self.state = self.cleanup
return
self.state = self.select_bed
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
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):
if self.g.game.select_item(items.BED_IDS):
@ -778,6 +822,7 @@ class SleepWithBedStates:
def place_bed(self):
self.g.game.place_block(self.area, BlockFace.TOP)
self.my_bed = True
self.state = self.use_bed
def use_bed(self):
@ -803,8 +848,12 @@ class SleepWithBedStates:
self.state = self.break_bed
def break_bed(self):
self.g.game.break_block(self.area)
self.state = self.collect_bed
if self.my_bed:
self.g.game.break_block(self.area)
self.state = self.collect_bed
else:
self.state = self.cleanup
def collect_bed(self):
if not self.g.breaking:
@ -833,6 +882,8 @@ class SleepWithBedStates:
self.silent = False
self.my_bed = False
self.beds = []
self.area = None
self.opening = None
self.bad_areas = []

View File

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

69
path.py
View File

@ -121,8 +121,9 @@ HALF_PARKOUR = {
BLOCK_CACHE_SIZE = 2**14
class Pathfinder(AStar):
def __init__(self, chunks):
self.chunks = chunks
def __init__(self, g):
self.g = g
self.chunks = g.chunks
self.start_time = time.time()
@functools.lru_cache(maxsize=BLOCK_CACHE_SIZE)
@ -133,6 +134,23 @@ class Pathfinder(AStar):
def bavoid(self, p):
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):
dest = utils.padd(node, offset)
@ -254,27 +272,32 @@ class Pathfinder(AStar):
def neighbors(self, node):
results = []
for offset in TRAVERSE:
if self.check_traverse(node, offset):
results.append(utils.padd(node, offset))
for offset in DIAGONAL:
if self.check_diagonal(node, offset):
results.append(utils.padd(node, offset))
for offset in ASCEND:
if self.check_ascend(node, offset):
results.append(utils.padd(node, offset))
for offset in DESCEND:
if self.check_descend(node, offset):
results.append(utils.padd(node, offset))
for offset in DESCEND2:
if self.check_descend2(node, offset):
results.append(utils.padd(node, offset))
for offset in DESCEND3:
if self.check_descend3(node, offset):
results.append(utils.padd(node, offset))
for offset in PARKOUR:
if self.check_parkour(node, offset):
results.append(utils.padd(node, offset))
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:
if self.check_traverse(node, offset):
results.append(utils.padd(node, offset))
for offset in DIAGONAL:
if self.check_diagonal(node, offset):
results.append(utils.padd(node, offset))
for offset in ASCEND:
if self.check_ascend(node, offset):
results.append(utils.padd(node, offset))
for offset in DESCEND:
if self.check_descend(node, offset):
results.append(utils.padd(node, offset))
for offset in DESCEND2:
if self.check_descend2(node, offset):
results.append(utils.padd(node, offset))
for offset in DESCEND3:
if self.check_descend3(node, offset):
results.append(utils.padd(node, offset))
for offset in PARKOUR:
if self.check_parkour(node, offset):
results.append(utils.padd(node, offset))
if not results:
if time.time() - self.start_time > 2.0:

View File

@ -447,3 +447,14 @@ class SelectTradePacket(Packet):
definition = [
{'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')