Add inventory controls and enhance dashboard
This commit is contained in:
+19
-1
@@ -289,6 +289,11 @@ def init(global_state):
|
||||
g.break_time = 0
|
||||
|
||||
g.dumping = None
|
||||
g.dump_waiting = None
|
||||
g.dump_dropped = 0
|
||||
g.dump_total = 0
|
||||
g.dump_error = None
|
||||
g.drop_waiting = None
|
||||
g.draining = False
|
||||
g.item_lock = False
|
||||
g.command_lock = False
|
||||
@@ -413,6 +418,12 @@ def start_automation(global_state, initialize=True):
|
||||
g.item_lock = False
|
||||
g.minimum_cache_slots = g.get('minimum_cache_slots', 27)
|
||||
g.draining = False
|
||||
g.dumping = None
|
||||
g.dump_waiting = None
|
||||
g.dump_dropped = 0
|
||||
g.dump_total = 0
|
||||
g.dump_error = None
|
||||
g.drop_waiting = None
|
||||
g.drain_slots = []
|
||||
g.drain_waiting = None
|
||||
g.drain_dropped = 0
|
||||
@@ -437,6 +448,12 @@ def start_automation(global_state, initialize=True):
|
||||
# Preserve active work during hot reloads, but give the worker all fields
|
||||
# introduced by the currently migrated inventory controls.
|
||||
g.setdefault('draining', False)
|
||||
g.setdefault('dumping', None)
|
||||
g.setdefault('dump_waiting', None)
|
||||
g.setdefault('dump_dropped', 0)
|
||||
g.setdefault('dump_total', 0)
|
||||
g.setdefault('dump_error', None)
|
||||
g.setdefault('drop_waiting', None)
|
||||
g.setdefault('drain_slots', [])
|
||||
g.setdefault('drain_waiting', None)
|
||||
g.setdefault('drain_dropped', 0)
|
||||
@@ -485,7 +502,8 @@ def start_automation(global_state, initialize=True):
|
||||
movement_ready = initial_world_ready
|
||||
job_active = (g.job is not None and g.job.state != g.job.idle)
|
||||
has_work = (bool(g.path) or job_active or bool(g.breaking) or
|
||||
bool(g.draining))
|
||||
bool(g.draining) or g.get('dumping') is not None or
|
||||
bool(g.get('drop_waiting')))
|
||||
if g.get('movement_paused'):
|
||||
set_status('paused')
|
||||
elif has_work and not movement_ready:
|
||||
|
||||
+7
-9
@@ -67,8 +67,7 @@ class Commands:
|
||||
|
||||
unsupported = {
|
||||
'gather', 'trade', 'gapple', 'fill',
|
||||
'drop', 'dump', 'break', 'open', 'click', 'use',
|
||||
'interact',
|
||||
'break', 'open', 'click', 'use', 'interact',
|
||||
}
|
||||
if (command in unsupported or
|
||||
(command == 'farm' and not data.lower().startswith('crop'))):
|
||||
@@ -471,7 +470,7 @@ class Commands:
|
||||
|
||||
## 1drop - drops the current stack its holding
|
||||
if command == 'drop':
|
||||
self.g.game.drop_stack()
|
||||
reply = 'ok' if self.g.game.drop_stack() else 'empty or busy'
|
||||
|
||||
## 1select [id] - moves item with id into main hand
|
||||
if command == 'select' and data:
|
||||
@@ -484,16 +483,15 @@ class Commands:
|
||||
## 1dump [id] - drops all items matching id
|
||||
if command == 'dump' and data:
|
||||
item = int(data)
|
||||
if self.g.game.count_items([item]):
|
||||
self.g.dumping = item
|
||||
reply = 'ok'
|
||||
else:
|
||||
reply = 'not found'
|
||||
count = self.g.game.start_dump(item)
|
||||
reply = ('busy' if count is None else
|
||||
'ok' if count else 'not found')
|
||||
|
||||
## 1drain - drops all items in inventory
|
||||
if command == 'drain':
|
||||
count = self.g.game.start_drain()
|
||||
reply = 'ok' if count else 'empty'
|
||||
reply = ('busy' if count is None else
|
||||
'ok' if count else 'empty')
|
||||
|
||||
if command == 'gapple':
|
||||
self.g.job.state = self.g.job.find_gapple
|
||||
|
||||
+133
-2
@@ -8,7 +8,7 @@ from minecraft.networking.packets import Packet, clientbound, serverbound
|
||||
from minecraft.networking.types import BlockFace, Byte, Short, VarInt
|
||||
from minecraft.networking.modern import (
|
||||
ClientTickEnd, ContainerClickSwapPacket, PlayerActionPacket,
|
||||
SetCarriedItemPacket, UseItemOnBlockPacket,
|
||||
SetCarriedItemPacket, SetHeldSlotPacket, UseItemOnBlockPacket,
|
||||
)
|
||||
|
||||
from mosfet.protocol.packets import (
|
||||
@@ -45,7 +45,7 @@ class ContainerClickDropPacket(Packet):
|
||||
VarInt.send(self.container_id, packet_buffer)
|
||||
VarInt.send(self.state_id, packet_buffer)
|
||||
Short.send(self.slot, packet_buffer)
|
||||
Byte.send(1, packet_buffer) # drop the whole stack
|
||||
Byte.send(getattr(self, 'button', 1), packet_buffer)
|
||||
VarInt.send(4, packet_buffer) # ContainerInput.THROW
|
||||
VarInt.send(0, packet_buffer) # no changed-slot predictions
|
||||
VarInt.send(0, packet_buffer) # empty carried item stack
|
||||
@@ -166,6 +166,16 @@ class LegacyGame:
|
||||
if new_path:
|
||||
self.g.path = new_path
|
||||
|
||||
def handle_set_held_slot(self, packet):
|
||||
slot = int(packet.slot)
|
||||
if not 0 <= slot <= 8:
|
||||
return
|
||||
self.g.holding_slot = slot
|
||||
self.g.holding_hand = 0
|
||||
item = self.g.inv.get(36 + slot)
|
||||
self.g.holding = (item.item_id if item and
|
||||
getattr(item, 'present', False) else 0)
|
||||
|
||||
def handle_time_update(self, packet):
|
||||
if hasattr(packet, 'time_of_day'):
|
||||
self.g.time = packet.time_of_day % 24000
|
||||
@@ -665,6 +675,8 @@ class Game(LegacyGame):
|
||||
self.handle_set_health, SetHealthPacket)
|
||||
self.g.connection.register_packet_listener(
|
||||
self.handle_time_update, UpdateTimePacket)
|
||||
self.g.connection.register_packet_listener(
|
||||
self.handle_set_held_slot, SetHeldSlotPacket)
|
||||
|
||||
if not self.g.get('health_update_received', False):
|
||||
self.g.health = None
|
||||
@@ -717,6 +729,118 @@ class Game(LegacyGame):
|
||||
ClientCommandPacket(action_id=0), force=True)
|
||||
self.g.connection.write_packet(ClientTickEnd(), force=True)
|
||||
|
||||
def _send_drop_click(self, slot, button=1):
|
||||
state_id = self.g.inventory_state_ids.get(0, 0)
|
||||
self.g.connection.write_packet(ContainerClickDropPacket(
|
||||
container_id=0, state_id=state_id, slot=slot,
|
||||
button=button), force=True)
|
||||
self.g.connection.write_packet(ClientTickEnd(), force=True)
|
||||
return state_id
|
||||
|
||||
def drop_inventory_slot(self, slot, whole_stack=True):
|
||||
"""Drop one item or the full stack from a player inventory slot."""
|
||||
try:
|
||||
slot = int(slot)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
if (not 5 <= slot <= 45 or self.g.get('window') is not None or
|
||||
self.g.get('draining') or self.g.get('dumping') is not None or
|
||||
self.g.get('drop_waiting')):
|
||||
return False
|
||||
item = self.g.inv.get(slot)
|
||||
if not item or not getattr(item, 'present', False):
|
||||
return False
|
||||
count = int(item.item_count or 0)
|
||||
if count <= 0:
|
||||
return False
|
||||
button = 1 if whole_stack else 0
|
||||
state_id = self._send_drop_click(slot, button)
|
||||
self.g.drop_waiting = {
|
||||
'slot': slot, 'item_id': item.item_id,
|
||||
'expected_count': 0 if whole_stack else count - 1,
|
||||
'sent_at': time.monotonic(), 'state_id': state_id,
|
||||
}
|
||||
return True
|
||||
|
||||
def drop_stack(self):
|
||||
"""Drop the full stack in the currently held hand (26.3 click)."""
|
||||
slot = (45 if self.g.get('holding_hand', 0) == 1 else
|
||||
36 + int(self.g.get('holding_slot', 0) or 0))
|
||||
return self.drop_inventory_slot(slot, whole_stack=True)
|
||||
|
||||
def _tick_single_drop(self):
|
||||
waiting = self.g.get('drop_waiting')
|
||||
if not waiting:
|
||||
return
|
||||
item = self.g.inv.get(waiting['slot'])
|
||||
remaining = (item.item_count if item and
|
||||
getattr(item, 'present', False) and
|
||||
item.item_id == waiting['item_id'] else 0)
|
||||
if remaining <= waiting['expected_count']:
|
||||
print('[drop] server confirmed slot {} updated'.format(
|
||||
waiting['slot']), flush=True)
|
||||
self.g.drop_waiting = None
|
||||
elif time.monotonic() - waiting['sent_at'] > 3.0:
|
||||
print('[drop] timed out waiting for inventory update on slot {}'.format(
|
||||
waiting['slot']), flush=True)
|
||||
self.g.drop_waiting = None
|
||||
|
||||
def start_dump(self, item_id):
|
||||
"""Start dropping every stack of an item id from player inventory."""
|
||||
if (self.g.get('window') is not None or self.g.get('draining') or
|
||||
self.g.get('dumping') is not None or self.g.get('drop_waiting')):
|
||||
return None
|
||||
slots = [slot for slot, item in self.g.inv.items()
|
||||
if 5 <= slot <= 45 and getattr(item, 'present', False)
|
||||
and item.item_id == item_id]
|
||||
if not slots:
|
||||
return 0
|
||||
self.g.dumping = item_id
|
||||
self.g.dump_waiting = None
|
||||
self.g.dump_dropped = 0
|
||||
self.g.dump_total = len(slots)
|
||||
self.g.dump_error = None
|
||||
return len(slots)
|
||||
|
||||
def _tick_dump(self):
|
||||
item_id = self.g.get('dumping')
|
||||
if item_id is None:
|
||||
return
|
||||
waiting = self.g.get('dump_waiting')
|
||||
if waiting:
|
||||
item = self.g.inv.get(waiting['slot'])
|
||||
if (not item or not getattr(item, 'present', False) or
|
||||
item.item_id != item_id):
|
||||
self.g.dump_dropped += 1
|
||||
print('[dump] dropped stack from slot {}; {}/{} complete'.format(
|
||||
waiting['slot'], self.g.dump_dropped,
|
||||
self.g.dump_total), flush=True)
|
||||
self.g.dump_waiting = None
|
||||
elif time.monotonic() - waiting['sent_at'] > 3.0:
|
||||
self.g.dump_error = 'server did not clear inventory slot {}'.format(
|
||||
waiting['slot'])
|
||||
print('[dump] stopped: ' + self.g.dump_error, flush=True)
|
||||
self.g.dumping = None
|
||||
self.g.dump_waiting = None
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
for slot in range(5, 46):
|
||||
item = self.g.inv.get(slot)
|
||||
if item and getattr(item, 'present', False) and item.item_id == item_id:
|
||||
state_id = self._send_drop_click(slot)
|
||||
self.g.dump_waiting = {
|
||||
'slot': slot, 'item_count': item.item_count,
|
||||
'sent_at': time.monotonic(), 'state_id': state_id,
|
||||
}
|
||||
return
|
||||
|
||||
print('[dump] complete: dropped {} stacks of item {}'.format(
|
||||
self.g.dump_dropped, item_id), flush=True)
|
||||
self.g.dumping = None
|
||||
self.g.dump_waiting = None
|
||||
|
||||
def leave_bed(self):
|
||||
self._send_action(PlayerCommandPacket(
|
||||
entity_id=self.g.eid, action_id=0, jump_boost=0))
|
||||
@@ -728,6 +852,10 @@ class Game(LegacyGame):
|
||||
pitch=float(self.g.get('pitch', 0.0))))
|
||||
|
||||
def start_drain(self):
|
||||
if (self.g.get('window') is not None or
|
||||
self.g.get('dumping') is not None or
|
||||
self.g.get('drop_waiting')):
|
||||
return None
|
||||
slots = [slot for slot, item in sorted(self.g.inv.items())
|
||||
if 9 <= slot <= 45 and getattr(item, 'present', False)]
|
||||
self.g.drain_slots = slots
|
||||
@@ -787,6 +915,9 @@ class Game(LegacyGame):
|
||||
self.g.drain_dropped), flush=True)
|
||||
|
||||
def tick(self):
|
||||
self._tick_single_drop()
|
||||
if self.g.get('dumping') is not None:
|
||||
self._tick_dump()
|
||||
if self.g.get('draining'):
|
||||
self._tick_drain()
|
||||
if not self.g.get('breaking') or self.break_deadline is None:
|
||||
|
||||
@@ -471,6 +471,7 @@ class InventoryManager:
|
||||
self.g.inv = slots
|
||||
self.g.inventory_partial = packet.partial
|
||||
self.g.cursor = self.to_slot(packet.carried_item)
|
||||
self._sync_held_item()
|
||||
elif (self.g.get('window') and
|
||||
packet.container_id == self.g.window.window_id):
|
||||
self.g.window.contents = slots
|
||||
@@ -488,6 +489,9 @@ class InventoryManager:
|
||||
if packet.container_id == 0:
|
||||
self.g.inv[packet.slot] = item
|
||||
self.g.item_lock = False
|
||||
if (self.g.get('holding_hand', 0) == 0 and
|
||||
packet.slot == 36 + self.g.get('holding_slot', 0)):
|
||||
self.g.holding = item.item_id if item.present else 0
|
||||
elif packet.container_id == -1 and packet.slot == -1:
|
||||
self.g.cursor = item
|
||||
elif (self.g.get('window') and
|
||||
@@ -501,6 +505,14 @@ class InventoryManager:
|
||||
self.g.inv[36 + offset - 27] = item
|
||||
self.g.item_lock = False
|
||||
|
||||
def _sync_held_item(self):
|
||||
if self.g.get('holding_hand', 0) != 0:
|
||||
return
|
||||
hotbar_slot = int(self.g.get('holding_slot', 0) or 0)
|
||||
item = self.g.inv.get(36 + hotbar_slot)
|
||||
self.g.holding = (item.item_id if item and
|
||||
getattr(item, 'present', False) else 0)
|
||||
|
||||
def handle_cursor(self, packet):
|
||||
if packet.item.components_unknown:
|
||||
self.g.inventory_partial = True
|
||||
|
||||
Reference in New Issue
Block a user