Add state machine for caching items into chests

This commit is contained in:
Tanner Collin 2020-09-20 19:08:23 -06:00
parent b8cbd9a5f0
commit f8d44e7e38
12 changed files with 326 additions and 59 deletions

View File

@ -1,11 +1,12 @@
import minecraft_data
import json
import importlib
mcd = minecraft_data('1.16.2')
import data
importlib.reload(data)
MCD_BLOCKS = {}
for data in mcd.blocks.values():
MCD_BLOCKS[data['name']] = data
for d in data.mcd.blocks.values():
MCD_BLOCKS[d['name']] = d
with open('mcdata/blocks.json') as f:
JSON_BLOCKS = json.load(f)
@ -18,9 +19,11 @@ for name, data in JSON_BLOCKS.items():
AIR = 0
SAND = 66
SINGLE_SNOW = 3921
SOUL_TORCH = 4008
#SOUL_TORCH = 4008
SOUL_TORCH = 1435
TEST_BLOCK = (616, 78, 496)
#TEST_BLOCK = (616, 78, 496)
TEST_BLOCK = (-100, 64, -167)
AVOID = [

9
bot.py
View File

@ -10,6 +10,7 @@ from math import floor, ceil
USERNAME = os.environ['USERNAME']
PASSWORD = os.environ['PASSWORD']
SERVER = os.environ['SERVER']
PORT = int(os.environ.get('PORT', 25565))
import monkey_patch # must be before any possible pyCraft imports
@ -154,7 +155,9 @@ def init(global_state):
g.break_time = 0
g.dumping = None
g.dump_lock = False
g.item_lock = False
g.window = None
g.job = jobs.JobStates(g)
@ -172,7 +175,7 @@ def bot(global_state):
print(e)
sys.exit()
print("Logged in as %s..." % auth_token.username)
g.connection = Connection(SERVER, 25565, auth_token=auth_token)
g.connection = Connection(SERVER, PORT, auth_token=auth_token)
g.chunks = ChunksManager(g.mcdata)
@ -198,8 +201,6 @@ def bot(global_state):
init(g)
print('Initialized.')
print(blocks.mcd.windows)
while g.running:
tick(g)

26
data.py Normal file
View File

@ -0,0 +1,26 @@
import json
from bunch import Bunch
import minecraft_data
mcd = minecraft_data('1.16.2')
with open('mcdata/registries.json') as f:
DATA = json.load(f)
SINGLE_CHEST = 2
DOUBLE_CHEST = 5
WINDOWS = {
SINGLE_CHEST: Bunch(
container=list(range(0, 27)),
inventory=list(range(27, 63)),
slot_diff=18,
),
DOUBLE_CHEST: Bunch(
container=list(range(0, 54)),
inventory=list(range(54, 90)),
slot_diff=45,
),
}

66
game.py
View File

@ -3,13 +3,14 @@ import time
import importlib
from math import hypot
from itertools import count
from bunch import Bunch
from panda3d.core import LPoint3f
from minecraft.networking.packets import Packet, clientbound, serverbound
from minecraft.networking.types import BlockFace
from protocol.packets import TimeUpdatePacket, SetSlotPacket, PlayerDiggingPacket, BlockBreakAnimationPacket, AcknowledgePlayerDiggingPacket, HeldItemChangePacket, PickItemPacket
from protocol.packets import TimeUpdatePacket, SetSlotPacket, PlayerDiggingPacket, BlockBreakAnimationPacket, AcknowledgePlayerDiggingPacket, HeldItemChangePacket, PickItemPacket, OpenWindowPacket, ClickWindowPacket, CloseWindowPacket
import utils
importlib.reload(utils)
@ -19,6 +20,8 @@ import blocks
importlib.reload(blocks)
import items
importlib.reload(items)
import data
importlib.reload(data)
class MCWorld:
def __init__(self, global_state):
@ -122,6 +125,9 @@ class MCWorld:
areas.sort(key=lambda x: utils.phyp(center, x))
return areas
def find_cache_areas(self, center, distance):
return self.find_bed_areas(center, distance)
def sand_adjacent_safe(self, sand):
for direction in path.CHECK_DIRECTIONS:
if self.block_at(*utils.padd(sand, direction)) in blocks.AVOID_IDS:
@ -163,6 +169,9 @@ class MCWorld:
result.append(utils.padd(area, direction))
return result
def find_cache_openings(self, area):
return self.find_bed_openings(area)
class Game:
def __init__(self, global_state):
@ -176,6 +185,7 @@ class Game:
register(self.handle_set_slot, SetSlotPacket)
register(self.handle_break_animation, BlockBreakAnimationPacket)
register(self.handle_break_ack, AcknowledgePlayerDiggingPacket)
register(self.handle_window, OpenWindowPacket)
self.g.chat.set_handler(self.handle_chat)
@ -314,7 +324,22 @@ class Game:
reply = 'not found'
if command == 'open':
self.place_block(blocks.TEST_BLOCK, BlockFace.TOP)
self.open_container(blocks.TEST_BLOCK)
if command == 'close':
if self.g.window:
self.close_window()
else:
reply = 'nothing open'
if command == 'click' and data:
if self.g.window:
window_id, slot, button, mode = [int(x) for x in data.split(' ')]
item = self.g.window.contents[slot]
print(item)
self.click_window(window_id, slot, button, mode, item)
else:
reply = 'nothing open'
if reply:
print(reply)
@ -324,12 +349,15 @@ class Game:
self.g.time = packet.time_of_day % 24000
def handle_set_slot(self, packet):
g = self.g
print(packet)
if packet.window_id == 0:
self.g.inv[packet.slot] = packet.slot_data
g.inv[packet.slot] = packet.slot_data
elif g.window:
g.window.contents[packet.slot] = packet.slot_data
if not packet.slot_data.present:
self.g.dump_lock = False
g.item_lock = False
def break_block(self, location):
bid = self.g.chunks.get_block_at(*location)
@ -416,6 +444,32 @@ class Game:
packet.face = 1
self.g.connection.write_packet(packet)
def open_container(self, location):
bid = self.g.chunks.get_block_at(*location)
# TODO: check if block is a chest??
self.place_block(location, BlockFace.TOP)
def handle_window(self, packet):
print(packet)
self.g.window = Bunch(data=packet, contents=dict())
def click_window(self, window_id, slot, button, mode, item):
packet = ClickWindowPacket()
packet.window_id = window_id
packet.slot = slot
packet.button = button
packet.action_number = 1
packet.mode = mode
packet.clicked_item = item
self.g.connection.write_packet(packet)
def close_window(self):
packet = CloseWindowPacket()
packet.window_id = self.g.window.data.window_id
self.g.connection.write_packet(packet)
self.g.window = None
def tick(self):
if self.g.breaking:
self.animate()
@ -423,10 +477,10 @@ class Game:
if time.time() >= self.g.break_time - 2*utils.TICK:
self.break_finish()
if self.g.dumping and not self.g.dump_lock:
if self.g.dumping and not self.g.item_lock:
if self.select_item([self.g.dumping]):
self.drop_stack()
self.g.dump_lock = True
self.g.item_lock = True
else:
self.g.dumping = None

View File

@ -29,3 +29,7 @@ for item_name in BEDS:
ITEM_NAMES = {}
for item_name, item in ITEMS.items():
ITEM_NAMES[ITEMS[item_name]['protocol_id']] = item_name.replace('minecraft:', '')
CHEST_ID = set([ITEMS['minecraft:chest']['protocol_id']])
USEFUL_ITEMS = BED_IDS | CHEST_ID

177
jobs.py
View File

@ -310,7 +310,7 @@ class SleepWithBedStates:
self.state = self.select_bed
def select_bed(self):
if self.game.select_item(items.BED_IDS):
if self.g.game.select_item(items.BED_IDS):
self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW)
self.state = self.place_bed
else:
@ -372,32 +372,148 @@ class SleepWithBedStates:
self.state()
class JobStates:
class CacheItemsStates:
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
def init(self):
#if len(self.g.inv) >= 27:
if len(self.g.inv) >= 2:
self.state = self.find_chest_spot
else:
print('Aborting caching, not full')
self.state = self.cleanup
if self.prev_state:
print('Reverting to prev state')
self.state = self.prev_state
def find_cache_spot(self):
print('Finding a chest spot...')
w = self.g.world
p = utils.pint(self.g.pos)
areas = w.find_cache_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
s.run()
openings = w.find_bed_openings(self.area)
for o in openings:
navpath = w.path_to_place(p, o)
self.opening = o
if navpath: break
else: # for
print('Unable to get to cache area', self.area)
self.bad_areas.append(self.area)
self.state = self.cleanup
return
self.g.path = navpath
self.state = self.going_to_area
self.last_area = self.area
def going_to_area(self):
if utils.pint(self.g.pos) == self.opening:
self.g.look_at = self.area
self.state = self.select_chest
def select_chest(self):
if self.g.game.select_item(items.CHEST_ID):
self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW)
self.state = self.place_chest
else:
self.g.chat.send('I need a chest')
self.state = self.cleanup
def place_chest(self):
self.g.game.place_block(self.area, BlockFace.TOP)
self.state = self.open_chest
def open_chest(self):
print('Opening chest')
self.g.game.open_container(self.area)
self.wait_time = 1
self.state = self.wait
def wait(self):
# wait for server to send us chest contents
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.move_items
def move_items(self):
if self.g.item_lock: return
w = self.g.window
w_data = data.WINDOWS[w.window_type]
w_inventory_slots = w_data.inventory
for slot_num in w_inventory_slots:
if slot_num not in w.contents:
continue
slot = w.contents[slot_num]
if slot.item_id in items.USEFUL_ITEMS:
continue
print('moving', slot)
inv_slot_num = slot_num - w_data.slot_diff
self.g.inv.pop(inv_slot_num, None)
self.g.item_lock = True
self.g.game.click_window(w.window_id, slot_num, 0, 1, slot)
return
print('nothing left to move')
self.state = close_chest
def close_chest(self):
print('closing chest')
self.g.game.close_window()
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.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 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
#s.state = s.init
self.prev_state = self.gather_sand
self.state = self.sleep_with_bed
@ -406,23 +522,35 @@ class JobStates:
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
s1 = self.lumberjack_states
s2 = self.sleep_with_bed_states
s3 = self.cache_items_states
self.prev_state = self.lumberjack
self.state = self.sleep_with_bed
if s1.state == s1.idle:
s1.state = s1.init
s2.state = s2.init
s3.state = s3.init
elif s1.state == s1.done:
if s2.state != s2.done:
s2.run()
return
s.run()
if s3.state != s3.done:
s3.run()
return
s1.state = s1.init
s2.state = s2.init
s3.state = s3.init
return
s1.run()
def stop(self):
self.lumberjack_states = LumberjackStates(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.state = self.idle
def __init__(self, global_state):
@ -433,6 +561,7 @@ class JobStates:
self.lumberjack_states = LumberjackStates(self.g)
self.gather_sand_states = GatherSandStates(self.g)
self.sleep_with_bed_states = SleepWithBedStates(self.g)
self.cache_items_states = CacheItemsStates(self.g)
def tick(self):
self.state()

12
main.py
View File

@ -2,6 +2,7 @@ import importlib
import threading
import time
import traceback
import json
from flask import Flask
app = Flask(__name__)
@ -21,9 +22,14 @@ g.inv = {}
@app.route('/')
def hello_world():
#print(chunks.chunks)
return str(g.chunks.get_block_at(84,62,54))
#return 'ok'
data = json.dumps(g, default=lambda o: str(o), indent=4)
response = app.response_class(
response=data,
status=200,
mimetype='application/json'
)
return response
reload_timeout = time.time()

View File

@ -15,6 +15,9 @@ def get_packets(old_get_packets):
mc_packets.add(packets.PlayerDiggingPacket)
mc_packets.add(packets.PickItemPacket)
mc_packets.add(packets.HeldItemChangePacket)
mc_packets.add(packets.OpenWindowPacket)
mc_packets.add(packets.CloseWindowPacket)
mc_packets.add(packets.ClickWindowPacket)
return mc_packets
return lambda x: wrapper(old_get_packets, x)

View File

@ -112,6 +112,8 @@ class ChatManager:
return ''.join([self.translate_chat(x) for x in data['extra']])
elif 'text' in data:
return data['text']
elif 'with' in data:
return '<{}> {}'.format(*[self.translate_chat(x) for x in data['with']])
elif 'translate' in data:
return data['translate']
else:

View File

@ -4,7 +4,7 @@ from minecraft.networking.packets import Packet, PacketBuffer
from minecraft.networking.types import (
VarInt, Integer, UnsignedByte, Position, Vector, MutableRecord,
attribute_alias, multi_attribute_alias, Long, Boolean, VarLong,
Short, UnsignedLong, Byte, BlockFace,
Short, UnsignedLong, Byte, BlockFace, String
)
from protocol.types import Nbt, Slot
@ -217,3 +217,45 @@ class HeldItemChangePacket(Packet):
definition = [
{'slot': Short},
]
class OpenWindowPacket(Packet):
# Sent to the client when it should open an inventory, such as a chest, workbench, or furnace
# https://wiki.vg/Protocol#Open_Window
id = 0x2D
packet_name = 'open window'
definition = [
{'window_id': VarInt},
{'window_type': VarInt},
{'window_title': String},
]
class CloseWindowPacket(Packet):
# Sent by the client when closing a window
# https://wiki.vg/Protocol#Close_Window_.28serverbound.29
id = 0x0A
packet_name = 'close window'
definition = [
{'window_id': UnsignedByte},
]
class ClickWindowPacket(Packet):
# Sent by the player when it clicks on a slot in a window
# https://wiki.vg/Protocol#Click_Window
id = 0x09
packet_name = 'click window'
definition = [
{'window_id': UnsignedByte},
{'slot': Short},
{'button': Byte},
{'action_number': Short},
{'mode': VarInt},
{'clicked_item': Slot},
]

View File

@ -120,14 +120,10 @@ class Slot(Type):
item_count = Byte.read(file_object) if present else None
nbt = TrailingByteArray.read(file_object) if present else None
return Slot(present, item_id, item_count, nbt)
#a = {}
#a['present'] = Boolean.read(file_object)
#a['item_id'] = VarInt.read(file_object) if a['present'] else None
#a['item_count'] = Byte.read(file_object) if a['present'] else None
#a['nbt'] = TrailingByteArray.read(file_object) if a['present'] else None
#return a
@staticmethod
def send(value, socket):
# TODO
pass
Boolean.send(value.present, socket)
VarInt.send(value.item_id, socket)
Byte.send(value.item_count, socket)
TrailingByteArray.send(value.nbt, socket)

View File

@ -3,6 +3,7 @@ from math import floor, ceil, sqrt, hypot
import blocks
importlib.reload(blocks)
import data
TICK = 0.05
@ -65,13 +66,13 @@ def diffrange(n):
def break_time(block_id, held_item=0, in_water=False, on_ground=True, enchantments=[], effects={}):
# from PrismarineJS/prismarine-block
data = blocks.get(block_id)
block_data = blocks.get(block_id)
can_harvest = 'harvestTools' not in data or str(held_item) in data['harvestTools']
material = data.get('material', 'n/a')
tool_multipliers = blocks.mcd.materials.get(material, [])
can_harvest = 'harvestTools' not in block_data or str(held_item) in block_data['harvestTools']
material = block_data.get('material', 'n/a')
tool_multipliers = data.mcd.materials.get(material, [])
is_best_tool = held_item in tool_multipliers
time = data['hardness']
time = block_data['hardness']
if can_harvest:
time *= 1.5