Port inventory components to Minecraft 26.3
This commit is contained in:
@@ -34,10 +34,10 @@ AuthMe password. Override `SERVER`, `PORT`, `PLAYER_NAME`, or `GAME_PASSWORD`
|
|||||||
through environment variables as needed. Microsoft authentication is not used.
|
through environment variables as needed. Microsoft authentication is not used.
|
||||||
|
|
||||||
The 26.3 client now handles AuthMe, chat commands, chunk/world-state caching,
|
The 26.3 client now handles AuthMe, chat commands, chunk/world-state caching,
|
||||||
modern block/item/entity data, basic entity tracking, and path-following movement
|
modern block/item/entity data, entity tracking, read-only inventory/component
|
||||||
(`1goto x y z`).
|
sync, and path-following movement (`1goto x y z`). Inventory actions, block
|
||||||
Inventory, entity tracking, block breaking/placing, and the farming/trading jobs
|
breaking/placing, and farming/trading jobs remain disabled until their 26.3
|
||||||
remain disabled until their 26.3 packets are migrated. The default server is
|
packets are migrated. The default server is
|
||||||
`127.0.0.1`; set `SERVER` explicitly only when you intentionally want another
|
`127.0.0.1`; set `SERVER` explicitly only when you intentionally want another
|
||||||
server.
|
server.
|
||||||
|
|
||||||
@@ -61,9 +61,12 @@ and all bots will run the command.
|
|||||||
|
|
||||||
The current 26.3 command path handles public chat. Private-message routing is
|
The current 26.3 command path handles public chat. Private-message routing is
|
||||||
not migrated yet. Verified commands are `!ping`, `!pos`, `!loaded`, `!info x y
|
not migrated yet. Verified commands are `!ping`, `!pos`, `!loaded`, `!info x y
|
||||||
z`, and addressed path movement such as `1goto x y z`. The command catalog below
|
z`, `!inv` (basic slot/count data), and addressed path movement such as
|
||||||
is retained from the legacy bot; inventory, interaction, and farming/trading
|
`1goto x y z`. The command catalog below is retained from the legacy bot;
|
||||||
commands are not yet available on 26.3.
|
inventory actions, block interaction, and farming/trading commands are not yet
|
||||||
|
available on 26.3. Common item components (names, damage, enchantments, book
|
||||||
|
contents, and shulker contents) are decoded; unrecognized component types are
|
||||||
|
marked partial rather than silently discarded.
|
||||||
|
|
||||||
### Public Commands
|
### Public Commands
|
||||||
|
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ def root():
|
|||||||
def main():
|
def main():
|
||||||
"""Connect with offline/AuthMe and enable only the migrated 26.3 features.
|
"""Connect with offline/AuthMe and enable only the migrated 26.3 features.
|
||||||
|
|
||||||
Current gameplay support is read-only world/entity state plus path
|
Current gameplay support is read-only world/entity/inventory state plus
|
||||||
following; inventory, interaction, and legacy job packet handlers remain
|
path following; inventory actions, block interactions, and legacy job
|
||||||
disabled until migrated.
|
packet handlers remain disabled until migrated.
|
||||||
"""
|
"""
|
||||||
reload_timeout = time.time()
|
reload_timeout = time.time()
|
||||||
reload_requested = False
|
reload_requested = False
|
||||||
|
|||||||
+60
-15
@@ -113,30 +113,75 @@ class Commands:
|
|||||||
if command == 'inv' or command == 'inventory':
|
if command == 'inv' or command == 'inventory':
|
||||||
inv_list = []
|
inv_list = []
|
||||||
uniq_item_counts = {}
|
uniq_item_counts = {}
|
||||||
for i in self.g.inv.values():
|
for slot, i in self.g.inv.items():
|
||||||
if i.present:
|
if not i.present:
|
||||||
inv_list.append((items.ITEM_NAMES[i.item_id], str(i.item_id), i.item_count))
|
continue
|
||||||
|
base_name = items.ITEM_NAMES.get(i.item_id, str(i.item_id))
|
||||||
|
components = getattr(i, 'components', {}) or {}
|
||||||
|
custom_name = components.get('custom_name') or components.get('item_name')
|
||||||
|
if isinstance(custom_name, dict):
|
||||||
|
custom_name = custom_name.get('text') or custom_name.get('raw')
|
||||||
|
display_name = str(custom_name or base_name)
|
||||||
|
details = []
|
||||||
|
for key in ('enchantments', 'stored_enchantments'):
|
||||||
|
for name, level in components.get(key, {}).items():
|
||||||
|
details.append('{} {}'.format(name, level))
|
||||||
|
contents = components.get('container')
|
||||||
|
if contents:
|
||||||
|
nested_count = sum(bool(getattr(item, 'present', False))
|
||||||
|
for item in contents)
|
||||||
|
details.append('{} items inside'.format(nested_count))
|
||||||
|
written = components.get('written_book_content')
|
||||||
|
if written:
|
||||||
|
title = written.get('title', {}).get('raw', '')
|
||||||
|
details.append('book: {} ({} pages)'.format(
|
||||||
|
title, len(written.get('pages', []))))
|
||||||
|
writable = components.get('writable_book_content')
|
||||||
|
if writable:
|
||||||
|
details.append('writable book ({} pages)'.format(
|
||||||
|
len(writable)))
|
||||||
|
if getattr(i, 'components_unknown', False):
|
||||||
|
details.append('components partial')
|
||||||
|
detail_text = ' [{}]'.format(', '.join(details)) if details else ''
|
||||||
|
inv_list.append((slot, display_name, str(i.item_id),
|
||||||
|
i.item_count, detail_text))
|
||||||
|
uniq_item_counts[i.item_id] = (
|
||||||
|
uniq_item_counts.get(i.item_id, 0) + i.item_count)
|
||||||
|
|
||||||
if i.item_id not in uniq_item_counts:
|
inv_list.sort(key=lambda entry: (entry[1], entry[2]))
|
||||||
uniq_item_counts[i.item_id] = 0
|
console_result = '\n'.join(
|
||||||
uniq_item_counts[i.item_id] += i.item_count
|
'slot {}: {}:{} x {}{}'.format(*entry)
|
||||||
|
for entry in inv_list)
|
||||||
inv_list.sort()
|
|
||||||
console_result = '\n'.join(['{}:{} x {}'.format(*x) for x in inv_list])
|
|
||||||
|
|
||||||
if not console_result:
|
if not console_result:
|
||||||
print('Empty')
|
print('Empty')
|
||||||
reply = 'empty'
|
reply = 'empty'
|
||||||
else:
|
else:
|
||||||
print(console_result)
|
print(console_result)
|
||||||
|
if self.g.get('inventory_partial', False):
|
||||||
|
print('Warning: inventory contains stacks with undecoded components.')
|
||||||
|
|
||||||
reply_result_1 = ', '.join(['{}:{} x {}'.format(*x) for x in inv_list])
|
reply_result_1 = ', '.join(
|
||||||
reply_result_2 = ', '.join(['{}:{} x {}'.format(items.ITEM_NAMES[k], str(k), v) for k,v in uniq_item_counts.items()])
|
'{}:{} x {}{}'.format(*entry[1:]) for entry in inv_list)
|
||||||
reply_result_3 = ', '.join(['{}:{}x{}'.format(items.ITEM_NAMES[k], str(k), v) for k,v in uniq_item_counts.items()])
|
reply_result_components = ', '.join(
|
||||||
reply_result_4 = ', '.join(['{}:{}x{}'.format(re.sub(r'[aeiou]', '', items.ITEM_NAMES[k]), str(k), v) for k,v in uniq_item_counts.items()])
|
'{}{}'.format(entry[1], entry[4])
|
||||||
reply_result_5 = ' '.join(['{}{}x{}'.format(re.sub(r'[aeiou]', '', items.ITEM_NAMES[k]), str(k), v) for k,v in uniq_item_counts.items()])
|
for entry in inv_list if entry[4])
|
||||||
|
reply_result_2 = ', '.join(
|
||||||
|
'{}:{} x {}'.format(items.ITEM_NAMES.get(k, str(k)), str(k), v)
|
||||||
|
for k, v in uniq_item_counts.items())
|
||||||
|
reply_result_3 = ', '.join(
|
||||||
|
'{}:{}x{}'.format(items.ITEM_NAMES.get(k, str(k)), str(k), v)
|
||||||
|
for k, v in uniq_item_counts.items())
|
||||||
|
reply_result_4 = ', '.join(
|
||||||
|
'{}:{}x{}'.format(re.sub(r'[aeiou]', '', items.ITEM_NAMES.get(k, str(k))), str(k), v)
|
||||||
|
for k, v in uniq_item_counts.items())
|
||||||
|
reply_result_5 = ' '.join(
|
||||||
|
'{}{}x{}'.format(re.sub(r'[aeiou]', '', items.ITEM_NAMES.get(k, str(k))), str(k), v)
|
||||||
|
for k, v in uniq_item_counts.items())
|
||||||
|
|
||||||
for r in [reply_result_1, reply_result_2, reply_result_3, reply_result_4, reply_result_5]:
|
for r in [reply_result_1, reply_result_components,
|
||||||
|
reply_result_2, reply_result_3, reply_result_4,
|
||||||
|
reply_result_5]:
|
||||||
reply = r
|
reply = r
|
||||||
if len(reply) < 256:
|
if len(reply) < 256:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -80,10 +80,12 @@ def main(global_state=None):
|
|||||||
from mosfet.commands import Commands
|
from mosfet.commands import Commands
|
||||||
from mosfet.protocol.modern import ModernChatManager
|
from mosfet.protocol.modern import ModernChatManager
|
||||||
from mosfet.protocol.entities_26_3 import ModernEntityManager
|
from mosfet.protocol.entities_26_3 import ModernEntityManager
|
||||||
|
from mosfet.protocol.inventory_26_3 import ModernInventoryManager
|
||||||
from mosfet.automation_26_3 import start as start_modern_movement
|
from mosfet.automation_26_3 import start as start_modern_movement
|
||||||
global_state.world = World(global_state)
|
global_state.world = World(global_state)
|
||||||
global_state.chat = ModernChatManager(global_state)
|
global_state.chat = ModernChatManager(global_state)
|
||||||
global_state.entities = ModernEntityManager(global_state)
|
global_state.entities = ModernEntityManager(global_state)
|
||||||
|
global_state.inventory = ModernInventoryManager(global_state)
|
||||||
global_state.commands = Commands(global_state)
|
global_state.commands = Commands(global_state)
|
||||||
start_modern_movement(global_state)
|
start_modern_movement(global_state)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"""Minimal 26.3 inventory synchronization for legacy read-only commands."""
|
||||||
|
from mosfet.protocol.types import Slot
|
||||||
|
from minecraft.networking.modern import (
|
||||||
|
ContainerSetContentPacket, ContainerSetSlotPacket, SetCursorItemPacket,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModernInventoryManager:
|
||||||
|
def __init__(self, global_state):
|
||||||
|
self.g = global_state
|
||||||
|
self.g.inv = self.g.get('inv') or {}
|
||||||
|
self.g.inventory_state_ids = self.g.get('inventory_state_ids') or {}
|
||||||
|
self.g.inventory_partial = False
|
||||||
|
self.g.cursor = Slot(present=False)
|
||||||
|
register = self.g.connection.register_packet_listener
|
||||||
|
register(self.handle_content, ContainerSetContentPacket)
|
||||||
|
register(self.handle_slot, ContainerSetSlotPacket)
|
||||||
|
register(self.handle_cursor, SetCursorItemPacket)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_slot(item):
|
||||||
|
return Slot(item.present, item.item_id, item.item_count,
|
||||||
|
components=item.components,
|
||||||
|
component_ids=item.component_ids,
|
||||||
|
removed_components=item.removed_components,
|
||||||
|
components_unknown=item.components_unknown)
|
||||||
|
|
||||||
|
def handle_content(self, packet):
|
||||||
|
slots = {index: self.to_slot(item)
|
||||||
|
for index, item in enumerate(packet.items)}
|
||||||
|
self.g.inventory_state_ids[packet.container_id] = packet.state_id
|
||||||
|
if packet.container_id == 0:
|
||||||
|
self.g.inv = slots
|
||||||
|
self.g.inventory_partial = packet.partial
|
||||||
|
self.g.cursor = self.to_slot(packet.carried_item)
|
||||||
|
elif self.g.get('window'):
|
||||||
|
self.g.window.contents = slots
|
||||||
|
self.g.cursor = self.to_slot(packet.carried_item)
|
||||||
|
|
||||||
|
def handle_slot(self, packet):
|
||||||
|
self.g.inventory_state_ids[packet.container_id] = packet.state_id
|
||||||
|
item = self.to_slot(packet.item)
|
||||||
|
if packet.item.components_unknown:
|
||||||
|
self.g.inventory_partial = True
|
||||||
|
if packet.container_id == 0:
|
||||||
|
self.g.inv[packet.slot] = item
|
||||||
|
elif packet.container_id == -1 and packet.slot == -1:
|
||||||
|
self.g.cursor = item
|
||||||
|
elif self.g.get('window'):
|
||||||
|
self.g.window.contents[packet.slot] = item
|
||||||
|
|
||||||
|
def handle_cursor(self, packet):
|
||||||
|
if packet.item.components_unknown:
|
||||||
|
self.g.inventory_partial = True
|
||||||
|
self.g.cursor = self.to_slot(packet.item)
|
||||||
@@ -103,11 +103,17 @@ class Nbt(Type):
|
|||||||
|
|
||||||
|
|
||||||
class Slot(Type):
|
class Slot(Type):
|
||||||
def __init__(self, present, item_id=None, item_count=None, nbt=None):
|
def __init__(self, present, item_id=None, item_count=None, nbt=None,
|
||||||
|
components=None, component_ids=None,
|
||||||
|
removed_components=None, components_unknown=False):
|
||||||
self.present = present
|
self.present = present
|
||||||
self.item_id = item_id
|
self.item_id = item_id
|
||||||
self.item_count = item_count
|
self.item_count = item_count
|
||||||
self.nbt = nbt
|
self.nbt = nbt
|
||||||
|
self.components = components or {}
|
||||||
|
self.component_ids = component_ids or []
|
||||||
|
self.removed_components = removed_components or []
|
||||||
|
self.components_unknown = components_unknown
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.__dict__)
|
return str(self.__dict__)
|
||||||
|
|||||||
Reference in New Issue
Block a user