minecraft-bot/bot.py

1398 lines
39 KiB
Python

import os
import time
import functools
from math import ceil, floor, hypot, sqrt
from itertools import count
import blocks
import items
import custom_packets
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
class AStarTimeout(Exception):
pass
class DataManager:
def __init__(self):
self.blocks_states = {}
self.blocks_properties = {}
self.registries = {}
self.biomes = {}
self.entity_type = {}
self.blocks = {}
from panda3d.core import *
from astar import AStar
BLOCK_ABOVE = (0, +1, 0)
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)
TRAVERSE_EAST = (+1, 0, 0)
TRAVERSE_WEST = (-1, 0, 0)
ASCEND_NORTH = (0, +1, -1)
ASCEND_SOUTH = (0, +1, +1)
ASCEND_EAST = (+1, +1, 0)
ASCEND_WEST = (-1, +1, 0)
DESCEND_EAST = (+1, -1, 0)
DESCEND_WEST = (-1, -1, 0)
DESCEND_NORTH = (0, -1, -1)
DESCEND_SOUTH = (0, -1, +1)
DESCEND2_EAST = (+1, -2, 0)
DESCEND2_WEST = (-1, -2, 0)
DESCEND2_NORTH = (0, -2, -1)
DESCEND2_SOUTH = (0, -2, +1)
DESCEND3_EAST = (+1, -3, 0)
DESCEND3_WEST = (-1, -3, 0)
DESCEND3_NORTH = (0, -3, -1)
DESCEND3_SOUTH = (0, -3, +1)
DIAGONAL_NORTHEAST = (+1, 0, -1)
DIAGONAL_NORTHWEST = (-1, 0, -1)
DIAGONAL_SOUTHEAST = (+1, 0, +1)
DIAGONAL_SOUTHWEST = (-1, 0, +1)
PARKOUR_NORTH = (0, 0, -2)
PARKOUR_SOUTH = (0, 0, +2)
PARKOUR_EAST = (+2, 0, 0)
PARKOUR_WEST = (-2, 0, 0)
TRAVERSE = [
TRAVERSE_NORTH,
TRAVERSE_SOUTH,
TRAVERSE_EAST,
TRAVERSE_WEST,
]
ASCEND = [
ASCEND_NORTH,
ASCEND_SOUTH,
ASCEND_EAST,
ASCEND_WEST,
]
DESCEND = [
DESCEND_EAST,
DESCEND_WEST,
DESCEND_NORTH,
DESCEND_SOUTH,
]
DESCEND2 = [
DESCEND2_EAST,
DESCEND2_WEST,
DESCEND2_NORTH,
DESCEND2_SOUTH,
]
DESCEND3 = [
DESCEND3_EAST,
DESCEND3_WEST,
DESCEND3_NORTH,
DESCEND3_SOUTH,
]
DIAGONAL = [
DIAGONAL_NORTHEAST,
DIAGONAL_NORTHWEST,
DIAGONAL_SOUTHEAST,
DIAGONAL_SOUTHWEST,
]
PARKOUR = [
PARKOUR_NORTH,
PARKOUR_SOUTH,
PARKOUR_EAST,
PARKOUR_WEST,
]
HALF_PARKOUR = {
(0, 0, -2): (0, 0, -1),
(0, 0, 2): (0, 0, 1),
(2, 0, 0): (1, 0, 0),
(-2, 0, 0): (-1, 0, 0),
}
HYPOT_LUT = {
(0, -1): 1.0,
(0, 1): 1.0,
(1, 0): 1.0,
(-1, 0): 1.0,
(1, -1): 1.414,
(-1, -1): 1.414,
(1, 1): 1.414,
(-1, 1): 1.414,
(0, 2): 2.0,
(-2, 0): 2.0,
(2, 0): 2.0,
(0, -2): 2.0,
}
def padd(p1, p2):
return (p1[0] + p2[0], p1[1] + p2[1], p1[2] + p2[2])
def psub(p1, p2):
return (p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2])
def pmul(p, s):
return (s*p[0], s*p[1], s*p[2])
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]))
# larger started being slower
BLOCK_CACHE_SIZE = 2**14
class MazeSolver(AStar):
def __init__(self, chunks):
self.chunks = chunks
self.start_time = time.time()
@functools.lru_cache(maxsize=BLOCK_CACHE_SIZE)
def bair(self, p):
return self.chunks.get_block_at(*p) in blocks.NON_SOLID_IDS
@functools.lru_cache(maxsize=BLOCK_CACHE_SIZE)
def bavoid(self, p):
return self.chunks.get_block_at(*p) in blocks.AVOID_IDS
def check_traverse(self, node, offset):
dest = padd(node, offset)
if not self.bair(dest):
return False
if self.bair(padd(dest, BLOCK_BELOW)):
return False
if not self.bair(padd(dest, BLOCK_ABOVE)):
return False
if self.bavoid(dest):
return False
if self.bavoid(padd(dest, BLOCK_BELOW)):
return False
if self.bavoid(padd(dest, BLOCK_ABOVE)):
return False
return True
def check_diagonal(self, node, offset):
if not self.check_traverse(node, offset):
return False
dest = padd(node, offset)
thru1 = (node[0], node[1], dest[2])
thru2 = (dest[0], node[1], node[2])
if not self.bair(thru1):
return False
if not self.bair(padd(thru1, BLOCK_ABOVE)):
return False
if self.bavoid(padd(thru1, BLOCK_BELOW)):
return False
if not self.bair(thru2):
return False
if not self.bair(padd(thru2, BLOCK_ABOVE)):
return False
if self.bavoid(padd(thru2, BLOCK_BELOW)):
return False
return True
def check_ascend(self, node, offset):
if not self.check_traverse(node, offset):
return False
dest = padd(node, offset)
if not self.bair(padd(node, BLOCK_ABOVE2)):
return False
return True
def check_descend(self, node, offset):
if not self.check_traverse(node, offset):
return False
dest = padd(node, offset)
if not self.bair(padd(dest, BLOCK_ABOVE2)):
return False
return True
def check_descend2(self, node, offset):
if not self.check_descend(node, offset):
return False
dest = padd(node, offset)
if not self.bair(padd(dest, BLOCK_ABOVE3)):
return False
return True
def check_descend3(self, node, offset):
if not self.check_descend2(node, offset):
return False
dest = padd(node, offset)
if not self.bair(padd(dest, BLOCK_ABOVE4)):
return False
return True
def check_parkour(self, node, offset):
dest = padd(node, offset)
half_offset = HALF_PARKOUR[offset]
middle = padd(node, half_offset)
# dont jump if we can walk instead
if not self.bair(padd(middle, BLOCK_BELOW)):
return False
if not self.check_ascend(node, offset):
return False
if not self.bair(padd(dest, BLOCK_ABOVE2)):
return False
if not self.bair(padd(middle, BLOCK_ABOVE)):
return False
if not self.bair(padd(middle, BLOCK_ABOVE2)):
return False
return True
def neighbors(self, node):
results = []
for offset in TRAVERSE:
if self.check_traverse(node, offset):
results.append(padd(node, offset))
for offset in DIAGONAL:
if self.check_diagonal(node, offset):
results.append(padd(node, offset))
for offset in ASCEND:
if self.check_ascend(node, offset):
results.append(padd(node, offset))
for offset in DESCEND:
if self.check_descend(node, offset):
results.append(padd(node, offset))
for offset in DESCEND2:
if self.check_descend2(node, offset):
results.append(padd(node, offset))
for offset in DESCEND3:
if self.check_descend3(node, offset):
results.append(padd(node, offset))
for offset in PARKOUR:
if self.check_parkour(node, offset):
results.append(padd(node, offset))
if not results:
if time.time() - self.start_time > 2.0:
raise(AStarTimeout)
return results
def distance_between(self, n1, n2):
(x1, y1, z1) = n1
(x2, y2, z2) = n2
return HYPOT_LUT[x2 - x1, z2 - z1]
def heuristic_cost_estimate(self, n1, n2):
(x1, y1, z1) = n1
(x2, y2, z2) = n2
return hypot(x2 - x1, z2 - z1)
def spiral(n):
# return x, 0, z coords along a spiral at step n
# I forget where I found this
n += 1
k = ceil((sqrt(n)-1)/2)
t = 2 * k + 1
m = t**2
t = t - 1
if n >= m-t:
return k-(m-n), 0, -k
else:
m = m-t
if n >= m-t:
return -k, 0, -k+(m-n)
else:
m = m-t
if n >= m-t:
return -k+(m-n), 0, k
else:
return k, 0, k-(m-n-t)
def alternate(n, amount):
# return 0, y, 0 where y alternates +/- by amount
# example: 0, 2, -2, 4, -4, 6, -6 for amount = 2
sign = 1 if n % 2 else -1
return (0, ceil(n/2) * sign * amount, 0)
def diffrange(n):
# same as range(n+1) but can go negative
sign = 1 if n >= 0 else -1
return range(0, n+sign, sign)
def break_block(connection, coords, time):
packet = custom_packets.PlayerDiggingPacket()
packet.status = 0
packet.location = coords
packet.face = 1
connection.write_packet(packet)
s['break_finished_packet'] = custom_packets.PlayerDiggingPacket()
s['break_finished_packet'].status = 2
s['break_finished_packet'].location = coords
s['break_finished_packet'].face = 1
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)
CHECK_NORTH = (0, 0, -1)
CHECK_SOUTH = (0, 0, +1)
CHECK_EAST = (+1, 0, 0)
CHECK_WEST = (-1, 0, 0)
CHECK_DIRECTIONS = [
CHECK_NORTH,
CHECK_SOUTH,
CHECK_EAST,
CHECK_WEST,
]
class MCWorld:
def __init__(self, chunks):
self.chunks = chunks
def block_at(self, x, y, z):
return self.chunks.get_block_at(x, y, z)
def find_blocks(self, center, distance, block_ids, limit=0):
# search in a spiral from center to all blocks with ID
result = []
for n in count():
offset = spiral(n)
check = padd(center, offset)
if self.block_at(*check) in block_ids:
if hypot(*offset) < distance:
result.append(check)
if limit and len(result) == limit:
return result
if offset[0] > distance:
return result
def find_trees(self, center, distance):
logs = []
for i in range(5):
check = padd(center, alternate(i, 3))
logs.extend(self.find_blocks(check, distance, blocks.LOG_IDS, 50))
trees = []
for log in logs:
# crawl to the bottom log
while self.block_at(*padd(log, BLOCK_BELOW)) in blocks.LOG_IDS:
log = padd(log, BLOCK_BELOW)
# make sure we are on the ground
if self.block_at(*padd(log, BLOCK_BELOW)) in blocks.NON_SOLID_IDS:
continue
# crawl to the top log to count
log_count = 1
while self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LOG_IDS:
log = padd(log, BLOCK_ABOVE)
log_count += 1
# make sure it's a good tree
if self.block_at(*padd(log, BLOCK_ABOVE)) in blocks.LEAF_IDS and log_count > 2:
# crawl back to the bottom log
while self.block_at(*padd(log, BLOCK_BELOW)) in blocks.LOG_IDS:
log = padd(log, BLOCK_BELOW)
trees.append(log)
trees.sort(key=lambda x: phyp(center, x))
return trees
def find_tree_openings(self, tree):
# returns coords in a cardinal direction where we can stand by tree
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 = pmul(direction, distance+1)
if maze_solver.check_traverse(tree, offset):
result.append(padd(tree, offset))
return result
def path_to_place(self, start, place):
maze_solver = MazeSolver(self.chunks)
try:
s = maze_solver.astar(start, place)
return list(s) if s else None
except AStarTimeout:
return None
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))
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:
def bair(self, p):
return self.player_info.chunks.get_block_at(*p) in blocks.NON_SOLID_IDS
def blog(self, p):
return self.player_info.chunks.get_block_at(*p) in blocks.LOG_IDS
def idle(self):
return None
def init(self):
self.state = self.find_new_tree
def find_new_tree(self):
print('Finding new tree...')
w = MCWorld(self.player_info.chunks)
p = pint(self.player_info.pos)
trees = w.find_trees(p, 100)
print('Found trees:', trees)
while trees[0] in self.bad_trees:
trees.pop(0)
self.tree = trees[0]
self.openings = w.find_tree_openings(self.tree)
self.state = self.choose_opening
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.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):
if pint(self.player_info.pos) == self.openings[0]:
s['look_at'] = self.tree
self.state = self.clear_leaves
def clear_leaves(self):
if not s['break_timeout']:
p = pint(self.player_info.pos)
diff = psub(self.tree, p)
for x in diffrange(diff[0]):
for z in diffrange(diff[2]):
for y in range(2):
check = padd(p, (x, y, z))
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
above = padd(self.tree, BLOCK_ABOVE)
if self.blog(base):
s['break'] = (base, 3)
print('breaking base')
elif self.blog(above):
s['break'] = (above, 3)
print('breaking above')
else:
w = MCWorld(self.player_info.chunks)
p = pint(self.player_info.pos)
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):
if pint(self.player_info.pos) == self.tree:
s['look_at'] = padd(self.tree, BLOCK_ABOVE2)
self.state = self.clear_trunk
def clear_trunk(self):
if not s['break_timeout']:
check = self.tree
count = 0
while self.bair(check) and count < 6:
check = padd(check, BLOCK_ABOVE)
count += 1
if self.blog(check):
print('breaking log', check)
s['break'] = (check, 3)
else:
print('Finished clearing tree')
self.wait_time = 0.5
self.state = self.wait
def wait(self):
# wait for the last log to fall
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):
self.player_info = player_info
self.state = self.idle
self.tree = None
self.openings = []
self.bad_trees = []
self.wait_time = 0
def run(self):
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 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):
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
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):
self.state()
TICK = 0.05
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)
YAW_LOOK_AHEAD = 4
running = True
get_mod_time = lambda: os.path.getmtime('bot.py')
last_mod_time = get_mod_time()
# state dictionary
s = dict()
def cap(x, amount):
sign = 1 if x >= 0 else -1
return sign * min(abs(x), amount)
def tick(connection, player_info):
target = None
p = player_info.pos
try:
player_info.chunks.get_block_at(*pint(p))
except chunks.ChunkNotLoadedException:
return
s['jobstate'].run()
if s['path'] and len(s['path']):
target = LPoint3f(s['path'][0])
target.x += 0.5
target.z += 0.5
if target:
d = p - target
# jump up block
if d.y < -0.9 and not s['y_v']:
s['y_v'] = 8.5
s['y_a'] = -36.0
# jump gap
if d.xz.length() > 1.6 and not s['y_v']:
s['y_v'] = 8.5
s['y_a'] = -36.0
if d.length() > 0:
if s['y_v'] < 5:
p.x -= cap(d.x, 0.2)
p.z -= cap(d.z, 0.2)
if len(s['path']) > 1 and d.length() < 0.2:
# removes some jitter in walking
s['path'].pop(0)
elif d.length() == 0:
s['path'].pop(0)
if s['y_v'] or s['y_a']:
p.y += s['y_v'] * TICK
s['y_v'] += s['y_a'] * TICK
block_below = player_info.chunks.get_block_at(floor(p.x), ceil(p.y-1), floor(p.z))
in_air = block_below in blocks.NON_SOLID_IDS
if in_air:
s['y_a'] = -36.0
else:
p.y = ceil(p.y)
s['y_v'] = 0
s['y_a'] = 0
if s['look_at']:
look_at = LPoint3f(s['look_at'])
elif s['path'] and len(s['path']) > YAW_LOOK_AHEAD:
look_at = LPoint3f(s['path'][YAW_LOOK_AHEAD])
elif s['path'] and len(s['path']):
look_at = LPoint3f(s['path'][-1])
else:
look_at = None
if look_at:
look_at.x += 0.5
look_at.z += 0.5
look_at_d = p - look_at
if look_at_d.length() > 0.6:
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)
packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=s['pitch'], yaw=s['yaw'], on_ground=(not in_air))
connection.write_packet(packet, force=True)
if s['break']:
break_block(connection, *s['break'])
s['break'] = None
if s['break_time'] > 0:
packet = serverbound.play.AnimationPacket()
packet.hand = packet.HAND_MAIN
connection.write_packet(packet)
#print(s['break_time'])
s['break_time'] -= TICK
elif s['break_finished_packet']:
print('break finished')
connection.write_packet(s['break_finished_packet'])
s['break_finished_packet'] = None
elif s['break_timeout'] > 0:
s['break_timeout'] -= TICK
if s['break_timeout'] < 0:
s['break_timeout'] = 0
def init(connection, player_info):
p = player_info.pos
s['time'] = 0
s['path'] = []
s['look_at'] = None
s['y_v'] = 0
s['y_a'] = 0
s['yaw'] = 360
s['pitch'] = 0
s['break'] = None
s['break_time'] = 0
s['break_timeout'] = 0
s['break_finished_packet'] = None
s['jobstate'] = JobStates(connection, player_info)
s['jobstate'].run()
def main(connection, player_info):
def handle_join_game(join_game_packet):
print('Connected.')
print(join_game_packet)
player_info.eid = join_game_packet
connection.register_packet_listener(
handle_join_game, clientbound.play.JoinGamePacket)
def h_position_and_look(packet):
print('pos and look:')
print(packet)
p = LPoint3f(x=packet.x, y=packet.y, z=packet.z)
player_info.pos = p
connection.register_packet_listener(
h_position_and_look, clientbound.play.PlayerPositionAndLookPacket)
def x(p):
#print('block change:')
#print(p)
if p.block_state_id == 3885:
try:
s['goal'] = LPoint3f(x=p.location[0], y=p.location[1], z=p.location[2])
print('new waypoint:', s['goal'])
start = time.time()
solution = MazeSolver(player_info.chunks).astar(pint(player_info.pos), pint(s['goal']))
if solution:
solution = list(solution)
s['path'] = solution
s['jobstate'].state = s['jobstate'].stop
print(len(solution))
print(solution)
print(round(time.time() - start, 3), 'seconds')
else:
say(connection, 'No path found')
#s['y_v'] = 10.0
#s['y_a'] = -36.0
except BaseException as e:
import traceback
print(traceback.format_exc())
connection.register_packet_listener(
x, clientbound.play.BlockChangePacket)
def handle_time_update(p):
s['time'] = p.time_of_day % 24000
connection.register_packet_listener(
handle_time_update, custom_packets.TimeUpdatePacket)
def handle_set_slot(p):
print(p)
if p.window_id == 0:
player_info.inv[p.slot] = p.slot_data
connection.register_packet_listener(
handle_set_slot, custom_packets.SetSlotPacket)
def print_chat(chat_packet):
try:
print("Message (%s): %s" % (
chat_packet.field_string('position'), chat_packet.json_data))
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:
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
connection.write_packet(packet)
elif '!chunk' in chat_packet.json_data:
print(len(player_info.chunks.chunks.keys()))
print(player_info.chunks.chunks[(38, 4, 33)].__dict__)
elif '!block' in chat_packet.json_data:
block = player_info.chunks.get_block_at(616, 78, 496)
packet = serverbound.play.ChatPacket()
packet.message = str(block)
connection.write_packet(packet)
elif '!path' in chat_packet.json_data:
s['goal'] = LPoint3f(655, 86, 341)
print('new waypoint:', s['goal'])
start = time.time()
solution = MazeSolver(player_info.chunks).astar(pint(player_info.pos), pint(s['goal']))
solution = list(solution)
s['path'] = solution
print(len(solution))
print(round(time.time() - start, 3), 'seconds')
elif '!tree' in chat_packet.json_data:
mc_world = MCWorld(player_info.chunks)
start = time.time()
coords = mc_world.find_tree(pint(player_info.pos), 100)
print(coords)
openings = mc_world.find_tree_openings(coords)
print(openings)
path = mc_world.navigate_to_opening(pint(player_info.pos), openings[0])
print(path)
print(round(time.time() - start, 3), 'seconds')
elif '!break' in chat_packet.json_data:
coords = pint(player_info.pos)
coords = padd(coords, CHECK_NORTH)
break_block(connection, coords, 2.5)
#break_block(connection, coords, 0.35)
elif '!pick' in chat_packet.json_data:
packet = custom_packets.PickItemPacket()
packet.slot_to_use = 1
connection.write_packet(packet)
elif '!inv' in chat_packet.json_data:
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
say(connection, 'I need a bed')
return
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
elif 'where are you' in chat_packet.json_data:
say(connection, str(pint(player_info.pos))[1:-1])
except BaseException as e:
import traceback
print(traceback.format_exc())
connection.register_packet_listener(
print_chat, clientbound.play.ChatMessagePacket)
if not player_info.chunks:
player_info.mcdata = DataManager()
player_info.chunks = ChunksManager(player_info.mcdata)
player_info.chunks.register(connection)
#packet = serverbound.play.ChatPacket()
#packet.message = '> reloaded'
#connection.write_packet(packet)
print()
print()
print('Reloaded.')
#if player_info.pos:
# print('Loaded positions', player_info.pos)
try:
while not player_info.pos:
time.sleep(TICK)
print('Player loaded.')
x, y, z = pint(player_info.pos)
while (floor(x/16), floor(y/16), floor(z/16)) not in player_info.chunks.chunks:
time.sleep(TICK)
print('Chunks loaded.')
init(connection, player_info)
while running:
tick(connection, player_info)
global last_tick
sleep_time = TICK + last_tick - time.time()
if sleep_time < 0: sleep_time = 0
time.sleep(sleep_time)
last_tick = time.time()
if get_mod_time() != last_mod_time:
break
finally:
connection.packet_listeners = []
connection.early_packet_listeners = []
connection.outgoing_packet_listeners = []
connection.early_outgoing_packet_listeners = []