The fang's element, the core's new corners, and gem slots for limbs

Sea serpent fang was a fang anywhere: one of them, socketed, worn or held,
and you breathed underwater. It is an armour material now, and worth
something different in hand.

Worn, it grants half a lung per module in each armour slot and the handler
still only reads 1 as breathing, so it takes two worn fang modules - two
sockets, two pieces built of it, or a socketed piece that is both. That is
the trade the space set already makes, and a fang weapon no longer makes it
at all.

Held, it does what a serpent does in its element. Mining needs nothing of
ours: vanilla models underwater digging as an attribute of its own,
minecraft:player.submerged_mining_speed, base 0.2 for the fifth-speed
penalty, and +0.6 a module puts a single fang module four times past a plain
tool and two of them past dry land. Damage and swing speed have no such
switch, so WaterCombat.java registers cmmodular:water_combat the way
FireImmunity and WaterBreathing register theirs. Damage is answered on the
blow. Attack speed cannot be - the game reads it off the attribute map
whenever it likes - so a transient modifier under one id goes on and comes
off as the bearer enters and leaves the water.

The arcana core's recipe puts a trophy from each corner of the game in the
corners of the grid - nether star, dragon's breath, heart of the sea and
Iron's Spells' ancient knowledge fragment - with two cinder and two arcane
essence on the edges and nothing in the middle.

Armory sockets a helmet, a chestplate, a backplate and a belt and stops
there, so an arm or a leg could never hold a gem. Four pieces here finish it:
the heavy pauldron and the heavy pants, left and right, inheriting Armory's
own whole and adding the same gem_armor_medium slot at the same price, 5% of
the piece's armour. Where the gem sits is the other half - out on the
shoulder in the pauldron, forward on the knee in the pants - and the numbers
come from Armory's own art, which lies a sprite proud of the side of an arm
at 2.15 and of the front of a body at 2.35.

Last, a gem case, for anyone running Apotheosis. Its gems are not modules and
never can be, since what makes one worth having is its own rarity and purity
data, but Apotheosis counts sockets in a plain data component and MIAPI's
component property sets any component a module likes. So the case is one line
of data: put it in an armour piece's gem slot instead of a gemstone and the
piece reports an Apotheosis socket, which the gem goes into at a smithing
table. Modular armour extends vanilla ArmorItem, so Apotheosis' LootCategory
already calls it a helmet or a chestplate, and gem bonuses arrive through
ItemAttributeModifierEvent, where MIAPI is not looking. The pack is gated on
Apotheosis and Armory both, which is why a pack can now name more than one
mod.

None of this is compiled or run - there is no JDK on the machine it was
written on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gem-slots-and-the-fangs-element
Amir Alexander Abdelbaki 2026-08-17 09:45:56 +02:00
parent 3f3b4bdef6
commit 87a78136ec
14 changed files with 532 additions and 33 deletions

106
README.md
View File

