Gems that stay on the limb they were cut into

MIAPI draws a module's model in the pass its own origin names, and positions it
from the entry of the transform stack filed under that same name - a model with
no origin is drawn only for the inventory icon, from the entry a transform lands
in when it names none. Armory's gemstone names no origin, so it was never drawn
on worn armour by any route, and a slot transform aimed at the body reached
neither the limb nor the icon.

So the gems are ours now: one per limb, tagged so a left pauldron gem cannot go
in a knee and be drawn on the wrong arm, and an Apothic case beside each on the
same model and placement. The cases keep counting one Apotheosis socket apiece -
ApothicSockets already listed these module ids, so the Java is unchanged and two
armed cases still make two sockets on the chestplate.

Each carries two model entries, because one transform cannot aim both places: a
limb entry drawn under the part's animated pose, so it follows the walk cycle,
and an origin-less entry with its own offset for the icon. The limb placements
were solved rather than tried - the target taken in body space, Armory's limb
chain inverted, the result decomposed - and land on the pauldron face and the
knee to the pixel. The icon offsets sit on the pauldron boss and the knee band
of each of the four sprites.

The sockets themselves are drawn here too, under the limb origin, so there is a
setting for the stone to sit in. Their textures are unwrap templates for now and
want painting. The Apothic case wears the gemstone's model rather than its own
art, its materials unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-08-24 15:12:58 +02:00
parent 5e1b62fd9b
commit 742705839d
22 changed files with 804 additions and 22 deletions

View File

@ -25,6 +25,14 @@
"miapi.module.cmmodular.armor.socket.leg_right.name": "%s Heavy Socket Right Pants",
"miapi.module.cmmodular.armor.socket.leg_right.description": "Heavy Pants with a Gemstone set into the Knee",
"miapi.module.cmmodular.gem.apothic_case.name": "Apothic Gem Case",
"miapi.module.cmmodular.gem.limb.gem_arm_left.name": "%s Left Pauldron Gemstone",
"miapi.module.cmmodular.gem.limb.case_arm_left.name": "Left Pauldron Apothic Gem Case",
"miapi.module.cmmodular.gem.limb.gem_arm_right.name": "%s Right Pauldron Gemstone",
"miapi.module.cmmodular.gem.limb.case_arm_right.name": "Right Pauldron Apothic Gem Case",
"miapi.module.cmmodular.gem.limb.gem_leg_left.name": "%s Left Knee Gemstone",
"miapi.module.cmmodular.gem.limb.case_leg_left.name": "Left Knee Apothic Gem Case",
"miapi.module.cmmodular.gem.limb.gem_leg_right.name": "%s Right Knee Gemstone",
"miapi.module.cmmodular.gem.limb.case_leg_right.name": "Right Knee Apothic Gem Case",
"miapi.material.cmmodular.bone.dragonbone": "Dragonbone",
"miapi.material.cmmodular.bone.hippogryph_talon": "Talon",
"miapi.material.cmmodular.bone.sea_serpent_fang": "Sea Serpent Fang",

View File

@ -0,0 +1,91 @@
{
"comment": "Gem socket drawn under left_arm, so it moves with the limb. MIAPI draws a model in the pass its own origin names, and Armory's gemstone names none, so the socket has to be ours for anything here to follow the limb at all.",
"texture_size": [
16,
8
],
"textures": {
"0": "cmmodular:equipment/arm_left_socket",
"particle": "cmmodular:equipment/arm_left_socket"
},
"elements": [
{
"name": "pauldron socket",
"from": [
-2.5,
-0.25,
-1.5
],
"to": [
-2.0,
1.75,
1.5
],
"rotation": {
"angle": -22.5,
"axis": "z",
"origin": [
0,
0,
0
]
},
"faces": {
"up": {
"texture": "#0",
"uv": [
3.0,
0.0,
4.0,
3.0
]
},
"down": {
"texture": "#0",
"uv": [
4.0,
0.0,
5.0,
3.0
]
},
"west": {
"texture": "#0",
"uv": [
0.0,
3.0,
3.0,
5.0
]
},
"north": {
"texture": "#0",
"uv": [
3.0,
3.0,
4.0,
5.0
]
},
"east": {
"texture": "#0",
"uv": [
4.0,
3.0,
7.0,
5.0
]
},
"south": {
"texture": "#0",
"uv": [
7.0,
3.0,
8.0,
5.0
]
}
}
}
]
}

