-
Notifications
You must be signed in to change notification settings - Fork 50
Magic
The Magic System in CustomNPC+ defines elemental/magical damage types, their interactions, and how they integrate with combat. Magics can be assigned to NPCs, Players, and Abilities to create a rock-paper-scissors style damage system with visual cycle diagrams.
-
Magics: Named damage types (e.g., Fire, Water, Arcane) with a color, icon, and interaction modifiers against other magics.
-
Magic Cycles: Visual groupings of magics displayed as diagrams (circular, tree, chart, etc.) that show how magics relate to each other.
-
Magic Book: An in-game item that lets players browse magic cycles and view interaction diagrams.
-
Magic Data: Per-entity (NPC or Player) magic assignments with damage bonuses and split percentages that feed into the combat damage pipeline.
On first world load, 8 default magics are created and organized into the "Elementa Cycle":
| Magic | Color |
|---|---|
| Earth | Green (0x00DD00) |
| Water | Yellow (0xF2DD00) |
| Fire | Red (0xDD0000) |
| Air | Red (0xDD0000) |
| Magic | Color |
|---|---|
| Dark | Red (0xDD0000) |
| Holy | Red (0xDD0000) |
| Nature | Red (0xDD0000) |
| Arcane | Red (0xDD0000) |
Note: All magics, names, colors, icons, and interactions are fully customizable. The defaults are just a starting point.
Interactions define how one magic type performs against another. Each interaction is a percentage modifier applied as a damage multiplier.
-
Positive interaction (+50%): Damage is multiplied by
1.50— the attacker's magic is strong against the defender's. -
Negative interaction (-50%): Damage is multiplied by
0.50— the attacker's magic is weak against the defender's.
Inner Circle (±50%):
| Attacker | Defender | Modifier |
|---|---|---|
| Earth | Air | -50% |
| Air | Earth | +50% |
| Water | Earth | -50% |
| Earth | Water | +50% |
| Fire | Water | -50% |
| Water | Fire | +50% |
| Air | Fire | -50% |
| Fire | Air | +50% |
Outer Circle (±50%):
| Attacker | Defender | Modifier |
|---|---|---|
| Dark | Nature | -50% |
| Nature | Dark | +50% |
| Nature | Holy | -50% |
| Holy | Nature | +50% |
| Holy | Arcane | -50% |
| Arcane | Holy | +50% |
| Arcane | Dark | -50% |
| Dark | Arcane | +50% |
Cross-Circle (±25%):
| Attacker | Defender | Modifier |
|---|---|---|
| Earth | Nature | -25% |
| Nature | Earth | +25% |
| Water | Holy | -25% |
| Holy | Water | +25% |
| Fire | Arcane | -25% |
| Arcane | Fire | +25% |
| Air | Dark | -25% |
| Dark | Air | +25% |
| Dark | Fire | -25% |
| Fire | Dark | +25% |
| Nature | Air | -25% |
| Air | Nature | +25% |
| Holy | Earth | -25% |
| Earth | Holy | +25% |
| Arcane | Water | -25% |
| Water | Arcane | +25% |
Interactions stack multiplicatively. If an attacker's Fire magic has interactions against multiple magics the defender possesses, all modifiers are applied:
damage × (1 + mod₁) × (1 + mod₂) × ...
Each magic has the following configurable properties:
| Property | Description |
|---|---|
| Name | Unique internal identifier (auto-formatted to title case) |
| Display Name | Cosmetic name shown in GUIs (supports & color codes) |
| Color | Hex color value for visual representation |
| Icon Type |
Base (texture path) or Item (uses an ItemStack's texture) |
| Icon Texture | Texture path (format: modID:itemName) when using Base type |
| Item | ItemStack used as icon when using Item type |
| Interactions | Map of other magic IDs to percentage modifiers (-1.0 to +1.0) |
Magic Cycles are visual groupings that organize magics into diagrams. They are used in the Magic Book and the management GUI.
| Property | Description |
|---|---|
| Name | Unique internal identifier |
| Display Name | Cosmetic name (supports & color codes) |
| Layout | Diagram layout type (see below) |
| Associations | Which magics belong to this cycle, with index and priority values |
Each magic in a cycle has:
| Property | Description |
|---|---|
| Index | Position group index (used by auto-layout and manual layout) |
| Priority | Ordering priority within the index group |
| Layout | ID | Description |
|---|---|---|
| Circular | 0 | Auto-arranged circle |
| Square | 1 | Auto-arranged square |
| Tree | 2 | Auto-arranged tree |
| Generated | 3 | Auto-generated arrangement |
| Circular Manual | 4 | Circle layout with manual index/priority positioning |
| Square Manual | 5 | Square layout with manual positioning |
| Tree Manual | 6 | Tree layout with manual positioning |
| Chart | 7 | Chart-style layout |
| Manual | 8 | Fully manual x/y positioning |
Manual variants (IDs 4–6) use the index and priority values to control exact placement within the layout shape. The default "Elementa Cycle" uses Circular Manual.
The Magic Book is an in-game item (ItemNpcTool with damage value 2) that lets players browse magic cycles.
- Hold the Magic Book item in hand
- Right-click to open the Magic Book GUI
- Select a magic cycle from the left scroll list
- View the diagram on the right, showing all associated magics and their interactions
- Interaction arrows display percentage values (e.g.,
+50%,-25%)
The diagram renders magic icons connected by curved arrows, with interaction percentages displayed along each connection.
NPCs can have magics assigned through the NPC editor. Magic data on an NPC determines what elemental damage they deal and what elemental properties they have for interaction calculations.
- Open the NPC editor with the Wand tool
- Navigate to Advanced → Magic
- Select a magic from the Available Magics list (left)
- Click > to assign it to the NPC (right)
- Configure the Split and Bonus Damage values
| Property | Description |
|---|---|
| Split | Percentage of the NPC's physical damage allocated to this magic type (0.0–1.0) |
| Bonus Damage | Flat damage added on top of the split allocation |
- The Standardize button evenly distributes the split value (1.0) across all assigned magics.
- If splits total less than 1.0, the remaining portion stays as untyped physical damage.
- If splits total more than 1.0, extra damage is effectively created for those magic types.
Players have their own MagicData that works identically to NPC magic data. Player magic data determines:
- What elemental damage the player deals in combat
- What elemental affinities the player has for defensive interaction calculations
Player magic data is stored in PlayerData and persists across sessions.
Abilities can have their own magic data, which overrides the caster's magic data for that ability's damage.
- Open an ability in the Ability editor
- Click the Magic(s) button
- Select magics from the available list and assign them with split values
- Use Standardize to evenly distribute splits if desired
When an ability deals damage, the system resolves magic data in this order:
- Ability's own magic data — used if the ability has any magics assigned
- Caster's magic data — used as fallback if the ability has no magics
- No magic — if neither has magic data, damage is pure physical
If an ability has magic data assigned, the label shows "Magic(s): X" (count). If it relies on the caster's data, it shows "Uses Caster Magic".
Magic integrates deeply with the attribute and combat system. Here's how magic damage flows:
1. Base physical damage determined
2. Allocate into magic types (split × physical + bonus)
3. Leftover physical = physical × (1 - totalSplit)
4. Add attribute-based magic damage & boosts (if Attributes enabled)
5. Apply magic interactions vs defender's magics
6. Apply defender's magic defense & resistance (if Attributes enabled)
7. Sum adjusted magic damage + leftover physical
For each assigned magic:
allocated = (physicalDamage × split) + bonusDamage
Leftover physical:
leftover = physicalDamage × max(0, 1 - totalSplit)
For each attacker magic vs each defender magic:
multiplier = 1.0
for each defender magic that has an interaction:
multiplier *= (1 + interactionValue)
allocatedDamage *= multiplier
For each magic type:
totalDefense = magicDefense × (1 + magicResistance / 100)
finalDamage = max(0, allocatedDamage - totalDefense)
When the Attribute System is enabled, these per-magic attributes affect combat:
| Attribute | Type | Description |
|---|---|---|
| Magic Damage | Offensive (flat) | Additional flat magic damage per type |
| Magic Boost | Offensive (%) | Percentage multiplier on magic damage per type |
| Magic Defense | Defensive (flat) | Flat damage reduction per magic type |
| Magic Resistance | Defensive (%) | Scales magic defense effectiveness per type |
The global magic editor lets you create, edit, and delete magics and cycles.
- Use the Wand tool to open an NPC
- Navigate to Global → Magic (requires
customnpcs.global.magicpermission)
The editor has two view modes, toggled with the View button:
| Mode | Description |
|---|---|
| Global View | Flat list of all magics |
| Cycle View | Left panel shows cycles, right panel shows magics in the selected cycle |
- Click Add in the magic list
- Set the Name (unique internal ID) and Display Name
- Choose a Color and Icon (Base texture or Item)
- Add Interactions to other magics with percentage values
- Click Done to save
- Switch to Cycle View
- Click Add Cycle
- Set the Name, Display Name, and Layout type
- Add magics to the cycle using the > / >> buttons
- Set Index and Priority for manual layouts
- Click Done to save
- In Cycle View, select a cycle and click View to open the diagram viewer
- The diagram shows all magics in the cycle with interaction arrows and percentages
The Magic System is accessible through the scripting API via the IMagicHandler.
var magicHandler = API.getMagicHandler();| Method | Returns | Description |
|---|---|---|
getMagic(magicId) |
IMagic |
Get a magic definition by ID |
getCycle(cycleId) |
IMagicCycle |
Get a magic cycle by ID |
addMagicToCycle(magicId, cycleId, index, priority) |
void |
Associate a magic with a cycle |
removeMagicFromCycle(magicId, cycleId) |
void |
Remove a magic from a cycle |
| Method | Returns | Description |
|---|---|---|
getId() |
int |
Magic's unique ID |
getName() / setName(name)
|
String |
Internal name |
getDisplayName() / setDisplayName(name)
|
String |
Display name (supports color codes) |
getColor() / setColor(color)
|
int |
Hex color value |
hasInteraction(magicId) |
boolean |
Check if interaction exists |
getInteraction(magicId, defaultValue) |
float |
Get interaction modifier |
setInteraction(magicId, value) |
void |
Set interaction modifier |
save() |
void |
Persist changes |
| Method | Returns | Description |
|---|---|---|
getId() |
int |
Cycle's unique ID |
getName() / setName(name)
|
String |
Internal name |
getDisplayName() / setDisplayName(name)
|
String |
Display name |
getLayoutType() / setLayoutType(layout)
|
int |
Layout enum ordinal (0–8) |
Available on player.getMagicData() and npc.getMagicData():
| Method | Returns | Description |
|---|---|---|
addMagic(id, damage, split) |
void |
Add a magic with bonus damage and split |
removeMagic(id) |
void |
Remove a magic |
hasMagic(id) |
boolean |
Check if entity has a magic |
getMagicDamage(id) |
float |
Get flat bonus damage for a magic |
getMagicSplit(id) |
float |
Get split percentage for a magic |
isEmpty() |
boolean |
Check if no magics assigned |
clear() |
void |
Remove all magics |
var magicData = player.getMagicData();
// Fire magic ID 3, bonus damage 2.0, 50% split
magicData.addMagic(3, 2.0, 0.5);var handler = API.getMagicHandler();
var fire = handler.getMagic(3); // Fire
var water = handler.getMagic(2); // Water
// Fire is weak against Water (-50%)
fire.setInteraction(water.getId(), -0.50);
// Water is strong against Fire (+50%)
water.setInteraction(fire.getId(), 0.50);
fire.save();
water.save();| Data | Location |
|---|---|
| Magic & Cycle Definitions |
{world}/customnpcs/magic.dat (GZIP compressed NBT) |
| NPC Magic Data | Stored in each NPC's DataStats NBT |
| Player Magic Data | Stored in each player's PlayerData NBT |
A backup (magic.dat_old) is created on every save using atomic file swapping.
| Permission | Description |
|---|---|
customnpcs.global.magic |
Create/edit/delete magic definitions and cycles (Global Editor) |
customnpcs.npc.advanced.magic |
Edit NPC magic configuration (NPC Editor) |
customnpcs.kamkeel.* |
Universal access to all /kam commands |