Improve labeled chest caching interactions
This commit is contained in:
+29
-11
@@ -26,7 +26,8 @@ from mosfet.protocol.packets import (
|
|||||||
|
|
||||||
from mosfet.protocol.types import Slot
|
from mosfet.protocol.types import Slot
|
||||||
from mosfet.protocol.modern import (
|
from mosfet.protocol.modern import (
|
||||||
ClientCommandPacket, PlayerCommandPacket, UpdateTimePacket, UseItemPacket,
|
ClientCommandPacket, PlayerCommandPacket, PlayerInputPacket,
|
||||||
|
UpdateTimePacket, UseItemPacket,
|
||||||
)
|
)
|
||||||
|
|
||||||
from mosfet import utils
|
from mosfet import utils
|
||||||
@@ -386,14 +387,18 @@ class LegacyGame:
|
|||||||
|
|
||||||
def open_container(self, location):
|
def open_container(self, location):
|
||||||
location = tuple(int(value) for value in location)
|
location = tuple(int(value) for value in location)
|
||||||
bid = self.g.chunks.get_block_at(*location)
|
positions = {location}
|
||||||
block_name = blocks.BLOCKS.get(bid)
|
world = self.g.get('world')
|
||||||
if (block_name and block_name != 'ender_chest' and
|
if world and hasattr(world, '_double_chest_positions'):
|
||||||
(block_name == 'chest' or block_name.endswith('_chest'))):
|
positions.update(world._double_chest_positions(location))
|
||||||
self.g.pending_chest_open = {
|
for offset in ((1, 0, 0), (-1, 0, 0), (0, 0, 1),
|
||||||
'position': location,
|
(0, 0, -1), (0, 1, 0), (0, -1, 0)):
|
||||||
'requested_at': time.monotonic(),
|
positions.add(utils.padd(location, offset))
|
||||||
}
|
self.g.pending_chest_open = {
|
||||||
|
'position': location,
|
||||||
|
'positions': tuple(sorted(positions)),
|
||||||
|
'requested_at': time.monotonic(),
|
||||||
|
}
|
||||||
self.place_block(location, BlockFace.TOP)
|
self.place_block(location, BlockFace.TOP)
|
||||||
|
|
||||||
def handle_window(self, packet):
|
def handle_window(self, packet):
|
||||||
@@ -1042,7 +1047,8 @@ class Game(LegacyGame):
|
|||||||
self.g.connection.write_packet(ClientTickEnd(), force=True)
|
self.g.connection.write_packet(ClientTickEnd(), force=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def place_block(self, location, face, hand=0, cursor_y=None):
|
def place_block(self, location, face, hand=0, cursor_y=None,
|
||||||
|
sneaking=False):
|
||||||
location = tuple(int(value) for value in location)
|
location = tuple(int(value) for value in location)
|
||||||
if cursor_y is None:
|
if cursor_y is None:
|
||||||
cursor_y = 0.99 if int(face) == 1 else 0.5
|
cursor_y = 0.99 if int(face) == 1 else 0.5
|
||||||
@@ -1051,7 +1057,18 @@ class Game(LegacyGame):
|
|||||||
cursor_x=0.5, cursor_y=cursor_y,
|
cursor_x=0.5, cursor_y=cursor_y,
|
||||||
cursor_z=0.5, inside_block=False, world_border_hit=False,
|
cursor_z=0.5, inside_block=False, world_border_hit=False,
|
||||||
sequence=self._next_sequence())
|
sequence=self._next_sequence())
|
||||||
self._send_action(packet)
|
if sneaking:
|
||||||
|
self.g.connection.write_packet(PlayerInputPacket(flags=0x20),
|
||||||
|
force=True)
|
||||||
|
self.g.connection.write_packet(ClientTickEnd(), force=True)
|
||||||
|
try:
|
||||||
|
self._send_action(packet)
|
||||||
|
finally:
|
||||||
|
self.g.connection.write_packet(PlayerInputPacket(flags=0),
|
||||||
|
force=True)
|
||||||
|
self.g.connection.write_packet(ClientTickEnd(), force=True)
|
||||||
|
else:
|
||||||
|
self._send_action(packet)
|
||||||
|
|
||||||
def count_window_slots(self):
|
def count_window_slots(self):
|
||||||
window = self.g.get('window')
|
window = self.g.get('window')
|
||||||
@@ -1083,3 +1100,4 @@ class Game(LegacyGame):
|
|||||||
self.g.connection.write_packet(ContainerClosePacket(
|
self.g.connection.write_packet(ContainerClosePacket(
|
||||||
container_id=window.window_id), force=True)
|
container_id=window.window_id), force=True)
|
||||||
self.g.window = None
|
self.g.window = None
|
||||||
|
self.g.pending_chest_open = None
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class CacheItemsStates:
|
|||||||
def init(self):
|
def init(self):
|
||||||
self.skip_slots = []
|
self.skip_slots = []
|
||||||
self.skip_items = []
|
self.skip_items = []
|
||||||
|
self.failed_transfer_slots = set()
|
||||||
|
self.pending_transfer = None
|
||||||
|
self.transferred_to_labeled_chest = False
|
||||||
self.is_labeled_container = False
|
self.is_labeled_container = False
|
||||||
self.container_item_ids = None
|
self.container_item_ids = None
|
||||||
|
|
||||||
@@ -80,6 +83,10 @@ class CacheItemsStates:
|
|||||||
if not len(self.trapped_chests):
|
if not len(self.trapped_chests):
|
||||||
self.is_labeled_container = False
|
self.is_labeled_container = False
|
||||||
self.container_item_ids = None
|
self.container_item_ids = None
|
||||||
|
if self.transferred_to_labeled_chest:
|
||||||
|
print('Already cached items in labeled chests; skipping new chest')
|
||||||
|
self.state = self.cleanup
|
||||||
|
return
|
||||||
print('No labeled or trapped chests')
|
print('No labeled or trapped chests')
|
||||||
self.state = self.select_chest
|
self.state = self.select_chest
|
||||||
return
|
return
|
||||||
@@ -166,11 +173,13 @@ class CacheItemsStates:
|
|||||||
|
|
||||||
def place_chest(self):
|
def place_chest(self):
|
||||||
support = utils.padd(self.area, path.BLOCK_BELOW)
|
support = utils.padd(self.area, path.BLOCK_BELOW)
|
||||||
self.g.game.place_block(support, BlockFace.TOP)
|
self.g.game.place_block(support, BlockFace.TOP, sneaking=True)
|
||||||
self.state = self.open_chest
|
self.state = self.open_chest
|
||||||
|
|
||||||
def open_chest(self):
|
def open_chest(self):
|
||||||
print('Opening chest')
|
print('Opening chest')
|
||||||
|
self.failed_transfer_slots.clear()
|
||||||
|
self.pending_transfer = None
|
||||||
self.g.game.open_container(self.area)
|
self.g.game.open_container(self.area)
|
||||||
self.wait_time = 1
|
self.wait_time = 1
|
||||||
self.state = self.wait
|
self.state = self.wait
|
||||||
@@ -236,6 +245,21 @@ class CacheItemsStates:
|
|||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.pending_transfer:
|
||||||
|
pending = self.pending_transfer
|
||||||
|
if time.monotonic() < pending['confirm_after']:
|
||||||
|
return
|
||||||
|
source = w.contents.get(pending['window_slot'])
|
||||||
|
moved = (source is None or not source.present or
|
||||||
|
source.item_id != pending['item_id'] or
|
||||||
|
source.item_count < pending['item_count'])
|
||||||
|
if moved:
|
||||||
|
if pending['is_labeled']:
|
||||||
|
self.transferred_to_labeled_chest = True
|
||||||
|
else:
|
||||||
|
self.failed_transfer_slots.add(pending['window_slot'])
|
||||||
|
self.pending_transfer = None
|
||||||
|
|
||||||
w_container_slots = range(w.container_slots)
|
w_container_slots = range(w.container_slots)
|
||||||
w_inventory_slots = range(w.container_slots, len(w.contents))
|
w_inventory_slots = range(w.container_slots, len(w.contents))
|
||||||
|
|
||||||
@@ -272,7 +296,8 @@ class CacheItemsStates:
|
|||||||
elif slot.item_id in items.NEEDED_ITEMS:
|
elif slot.item_id in items.NEEDED_ITEMS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if slot_num in self.skip_slots:
|
if (slot_num in self.skip_slots or
|
||||||
|
slot_num in self.failed_transfer_slots):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
slot_list.append((slot_num, slot))
|
slot_list.append((slot_num, slot))
|
||||||
@@ -288,6 +313,13 @@ class CacheItemsStates:
|
|||||||
|
|
||||||
print('moving', slot)
|
print('moving', slot)
|
||||||
|
|
||||||
|
self.pending_transfer = {
|
||||||
|
'window_slot': slot_num,
|
||||||
|
'item_id': slot.item_id,
|
||||||
|
'item_count': slot.item_count,
|
||||||
|
'is_labeled': self.is_labeled_container,
|
||||||
|
'confirm_after': time.monotonic() + 0.15,
|
||||||
|
}
|
||||||
self.g.item_lock = True
|
self.g.item_lock = True
|
||||||
self.g.game.click_window(slot_num, 0, 1, slot)
|
self.g.game.click_window(slot_num, 0, 1, slot)
|
||||||
return
|
return
|
||||||
@@ -325,6 +357,9 @@ class CacheItemsStates:
|
|||||||
self.silent = False
|
self.silent = False
|
||||||
|
|
||||||
self.skip_slots = []
|
self.skip_slots = []
|
||||||
|
self.failed_transfer_slots = set()
|
||||||
|
self.pending_transfer = None
|
||||||
|
self.transferred_to_labeled_chest = False
|
||||||
self.skip_items = []
|
self.skip_items = []
|
||||||
|
|
||||||
self.labeled_chests = []
|
self.labeled_chests = []
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ class ChestOpenManager:
|
|||||||
self.g.chest_viewers = self.g.get('chest_viewers') or {}
|
self.g.chest_viewers = self.g.get('chest_viewers') or {}
|
||||||
self.g.chest_open_events = self.g.get('chest_open_events') or []
|
self.g.chest_open_events = self.g.get('chest_open_events') or []
|
||||||
self.g.last_chest_opened_by_other = None
|
self.g.last_chest_opened_by_other = None
|
||||||
|
self._own_open_suppressions = {}
|
||||||
self.g.connection.register_packet_listener(
|
self.g.connection.register_packet_listener(
|
||||||
self.handle_block_event, BlockEventPacket)
|
self.handle_block_event, BlockEventPacket)
|
||||||
|
|
||||||
@@ -396,17 +397,31 @@ class ChestOpenManager:
|
|||||||
else:
|
else:
|
||||||
self.g.chest_viewers.pop(position, None)
|
self.g.chest_viewers.pop(position, None)
|
||||||
|
|
||||||
|
now = time.monotonic()
|
||||||
|
self._own_open_suppressions = {
|
||||||
|
pos: expires for pos, expires in self._own_open_suppressions.items()
|
||||||
|
if expires > now
|
||||||
|
}
|
||||||
pending = self.g.get('pending_chest_open')
|
pending = self.g.get('pending_chest_open')
|
||||||
pending_age = (time.monotonic() - pending.get('requested_at', 0)
|
pending_age = (now - pending.get('requested_at', 0)
|
||||||
if pending is not None else None)
|
if pending is not None else None)
|
||||||
pending_is_fresh = (pending_age is not None and
|
pending_is_fresh = (pending_age is not None and
|
||||||
0 <= pending_age <= self.OWN_OPEN_TIMEOUT)
|
0 <= pending_age <= self.OWN_OPEN_TIMEOUT)
|
||||||
is_bot_opening = (
|
pending_positions = (
|
||||||
pending_is_fresh and
|
pending.get('positions') or (pending.get('position', ()),)
|
||||||
tuple(pending.get('position', ())) == position
|
if pending is not None else ()
|
||||||
)
|
)
|
||||||
if pending is not None and (is_bot_opening or not pending_is_fresh):
|
pending_positions = {tuple(pos) for pos in pending_positions}
|
||||||
|
is_bot_opening = (
|
||||||
|
pending_is_fresh and position in pending_positions
|
||||||
|
)
|
||||||
|
if pending is not None and not pending_is_fresh:
|
||||||
self.g.pending_chest_open = None
|
self.g.pending_chest_open = None
|
||||||
|
if is_bot_opening:
|
||||||
|
for own_position in pending_positions:
|
||||||
|
self._own_open_suppressions[own_position] = now + 0.5
|
||||||
|
is_bot_opening = (is_bot_opening or
|
||||||
|
position in self._own_open_suppressions)
|
||||||
|
|
||||||
if (viewers <= previous_viewers or
|
if (viewers <= previous_viewers or
|
||||||
(is_bot_opening and
|
(is_bot_opening and
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import time
|
|||||||
from contextlib import nullcontext
|
from contextlib import nullcontext
|
||||||
|
|
||||||
from minecraft.networking.packets import Packet
|
from minecraft.networking.packets import Packet
|
||||||
from minecraft.networking.types import Float, Long, VarInt, VarLong
|
from minecraft.networking.types import (
|
||||||
|
Float, Long, UnsignedByte, VarInt, VarLong,
|
||||||
|
)
|
||||||
from minecraft.networking.modern import (
|
from minecraft.networking.modern import (
|
||||||
ChatCommand, ClientboundPlayerChatPacket, ServerboundChatPacket, SystemChat,
|
ChatCommand, ClientboundPlayerChatPacket, ServerboundChatPacket, SystemChat,
|
||||||
)
|
)
|
||||||
@@ -29,6 +31,13 @@ class UpdateTimePacket(Packet):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerInputPacket(Packet):
|
||||||
|
"""Serverbound key state, including sneak for container-safe placement."""
|
||||||
|
id = 0x2B
|
||||||
|
packet_name = 'player input'
|
||||||
|
definition = [{'flags': UnsignedByte}]
|
||||||
|
|
||||||
|
|
||||||
class PlayerCommandPacket(Packet):
|
class PlayerCommandPacket(Packet):
|
||||||
"""Client player-command packet; action 0 stops sleeping (protocol 777)."""
|
"""Client player-command packet; action 0 stops sleeping (protocol 777)."""
|
||||||
id = 0x2A
|
id = 0x2A
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import unittest
|
||||||
|
from munch import Munch
|
||||||
|
|
||||||
|
from minecraft.networking.types import BlockFace
|
||||||
|
|
||||||
|
from mosfet.info import items
|
||||||
|
from mosfet.jobs.cache_items import CacheItemsStates
|
||||||
|
|
||||||
|
|
||||||
|
class CacheItemsTests(unittest.TestCase):
|
||||||
|
def test_successful_labeled_transfer_skips_placing_another_cache_chest(self):
|
||||||
|
item_id = items.get_id('stone')
|
||||||
|
stack = Munch(present=True, item_id=item_id, item_count=16)
|
||||||
|
window = Munch(container_slots=27, contents={
|
||||||
|
**dict.fromkeys(range(27)), 27: stack,
|
||||||
|
})
|
||||||
|
clicks = []
|
||||||
|
|
||||||
|
class FakeGame:
|
||||||
|
@staticmethod
|
||||||
|
def count_window_slots():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def click_window(*args):
|
||||||
|
clicks.append(args)
|
||||||
|
|
||||||
|
class FakeWorld:
|
||||||
|
@staticmethod
|
||||||
|
def find_blocks_indexed(*_args):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def find_labeled_chests(*_args):
|
||||||
|
return []
|
||||||
|
|
||||||
|
g = Munch(
|
||||||
|
game=FakeGame(), world=FakeWorld(), pos=(0, 64, 0),
|
||||||
|
inv={}, window=window, item_lock=False)
|
||||||
|
state = CacheItemsStates(g)
|
||||||
|
state.is_labeled_container = True
|
||||||
|
state.container_item_ids = {item_id}
|
||||||
|
|
||||||
|
state.move_items()
|
||||||
|
self.assertEqual(len(clicks), 1)
|
||||||
|
self.assertFalse(state.transferred_to_labeled_chest)
|
||||||
|
|
||||||
|
window.contents[27] = Munch(present=False, item_id=0, item_count=0)
|
||||||
|
g.item_lock = False
|
||||||
|
state.pending_transfer['confirm_after'] = 0
|
||||||
|
state.move_items()
|
||||||
|
self.assertTrue(state.transferred_to_labeled_chest)
|
||||||
|
|
||||||
|
state.labeled_chests = []
|
||||||
|
state.trapped_chests = []
|
||||||
|
state.choose_trapped_chest()
|
||||||
|
self.assertIs(state.state.__func__, state.cleanup.__func__)
|
||||||
|
|
||||||
|
def test_cache_chest_placement_sneaks_to_avoid_opening_support_container(self):
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
class FakeGame:
|
||||||
|
@staticmethod
|
||||||
|
def place_block(*args, **kwargs):
|
||||||
|
calls.append((args, kwargs))
|
||||||
|
|
||||||
|
state = CacheItemsStates(Munch(game=FakeGame()))
|
||||||
|
state.area = (3, 65, 7)
|
||||||
|
|
||||||
|
state.place_chest()
|
||||||
|
|
||||||
|
self.assertEqual(calls, [(
|
||||||
|
((3, 64, 7), BlockFace.TOP), {'sneaking': True},
|
||||||
|
)])
|
||||||
|
self.assertIs(state.state.__func__, state.open_chest.__func__)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -27,8 +27,8 @@ class ChestDetectionTests(unittest.TestCase):
|
|||||||
self.manager = ChestOpenManager(self.g)
|
self.manager = ChestOpenManager(self.g)
|
||||||
self.position = (12, 64, -7)
|
self.position = (12, 64, -7)
|
||||||
|
|
||||||
def event(self, block_id=2, action=1, viewers=1):
|
def event(self, block_id=2, action=1, viewers=1, position=None):
|
||||||
x, y, z = self.position
|
x, y, z = position or self.position
|
||||||
return Munch(
|
return Munch(
|
||||||
location=Munch(x=x, y=y, z=z),
|
location=Munch(x=x, y=y, z=z),
|
||||||
block_id=block_id, action=action, parameter=viewers,
|
block_id=block_id, action=action, parameter=viewers,
|
||||||
@@ -54,12 +54,27 @@ class ChestDetectionTests(unittest.TestCase):
|
|||||||
self.manager.handle_block_event(self.event(viewers=1))
|
self.manager.handle_block_event(self.event(viewers=1))
|
||||||
|
|
||||||
self.assertIsNone(self.g.last_chest_opened_by_other)
|
self.assertIsNone(self.g.last_chest_opened_by_other)
|
||||||
self.assertIsNone(self.g.pending_chest_open)
|
self.assertIsNotNone(self.g.pending_chest_open)
|
||||||
|
|
||||||
self.manager.handle_block_event(self.event(viewers=2))
|
self.manager.handle_block_event(self.event(viewers=2))
|
||||||
self.assertEqual(self.g.last_chest_opened_by_other['viewers'], 2)
|
self.assertEqual(self.g.last_chest_opened_by_other['viewers'], 2)
|
||||||
self.assertEqual(self.g.last_chest_opened_by_other['previous_viewers'], 1)
|
self.assertEqual(self.g.last_chest_opened_by_other['previous_viewers'], 1)
|
||||||
|
|
||||||
|
def test_bot_open_of_either_double_chest_half_is_not_misreported(self):
|
||||||
|
other_half = (13, 64, -7)
|
||||||
|
self.g.pending_chest_open = {
|
||||||
|
'position': self.position,
|
||||||
|
'positions': (self.position, other_half),
|
||||||
|
'requested_at': time.monotonic(),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.manager.handle_block_event(
|
||||||
|
self.event(position=other_half, viewers=1))
|
||||||
|
|
||||||
|
self.assertIsNone(self.g.last_chest_opened_by_other)
|
||||||
|
self.assertIsNotNone(self.g.pending_chest_open)
|
||||||
|
self.assertEqual(self.g.chest_viewers[other_half], 1)
|
||||||
|
|
||||||
def test_ignores_non_chests_and_tracks_close(self):
|
def test_ignores_non_chests_and_tracks_close(self):
|
||||||
self.manager.handle_block_event(self.event(block_id=1, viewers=1))
|
self.manager.handle_block_event(self.event(block_id=1, viewers=1))
|
||||||
self.assertIsNone(self.g.last_chest_opened_by_other)
|
self.assertIsNone(self.g.last_chest_opened_by_other)
|
||||||
@@ -71,11 +86,13 @@ class ChestDetectionTests(unittest.TestCase):
|
|||||||
self.assertEqual(len(self.g.chest_open_events), 1)
|
self.assertEqual(len(self.g.chest_open_events), 1)
|
||||||
|
|
||||||
def test_open_container_marks_bot_initiated_chest_open(self):
|
def test_open_container_marks_bot_initiated_chest_open(self):
|
||||||
chest_state = 5552
|
|
||||||
sent = []
|
sent = []
|
||||||
game = Game.__new__(Game)
|
game = Game.__new__(Game)
|
||||||
game.g = Munch(chunks=Munch(
|
other_half = (12, 64, -8)
|
||||||
get_block_at=lambda *_position: chest_state))
|
game.g = Munch(
|
||||||
|
chunks=Munch(get_block_at=lambda *_position: None),
|
||||||
|
world=Munch(_double_chest_positions=lambda _position:
|
||||||
|
(self.position,)))
|
||||||
game.place_block = lambda location, face: sent.append(
|
game.place_block = lambda location, face: sent.append(
|
||||||
(location, face))
|
(location, face))
|
||||||
|
|
||||||
@@ -83,6 +100,8 @@ class ChestDetectionTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(sent[0][0], self.position)
|
self.assertEqual(sent[0][0], self.position)
|
||||||
self.assertEqual(game.g.pending_chest_open['position'], self.position)
|
self.assertEqual(game.g.pending_chest_open['position'], self.position)
|
||||||
|
self.assertIn(self.position, game.g.pending_chest_open['positions'])
|
||||||
|
self.assertIn(other_half, game.g.pending_chest_open['positions'])
|
||||||
self.assertIsInstance(game.g.pending_chest_open['requested_at'], float)
|
self.assertIsInstance(game.g.pending_chest_open['requested_at'], float)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import unittest
|
||||||
|
from munch import Munch
|
||||||
|
|
||||||
|
from minecraft.networking.modern import ClientTickEnd, UseItemOnBlockPacket
|
||||||
|
from mosfet.game import Game
|
||||||
|
from mosfet.protocol.modern import PlayerInputPacket
|
||||||
|
|
||||||
|
|
||||||
|
class FakeConnection:
|
||||||
|
def __init__(self):
|
||||||
|
self.packets = []
|
||||||
|
|
||||||
|
def write_packet(self, packet, force=False):
|
||||||
|
self.packets.append((packet, force))
|
||||||
|
|
||||||
|
|
||||||
|
class SneakPlacementTests(unittest.TestCase):
|
||||||
|
def test_sneaking_wraps_block_placement_and_is_released(self):
|
||||||
|
connection = FakeConnection()
|
||||||
|
game = Game.__new__(Game)
|
||||||
|
game.g = Munch(connection=connection)
|
||||||
|
game.sequence = 0
|
||||||
|
|
||||||
|
game.place_block((3, 64, 7), 1, sneaking=True)
|
||||||
|
|
||||||
|
packets = [packet for packet, _force in connection.packets]
|
||||||
|
self.assertEqual(len(packets), 6)
|
||||||
|
self.assertIsInstance(packets[0], PlayerInputPacket)
|
||||||
|
self.assertEqual(packets[0].flags, 0x20)
|
||||||
|
self.assertIsInstance(packets[1], ClientTickEnd)
|
||||||
|
self.assertIsInstance(packets[2], UseItemOnBlockPacket)
|
||||||
|
self.assertEqual(packets[2].location, (3, 64, 7))
|
||||||
|
self.assertIsInstance(packets[3], ClientTickEnd)
|
||||||
|
self.assertIsInstance(packets[4], PlayerInputPacket)
|
||||||
|
self.assertEqual(packets[4].flags, 0)
|
||||||
|
self.assertIsInstance(packets[5], ClientTickEnd)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user