Track reopened supply chests and fix API snapshots

This commit is contained in:
Tanner
2026-11-07 17:02:29 +00:00
parent 43b37b76f6
commit 9251edc36a
8 changed files with 280 additions and 7 deletions
+74 -1
View File
@@ -1,10 +1,11 @@
import json
import time
from math import floor
from munch import Munch
from minecraft.networking.packets import clientbound, serverbound
from minecraft.networking.modern import (
AddEntityPacket, BlockEntityDataPacket, ContainerOpenPacket,
AddEntityPacket, BlockEntityDataPacket, BlockEventPacket, ContainerOpenPacket,
ContainerSetContentPacket, ContainerSetSlotPacket, SetEntityDataPacket,
nbt_plain,
PaletteSection,
@@ -355,6 +356,78 @@ class WorldState:
self.center, self.radius)
class ChestOpenManager:
"""Track chest viewer-count events and report openings by other players."""
OWN_OPEN_TIMEOUT = 3.0
def __init__(self, global_state):
self.g = global_state
self.g.chest_viewers = self.g.get('chest_viewers') or {}
self.g.chest_open_events = self.g.get('chest_open_events') or []
self.g.last_chest_opened_by_other = None
self.g.connection.register_packet_listener(
self.handle_block_event, BlockEventPacket)
def _block_name(self, packet):
registry = getattr(self.g.connection, 'registry_ids', {}).get(
'minecraft:block', {})
name = next((name for name, block_id in registry.items()
if block_id == packet.block_id), None)
if name is not None:
return name.removeprefix('minecraft:')
location = packet.location
state_id = self.g.chunks.get_block_at(
location.x, location.y, location.z)
return blocks.BLOCKS.get(state_id)
def handle_block_event(self, packet):
block_name = self._block_name(packet)
if (block_name is None or block_name == 'ender_chest' or
not (block_name == 'chest' or block_name.endswith('_chest')) or
packet.action != 1):
return
location = packet.location
position = (location.x, location.y, location.z)
viewers = packet.parameter
previous_viewers = self.g.chest_viewers.get(position, 0)
if viewers:
self.g.chest_viewers[position] = viewers
else:
self.g.chest_viewers.pop(position, None)
pending = self.g.get('pending_chest_open')
pending_age = (time.monotonic() - pending.get('requested_at', 0)
if pending is not None else None)
pending_is_fresh = (pending_age is not None and
0 <= pending_age <= self.OWN_OPEN_TIMEOUT)
is_bot_opening = (
pending_is_fresh and
tuple(pending.get('position', ())) == position
)
if pending is not None and (is_bot_opening or not pending_is_fresh):
self.g.pending_chest_open = None
if (viewers <= previous_viewers or
(is_bot_opening and
viewers <= max(1, previous_viewers))):
return
event = {
'position': position,
'block': block_name,
'viewers': viewers,
'previous_viewers': previous_viewers,
'timestamp': time.time(),
}
self.g.last_chest_opened_by_other = event
self.g.chest_open_events.append(event)
del self.g.chest_open_events[:-100]
print('[chest] another player opened {} at {} ({} viewer{})'.format(
block_name, position, viewers, '' if viewers == 1 else 's'),
flush=True)
class EntityManager:
def __init__(self, global_state):
self.g = global_state