Add state machine for caching items into chests
This commit is contained in:
parent
b8cbd9a5f0
commit
f8d44e7e38
15
blocks.py
15
blocks.py
|
@ -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
9
bot.py
|
@ -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
26
data.py
Normal 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
66
game.py
|
@ -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
|
||||
|
||||
|
|
4
items.py
4
items.py
|
@ -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
177
jobs.py
|
@ -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
12
main.py
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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},
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
11
utils.py
11
utils.py
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user