Compare commits

...

3 Commits

9 changed files with 451 additions and 80 deletions

View File

@ -194,6 +194,12 @@ NON_SOLID = [
'cave_air',
'lantern',
'soul_torch',
#'oak_sapling', # saplings can grow up and hurt
#'spruce_sapling',
#'birch_sapling',
#'jungle_sapling',
#'acacia_sapling',
#'dark_oak_sapling',
]
LOGS = [
@ -214,6 +220,15 @@ LEAVES = [
'dark_oak_leaves',
]
SAPLINGS = [
'oak_sapling',
'spruce_sapling',
'birch_sapling',
'jungle_sapling',
'acacia_sapling',
'dark_oak_sapling',
]
CHESTS = [
'chest',
]
@ -253,6 +268,11 @@ 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'])
def get(bid):
name = BLOCKS[bid]

35
bot.py
View File

@ -6,6 +6,7 @@ import os
import time
import importlib
from math import floor, ceil
from copy import copy
USERNAME = os.environ['USERNAME']
PASSWORD = os.environ['PASSWORD']
@ -22,7 +23,7 @@ from minecraft.networking.packets import Packet, clientbound, serverbound
from protocol.managers import DataManager, ChunksManager, ChatManager, ChunkNotLoadedException
from bunch import Bunch
from munch import Munch
from panda3d.core import LPoint3f, LVector3f
import game
@ -59,6 +60,37 @@ def tick(global_state):
g.chunks.unload_chunks(p)
########## object physics ##########
for eid, obj in copy(g.objects).items():
start_x = obj.x
if obj.velocity_x:
obj.x += obj.velocity_x / 8000
if obj.velocity_y:
obj.y += obj.velocity_y / 8000
if obj.velocity_z:
obj.z += obj.velocity_z / 8000
block_below = g.chunks.get_block_at(floor(obj.x), int(obj.y-0.20), floor(obj.z))
in_air = block_below in blocks.NON_SOLID_IDS
if in_air:
obj.velocity_x *= 0.988
obj.velocity_y -= 390
obj.velocity_z *= 0.988
else:
obj.y = int(obj.y-0.20)+1
obj.velocity_x *= 0.5
obj.velocity_y = 0
obj.velocity_z *= 0.5
if abs(obj.velocity_x) < 1: obj.velocity_x = 0
if abs(obj.velocity_z) < 1: obj.velocity_z = 0
########## player physics ##########
if g.path and len(g.path):
target = LPoint3f(g.path[0])
target.x += 0.5
@ -162,6 +194,7 @@ def init(global_state):
g.window = None
g.job = jobs.JobStates(g)
g.chopped_tree = False
def bot(global_state):
g = global_state

147
game.py
View File

@ -1,9 +1,11 @@
import re
import time
import importlib
import random
from math import hypot
from itertools import count
from bunch import Bunch
from munch import Munch
from copy import copy
from panda3d.core import LPoint3f
@ -11,14 +13,12 @@ from minecraft.networking.packets import Packet, clientbound, serverbound
from minecraft.networking.types import BlockFace
from protocol.packets import (
TimeUpdatePacket, SetSlotPacket, PlayerDiggingPacket,
SetSlotPacket, PlayerDiggingPacket,
BlockBreakAnimationPacket, AcknowledgePlayerDiggingPacket,
HeldItemChangePacket, PickItemPacket, OpenWindowPacket,
ClickWindowPacket, CloseWindowPacket, ServerWindowConfirmationPacket,
ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionPacket,
EntityPositionRotationPacket,
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
)
from protocol.types import Slot
@ -93,7 +93,7 @@ class MCWorld:
result = []
# TODO: make sure only non-solid and leaves between
# make sure traversable too
# make sure traversable too and non-avoid
for distance in range(5):
for direction in path.CHECK_DIRECTIONS:
@ -115,20 +115,23 @@ class MCWorld:
air = []
for i in range(5):
check = utils.padd(center, utils.alternate(i, 1))
air.extend(self.find_blocks(check, distance, [0], 200))
air.extend(self.find_blocks(check, distance, [0], 0))
bed_clearance = 25 # 5x5 area
clear_distance = 3
areas = []
for a in air:
# check for ground around the area
if len(self.find_blocks(utils.padd(a, path.BLOCK_BELOW), 2, blocks.NON_SOLID_IDS, 9)):
# check for air around the area
if len(self.find_blocks(a, clear_distance, [0], bed_clearance)) < bed_clearance:
continue
# check for air around the area
if len(self.find_blocks(a, 2, [0], 9)) < 9:
# check for ground around the area
if len(self.find_blocks(utils.padd(a, path.BLOCK_BELOW), clear_distance, blocks.NON_SOLID_IDS, bed_clearance)):
continue
# check for air above the area
if len(self.find_blocks(utils.padd(a, path.BLOCK_ABOVE), 2, [0], 9)) < 9:
if len(self.find_blocks(utils.padd(a, path.BLOCK_ABOVE), clear_distance, [0], bed_clearance)) < bed_clearance:
continue
areas.append(a)
@ -183,6 +186,13 @@ class MCWorld:
def find_cache_openings(self, area):
return self.find_bed_openings(area)
def find_objects(self, object_ids):
result = []
for eid, obj in copy(self.g.objects).items():
if obj.get('item_id', None) in object_ids:
result.append(obj)
return result
class Game:
def __init__(self, global_state):
@ -192,7 +202,7 @@ class Game:
register(self.handle_block_change, clientbound.play.BlockChangePacket)
register(self.handle_join_game, clientbound.play.JoinGamePacket)
register(self.handle_position_and_look, clientbound.play.PlayerPositionAndLookPacket)
register(self.handle_time_update, TimeUpdatePacket)
register(self.handle_time_update, clientbound.play.TimeUpdatePacket)
register(self.handle_set_slot, SetSlotPacket)
register(self.handle_break_animation, BlockBreakAnimationPacket)
register(self.handle_break_ack, AcknowledgePlayerDiggingPacket)
@ -201,8 +211,10 @@ class Game:
register(self.handle_spawn_object, clientbound.play.SpawnObjectPacket)
register(self.handle_entity_metadata, EntityMetadataPacket)
register(self.handle_spawn_living, SpawnLivingEntityPacket)
register(self.handle_entity_position, EntityPositionPacket)
register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket)
register(self.handle_entity_position_rotation, EntityPositionRotationPacket)
register(self.handle_destroy_entities, DestroyEntitiesPacket)
#register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket)
#register(self.handle_packet, Packet, early=True)
@ -224,8 +236,8 @@ class Game:
solution = path.Pathfinder(self.g.chunks).astar(utils.pint(self.g.pos), utils.pint(self.g.goal))
if solution:
solution = list(solution)
#self.g.path = solution
#self.g.job.state = self.g.job.stop
self.g.path = solution
self.g.job.state = self.g.job.stop
print(len(solution))
print(solution)
print(round(time.time() - start, 3), 'seconds')
@ -306,7 +318,7 @@ class Game:
if command == 'gather' and data:
if data == 'wood':
self.g.job.state = self.g.job.lumberjack
self.g.job.state = self.g.job.gather_wood
reply = 'ok'
elif data == 'sand':
self.g.job.state = self.g.job.gather_sand
@ -320,6 +332,19 @@ class Game:
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'
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.state = self.g.job.stop
reply = 'ok'
@ -386,6 +411,11 @@ class Game:
self.g.job.find_gapple_states.count = int(data)
reply = 'ok'
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 reply:
print(reply)
self.g.chat.send(reply)
@ -483,6 +513,23 @@ class Game:
else: #for
return False
def select_random_item(self, items):
# select a random match from items of inv
# this is random per item type
# example: 5 stacks wood, 1 stack glass
# -> still 50/50 chance between them
matches = set()
for slot, item in self.g.inv.items():
if item.item_id in items:
matches.add(item.item_id)
if matches:
return self.select_item([random.choice(list(matches))])
else:
return False
def drop_stack(self):
packet = PlayerDiggingPacket()
packet.status = 3
@ -497,7 +544,7 @@ class Game:
def handle_window(self, packet):
print(packet)
self.g.window = Bunch(data=packet, contents=dict(), count=0)
self.g.window = Munch(data=packet, contents=dict(), count=0)
def click_window(self, slot, button, mode, item):
w = self.g.window
@ -529,41 +576,77 @@ class Game:
self.g.connection.write_packet(packet2)
def handle_spawn_object(self, packet):
return
#return
if packet.type_id != 37: return
print(packet)
self.g.objects[packet.entity_id] = Munch(
entity_id=packet.entity_id,
x=packet.x,
y=packet.y,
z=packet.z,
velocity_x=packet.velocity_x,
velocity_y=packet.velocity_y,
velocity_z=packet.velocity_z,
)
def handle_entity_metadata(self, packet):
if not packet.metadata:
return
def check_gapple(self, packet):
if not self.g.job:
return
current_chest = self.g.job.find_gapple_states.current_chest
if not current_chest:
return
current_gapple_chest = self.g.job.find_gapple_states.current_chest
if current_gapple_chest:
for entry in packet.metadata:
if entry.type != 6:
continue
if entry.value.item_id in items.GAPPLE_ID:
self.g.chat.send('gapple found: ' + str(current_chest)[1:-1])
print('gapple found:', str(current_chest)[1:-1])
self.g.chat.send('gapple found: ' + str(current_gapple_chest)[1:-1])
print('gapple found:', str(current_gapple_chest)[1:-1])
def handle_entity_metadata(self, packet):
if not packet.metadata:
return
self.check_gapple(packet)
obj = self.g.objects.get(packet.entity_id, None)
if obj:
for entry in packet.metadata:
if entry.type != 6:
continue
obj.item_id = entry.value.item_id
obj.item_count = entry.value.item_count
def handle_spawn_living(self, packet):
return
print(packet)
def handle_entity_position(self, packet):
return
print(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
def handle_entity_position_rotation(self, packet):
return
obj = self.g.objects.get(packet.entity_id, None)
if obj:
print('object rotation found:', packet)
raise
def handle_entity_velocity(self, packet):
obj = self.g.objects.get(packet.entity_id, None)
if obj:
print(packet)
#obj.velocity_x = packet.velocity_x
#obj.velocity_y = packet.velocity_y
#obj.velocity_z = packet.velocity_z
def handle_destroy_entities(self, packet):
for eid in packet.entity_ids:
if eid in self.g.objects:
del self.g.objects[eid]
def tick(self):
if self.g.breaking:

View File

@ -22,10 +22,23 @@ BEDS = [
'black_bed',
]
SAPLINGS = [
'oak_sapling',
'spruce_sapling',
'birch_sapling',
'jungle_sapling',
'acacia_sapling',
'dark_oak_sapling',
]
BED_IDS = set()
for item_name in BEDS:
BED_IDS.add(ITEMS['minecraft:'+item_name]['protocol_id'])
SAPLING_IDS = set()
for item_name in SAPLINGS:
SAPLING_IDS.add(ITEMS['minecraft:'+item_name]['protocol_id'])
ITEM_NAMES = {}
for item_name, item in ITEMS.items():
ITEM_NAMES[ITEMS[item_name]['protocol_id']] = item_name.replace('minecraft:', '')

244
jobs.py
View File

@ -1,6 +1,7 @@
import re
import time
import importlib
import random
from math import hypot
from panda3d.core import LPoint3f
@ -120,7 +121,7 @@ class FindGappleStates:
self.state()
class LumberjackStates:
class GatherWoodStates:
def bair(self, p):
return self.g.chunks.get_block_at(*p) in blocks.NON_SOLID_IDS
@ -131,6 +132,7 @@ class LumberjackStates:
return None
def init(self):
self.g.chopped_tree = False
self.state = self.find_new_tree
def find_new_tree(self):
@ -141,9 +143,14 @@ class LumberjackStates:
trees = w.find_trees(p, 100)
print('Found trees:', trees)
try:
while trees[0] in self.bad_trees:
trees.pop(0)
self.tree = trees[0]
except IndexError:
print('No good tress left, aborting.')
self.state = self.cleanup
return
self.openings = w.find_tree_openings(self.tree)
self.state = self.choose_opening
@ -241,6 +248,7 @@ class LumberjackStates:
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.g.chopped_tree = True
self.state = self.cleanup
def cleanup(self):
@ -428,6 +436,7 @@ class SleepWithBedStates:
if self.g.time >= 12542:
print('Sleeping')
self.g.game.place_block(self.area, BlockFace.TOP)
if not self.silent:
self.g.chat.send('zzz')
self.state = self.sleep_bed
@ -465,6 +474,8 @@ class SleepWithBedStates:
self.g = global_state
self.state = self.idle
self.silent = False
self.area = None
self.opening = None
self.bad_areas = []
@ -507,7 +518,7 @@ class CacheItemsStates:
self.state = self.cleanup
return
openings = w.find_bed_openings(self.area)
openings = w.find_cache_openings(self.area)
for o in openings:
navpath = w.path_to_place(p, o)
@ -612,6 +623,182 @@ class CacheItemsStates:
self.state()
class PlantTreeStates:
def idle(self):
return None
def init(self):
if self.g.chopped_tree:
self.state = self.check_feet
else:
print('Aborting planting, did not plant')
self.state = self.cleanup
def check_feet(self):
p = utils.pint(self.g.pos)
# check for air at feet
if self.g.chunks.get_block_at(*p) in [0]:
self.state = self.select_sapling
else:
print('Aborting planting, feet not air')
self.state = self.cleanup
def select_sapling(self):
p = utils.pint(self.g.pos)
if self.g.game.select_random_item(items.SAPLING_IDS):
self.g.look_at = utils.padd(p, path.BLOCK_BELOW)
self.state = self.wait_select
self.wait_time = 1
else:
print('Aborting planting, no saplings')
self.state = self.cleanup
def wait_select(self):
# wait a bit to look down
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.place_sapling
def place_sapling(self):
p = utils.pint(self.g.pos)
self.g.game.place_block(p, BlockFace.TOP)
print('Placed sapling')
self.state = self.wait_place
self.wait_time = 1
def wait_place(self):
# wait a bit for chunk data to update
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.find_open_spot
def find_open_spot(self):
print('Finding an open spot to stand...')
w = self.g.world
p = utils.pint(self.g.pos)
areas = w.find_cache_areas(p, 20)
print('Found areas:', areas)
try:
while areas[0] in self.bad_areas:
areas.pop(0)
self.area = areas[0]
except IndexError:
print('No good areas left, aborting.')
self.bad_areas = []
self.state = self.cleanup
return
navpath = w.path_to_place(p, self.area)
if not navpath:
print('Unable to get to open area', self.area)
self.bad_areas.append(self.area)
self.state = self.cleanup
return
self.g.path = navpath
self.state = self.going_to_area
print('Going to area', self.area)
def going_to_area(self):
if utils.pint(self.g.pos) == self.area:
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.wait_time = 0
self.area = None
self.bad_areas = []
def run(self):
self.state()
class GrabSaplingStates:
def idle(self):
return None
def init(self):
self.state = self.find_saplings
print('Trying to grab a sapling')
def find_saplings(self):
w = self.g.world
saplings = w.find_objects(items.SAPLING_IDS)
if not saplings:
print('No saplings objects found, aborting')
self.state = self.cleanup
return
random.shuffle(saplings)
for s in saplings:
p = utils.pint(self.g.pos)
s_pos = utils.pint((s.x, s.y, s.z))
check = utils.padd(s_pos, path.BLOCK_BELOW)
if s.entity_id in self.eid_blacklist:
continue
# slip if the sapling is floating
if self.g.chunks.get_block_at(*check) in blocks.LEAF_IDS | {0}:
continue
navpath = w.path_to_place(p, s_pos)
if navpath:
self.g.path = navpath
self.state = self.going_to_sapling
self.sapling = s_pos
self.eid_blacklist.append(s.entity_id)
print('Going to sapling', self.sapling)
return
print('Cant get to any saplings, aborting')
self.state = self.cleanup
def going_to_sapling(self):
if utils.pint(self.g.pos) == self.sapling:
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.sapling = None
self.eid_blacklist = []
def run(self):
self.state()
class JobStates:
def idle(self):
return None
@ -651,8 +838,8 @@ class JobStates:
s1.run()
def lumberjack(self):
s1 = self.lumberjack_states
def gather_wood(self):
s1 = self.gather_wood_states
s2 = self.sleep_with_bed_states
s3 = self.cache_items_states
@ -676,12 +863,55 @@ class JobStates:
s1.run()
def farm_wood(self):
self.sleep_with_bed_states.silent = True
s1 = self.gather_wood_states
s2 = self.plant_tree_states
s3 = self.sleep_with_bed_states
s4 = self.cache_items_states
s5 = self.grab_sapling_states
if s1.state == s1.idle:
s1.state = s1.init
s2.state = s2.init
s3.state = s3.init
s4.state = s4.init
s5.state = s5.init
elif s1.state == s1.done:
if s2.state != s2.done:
s2.run()
return
if s3.state != s3.done:
s3.run()
return
if s4.state != s4.done:
s4.run()
return
if s5.state != s5.done:
s5.run()
return
s1.state = s1.init
s2.state = s2.init
s3.state = s3.init
s4.state = s4.init
s5.state = s5.init
return
s1.run()
def stop(self):
self.lumberjack_states = LumberjackStates(self.g)
self.gather_wood_states = GatherWoodStates(self.g)
self.gather_sand_states = GatherSandStates(self.g)
self.sleep_with_bed_states = SleepWithBedStates(self.g)
self.cache_items_states = CacheItemsStates(self.g)
self.find_gapple_states = FindGappleStates(self.g)
self.plant_tree_states = PlantTreeStates(self.g)
self.grab_sapling_states = GrabSaplingStates(self.g)
self.state = self.idle
def __init__(self, global_state):
@ -689,11 +919,13 @@ class JobStates:
self.state = self.idle
self.prev_state = None
self.lumberjack_states = LumberjackStates(self.g)
self.gather_wood_states = GatherWoodStates(self.g)
self.gather_sand_states = GatherSandStates(self.g)
self.sleep_with_bed_states = SleepWithBedStates(self.g)
self.cache_items_states = CacheItemsStates(self.g)
self.find_gapple_states = FindGappleStates(self.g)
self.plant_tree_states = PlantTreeStates(self.g)
self.grab_sapling_states = GrabSaplingStates(self.g)
def tick(self):
self.state()

View File

@ -7,18 +7,19 @@ import json
from flask import Flask
app = Flask(__name__)
from bunch import Bunch
from munch import Munch
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
import bot
global_state = Bunch()
global_state = Munch()
g = global_state
g.connection = False
g.mcdata = False
g.pos = False
g.inv = {}
g.objects = {}
g.window = None
g.job = None

View File

@ -11,7 +11,6 @@ def get_packets(old_get_packets):
mc_packets.add(packets.AcknowledgePlayerDiggingPacket)
mc_packets.add(packets.BlockBreakAnimationPacket)
mc_packets.add(packets.SetSlotPacket)
mc_packets.add(packets.TimeUpdatePacket)
mc_packets.add(packets.PlayerDiggingPacket)
mc_packets.add(packets.PickItemPacket)
mc_packets.add(packets.HeldItemChangePacket)
@ -22,8 +21,8 @@ def get_packets(old_get_packets):
mc_packets.add(packets.ServerWindowConfirmationPacket)
mc_packets.add(packets.EntityMetadataPacket)
mc_packets.add(packets.SpawnLivingEntityPacket)
mc_packets.add(packets.EntityPositionPacket)
mc_packets.add(packets.EntityPositionRotationPacket)
mc_packets.add(packets.DestroyEntitiesPacket)
return mc_packets
return lambda x: wrapper(old_get_packets, x)

View File

@ -174,15 +174,6 @@ class SetSlotPacket(Packet):
]
class TimeUpdatePacket(Packet):
id = 0x4E
packet_name = 'time update'
definition = [
{'world_age': Long},
{'time_of_day': Long},
]
class PlayerDiggingPacket(Packet):
# used when player mines / breaks blocks
# https://wiki.vg/Protocol#Player_Digging
@ -334,24 +325,9 @@ class SpawnLivingEntityPacket(Packet):
{'yaw': Angle},
{'pitch': Angle},
{'head_pitch': Angle},
{'x_velocity': Short},
{'y_velocity': Short},
{'z_velocity': Short},
]
class EntityPositionPacket(Packet):
# Sent by the server when an entity moves less then 8 blocks
# https://wiki.vg/Protocol#Spawn_Entity
id = 0x27
packet_name = 'entity position'
definition = [
{'entity_id': VarInt},
{'delta_x': Short},
{'delta_y': Short},
{'delta_z': Short},
{'on_ground': Boolean},
{'velocity_x': Short},
{'velocity_y': Short},
{'velocity_z': Short},
]
class EntityPositionRotationPacket(Packet):
@ -370,3 +346,18 @@ class EntityPositionRotationPacket(Packet):
{'pitch': Angle},
{'on_ground': Boolean},
]
class DestroyEntitiesPacket(Packet):
# Sent by the server when a list of entities is to be destroyed on the client
# https://wiki.vg/Protocol#Destroy_Entities
id = 0x36
packet_name = 'destroy entities'
fields = 'count', 'entity_ids'
def read(self, file_object):
self.count = VarInt.read(file_object)
self.entity_ids = []
for _ in range(self.count):
eid = VarInt.read(file_object)
self.entity_ids.append(eid)

View File

@ -1,5 +1,4 @@
astar==0.92
bunch==1.0.1
certifi==2020.6.20
cffi==1.14.2
chardet==3.0.4