Skip to content

Commit 27e2422

Browse files
Use a NamedTuple for Slot data
- Cache the classes for FixedBitset - Create an `AdvancementFrame` IntEnum - Use attrs' validators and convertors
1 parent 505486c commit 27e2422

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+778
-885
lines changed

changes/297.feature.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
- Added more types to the implementation:
2+
23
- `Angle`: Represents an angle.
34
- `BitSet`: Represents a set of bits of variable length.
45
- `FixedBitSet`: Represents a set of bits of fixed length.
@@ -10,4 +11,7 @@
1011
- `Vec3`: Represents a 3D vector.
1112
- `Position`: Represents a position with packed integers.
1213
- `EntityMetadata`: Represents metadata for an entity.
13-
> There are **A LOT** of different entity metadata types, so I'm not going to list them all here.
14+
> There are **A LOT** of different entity metadata types, so I'm not going to list them all here.
15+
16+
- Removed the `validate` method from most `Serializable` classes.
17+
- Make use of validators and convertors from the `attrs` library instead.

docs/api/types/general.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ Here are documented the general types used throughout the Minecraft protocol.
77
:no-undoc-members:
88
:exclude-members: NBTag, StringNBT, CompoundNBT, EndNBT, EntityMetadata, UUID
99

10-
.. autoclass:: mcproto.types.UUID
11-
:class-doc-from: class
10+
.. autoclass:: mcproto.types.UUID
11+
:class-doc-from: class

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from packaging.version import parse as parse_version
1818
from typing_extensions import override
1919

20-
from mcproto.types.entity.metadata import _ProxyEntityMetadataEntry, _DefaultEntityMetadataEntry
20+
from mcproto.types.entity.metadata import _DefaultEntityMetadataEntry, _ProxyEntityMetadataEntry
2121

2222
if sys.version_info >= (3, 11):
2323
from tomllib import load as toml_parse

mcproto/packets/handshaking/handshake.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from enum import IntEnum
4-
from typing import ClassVar, cast, final
4+
from typing import ClassVar, final
55

66
from attrs import define
77
from typing_extensions import Self, override
@@ -42,19 +42,11 @@ class Handshake(ServerBoundPacket):
4242
protocol_version: int
4343
server_address: str
4444
server_port: int
45-
next_state: NextState | int
46-
47-
@override
48-
def __attrs_post_init__(self) -> None:
49-
if not isinstance(self.next_state, NextState):
50-
self.next_state = NextState(self.next_state)
51-
52-
super().__attrs_post_init__()
45+
next_state: NextState
5346

5447
@override
5548
def serialize_to(self, buf: Buffer) -> None:
5649
"""Serialize the packet."""
57-
self.next_state = cast(NextState, self.next_state) # Handled by the __attrs_post_init__ method
5850
buf.write_varint(self.protocol_version)
5951
buf.write_utf(self.server_address)
6052
buf.write_value(StructFormat.USHORT, self.server_port)
@@ -67,5 +59,5 @@ def _deserialize(cls, buf: Buffer, /) -> Self:
6759
protocol_version=buf.read_varint(),
6860
server_address=buf.read_utf(),
6961
server_port=buf.read_value(StructFormat.USHORT),
70-
next_state=buf.read_varint(),
62+
next_state=NextState(buf.read_varint()),
7163
)

mcproto/packets/login/login.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import ClassVar, cast, final
44

5-
from attrs import define
5+
from attrs import define, field
66
from cryptography.hazmat.backends import default_backend
77
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
88
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, load_der_public_key
@@ -71,16 +71,9 @@ class LoginEncryptionRequest(ClientBoundPacket):
7171
PACKET_ID: ClassVar[int] = 0x01
7272
GAME_STATE: ClassVar[GameState] = GameState.LOGIN
7373

74-
public_key: RSAPublicKey
75-
verify_token: bytes
76-
server_id: str | None = None
77-
78-
@override
79-
def __attrs_post_init__(self) -> None:
80-
if self.server_id is None:
81-
self.server_id = " " * 20
82-
83-
super().__attrs_post_init__()
74+
public_key: RSAPublicKey = field()
75+
verify_token: bytes = field()
76+
server_id: str | None = field(default=" " * 20)
8477

8578
@override
8679
def serialize_to(self, buf: Buffer) -> None:
@@ -243,7 +236,7 @@ class LoginPluginResponse(ServerBoundPacket):
243236
GAME_STATE: ClassVar[GameState] = GameState.LOGIN
244237

245238
message_id: int
246-
data: bytes | None
239+
data: bytes | None = None
247240

248241
@override
249242
def serialize_to(self, buf: Buffer) -> None:

mcproto/packets/status/status.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import json
44
from typing import Any, ClassVar, final
55

