Add job for farming trees and object physics

This commit is contained in:
Tanner Collin 2020-10-13 20:26:50 -06:00
parent c6c0def867
commit f328f3443a
9 changed files with 361 additions and 38 deletions

View File

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

34
bot.py
View File

@ -22,7 +22,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 bunch import Bunch from munch import Munch
from panda3d.core import LPoint3f, LVector3f from panda3d.core import LPoint3f, LVector3f
import game import game
@ -59,6 +59,37 @@ def tick(global_state):
g.chunks.unload_chunks(p) g.chunks.unload_chunks(p)
########## object physics ##########
for eid, obj in 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): if g.path and len(g.path):
target = LPoint3f(g.path[0]) target = LPoint3f(g.path[0])
target.x += 0.5 target.x += 0.5
@ -162,6 +193,7 @@ def init(global_state):
g.window = None g.window = None
g.job = jobs.JobStates(g) g.job = jobs.JobStates(g)
g.chopped_tree = False
def bot(global_state): def bot(global_state):
g = global_state g = global_state

124
game.py
View File

@ -1,9 +1,10 @@
import re import re
import time import time
import importlib import importlib
import random
from math import hypot from math import hypot
from itertools import count from itertools import count
from bunch import Bunch from munch import Munch
from panda3d.core import LPoint3f from panda3d.core import LPoint3f
@ -17,8 +18,7 @@ from protocol.packets import (
ClickWindowPacket, CloseWindowPacket, ServerWindowConfirmationPacket, ClickWindowPacket, CloseWindowPacket, ServerWindowConfirmationPacket,
ClientWindowConfirmationPacket, EntityMetadataPacket, ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionPacket, SpawnLivingEntityPacket, EntityPositionPacket,
EntityPositionRotationPacket, EntityPositionRotationPacket, DestroyEntitiesPacket, EntityVelocityPacket,
) )
from protocol.types import Slot from protocol.types import Slot
@ -93,7 +93,7 @@ class MCWorld:
result = [] result = []
# TODO: make sure only non-solid and leaves between # 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 distance in range(5):
for direction in path.CHECK_DIRECTIONS: for direction in path.CHECK_DIRECTIONS:
@ -203,6 +203,8 @@ class Game:
register(self.handle_spawn_living, SpawnLivingEntityPacket) register(self.handle_spawn_living, SpawnLivingEntityPacket)
register(self.handle_entity_position, EntityPositionPacket) register(self.handle_entity_position, EntityPositionPacket)
register(self.handle_entity_position_rotation, EntityPositionRotationPacket) register(self.handle_entity_position_rotation, EntityPositionRotationPacket)
register(self.handle_destroy_entities, DestroyEntitiesPacket)
register(self.handle_entity_velocity, EntityVelocityPacket)
#register(self.handle_packet, Packet, early=True) #register(self.handle_packet, Packet, early=True)
@ -224,8 +226,8 @@ class Game:
solution = path.Pathfinder(self.g.chunks).astar(utils.pint(self.g.pos), utils.pint(self.g.goal)) solution = path.Pathfinder(self.g.chunks).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
#self.g.job.state = self.g.job.stop self.g.job.state = self.g.job.stop
print(len(solution)) print(len(solution))
print(solution) print(solution)
print(round(time.time() - start, 3), 'seconds') print(round(time.time() - start, 3), 'seconds')
@ -320,6 +322,19 @@ class Game:
else: else:
reply += ', I need a bed' 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': if command == 'stop':
self.g.job.state = self.g.job.stop self.g.job.state = self.g.job.stop
reply = 'ok' reply = 'ok'
@ -386,6 +401,11 @@ class Game:
self.g.job.find_gapple_states.count = int(data) self.g.job.find_gapple_states.count = int(data)
reply = 'ok' 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: if reply:
print(reply) print(reply)
self.g.chat.send(reply) self.g.chat.send(reply)
@ -483,6 +503,23 @@ class Game:
else: #for else: #for
return False 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): def drop_stack(self):
packet = PlayerDiggingPacket() packet = PlayerDiggingPacket()
packet.status = 3 packet.status = 3
@ -497,7 +534,7 @@ class Game:
def handle_window(self, packet): def handle_window(self, packet):
print(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): def click_window(self, slot, button, mode, item):
w = self.g.window w = self.g.window
@ -529,41 +566,76 @@ class Game:
self.g.connection.write_packet(packet2) self.g.connection.write_packet(packet2)
def handle_spawn_object(self, packet): def handle_spawn_object(self, packet):
return #return
if packet.type_id != 37: return if packet.type_id != 37: return
print(packet) print(packet)
self.g.objects[packet.entity_id] = Munch(
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 check_gapple(self, packet):
if not self.g.job:
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_gapple_chest)[1:-1])
print('gapple found:', str(current_gapple_chest)[1:-1])
def handle_entity_metadata(self, packet): def handle_entity_metadata(self, packet):
if not packet.metadata: if not packet.metadata:
return return
if not self.g.job: self.check_gapple(packet)
return
current_chest = self.g.job.find_gapple_states.current_chest
if not current_chest:
return
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])
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): def handle_spawn_living(self, packet):
return return
print(packet) print(packet)
def handle_entity_position(self, packet): def handle_entity_position(self, packet):
return obj = self.g.objects.get(packet.entity_id, None)
print(packet) 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): def handle_entity_position_rotation(self, packet):
return obj = self.g.objects.get(packet.entity_id, None)
print(packet) 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): def tick(self):
if self.g.breaking: if self.g.breaking:

View File

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

165
jobs.py
View File

