Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 124ecd7

Browse files
committedMay 21, 2024··
Added the Entity Metadata type
* Modified the conf.py sphinx configuration file to ignore the custom fields used in the Entity Metadata type * Added the Entity Metadata type to the documentation * Added `scripts` to the .codeclimate.yml ignore list
1 parent 2e98b96 commit 124ecd7

17 files changed

+16765
-5
lines changed
 

‎.codeclimate.yml

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ checks:
3131
return-statements:
3232
enabled: false
3333

34+
35+
3436
exclude_patterns:
3537
- "tests/**"
3638
- ".github/**"
39+
# The scripts directory is only there to provide quick scripts to generate things that are in the actual code
40+
# They don't need testing as they are not meant to be used outside the developpment process
41+
- "scripts/**"

‎docs/api/types/entity_metadata.rst

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Entity Metadata
2+
======================
3+
4+
This is the documentation for the NBT type used in Minecraft's network protocol.
5+
6+
7+
8+
9+
.. automodule:: mcproto.types.entity
10+
:no-undoc-members:

‎docs/api/types/index.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
.. api/types documentation master file
1+
.. Types Documentation
22
3-
=======================
4-
API Types Documentation
5-
=======================
3+
Types Documentation
4+
==================================
65

7-
Welcome to the API Types documentation! This documentation provides information about the various types used in the API.
6+
This folder contains the documentation for various types used in the project.
87

98
.. toctree::
109
:maxdepth: 2
1110

1211
nbt.rst
12+
entity_metadata.rst

‎docs/api/types/nbt.rst

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
NBT Format
22
==========
33

4+
This is the documentation for the NBT type used in Minecraft's network protocol.
5+
6+
47
.. automodule:: mcproto.types.nbt
58
:members:
69
:show-inheritance:

‎docs/conf.py

+18
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
import sys
1313
import datetime
1414
from pathlib import Path
15+
from typing import Any
1516

1617
from packaging.version import parse as parse_version
1718
from typing_extensions import override
1819

20+
from mcproto.types.entity.metadata import _ProxyEntityMetadataEntry, _DefaultEntityMetadataEntry
21+
1922
if sys.version_info >= (3, 11):
2023
from tomllib import load as toml_parse
2124
else:
@@ -117,6 +120,21 @@
117120
"exclude-members": "__dict__,__weakref__",
118121
}
119122

123+
124+
def autodoc_skip_member(app: Any, what: str, name: str, obj: Any, skip: bool, options: Any) -> bool:
125+
"""Skip EntityMetadataEntry class fields as they are already documented in the docstring."""
126+
if isinstance(obj, type) and (
127+
issubclass(obj, _ProxyEntityMetadataEntry) or issubclass(obj, _DefaultEntityMetadataEntry)
128+
):
129+
return True
130+
return skip
131+
132+
133+
def setup(app: Any) -> None:
134+
"""Set up the Sphinx app."""
135+
app.connect("autodoc-skip-member", autodoc_skip_member)
136+
137+
120138
# -- sphinx.ext.autosectionlabel ---------------
121139

122140
# Automatically generate section labels:

‎mcproto/types/entity/__init__.py