View File

@ -0,0 +1,91 @@
{
"comment": "Gem socket drawn under right_arm, so it moves with the limb. MIAPI draws a model in the pass its own origin names, and Armory's gemstone names none, so the socket has to be ours for anything here to follow the limb at all.",
"texture_size": [
16,
8
],
"textures": {
"0": "cmmodular:equipment/arm_right_socket",
"particle": "cmmodular:equipment/arm_right_socket"
},
"elements": [
{
"name": "pauldron socket",
"from": [
2.0,
-0.25,
-1.5
],
"to": [
2.5,
1.75,
1.5
],
"rotation": {
"angle": 22.5,
"axis": "z",
"origin": [
0,
0,
0
]
},
"faces": {
"up": {
"texture": "#0",
"uv": [
3.0,
0.0,
4.0,
3.0
]
},
"down": {
"texture": "#0",
"uv": [
4.0,
0.0,
5.0,
3.0
]
},
"west": {
"texture": "#0",
"uv": [
0.0,
3.0,
3.0,
5.0
]
},
"north": {
"texture": "#0",
"uv": [
3.0,
3.0,
4.0,
5.0
]
},
"east": {
"texture": "#0",
"uv": [
4.0,
3.0,
7.0,
5.0
]
},
"south": {
"texture": "#0",
"uv": [
7.0,
3.0,
8.0,
5.0
]
}
}
}
]
}

View File

@ -0,0 +1,82 @@
{
"comment": "Gem socket drawn under left_leg, so it moves with the limb. MIAPI draws a model in the pass its own origin names, and Armory's gemstone names none, so the socket has to be ours for anything here to follow the limb at all.",
"texture_size": [
8,
4
],
"textures": {
"0": "cmmodular:equipment/leg_left_socket",
"particle": "cmmodular:equipment/leg_left_socket"
},
"elements": [
{
"name": "knee socket",
"from": [
-1.5,
-2.95,
-2.7
],
"to": [
1.5,
-0.95,
-2.2
],
"faces": {
"up": {
"texture": "#0",
"uv": [
1.0,
0.0,
4.0,
1.0
]
},
"down": {
"texture": "#0",
"uv": [
4.0,
0.0,
7.0,
1.0
]
},
"west": {
"texture": "#0",
"uv": [
0.0,
1.0,
1.0,
3.0
]
},
"north": {
"texture": "#0",
"uv": [
1.0,
1.0,
4.0,
3.0
]
},
"east": {
"texture": "#0",
"uv": [
4.0,
1.0,
5.0,
3.0
]
},
"south": {
"texture": "#0",
"uv": [
5.0,
1.0,
8.0,
3.0
]
}
}
}
]
}

View File

@ -0,0 +1,82 @@
{
"comment": "Gem socket drawn under right_leg, so it moves with the limb. MIAPI draws a model in the pass its own origin names, and Armory's gemstone names none, so the socket has to be ours for anything here to follow the limb at all.",
"texture_size": [
8,
4
],
"textures": {
"0": "cmmodular:equipment/leg_right_socket",
"particle": "cmmodular:equipment/leg_right_socket"
},
"elements": [
{
"name": "knee socket",
"from": [
-1.5,
-2.95,
-2.7
],
"to": [
1.5,
-0.95,
-2.2
],
"faces": {
"up": {
"texture": "#0",
"uv": [
1.0,
0.0,
4.0,
1.0
]
},
"down": {
"texture": "#0",
"uv": [
4.0,
0.0,
7.0,
1.0
]
},
"west": {
"texture": "#0",
"uv": [
0.0,
1.0,
1.0,
3.0
]
},
"north": {
"texture": "#0",
"uv": [
1.0,
1.0,
4.0,
3.0
]
},
"east": {
"texture": "#0",
"uv": [
4.0,
1.0,
5.0,
3.0
]
},
"south": {
"texture": "#0",
"uv": [
5.0,
1.0,
8.0,
3.0
]
}
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

View File

@ -1,11 +1,11 @@
{
"display_name": "miapi.module.cmmodular.gem.apothic_case.name",
"model": {
"path": "cmmodular:models/item/armor/gems/case/[material.texture].json",
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 0.5, "y": 0.5, "z": 0.5}
"scale": {"x": 1, "y": 1, "z": 1}
}
},
"allowed_in_slots": [

View File

@ -0,0 +1,47 @@
{
"display_name": "miapi.module.cmmodular.gem.limb.case_arm_left.name",
"model": [
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "left_arm"
}
},
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 4.5, "y": 3.5, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}
}
],
"allowed_in_slots": [
"gem_armor_medium_arm_left"
],
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"material_property": [
"default"
],
"priority": 0,
"repair_priority": 0,
"rarity": "rare",
"allowed_material": {
"allowedMaterials": [
"metal",
"crystal",
"glass",
"bone",
"stone"
],
"cost": 2
}
}

