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',
'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]

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 bunch import Bunch
from munch import Munch
from panda3d.core import LPoint3f, LVector3f
import game
@ -59,6 +59,37 @@ def tick(global_state):
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):
target = LPoint3f(g.path[0])
target.x += 0.5
@ -162,6 +193,7 @@ def init(global_state):
g.window = None
g.job = jobs.JobStates(g)
g.chopped_tree = False
def bot(global_state):
g = global_state

124
game.py
View File

@ -1,9 +1,10 @@
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 panda3d.core import LPoint3f
@ -17,8 +18,7 @@ from protocol.packets import (
ClickWindowPacket, CloseWindowPacket, ServerWindowConfirmationPacket,
ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionPacket,
EntityPositionRotationPacket,
EntityPositionRotationPacket, DestroyEntitiesPacket, EntityVelocityPacket,
)
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:
@ -203,6 +203,8 @@ class Game:
register(self.handle_spawn_living, SpawnLivingEntityPacket)
register(self.handle_entity_position, EntityPositionPacket)
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)
@ -224,8 +226,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')
@ -320,6 +322,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 +401,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 +503,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 +534,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 +566,76 @@ 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(
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):
if not packet.metadata:
return
if not self.g.job:
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])
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
print(packet)
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:', '')

165
jobs.py
View File

@ -131,6 +131,7 @@ class GatherWoodStates:
return None
def init(self):
self.g.chopped_tree = False
self.state = self.find_new_tree
def find_new_tree(self):
@ -141,9 +142,14 @@ class GatherWoodStates:
trees = w.find_trees(p, 100)
print('Found trees:', trees)
while trees[0] in self.bad_trees:
trees.pop(0)
self.tree = trees[0]
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 +247,7 @@ class GatherWoodStates:
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.g.chopped_tree = True
self.state = self.cleanup
def cleanup(self):
@ -428,7 +435,8 @@ class SleepWithBedStates:
if self.g.time >= 12542:
print('Sleeping')
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
def sleep_bed(self):
@ -465,6 +473,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 +517,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 +622,115 @@ class CacheItemsStates:
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:
def idle(self):
return None
@ -676,12 +795,47 @@ 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
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):
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.state = self.idle
def __init__(self, global_state):
@ -694,6 +848,7 @@ class JobStates:
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)
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

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

View File

@ -334,9 +334,9 @@ class SpawnLivingEntityPacket(Packet):
{'yaw': Angle},
{'pitch': Angle},
{'head_pitch': Angle},
{'x_velocity': Short},
{'y_velocity': Short},
{'z_velocity': Short},
{'velocity_x': Short},
{'velocity_y': Short},
{'velocity_z': Short},
]
class EntityPositionPacket(Packet):
@ -370,3 +370,32 @@ 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)
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
bunch==1.0.1
certifi==2020.6.20
cffi==1.14.2
chardet==3.0.4