+300
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
from mcproto.types.entity.generated import (
2+
EntityEM,
3+
InteractionEM,
4+
DisplayEM,
5+
BlockDisplayEM,
6+
ItemDisplayEM,
7+
TextDisplayEM,
8+
ThrownItemProjectileEM,
9+
ThrownEggEM,
10+
ThrownEnderPearlEM,
11+
ThrownExperienceBottleEM,
12+
ThrownPotionEM,
13+
ThrownSnowballEM,
14+
EyeOfEnderEM,
15+
FallingBlockEM,
16+
AreaEffectCloudEM,
17+
FishingHookEM,
18+
AbstractArrowEM,
19+
ArrowEM,
20+
SpectralArrowEM,
21+
ThrownTridentEM,
22+
AbstractVehicleEM,
23+
BoatEM,
24+
ChestBoatEM,
25+
AbstractMinecartEM,
26+
MinecartEM,
27+
AbstractMinecartContainerEM,
28+
MinecartHopperEM,
29+
MinecartChestEM,
30+
MinecartFurnaceEM,
31+
MinecartTNTEM,
32+
MinecartSpawnerEM,
33+
MinecartCommandBlockEM,
34+
EndCrystalEM,
35+
DragonFireballEM,
36+
SmallFireballEM,
37+
FireballEM,
38+
WitherSkullEM,
39+
FireworkRocketEM,
40+
ItemFrameEM,
41+
GlowingItemFrameEM,
42+
PaintingEM,
43+
ItemEntityEM,
44+
LivingEntityEM,
45+
PlayerEM,
46+
ArmorStandEM,
47+
MobEM,
48+
AmbientCreatureEM,
49+
BatEM,
50+
PathfinderMobEM,
51+
WaterAnimalEM,
52+
SquidEM,
53+
DolphinEM,
54+
AbstractFishEM,
55+
CodEM,
56+
PufferFishEM,
57+
SalmonEM,
58+
TropicalFishEM,
59+
TadpoleEM,
60+
AgeableMobEM,
61+
AnimalEM,
62+
SnifferEM,
63+
AbstractHorseEM,
64+
HorseEM,
65+
ZombieHorseEM,
66+
SkeletonHorseEM,
67+
CamelEM,
68+
ChestedHorseEM,
69+
DonkeyEM,
70+
LlamaEM,
71+
TraderLlamaEM,
72+
MuleEM,
73+
AxolotlEM,
74+
BeeEM,
75+
FoxEM,
76+
FrogEM,
77+
OcelotEM,
78+
PandaEM,
79+
PigEM,
80+
RabbitEM,
81+
TurtleEM,
82+
PolarBearEM,
83+
ChickenEM,
84+
CowEM,
85+
MooshroomEM,
86+
HoglinEM,
87+
SheepEM,
88+
StriderEM,
89+
GoatEM,
90+
TameableAnimalEM,
91+
CatEM,
92+
WolfEM,
93+
ParrotEM,
94+
AbstractVillagerEM,
95+
VillagerEM,
96+
WanderingTraderEM,
97+
AbstractGolemEM,
98+
IronGolemEM,
99+
SnowGolemEM,
100+
ShulkerEM,
101+
MonsterEM,
102+
BasePiglinEM,
103+
PiglinEM,
104+
PiglinBruteEM,
105+
BlazeEM,
106+
CreeperEM,
107+
EndermiteEM,
108+
GiantEM,
109+
GuardianEM,
110+
ElderGuardianEM,
111+
SilverfishEM,
112+
RaiderEM,
113+
AbstractIllagerEM,
114+
VindicatorEM,
115+
PillagerEM,
116+
SpellcasterIllagerEM,
117+
EvokerEM,
118+
IllusionerEM,
119+
RavagerEM,
120+
WitchEM,
121+
EvokerFangsEM,
122+
VexEM,
123+
AbstractSkeletonEM,
124+
SkeletonEM,
125+
WitherSkeletonEM,
126+
StrayEM,
127+
SpiderEM,
128+
WardenEM,
129+
WitherEM,
130+
ZoglinEM,
131+
ZombieEM,
132+
ZombieVillagerEM,
133+
HuskEM,
134+
DrownedEM,
135+
ZombifiedPiglinEM,
136+
EndermanEM,
137+
EnderDragonEM,
138+
FlyingEM,
139+
GhastEM,
140+
PhantomEM,
141+
SlimeEM,
142+
LlamaSpitEM,
143+
PrimedTntEM,
144+
)
145+
146+
######################################################################
147+
# This file is automatically generated by the entity generator script.
148+
# You can modify it by changing what you want in the script.
149+
######################################################################
150+
151+
from mcproto.types.entity.enums import Direction, Pose, SnifferState, DragonPhase
152+
153+
__all__ = [
154+
"EntityEM",
155+
"InteractionEM",
156+
"DisplayEM",
157+
"BlockDisplayEM",
158+
"ItemDisplayEM",
159+
"TextDisplayEM",
160+
"ThrownItemProjectileEM",
161+
"ThrownEggEM",
162+
"ThrownEnderPearlEM",
163+
"ThrownExperienceBottleEM",
164+
"ThrownPotionEM",
165+
"ThrownSnowballEM",
166+
"EyeOfEnderEM",
167+
"FallingBlockEM",
168+
"AreaEffectCloudEM",
169+
"FishingHookEM",
170+
"AbstractArrowEM",
171+
"ArrowEM",
172+
"SpectralArrowEM",
173+
"ThrownTridentEM",
174+
"AbstractVehicleEM",
175+
"BoatEM",
176+
"ChestBoatEM",
177+
"AbstractMinecartEM",
178+
"MinecartEM",
179+
"AbstractMinecartContainerEM",
180+
"MinecartHopperEM",
181+
"MinecartChestEM",
182+
"MinecartFurnaceEM",
183+
"MinecartTNTEM",
184+
"MinecartSpawnerEM",
185+
"MinecartCommandBlockEM",
186+
"EndCrystalEM",
187+
"DragonFireballEM",
188+
"SmallFireballEM",
189+
"FireballEM",
190+
"WitherSkullEM",
191+
"FireworkRocketEM",
192+
"ItemFrameEM",
193+
"GlowingItemFrameEM",
194+
"PaintingEM",
195+
"ItemEntityEM",
196+
"LivingEntityEM",
197+
"PlayerEM",
198+
"ArmorStandEM",
199+
"MobEM",
200+
"AmbientCreatureEM",
201+
"BatEM",
202+
"PathfinderMobEM",
203+
"WaterAnimalEM",
204+
"SquidEM",
205+
"DolphinEM",
206+
"AbstractFishEM",
207+
"CodEM",
208+
"PufferFishEM",
209+
"SalmonEM",
210+
"TropicalFishEM",
211+
"TadpoleEM",
212+
"AgeableMobEM",
213+
"AnimalEM",
214+
"SnifferEM",
215+
"AbstractHorseEM",
216+
"HorseEM",
217+
"ZombieHorseEM",
218+
"SkeletonHorseEM",
219+
"CamelEM",
220+
"ChestedHorseEM",
221+
"DonkeyEM",
222+
"LlamaEM",
223+
"TraderLlamaEM",
224+
"MuleEM",
225+
"AxolotlEM",
226+
"BeeEM",
227+
"FoxEM",
228+
"FrogEM",
229+
"OcelotEM",
230+
"PandaEM",
231+
"PigEM",
232+
"RabbitEM",
233+
"TurtleEM",
234+
"PolarBearEM",
235+
"ChickenEM",
236+
"CowEM",
237+
"MooshroomEM",
238+
"HoglinEM",
239+
"SheepEM",
240+
"StriderEM",
241+
"GoatEM",
242+
"TameableAnimalEM",
243+
"CatEM",
244+
"WolfEM",
245+
"ParrotEM",
246+
"AbstractVillagerEM",
247+
"VillagerEM",
248+
"WanderingTraderEM",
249+
"AbstractGolemEM",
250+
"IronGolemEM",
251+
"SnowGolemEM",
252+
"ShulkerEM",
253+
"MonsterEM",
254+
"BasePiglinEM",
255+
"PiglinEM",
256+
"PiglinBruteEM",
257+
"BlazeEM",
258+
"CreeperEM",
259+
"EndermiteEM",
260+
"GiantEM",
261+
"GuardianEM",
262+
"ElderGuardianEM",
263+
"SilverfishEM",
264+
"RaiderEM",
265+
"AbstractIllagerEM",
266+
"VindicatorEM",
267+
"PillagerEM",
268+
"SpellcasterIllagerEM",
269+
"EvokerEM",
270+
"IllusionerEM",
271+
"RavagerEM",
272+
"WitchEM",
273+
"EvokerFangsEM",
274+
"VexEM",
275+
"AbstractSkeletonEM",
276+
"SkeletonEM",
277+
"WitherSkeletonEM",
278+
"StrayEM",
279+
"SpiderEM",
280+
"WardenEM",
281+
"WitherEM",
282+
"ZoglinEM",
283+
"ZombieEM",
284+
"ZombieVillagerEM",
285+
"HuskEM",
286+
"DrownedEM",
287+
"ZombifiedPiglinEM",
288+
"EndermanEM",
289+
"EnderDragonEM",
290+
"FlyingEM",
291+
"GhastEM",
292+
"PhantomEM",
293+
"SlimeEM",
294+
"LlamaSpitEM",
295+
"PrimedTntEM",
296+
"Direction",
297+
"Pose",
298+
"SnifferState",
299+
"DragonPhase",
300+
]