View File

@ -0,0 +1,47 @@
{
"display_name": "miapi.module.cmmodular.gem.limb.case_arm_right.name",
"model": [
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "right_arm"
}
},
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": -4.5, "y": 3.5, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}
}
],
"allowed_in_slots": [
"gem_armor_medium_arm_right"
],
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"material_property": [
"default"
],
"priority": 0,
"repair_priority": 0,
"rarity": "rare",
"allowed_material": {
"allowedMaterials": [
"metal",
"crystal",
"glass",
"bone",
"stone"
],
"cost": 2
}
}

View File

@ -0,0 +1,47 @@
{
"display_name": "miapi.module.cmmodular.gem.limb.case_leg_left.name",
"model": [
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "left_leg"
}
},
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 2.5, "y": -4.5, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}
}
],
"allowed_in_slots": [
"gem_armor_medium_leg_left"
],
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"material_property": [
"default"
],
"priority": 0,
"repair_priority": 0,
"rarity": "rare",
"allowed_material": {
"allowedMaterials": [
"metal",
"crystal",
"glass",
"bone",
"stone"
],
"cost": 2
}
}

View File

@ -0,0 +1,47 @@
{
"display_name": "miapi.module.cmmodular.gem.limb.case_leg_right.name",
"model": [
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "right_leg"
}
},
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": -2.5, "y": -4.5, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}
}
],
"allowed_in_slots": [
"gem_armor_medium_leg_right"
],
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"material_property": [
"default"
],
"priority": 0,
"repair_priority": 0,
"rarity": "rare",
"allowed_material": {
"allowedMaterials": [
"metal",
"crystal",
"glass",
"bone",
"stone"
],
"cost": 2
}
}

View File

@ -12,6 +12,18 @@
"operation": "*",
"slot": "chest"
}
],
"model": [
{
"path": "cmmodular:models/item/armor/model/arm_left/socket/[material.texture].json",
"trim_mode": "ARMOR_LAYER_ONE",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "left_arm"
}
}
]
},
"replace": {
@ -32,14 +44,14 @@
},
"gem": {
"allowed": [
"gem_armor_medium"
"gem_armor_medium_arm_left"
],
"translationKey": "miapi.slot.gemstone.medium",
"transform": {
"rotation": {"x": 0, "y": 90, "z": 0},
"translation": {"x": 8.6, "y": 1.2, "z": 0},
"scale": {"x": 1.4, "y": 1.4, "z": 1.4},
"origin": "body"
"rotation": {"x": 90.0, "y": 55.7523, "z": -90.0},
"translation": {"x": -1.7667, "y": 1.5983, "z": 0.0},
"scale": {"x": 0.6667, "y": 0.8171, "z": 0.7123},
"origin": "left_arm"
}
}
},

View File

@ -12,6 +12,18 @@
"operation": "*",
"slot": "chest"
}
],
"model": [
{
"path": "cmmodular:models/item/armor/model/arm_right/socket/[material.texture].json",
"trim_mode": "ARMOR_LAYER_ONE",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "right_arm"
}
}
]
},
"replace": {
@ -32,14 +44,14 @@
},
"gem": {
"allowed": [
"gem_armor_medium"
"gem_armor_medium_arm_right"
],
"translationKey": "miapi.slot.gemstone.medium",
"transform": {
"rotation": {"x": 0, "y": -90, "z": 0},
"translation": {"x": -8.6, "y": 1.2, "z": 0},
"scale": {"x": 1.4, "y": 1.4, "z": 1.4},
"origin": "body"
"rotation": {"x": 90.0, "y": -55.7523, "z": 90.0},
"translation": {"x": 1.7667, "y": 1.5983, "z": 0.0},
"scale": {"x": 0.6667, "y": 0.8171, "z": 0.7123},
"origin": "right_arm"
}
}
},

View File

