Detect monsters and flee to safety

master
Tanner Collin 3 years ago
parent 5a7a2f0113
commit b8952db742
  1. 4
      blocks.py
  2. 2
      bot.py
  3. 412
      game.py
  4. 153
      jobs.py
  5. 50
      mobs.py
  6. 1
      monkey_patch.py
  7. 13
      protocol/packets.py

@ -16,12 +16,13 @@ for name, data in JSON_BLOCKS.items():
for state in data['states']:
BLOCKS[state['id']] = name.replace('minecraft:', '')
BREAK_DISTANCE = 5
BREAK_DISTANCE = 6
AIR = 0
SAND = 66
SINGLE_SNOW = 3921
SOUL_TORCH = 4008
EMERALD_BLOCK = 5407
TEST_BLOCK = (616, 78, 496)
@ -246,6 +247,7 @@ TRAPPED_CHESTS = [
INDEXED = [
'chest',
'trapped_chest',
'emerald_block',
]

@ -203,6 +203,8 @@ def init(global_state):
g.job = jobs.JobStates(g)
g.chopped_tree = False
g.queue_afk = False
def bot(global_state):
g = global_state

@ -2,6 +2,7 @@ import re
import time
import importlib
import random
import functools
from math import hypot
from itertools import count
from munch import Munch
@ -19,6 +20,7 @@ from protocol.packets import (
ClickWindowPacket, CloseWindowPacket, ServerWindowConfirmationPacket,
ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
EntityActionPacket,
)
from protocol.types import Slot
@ -33,6 +35,8 @@ import items
importlib.reload(items)
import data
importlib.reload(data)
import mobs
importlib.reload(mobs)
class MCWorld:
def __init__(self, global_state):
@ -41,6 +45,13 @@ class MCWorld:
def block_at(self, x, y, z):
return self.g.chunks.get_block_at(x, y, z)
def check_air_column(self, pos, distance):
for i in range(distance):
check = utils.padd(pos, (0, i, 0))
if self.block_at(*check) not in blocks.NON_SOLID_IDS:
return False
return True
def find_blocks_3d(self, center, block_ids, distance=0, y_limit=0):
for offset in utils.search_3d(distance, y_limit):
check = utils.padd(center, offset)
@ -189,10 +200,9 @@ class MCWorld:
return safe_sand
def check_sand_slice(self, center):
# checks if a 5x5x1 slice has diggable sand in it
# checks if a 5x5x1 slice has sand in it
for i in range(9):
s = utils.padd(center, utils.spiral(i))
if self.block_at(*s) != blocks.SAND:
continue
# make sure it has solid below
@ -206,18 +216,17 @@ class MCWorld:
continue
if not self.sand_adjacent_safe(s):
continue
return True
return False
def find_sand_slice(self, center, distance, bad_slices=[]):
def find_sand_slice(self, center, distance, y_limit=0, bad_slices=[], prev_layer=0):
# returns the centre coord of the next 5x5x1 slice that still has
# diggable sand in it. lower slices are only valid if there's an
# adjacent slice farther at the same level. this should ensure an
# upside down pyramid gets excavated so the edges are still climbable
for v in count():
peak = utils.padd(center, (0, 20-v, 0))
for v in count(prev_layer):
peak = utils.padd(center, (0, 10-v, 0))
slices = []
layer = 0
@ -228,14 +237,16 @@ class MCWorld:
check = utils.padd(peak, offset)
check = utils.padd(check, (0, layer, 0))
if utils.phyp(center, check) >= distance:
if y_limit and check[1] - center[1] > y_limit:
break
if utils.phyp_king(center, check) > distance:
break
if self.check_sand_slice(check) and check not in bad_slices:
slices.append(check)
if len(slices):
return slices[-1]
return v, slices[-1]
elif v > 40:
return None, None
@ -262,6 +273,31 @@ class MCWorld:
for a in self.find_blocks_3d(center, blocks.LEAF_IDS, distance, 10):
yield a
def find_monsters(self, center, distance):
# finds monsters within distance
result = []
for eid, mob in self.g.mobs.items():
if mob.type not in mobs.EVIL_IDS:
continue
pos = utils.pint((mob.x, mob.y, mob.z))
if utils.phyp(center, pos) > distance:
continue
result.append(mob)
return result
def find_threats(self, center, distance):
# finds monsters on the surface within distance
monsters = self.find_monsters(center, distance)
result = []
for mob in monsters:
pos = utils.pint((mob.x, mob.y, mob.z))
# check distance number of blocks above, close enough?
if not self.check_air_column(pos, distance):
continue
result.append(mob)
return result
class Game:
def __init__(self, global_state):
@ -365,7 +401,7 @@ class Game:
else:
return
if text == 'zzz':
if text.startswith('zzz'):
text = '!zzz'
if text.startswith('! '):
@ -382,166 +418,205 @@ class Game:
command = text
data = None
if command == 'ping':
reply = 'pong'
if command == 'echo' and data:
reply = data
try:
if command == 'ping':
reply = 'pong'
if command == 'respawn':
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
self.g.connection.write_packet(packet)
reply = 'ok'
if command == 'echo' and data:
reply = data
if command == 'pos':
reply = str(utils.pint(self.g.pos))[1:-1]
if command == 'respawn':
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
self.g.connection.write_packet(packet)
reply = 'ok'
if command == 'afk':
reply = '/afk'
if command == 'pos':
reply = str(utils.pint(self.g.pos))[1:-1]
if command == 'error':
reply = 'ok'
raise
if command == 'afk':
reply = '/afk'
if command == 'break':
self.break_block(blocks.TEST_BLOCK)
reply = 'ok'
if command == 'error':
reply = 'ok'
raise
if command == 'gather' and data:
if data == 'wood':
self.g.job.state = self.g.job.gather_wood
if command == 'break':
self.break_block(blocks.TEST_BLOCK)
reply = 'ok'
elif data == 'sand':
self.g.job.state = self.g.job.gather_sand
if command == 'gather' and data:
if data == 'wood':
self.g.job.state = self.g.job.gather_wood
reply = 'ok'
elif data == 'sand':
self.g.job.state = self.g.job.gather_sand
reply = 'ok'
if reply:
for i in self.g.inv.values():
print(i.item_id)
if i.item_id in items.BED_IDS:
break
else:
reply += ', I need a bed'
if command == 'farm' and data:
if data == 'wood':
self.g.job.state = self.g.job.farm_wood
reply = 'ok'
elif data == 'sand':
self.g.job.state = self.g.job.farm_sand
reply = 'ok'
if reply:
for i in self.g.inv.values():
if i.item_id in items.BED_IDS:
break
else:
reply += ', I need a bed'
if command == 'stop':
self.g.job.stop()
self.g.path = []
self.g.look_at = None
reply = 'ok'
if reply:
if command == 'inv':
inv_list = []
for i in self.g.inv.values():
print(i.item_id)
if i.item_id in items.BED_IDS:
break
else:
reply += ', I need a bed'
if i.present:
inv_list.append('{}:{} x {}'.format(items.ITEM_NAMES[i.item_id], str(i.item_id), i.item_count))
inv_list.sort()
result = '\n'.join(inv_list)
print(result or 'Empty')
if command == 'farm' and data:
if data == 'wood':
self.g.job.state = self.g.job.farm_wood
reply = 'ok'
if command == 'drop':
self.drop_stack()
if reply:
for i in self.g.inv.values():
print(i.item_id)
if i.item_id in items.BED_IDS:
break
else:
reply += ', I need a bed'
if command == 'stop':
self.g.job.stop()
self.g.path = []
self.g.look_at = None
reply = 'ok'
if command == 'inv':
inv_list = []
for i in self.g.inv.values():
if i.present:
inv_list.append('{}:{} x {}'.format(items.ITEM_NAMES[i.item_id], str(i.item_id), i.item_count))
inv_list.sort()
result = '\n'.join(inv_list)
print(result or 'Empty')
if command == 'drop':
self.drop_stack()
if command == 'time':
reply = str(self.g.time)
if command == 'select' and data:
item = int(data)
if self.select_item([item]):
reply = 'ok'
else:
reply = 'not found'
if command == 'time':
reply = str(self.g.time)
if command == 'dump' and data:
item = int(data)
if self.count_items([item]):
self.g.dumping = item
reply = 'ok'
else:
reply = 'not found'
if command == 'select' and data:
item = int(data)
if self.select_item([item]):
reply = 'ok'
else:
reply = 'not found'
if command == 'count' and data:
item = int(data)
reply = str(self.count_items([item]))
if command == 'dump' and data:
item = int(data)
if self.count_items([item]):
self.g.dumping = item
reply = 'ok'
else:
reply = 'not found'
if command == 'open':
self.open_container(blocks.TEST_BLOCK)
if command == 'count' and data:
item = int(data)
reply = str(self.count_items([item]))
if command == 'close':
if self.g.window:
self.close_window()
else:
reply = 'nothing open'
if command == 'click' and data:
if self.g.window:
slot, button, mode = [int(x) for x in data.split(' ')]
try:
item = self.g.window.contents[slot]
except KeyError:
item = Slot(present=False, item_id=None, item_count=None, nbt=None)
print(item)
self.click_window(slot, button, mode, item)
else:
reply = 'nothing open'
if command == 'open':
self.open_container(blocks.TEST_BLOCK)
if command == 'loaded':
reply = str(self.g.chunks.get_loaded_area())
if command == 'close':
if self.g.window:
self.close_window()
else:
reply = 'nothing open'
if command == 'click' and data:
if self.g.window:
slot, button, mode = [int(x) for x in data.split(' ')]
try:
item = self.g.window.contents[slot]
except KeyError:
item = Slot(present=False, item_id=None, item_count=None, nbt=None)
print(item)
self.click_window(slot, button, mode, item)
else:
reply = 'nothing open'
if command == 'gapple':
self.g.job.state = self.g.job.find_gapple
if data:
self.g.job.find_gapple_states.count = int(data)
reply = 'ok'
if command == 'loaded':
reply = str(self.g.chunks.get_loaded_area())
if command == 'objects':
for k, v in self.g.objects.items():
if data and v.item_id != int(data): continue
print(str(k) + ':', v)
if command == 'gapple':
self.g.job.state = self.g.job.find_gapple
if data:
self.g.job.find_gapple_states.count = int(data)
reply = 'ok'
if command == 'cache':
self.g.job.state = self.g.job.cache_items
self.g.job.cache_items_states.minimum = 0
self.g.job.cache_items_states.silent = True
reply = 'ok'
if command == 'objects':
if data == 'clear':
self.g.objects = {}
reply = 'ok'
else:
for k, v in self.g.objects.items():
if data and v.item_id != int(data): continue
print(str(k) + ':', v, items.ITEM_NAMES[v.item_id])
if command == 'mobs':
if data == 'clear':
self.g.mobs = {}
reply = 'ok'
else:
all_mobs = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type)
for k, v in all_mobs:
if data and v.type != int(data): continue
print(str(k) + ':', v, mobs.MOB_NAMES[v.type])
reply = str(len(all_mobs)) + ' mobs'
if command == 'monsters':
monsters = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type)
count = 0
for k, v in monsters:
if v.type not in mobs.EVIL_IDS: continue
if data and v.type != int(data): continue
count += 1
print(str(k) + ':', v, mobs.MOB_NAMES[v.type])
reply = str(count) + ' monsters'
if command == 'threats':
distance = int(data) if data else 20
p = utils.pint(self.g.pos)
threats = self.g.world.find_threats(p, distance)
for t in threats:
print(str(t.entity_id) + ':', t, mobs.MOB_NAMES[t.type])
reply = str(len(threats)) + ' threats'
if command == 'cache':
self.g.job.state = self.g.job.cache_items
self.g.job.cache_items_states.minimum = 0
self.g.job.cache_items_states.silent = True
reply = 'ok'
if command == 'spiral' and data:
for i in range(int(data)):
print(utils.spiral(i))
if command == 'spiral' and data:
for i in range(int(data)):
print(utils.spiral(i))
if command == 'sand_slice':
try:
if command == 'sand_slice':
result = self.g.world.find_sand_slice(utils.pint(self.g.pos), 50)
reply = str(result)
except:
import traceback
print(traceback.format_exc())
reply = 'error'
if command == 'zzz':
if not self.g.afk:
reply = '/afk'
if command == 'print':
try:
if command == 'zzz':
if not self.g.afk:
if self.g.path:
travel_time = int(len(self.g.path) * 0.4) + 2
reply = 'give me ' + str(travel_time) + ' secs, moving'
self.g.queue_afk = True
else:
reply = '/afk'
if command == 'print' and sender == 'tanner6':
data = data.replace('^', '.')
reply = str(eval(data))
except BaseException as e:
import traceback
print(traceback.format_exc())
reply = 'Error: {} - {}\n'.format(e.__class__.__name__, e)
except BaseException as e:
import traceback
print(traceback.format_exc())
reply = 'Error: {} - {}\n'.format(e.__class__.__name__, e)
pass
if reply:
print(reply)
@ -567,8 +642,8 @@ class Game:
def break_block(self, location):
p = utils.pint(self.g.pos)
if utils.phyp(p, location) > blocks.BREAK_DISTANCE:
return False
#if utils.phyp(p, location) > blocks.BREAK_DISTANCE + 1:
# return False
bid = self.g.chunks.get_block_at(*location)
if bid == 0:
@ -596,6 +671,7 @@ class Game:
self.g.breaking = None
def handle_break_animation(self, packet):
return
print(packet)
def handle_break_ack(self, packet):
@ -761,22 +837,28 @@ class Game:
obj.item_count = entry.value.item_count
def handle_spawn_living(self, packet):
print(packet)
return
self.g.mobs[packet.entity_id] = Munch(
entity_id=packet.entity_id,
entity_uuid=packet.entity_uuid,
type=packet.type,
x=packet.x,
y=packet.y,
z=packet.z,
)
def handle_entity_position(self, packet):
obj = self.g.objects.get(packet.entity_id, None)
if obj:
pass
#obj.x += packet.delta_x
#obj.y += packet.delta_y
#obj.z += packet.delta_z
mob = self.g.mobs.get(packet.entity_id, None)
if mob:
mob.x += packet.delta_x / 4096.0
mob.y += packet.delta_y / 4096.0
mob.z += packet.delta_z / 4096.0
def handle_entity_position_rotation(self, packet):
obj = self.g.objects.get(packet.entity_id, None)
if obj:
print('object rotation found:', packet)
raise
mob = self.g.mobs.get(packet.entity_id, None)
if mob:
mob.x += packet.delta_x / 4096.0
mob.y += packet.delta_y / 4096.0
mob.z += packet.delta_z / 4096.0
def handle_entity_velocity(self, packet):
obj = self.g.objects.get(packet.entity_id, None)
@ -790,6 +872,16 @@ class Game:
for eid in packet.entity_ids:
if eid in self.g.objects:
del self.g.objects[eid]
if eid in self.g.mobs:
del self.g.mobs[eid]
def leave_bed(self):
packet = EntityActionPacket()
packet.entity_id = self.g.eid
packet.action_id = 2
packet.jump_boost = 0
self.g.connection.write_packet(packet)
def tick(self):
if self.g.breaking:
@ -805,6 +897,10 @@ class Game:
else:
self.g.dumping = None
if not len(self.g.path):
if not self.g.path:
self.g.correction_count = 0
if self.g.queue_afk:
self.g.chat.send('/afk')
self.g.queue_afk = False