‎mcproto/types/entity/enums.py

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from enum import IntEnum
2+
3+
4+
class Direction(IntEnum):
5+
"""Represents a direction in the world."""
6+
7+
DOWN = 0
8+
"""Towards the negative y-axis. (-y)"""
9+
UP = 1
10+
"""Towards the positive y-axis. (+y)"""
11+
NORTH = 2
12+
"""Towards the negative z-axis. (-z)"""
13+
SOUTH = 3
14+
"""Towards the positive z-axis. (+z)"""
15+
WEST = 4
16+
"""Towards the negative x-axis. (-x)"""
17+
EAST = 5
18+
"""Towards the positive x-axis. (+x)"""
19+
20+
21+
class Pose(IntEnum):
22+
"""Represents a pose of an entity."""
23+
24+
STANDING = 0
25+
"""The entity is standing. (default)"""
26+
FALL_FLYING = 1
27+
"""The entity is falling or flying."""
28+
SLEEPING = 2
29+
"""The entity is sleeping. (e.g. in a bed)"""
30+
SWIMMING = 3
31+
"""The entity is swimming."""
32+
SPIN_ATTACK = 4
33+
"""The entity is performing a spin attack (with a riptide trident)."""
34+
SNEAKING = 5
35+
"""The entity is sneaking."""
36+
LONG_JUMPING = 6
37+
"""The entity is long jumping."""
38+
DYING = 7
39+
"""The entity is dying"""
40+
CROAKING = 8
41+
"""The entity is croaking. (a frog)"""
42+
USING_TONGUE = 9
43+
"""The entity is using its tongue. (a frog)"""
44+
SITTING = 10
45+
"""The entity is sitting. (e.g. a pet)"""
46+
ROARING = 11
47+
"""The entity is roaring. (a warden)"""
48+
SNIFFING = 12
49+
"""The entity is sniffing."""
50+
EMERGING = 13
51+
"""The entity is emerging from the ground. (a warden)"""
52+
DIGGING = 14
53+
"""The entity is digging."""
54+
55+
56+
class SnifferState(IntEnum):
57+
"""Represents the state of a sniffer."""
58+
59+
IDLING = 0
60+
FEELING_HAPPY = 1
61+
SCENTING = 2
62+
SNIFFING = 3
63+
SEARCHING = 4
64+
DIGGING = 5
65+
RISING = 6
66+
67+
68+
class DragonPhase(IntEnum):
69+
"""Represents the state the ender dragon is in."""
70+
71+
CIRCLING = 0
72+
"""The dragon is circling around the portal."""
73+
STRAFING = 1
74+
"""The dragon is strafing the player."""
75+
FLYING_TO_PORTAL = 2
76+
"""The dragon is flying to the portal."""
77+
LANDING_ON_PORTAL = 3
78+
"""The dragon is landing on the portal. (perching)"""
79+
TAKING_OFF_FROM_PORTAL = 4
80+
"""The dragon is taking off from the portal."""
81+
LANDED_BREATH_ATTACK = 5
82+
"""The dragon has landed and is performing a breath attack."""
83+
LANDED_LOOKING_FOR_PLAYER = 6
84+
"""The dragon has landed and is looking for the player."""
85+
LANDED_ROAR = 7
86+
"""The dragon has landed and is roaring."""
87+
CHARGING_PLAYER = 8
88+
"""The dragon is charging at the player."""
89+
FLYING_TO_PORTAL_TO_DIE = 9
90+
"""The dragon is flying to the portal to die."""
91+
HOVERING_NO_AI = 10
92+
"""The dragon is hovering in place with no AI. (default)"""

