diff --git a/README.md b/README.md index 8b05762..2121826 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,10 @@ aerospace addons - so their metals, gems, scales and essences can all be used to build modular tools and armour. 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 -let you breathe in the vacuum Create: Cosmonautics puts above the atmosphere. +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, +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 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 underwater.** And again, for sea serpent fang: `WaterBreathing.java` -answers the same `LivingBreatheEvent`, but only when the bearer's eyes are in -water, so a fang is a fang rather than half a spacesuit. +**Breathing underwater.** And again, for sea serpent fang armour: +`WaterBreathing.java` answers the same `LivingBreatheEvent`, but only when the +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 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 -Three 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. +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 - and, for the gem case, on Apotheosis as well. **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 @@ -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 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, rebuilt as modules. Cosmonautics suffocates you above the atmosphere by answering NeoForge's `LivingBreatheEvent` with "no", and exempts its own space @@ -141,8 +209,10 @@ Textures below. ## The arcana core 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 -on the edges, a nether star in the middle - all of which are already endgame - +simply a good material. One trophy from each corner of the game in the corners +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' 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, @@ -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 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 - serpent leaves behind besides its scales. It grants water breathing, which is - what Ice and Fire's own Tide Guardian armour does, from slot `any` - so one - fang socketed, worn or held is enough, and a second neither adds nor dilutes. + serpent leaves behind besides its scales. +- What a fang is worth depends on where it sits, and all of it is filed under + `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 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 diff --git a/src/main/java/eu/abdelbaki/cmmodular/CMModular.java b/src/main/java/eu/abdelbaki/cmmodular/CMModular.java index b6b0bd2..34be827 100644 --- a/src/main/java/eu/abdelbaki/cmmodular/CMModular.java +++ b/src/main/java/eu/abdelbaki/cmmodular/CMModular.java @@ -33,10 +33,15 @@ import net.neoforged.neoforge.event.AddPackFindersEvent; public class CMModular { public static final String MOD_ID = "cmmodular"; - /** A built-in datapack - materials or modules - and the mod it needs. */ - private record MaterialPack(String id, String name, String requires) { + /** A built-in datapack - materials or modules - and the mods it needs. */ + private record MaterialPack(String id, String name, String... requires) { 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 // slots - and inherit modules - that only exist with these installed. 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) { FireImmunity.register(modBus); VacuumSeal.register(modBus); WaterBreathing.register(modBus); + WaterCombat.register(modBus); SandPhasing.register(modBus); EquipmentAttributes.register(modBus); ArcanaCore.register(modBus); diff --git a/src/main/java/eu/abdelbaki/cmmodular/WaterBreathing.java b/src/main/java/eu/abdelbaki/cmmodular/WaterBreathing.java index 3d850f5..79ccdcf 100644 --- a/src/main/java/eu/abdelbaki/cmmodular/WaterBreathing.java +++ b/src/main/java/eu/abdelbaki/cmmodular/WaterBreathing.java @@ -13,8 +13,9 @@ import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; /** - * Sea serpent fang lets its bearer breathe underwater, which is what Ice and - * Fire's own Tide Guardian armour does and the only reason to hunt the thing. + * Sea serpent fang armour lets its wearer breathe underwater, which is what Ice + * and Fire's own Tide Guardian armour does and the only reason to hunt the + * thing. * *
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 @@ -22,6 +23,12 @@ import net.neoforged.neoforge.registries.DeferredRegister; * {@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. * + *
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. + * *
It answers the same event {@link VacuumSeal} does but only underwater, so
* a fang is a fang and not half a spacesuit.
*/
@@ -29,7 +36,7 @@ public final class WaterBreathing {
private static final DeferredRegister 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.
+ *
+ * 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 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);
+ }
+}
diff --git a/src/main/resources/assets/cmmodular/lang/en_us.json b/src/main/resources/assets/cmmodular/lang/en_us.json
index 3f4479b..d020a47 100644
--- a/src/main/resources/assets/cmmodular/lang/en_us.json
+++ b/src/main/resources/assets/cmmodular/lang/en_us.json
@@ -2,6 +2,7 @@
"attribute.cmmodular.fire_immunity": "Fire Immunity",
"attribute.cmmodular.vacuum_seal": "Vacuum Seal",
"attribute.cmmodular.water_breathing": "Water Breathing",
+ "attribute.cmmodular.water_combat": "Underwater Fighting",
"attribute.cmmodular.sand_phasing": "Sand Phasing",
"item.cmmodular.arcana_core": "Arcana Core",
"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.life_support.name": "Life Support Pack",
"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.hippogryph_talon": "Talon",
"miapi.material.cmmodular.bone.sea_serpent_fang": "Sea Serpent Fang",
diff --git a/src/main/resources/packs/apotheosis/data/cmmodular/miapi/modules/gem/apothic_case.json b/src/main/resources/packs/apotheosis/data/cmmodular/miapi/modules/gem/apothic_case.json
new file mode 100644
index 0000000..7a6c8be
--- /dev/null
+++ b/src/main/resources/packs/apotheosis/data/cmmodular/miapi/modules/gem/apothic_case.json
@@ -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
+ }
+}
diff --git a/src/main/resources/packs/apotheosis/pack.mcmeta b/src/main/resources/packs/apotheosis/pack.mcmeta
new file mode 100644
index 0000000..5e9936a
--- /dev/null
+++ b/src/main/resources/packs/apotheosis/pack.mcmeta
@@ -0,0 +1,6 @@
+{
+ "pack": {
+ "description": "Apothic Gem Cases for Truly Modular",
+ "pack_format": 48
+ }
+}
diff --git a/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/arm_left.json b/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/arm_left.json
new file mode 100644
index 0000000..3e3f717
--- /dev/null
+++ b/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/arm_left.json
@@ -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"
+ }
+ }
+}
diff --git a/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/arm_right.json b/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/arm_right.json
new file mode 100644
index 0000000..fcd4262
--- /dev/null
+++ b/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/arm_right.json
@@ -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"
+ }
+ }
+}
diff --git a/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/leg_left.json b/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/leg_left.json
new file mode 100644
index 0000000..1c80668
--- /dev/null
+++ b/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/leg_left.json
@@ -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"
+ }
+ }
+}
diff --git a/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/leg_right.json b/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/leg_right.json
new file mode 100644
index 0000000..e23270c
--- /dev/null
+++ b/src/main/resources/packs/armory/data/cmmodular/miapi/modules/armor/socket/leg_right.json
@@ -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"
+ }
+ }
+}
diff --git a/src/main/resources/packs/iceandfire/data/cmmodular/miapi/materials/bone/sea_serpent_fang.json b/src/main/resources/packs/iceandfire/data/cmmodular/miapi/materials/bone/sea_serpent_fang.json
index bad4d60..2c3ed99 100644
--- a/src/main/resources/packs/iceandfire/data/cmmodular/miapi/materials/bone/sea_serpent_fang.json
+++ b/src/main/resources/packs/iceandfire/data/cmmodular/miapi/materials/bone/sea_serpent_fang.json
@@ -42,9 +42,39 @@
"attributes": [
{
"attribute": "cmmodular:water_breathing",
- "value": "1",
+ "value": "0.5",
"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"
}
]
},
diff --git a/src/main/resources/packs/irons_spellbooks/data/cmmodular/recipe/arcana_core.json b/src/main/resources/packs/irons_spellbooks/data/cmmodular/recipe/arcana_core.json
index b7eabf3..bcf1998 100644
--- a/src/main/resources/packs/irons_spellbooks/data/cmmodular/recipe/arcana_core.json
+++ b/src/main/resources/packs/irons_spellbooks/data/cmmodular/recipe/arcana_core.json
@@ -2,14 +2,17 @@
"type": "minecraft:crafting_shaped",
"category": "misc",
"pattern": [
- "ACA",
- "CNC",
- "ACA"
+ "NAB",
+ "C C",
+ "HAK"
],
"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",
- "C": "irons_spellbooks:cinder_essence",
- "N": "minecraft:nether_star"
+ "C": "irons_spellbooks:cinder_essence"
},
"result": {
"id": "cmmodular:arcana_core",
diff --git a/tools/materials.py b/tools/materials.py
index 27e10a6..9140f1e 100644
--- a/tools/materials.py
+++ b/tools/materials.py
@@ -78,13 +78,23 @@ PACKS = {
# packs/armory, and are listed here so their pack.mcmeta is written.
"arsenal": {"mod": "tm_arsenal", "name": "Arsenal 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
-# 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"
WATER_BREATHING = "cmmodular:water_breathing"
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="+"):
@@ -444,16 +454,30 @@ MATERIALS = [
tier=4, hardness=6.0, density=3.0, flexibility=4, durability=760,
enchantability=16, mining_speed=7, toughness=2, armor_durability=26,
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={
"default": {
"attributes": [
# Ice and Fire's own sea serpent armour grants water
- # breathing, and that is the whole reason to hunt one. Slot
- # `any`, capped at 1 and read as "at or above 1" by the
- # handler, so one fang anywhere - socketed, worn or held - is
- # enough and a second neither adds nor dilutes, exactly as
- # cinder essence does for fire.
- attribute(WATER_BREATHING, 1, "any"),
+ # breathing, and that is the whole reason to hunt one. Half a
+ # lung per module and capped at 1, the trade the space set
+ # makes: two fangs worn - two sockets, two pieces, or a
+ # socketed piece built of it - and not one held.
+ *(attribute(WATER_BREATHING, 0.5, slot)
+ 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}},