@ -20,6 +20,8 @@ import items
importlib.reload(items)
import data
importlib.reload(data)
import mobs
importlib.reload(mobs)
class FindGappleStates:
@ -308,12 +310,13 @@ class GatherSandStates:
w = self.g.world
print('using origin', self.origin)
s = w.find_sand_slice(self.origin, 50, self.bad_slices)
print('Found slice:', s)
start = time.time()
self.prev_layer, s = w.find_sand_slice(self.origin, 200, 10, self.bad_slices, self.prev_layer)
print('Found slice:', s, 'in', time.time() - start, 'seconds')
if s:
self.slice = s
#self.bad_slices.append(s)
self.bad_slices.append(s)
self.state = self.find_new_sand
else:
print('No slices remaining.')
@ -323,24 +326,23 @@ class GatherSandStates:
print('Finding new sand...')
w = self.g.world
p = utils.pint(self.g.pos)
head = utils.padd(p, path.BLOCK_ABOVE)
sand = w.find_sand(self.slice, 2, p)
print('Found sand:', sand)
if not len(sand):
for sand in w.find_sand(self.slice, 2, p):
if sand not in self.bad_sand:
print('Found sand:', sand)
break
else: # for
print('No good sands left, aborting.')
self.state = self.cleanup
return
for check in sand:
if check in self.bad_sand:
continue
self.sand = check
break
self.sand = sand
if utils.phyp(p, self.sand) > blocks.BREAK_DISTANCE:
self.state = self.nav_to_sand
else:
if utils.phyp(head, self.sand) < blocks.BREAK_DISTANCE:
self.state = self.dig_sand
else:
self.state = self.nav_to_sand
def nav_to_sand(self):
w = self.g.world
@ -356,6 +358,7 @@ class GatherSandStates:
self.g.path = navpath[:-1]
self.state = self.going_to_sand
else:
print('Cant get to that sand')
self.bad_sand.append(self.sand)
self.state = self.find_new_sand
@ -387,8 +390,10 @@ class GatherSandStates:
self.state = self.idle
self.origin = utils.pint(self.g.pos)
self.origin = (2019, 64, 238)
self.slice = None
self.bad_slices = []
self.prev_layer = 0
self.sand = None
self.bad_sand = []
self.wait_time = 0
@ -519,7 +524,7 @@ class SleepWithBedStates:
def going_to_area(self):
if utils.pint(self.g.pos) == self.opening:
self.g.look_at = self.area
self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW)
self.state = self.place_bed
def place_bed(self):
@ -535,8 +540,16 @@ class SleepWithBedStates:
self.state = self.sleep_bed
def sleep_bed(self):
if self.g.time < 100:
print('Woke up')
w = self.g.world
p = utils.pint(self.g.pos)
threats = w.find_threats(p, 30)
if threats:
print('Waking up due to threats')
self.g.game.leave_bed()
self.state = self.break_bed
elif self.g.time < 100:
print('Woke up time')
self.state = self.break_bed
def break_bed(self):
@ -1004,6 +1017,96 @@ class GrabSaplingStates:
self.state()
class CheckThreatsStates:
def idle(self):
return None
def init(self):
self.state = self.find_threats
print('Checking for threats')
def find_threats(self):
w = self.g.world
p = utils.pint(self.g.pos)
threats = w.find_threats(p, 40)
if threats:
print('Found', len(threats), 'threats, fleeing')
self.state = self.find_safety
else:
print('Aborting, no threats')
self.state = self.cleanup
def find_safety(self):
w = self.g.world
p = utils.pint(self.g.pos)
safety = w.find_blocks_indexed(p, [blocks.EMERALD_BLOCK])
if not safety:
print('No emerald blocks found, aborting')
self.state = self.cleanup
return
safety.sort(key=lambda s: utils.phyp(p, s))
print('Found emerald blocks:', safety)
for s in safety:
s = utils.padd(s, path.BLOCK_ABOVE)
navpath = w.path_to_place(p, s)
if navpath:
self.g.path = navpath
self.state = self.going_to_safety
self.safety = s
print('Going to safety', self.safety)
return
else:
print('Cant get to safety', self.safety)
print('Cant get to safety, aborting')
self.state = self.cleanup
def going_to_safety(self):
if utils.pint(self.g.pos) == self.safety:
print('At safety spot, waiting to be moved')
self.state = self.wait_for_move
def wait_for_move(self):
# wait for the server to move the bot when it's safe
# ie. a piston + daylight sensor
if utils.pint(self.g.pos) != self.safety:
print('Moved, resuming job')
self.state = self.wait
self.wait_time = 3
def wait(self):
# wait to land, etc
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
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.safety = None
self.wait_time = 0
def run(self):
self.state()
class JobStates:
def idle(self):
return []
@ -1018,6 +1121,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.check_threats_states = CheckThreatsStates(self.g)
def run_machines(self, machines):
for m in machines:
@ -1040,6 +1144,18 @@ class JobStates:
]
return machines
def farm_sand(self):
machines = [
self.check_threats_states,
self.gather_sand_states,
self.grab_sand_states,
self.cache_items_states,
self.sleep_with_bed_states,
]
self.sleep_with_bed_states.silent = True
self.cache_items_states.silent = True
return machines
def cache_items(self):
machines = [
self.cache_items_states,
@ -1070,7 +1186,6 @@ class JobStates:
self.sleep_with_bed_states,
self.cache_items_states,
]
self.sleep_with_bed_states.silent = True
self.cache_items_states.silent = True
return machines