‎mcproto/types/entity/generated.py

+11,383
Large diffs are not rendered by default.

‎mcproto/types/entity/metadata.py

+459
Large diffs are not rendered by default.

‎mcproto/types/entity/metadata_types.py

+670
Large diffs are not rendered by default.

‎scripts/__init__.py

Whitespace-only changes.

‎scripts/entity_generator.py

+277
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
from __future__ import annotations
2+
from typing import List, Dict, Union, cast
3+
from pathlib import Path
4+
import subprocess
5+
import sys
6+
7+
from scripts.entity_generator_data import ENTITY_DATA, EntityData, Field
8+
9+
BASE_CLASS = '''
10+
class {name}({base}):
11+
"""{class_description}
12+
13+
{fields_docstring}
14+
15+
"""
16+
17+
{fields}
18+
19+
__slots__ = ()
20+
21+
'''
22+
23+
24+
ENTRY_FIELD = """{name}: {input} = entry({type}, {default})"""
25+
INPUT_UNAVAILABLE = """ClassVar[{input}]"""
26+
# Mark as ignore by sphinx
27+
PROXY_FIELD = """{name}: {input} = proxy({target}, {type}, {kwargs})"""
28+
29+
FIELD_DESCRIPTION = """:param {name}: {description}
30+
:type {name}: {input}, optional, default: {default}"""
31+
PROXY_DESCRIPTION = """:param {name}: {description} (this affects :attr:`{target}`)
32+
:type {name}: {input}, optional"""
33+
UNAVAILABLE_PREFIX = "_"
34+
TYPE_SUFFIX = "EME" # EntityMetadataEntry
35+
NAME_SUFFIX = "EM" # EntityMetadata
36+
37+
38+
FILE_HEADER = """
39+
######################################################################
40+
# This file is automatically generated by the entity generator script.
41+
# You can modify it by changing what you want in the script.
42+
######################################################################
43+
44+
from mcproto.types.entity.enums import {enums}
45+
46+
__all__ = [
47+
{all}
48+
]
49+
"""
50+
51+
BASE_FILE = """
52+
from __future__ import annotations
53+
from typing import ClassVar, Any
54+
{header}
55+
56+
from mcproto.types.entity.metadata import (
57+
proxy,
58+
entry,
59+
EntityMetadata,
60+
)
61+
from mcproto.types.entity.metadata_types import {types}
62+
from mcproto.types.slot import Slot
63+
from mcproto.types.chat import TextComponent
64+
from mcproto.types.nbt import NBTag, EndNBT
65+
from mcproto.types.vec3 import Position
66+
from mcproto.types.uuid import UUID
67+
68+
{classes}
69+
"""
70+
INIT_FILE = """
71+
from mcproto.types.entity.generated import {generated}
72+
{header}
73+
"""
74+
75+
FILE_PATH = "mcproto/types/entity/generated.py"
76+
INIT_PATH = "mcproto/types/entity/__init__.py"
77+
78+
# This dictionary holds the decumentation for each entity to allow it to be appended to inherited classes
79+
main_doc_repo: dict[str, str] = {}
80+
81+
82+
def generate_class(entity_description: EntityData) -> tuple[str, set[str], set[str]]: # noqa: PLR0912
83+
"""Generate a class from the entity description.
84+
85+
:param entity_description: The entity description to generate the class from.
86+
87+
:return: The generated string for the class, a set of used enums, and a set of used types.
88+
"""
89+
field_docs: list[str] = []
90+
fields: list[str] = []
91+
used_enums: set[str] = set()
92+
used_types: set[str] = set()
93+
name = entity_description["name"]
94+
name = cast(str, name) + NAME_SUFFIX # EntityMetadata
95+
base = entity_description.get("base", "EntityMetadata")
96+
base = cast(str, base)
97+
if base != "EntityMetadata":
98+
base += NAME_SUFFIX
99+
for variable in entity_description["fields"]:
100+
variable = cast(Field, variable)
101+
v_type = cast(str, variable["type"])
102+
v_name = cast(str, variable["name"])
103+
v_default = variable["default"]
104+
v_input = variable["input"]
105+
v_description = variable.get("description", "")
106+
v_proxy = cast(List[Dict[str, Union[type, str, int]]], variable.get("proxy", []))
107+
v_available = variable.get("available", True)
108+
v_enum = variable.get("enum", False)
109+
110+
if v_enum:
111+
used_enums.add(v_type)
112+
v_default = f"{v_type}.{v_default}"
113+
114+
v_type = v_type + TYPE_SUFFIX
115+
used_types.add(v_type)
116+
117+
if not v_available:
118+
v_name = UNAVAILABLE_PREFIX + v_name
119+
120+
if isinstance(v_input, type):
121+
v_input = v_input.__name__
122+
v_input = cast(str, v_input)
123+
124+
if v_input == "str":
125+
v_default = f'"{v_default}"'
126+
127+
if v_available:
128+
if v_description:
129+
if v_enum or not v_input.startswith(("str", "tuple", "float", "int", "bool")):
130+
v_default_desc = f":attr:`{v_default}`"
131+
v_input_desc = f":class:`{v_input}`"
132+
else:
133+
v_default_desc = v_default
134+
v_input_desc = v_input
135+
136+
field_docs.append(
137+
FIELD_DESCRIPTION.format(
138+
name=v_name, description=v_description, input=v_input_desc, default=v_default_desc
139+
)
140+
)
141+
else:
142+
v_input = INPUT_UNAVAILABLE.format(input=v_input)
143+
144+
fields.append(
145+
ENTRY_FIELD.format(
146+
name=v_name,
147+
input=v_input,
148+
type=v_type,
149+
default=v_default,
150+
)
151+
)
152+
for proxy_field in v_proxy:
153+
proxy_name = cast(str, proxy_field["name"])
154+
proxy_type = cast(str, proxy_field["type"])
155+
proxy_input = proxy_field.get("input", int)
156+
proxy_description = proxy_field.get("description", "")
157+
proxy_target = v_name
158+
159+
if isinstance(proxy_input, type):
160+
proxy_input = proxy_input.__name__ # convert the str type to "str" ...
161+
162+
used_types.add(proxy_type)
163+
164+
proxy_kwargs = ""
165+
for k, v in proxy_field.items():
166+
if k not in ("name", "type", "input", "description"):
167+
if k == "mask":
168+
v = cast(int, v)
169+
v = hex(v) # noqa: PLW2901
170+
proxy_kwargs += f"{k}={v},"
171+
if proxy_kwargs:
172+
proxy_kwargs = proxy_kwargs[:-1] # remove the last comma
173+
174+
if proxy_description:
175+
field_docs.append(
176+
PROXY_DESCRIPTION.format(
177+
name=proxy_name,
178+
description=proxy_description,
179+
target=proxy_target,
180+
input=proxy_input,
181+
)
182+
)
183+
184+
fields.append(
185+
PROXY_FIELD.format(
186+
name=proxy_name,
187+
input=proxy_input,
188+
target=proxy_target,
189+
type=proxy_type,
190+
kwargs=proxy_kwargs,
191+
)
192+
)
193+
194+
# Split lines inside docstrings
195+
196+
if base in main_doc_repo:
197+
field_docs += ["", f"Inherited from :class:`{base}`:", ""] + [
198+
line.strip("\n\r\t").replace(" " * 4, "", 1) for line in main_doc_repo[base].split("\n")
199+
]
200+
else:
201+
print(f"Warning: {base} not found in main_doc_repo") # noqa: T201
202+
203+
if field_docs[0] == "":
204+
field_docs = field_docs[1:]
205+
field_docs_str = "\n ".join("\n".join(field_docs).split("\n"))
206+
main_doc_repo[name] = field_docs_str
207+
return (
208+
BASE_CLASS.format(
209+
name=name,
210+
base=base,
211+
class_description=entity_description["description"],
212+
fields_docstring=field_docs_str,
213+
fields="\n ".join(fields),
214+
),
215+
used_enums,
216+
used_types,
217+
)
218+
219+
220+
def write_files(entity_data: list[EntityData]) -> None:
221+
"""Generate the base file for the entities.
222+
223+
:param entity_data: The entity data to generate the base file from.
224+
225+
:return: The generated string for the base file.
226+
"""
227+
types: set[str] = set()
228+
enums: set[str] = set()
229+
all_classes: list[str] = []
230+
class_code: list[str] = []
231+
generated: list[str] = []
232+
for entity in entity_data:
233+
class_str, used_enums, used_types = generate_class(entity)
234+
entity_name = cast(str, entity["name"]) + NAME_SUFFIX
235+
types.update(used_types)
236+
enums.update(used_enums)
237+
all_classes.append(entity_name)
238+
class_code.append(class_str)
239+
generated.append(entity_name)
240+
all_classes_str = ",\n ".join(f'"{c}"' for c in all_classes + list(enums))
241+
types_str = ", ".join(types)
242+
enums_str = ", ".join(enums)
243+
generated_str = ", ".join(generated)
244+
245+
header = FILE_HEADER.format(
246+
enums=enums_str,
247+
all=all_classes_str,
248+
)
249+
with Path(FILE_PATH).open("w") as file:
250+
file.write(
251+
BASE_FILE.format(
252+
types=types_str,
253+
header=header,
254+
classes="\n\n".join(class_code),
255+
)
256+
)
257+
258+
with Path(INIT_PATH).open("w") as file:
259+
file.write(INIT_FILE.format(header=header, generated=generated_str))
260+
261+
262+
def format_ruff(path: Path) -> None:
263+
"""Format the generated files with ruff.
264+
265+
:param path: The path to the file to format.
266+
"""
267+
# Get the python site packages path
268+
subprocess.run(
269+
[sys.executable, "-m", "ruff", "format", str(path.absolute())], # noqa: S603
270+
check=True,
271+
)
272+
273+
274+
if __name__ == "__main__":
275+
write_files(ENTITY_DATA)
276+
format_ruff(Path(FILE_PATH))
277+
format_ruff(Path(INIT_PATH))