6-
from attrs import define
6+
from attrs import define, field
77
from typing_extensions import Self, override
88

99
from mcproto.buffer import Buffer
@@ -43,7 +43,7 @@ class StatusResponse(ClientBoundPacket):
4343
PACKET_ID: ClassVar[int] = 0x00
4444
GAME_STATE: ClassVar[GameState] = GameState.STATUS
4545

46-
data: dict[str, Any] # JSON response data sent back to the client.
46+
data: dict[str, Any] = field(validator=lambda self, _, value: json.dumps(value))
4747

4848
@override
4949
def serialize_to(self, buf: Buffer) -> None:
@@ -56,11 +56,3 @@ def _deserialize(cls, buf: Buffer, /) -> Self:
5656
s = buf.read_utf()
5757
data_ = json.loads(s)
5858
return cls(data_)
59-
60-
@override
61-
def validate(self) -> None:
62-
# Ensure the data is serializable to JSON
63-
try:
64-
json.dumps(self.data)
65-
except TypeError as exc:
66-
raise ValueError("Data is not serializable to JSON.") from exc

mcproto/types/__init__.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,57 @@
11
from __future__ import annotations
22

33
from mcproto.types.abc import MCType, Serializable
4-
from mcproto.types.advancement import Advancement, AdvancementProgress, AdvancementDisplay, AdvancementCriterion
4+
from mcproto.types.advancement import (
5+
Advancement,
6+
AdvancementCriterion,
7+
AdvancementDisplay,
8+
AdvancementFrame,
9+
AdvancementProgress,
10+
)
511
from mcproto.types.angle import Angle
612
from mcproto.types.bitset import Bitset, FixedBitset
713
from mcproto.types.block_entity import BlockEntity
814
from mcproto.types.chat import JSONTextComponent, TextComponent
915
from mcproto.types.entity import EntityMetadata
1016
from mcproto.types.identifier import Identifier
11-
from mcproto.types.map_icon import MapIcon, IconType
17+
from mcproto.types.map_icon import IconType, MapIcon
1218
from mcproto.types.modifier import ModifierData, ModifierOperation
13-
from mcproto.types.nbt import NBTag, CompoundNBT
19+
from mcproto.types.nbt import CompoundNBT, NBTag
1420
from mcproto.types.particle_data import ParticleData
1521
from mcproto.types.quaternion import Quaternion
1622
from mcproto.types.recipe import (
17-
Recipe,
1823
ArmorDyeRecipe,
1924
BannerDuplicateRecipe,
2025
BlastingRecipe,
2126
BookCloningRecipe,
22-
ShulkerBoxColoringRecipe,
2327
CampfireRecipe,
24-
ShapedRecipe,
2528
DecoratedPotRecipe,
2629
FireworkRocketRecipe,
30+
FireworkStarFadeRecipe,
31+
FireworkStarRecipe,
32+
Ingredient,
2733
MapCloningRecipe,
2834
MapExtendingRecipe,
35+
Recipe,
2936
RepairItemRecipe,
37+
ShapedRecipe,
3038
ShapelessRecipe,
3139
ShieldDecorationRecipe,
32-
SmokingRecipe,
33-
FireworkStarRecipe,
34-
FireworkStarFadeRecipe,
35-
Ingredient,
40+
ShulkerBoxColoringRecipe,
3641
SmeltingRecipe,
3742
SmithingTransformRecipe,
3843
SmithingTrimRecipe,
39-
TippedArrowRecipe,
44+
SmokingRecipe,
4045
StoneCuttingRecipe,
4146
SuspiciousStewRecipe,
47+
TippedArrowRecipe,
4248
)
43-
from mcproto.types.slot import Slot
4449
from mcproto.types.registry_tag import RegistryTag
50+
from mcproto.types.slot import Slot, SlotData
4551
from mcproto.types.trade import Trade
4652
from mcproto.types.uuid import UUID
4753
from mcproto.types.vec3 import Position, Vec3
4854