@ -0,0 +1,50 @@
import json
with open('mcdata/registries.json') as f:
MOBS = json.load(f)['minecraft:entity_type']['entries']
EVIL = [
'blaze',
'cave_spider',
'creeper',
'drowned',
'elder_guardian',
'ender_dragon',
'enderman',
'endermite',
'evoker',
'ghast',
'giant',
'guardian',
'hoglin',
'husk',
'illusioner',
'magma_cube',
'phantom',
'piglin',
'piglin_brute',
'pillager',
'ravager',
'shulker',
'silverfish',
'skeleton',
'skeleton_horse',
'slime',
'spider',
'stray',
'vex',
'vindicator',
'witch',
'wither',
'zoglin',
'zombie',
'zombie_villager',
]
EVIL_IDS = set()
for mob_name in EVIL:
EVIL_IDS.add(MOBS['minecraft:'+mob_name]['protocol_id'])
MOB_NAMES = {}
for mob_name, mob in MOBS.items():
MOB_NAMES[MOBS[mob_name]['protocol_id']] = mob_name.replace('minecraft:', '')

@ -23,6 +23,7 @@ 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)
return mc_packets
return lambda x: wrapper(old_get_packets, x)

@ -361,3 +361,16 @@ class DestroyEntitiesPacket(Packet):
for _ in range(self.count):
eid = VarInt.read(file_object)
self.entity_ids.append(eid)
class EntityActionPacket(Packet):
# Sent by the client when it performs an action
# https://wiki.vg/Protocol#Entity_Action
id = 0x1C
packet_name = 'entity action'
definition = [
{'entity_id': VarInt},
{'action_id': VarInt},
{'jump_boost': VarInt},
]

Loading…
Cancel
Save