@ -9,8 +9,10 @@ aerospace addons - so their metals, gems, scales and essences can all be
used to build modular tools and armour. used to build modular tools and armour.
On top of the materials it adds a few modules of its own: a khopesh blade for On top of the materials it adds a few modules of its own: a khopesh blade for
Arsenal, and a sealed helmet and life support pack for Armory that between them Arsenal, a sealed helmet and life support pack for Armory that between them
let you breathe in the vacuum Create: Cosmonautics puts above the atmosphere. let you breathe in the vacuum Create: Cosmonautics puts above the atmosphere,
socketed arms and legs for the armour pieces Armory leaves without a gem slot,
and a gem case that turns one of Armory's gem slots into an Apotheosis one.
It adds one item as well, the arcana core, which is an endgame gem that ends It adds one item as well, the arcana core, which is an endgame gem that ends
every limit Iron's Spells puts on a caster. every limit Iron's Spells puts on a caster.
@ -60,9 +62,21 @@ watches for it. Neither side knows about the other.
**Breathing in a vacuum.** The same trick, for the space set - see below. **Breathing in a vacuum.** The same trick, for the space set - see below.
**Breathing underwater.** And again, for sea serpent fang: `WaterBreathing.java` **Breathing underwater.** And again, for sea serpent fang armour:
answers the same `LivingBreatheEvent`, but only when the bearer's eyes are in `WaterBreathing.java` answers the same `LivingBreatheEvent`, but only when the
water, so a fang is a fang rather than half a spacesuit. wearer's eyes are in water, so a fang is a fang rather than half a spacesuit.
Half a lung per module, the same trade the space set makes, so it takes two
worn fang modules and a fang weapon does nothing at all - see below.
**Fighting underwater.** The other half of what a fang is for.
`WaterCombat.java` registers a `cmmodular:water_combat` attribute that a fang
grants in the main hand, and while the bearer is in water it adds that fraction
to the damage they deal and to how fast they swing. Damage is answered on the
blow. Attack speed cannot be - the game reads it off the attribute map whenever
it wants to know - so a transient modifier under one id goes on and comes off as
the bearer enters and leaves the water. Mining needs none of this: vanilla
already models underwater digging as an attribute,
`minecraft:player.submerged_mining_speed`, and the material simply adds to it.
**Moving through sand.** Deathworm chitin does what the worm does, and this is **Moving through sand.** Deathworm chitin does what the worm does, and this is
the one thing an attribute and an event cannot carry on their own: whether a the one thing an attribute and an event cannot carry on their own: whether a
@ -97,8 +111,9 @@ material or a module - see below.
## Modules ## Modules
Three of the packs hold modules rather than materials. They are hand-written Four of the packs hold modules rather than materials. They are hand-written
rather than generated, and gated on the Truly Modular addon whose slots they use. rather than generated, and gated on the Truly Modular addon whose slots they
use - and, for the gem case, on Apotheosis as well.
**Khopesh blade** (`packs/arsenal`) fits any `sword_blade` slot, so it is an **Khopesh blade** (`packs/arsenal`) fits any `sword_blade` slot, so it is an
option wherever the plain sword blade is. It is the chopping sword it looks option wherever the plain sword blade is. It is the chopping sword it looks
@ -119,6 +134,59 @@ Arsenal, Armory, Archery and MIAPI turns up no module anywhere that offers a
go. So there are two here: a medium socket, and a great socket for the large go. So there are two here: a medium socket, and a great socket for the large
gem. Each is a heavier counterweight than the last, paid for in swing speed. gem. Each is a heavier counterweight than the last, paid for in swing speed.
**Socketed arms and legs** (`packs/armory`) finish a set Armory started.
Armory sockets four pieces - helmet, chestplate, backplate and belt - and stops
there, so an arm or a leg is a piece of armour that can never hold a gem. These
four are the missing ones: the heavy pauldron and the heavy pants, left and
right, each with the same `gem_armor_medium` slot the rest of the set uses and
the same price, 5% of the piece's armour.
They inherit Armory's heavy pieces whole and change two things: the slot, and
where the gem sits in it. Armory's own gems are placed on the body - the chest's
sits on the sternum, from `origin: body` - so these do the same with the limb as
the origin. On an arm the gem is turned to face outwards and set at the top of
the shoulder, in the pauldron; on a leg it faces forward, halfway down, in the
knee. The numbers come from Armory's own: a sprite lies proud of the side of an
arm at 2.15 and of the front of a body at 2.35, which is what its banner patches
use, so the gems use it too. They are eyeball values on someone else's model
and the four transforms are the only thing to touch if a gem sits proud or
sinks in.
The pieces take any medium armour gemstone, which includes the Apothic gem case
below - so a pauldron can hold an Apotheosis gem as readily as a Truly Modular
one.
**The Apothic gem case** (`packs/apotheosis`) trades a Truly Modular gem slot
for an Apotheosis one. Apotheosis gems are not modules and never can be - what
makes one worth having is its own rarity and purity data, which a MIAPI
material cannot carry - so the two systems cannot share a slot. What they can
share is the item. Apotheosis stores how many gems a thing holds in a plain
data component, `apotheosis:sockets`, and MIAPI's `component` property sets any
component a module likes, so the case is one line of data: put it in an armour
piece's `gem_armor_medium` slot instead of a gemstone, and the piece reports one
Apotheosis socket. The gem itself goes in at a smithing table, through
Apotheosis' own socketing recipe, not at the workbench.
That leaves the two systems where they each work best. Apotheosis reads the
socket count through `SocketHelper`, which classifies the item with
`LootCategory` - and modular armour extends vanilla `ArmorItem`, so it is a
helmet or a chestplate to Apotheosis rather than nothing at all. Gem bonuses
arrive as attribute modifiers on the stack, through `ItemAttributeModifierEvent`,
which is where MIAPI is not looking; MIAPI writes its own numbers into the
`ATTRIBUTE_MODIFIERS` component, and the two add up rather than overwrite.
It wears Armory's own medium gemstone model rather than art of its own, which
is both the least work and the right picture: a case is a setting cut for a
gem, in whatever material it was built from, and it sits exactly where a
gemstone would. Nothing is copied to do it - the module points at Armory's
model path, and Armory is required for the pack to load at all.
One socket a case, which is the one-for-one trade the module is. Two things
worth knowing: the component is the module's while the module is in, so sockets
added with a Sigil of Socketing are overwritten by it and come back when the
case comes out, and a gem sitting in a case stops counting if the case is
removed - it is not destroyed, it is just in a socket that no longer exists.
**The space set** (`packs/armory`) is Create: Cosmonautics' answer to vacuum, **The space set** (`packs/armory`) is Create: Cosmonautics' answer to vacuum,
rebuilt as modules. Cosmonautics suffocates you above the atmosphere by rebuilt as modules. Cosmonautics suffocates you above the atmosphere by
answering NeoForge's `LivingBreatheEvent` with "no", and exempts its own space answering NeoForge's `LivingBreatheEvent` with "no", and exempts its own space
@ -141,8 +209,10 @@ Textures below.
## The arcana core ## The arcana core
The one item this mod adds, and the only late-game thing in it that is not The one item this mod adds, and the only late-game thing in it that is not
simply a good material. Four arcane essence at the corners, four cinder essence simply a good material. One trophy from each corner of the game in the corners
on the edges, a nether star in the middle - all of which are already endgame - of the grid - a nether star, a dragon's breath, a heart of the sea and Iron's
Spells' ancient knowledge fragment - with two cinder essence and two arcane
essence on the edges and nothing in the middle. All of it is already endgame,
and what comes out is a gem that fits any socket and ends Iron's Spells' and what comes out is a gem that fits any socket and ends Iron's Spells'
limits: a mana pool of a million refilled a hundred times over, cooldowns and limits: a mana pool of a million refilled a hundred times over, cooldowns and
cast times at zero, and ten times the spell power. It is deliberately absurd, cast times at zero, and ten times the spell power. It is deliberately absurd,
@ -317,9 +387,21 @@ folders, so the hand-written packs survive it.
`gem_armor` as a hidden group as well, so a piece can be set into an armour `gem_armor` as a hidden group as well, so a piece can be set into an armour
socket instead of only shaped into the armour. Sea serpent fang is new and socket instead of only shaped into the armour. Sea serpent fang is new and
does both: Ice and Fire has no sea serpent bone, and the fang is what a does both: Ice and Fire has no sea serpent bone, and the fang is what a
serpent leaves behind besides its scales. It grants water breathing, which is serpent leaves behind besides its scales.
what Ice and Fire's own Tide Guardian armour does, from slot `any` - so one - What a fang is worth depends on where it sits, and all of it is filed under
fang socketed, worn or held is enough, and a second neither adds nor dilutes. `default` so a fang set into an armour socket counts as much as one shaped
into the piece. Worn, it grants 0.5 water breathing in each armour slot -
which is what Ice and Fire's own Tide Guardian armour does - and the handler
only reads 1 as breathing, so it takes two worn fang modules: two sockets,
two pieces built of it, or a socketed piece that is both. Held, it breathes
not at all and instead does what a serpent does in its element: `+0.6`
submerged mining speed on vanilla's base of `0.2`, so a single fang module
digs four times faster underwater than a plain tool and two of them beat
dry-land speed, and `+0.10` `cmmodular:water_combat`, so a weapon built of it
hits that much harder and swings that much faster while its bearer is in
water. Both stack per module; the combat half caps at `+0.5`. That half is
the bearer's rather than the weapon's, so a fang bow's arrows count as well,
as long as the fang is in hand and its bearer in water when they land.
- Scales are in the `scute` group, and no module in Truly Modular accepts that - Scales are in the `scute` group, and no module in Truly Modular accepts that
group - not Armory's armour, not Arsenal's or Archery's parts, and MIAPI's own group - not Armory's armour, not Arsenal's or Archery's parts, and MIAPI's own
turtle and armadillo scutes are stuck the same way. So each scale material turtle and armadillo scutes are stuck the same way. So each scale material