49-
5055
__all__ = [
5156
"MCType",
5257
"Serializable",
@@ -60,6 +65,7 @@
6065
"CompoundNBT",
6166
"Quaternion",
6267
"Slot",
68+
"SlotData",
6369
"RegistryTag",
6470
"UUID",
6571
"Position",
@@ -74,6 +80,7 @@
7480
"AdvancementProgress",
7581
"AdvancementDisplay",
7682
"AdvancementCriterion",
83+
"AdvancementFrame",
7784
"ModifierData",
7885
"ModifierOperation",
7986
"Recipe",

mcproto/types/advancement.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
from __future__ import annotations
22

3+
from enum import IntEnum
34
from typing import final
4-
from attrs import define
55

6+
from attrs import define
67
from typing_extensions import override
78

89
from mcproto.buffer import Buffer
910
from mcproto.protocol import StructFormat
10-
from mcproto.types.identifier import Identifier
11+
from mcproto.types.abc import MCType
1112
from mcproto.types.chat import TextComponent
13+
from mcproto.types.identifier import Identifier
1214
from mcproto.types.slot import Slot
13-
from mcproto.types.abc import MCType
1415

1516

1617
@final
1718
@define
1819
class Advancement(MCType):
1920
"""Represents an advancement in the game.
2021
21-
https://wiki.vg/Protocol#Update_Advancements
22+
Non-standard type, see: `<https://wiki.vg/Protocol#Update_Advancements>`
2223
2324
:param parent: The parent advancement.
2425
:type parent: :class:`~mcproto.types.identifier.Identifier`, optional
@@ -56,6 +57,14 @@ def deserialize(cls, buf: Buffer) -> Advancement:
5657
return cls(parent=parent, display=display, requirements=requirements, telemetry=telemetry)
5758

5859

60+
class AdvancementFrame(IntEnum):
61+
"""Represents the shape of the frame of an advancement in the GUI."""
62+
63+
TASK = 0
64+
CHALLENGE = 1
65+
GOAL = 2
66+
67+
5968
@final
6069
@define
6170
class AdvancementDisplay(MCType):
@@ -67,7 +76,8 @@ class AdvancementDisplay(MCType):
6776
:type description: :class:`~mcproto.types.chat.TextComponent`
6877
:param icon: The icon of the advancement.
6978
:type icon: :class:`~mcproto.types.slot.Slot`
70-
:param frame: The frame of the advancement (0: task, 1: challenge, 2: goal).
79+
:param frame: The frame of the advancement.
80+
:type frame: :class:`AdvancementFrame`
7181
:param background: The background texture of the advancement.
7282
:type background: :class:`~mcproto.types.identifier.Identifier`, optional
7383
:param show_toast: Whether to show a toast notification.
@@ -83,7 +93,7 @@ class AdvancementDisplay(MCType):
8393
title: TextComponent
8494
description: TextComponent
8595
icon: Slot
86-
frame: int
96+
frame: AdvancementFrame
8797
background: Identifier | None
8898
show_toast: bool
8999
hidden: bool
@@ -95,7 +105,7 @@ def serialize_to(self, buf: Buffer) -> None:
95105
self.title.serialize_to(buf)
96106
self.description.serialize_to(buf)
97107
self.icon.serialize_to(buf)
98-
buf.write_varint(self.frame)
108+
buf.write_varint(self.frame.value)
99109

100110
flags = (self.background is not None) << 0 | self.show_toast << 1 | self.hidden << 2
101111
buf.write_value(StructFormat.BYTE, flags)
@@ -110,7 +120,7 @@ def deserialize(cls, buf: Buffer) -> AdvancementDisplay:
110120
title = TextComponent.deserialize(buf)
111121
description = TextComponent.deserialize(buf)
112122
icon = Slot.deserialize(buf)
113-
frame = buf.read_varint()
123+
frame = AdvancementFrame(buf.read_varint())
114124
flags = buf.read_value(StructFormat.BYTE)
115125
background = Identifier.deserialize(buf) if flags & 0x1 else None
116126
show_toast = bool(flags & 0x2)
@@ -166,7 +176,7 @@ class AdvancementCriterion(MCType):
166176
:type date: int, optional
167177
"""
168178

169-
date: int | None
179+
date: int | None = None
170180

171181
@override
172182
def serialize_to(self, buf: Buffer) -> None:

mcproto/types/angle.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import annotations
22

3-
from typing import final
43
import math
5-
from attrs import define
4+
from typing import final
65

6+
from attrs import define, field
77
from typing_extensions import override
88

99
from mcproto.buffer import Buffer
@@ -18,9 +18,12 @@ class Angle(MCType):
1818
"""Represents a rotation angle for an entity.
1919
2020
:param value: The angle value in 1/256th of a full rotation.
21+
:type value: int
22+
23+
.. note:: The angle is stored as a byte, so the value is in the range [0, 255].
2124
"""
2225

23-
angle: int
26+
angle: int = field(converter=lambda x: int(x) % 256)
2427

2528
@override
2629
def serialize_to(self, buf: Buffer) -> None:
@@ -36,11 +39,6 @@ def deserialize(cls, buf: Buffer) -> Angle:
3639
payload = buf.read_value(StructFormat.BYTE)
3740
return cls(angle=int(payload * 360 / 256))
3841

39-
@override
40-
def validate(self) -> None:
41-
"""Constrain the angle to the range [0, 256)."""
42-
self.angle %= 256
43-
4442
def in_direction(self, base: Vec3, distance: float) -> Vec3:
4543
"""Calculate the position in the direction of the angle in the xz-plane.
4644

0 commit comments

Comments
 (0)