@ -12,20 +12,32 @@
"operation": "*",
"slot": "legs"
}
],
"model": [
{
"path": "cmmodular:models/item/armor/model/leg_left/socket/[material.texture].json",
"trim_mode": "ARMOR_LAYER_ONE",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "left_leg"
}
}
]
},
"replace": {
"slots": {
"gem": {
"allowed": [
"gem_armor_medium"
"gem_armor_medium_leg_left"
],
"translationKey": "miapi.slot.gemstone.medium",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 1.9, "y": 18.5, "z": -2.8},
"scale": {"x": 1.4, "y": 1.4, "z": 1.4},
"origin": "body"
"rotation": {"x": 0.0, "y": 0.0, "z": -180.0},
"translation": {"x": 0.0, "y": -1.95, "z": -2.4917},
"scale": {"x": 0.8333, "y": 1.0, "z": 0.8333},
"origin": "left_leg"
}
}
},

View File

@ -12,20 +12,32 @@
"operation": "*",
"slot": "legs"
}
],
"model": [
{
"path": "cmmodular:models/item/armor/model/leg_right/socket/[material.texture].json",
"trim_mode": "ARMOR_LAYER_ONE",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "right_leg"
}
}
]
},
"replace": {
"slots": {
"gem": {
"allowed": [
"gem_armor_medium"
"gem_armor_medium_leg_right"
],
"translationKey": "miapi.slot.gemstone.medium",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": -1.9, "y": 18.5, "z": -2.8},
"scale": {"x": 1.4, "y": 1.4, "z": 1.4},
"origin": "body"
"rotation": {"x": 0.0, "y": 0.0, "z": -180.0},
"translation": {"x": 0.0, "y": -1.95, "z": -2.4917},
"scale": {"x": 0.8333, "y": 1.0, "z": 0.8333},
"origin": "right_leg"
}
}
},

View File

@ -0,0 +1,48 @@
{
"display_name": "miapi.module.cmmodular.gem.limb.gem_arm_left.name",
"model": [
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "left_arm"
}
},
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 4.5, "y": 3.5, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}
}
],
"allowed_in_slots": [
"gem_armor_medium_arm_left"
],
"material_property": [
"default",
"gem_armor_handheld",
"gem_armor_generic"
],
"module_stats": {
"gem_power": 1
},
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"priority": 0,
"repair_priority": 0,
"rarity": "uncommon",
"allowed_material": {
"allowedMaterials": [
"gem_armor"
],
"cost": 2
}
}

View File

@ -0,0 +1,48 @@
{
"display_name": "miapi.module.cmmodular.gem.limb.gem_arm_right.name",
"model": [
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "right_arm"
}
},
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": -4.5, "y": 3.5, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}
}
],
"allowed_in_slots": [
"gem_armor_medium_arm_right"
],
"material_property": [
"default",
"gem_armor_handheld",
"gem_armor_generic"
],
"module_stats": {
"gem_power": 1
},
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"priority": 0,
"repair_priority": 0,
"rarity": "uncommon",
"allowed_material": {
"allowedMaterials": [
"gem_armor"
],
"cost": 2
}
}

View File

@ -0,0 +1,48 @@
{
"display_name": "miapi.module.cmmodular.gem.limb.gem_leg_left.name",
"model": [
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "left_leg"
}
},
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 2.5, "y": -4.5, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}
}
],
"allowed_in_slots": [
"gem_armor_medium_leg_left"
],
"material_property": [
"default",
"gem_armor_handheld",
"gem_armor_generic"
],
"module_stats": {
"gem_power": 1
},
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"priority": 0,
"repair_priority": 0,
"rarity": "uncommon",
"allowed_material": {
"allowedMaterials": [
"gem_armor"
],
"cost": 2
}
}

View File

@ -0,0 +1,48 @@
{
"display_name": "miapi.module.cmmodular.gem.limb.gem_leg_right.name",
"model": [
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "right_leg"
}
},
{
"path": "miapi:models/item/armor/gems/medium/[material.texture].json",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": -2.5, "y": -4.5, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}
}
],
"allowed_in_slots": [
"gem_armor_medium_leg_right"
],
"material_property": [
"default",
"gem_armor_handheld",
"gem_armor_generic"
],
"module_stats": {
"gem_power": 1
},
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"priority": 0,
"repair_priority": 0,
"rarity": "uncommon",
"allowed_material": {
"allowedMaterials": [
"gem_armor"
],
"cost": 2
}
}