View File

@ -33,10 +33,15 @@ import net.neoforged.neoforge.event.AddPackFindersEvent;
public class CMModular { public class CMModular {
public static final String MOD_ID = "cmmodular"; public static final String MOD_ID = "cmmodular";
/** A built-in datapack - materials or modules - and the mod it needs. */ /** A built-in datapack - materials or modules - and the mods it needs. */
private record MaterialPack(String id, String name, String requires) { private record MaterialPack(String id, String name, String... requires) {
boolean present() { boolean present() {
return ModList.get().isLoaded(requires); for (String mod : requires) {
if (!ModList.get().isLoaded(mod)) {
return false;
}
}
return true;
} }
} }
@ -68,12 +73,17 @@ public class CMModular {
// Modules, not materials, and gated for the same reason: they name // Modules, not materials, and gated for the same reason: they name
// slots - and inherit modules - that only exist with these installed. // slots - and inherit modules - that only exist with these installed.
new MaterialPack("arsenal", "Arsenal Modules", "tm_arsenal"), new MaterialPack("arsenal", "Arsenal Modules", "tm_arsenal"),
new MaterialPack("armory", "Armory Modules", "tm_armory")); new MaterialPack("armory", "Armory Modules", "tm_armory"),
// Two mods, because the case sits in one of Armory's gem slots and
// sets a component only Apotheosis knows what to do with. Without
// either half it is a module that fits nowhere and does nothing.
new MaterialPack("apotheosis", "Apothic Gem Cases", "apotheosis", "tm_armory"));
public CMModular(IEventBus modBus) { public CMModular(IEventBus modBus) {
FireImmunity.register(modBus); FireImmunity.register(modBus);
VacuumSeal.register(modBus); VacuumSeal.register(modBus);
WaterBreathing.register(modBus); WaterBreathing.register(modBus);
WaterCombat.register(modBus);
SandPhasing.register(modBus); SandPhasing.register(modBus);
EquipmentAttributes.register(modBus); EquipmentAttributes.register(modBus);
ArcanaCore.register(modBus); ArcanaCore.register(modBus);

View File

@ -13,8 +13,9 @@ import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister; import net.neoforged.neoforge.registries.DeferredRegister;
/** /**
* Sea serpent fang lets its bearer breathe underwater, which is what Ice and * Sea serpent fang armour lets its wearer breathe underwater, which is what Ice
* Fire's own Tide Guardian armour does and the only reason to hunt the thing. * and Fire's own Tide Guardian armour does and the only reason to hunt the
* thing.
* *
* <p>There is no attribute for it and MIAPI has no property for it either - the * <p>There is no attribute for it and MIAPI has no property for it either - the
* closest data can get is a fake Respiration enchantment, which only postpones * closest data can get is a fake Respiration enchantment, which only postpones
@ -22,6 +23,12 @@ import net.neoforged.neoforge.registries.DeferredRegister;
* {@link VacuumSeal} already set: an attribute of this mod's own that the * {@link VacuumSeal} already set: an attribute of this mod's own that the
* material grants like any other, and a listener here that watches for it. * material grants like any other, and a listener here that watches for it.
* *
* <p>Half a lung per module, as with {@link VacuumSeal}: the fang grants 0.5 in
* each armour slot and nothing in hand, so it takes two worn pieces of it -
* two sockets, two pieces built of it, or a socketed piece that is both - to
* breathe, and a fang weapon breathes not at all. What a fang is worth in hand
* is {@link WaterCombat}'s.
*
* <p>It answers the same event {@link VacuumSeal} does but only underwater, so * <p>It answers the same event {@link VacuumSeal} does but only underwater, so
* a fang is a fang and not half a spacesuit. * a fang is a fang and not half a spacesuit.
*/ */
@ -29,7 +36,7 @@ public final class WaterBreathing {
private static final DeferredRegister<Attribute> ATTRIBUTES = private static final DeferredRegister<Attribute> ATTRIBUTES =
DeferredRegister.create(Registries.ATTRIBUTE, CMModular.MOD_ID); DeferredRegister.create(Registries.ATTRIBUTE, CMModular.MOD_ID);
/** At or above 1 the bearer never drowns; the fang grants a whole one. */ /** At or above 1 the bearer never drowns; each worn fang module is 0.5. */
public static final DeferredHolder<Attribute, Attribute> WATER_BREATHING = public static final DeferredHolder<Attribute, Attribute> WATER_BREATHING =
ATTRIBUTES.register("water_breathing", id -> new RangedAttribute( ATTRIBUTES.register("water_breathing", id -> new RangedAttribute(
"attribute.cmmodular.water_breathing", 0.0D, 0.0D, 1.0D).setSyncable(true)); "attribute.cmmodular.water_breathing", 0.0D, 0.0D, 1.0D).setSyncable(true));

View File

@ -0,0 +1,116 @@
package eu.abdelbaki.cmmodular;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
import net.minecraft.world.entity.player.Player;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.EntityAttributeModificationEvent;
import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent;
import net.neoforged.neoforge.event.tick.PlayerTickEvent;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
/**
* A sea serpent fang in hand is worth more in water than out of it: harder
* hits and a faster swing while the bearer is in it.
*
* <p>The mining half of the same idea needs nothing here - vanilla models
* underwater digging as an attribute of its own,
* {@code minecraft:player.submerged_mining_speed}, and the material simply
* adds to it. Damage and attack speed have no such switch, so this follows the
* pattern {@link FireImmunity} and {@link WaterBreathing} already set: an
* attribute of this mod's own that the material grants like any other, and a
* listener here that watches for it.
*
* <p>Damage is answered on the blow, which needs no state. Attack speed cannot
* be - it is read off the attribute map whenever the game wants to know how
* fast the bearer swings - so a modifier goes on and comes off as the bearer
* enters and leaves the water. It is transient and keyed by one id, so a
* player who never touches a fang never has one and a player who does never
* collects two.
*/
public final class WaterCombat {
private static final DeferredRegister<Attribute> ATTRIBUTES =
DeferredRegister.create(Registries.ATTRIBUTE, CMModular.MOD_ID);
/** No more than half again, however much fang the weapon is built from. */
private static final double MAX = 0.5D;
/** The fraction added to damage dealt and to swing speed, in water only. */
public static final DeferredHolder<Attribute, Attribute> WATER_COMBAT =
ATTRIBUTES.register("water_combat", id -> new RangedAttribute(
"attribute.cmmodular.water_combat", 0.0D, 0.0D, MAX).setSyncable(true));
/** The one attack speed modifier this puts on, so it can be taken off. */
private static final ResourceLocation SWING_ID =
ResourceLocation.fromNamespaceAndPath(CMModular.MOD_ID, "water_combat");
private WaterCombat() {
}
static void register(IEventBus modBus) {
ATTRIBUTES.register(modBus);
modBus.addListener(WaterCombat::attachToEntities);
NeoForge.EVENT_BUS.addListener(WaterCombat::onIncomingDamage);
NeoForge.EVENT_BUS.addListener(WaterCombat::onPlayerTick);
}
/** Attributes only exist on an entity if they were added to its type. */
private static void attachToEntities(EntityAttributeModificationEvent event) {
event.getTypes().forEach(type -> event.add(type, WATER_COMBAT));
}
private static void onIncomingDamage(LivingIncomingDamageEvent event) {
if (!(event.getSource().getEntity() instanceof LivingEntity attacker)
|| !attacker.isInWater()) {
return;
}
double bonus = boost(attacker);
if (bonus > 0.0D) {
event.setAmount((float) (event.getAmount() * (1.0D + bonus)));
}
}
/**
* Keeps the swing speed modifier in step with the water and the weapon.
*
* <p>Only players, because attack speed is only ever read for a player.
* The equipment is only consulted while the bearer is in water, so this
* costs nothing on dry land.
*/
private static void onPlayerTick(PlayerTickEvent.Post event) {
Player player = event.getEntity();
AttributeInstance speed = player.getAttribute(Attributes.ATTACK_SPEED);
if (speed == null) {
return;
}
double bonus = player.isInWater() ? boost(player) : 0.0D;
AttributeModifier applied = speed.getModifier(SWING_ID);
if (bonus <= 0.0D) {
if (applied != null) {
speed.removeModifier(SWING_ID);
}
return;
}
if (applied != null && applied.amount() == bonus) {
return;
}
// Weapon swapped for one with more fang in it, or the map was rebuilt
// out from under the modifier - either way, put the right one back.
speed.removeModifier(SWING_ID);
speed.addTransientModifier(new AttributeModifier(SWING_ID, bonus,
AttributeModifier.Operation.ADD_MULTIPLIED_BASE));
}
/** What the held modules are worth, clamped to what the attribute allows. */
private static double boost(LivingEntity entity) {
return Math.min(EquipmentAttributes.value(entity, WATER_COMBAT), MAX);
}
}

View File

@ -2,6 +2,7 @@
"attribute.cmmodular.fire_immunity": "Fire Immunity", "attribute.cmmodular.fire_immunity": "Fire Immunity",
"attribute.cmmodular.vacuum_seal": "Vacuum Seal", "attribute.cmmodular.vacuum_seal": "Vacuum Seal",
"attribute.cmmodular.water_breathing": "Water Breathing", "attribute.cmmodular.water_breathing": "Water Breathing",
"attribute.cmmodular.water_combat": "Underwater Fighting",
"attribute.cmmodular.sand_phasing": "Sand Phasing", "attribute.cmmodular.sand_phasing": "Sand Phasing",
"item.cmmodular.arcana_core": "Arcana Core", "item.cmmodular.arcana_core": "Arcana Core",
"miapi.module.cmmodular.blade.khopesh.name": "Khopesh Blade", "miapi.module.cmmodular.blade.khopesh.name": "Khopesh Blade",
@ -11,6 +12,19 @@
"miapi.module.cmmodular.armor.space.helmet_socket.name": "Sealed Helmet with Socket", "miapi.module.cmmodular.armor.space.helmet_socket.name": "Sealed Helmet with Socket",
"miapi.module.cmmodular.armor.space.life_support.name": "Life Support Pack", "miapi.module.cmmodular.armor.space.life_support.name": "Life Support Pack",
"miapi.module.cmmodular.armor.space.life_support_socket.name": "Life Support Pack with Socket", "miapi.module.cmmodular.armor.space.life_support_socket.name": "Life Support Pack with Socket",
"miapi.module.cmmodular.armor.socket.arm_left": "Left Heavy Socket Pauldron",
"miapi.module.cmmodular.armor.socket.arm_left.name": "%s Left Heavy Socket Pauldron",
"miapi.module.cmmodular.armor.socket.arm_left.description": "A Heavy Pauldron with a Gemstone set into it",
"miapi.module.cmmodular.armor.socket.arm_right": "Right Heavy Socket Pauldron",
"miapi.module.cmmodular.armor.socket.arm_right.name": "%s Right Heavy Socket Pauldron",
"miapi.module.cmmodular.armor.socket.arm_right.description": "A Heavy Pauldron with a Gemstone set into it",
"miapi.module.cmmodular.armor.socket.leg_left": "Heavy Socket Left Pants",
"miapi.module.cmmodular.armor.socket.leg_left.name": "%s Heavy Socket Left Pants",
"miapi.module.cmmodular.armor.socket.leg_left.description": "Heavy Pants with a Gemstone set into the Knee",
"miapi.module.cmmodular.armor.socket.leg_right": "Heavy Socket Right Pants",
"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.material.cmmodular.bone.dragonbone": "Dragonbone", "miapi.material.cmmodular.bone.dragonbone": "Dragonbone",
"miapi.material.cmmodular.bone.hippogryph_talon": "Talon", "miapi.material.cmmodular.bone.hippogryph_talon": "Talon",
"miapi.material.cmmodular.bone.sea_serpent_fang": "Sea Serpent Fang", "miapi.material.cmmodular.bone.sea_serpent_fang": "Sea Serpent Fang",

View File

@ -0,0 +1,39 @@
{
"display_name": "miapi.module.cmmodular.gem.apothic_case.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}
}
},
"allowed_in_slots": [
"gem_armor_medium"
],
"tag": [
"gem_armor",
"gem_armor_medium",
"gem_armor_generic",
"gem_armor_medium_generic"
],
"component": {
"apotheosis:sockets": 1
},
"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,6 @@
{
"pack": {
"description": "Apothic Gem Cases for Truly Modular",
"pack_format": 48
}
}

View File

@ -0,0 +1,49 @@
{
"parent": "tm_armory:armor/heavy/arm_left",
"data": {
"merge": {
"module_tag": [
"socket_armor"
],
"attributes": [
{
"attribute": "minecraft:generic.armor",
"value": "-0.05",
"operation": "*",
"slot": "chest"
}
]
},
"replace": {
"slots": {
"banner-arm-left": {
"allowed": [
"cosmetic_2x4",
"banner_2x4"
],
"slotType": "cosmetic",
"translationKey": "miapi.slot.arm_patch.name",
"transform": {
"rotation": {"x": 0, "y": 90, "z": 0},
"translation": {"x": -2.15, "y": -1, "z": 0},
"scale": {"x": 1.1, "y": 1, "z": 1},
"origin": "left_arm"
}
},
"gem": {
"allowed": [
"gem_armor_medium"
],
"translationKey": "miapi.slot.gemstone.medium",
"transform": {
"rotation": {"x": 0, "y": 90, "z": 0},
"translation": {"x": -2.15, "y": -2, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "left_arm"
}
}
},
"display_name": "miapi.module.cmmodular.armor.socket.arm_left.name"
}
}
}

View File

@ -0,0 +1,49 @@
{
"parent": "tm_armory:armor/heavy/arm_right",
"data": {
"merge": {
"module_tag": [
"socket_armor"
],
"attributes": [
{
"attribute": "minecraft:generic.armor",
"value": "-0.05",
"operation": "*",
"slot": "chest"
}
]
},
"replace": {
"slots": {
"banner-arm-right": {
"allowed": [
"cosmetic_2x4",
"banner_2x4"
],
"slotType": "cosmetic",
"translationKey": "miapi.slot.arm_patch.name",
"transform": {
"rotation": {"x": 0, "y": -90, "z": 0},
"translation": {"x": 2.15, "y": -1, "z": 0},
"scale": {"x": 1.1, "y": 1, "z": 1},
"origin": "right_arm"
}
},
"gem": {
"allowed": [
"gem_armor_medium"
],
"translationKey": "miapi.slot.gemstone.medium",
"transform": {
"rotation": {"x": 0, "y": -90, "z": 0},
"translation": {"x": 2.15, "y": -2, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "right_arm"
}
}
},
"display_name": "miapi.module.cmmodular.armor.socket.arm_right.name"
}
}
}

View File

@ -0,0 +1,35 @@
{
"parent": "tm_armory:armor/heavy/leg_left",
"data": {
"merge": {
"module_tag": [
"socket_armor"
],
"attributes": [
{
"attribute": "minecraft:generic.armor",
"value": "-0.05",
"operation": "*",
"slot": "legs"
}
]
},
"replace": {
"slots": {
"gem": {
"allowed": [
"gem_armor_medium"
],
"translationKey": "miapi.slot.gemstone.medium",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 5, "z": -2.35},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "left_leg"
}
}
},
"display_name": "miapi.module.cmmodular.armor.socket.leg_left.name"
}
}
}

View File

@ -0,0 +1,35 @@
{
"parent": "tm_armory:armor/heavy/leg_right",
"data": {
"merge": {
"module_tag": [
"socket_armor"
],
"attributes": [
{
"attribute": "minecraft:generic.armor",
"value": "-0.05",
"operation": "*",
"slot": "legs"
}
]
},
"replace": {
"slots": {
"gem": {
"allowed": [
"gem_armor_medium"
],
"translationKey": "miapi.slot.gemstone.medium",
"transform": {
"rotation": {"x": 0, "y": 0, "z": 0},
"translation": {"x": 0, "y": 5, "z": -2.35},
"scale": {"x": 1, "y": 1, "z": 1},
"origin": "right_leg"
}
}
},
"display_name": "miapi.module.cmmodular.armor.socket.leg_right.name"
}
}
}

View File

@ -42,9 +42,39 @@
"attributes": [ "attributes": [
{ {
"attribute": "cmmodular:water_breathing", "attribute": "cmmodular:water_breathing",
"value": "1", "value": "0.5",
"operation": "+", "operation": "+",
"slot": "any" "slot": "head"
},
{
"attribute": "cmmodular:water_breathing",
"value": "0.5",
"operation": "+",
"slot": "chest"
},
{
"attribute": "cmmodular:water_breathing",
"value": "0.5",
"operation": "+",
"slot": "legs"
},
{
"attribute": "cmmodular:water_breathing",
"value": "0.5",
"operation": "+",
"slot": "feet"
},
{
"attribute": "minecraft:player.submerged_mining_speed",
"value": "0.6",
"operation": "+",
"slot": "mainhand"
},
{
"attribute": "cmmodular:water_combat",
"value": "0.1",
"operation": "+",
"slot": "mainhand"
} }
] ]
}, },

View File

@ -2,14 +2,17 @@
"type": "minecraft:crafting_shaped", "type": "minecraft:crafting_shaped",
"category": "misc", "category": "misc",
"pattern": [ "pattern": [
"ACA", "NAB",
"CNC", "C C",
"ACA" "HAK"
], ],
"key": { "key": {
"N": "minecraft:nether_star",
"B": "minecraft:dragon_breath",
"H": "minecraft:heart_of_the_sea",
"K": "irons_spellbooks:ancient_knowledge_fragment",
"A": "irons_spellbooks:arcane_essence", "A": "irons_spellbooks:arcane_essence",
"C": "irons_spellbooks:cinder_essence", "C": "irons_spellbooks:cinder_essence"
"N": "minecraft:nether_star"
}, },
"result": { "result": {
"id": "cmmodular:arcana_core", "id": "cmmodular:arcana_core",

View File

@ -78,13 +78,23 @@ PACKS = {
# packs/armory, and are listed here so their pack.mcmeta is written. # packs/armory, and are listed here so their pack.mcmeta is written.
"arsenal": {"mod": "tm_arsenal", "name": "Arsenal Modules"}, "arsenal": {"mod": "tm_arsenal", "name": "Arsenal Modules"},
"armory": {"mod": "tm_armory", "name": "Armory Modules"}, "armory": {"mod": "tm_armory", "name": "Armory Modules"},
# Same again, for the one module that needs a mod outside Truly Modular:
# the gem case wants Armory's slot and Apotheosis' component both, so
# CMModular.java gates it on the pair.
"apotheosis": {"mod": "apotheosis", "name": "Apothic Gem Cases"},
} }
# Attributes this mod registers itself, so a material can grant something the # Attributes this mod registers itself, so a material can grant something the
# game has no attribute for. See FireImmunity.java and WaterBreathing.java. # game has no attribute for. See FireImmunity.java, WaterBreathing.java and
# WaterCombat.java.
FIRE_IMMUNITY = "cmmodular:fire_immunity" FIRE_IMMUNITY = "cmmodular:fire_immunity"
WATER_BREATHING = "cmmodular:water_breathing" WATER_BREATHING = "cmmodular:water_breathing"
SAND_PHASING = "cmmodular:sand_phasing" SAND_PHASING = "cmmodular:sand_phasing"
WATER_COMBAT = "cmmodular:water_combat"
# Vanilla's own underwater mining multiplier, base 0.2 - the 5x penalty for
# swinging a pick with your eyes under water, and what Aqua Affinity undoes.
SUBMERGED_MINING_SPEED = "minecraft:player.submerged_mining_speed"
def attribute(attr, value, slot, operation="+"): def attribute(attr, value, slot, operation="+"):
@ -444,16 +454,30 @@ MATERIALS = [
tier=4, hardness=6.0, density=3.0, flexibility=4, durability=760, tier=4, hardness=6.0, density=3.0, flexibility=4, durability=760,
enchantability=16, mining_speed=7, toughness=2, armor_durability=26, enchantability=16, mining_speed=7, toughness=2, armor_durability=26,
groups=("bone",), hidden_groups=("gem_armor",), textures=("rough", "shiny"), groups=("bone",), hidden_groups=("gem_armor",), textures=("rough", "shiny"),
# All of it under `default`, so a fang set into an armour socket counts
# as much as one shaped into the piece - a gem module is not tagged
# `armor` or `handheld` and would never see anything filed there. What
# the fang is worth is decided by the slot instead: worn, it breathes;
# held, it fights and mines.
properties={ properties={
"default": { "default": {
"attributes": [ "attributes": [
# Ice and Fire's own sea serpent armour grants water # Ice and Fire's own sea serpent armour grants water
# breathing, and that is the whole reason to hunt one. Slot # breathing, and that is the whole reason to hunt one. Half a
# `any`, capped at 1 and read as "at or above 1" by the # lung per module and capped at 1, the trade the space set
# handler, so one fang anywhere - socketed, worn or held - is # makes: two fangs worn - two sockets, two pieces, or a
# enough and a second neither adds nor dilutes, exactly as # socketed piece built of it - and not one held.
# cinder essence does for fire. *(attribute(WATER_BREATHING, 0.5, slot)
attribute(WATER_BREATHING, 1, "any"), for slot in ("head", "chest", "legs", "feet")),
# Held rather than worn, and only under water. Vanilla mines
# at a fifth of the usual speed with your eyes submerged;
# 0.6 on a base of 0.2 is four times better than that off a
# single fang module and past dry-land speed off two.
attribute(SUBMERGED_MINING_SPEED, 0.6, "mainhand"),
# And the same water makes a serpent's tooth bite: a tenth
# more damage and a tenth faster a module, capped at half
# again - see WaterCombat.java.
attribute(WATER_COMBAT, 0.10, "mainhand"),
], ],
}, },
"boot": {"fake_enchants": {"minecraft:depth_strider": 1}}, "boot": {"fake_enchants": {"minecraft:depth_strider": 1}},