‎scripts/entity_generator_data.py

+2,878
Large diffs are not rendered by default.

‎tests/helpers.py

+1
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ def test_serialization(self, kwargs: dict[str, Any], expected_bytes: bytes):
302302
def test_deserialization(self, kwargs: dict[str, Any], expected_bytes: bytes):
303303
"""Test deserialization of the object."""
304304
buf = Buffer(expected_bytes)
305+
305306
obj = cls.deserialize(buf)
306307
equality = cls(**kwargs) == obj
307308
error_message: list[str] = []
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from __future__ import annotations
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from mcproto.types.entity import PandaEM
2+
from mcproto.buffer import Buffer
3+
4+
5+
import pytest
6+
from mcproto.types.entity.metadata import proxy, entry, EntityMetadataCreator, EntityMetadata
7+
from mcproto.types.entity.metadata_types import ByteEME, Masked
8+
9+
10+
def test_panda():
11+
"""Test the Panda entity (it has a lot of inherited fields, so it's a good test case)."""
12+
panda = PandaEM()
13+
14+
assert panda.serialize() == b"\xff" # No metadata
15+
16+
panda.active_hand = 0
17+
panda.is_hand_active = True
18+
panda.is_riptide_spin_attack = True
19+
# Index 8, Type 0 (byte), Value 0b00000101
20+
assert panda.serialize() == b"\x08\x00\x05\xff"
21+
# 0bxx1 hand active
22+
# 0bx0x main hand active (1 for offhand)
23+
# 0b1xx riptide spin attack
24+
25+
panda.is_sneezing = True
26+
# Index 8, Type 0 (byte), Value 0b00000101, Index 22, Type 0 (byte), Value 2
27+
assert panda.serialize() == b"\x08\x00\x05\x16\x00\x02\xff"
28+
29+
panda.active_hand = 0
30+
panda.is_hand_active = False
31+
panda.is_riptide_spin_attack = False
32+
panda.eat_timer = -1 # VarInt 0xff 0xff 0xff 0xff 0x0f
33+
# Index 22, Type 0 (byte), Value 2, Index 19, Type 1 (VarInt), Value -1
34+
assert panda.serialize() == b"\x13\x01\xff\xff\xff\xff\x0f\x16\x00\x02\xff"
35+
36+
buf = Buffer(b"\x13\x01\x02\x16\x00\x02\x08\x00\x07\xff")
37+
panda2 = PandaEM.deserialize(buf)
38+
assert panda2.active_hand == 1
39+
assert panda2.is_hand_active
40+
assert panda2.is_riptide_spin_attack
41+
assert panda2.eat_timer == 2
42+
assert panda2.is_sneezing
43+
assert not panda2.is_sitting
44+
45+
46+
def test_kwargs():
47+
"""Test kwargs for EnitityMetadata."""
48+
panda = PandaEM(custom_name="test", is_custom_name_visible=True)
49+
assert panda.custom_name == "test"
50+
assert panda.is_custom_name_visible
51+
52+
53+
def test_class_error():
54+
"""Test errors for EntityMetadataCreator."""
55+
with pytest.raises(TypeError):
56+
proxy("test", Masked, mask=0x1) # wrong type
57+
58+
with pytest.raises(TypeError):
59+
proxy(object, Masked, mask=0x1) # wrong type
60+
61+
with pytest.raises(ValueError):
62+
63+
class Test(metaclass=EntityMetadataCreator): # type: ignore
64+
test: int
65+
66+
with pytest.raises(ValueError):
67+
68+
class Test(metaclass=EntityMetadataCreator): # type: ignore # noqa: F811
69+
test: int = 0
70+
71+
with pytest.raises(ValueError):
72+
EntityMetadata(1, 1) # type: ignore
73+
74+
with pytest.raises(ValueError):
75+
76+
class Test(metaclass=EntityMetadataCreator): # noqa: F811
77+
test: int = proxy(entry(ByteEME, 1), Masked, mask=0x1)
78+
79+
buf = Buffer(b"\x00\x02\x00") # Wrong metadata type
80+
with pytest.raises(TypeError):
81+
ByteEME.deserialize(buf)
82+
83+
EntityMetadata()
84+
85+
buf = Buffer(b"\x00\xff\x00") # Invalid metadata type
86+
with pytest.raises(TypeError):
87+
EntityMetadata.deserialize(buf)
88+
89+
with pytest.raises(ValueError):
90+
EntityMetadata(keyword="test")

‎tests/mcproto/types/entity/test_metadata_types.py

+573
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.