Compare commits
23 Commits
modules-an
...
main
|
|
@ -5,3 +5,6 @@ runs/
|
||||||
repo/
|
repo/
|
||||||
*.class
|
*.class
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
|
# Python virtualenv for the tools in tools/
|
||||||
|
tools/.venv/
|
||||||
|
|
|
||||||
380
README.md
|
|
@ -1,7 +1,7 @@
|
||||||
# Create/Mekanism Modular
|
# Create/Mekanism Modular
|
||||||
|
|
||||||
A [Truly Modular](https://modrinth.com/mod/modular-item-api) addon for Minecraft
|
A [Truly Modular](https://modrinth.com/mod/modular-item-api) addon for Minecraft
|
||||||
1.21.1 / NeoForge. It adds 126 materials drawn from 22 mods - Create, Mekanism, Ice
|
1.21.1 / NeoForge. It adds 128 materials drawn from 22 mods - Create, Mekanism, Ice
|
||||||
and Fire, Immersive Engineering, Ender IO, Applied Energistics, Occultism,
|
and Fire, Immersive Engineering, Ender IO, Applied Energistics, Occultism,
|
||||||
Ars Nouveau, Iron's Spells, Pastel, Crystal Chronicles, NauTec, Superb Warfare,
|
Ars Nouveau, Iron's Spells, Pastel, Crystal Chronicles, NauTec, Superb Warfare,
|
||||||
Industrial Foregoing, RFTools, Create Deco, Steampunk Dimension and the Create
|
Industrial Foregoing, RFTools, Create Deco, Steampunk Dimension and the Create
|
||||||
|
|
@ -9,8 +9,12 @@ 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
|
||||||
|
every limit Iron's Spells puts on a caster.
|
||||||
|
|
||||||
Every source mod is optional. Install any subset - the materials for mods you do
|
Every source mod is optional. Install any subset - the materials for mods you do
|
||||||
not have are never registered.
|
not have are never registered.
|
||||||
|
|
@ -20,7 +24,7 @@ not have are never registered.
|
||||||
Almost all of this mod is data: MIAPI material JSON under
|
Almost all of this mod is data: MIAPI material JSON under
|
||||||
`src/main/resources/packs/<mod>/data/cmmodular/miapi/materials/`.
|
`src/main/resources/packs/<mod>/data/cmmodular/miapi/materials/`.
|
||||||
|
|
||||||
The small amount of Java exists for two reasons.
|
The small amount of Java exists for the handful of things data cannot do.
|
||||||
|
|
||||||
**Conditional loading.** MIAPI reads materials with its own loader, which does
|
**Conditional loading.** MIAPI reads materials with its own loader, which does
|
||||||
not honour `neoforge:conditions` or anything else that could switch a file off,
|
not honour `neoforge:conditions` or anything else that could switch a file off,
|
||||||
|
|
@ -58,10 +62,65 @@ 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 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
|
||||||
|
block collides is asked of the block, with the entity along for the ride in the
|
||||||
|
collision context and nothing in between. So there is one mixin,
|
||||||
|
`BlockStateBaseMixin`, and it is two lines - `SandPhasing.java` owns the
|
||||||
|
decision. A block in `cmmodular:sand_like` returns no collision to a bearer of
|
||||||
|
`cmmodular:sand_phasing`, and sand you are standing inside of cannot suffocate
|
||||||
|
you.
|
||||||
|
|
||||||
|
It does not drop you through the desert. A block wholly below the feet still
|
||||||
|
holds the bearer up, so sand stays something to walk on and a dune becomes
|
||||||
|
something to walk into: step in and you keep your level, jump and the sand under
|
||||||
|
you turns solid and puts you back on top. Without that rule the first sand block
|
||||||
|
you stood on would swallow you down to the sandstone and leave you there, which
|
||||||
|
is a trap rather than an ability.
|
||||||
|
|
||||||
|
**Attributes that go missing.** Modifiers granted by worn and held items are
|
||||||
|
transient - vanilla reads them when equipment changes and the entity's attribute
|
||||||
|
map is the only place they live. Go through a portal and they can be gone, with
|
||||||
|
no equipment change to follow, so nothing puts them back until the piece is
|
||||||
|
taken off and put on again; cinder essence's fire immunity is the one that gets
|
||||||
|
noticed, because the Nether is exactly where it was wanted.
|
||||||
|
`EquipmentAttributes.java` rebuilds the map from the equipment after a dimension
|
||||||
|
change or a respawn, which is what re-equipping would have done and which fixes
|
||||||
|
every attribute rather than only this mod's, and reads this mod's own attributes
|
||||||
|
back off the equipment as well, so the answer is the same whether or not the map
|
||||||
|
ever lost them.
|
||||||
|
|
||||||
|
**Counting gem cases.** `ApothicSockets.java` makes each Apothic gem case worth
|
||||||
|
an Apotheosis socket, which data cannot do at all - every route through MIAPI's
|
||||||
|
component properties is either dead or crashes the crafting screen, see below.
|
||||||
|
It is the one place this mod reaches into another's internals, and it does it
|
||||||
|
reflectively so that neither Apotheosis nor Truly Modular becomes something to
|
||||||
|
build against.
|
||||||
|
|
||||||
|
**One item.** The arcana core, which is the only thing here that is not a
|
||||||
|
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
|
||||||
|
|
@ -81,6 +140,137 @@ Arsenal, Armory, Archery and MIAPI turns up no module anywhere that offers a
|
||||||
`gem_melee_large` slot - the large gemstone is defined and then has nowhere to
|
`gem_melee_large` slot - the large gemstone is defined and then has nowhere to
|
||||||
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.
|
||||||
|
Either socket takes an Apothic gem case as readily as a gemstone - see below.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
Where the gem sits is the unfinished part of these four, and the honest summary
|
||||||
|
is that it is placed by trial rather than by arithmetic. MIAPI draws a worn piece
|
||||||
|
once per body part; which part a module is drawn under comes from its own model's
|
||||||
|
`origin`, and where it sits comes from the matching entry of its slot's transform
|
||||||
|
stack. Armory's gemstone declares no origin, so a gem is drawn once, in the
|
||||||
|
`body` pass, from the `body` entry - which means a gem slot is placed in body
|
||||||
|
space no matter which limb it is cut into, and is pinned to the torso rather than
|
||||||
|
to the limb it belongs to.
|
||||||
|
|
||||||
|
The numbers are body-space pixels from the neck: +x to the wearer's left, +y
|
||||||
|
down, -z forward. A pauldron gem is turned a quarter turn about y to face out of
|
||||||
|
the shoulder; a knee gem faces forward already. Right-hand pieces are the same
|
||||||
|
numbers with x and the turn negated.
|
||||||
|
|
||||||
|
They are not right yet, and two attempts to make them right are worth recording
|
||||||
|
so that they are not repeated. Naming the limb as the gem slot's `origin` - so
|
||||||
|
the offset lands in the entry the limb pass reads and the gem follows the arm -
|
||||||
|
composes correctly on paper and in game puts the gems several blocks away from
|
||||||
|
the wearer, floating. And `tools/preview_armour.py`, which applies the same chain
|
||||||
|
MIAPI does and draws the result from any angle, agrees with the paper rather than
|
||||||
|
with the game: given the configuration that in practice left gems at the neck, it
|
||||||
|
puts them on the pauldron. The tool is worth keeping for reading Armory's own
|
||||||
|
geometry - where the plate bands are, which face is which, how far the chain
|
||||||
|
scales a limb - and it should not be trusted to predict a gem's final position
|
||||||
|
until it can reproduce a symptom somebody has actually seen.
|
||||||
|
|
||||||
|
One thing does not survive any of this and is not fixable from here. A slot
|
||||||
|
transform that names an origin has nothing left for the inventory icon, which
|
||||||
|
reads a different entry of the same stack, so the gem sits at the centre of the
|
||||||
|
icon - the collar of a chestplate, which is where Armory leaves its own, and the
|
||||||
|
crotch of a pair of pants, which is where this one leaves ours. The two
|
||||||
|
placements come from one transform and cannot both be aimed.
|
||||||
|
|
||||||
|
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 can set any component a module
|
||||||
|
likes: put a case in an armour piece's `gem_armor_medium` slot instead of a
|
||||||
|
gemstone, and the piece reports an Apotheosis socket. The gem itself goes in at
|
||||||
|
a smithing table, through Apotheosis' own socketing recipe, not at the workbench.
|
||||||
|
|
||||||
|
A weapon takes one too, and there is a case per gem size to say so. A slot
|
||||||
|
accepts what its `allowed` list names: the socketed pommels above ask for
|
||||||
|
`gem_melee_medium` and `gem_melee_large`, and the baseline sockets - Arsenal's
|
||||||
|
own socket pommel, and the dual socket guard that carries two of them - ask for
|
||||||
|
`gem_melee_small`. So the case is allowed in the medium slots, a **great case**
|
||||||
|
answers for the large, and a **small case** for the small, which is the same
|
||||||
|
trade in a sword that it is in a pauldron. The small one names the armour size
|
||||||
|
key as well, because a small slot is a small slot wherever it was cut and a key
|
||||||
|
nothing asks for costs nothing.
|
||||||
|
|
||||||
|
Nothing else changes. Sockets are counted per case rather than per item, so a
|
||||||
|
sword with a case in its pommel reports one the same way a chestplate does and
|
||||||
|
a guard holding two small cases reports two; Apotheosis sockets gems into
|
||||||
|
weapons already. All three are filed under `packs/apotheosis` rather than with
|
||||||
|
the pommels, because a case without Apotheosis is an item that grants nothing,
|
||||||
|
while a case without Arsenal is one that simply never fits anywhere.
|
||||||
|
|
||||||
|
One socket a case, and none of it is in the module JSON, because every route
|
||||||
|
through data is closed. MIAPI's `components` property sets a component rather
|
||||||
|
than adding to it, so two cases both writing `1` leave the piece with one socket
|
||||||
|
between them. The property that could resolve a count instead of a constant,
|
||||||
|
`advanced_components`, is written but never registered in MIAPI 2.3.8 -
|
||||||
|
`PropertyRegistry` wires up `components` and `material_component_property` and
|
||||||
|
never mentions it - so that key resolves to nothing. And the one that does work
|
||||||
|
turned out to be worse than useless: `ComponentProperty.preview` dereferences the
|
||||||
|
workbench without a null check, MIAPI passes it null while hovering an option in
|
||||||
|
the replace list, and `shouldExecuteOnCraft` only calls it for a module that
|
||||||
|
carries the property - so a gem case with a `components` line crashed the
|
||||||
|
crafting screen, and only a gem case did.
|
||||||
|
|
||||||
|
So `ApothicSockets.java` answers it instead, and Apotheosis makes that easy by
|
||||||
|
asking the question out loud: `SocketHelper` reads the component and then fires
|
||||||
|
`GetItemSocketsEvent` so anything may revise the answer. This counts the cases on
|
||||||
|
the stack and adds one for each. Nothing writes the component now, which is
|
||||||
|
better than the data version managed: a socket cut by a Sigil of Socketing
|
||||||
|
survives and the cases stack on top, where the component used to overwrite it.
|
||||||
|
|
||||||
|
It reaches both mods reflectively rather than compiling against them. Neither is
|
||||||
|
a build dependency here and one socket is not worth making them one, so the seven
|
||||||
|
members it needs - three off MIAPI's module tree, three off the event, and the
|
||||||
|
event class itself - are resolved once at startup and cached. Missing classes
|
||||||
|
mean the mods are not installed, which is ordinary and silent; a missing method
|
||||||
|
means something was renamed, which is logged, because it is a thing to fix rather
|
||||||
|
than a thing to expect. Either way the count falls back to what the component
|
||||||
|
says on its own.
|
||||||
|
|
||||||
|
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 is drawn here rather than borrowed. It used to wear Armory's medium gemstone
|
||||||
|
model, which was the least work and the wrong picture: a case is the setting,
|
||||||
|
not the stone, and one that looks like a gemstone is indistinguishable from the
|
||||||
|
gemstone it is an alternative to. So `items/gem_case.png` is a four-by-four
|
||||||
|
bezel around an empty two-by-two socket - a gemstone's own footprint, with the
|
||||||
|
mount drawn around it - lit from the top left like the pommel sockets, and on
|
||||||
|
the same greys the materials' palettes map, so it comes out in whatever it was
|
||||||
|
built from. The socket stays empty-looking whatever is in it, which is honest:
|
||||||
|
an Apotheosis gem lives in a component and MIAPI has nothing to render for it.
|
||||||
|
|
||||||
|
The bezel is twice a gemstone across, which would put a case at twice a
|
||||||
|
gemstone's size in a slot sized for one, so the module's own model transform
|
||||||
|
carries a scale of 0.5 and hands the slot back a gemstone's footprint. That is
|
||||||
|
the module's business rather than the slot's: a socket that holds either should
|
||||||
|
not have to be scaled differently depending on which went in.
|
||||||
|
|
||||||
|
One thing worth knowing: 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
|
||||||
|
|
@ -101,6 +291,37 @@ drawn in, rather than wearing the plain piece's art.
|
||||||
All of the art here is placeholder-grade and meant to be repainted - see
|
All of the art here is placeholder-grade and meant to be repainted - see
|
||||||
Textures below.
|
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. 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,
|
||||||
|
which is the point of the recipe.
|
||||||
|
|
||||||
|
Cooldown and cast time are read by Iron's Spells as a factor on a base of 1 and
|
||||||
|
spent as `time * (2 - value)`, so a `*` modifier of 1 puts the value at 2 and
|
||||||
|
the time at nothing; spell power is the same reading, where `1 + 9` is ten
|
||||||
|
times. There is no "infinite mana" to ask for, so a pool nothing can empty and a
|
||||||
|
regeneration that refills it instantly is what that means here.
|
||||||
|
|
||||||
|
The item is always registered, because an item cannot be added conditionally the
|
||||||
|
way a datapack can. Nothing that makes it worth having is: the material and the
|
||||||
|
recipe both live in the Iron's Spells pack, so without that mod the core cannot
|
||||||
|
be crafted, is not a material, and grants nothing.
|
||||||
|
|
||||||
|
Its texture is Minecraft's own heart of the sea with its hue rotated into
|
||||||
|
violet - the only art here that is not drawn for this mod, and Mojang's rather
|
||||||
|
than ours, which is worth knowing given what the khopesh section above says
|
||||||
|
about shipping other people's. Nothing but the hue is touched, so the shading
|
||||||
|
is the vanilla item's, including how dark its underside is: the palette the
|
||||||
|
material hands out is therefore a deep one with little highlight, which is what
|
||||||
|
the item actually looks like rather than what a gem usually does.
|
||||||
|
|
||||||
## Stats
|
## Stats
|
||||||
|
|
||||||
Tinkers' Construct 3 is the reference, re-scaled onto MIAPI's vanilla-derived
|
Tinkers' Construct 3 is the reference, re-scaled onto MIAPI's vanilla-derived
|
||||||
|
|
@ -172,11 +393,26 @@ new piece is wanted rather than a repaint of an existing one.
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./gradlew build
|
./build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Needs nothing but NeoForge - Truly Modular is a runtime dependency only, since
|
The jar lands in `build/libs/`. Needs nothing but NeoForge - Truly Modular is a
|
||||||
this mod talks to it purely through datapack JSON.
|
runtime dependency only, since this mod talks to it purely through datapack
|
||||||
|
JSON.
|
||||||
|
|
||||||
|
`./gradlew build` does the same thing, and is fine if the java on your path is
|
||||||
|
one Gradle can run on. Gradle 8.12 cannot run on a JDK newer than 23, which a
|
||||||
|
distro's default java may well be by now, and the failure when it is says
|
||||||
|
`Could not create task ':test'` and `Type T not present` without ever mentioning
|
||||||
|
Java. `build.sh` exists to pick a JDK between 17 and 23 - honouring `JAVA_HOME`
|
||||||
|
if it already points at one - and to say what is wrong in as many words when
|
||||||
|
there is none to pick. Arguments are passed through, with any task named
|
||||||
|
replacing the default `build` rather than adding to it, so `./build.sh clean
|
||||||
|
build` cleans and then builds; a bare flag such as `./build.sh --info` still
|
||||||
|
gets the default task.
|
||||||
|
|
||||||
|
None of this touches what the mod is compiled against: the toolchain in
|
||||||
|
`build.gradle` is Java 21 either way, and Gradle provisions it if it is missing.
|
||||||
|
|
||||||
## Regenerating materials
|
## Regenerating materials
|
||||||
|
|
||||||
|
|
@ -187,8 +423,10 @@ textures. The generated JSON is committed, so a plain build does not need this.
|
||||||
python3 tools/generate_materials.py --jars ~/path/to/mod/jars
|
python3 tools/generate_materials.py --jars ~/path/to/mod/jars
|
||||||
```
|
```
|
||||||
|
|
||||||
Point it at the pack's `mods` folder. Every item id in `materials.py` is checked
|
Point it at the pack's `mods` folder. This mod's own namespace is served out of
|
||||||
against those jars and the run fails on a typo, rather than shipping a material
|
`src/main/resources/assets/cmmodular/` rather than a jar, so the arcana core is
|
||||||
|
checked and sampled like everything else. Every item id in `materials.py` is
|
||||||
|
checked against those jars and the run fails on a typo, rather than shipping a material
|
||||||
that silently refuses to load.
|
that silently refuses to load.
|
||||||
|
|
||||||
A mod whose jar is not there is skipped instead, with a line saying so, and its
|
A mod whose jar is not there is skipped instead, with a line saying so, and its
|
||||||
|
|
@ -205,6 +443,58 @@ Requires `pillow` and `numpy`.
|
||||||
Module JSON and textures are not generated. A run only clears the material
|
Module JSON and textures are not generated. A run only clears the material
|
||||||
folders, so the hand-written packs survive it.
|
folders, so the hand-written packs survive it.
|
||||||
|
|
||||||
|
## Looking at the armour
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tools/ARMOUR_QUICKSTART.py # every armour model, one at a time
|
||||||
|
python3 tools/ARMOUR_GUI.py --jar <armory> # draw and unwrap socket geometry
|
||||||
|
python3 tools/ARMOUR_EDITOR.py --jar <armory> # pose a scene and nudge placements
|
||||||
|
```
|
||||||
|
|
||||||
|
The quickstart is the way in and needs no arguments: it reads every model in
|
||||||
|
`src/main/resources` - worn armour, icons, sword parts, the loose item models,
|
||||||
|
the files in the tree rather than a built jar - and lists them with the first
|
||||||
|
one showing. A jar passed with `--jar` adds its models to the list too, after
|
||||||
|
yours and marked with their namespace, which is the only way to see a socket
|
||||||
|
against the plate it is cut into: the geometry in this repo is the socket, and
|
||||||
|
`arm_left/heavy` is Armory's. Click a row to show or hide it and to point the camera at it;
|
||||||
|
`Reload from disk` picks up a model saved in another window.
|
||||||
|
|
||||||
|
The left panel is the selected model's MIAPI transform. Three arrows on the
|
||||||
|
model's origin move it along an axis and the rotate toggle swaps them for three
|
||||||
|
rings; the position and rotation boxes read what the drag did, in the units a
|
||||||
|
module writes, and take typed numbers back. `Copy as JSON` puts the `transform`
|
||||||
|
block on the clipboard, and `Write to source` puts it back in the module entry
|
||||||
|
it was read from - scale and origin untouched. It writes every model that has
|
||||||
|
moved rather than just the selected one, and marks a moved row with a `*` until
|
||||||
|
it has; a model no module names has nowhere to write to and is named in the
|
||||||
|
report instead. `Unwrap UVs` re-cuts the selected model's texture so every
|
||||||
|
face has a patch of its own, writes the atlas size beside the boxes, and paints
|
||||||
|
a template to match where there is no texture yet - never over one there is.
|
||||||
|
|
||||||
|
Where a model goes comes from the mod's own module data: MIAPI draws a model
|
||||||
|
under the body part its `origin` names, and those are declared in
|
||||||
|
`packs/*/data/*/miapi/modules/`. What is *not* in this tree is the offset
|
||||||
|
Armory's slot transforms carry, so without `--jar` a worn model is flipped onto
|
||||||
|
its part but not moved along it - right limb, roughly right place. Pass the
|
||||||
|
Armory jar for exact placement, and for its own plates to line new geometry up
|
||||||
|
against.
|
||||||
|
|
||||||
|
These need pyvista and pyvistaqt, which hardly any distribution packages, so
|
||||||
|
the tools keep a virtualenv at `tools/.venv` and re-run themselves inside it.
|
||||||
|
Build it once with
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -m venv --system-site-packages tools/.venv
|
||||||
|
tools/.venv/bin/pip install pyvista pyvistaqt
|
||||||
|
```
|
||||||
|
|
||||||
|
`--system-site-packages` is what keeps the distribution's Qt in charge; a
|
||||||
|
PySide6 from pip alongside the system Qt is a partial upgrade waiting to
|
||||||
|
happen. On a Wayland session the viewport runs through XWayland, because VTK's
|
||||||
|
Python wheels have no Wayland window backend - the tools say which display they
|
||||||
|
picked, and why, when they start.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Titanium's palette comes from Cosmonautics' ingot, which is violet. Air War's
|
- Titanium's palette comes from Cosmonautics' ingot, which is violet. Air War's
|
||||||
|
|
@ -213,6 +503,51 @@ folders, so the hand-written packs survive it.
|
||||||
emissive, so it still reads as fire in game.
|
emissive, so it still reads as fire in game.
|
||||||
- Dragon scales are one material per colour (12 dragon, 7 sea serpent, 3 death
|
- Dragon scales are one material per colour (12 dragon, 7 sea serpent, 3 death
|
||||||
worm chitin). Colours within an element share stats and differ only in palette.
|
worm chitin). Colours within an element share stats and differ only in palette.
|
||||||
|
- Sea serpent scales add `miapi:generic.swim_speed` per worn slot, 0.10 a
|
||||||
|
module. It stacks per module like everything else here, so a full suit of
|
||||||
|
them lands near +1 - about what Armory's own scuba set is worth, which is the
|
||||||
|
company a sea serpent belongs in.
|
||||||
|
- Deathworm chitin's sand phasing is gated on a tag, `cmmodular:sand_like`, so
|
||||||
|
a pack can widen it: the common `#c:sands`, `#c:gravels` and
|
||||||
|
`#c:concrete_powders` (the `c:` namespace being where Forge's tags went),
|
||||||
|
each optional in case a set is not registered, plus the vanilla loose blocks
|
||||||
|
by name so it works with none of them.
|
||||||
|
- Dragon bone burns what it hits: a fake Fire Aspect I on handheld modules, the
|
||||||
|
way Ice and Fire's own flamed dragonbone sword does. MIAPI's `immolate` is
|
||||||
|
not that - despite the name it is bonus damage against something that is
|
||||||
|
already alight, and MIAPI's own wiki marks it unimplemented. Ice and Fire
|
||||||
|
ships one dragon bone item for all three kinds of dragon, so this cannot be
|
||||||
|
the fire dragon's alone; the fire dragonscales are the only element-specific
|
||||||
|
dragon material there is.
|
||||||
|
- Bones are the other half of that. Dragon bone already built grips, handles
|
||||||
|
and hafts - `bone` is on every tool and bow whitelist - and now carries
|
||||||
|
`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.
|
||||||
|
- 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
|
||||||
|
carries `bone` as a hidden group: matched when a module decides what it will
|
||||||
|
accept, ignored when the workbench decides what heading to file the material
|
||||||
|
under, which is how Armory hands its own gem slots vanilla materials. Bone is
|
||||||
|
on every armour whitelist except the scuba set - that one takes
|
||||||
|
fabric/metal/glass, being a sealed suit rather than a plated one - so a scale
|
||||||
|
builds every other piece of armour, and tool and bow parts besides.
|
||||||
- Interstellar Expansion is mostly machinery. Only three of its items are a
|
- Interstellar Expansion is mostly machinery. Only three of its items are a
|
||||||
material rather than a component; solid E-710 is left out because it is rocket
|
material rather than a component; solid E-710 is left out because it is rocket
|
||||||
fuel.
|
fuel.
|
||||||
|
|
@ -239,25 +574,34 @@ folders, so the hand-written packs survive it.
|
||||||
- Cinder essence uses slot `any`, so one gem grants fire immunity wherever it
|
- Cinder essence uses slot `any`, so one gem grants fire immunity wherever it
|
||||||
sits, worn or held. The attribute caps at 1 and the handler treats anything at
|
sits, worn or held. The attribute caps at 1 and the handler treats anything at
|
||||||
or above 1 as immune, so extra pieces neither add nor dilute.
|
or above 1 as immune, so extra pieces neither add nor dilute.
|
||||||
|
- A material's name is a lang key, not a field on the material. MIAPI reads
|
||||||
|
`miapi.material.<namespace>.<group>.<name>` - its own id with the separators
|
||||||
|
turned into dots - so `cmmodular:metal/arcane_metal` is named by
|
||||||
|
`miapi.material.cmmodular.metal.arcane_metal` in `assets/cmmodular/lang/`,
|
||||||
|
which the generator writes. The value is the bare name: MIAPI puts in the
|
||||||
|
space before the module's own name. There is a `translation` field, but it is
|
||||||
|
only read alongside `fake_translation`, as a pair registering a name for a key
|
||||||
|
at runtime; on its own it names nothing, and the key shows raw in game.
|
||||||
- Material properties belong under the `default` key, not `armor` or `handheld`.
|
- Material properties belong under the `default` key, not `armor` or `handheld`.
|
||||||
A gem socketed into armour is not tagged `armor`: gem modules declare which
|
A gem socketed into armour is not tagged `armor`: gem modules declare which
|
||||||
material properties they accept and list only `default` plus their own gem
|
material properties they accept and list only `default` plus their own gem
|
||||||
tags. Anything filed under `armor`/`handheld` silently never applies to a
|
tags. Anything filed under `armor`/`handheld` silently never applies to a
|
||||||
socketed gem. The attribute's own slot is what decides where it counts.
|
socketed gem. The attribute's own slot is what decides where it counts.
|
||||||
- Arcane alloy shortens Iron's Spells cooldowns, and that is the point of it:
|
- Arcane metal shortens Iron's Spells cooldowns, and that is the point of it:
|
||||||
0.10 held, 0.05 on a helmet, 0.03 on the lower armour, filed under `default`
|
0.10 held, 0.05 on a helmet, 0.03 on the lower armour, filed under `default`
|
||||||
so a suit plated in it counts as well as a staff made of it. Iron's Spells
|
so a suit plated in it counts as well as a staff made of it. Iron's Spells
|
||||||
reads the attribute as a factor on a base of 1 and spends it as
|
reads the attribute as a factor on a base of 1 and spends it as
|
||||||
`cooldown * (2 - value)`, so the operation is `*` - a flat `+` would be added
|
`cooldown * (2 - value)`, so the operation is `*` - a flat `+` would be added
|
||||||
to a base of zero and do nothing - and 0.10 means 10% off the wait. It stacks
|
to a base of zero and do nothing - and 0.10 means 10% off the wait. It stacks
|
||||||
per module, so a weapon built entirely of arcane alloy is around 40%, against
|
per module, so a weapon built entirely of arcane metal is around 40%, against
|
||||||
25% for Iron's Spells' own best staff; a full set adds roughly another 45%,
|
25% for Iron's Spells' own best staff; a full set adds roughly another 45%,
|
||||||
which keeps even an all-arcane player under the 100% where the formula
|
which keeps even an all-arcane player under the 100% where the formula
|
||||||
bottoms out. The material
|
bottoms out. The material is called Arcane Metal because the item is an Arcane
|
||||||
is called Arcane Alloy rather than Arcane Steel because Create: Wizardry
|
Ingot and a material is named for the substance, not the shape it is traded
|
||||||
renames the ingot that way, and Create: Wizardry's arcane sheet and block are
|
in; plain Arcane, which is what Iron's Spells calls the material itself, is
|
||||||
the same metal - they arrive as a material extension in that mod's own pack,
|
taken here by the essence. Create: Wizardry's arcane sheet and block are the
|
||||||
since naming them directly would break the material for anyone without it.
|
same metal - they arrive as a material extension in that mod's own pack, since
|
||||||
|
naming them directly would break the material for anyone without it.
|
||||||
- Platinum comes from Create Propulsion: Simulated, the only platinum in the
|
- Platinum comes from Create Propulsion: Simulated, the only platinum in the
|
||||||
pack. It is soft for its density, so it swings slowly and hits like steel; it
|
pack. It is soft for its density, so it swings slowly and hits like steel; it
|
||||||
is worth mining for gold-grade enchantability that does not fall apart, and it
|
is worth mining for gold-grade enchantability that does not fall apart, and it
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Builds the mod jar into build/libs/.
|
||||||
|
#
|
||||||
|
# Gradle 8.12 cannot run on a JDK newer than 23, and a distro's default java is
|
||||||
|
# often newer than that, so this picks a JDK Gradle can run on rather than
|
||||||
|
# leaving you to read the "Type T not present" failure that comes of not doing
|
||||||
|
# so. The build itself still compiles against Java 21 either way - that is the
|
||||||
|
# toolchain in build.gradle, and Gradle provisions it if it is missing.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Major version of the java at $1/bin/java, or nothing if it will not run.
|
||||||
|
java_major() {
|
||||||
|
local out
|
||||||
|
out=$("$1/bin/java" -version 2>&1) || return 0
|
||||||
|
sed -n 's/^[^"]*"\([0-9]*\).*/\1/p' <<<"$out" | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
usable() {
|
||||||
|
local major
|
||||||
|
[ -x "$1/bin/java" ] || return 1
|
||||||
|
major=$(java_major "$1")
|
||||||
|
[ -n "$major" ] && [ "$major" -ge 17 ] && [ "$major" -le 23 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "${JAVA_HOME:-}" ] || ! usable "$JAVA_HOME"; then
|
||||||
|
for candidate in /usr/lib/jvm/java-{21,17}-openjdk /usr/lib/jvm/java-{21,17}-openjdk-amd64; do
|
||||||
|
if usable "$candidate"; then
|
||||||
|
export JAVA_HOME="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${JAVA_HOME:-}" ] || ! usable "$JAVA_HOME"; then
|
||||||
|
echo "build.sh: no JDK between 17 and 23 found - Gradle 8.12 cannot run on anything newer." >&2
|
||||||
|
echo "Install one (e.g. pacman -S jdk17-openjdk) or point JAVA_HOME at it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Named tasks replace the default rather than being appended to it. Appending
|
||||||
|
# would turn `build.sh clean build` into `gradlew build clean build`, which
|
||||||
|
# Gradle folds back into build and clean, in that order - it cleans away the jar
|
||||||
|
# it just built. Bare flags still get the default task, so `build.sh --info`
|
||||||
|
# means an informative build rather than a no-op.
|
||||||
|
tasks=()
|
||||||
|
for arg in "$@"; do
|
||||||
|
[ "${arg#-}" = "$arg" ] && tasks+=("$arg")
|
||||||
|
done
|
||||||
|
[ ${#tasks[@]} -eq 0 ] && set -- build "$@"
|
||||||
|
|
||||||
|
echo "build.sh: using JDK $(java_major "$JAVA_HOME") at $JAVA_HOME"
|
||||||
|
./gradlew "$@"
|
||||||
|
|
||||||
|
# Name the jar this run produced. Old versions linger in build/libs/, so listing
|
||||||
|
# the whole directory would be ambiguous about which one is current. Only worth
|
||||||
|
# saying when a task that builds one actually ran: `build.sh clean` legitimately
|
||||||
|
# ends with no jar at all.
|
||||||
|
builds_jar=false
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in build | assemble | jar) builds_jar=true ;; esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$builds_jar" = true ]; then
|
||||||
|
prop() { sed -n "s/^$1=//p" gradle.properties; }
|
||||||
|
jar="build/libs/$(prop mod_id)-$(prop mod_version).jar"
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ -f "$jar" ]; then
|
||||||
|
echo "Built: $jar"
|
||||||
|
else
|
||||||
|
echo "build.sh: expected $jar, but it is not there." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
@ -7,7 +7,7 @@ neo_version=21.1.248
|
||||||
|
|
||||||
mod_id=cmmodular
|
mod_id=cmmodular
|
||||||
mod_name=Create/Mekanism Modular
|
mod_name=Create/Mekanism Modular
|
||||||
mod_version=1.2.0
|
mod_version=1.4.0
|
||||||
mod_group_id=eu.abdelbaki.cmmodular
|
mod_group_id=eu.abdelbaki.cmmodular
|
||||||
mod_authors=themiro
|
mod_authors=themiro
|
||||||
mod_description=Materials from Create, Mekanism, Ice and Fire, Iron's Spells and the Create aerospace addons, for Truly Modular - plus a khopesh blade and a sealed space suit.
|
mod_description=Materials from Create, Mekanism, Ice and Fire, Iron's Spells and the Create aerospace addons, for Truly Modular - plus a khopesh blade and a sealed space suit.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
package eu.abdelbaki.cmmodular;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.bus.api.Event;
|
||||||
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes each Apothic gem case worth one Apotheosis socket instead of the piece
|
||||||
|
* as a whole being worth one.
|
||||||
|
*
|
||||||
|
* <p>None of this is in the module JSON, and it cannot be. MIAPI's
|
||||||
|
* {@code components} property sets a component rather than adding to it, so two
|
||||||
|
* cases both writing 1 would leave the piece with one socket between them; the
|
||||||
|
* property that could resolve a count instead, {@code advanced_components}, is
|
||||||
|
* written but never registered in MIAPI 2.3.8, so that key is dead. And the one
|
||||||
|
* that does work is worse than useless here: {@code ComponentProperty.preview}
|
||||||
|
* dereferences the workbench without a null check, and MIAPI hands it null while
|
||||||
|
* hovering an option in the replace list, so merely carrying a {@code components}
|
||||||
|
* property crashes the crafting screen on a server.
|
||||||
|
*
|
||||||
|
* <p>Apotheosis asks the question itself. {@code SocketHelper.getSockets} reads
|
||||||
|
* the component and then fires {@code GetItemSocketsEvent} so anything can
|
||||||
|
* revise the answer, which is exactly what is wanted, so this counts the cases
|
||||||
|
* and revises it.
|
||||||
|
*
|
||||||
|
* <p>It does that without compiling against either mod. This mod deliberately
|
||||||
|
* has no dependencies to build on - Truly Modular and Apotheosis are runtime
|
||||||
|
* mods it talks to through data - and one socket is not worth giving that up.
|
||||||
|
* So both sides are reached reflectively and resolved once at startup. Failing
|
||||||
|
* costs nothing: the count reverts to what the component alone says, which is
|
||||||
|
* the behaviour without this class at all. Absence is silent, since a pack
|
||||||
|
* without those mods is the ordinary case, but a rename is logged, because
|
||||||
|
* that one is a thing to fix rather than a thing to expect.
|
||||||
|
*/
|
||||||
|
public final class ApothicSockets {
|
||||||
|
/** The one place this mod logs: a silent reflective failure is unfindable. */
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CMModular.MOD_ID);
|
||||||
|
|
||||||
|
private static final String EVENT = "dev.shadowsoffire.apotheosis.event.GetItemSocketsEvent";
|
||||||
|
private static final String MIAPI_MODULE = "smartin.miapi.modules.ItemModule";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module ids that are a gem case: one per gem size, one per limb socket.
|
||||||
|
* A case is not listed twice for being allowed in a pommel as well as in
|
||||||
|
* armour - it is one module either way, and this counts modules, so a
|
||||||
|
* guard holding two small cases is worth two sockets by the same rule
|
||||||
|
* that makes a chestplate holding one worth one.
|
||||||
|
*/
|
||||||
|
private static final List<String> CASES = List.of(
|
||||||
|
"cmmodular:gem/apothic_case",
|
||||||
|
"cmmodular:gem/case_small",
|
||||||
|
"cmmodular:gem/case_great",
|
||||||
|
"cmmodular:gem/limb/case_arm_left",
|
||||||
|
"cmmodular:gem/limb/case_arm_right",
|
||||||
|
"cmmodular:gem/limb/case_leg_left",
|
||||||
|
"cmmodular:gem/limb/case_leg_right");
|
||||||
|
|
||||||
|
/** MIAPI's module tree, reached without naming its types at compile time. */
|
||||||
|
private static Method getModules;
|
||||||
|
private static Method getFlatList;
|
||||||
|
private static Method getModule;
|
||||||
|
private static Method moduleId;
|
||||||
|
|
||||||
|
/** Apotheosis' event, likewise. */
|
||||||
|
private static Method getStack;
|
||||||
|
private static Method getSockets;
|
||||||
|
private static Method setSockets;
|
||||||
|
|
||||||
|
private ApothicSockets() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register() {
|
||||||
|
Class<?> itemModule;
|
||||||
|
Class<?> moduleInstance;
|
||||||
|
Class<?> event;
|
||||||
|
try {
|
||||||
|
itemModule = Class.forName(MIAPI_MODULE);
|
||||||
|
moduleInstance = Class.forName("smartin.miapi.modules.ModuleInstance");
|
||||||
|
event = Class.forName(EVENT);
|
||||||
|
} catch (ClassNotFoundException absent) {
|
||||||
|
// One of the two mods is not installed, which is the ordinary case
|
||||||
|
// for a pack without them and nothing to say anything about. The
|
||||||
|
// classes answer that better than ModList does, which is not
|
||||||
|
// reliably populated this early.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
getModules = itemModule.getMethod("getModules", ItemStack.class);
|
||||||
|
getFlatList = moduleInstance.getMethod("getFlatList");
|
||||||
|
getModule = moduleInstance.getMethod("getModule");
|
||||||
|
moduleId = itemModule.getMethod("id");
|
||||||
|
getStack = event.getMethod("getStack");
|
||||||
|
getSockets = event.getMethod("getSockets");
|
||||||
|
setSockets = event.getMethod("setSockets", int.class);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<Event> type = (Class<Event>) event;
|
||||||
|
NeoForge.EVENT_BUS.addListener(type, (Consumer<Event>) ApothicSockets::onGetSockets);
|
||||||
|
} catch (ReflectiveOperationException | ClassCastException moved) {
|
||||||
|
// Both mods are here but something has been renamed, which is worth
|
||||||
|
// saying out loud: the cases still work, they just stop counting.
|
||||||
|
LOGGER.warn("Apothic gem cases will hold one socket a piece rather than one"
|
||||||
|
+ " each - Apotheosis or MIAPI has moved something: {}", moved.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answers with a socket a case, added to whatever the stack already had.
|
||||||
|
* Nothing else here writes that component now, so a socket cut by a Sigil of
|
||||||
|
* Socketing survives and the cases stack on top of it, which is a better
|
||||||
|
* trade than the component's own answer of overwriting it.
|
||||||
|
*/
|
||||||
|
private static void onGetSockets(Event event) {
|
||||||
|
try {
|
||||||
|
int cases = countCases((ItemStack) getStack.invoke(event));
|
||||||
|
if (cases > 0) {
|
||||||
|
setSockets.invoke(event, (int) getSockets.invoke(event) + cases);
|
||||||
|
}
|
||||||
|
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||||
|
// Answering wrongly is worse than not answering: leave the count be.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int countCases(ItemStack stack) throws ReflectiveOperationException {
|
||||||
|
Object root = getModules.invoke(null, stack);
|
||||||
|
if (root == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int found = 0;
|
||||||
|
for (Object module : (List<?>) getFlatList.invoke(root)) {
|
||||||
|
Object id = moduleId.invoke(getModule.invoke(module));
|
||||||
|
if (id != null && CASES.contains(id.toString())) {
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package eu.abdelbaki.cmmodular;
|
||||||
|
|
||||||
|
import net.minecraft.world.item.CreativeModeTabs;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.Rarity;
|
||||||
|
import net.neoforged.bus.api.IEventBus;
|
||||||
|
import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredItem;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The one item this mod adds: an endgame gem that undoes Iron's Spells' three
|
||||||
|
* limits at once - mana, cooldown and cast time - and multiplies spell power
|
||||||
|
* tenfold.
|
||||||
|
*
|
||||||
|
* <p>The item is always registered, because an item cannot be added
|
||||||
|
* conditionally the way a datapack can. Everything that makes it worth having
|
||||||
|
* is not: its material and its recipe both live in the Iron's Spells pack, so
|
||||||
|
* without that mod the core is neither craftable nor a material, and the
|
||||||
|
* attributes it grants are never asked for.
|
||||||
|
*/
|
||||||
|
public final class ArcanaCore {
|
||||||
|
private static final DeferredRegister.Items ITEMS =
|
||||||
|
DeferredRegister.createItems(CMModular.MOD_ID);
|
||||||
|
|
||||||
|
public static final DeferredItem<Item> ITEM = ITEMS.registerSimpleItem(
|
||||||
|
"arcana_core", new Item.Properties().rarity(Rarity.EPIC).fireResistant().stacksTo(16));
|
||||||
|
|
||||||
|
private ArcanaCore() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register(IEventBus modBus) {
|
||||||
|
ITEMS.register(modBus);
|
||||||
|
modBus.addListener(ArcanaCore::addToCreativeTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** With the other crafting ingredients, which is what it is. */
|
||||||
|
private static void addToCreativeTab(BuildCreativeModeTabContentsEvent event) {
|
||||||
|
if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) {
|
||||||
|
event.accept(ITEM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,11 +73,21 @@ 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);
|
||||||
|
WaterCombat.register(modBus);
|
||||||
|
SandPhasing.register(modBus);
|
||||||
|
EquipmentAttributes.register(modBus);
|
||||||
|
ArcanaCore.register(modBus);
|
||||||
|
ApothicSockets.register();
|
||||||
modBus.addListener(this::addMaterialPacks);
|
modBus.addListener(this::addMaterialPacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package eu.abdelbaki.cmmodular;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
|
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.AttributeMap;
|
||||||
|
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.bus.api.IEventBus;
|
||||||
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
|
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps an attribute that a piece of equipment grants from quietly going
|
||||||
|
* missing.
|
||||||
|
*
|
||||||
|
* <p>Modifiers off worn and held items are transient: vanilla reads them when
|
||||||
|
* the equipment changes and puts them in the entity's attribute map, and that
|
||||||
|
* map is the only place they live. Change dimension and they can be gone - the
|
||||||
|
* player is rebuilt on the way through, no equipment change follows, and so
|
||||||
|
* nothing puts them back until the piece is taken off and put on again. Cinder
|
||||||
|
* essence's fire immunity is the one that gets noticed, because the Nether is
|
||||||
|
* exactly where it was wanted.
|
||||||
|
*
|
||||||
|
* <p>Two answers, because a modifier can be missing in two places. The map is
|
||||||
|
* rebuilt from the equipment after a dimension change or a respawn, which is
|
||||||
|
* what re-equipping would have done and which fixes every attribute rather than
|
||||||
|
* only this mod's. And when one of this mod's own attributes is read, the
|
||||||
|
* equipment is consulted as a fallback - the same data vanilla applies from, so
|
||||||
|
* the answer is the same whether or not the map ever lost it.
|
||||||
|
*/
|
||||||
|
public final class EquipmentAttributes {
|
||||||
|
private EquipmentAttributes() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register(IEventBus modBus) {
|
||||||
|
NeoForge.EVENT_BUS.addListener(EquipmentAttributes::onChangedDimension);
|
||||||
|
NeoForge.EVENT_BUS.addListener(EquipmentAttributes::onRespawn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onChangedDimension(PlayerEvent.PlayerChangedDimensionEvent event) {
|
||||||
|
reapply(event.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onRespawn(PlayerEvent.PlayerRespawnEvent event) {
|
||||||
|
reapply(event.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Puts every modifier the worn and held items grant back into the map. */
|
||||||
|
private static void reapply(Player player) {
|
||||||
|
AttributeMap attributes = player.getAttributes();
|
||||||
|
for (EquipmentSlot slot : EquipmentSlot.values()) {
|
||||||
|
ItemStack stack = player.getItemBySlot(slot);
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Multimap<Holder<Attribute>, AttributeModifier> modifiers = HashMultimap.create();
|
||||||
|
stack.forEachModifier(slot, modifiers::put);
|
||||||
|
// Each modifier is removed and added again, so this is a no-op
|
||||||
|
// wherever one survived and a repair wherever one did not.
|
||||||
|
attributes.addTransientAttributeModifiers(modifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What one of this mod's attributes is worth on this entity, falling back
|
||||||
|
* to the equipment when the attribute map has lost it.
|
||||||
|
*
|
||||||
|
* <p>Only additive modifiers are counted, which is all this mod grants.
|
||||||
|
*/
|
||||||
|
static double value(LivingEntity entity, Holder<Attribute> attribute) {
|
||||||
|
AttributeInstance instance = entity.getAttribute(attribute);
|
||||||
|
double live = instance == null ? 0.0D : instance.getValue();
|
||||||
|
if (live > 0.0D) {
|
||||||
|
return live;
|
||||||
|
}
|
||||||
|
double[] worn = {0.0D};
|
||||||
|
for (EquipmentSlot slot : EquipmentSlot.values()) {
|
||||||
|
ItemStack stack = entity.getItemBySlot(slot);
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
stack.forEachModifier(slot, (holder, modifier) -> {
|
||||||
|
if (holder.value() == attribute.value()
|
||||||
|
&& modifier.operation() == AttributeModifier.Operation.ADD_VALUE) {
|
||||||
|
worn[0] += modifier.amount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return worn[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ package eu.abdelbaki.cmmodular;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.tags.DamageTypeTags;
|
import net.minecraft.tags.DamageTypeTags;
|
||||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
|
||||||
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
|
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
|
||||||
import net.neoforged.bus.api.IEventBus;
|
import net.neoforged.bus.api.IEventBus;
|
||||||
import net.neoforged.neoforge.common.NeoForge;
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
|
|
@ -47,8 +46,7 @@ public final class FireImmunity {
|
||||||
if (!event.getSource().is(DamageTypeTags.IS_FIRE)) {
|
if (!event.getSource().is(DamageTypeTags.IS_FIRE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AttributeInstance instance = event.getEntity().getAttribute(FIRE_IMMUNITY);
|
if (EquipmentAttributes.value(event.getEntity(), FIRE_IMMUNITY) >= 1.0D) {
|
||||||
if (instance != null && instance.getValue() >= 1.0D) {
|
|
||||||
// Put the wearer out as well, otherwise they burn harmlessly but
|
// Put the wearer out as well, otherwise they burn harmlessly but
|
||||||
// permanently, which reads as a bug.
|
// permanently, which reads as a bug.
|
||||||
event.getEntity().clearFire();
|
event.getEntity().clearFire();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package eu.abdelbaki.cmmodular;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.tags.TagKey;
|
||||||
|
import net.minecraft.world.damagesource.DamageTypes;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
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.RangedAttribute;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||||
|
import net.minecraft.world.phys.shapes.EntityCollisionContext;
|
||||||
|
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.registries.DeferredHolder;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deathworm chitin lets its bearer move through sand the way the worm it came
|
||||||
|
* off does.
|
||||||
|
*
|
||||||
|
* <p>Which blocks count is a tag rather than a list, so a pack can widen it:
|
||||||
|
* {@code cmmodular:sand_like} is the common {@code c:sands} and {@code c:gravels}
|
||||||
|
* plus the vanilla loose blocks by name, in case the common tags are not there.
|
||||||
|
*
|
||||||
|
* <p>Collision is not an attribute, an event or a property - it is asked of the
|
||||||
|
* block. So this is the one place the mod needs a mixin, and it is a small one:
|
||||||
|
* a sand block returns no collision shape to an entity carrying the attribute.
|
||||||
|
*
|
||||||
|
* <p>What it deliberately does <em>not</em> do is drop the bearer through the
|
||||||
|
* ground. A block entirely below their feet still holds them up, so a desert is
|
||||||
|
* still something to walk on and a dune is something to walk into: step into a
|
||||||
|
* dune and you keep the level you were at, jump and the sand under you turns
|
||||||
|
* solid again and puts you back on top. Without that rule the first sand block
|
||||||
|
* a wearer stood on would swallow them down to the sandstone and leave them
|
||||||
|
* there, which is a trap rather than an ability. Sand you are standing inside
|
||||||
|
* of cannot suffocate you either - that check is below.
|
||||||
|
*/
|
||||||
|
public final class SandPhasing {
|
||||||
|
private static final DeferredRegister<Attribute> ATTRIBUTES =
|
||||||
|
DeferredRegister.create(Registries.ATTRIBUTE, CMModular.MOD_ID);
|
||||||
|
|
||||||
|
/** At or above 1 the bearer passes through sand; chitin grants a whole one. */
|
||||||
|
public static final DeferredHolder<Attribute, Attribute> SAND_PHASING =
|
||||||
|
ATTRIBUTES.register("sand_phasing", id -> new RangedAttribute(
|
||||||
|
"attribute.cmmodular.sand_phasing", 0.0D, 0.0D, 1.0D).setSyncable(true));
|
||||||
|
|
||||||
|
/** The blocks that are loose enough to move through. */
|
||||||
|
public static final TagKey<Block> SAND_LIKE = TagKey.create(
|
||||||
|
Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(CMModular.MOD_ID, "sand_like"));
|
||||||
|
|
||||||
|
private SandPhasing() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register(IEventBus modBus) {
|
||||||
|
ATTRIBUTES.register(modBus);
|
||||||
|
modBus.addListener(SandPhasing::attachToEntities);
|
||||||
|
NeoForge.EVENT_BUS.addListener(SandPhasing::onIncomingDamage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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, SAND_PHASING));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this block should let this entity through. Called from the
|
||||||
|
* collision mixin for every block anything is about to collide with, so
|
||||||
|
* the cheap tests come first and the attribute is read straight off the
|
||||||
|
* entity rather than through {@link EquipmentAttributes}.
|
||||||
|
*/
|
||||||
|
public static boolean phasesThrough(BlockState state, BlockPos pos, CollisionContext context) {
|
||||||
|
if (!(context instanceof EntityCollisionContext entityContext)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Entity entity = entityContext.getEntity();
|
||||||
|
if (!(entity instanceof LivingEntity) || !state.is(SAND_LIKE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Anything wholly under the feet is floor, and floor stays solid.
|
||||||
|
if (pos.getY() + 1 <= entity.getBoundingBox().minY + 1.0E-7D) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return grants((LivingEntity) entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sand you are standing inside of is not sand that can crush you. */
|
||||||
|
private static void onIncomingDamage(LivingIncomingDamageEvent event) {
|
||||||
|
if (!event.getSource().is(DamageTypes.IN_WALL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LivingEntity entity = event.getEntity();
|
||||||
|
BlockPos eye = BlockPos.containing(entity.getEyePosition());
|
||||||
|
if (entity.level().getBlockState(eye).is(SAND_LIKE)
|
||||||
|
&& EquipmentAttributes.value(entity, SAND_PHASING) >= 1.0D) {
|
||||||
|
event.setCanceled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean grants(LivingEntity entity) {
|
||||||
|
AttributeInstance instance = entity.getAttribute(SAND_PHASING);
|
||||||
|
return instance != null && instance.getValue() >= 1.0D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package eu.abdelbaki.cmmodular;
|
||||||
|
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
|
||||||
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
|
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
|
||||||
import net.neoforged.bus.api.EventPriority;
|
import net.neoforged.bus.api.EventPriority;
|
||||||
import net.neoforged.bus.api.IEventBus;
|
import net.neoforged.bus.api.IEventBus;
|
||||||
|
|
@ -56,8 +55,7 @@ public final class VacuumSeal {
|
||||||
if (event.canBreathe()) {
|
if (event.canBreathe()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AttributeInstance instance = event.getEntity().getAttribute(VACUUM_SEAL);
|
if (EquipmentAttributes.value(event.getEntity(), VACUUM_SEAL) >= 1.0D) {
|
||||||
if (instance != null && instance.getValue() >= 1.0D) {
|
|
||||||
event.setCanBreathe(true);
|
event.setCanBreathe(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package eu.abdelbaki.cmmodular;
|
||||||
|
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.tags.FluidTags;
|
||||||
|
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||||
|
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
|
||||||
|
import net.neoforged.bus.api.EventPriority;
|
||||||
|
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.LivingBreatheEvent;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredHolder;
|
||||||
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* <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
|
||||||
|
* drowning. So this follows the pattern {@link FireImmunity} and
|
||||||
|
* {@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.
|
||||||
|
*
|
||||||
|
* <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
|
||||||
|
* a fang is a fang and not half a spacesuit.
|
||||||
|
*/
|
||||||
|
public final class WaterBreathing {
|
||||||
|
private static final DeferredRegister<Attribute> ATTRIBUTES =
|
||||||
|
DeferredRegister.create(Registries.ATTRIBUTE, CMModular.MOD_ID);
|
||||||
|
|
||||||
|
/** At or above 1 the bearer never drowns; each worn fang module is 0.5. */
|
||||||
|
public static final DeferredHolder<Attribute, Attribute> WATER_BREATHING =
|
||||||
|
ATTRIBUTES.register("water_breathing", id -> new RangedAttribute(
|
||||||
|
"attribute.cmmodular.water_breathing", 0.0D, 0.0D, 1.0D).setSyncable(true));
|
||||||
|
|
||||||
|
private WaterBreathing() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register(IEventBus modBus) {
|
||||||
|
ATTRIBUTES.register(modBus);
|
||||||
|
modBus.addListener(WaterBreathing::attachToEntities);
|
||||||
|
// Last, so it answers after whatever said there was no air.
|
||||||
|
NeoForge.EVENT_BUS.addListener(EventPriority.LOWEST, WaterBreathing::onBreathe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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_BREATHING));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onBreathe(LivingBreatheEvent event) {
|
||||||
|
if (event.canBreathe() || !event.getEntity().isEyeInFluid(FluidTags.WATER)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (EquipmentAttributes.value(event.getEntity(), WATER_BREATHING) >= 1.0D) {
|
||||||
|
event.setCanBreathe(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package eu.abdelbaki.cmmodular.mixin;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import eu.abdelbaki.cmmodular.SandPhasing;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.BlockGetter;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||||
|
import net.minecraft.world.phys.shapes.Shapes;
|
||||||
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The one mixin in this mod, and the only way in: whether a block collides is
|
||||||
|
* asked of the block, with the entity along for the ride in the collision
|
||||||
|
* context, and no event or property sits in between. Deathworm chitin answers
|
||||||
|
* for sand - see {@link SandPhasing}, which owns the decision so that this
|
||||||
|
* stays a two-line hook into someone else's class.
|
||||||
|
*/
|
||||||
|
@Mixin(BlockBehaviour.BlockStateBase.class)
|
||||||
|
public abstract class BlockStateBaseMixin {
|
||||||
|
@Inject(
|
||||||
|
method = "getCollisionShape(Lnet/minecraft/world/level/BlockGetter;"
|
||||||
|
+ "Lnet/minecraft/core/BlockPos;"
|
||||||
|
+ "Lnet/minecraft/world/phys/shapes/CollisionContext;)"
|
||||||
|
+ "Lnet/minecraft/world/phys/shapes/VoxelShape;",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true)
|
||||||
|
private void cmmodular$phaseThroughSand(BlockGetter level, BlockPos pos,
|
||||||
|
CollisionContext context,
|
||||||
|
CallbackInfoReturnable<VoxelShape> callback) {
|
||||||
|
if (SandPhasing.phasesThrough((BlockState) (Object) this, pos, context)) {
|
||||||
|
callback.setReturnValue(Shapes.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,11 @@ loaderVersion = "[4,)"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
issueTrackerURL = "https://github.com/themiro/create-mekanism-modular/issues"
|
issueTrackerURL = "https://github.com/themiro/create-mekanism-modular/issues"
|
||||||
|
|
||||||
|
# Collision is asked of the block, not raised as an event, so deathworm chitin's
|
||||||
|
# way through sand needs one small mixin. Nothing else here does.
|
||||||
|
[[mixins]]
|
||||||
|
config = "cmmodular.mixins.json"
|
||||||
|
|
||||||
[[mods]]
|
[[mods]]
|
||||||
modId = "${mod_id}"
|
modId = "${mod_id}"
|
||||||
version = "${mod_version}"
|
version = "${mod_version}"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,166 @@
|
||||||
{
|
{
|
||||||
"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_combat": "Underwater Fighting",
|
||||||
|
"attribute.cmmodular.sand_phasing": "Sand Phasing",
|
||||||
|
"item.cmmodular.arcana_core": "Arcana Core",
|
||||||
"miapi.module.cmmodular.blade.khopesh.name": "Khopesh Blade",
|
"miapi.module.cmmodular.blade.khopesh.name": "Khopesh Blade",
|
||||||
"miapi.module.cmmodular.pommel.gem_socket.name": "Socketed Pommel",
|
"miapi.module.cmmodular.pommel.gem_socket.name": "Socketed Pommel",
|
||||||
"miapi.module.cmmodular.pommel.great_socket.name": "Great Socketed Pommel",
|
"miapi.module.cmmodular.pommel.great_socket.name": "Great Socketed Pommel",
|
||||||
|
|
||||||
"miapi.module.cmmodular.armor.space.helmet.name": "Sealed Helmet",
|
"miapi.module.cmmodular.armor.space.helmet.name": "Sealed Helmet",
|
||||||
"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.module.cmmodular.gem.case_small.name": "Small Apothic Gem Case",
|
||||||
|
"miapi.module.cmmodular.gem.case_great.name": "Great 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",
|
||||||
|
"miapi.material.cmmodular.bone.witherbone": "Witherbone",
|
||||||
|
"miapi.material.cmmodular.crystal.arcana_core": "Arcana Core",
|
||||||
|
"miapi.material.cmmodular.crystal.arcane_essence": "Arcane",
|
||||||
|
"miapi.material.cmmodular.crystal.avaricite_shard": "Avaricite",
|
||||||
|
"miapi.material.cmmodular.crystal.bismuth_crystal": "Bismuth",
|
||||||
|
"miapi.material.cmmodular.crystal.blazing_crystal": "Blazing",
|
||||||
|
"miapi.material.cmmodular.crystal.certus_quartz": "Certus Quartz",
|
||||||
|
"miapi.material.cmmodular.crystal.charged_certus_quartz": "Charged Certus",
|
||||||
|
"miapi.material.cmmodular.crystal.cinder_essence": "Cinder",
|
||||||
|
"miapi.material.cmmodular.crystal.citrine_shard": "Citrine",
|
||||||
|
"miapi.material.cmmodular.crystal.dimensional_shard": "Dimensional",
|
||||||
|
"miapi.material.cmmodular.crystal.divinite_shard": "Divinite",
|
||||||
|
"miapi.material.cmmodular.crystal.dread_shard": "Dread",
|
||||||
|
"miapi.material.cmmodular.crystal.ender_crystal": "Ender Crystal",
|
||||||
|
"miapi.material.cmmodular.crystal.enticing_crystal": "Enticing Crystal",
|
||||||
|
"miapi.material.cmmodular.crystal.floralite_shard": "Floralite",
|
||||||
|
"miapi.material.cmmodular.crystal.fluix": "Fluix",
|
||||||
|
"miapi.material.cmmodular.crystal.fluorite": "Fluorite",
|
||||||
|
"miapi.material.cmmodular.crystal.frostbite_crystal": "Frostbite",
|
||||||
|
"miapi.material.cmmodular.crystal.glow_shard": "Glowshard",
|
||||||
|
"miapi.material.cmmodular.crystal.hemalite_shard": "Hemalite",
|
||||||
|
"miapi.material.cmmodular.crystal.ice_shard": "Ice",
|
||||||
|
"miapi.material.cmmodular.crystal.moonstone_shard": "Moonstone",
|
||||||
|
"miapi.material.cmmodular.crystal.onyx_shard": "Onyx",
|
||||||
|
"miapi.material.cmmodular.crystal.paltaeria_gem": "Paltaeria",
|
||||||
|
"miapi.material.cmmodular.crystal.pixie_dust": "Pixie Dust",
|
||||||
|
"miapi.material.cmmodular.crystal.prescient_crystal": "Prescient Crystal",
|
||||||
|
"miapi.material.cmmodular.crystal.pulsating_crystal": "Pulsating Crystal",
|
||||||
|
"miapi.material.cmmodular.crystal.sapphire": "Sapphire",
|
||||||
|
"miapi.material.cmmodular.crystal.shimmerstone_gem": "Shimmerstone",
|
||||||
|
"miapi.material.cmmodular.crystal.silicon_carbide": "Silicon Carbide",
|
||||||
|
"miapi.material.cmmodular.crystal.source_gem": "Source",
|
||||||
|
"miapi.material.cmmodular.crystal.spirit_attuned_gem": "Spirit Attuned",
|
||||||
|
"miapi.material.cmmodular.crystal.stratine_gem": "Stratine",
|
||||||
|
"miapi.material.cmmodular.crystal.sulfur": "Sulfur",
|
||||||
|
"miapi.material.cmmodular.crystal.topaz_shard": "Topaz",
|
||||||
|
"miapi.material.cmmodular.crystal.vibrant_crystal": "Vibrant Crystal",
|
||||||
|
"miapi.material.cmmodular.crystal.voidstone_shard": "Voidstone",
|
||||||
|
"miapi.material.cmmodular.crystal.volcanite_shard": "Volcanite",
|
||||||
|
"miapi.material.cmmodular.crystal.voltite_shard": "Voltite",
|
||||||
|
"miapi.material.cmmodular.crystal.weather_crystal": "Weather Crystal",
|
||||||
|
"miapi.material.cmmodular.fabric.troll_leather_forest": "Forest Troll Leather",
|
||||||
|
"miapi.material.cmmodular.fabric.troll_leather_frost": "Frost Troll Leather",
|
||||||
|
"miapi.material.cmmodular.fabric.troll_leather_mountain": "Mountain Troll Leather",
|
||||||
|
"miapi.material.cmmodular.fletching.amphithere_feather": "Amphithere",
|
||||||
|
"miapi.material.cmmodular.fletching.stymphalian_feather": "Stymphalian",
|
||||||
|
"miapi.material.cmmodular.metal.aluminum": "Aluminium",
|
||||||
|
"miapi.material.cmmodular.metal.amalgam": "Amalgam",
|
||||||
|
"miapi.material.cmmodular.metal.andesite_alloy": "Andesite Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.antimatter": "Antimatter",
|
||||||
|
"miapi.material.cmmodular.metal.aquarine_steel": "Aquarine Steel",
|
||||||
|
"miapi.material.cmmodular.metal.arcane_metal": "Arcane Metal",
|
||||||
|
"miapi.material.cmmodular.metal.atlantic_gold": "Atlantic Gold",
|
||||||
|
"miapi.material.cmmodular.metal.atomic_alloy": "Atomic Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.aviation_alloy": "Aviation Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.brass": "Brass",
|
||||||
|
"miapi.material.cmmodular.metal.bronze": "Bronze",
|
||||||
|
"miapi.material.cmmodular.metal.cast_iron": "Cast Iron",
|
||||||
|
"miapi.material.cmmodular.metal.cemented_carbide": "Cemented Carbide",
|
||||||
|
"miapi.material.cmmodular.metal.computronic_substrate": "Computronic",
|
||||||
|
"miapi.material.cmmodular.metal.conductive_alloy": "Conductive Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.constantan": "Constantan",
|
||||||
|
"miapi.material.cmmodular.metal.dark_steel": "Dark Steel",
|
||||||
|
"miapi.material.cmmodular.metal.dragonsteel_fire": "Fire Dragonsteel",
|
||||||
|
"miapi.material.cmmodular.metal.dragonsteel_ice": "Ice Dragonsteel",
|
||||||
|
"miapi.material.cmmodular.metal.dragonsteel_lightning": "Lightning Dragonsteel",
|
||||||
|
"miapi.material.cmmodular.metal.electrum": "Electrum",
|
||||||
|
"miapi.material.cmmodular.metal.end_steel": "End Steel",
|
||||||
|
"miapi.material.cmmodular.metal.energetic_alloy": "Energetic Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.ferdonzor": "Ferdonzor",
|
||||||
|
"miapi.material.cmmodular.metal.ghost_ingot": "Phantasmal",
|
||||||
|
"miapi.material.cmmodular.metal.hdpe": "HDPE",
|
||||||
|
"miapi.material.cmmodular.metal.hop_graphite": "Graphite",
|
||||||
|
"miapi.material.cmmodular.metal.iesnium": "Iesnium",
|
||||||
|
"miapi.material.cmmodular.metal.industrial_iron": "Industrial Iron",
|
||||||
|
"miapi.material.cmmodular.metal.infused_alloy": "Infused Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.lead": "Lead",
|
||||||
|
"miapi.material.cmmodular.metal.microlattice": "Microlattice",
|
||||||
|
"miapi.material.cmmodular.metal.mithril": "Mithril",
|
||||||
|
"miapi.material.cmmodular.metal.nickel": "Nickel",
|
||||||
|
"miapi.material.cmmodular.metal.osmium": "Osmium",
|
||||||
|
"miapi.material.cmmodular.metal.pink_slime": "Pink Slime",
|
||||||
|
"miapi.material.cmmodular.metal.platinum": "Platinum",
|
||||||
|
"miapi.material.cmmodular.metal.plutonium": "Plutonium",
|
||||||
|
"miapi.material.cmmodular.metal.polonium": "Polonium",
|
||||||
|
"miapi.material.cmmodular.metal.pulsating_alloy": "Pulsating Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.pyrium": "Pyrium",
|
||||||
|
"miapi.material.cmmodular.metal.redstone_alloy": "Redstone Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.refined_glowstone": "Refined Glowstone",
|
||||||
|
"miapi.material.cmmodular.metal.refined_obsidian": "Refined Obsidian",
|
||||||
|
"miapi.material.cmmodular.metal.refined_radiance": "Refined Radiance",
|
||||||
|
"miapi.material.cmmodular.metal.reinforced_alloy": "Reinforced Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.shadow_steel": "Shadow Steel",
|
||||||
|
"miapi.material.cmmodular.metal.silver": "Silver",
|
||||||
|
"miapi.material.cmmodular.metal.soularium": "Soularium",
|
||||||
|
"miapi.material.cmmodular.metal.steel": "Steel",
|
||||||
|
"miapi.material.cmmodular.metal.tin": "Tin",
|
||||||
|
"miapi.material.cmmodular.metal.titanium": "Titanium",
|
||||||
|
"miapi.material.cmmodular.metal.titanium_alloy": "Titanium Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.tungsten": "Tungsten",
|
||||||
|
"miapi.material.cmmodular.metal.uranium": "Uranium",
|
||||||
|
"miapi.material.cmmodular.metal.vibrant_alloy": "Vibrant Alloy",
|
||||||
|
"miapi.material.cmmodular.metal.zinc": "Zinc",
|
||||||
|
"miapi.material.cmmodular.scute.deathworm_chitin_red": "Red Chitin",
|
||||||
|
"miapi.material.cmmodular.scute.deathworm_chitin_white": "White Chitin",
|
||||||
|
"miapi.material.cmmodular.scute.deathworm_chitin_yellow": "Tan Chitin",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_amethyst": "Amethyst Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_black": "Black Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_blue": "Blue Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_bronze": "Bronze Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_copper": "Copper Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_electric": "Electric Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_gray": "Gray Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_green": "Green Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_red": "Red Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_sapphire": "Sapphire Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_silver": "Silver Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.dragonscales_white": "White Dragonscale",
|
||||||
|
"miapi.material.cmmodular.scute.sea_serpent_scales_blue": "Blue Serpent Scale",
|
||||||
|
"miapi.material.cmmodular.scute.sea_serpent_scales_bronze": "Bronze Serpent Scale",
|
||||||
|
"miapi.material.cmmodular.scute.sea_serpent_scales_deepblue": "Deep Blue Serpent Scale",
|
||||||
|
"miapi.material.cmmodular.scute.sea_serpent_scales_green": "Green Serpent Scale",
|
||||||
|
"miapi.material.cmmodular.scute.sea_serpent_scales_purple": "Purple Serpent Scale",
|
||||||
|
"miapi.material.cmmodular.scute.sea_serpent_scales_red": "Red Serpent Scale",
|
||||||
|
"miapi.material.cmmodular.scute.sea_serpent_scales_teal": "Teal Serpent Scale"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "cmmodular:items/arcana_core"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "cmmodular:items/gem_case"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "cmmodular:items/gem_case_great"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "cmmodular:items/gem_case_small"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 126 B |
|
After Width: | Height: | Size: 126 B |
|
After Width: | Height: | Size: 356 B |
|
After Width: | Height: | Size: 142 B |
|
After Width: | Height: | Size: 147 B |
|
After Width: | Height: | Size: 119 B |
|
Before Width: | Height: | Size: 128 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 152 B After Width: | Height: | Size: 163 B |
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "eu.abdelbaki.cmmodular.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_21",
|
||||||
|
"mixins": [
|
||||||
|
"BlockStateBaseMixin"
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"values": [
|
||||||
|
{ "id": "#c:sands", "required": false },
|
||||||
|
{ "id": "#c:gravels", "required": false },
|
||||||
|
{ "id": "#c:concrete_powders", "required": false },
|
||||||
|
"minecraft:sand",
|
||||||
|
"minecraft:red_sand",
|
||||||
|
"minecraft:suspicious_sand",
|
||||||
|
"minecraft:gravel",
|
||||||
|
"minecraft:suspicious_gravel",
|
||||||
|
"minecraft:soul_sand",
|
||||||
|
"minecraft:soul_soil"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Certus Quartz ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Charged Certus ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Fluix ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Aviation Alloy ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Sulfur ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem_melee",
|
"gem_melee",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Amalgam ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"gem_melee_medium"
|
||||||
|
],
|
||||||
|
"tag": [
|
||||||
|
"gem_armor",
|
||||||
|
"gem_armor_medium",
|
||||||
|
"gem_armor_generic",
|
||||||
|
"gem_armor_medium_generic",
|
||||||
|
"gem_melee",
|
||||||
|
"gem_melee_medium",
|
||||||
|
"gem_melee_generic",
|
||||||
|
"gem_melee_medium_generic"
|
||||||
|
],
|
||||||
|
"material_property": [
|
||||||
|
"default"
|
||||||
|
],
|
||||||
|
"priority": 0,
|
||||||
|
"repair_priority": 0,
|
||||||
|
"rarity": "rare",
|
||||||
|
"allowed_material": {
|
||||||
|
"allowedMaterials": [
|
||||||
|
"metal",
|
||||||
|
"crystal",
|
||||||
|
"glass",
|
||||||
|
"bone",
|
||||||
|
"stone"
|
||||||
|
],
|
||||||
|
"cost": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"comment": "The great socket takes a large gem and nothing else, so the case that goes in it has to be a large one too - `gem_melee_large` is the key the great pommel asks for, and a medium case does not answer to it.",
|
||||||
|
"display_name": "miapi.module.cmmodular.gem.case_great.name",
|
||||||
|
"model": {
|
||||||
|
"path": "miapi:models/item/armor/gems/large/[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_melee_large"
|
||||||
|
],
|
||||||
|
"tag": [
|
||||||
|
"gem_melee",
|
||||||
|
"gem_melee_large",
|
||||||
|
"gem_melee_generic",
|
||||||
|
"gem_melee_large_generic"
|
||||||
|
],
|
||||||
|
"material_property": [
|
||||||
|
"default"
|
||||||
|
],
|
||||||
|
"priority": 0,
|
||||||
|
"repair_priority": 0,
|
||||||
|
"rarity": "rare",
|
||||||
|
"allowed_material": {
|
||||||
|
"allowedMaterials": [
|
||||||
|
"metal",
|
||||||
|
"crystal",
|
||||||
|
"glass",
|
||||||
|
"bone",
|
||||||
|
"stone"
|
||||||
|
],
|
||||||
|
"cost": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"comment": "The small case, for the slots a small gemstone goes in - Arsenal's own socket pommel and the dual socket guard, which is two of them. Both size keys are named because a small slot is a small slot whether it was cut into a weapon or into armour, and a key nothing asks for costs nothing.",
|
||||||
|
"display_name": "miapi.module.cmmodular.gem.case_small.name",
|
||||||
|
"model": {
|
||||||
|
"path": "miapi:models/item/armor/gems/small/[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_melee_small",
|
||||||
|
"gem_armor_small"
|
||||||
|
],
|
||||||
|
"tag": [
|
||||||
|
"gem_melee",
|
||||||
|
"gem_melee_small",
|
||||||
|
"gem_melee_generic",
|
||||||
|
"gem_melee_small_generic",
|
||||||
|
"gem_armor",
|
||||||
|
"gem_armor_small",
|
||||||
|
"gem_armor_generic",
|
||||||
|
"gem_armor_small_generic"
|
||||||
|
],
|
||||||
|
"material_property": [
|
||||||
|
"default"
|
||||||
|
],
|
||||||
|
"priority": 0,
|
||||||
|
"repair_priority": 0,
|
||||||
|
"rarity": "rare",
|
||||||
|
"allowed_material": {
|
||||||
|
"allowedMaterials": [
|
||||||
|
"metal",
|
||||||
|
"crystal",
|
||||||
|
"glass",
|
||||||
|
"bone",
|
||||||
|
"stone"
|
||||||
|
],
|
||||||
|
"cost": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"pack": {
|
||||||
|
"description": "Apothic Gem Cases for Truly Modular",
|
||||||
|
"pack_format": 48
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"parent": "tm_armory:armor/heavy/arm_left",
|
||||||
|
"data": {
|
||||||
|
"merge": {
|
||||||
|
"module_tag": [
|
||||||
|
"socket_armor"
|
||||||
|
],
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"attribute": "minecraft:generic.armor",
|
||||||
|
"value": "-0.05",
|
||||||
|
"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": {
|
||||||
|
"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_arm_left"
|
||||||
|
],
|
||||||
|
"translationKey": "miapi.slot.gemstone.medium",
|
||||||
|
"transform": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"display_name": "miapi.module.cmmodular.armor.socket.arm_left.name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"parent": "tm_armory:armor/heavy/arm_right",
|
||||||
|
"data": {
|
||||||
|
"merge": {
|
||||||
|
"module_tag": [
|
||||||
|
"socket_armor"
|
||||||
|
],
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"attribute": "minecraft:generic.armor",
|
||||||
|
"value": "-0.05",
|
||||||
|
"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": {
|
||||||
|
"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_arm_right"
|
||||||
|
],
|
||||||
|
"translationKey": "miapi.slot.gemstone.medium",
|
||||||
|
"transform": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"display_name": "miapi.module.cmmodular.armor.socket.arm_right.name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"parent": "tm_armory:armor/heavy/leg_left",
|
||||||
|
"data": {
|
||||||
|
"merge": {
|
||||||
|
"module_tag": [
|
||||||
|
"socket_armor"
|
||||||
|
],
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"attribute": "minecraft:generic.armor",
|
||||||
|
"value": "-0.05",
|
||||||
|
"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_leg_left"
|
||||||
|
],
|
||||||
|
"translationKey": "miapi.slot.gemstone.medium",
|
||||||
|
"transform": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"display_name": "miapi.module.cmmodular.armor.socket.leg_left.name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"parent": "tm_armory:armor/heavy/leg_right",
|
||||||
|
"data": {
|
||||||
|
"merge": {
|
||||||
|
"module_tag": [
|
||||||
|
"socket_armor"
|
||||||
|
],
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"attribute": "minecraft:generic.armor",
|
||||||
|
"value": "-0.05",
|
||||||
|
"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": -2, "z": 0},
|
||||||
|
"scale": {"x": 1, "y": 1, "z": 1},
|
||||||
|
"origin": "right_leg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"slots": {
|
||||||
|
"gem": {
|
||||||
|
"allowed": [
|
||||||
|
"gem_armor_medium_leg_right"
|
||||||
|
],
|
||||||
|
"translationKey": "miapi.slot.gemstone.medium",
|
||||||
|
"transform": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"display_name": "miapi.module.cmmodular.armor.socket.leg_right.name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
"trim_mode": "ARMOR_LAYER_ONE",
|
"trim_mode": "ARMOR_LAYER_ONE",
|
||||||
"transform": {
|
"transform": {
|
||||||
"rotation": {"x": 0, "y": 0, "z": 0},
|
"rotation": {"x": 0, "y": 0, "z": 0},
|
||||||
"translation": {"x": 0, "y": 0, "z": 0},
|
"translation": {"x": 0, "y": 4.1387, "z": 0},
|
||||||
"scale": {"x": 1, "y": 1, "z": 1},
|
"scale": {"x": 1, "y": 1, "z": 1},
|
||||||
"origin": "head"
|
"origin": "head"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
"trim_mode": "ARMOR_LAYER_ONE",
|
"trim_mode": "ARMOR_LAYER_ONE",
|
||||||
"transform": {
|
"transform": {
|
||||||
"rotation": {"x": 0, "y": 0, "z": 0},
|
"rotation": {"x": 0, "y": 0, "z": 0},
|
||||||
"translation": {"x": 0, "y": 0, "z": 0},
|
"translation": {"x": 0, "y": -2.1901, "z": 0},
|
||||||
"scale": {"x": 1, "y": 1, "z": 1},
|
"scale": {"x": 1, "y": 1, "z": 1},
|
||||||
"origin": "body"
|
"origin": "body"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Source ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Titanium ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Titanium Alloy ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Andesite Alloy ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal",
|
"metal",
|
||||||
"stone"
|
"stone"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Brass ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Refined Radiance ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Shadow Steel ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Zinc ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Industrial Iron ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Platinum ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Avaricite ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Divinite ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Floralite ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Hemalite ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Ice ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Voidstone ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Volcanite ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Voltite ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Ender Crystal ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Enticing Crystal ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Prescient Crystal ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Pulsating Crystal ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Vibrant Crystal ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Weather Crystal ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem",
|
"gem",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Conductive Alloy ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Dark Steel ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "End Steel ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Energetic Alloy ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Pulsating Alloy ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Redstone Alloy ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Soularium ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Vibrant Alloy ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"metal"
|
"metal"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
{
|
{
|
||||||
"translation": "Dragonbone ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"bone"
|
"bone"
|
||||||
],
|
],
|
||||||
|
"hidden_groups": [
|
||||||
|
"gem_armor"
|
||||||
|
],
|
||||||
"icon": {
|
"icon": {
|
||||||
"type": "item",
|
"type": "item",
|
||||||
"item": "iceandfire:dragonbone"
|
"item": "iceandfire:dragonbone"
|
||||||
|
|
@ -37,6 +39,11 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"head": {
|
"head": {
|
||||||
"fracturing": "10"
|
"fracturing": "10"
|
||||||
|
},
|
||||||
|
"handheld": {
|
||||||
|
"fake_enchants": {
|
||||||
|
"minecraft:fire_aspect": 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Talon ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"bone"
|
"bone"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
"bone"
|
||||||
|
],
|
||||||
|
"hidden_groups": [
|
||||||
|
"gem_armor"
|
||||||
|
],
|
||||||
|
"icon": {
|
||||||
|
"type": "item",
|
||||||
|
"item": "iceandfire:sea_serpent_fang"
|
||||||
|
},
|
||||||
|
"hardness": 6.0,
|
||||||
|
"density": 3.0,
|
||||||
|
"flexibility": 4,
|
||||||
|
"durability": 760,
|
||||||
|
"enchantability": 16,
|
||||||
|
"toughness": 2,
|
||||||
|
"tier": 4,
|
||||||
|
"mining_speed": 7,
|
||||||
|
"mining_level": "minecraft:incorrect_for_diamond_tool",
|
||||||
|
"armor_durability": 26,
|
||||||
|
"color": "C8C8C9",
|
||||||
|
"color_palette": {
|
||||||
|
"type": "grayscale_map",
|
||||||
|
"colors": {
|
||||||
|
"24": "12131d",
|
||||||
|
"68": "222538",
|
||||||
|
"107": "525f68",
|
||||||
|
"150": "525f68",
|
||||||
|
"190": "8c8c8c",
|
||||||
|
"216": "c8c8c9",
|
||||||
|
"255": "e2fffa"
|
||||||
|
},
|
||||||
|
"filler": "interpolate"
|
||||||
|
},
|
||||||
|
"textures": [
|
||||||
|
"rough",
|
||||||
|
"shiny"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"default": {
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"attribute": "cmmodular:water_breathing",
|
||||||
|
"value": "0.5",
|
||||||
|
"operation": "+",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"boot": {
|
||||||
|
"fake_enchants": {
|
||||||
|
"minecraft:depth_strider": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item": "iceandfire:sea_serpent_fang",
|
||||||
|
"value": 1.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Witherbone ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"bone"
|
"bone"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Dread ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"metal",
|
"metal",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"translation": "Pixie Dust ",
|
|
||||||
"groups": [
|
"groups": [
|
||||||
"crystal",
|
"crystal",
|
||||||
"gem_melee",
|
"gem_melee",
|
||||||
|
|
|
||||||