153 lines
6.5 KiB
Python
153 lines
6.5 KiB
Python
from .lib.utils import pascal_case, split_prefixed
|
|
from .lib.java import java_float, java_static_invoke
|
|
|
|
def resolve_block(block: dict) -> dict:
|
|
block = dict(block)
|
|
block["concreteClassName"] = block["customClassName"] or pascal_case(block["path"])
|
|
block["propertyCalls"], block["propertyImports"] = build_property_calls(block["properties"])
|
|
if block["textures"] is not None:
|
|
block["resolvedTextures"] = resolve_textures(block["textures"])
|
|
if block["blockEntity"] is not None:
|
|
block_entity = dict(block["blockEntity"])
|
|
block_entity["concreteClassName"] = block_entity["customClassName"] or pascal_case(block["path"]) + "Entity"
|
|
block["blockEntity"] = block_entity
|
|
return block
|
|
|
|
|
|
def resolve_textures(textures: dict) -> dict:
|
|
"""Resolves the all/top/bottom/side/<face> convenience fields into the six
|
|
explicit faces (using the vanilla "cube" parent model's own variable
|
|
names: up/down instead of top/bottom) plus particle, applying
|
|
face-specific > side > all precedence."""
|
|
all_tex = textures["all"]
|
|
side = textures["side"] or all_tex
|
|
faces = {
|
|
"up": textures["top"] or all_tex,
|
|
"down": textures["bottom"] or all_tex,
|
|
"north": textures["north"] or side,
|
|
"south": textures["south"] or side,
|
|
"east": textures["east"] or side,
|
|
"west": textures["west"] or side,
|
|
}
|
|
faces["particle"] = faces["up"] or next(iter(faces.values()))
|
|
return faces
|
|
|
|
|
|
# Each entry describes how one `properties.*` field becomes a chained
|
|
# BlockBehaviour.Properties builder call. Extra fully-qualified imports are
|
|
# collected separately (Java method references can use FQNs inline, so only
|
|
# constants/enums referenced by simple name need an import).
|
|
def build_property_calls(properties: dict) -> tuple[list[str], list[str]]:
|
|
p = properties
|
|
calls: list[str] = []
|
|
imports: set[str] = set()
|
|
|
|
calls.append(f".destroyTime({java_float(p['hardness'])})")
|
|
if p["resistance"] is not None:
|
|
calls.append(f".explosionResistance({java_float(p['resistance'])})")
|
|
if p["requiresTool"]:
|
|
calls.append(".requiresCorrectToolForDrops()")
|
|
if p["noCollision"]:
|
|
calls.append(".noCollission()")
|
|
if p["noOcclusion"]:
|
|
calls.append(".noOcclusion()")
|
|
if p["dynamicShape"]:
|
|
calls.append(".dynamicShape()")
|
|
if p["forceSolidOn"]:
|
|
calls.append(".forceSolidOn()")
|
|
if p["pushReaction"] != "NORMAL":
|
|
calls.append(f".pushReaction(PushReaction.{p['pushReaction']})")
|
|
imports.add("net.minecraft.world.level.material.PushReaction")
|
|
if p["offsetType"] != "NONE":
|
|
calls.append(f".offsetType(BlockBehaviour.OffsetType.{p['offsetType']})")
|
|
if p["replaceable"]:
|
|
calls.append(".replaceable()")
|
|
if p["friction"] != 0.6:
|
|
calls.append(f".friction({java_float(p['friction'])})")
|
|
if p["speedFactor"] != 1.0:
|
|
calls.append(f".speedFactor({java_float(p['speedFactor'])})")
|
|
if p["jumpFactor"] != 1.0:
|
|
calls.append(f".jumpFactor({java_float(p['jumpFactor'])})")
|
|
|
|
if p["mapColor"] is not None:
|
|
kind, value = split_prefixed(p["mapColor"], "properties.mapColor")
|
|
if kind == "dye":
|
|
calls.append(f".mapColor(DyeColor.{value})")
|
|
imports.add("net.minecraft.world.item.DyeColor")
|
|
elif kind == "map":
|
|
calls.append(f".mapColor(MapColor.{value})")
|
|
imports.add("net.minecraft.world.level.material.MapColor")
|
|
elif kind == "closure":
|
|
calls.append(f".mapColor({value})")
|
|
else:
|
|
raise ValueError(f"properties.mapColor: unknown prefix '{kind}:', expected 'dye:', 'map:', or 'closure:'")
|
|
|
|
if p["lightLevel"] is not None:
|
|
light_level = p["lightLevel"]
|
|
if isinstance(light_level, int):
|
|
calls.append(f".lightLevel(state -> {light_level})")
|
|
else:
|
|
kind, value = split_prefixed(light_level, "properties.lightLevel")
|
|
if kind != "closure":
|
|
raise ValueError(f"properties.lightLevel: string values must be prefixed with 'closure:', got: {light_level!r}")
|
|
calls.append(f".lightLevel({value})")
|
|
|
|
if p["noTerrainParticles"]:
|
|
calls.append(".noTerrainParticles()")
|
|
|
|
if p["isViewBlocking"] is not None:
|
|
_, value = split_prefixed(p["isViewBlocking"], "properties.isViewBlocking")
|
|
calls.append(f".isViewBlocking({value})")
|
|
|
|
if p["isSuffocating"] is not None:
|
|
_, value = split_prefixed(p["isSuffocating"], "properties.isSuffocating")
|
|
calls.append(f".isSuffocating({value})")
|
|
|
|
calls.append(f".sound(SoundType.{p['sounds']})")
|
|
|
|
if p["instruments"] is not None:
|
|
calls.append(f".instrument(NoteBlockInstrument.{p['instruments']})")
|
|
imports.add("net.minecraft.world.level.block.state.properties.NoteBlockInstrument")
|
|
|
|
if p["noLootTable"]:
|
|
calls.append(".noLootTable()")
|
|
|
|
if p["lootFrom"] is not None:
|
|
kind, value = split_prefixed(p["lootFrom"], "properties.lootFrom")
|
|
if kind != "closure":
|
|
raise ValueError(f"properties.lootFrom: expected 'closure:pkg.Class::method', got: {p['lootFrom']!r}")
|
|
calls.append(f".dropsLike({java_static_invoke(value, 'properties.lootFrom')})")
|
|
|
|
if p["randomTicks"]:
|
|
calls.append(".randomTicks()")
|
|
if p["ignitesByLava"]:
|
|
calls.append(".ignitedByLava()")
|
|
if p["liquid"]:
|
|
calls.append(".liquid()")
|
|
|
|
if p["isValidSpawn"] is not None:
|
|
_, value = split_prefixed(p["isValidSpawn"], "properties.isValidSpawn")
|
|
calls.append(f".isValidSpawn({value})")
|
|
|
|
if p["air"]:
|
|
calls.append(".air()")
|
|
|
|
if p["requiredFeatures"]:
|
|
parts = []
|
|
for feature in p["requiredFeatures"]:
|
|
if feature.startswith("closure:"):
|
|
_, value = split_prefixed(feature, "properties.requiredFeatures")
|
|
parts.append(value)
|
|
else:
|
|
namespace, _, name = feature.partition(":")
|
|
if namespace != "minecraft":
|
|
raise ValueError(
|
|
f"properties.requiredFeatures: non-vanilla feature '{feature}' needs 'closure:pkg.Class::FIELD' "
|
|
"pointing at a static FeatureFlag field -- snowy doesn't yet know how to look up custom mods' flags by id"
|
|
)
|
|
parts.append(f"FeatureFlags.{name.upper()}")
|
|
imports.add("net.minecraft.world.flag.FeatureFlags")
|
|
calls.append(f".requiredFeatures({', '.join(parts)})")
|
|
|
|
return calls, sorted(imports)
|