@ -131,6 +131,7 @@ class GatherWoodStates:
return None return None
def init(self): def init(self):
self.g.chopped_tree = False
self.state = self.find_new_tree self.state = self.find_new_tree
def find_new_tree(self): def find_new_tree(self):
@ -141,9 +142,14 @@ class GatherWoodStates:
trees = w.find_trees(p, 100) trees = w.find_trees(p, 100)
print('Found trees:', trees) print('Found trees:', trees)
while trees[0] in self.bad_trees: try:
trees.pop(0) while trees[0] in self.bad_trees:
self.tree = trees[0] 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.openings = w.find_tree_openings(self.tree)
self.state = self.choose_opening self.state = self.choose_opening
@ -241,6 +247,7 @@ class GatherWoodStates:
if self.wait_time > 0: if self.wait_time > 0:
self.wait_time -= utils.TICK self.wait_time -= utils.TICK
else: else:
self.g.chopped_tree = True
self.state = self.cleanup self.state = self.cleanup
def cleanup(self): def cleanup(self):
@ -428,7 +435,8 @@ class SleepWithBedStates:
if self.g.time >= 12542: if self.g.time >= 12542:
print('Sleeping') print('Sleeping')
self.g.game.place_block(self.area, BlockFace.TOP) self.g.game.place_block(self.area, BlockFace.TOP)
self.g.chat.send('zzz') if not self.silent:
self.g.chat.send('zzz')
self.state = self.sleep_bed self.state = self.sleep_bed
def sleep_bed(self): def sleep_bed(self):
@ -465,6 +473,8 @@ class SleepWithBedStates:
self.g = global_state self.g = global_state
self.state = self.idle self.state = self.idle
self.silent = False
self.area = None self.area = None
self.opening = None self.opening = None
self.bad_areas = [] self.bad_areas = []
@ -507,7 +517,7 @@ class CacheItemsStates:
self.state = self.cleanup self.state = self.cleanup
return return
openings = w.find_bed_openings(self.area) openings = w.find_cache_openings(self.area)
for o in openings: for o in openings:
navpath = w.path_to_place(p, o) navpath = w.path_to_place(p, o)
@ -612,6 +622,115 @@ class CacheItemsStates:
self.state() self.state()
class PlantTreeStates:
def idle(self):
return None
# TODO: maybe add a "plant deficit" so we know when to plant or not
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 JobStates: class JobStates:
def idle(self): def idle(self):
return None return None
@ -676,12 +795,47 @@ class JobStates:
s1.run() 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
if s1.state == s1.idle:
s1.state = s1.init
s2.state = s2.init
s3.state = s3.init
s4.state = s4.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
s1.state = s1.init
s2.state = s2.init
s3.state = s3.init
s4.state = s4.init
return
s1.run()
def stop(self): def stop(self):
self.gather_wood_states = GatherWoodStates(self.g) self.gather_wood_states = GatherWoodStates(self.g)
self.gather_sand_states = GatherSandStates(self.g) self.gather_sand_states = GatherSandStates(self.g)
self.sleep_with_bed_states = SleepWithBedStates(self.g) self.sleep_with_bed_states = SleepWithBedStates(self.g)
self.cache_items_states = CacheItemsStates(self.g) self.cache_items_states = CacheItemsStates(self.g)
self.find_gapple_states = FindGappleStates(self.g) self.find_gapple_states = FindGappleStates(self.g)
self.plant_tree_states = PlantTreeStates(self.g)
self.state = self.idle self.state = self.idle
def __init__(self, global_state): def __init__(self, global_state):
@ -694,6 +848,7 @@ class JobStates:
self.sleep_with_bed_states = SleepWithBedStates(self.g) self.sleep_with_bed_states = SleepWithBedStates(self.g)
self.cache_items_states = CacheItemsStates(self.g) self.cache_items_states = CacheItemsStates(self.g)
self.find_gapple_states = FindGappleStates(self.g) self.find_gapple_states = FindGappleStates(self.g)
self.plant_tree_states = PlantTreeStates(self.g)
def tick(self): def tick(self):
self.state() self.state()

View File

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

View File

@ -24,6 +24,8 @@ def get_packets(old_get_packets):
mc_packets.add(packets.SpawnLivingEntityPacket) mc_packets.add(packets.SpawnLivingEntityPacket)
mc_packets.add(packets.EntityPositionPacket) mc_packets.add(packets.EntityPositionPacket)
mc_packets.add(packets.EntityPositionRotationPacket) mc_packets.add(packets.EntityPositionRotationPacket)
mc_packets.add(packets.DestroyEntitiesPacket)
mc_packets.add(packets.EntityVelocityPacket)
return mc_packets return mc_packets
return lambda x: wrapper(old_get_packets, x) return lambda x: wrapper(old_get_packets, x)

View File

@ -334,9 +334,9 @@ class SpawnLivingEntityPacket(Packet):
{'yaw': Angle}, {'yaw': Angle},
{'pitch': Angle}, {'pitch': Angle},
{'head_pitch': Angle}, {'head_pitch': Angle},
{'x_velocity': Short}, {'velocity_x': Short},
{'y_velocity': Short}, {'velocity_y': Short},
{'z_velocity': Short}, {'velocity_z': Short},
] ]
class EntityPositionPacket(Packet): class EntityPositionPacket(Packet):
@ -370,3 +370,32 @@ class EntityPositionRotationPacket(Packet):
{'pitch': Angle}, {'pitch': Angle},
{'on_ground': Boolean}, {'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)
class EntityVelocityPacket(Packet):
# Sent to update entity's velocity
# https://wiki.vg/Protocol#Entity_Velocity
id = 0x46
packet_name = 'entity velocity'
definition = [
{'entity_id': VarInt},
{'velocity_x': Short},
{'velocity_y': Short},
{'velocity_z': Short},
]

View File

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