mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 22:27:32 +00:00
Cleanup Damage Handling Logic for CNPC+ and DBC Addon for better readability / context. Added Magic Data Support to Abilities
This commit is contained in:
1 parent
c3096b320e
commit
d9f100d245
12 files changed
+604
-13
No files matched your search
@@ -22,6 +22,7 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityEnergyBarrier;
|
||||
import kamkeel.npcs.entity.EntityEnergyDome;
|
||||
import kamkeel.npcs.entity.EntityEnergyPanel;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
import kamkeel.npcs.util.FileNameHelper;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -37,6 +38,7 @@ import net.minecraft.util.StatCollector;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.NpcDamageSource;
|
||||
import noppes.npcs.config.ConfigMain;
|
||||
import noppes.npcs.api.INbt;
|
||||
import noppes.npcs.api.ability.IAbility;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
|
||||
@@ -49,11 +51,18 @@ import noppes.npcs.controllers.data.AbilityScript;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
import noppes.npcs.controllers.MagicController;
|
||||
import noppes.npcs.controllers.data.Magic;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.controllers.data.MagicEntry;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityMagic;
|
||||
import noppes.npcs.scripted.event.AbilityEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class Ability implements IAbility, IAbilityAction {
|
||||
|
||||
@@ -117,6 +126,9 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
// Custom data for external mods
|
||||
protected NBTTagCompound customData = new NBTTagCompound();
|
||||
|
||||
// Magic data — defines magic types and damage splits for this ability
|
||||
protected MagicData magicData = new MagicData();
|
||||
|
||||
// User type restriction
|
||||
protected UserType allowedBy = UserType.BOTH;
|
||||
|
||||
@@ -281,6 +293,15 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this ability type uses magic data.
|
||||
* By default, any ability that deals damage also has magic.
|
||||
* Override in subclasses like Barrier that need magic but don't deal direct damage.
|
||||
*/
|
||||
public boolean hasMagic() {
|
||||
return hasDamage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to provide variant templates for this ability type.
|
||||
* When a user creates this ability type, they will be shown a selection dialog
|
||||
@@ -472,13 +493,22 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
this, caster, hitEntity, damage, knockback, knockbackUp,
|
||||
knockbackDirX, knockbackDirZ, 1.0f);
|
||||
if (!handled) {
|
||||
float finalDamage = damage;
|
||||
|
||||
// Apply magic pipeline for player and NPC casters
|
||||
MagicData resolved = resolveMagicData(caster);
|
||||
if (resolved != null && !resolved.isEmpty()) {
|
||||
finalDamage = AttributeAttackUtil.calculateAbilityDamage(
|
||||
caster, hitEntity, damage, resolved);
|
||||
}
|
||||
|
||||
// Default damage path
|
||||
if (caster instanceof EntityNPCInterface) {
|
||||
hitEntity.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), damage);
|
||||
hitEntity.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), finalDamage);
|
||||
} else if (caster instanceof EntityPlayer) {
|
||||
hitEntity.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) caster), damage);
|
||||
hitEntity.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) caster), finalDamage);
|
||||
} else {
|
||||
hitEntity.attackEntityFrom(DamageSource.causeMobDamage(caster), damage);
|
||||
hitEntity.attackEntityFrom(DamageSource.causeMobDamage(caster), finalDamage);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -737,6 +767,41 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Magic section ────────────────────────────────────────────
|
||||
if (hasMagic()) {
|
||||
defs.add(FieldDef.section("ability.section.magic").tab("General"));
|
||||
|
||||
defs.add(FieldDef.labelField("ability.magic.note", () -> {
|
||||
if (magicData.isEmpty()) {
|
||||
return StatCollector.translateToLocal("ability.magic.usesCaster");
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<Integer, MagicEntry> entry : magicData.getMagics().entrySet()) {
|
||||
Magic magic = MagicController.getInstance().getMagic(entry.getKey());
|
||||
if (magic != null) {
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(magic.getDisplayName()).append(": ")
|
||||
.append(Math.round(entry.getValue().split * 100)).append("%");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}).tab("General"));
|
||||
|
||||
defs.add(FieldDef.subGuiField("ability.magic.editor",
|
||||
() -> new SubGuiAbilityMagic(magicData.copy()),
|
||||
gui -> {
|
||||
MagicData result = ((SubGuiAbilityMagic) gui).magicData;
|
||||
magicData.getMagics().clear();
|
||||
for (Map.Entry<Integer, MagicEntry> e : result.getMagics().entrySet()) {
|
||||
magicData.getMagics().put(e.getKey(), e.getValue());
|
||||
}
|
||||
})
|
||||
.buttonLabel(() -> magicData.isEmpty()
|
||||
? StatCollector.translateToLocal("ability.magic.usesCaster")
|
||||
: magicData.getMagics().size() + " Magic(s)")
|
||||
.tab("General"));
|
||||
}
|
||||
|
||||
// ── Target tab ───────────────────────────────────────────────
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.minRange", () -> (int) getMinRange(), v -> setMinRange(v)).range(0, 100),
|
||||
@@ -1410,6 +1475,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
nbt.setString("telegraphType", telegraphType.name());
|
||||
nbt.setFloat("telegraphHeightOffset", telegraphHeightOffset);
|
||||
nbt.setTag("customData", (NBTTagCompound) customData.copy());
|
||||
magicData.writeToNBT(nbt);
|
||||
nbt.setInteger("allowedBy", allowedBy.ordinal());
|
||||
nbt.setBoolean("ignoreCooldown", ignoreCooldown);
|
||||
nbt.setBoolean("perAbilityCooldown", perAbilityCooldown);
|
||||
@@ -1508,6 +1574,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
}
|
||||
telegraphHeightOffset = nbt.getFloat("telegraphHeightOffset");
|
||||
customData = (NBTTagCompound) nbt.getCompoundTag("customData").copy();
|
||||
magicData.readToNBT(nbt);
|
||||
allowedBy = UserType.fromOrdinal(nbt.getInteger("allowedBy"));
|
||||
ignoreCooldown = nbt.getBoolean("ignoreCooldown");
|
||||
perAbilityCooldown = nbt.getBoolean("perAbilityCooldown");
|
||||
@@ -2205,6 +2272,35 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
return customData;
|
||||
}
|
||||
|
||||
public MagicData getMagicData() {
|
||||
return magicData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve magic data for this ability + caster combination.
|
||||
* If the ability has its own magic data, use that (override).
|
||||
* Otherwise, fall back to the caster's magic data (player or NPC).
|
||||
*
|
||||
* @return Resolved MagicData, or null if neither has magic
|
||||
*/
|
||||
public MagicData resolveMagicData(EntityLivingBase caster) {
|
||||
if (!this.magicData.isEmpty()) {
|
||||
return this.magicData;
|
||||
}
|
||||
if (caster instanceof EntityPlayer) {
|
||||
PlayerData data = PlayerData.get((EntityPlayer) caster);
|
||||
if (data != null && data.magicData != null && !data.magicData.isEmpty()) {
|
||||
return data.magicData;
|
||||
}
|
||||
} else if (caster instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface npc = (EntityNPCInterface) caster;
|
||||
if (npc.stats != null && npc.stats.magicData != null && !npc.stats.magicData.isEmpty()) {
|
||||
return npc.stats.magicData;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<AbilityCondition> getConditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -69,6 +70,11 @@ public abstract class AbilityBarrier extends AbilityEnergy {
|
||||
return false; // Barriers are defensive
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMagic() {
|
||||
return true; // Barriers use magic for defense interactions
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowFreeOnCast() {
|
||||
return true;
|
||||
@@ -86,6 +92,7 @@ public abstract class AbilityBarrier extends AbilityEnergy {
|
||||
if (tick == 1) {
|
||||
barrierEntity = createBarrierEntity(caster, target);
|
||||
if (barrierEntity != null) {
|
||||
inheritMagicData(barrierEntity, caster);
|
||||
applyBarrierHealthModifiers(caster);
|
||||
barrierEntity.setupCharging(getWindUpTicks());
|
||||
spawnAbilityEntity(barrierEntity);
|
||||
@@ -102,12 +109,20 @@ public abstract class AbilityBarrier extends AbilityEnergy {
|
||||
// No windup or entity died — create fresh
|
||||
barrierEntity = createBarrierEntity(caster, target);
|
||||
if (barrierEntity != null) {
|
||||
inheritMagicData(barrierEntity, caster);
|
||||
applyBarrierHealthModifiers(caster);
|
||||
spawnAbilityEntity(barrierEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void inheritMagicData(EntityEnergyBarrier entity, EntityLivingBase caster) {
|
||||
MagicData resolved = resolveMagicData(caster);
|
||||
if (resolved != null) {
|
||||
entity.setMagicData(resolved.copy());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) {
|
||||
if (barrierEntity == null || barrierEntity.isDead) {
|
||||
|
||||
+5
@@ -25,6 +25,7 @@ import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.Vec3;
|
||||
import noppes.npcs.api.ability.type.IAbilityEnergyProjectile;
|
||||
import noppes.npcs.client.gui.builder.FieldDef;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -214,6 +215,10 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
entity.setEffects(this.effects);
|
||||
entity.setIgnoreIFrames(this.isIgnoreIFrames());
|
||||
entity.setSourceAbility(this);
|
||||
MagicData resolvedMagic = resolveMagicData(caster);
|
||||
if (resolvedMagic != null) {
|
||||
entity.setMagicData(resolvedMagic.copy());
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
|
||||
/**
|
||||
* Universal base class for all energy ability entities (Projectiles, Barriers, Sweeper).
|
||||
@@ -51,6 +52,9 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
*/
|
||||
protected NBTTagCompound customDamageData = null;
|
||||
|
||||
/** Magic data for this entity — defines magic types for outgoing damage or barrier defense. */
|
||||
protected MagicData magicData = new MagicData();
|
||||
|
||||
protected boolean ignoreIFrames = false;
|
||||
protected boolean previewMode = false;
|
||||
protected EntityLivingBase previewOwner = null;
|
||||
@@ -159,6 +163,14 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
this.customDamageData = data;
|
||||
}
|
||||
|
||||
public MagicData getMagicData() {
|
||||
return magicData;
|
||||
}
|
||||
|
||||
public void setMagicData(MagicData data) {
|
||||
this.magicData = data != null ? data : new MagicData();
|
||||
}
|
||||
|
||||
public boolean isIgnoreIFrames() {
|
||||
return ignoreIFrames;
|
||||
}
|
||||
@@ -346,6 +358,7 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
if (customDamageData != null) {
|
||||
nbt.setTag("CustomDamageData", customDamageData);
|
||||
}
|
||||
magicData.writeToNBT(nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,6 +373,7 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit
|
||||
if (nbt.hasKey("CustomDamageData")) {
|
||||
this.customDamageData = nbt.getCompoundTag("CustomDamageData");
|
||||
}
|
||||
magicData.readToNBT(nbt);
|
||||
}
|
||||
|
||||
// ==================== SPAWN DATA ====================
|
||||
|
||||
@@ -2,6 +2,7 @@ package kamkeel.npcs.entity;
|
||||
|
||||
import kamkeel.npcs.controllers.data.ability.util.AbilityTargetHelper;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
@@ -12,6 +13,8 @@ import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.CustomNpcs;
|
||||
import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
@@ -248,6 +251,13 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
|
||||
float multiplier = barrierData.getMultiplier(typeId);
|
||||
float finalDamage = damage * multiplier;
|
||||
|
||||
// Apply magic interactions if projectile is a CNPC+ energy entity
|
||||
if (projectileEntity instanceof EntityEnergyAbility) {
|
||||
float magicMultiplier = AttributeAttackUtil.calculateMagicInteractionMultiplier(
|
||||
((EntityEnergyAbility) projectileEntity).getMagicData(), this.magicData);
|
||||
finalDamage *= magicMultiplier;
|
||||
}
|
||||
|
||||
if (!worldObj.isRemote) {
|
||||
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, finalDamage);
|
||||
if (eventDamage < 0) {
|
||||
@@ -329,6 +339,11 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
|
||||
float multiplier = barrierData.getMultiplier(typeId);
|
||||
float damage = baseDamage * multiplier;
|
||||
|
||||
// Apply magic interactions (e.g., Water projectile vs Fire barrier)
|
||||
float magicMultiplier = AttributeAttackUtil.calculateMagicInteractionMultiplier(
|
||||
projectile.getMagicData(), this.magicData);
|
||||
damage *= magicMultiplier;
|
||||
|
||||
// Fire hit event (may cancel or modify damage)
|
||||
if (!worldObj.isRemote) {
|
||||
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, projectile, damage);
|
||||
@@ -419,6 +434,24 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility {
|
||||
|
||||
float damage = amount * barrierData.meleeDamageMultiplier;
|
||||
|
||||
// Apply magic interactions from attacker's magic vs barrier's magic
|
||||
Entity attacker = source.getEntity();
|
||||
if (attacker != null) {
|
||||
MagicData attackerMagic = null;
|
||||
if (attacker instanceof EntityPlayer) {
|
||||
PlayerData data = PlayerData.get((EntityPlayer) attacker);
|
||||
if (data != null) attackerMagic = data.magicData;
|
||||
} else if (attacker instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface npc = (EntityNPCInterface) attacker;
|
||||
if (npc.stats != null) attackerMagic = npc.stats.magicData;
|
||||
}
|
||||
if (attackerMagic != null) {
|
||||
float magicMultiplier = AttributeAttackUtil.calculateMagicInteractionMultiplier(
|
||||
attackerMagic, this.magicData);
|
||||
damage *= magicMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire hit event with null projectile (melee hit)
|
||||
float eventDamage = EventHooks.onEnergyBarrierHit(this, ownerEntityId, null, damage);
|
||||
if (eventDamage < 0) return false;
|
||||
|
||||
@@ -18,6 +18,7 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData;
|
||||
import kamkeel.npcs.network.packets.data.energy.ProjectileReflectPacket;
|
||||
import kamkeel.npcs.network.packets.data.energyexplosion.EnergyExplosionSpawnPacket;
|
||||
import kamkeel.npcs.util.AnchorPointHelper;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
import kamkeel.npcs.util.CNPCDebug;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
@@ -39,6 +40,7 @@ import noppes.npcs.EventHooks;
|
||||
import noppes.npcs.NpcDamageSource;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.config.ConfigEnergy;
|
||||
import noppes.npcs.config.ConfigMain;
|
||||
import noppes.npcs.controllers.PartyController;
|
||||
import noppes.npcs.controllers.data.Party;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
@@ -940,15 +942,23 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames);
|
||||
try {
|
||||
if (!handled) {
|
||||
float finalDmg = dmg;
|
||||
|
||||
// Apply magic pipeline for player and NPC casters
|
||||
if (this.magicData != null && !this.magicData.isEmpty() && owner instanceof EntityLivingBase) {
|
||||
finalDmg = AttributeAttackUtil.calculateAbilityDamage(
|
||||
(EntityLivingBase) owner, target, dmg, this.magicData);
|
||||
}
|
||||
|
||||
// Default damage path
|
||||
if (owner instanceof EntityNPCInterface) {
|
||||
defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg);
|
||||
defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), finalDmg);
|
||||
} else if (owner instanceof EntityPlayer) {
|
||||
defaultDamageApplied = target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg);
|
||||
defaultDamageApplied = target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), finalDmg);
|
||||
} else if (owner instanceof EntityLivingBase) {
|
||||
defaultDamageApplied = target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg);
|
||||
defaultDamageApplied = target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), finalDmg);
|
||||
} else {
|
||||
defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg);
|
||||
defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", null), finalDmg);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -7,7 +7,9 @@ import noppes.npcs.config.ConfigMain;
|
||||
import noppes.npcs.controllers.MagicController;
|
||||
import noppes.npcs.controllers.data.Magic;
|
||||
import noppes.npcs.controllers.data.MagicEntry;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -30,7 +32,7 @@ public class AttributeAttackUtil {
|
||||
// --- Helper Classes & Methods ---
|
||||
|
||||
// Container for allocation results.
|
||||
private static class AllocationResult {
|
||||
public static class AllocationResult {
|
||||
public Map<Integer, Float> allocation;
|
||||
public float leftover;
|
||||
|
||||
@@ -44,7 +46,7 @@ public class AttributeAttackUtil {
|
||||
* Allocates physical damage into magic allocations based on MagicData splits.
|
||||
* Returns both a mapping (magic ID -> allocated damage) and any leftover physical damage.
|
||||
*/
|
||||
private static AllocationResult allocateMagicDamage(float physicalDamage, Map<Integer, MagicEntry> magicData) {
|
||||
public static AllocationResult allocateMagicDamage(float physicalDamage, Map<Integer, MagicEntry> magicData) {
|
||||
Map<Integer, Float> allocation = new HashMap<>();
|
||||
float totalSplit = 0f;
|
||||
if (magicData != null) {
|
||||
@@ -64,7 +66,7 @@ public class AttributeAttackUtil {
|
||||
/**
|
||||
* Adds extra magic damage from attribute sources if attributes are enabled.
|
||||
*/
|
||||
private static void addAttributeMagicDamage(Map<Integer, Float> allocation,
|
||||
public static void addAttributeMagicDamage(Map<Integer, Float> allocation,
|
||||
Map<Integer, Float> attributeDamage,
|
||||
Map<Integer, Float> magicBoost) {
|
||||
if (!ConfigMain.AttributesEnabled) return;
|
||||
@@ -81,7 +83,7 @@ public class AttributeAttackUtil {
|
||||
* Applies magic interactions. For each magic in the allocation, multiplies its damage by (1 + bonus)
|
||||
* for each opposing magic the defender possesses.
|
||||
*/
|
||||
private static void applyMagicInteractions(Map<Integer, Float> allocation,
|
||||
public static void applyMagicInteractions(Map<Integer, Float> allocation,
|
||||
Set<Integer> defenderMagicIDs,
|
||||
MagicController magicController) {
|
||||
for (Map.Entry<Integer, Float> entry : allocation.entrySet()) {
|
||||
@@ -104,7 +106,7 @@ public class AttributeAttackUtil {
|
||||
* Applies defender magic defenses if attributes are enabled.
|
||||
* Subtracts tracker-based magic defense (scaled by resistance) from each magic allocation.
|
||||
*/
|
||||
private static float applyDefenderMagicDefense(Map<Integer, Float> allocation,
|
||||
public static float applyDefenderMagicDefense(Map<Integer, Float> allocation,
|
||||
PlayerAttributeTracker defender,
|
||||
MagicController magicController) {
|
||||
if (!ConfigMain.AttributesEnabled) return 0f;
|
||||
@@ -284,4 +286,141 @@ public class AttributeAttackUtil {
|
||||
|
||||
return mainDamage + gearDamage;
|
||||
}
|
||||
|
||||
// --- Magic Interaction Utility ---
|
||||
|
||||
/**
|
||||
* Calculate magic interaction multiplier between attacker's magic types
|
||||
* and defender's magic types. Used for barrier defense and entity-vs-entity interactions.
|
||||
*
|
||||
* @param attackerMagic Attacker's magic data (projectile or weapon)
|
||||
* @param defenderMagic Defender's magic data (barrier or entity)
|
||||
* @return Multiplier (1.0 = no interaction, >1.0 = bonus damage, <1.0 = resistance)
|
||||
*/
|
||||
public static float calculateMagicInteractionMultiplier(MagicData attackerMagic, MagicData defenderMagic) {
|
||||
if (attackerMagic == null || defenderMagic == null) return 1.0f;
|
||||
if (attackerMagic.isEmpty() || defenderMagic.isEmpty()) return 1.0f;
|
||||
|
||||
MagicController mc = MagicController.getInstance();
|
||||
float multiplier = 1.0f;
|
||||
|
||||
for (int attackMagicId : attackerMagic.getMagics().keySet()) {
|
||||
Magic magic = mc.getMagic(attackMagicId);
|
||||
if (magic == null || magic.interactions == null) continue;
|
||||
for (Map.Entry<Integer, Float> interaction : magic.interactions.entrySet()) {
|
||||
if (defenderMagic.getMagics().containsKey(interaction.getKey())) {
|
||||
multiplier *= (1 + interaction.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
// --- Ability Damage Pipeline ---
|
||||
|
||||
/**
|
||||
* Apply gear magic boost percentages to the split-allocated magic damage.
|
||||
* This multiplies each magic type's allocated damage by (1 + boost/100).
|
||||
* Unlike {@link #addAttributeMagicDamage}, this does NOT add flat gear damage —
|
||||
* it only applies the percentage multiplier from gear to the existing allocation.
|
||||
*/
|
||||
public static void applyMagicBoostToAllocation(Map<Integer, Float> allocation, Map<Integer, Float> magicBoost) {
|
||||
if (!ConfigMain.AttributesEnabled) return;
|
||||
for (Map.Entry<Integer, Float> entry : allocation.entrySet()) {
|
||||
float boost = magicBoost.getOrDefault(entry.getKey(), 0f);
|
||||
if (boost != 0f) {
|
||||
entry.setValue(entry.getValue() * (1 + boost / 100f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply gear magic boost percentages to barrier/dome health.
|
||||
* Each magic type's split determines what portion of health is affected by its boost.
|
||||
* E.g., Thunder [100%] dome + 50% Thunder gear boost = health * 1.5
|
||||
*/
|
||||
public static float applyMagicBoostToHealth(EntityPlayer caster, MagicData magicData, float health) {
|
||||
if (!ConfigMain.AttributesEnabled) return health;
|
||||
if (magicData == null || magicData.isEmpty()) return health;
|
||||
|
||||
PlayerAttributeTracker tracker = getTracker(caster);
|
||||
if (tracker == null) return health;
|
||||
|
||||
float multiplier = 1.0f;
|
||||
for (Map.Entry<Integer, MagicEntry> entry : magicData.getMagics().entrySet()) {
|
||||
float split = entry.getValue().split;
|
||||
float boost = tracker.magicBoost.getOrDefault(entry.getKey(), 0f);
|
||||
multiplier += split * (boost / 100f);
|
||||
}
|
||||
return health * multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the magic pipeline to ability damage. Works for both player and NPC casters.
|
||||
* Abilities do NOT get main attack or critical boosts — they have their own scaling.
|
||||
* This handles:
|
||||
* <ul>
|
||||
* <li>Magic allocation (splits from ability/caster magic data)</li>
|
||||
* <li>Gear magic boost multipliers on split damage (player casters only, no flat gear damage)</li>
|
||||
* <li>Magic interactions vs defender</li>
|
||||
* <li>Defender magic defense/resistance (player defenders only)</li>
|
||||
* </ul>
|
||||
* If no magic data is present, damage passes through unchanged.
|
||||
*
|
||||
* @param caster Caster (player or NPC)
|
||||
* @param target Target entity
|
||||
* @param damage Base ability damage (already scaled by ability's own system)
|
||||
* @param abilityMagic Resolved magic data (ability's own, or caster's fallback)
|
||||
* @return Final damage after magic splits, boosts, interactions, and defense
|
||||
*/
|
||||
public static float calculateAbilityDamage(EntityLivingBase caster, EntityLivingBase target,
|
||||
float damage, MagicData abilityMagic) {
|
||||
if (abilityMagic == null || abilityMagic.isEmpty()) return damage;
|
||||
|
||||
// 1. Allocate base damage into magic types using ability's splits
|
||||
AllocationResult result = allocateMagicDamage(damage, abilityMagic.getMagics());
|
||||
float leftover = result.leftover;
|
||||
|
||||
// 2. Apply gear magic boost multipliers to the split allocation (player casters only)
|
||||
if (caster instanceof EntityPlayer && ConfigMain.AttributesEnabled) {
|
||||
PlayerAttributeTracker tracker = getTracker((EntityPlayer) caster);
|
||||
if (tracker != null) {
|
||||
applyMagicBoostToAllocation(result.allocation, tracker.magicBoost);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Magic interactions against target
|
||||
MagicController magicController = MagicController.getInstance();
|
||||
Set<Integer> defenderMagicIDs = null;
|
||||
|
||||
if (target instanceof EntityPlayer) {
|
||||
PlayerData defenderData = PlayerData.get((EntityPlayer) target);
|
||||
if (defenderData != null) {
|
||||
defenderMagicIDs = new HashSet<>(defenderData.magicData.getMagics().keySet());
|
||||
}
|
||||
} else if (target instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface npc = (EntityNPCInterface) target;
|
||||
if (npc.stats != null && npc.stats.magicData != null) {
|
||||
defenderMagicIDs = new HashSet<>(npc.stats.magicData.getMagics().keySet());
|
||||
}
|
||||
}
|
||||
|
||||
if (defenderMagicIDs != null) {
|
||||
applyMagicInteractions(result.allocation, defenderMagicIDs, magicController);
|
||||
}
|
||||
|
||||
// 4. Apply defender magic defense (player defenders with attributes only)
|
||||
if (target instanceof EntityPlayer && ConfigMain.AttributesEnabled) {
|
||||
PlayerAttributeTracker defenderTracker = getTracker((EntityPlayer) target);
|
||||
if (defenderTracker != null) {
|
||||
float adjustedMagic = applyDefenderMagicDefense(result.allocation, defenderTracker, magicController);
|
||||
return leftover + adjustedMagic;
|
||||
}
|
||||
}
|
||||
|
||||
// Sum magic damage
|
||||
float magicTotal = 0f;
|
||||
for (float val : result.allocation.values()) magicTotal += val;
|
||||
return leftover + magicTotal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package noppes.npcs.client.gui.advanced;
|
||||
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import noppes.npcs.client.gui.util.GuiCustomScroll;
|
||||
import noppes.npcs.client.gui.util.GuiNpcButton;
|
||||
import noppes.npcs.client.gui.util.GuiNpcLabel;
|
||||
import noppes.npcs.client.gui.util.GuiNpcTextField;
|
||||
import noppes.npcs.client.gui.util.ICustomScrollListener;
|
||||
import noppes.npcs.client.gui.util.ITextfieldListener;
|
||||
import noppes.npcs.client.gui.util.SubGuiInterface;
|
||||
import noppes.npcs.controllers.MagicController;
|
||||
import noppes.npcs.controllers.data.Magic;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sub-GUI for editing magic splits on an Ability.
|
||||
* Dual-scroll layout: available magics on left, assigned magics on right.
|
||||
* Only split values (no flat damage) are editable.
|
||||
*/
|
||||
public class SubGuiAbilityMagic extends SubGuiInterface implements ICustomScrollListener, ITextfieldListener {
|
||||
|
||||
public MagicData magicData;
|
||||
|
||||
private GuiCustomScroll availableScroll;
|
||||
private GuiCustomScroll assignedScroll;
|
||||
|
||||
/** Maps display name → magic ID for all available magics. */
|
||||
private final HashMap<String, Integer> allMagic = new HashMap<>();
|
||||
/** Maps formatted display string (right scroll) → base display name. */
|
||||
private final HashMap<String, String> assignedDisplayToName = new HashMap<>();
|
||||
private String search = "";
|
||||
|
||||
private GuiNpcTextField splitField;
|
||||
|
||||
public SubGuiAbilityMagic(MagicData data) {
|
||||
this.magicData = data;
|
||||
setBackground("menubg.png");
|
||||
xSize = 420;
|
||||
ySize = 222;
|
||||
|
||||
// Load all available magics from the controller
|
||||
MagicController controller = MagicController.getInstance();
|
||||
for (Map.Entry<Integer, Magic> entry : controller.magics.entrySet()) {
|
||||
allMagic.put(entry.getValue().getDisplayName(), entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initGui() {
|
||||
super.initGui();
|
||||
|
||||
int scrollW = 150;
|
||||
int scrollH = 140;
|
||||
int leftX = guiLeft + 10;
|
||||
int rightX = guiLeft + 250;
|
||||
int topY = guiTop + 20;
|
||||
|
||||
// Title
|
||||
addLabel(new GuiNpcLabel(0, "ability.magic.editor", guiLeft + 10, guiTop + 5));
|
||||
|
||||
// Left scroll: available magics
|
||||
if (availableScroll == null) {
|
||||
availableScroll = new GuiCustomScroll(this, 0);
|
||||
availableScroll.setSize(scrollW, scrollH);
|
||||
}
|
||||
availableScroll.guiLeft = leftX;
|
||||
availableScroll.guiTop = topY;
|
||||
availableScroll.setList(getSearchList());
|
||||
addScroll(availableScroll);
|
||||
|
||||
// Search field below left scroll
|
||||
addTextField(new GuiNpcTextField(4, this, fontRendererObj, leftX, topY + scrollH + 5, scrollW, 20, search));
|
||||
|
||||
// Right scroll: assigned magics
|
||||
if (assignedScroll == null) {
|
||||
assignedScroll = new GuiCustomScroll(this, 1);
|
||||
assignedScroll.setSize(scrollW, scrollH);
|
||||
}
|
||||
assignedScroll.guiLeft = rightX;
|
||||
assignedScroll.guiTop = topY;
|
||||
updateAssignedList();
|
||||
addScroll(assignedScroll);
|
||||
|
||||
// Arrow buttons between scrolls
|
||||
int midX = guiLeft + 175;
|
||||
addButton(new GuiNpcButton(70, midX, topY + 40, 55, 20, ">"));
|
||||
addButton(new GuiNpcButton(71, midX, topY + 62, 55, 20, "<"));
|
||||
|
||||
// Standardize button
|
||||
addButton(new GuiNpcButton(72, midX, topY + 90, 55, 20, "magic.dist"));
|
||||
getButton(72).setHoverText("magic.distInfo");
|
||||
|
||||
// Split field below right scroll
|
||||
int tfY = topY + scrollH + 5;
|
||||
addLabel(new GuiNpcLabel(5002, "magic.split", rightX - 57, tfY + 5));
|
||||
splitField = new GuiNpcTextField(73, this, fontRendererObj, rightX, tfY, 60, 20, "");
|
||||
splitField.setFloatsOnly();
|
||||
splitField.setMinMaxDefaultFloat(0, 1, 0);
|
||||
splitField.enabled = false;
|
||||
addTextField(splitField);
|
||||
|
||||
// Done button
|
||||
addButton(new GuiNpcButton(80, guiLeft + xSize / 2 - 40, guiTop + ySize - 34, 80, 20, "gui.done"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buttonEvent(GuiButton guibutton) {
|
||||
// Add magic
|
||||
if (guibutton.id == 70) {
|
||||
if (availableScroll.hasSelected()) {
|
||||
String selected = availableScroll.getSelected();
|
||||
Integer id = allMagic.get(selected);
|
||||
if (id != null && !magicData.hasMagic(id)) {
|
||||
magicData.addMagic(id, 0, 0);
|
||||
}
|
||||
}
|
||||
updateAssignedList();
|
||||
return;
|
||||
}
|
||||
// Remove magic
|
||||
if (guibutton.id == 71) {
|
||||
if (assignedScroll.hasSelected()) {
|
||||
Integer id = resolveAssignedId(assignedScroll.getSelected());
|
||||
if (id != null && magicData.hasMagic(id)) {
|
||||
magicData.removeMagic(id);
|
||||
}
|
||||
}
|
||||
updateAssignedList();
|
||||
if (splitField != null) {
|
||||
splitField.setText("");
|
||||
splitField.enabled = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Standardize splits
|
||||
if (guibutton.id == 72) {
|
||||
if (magicData.getMagics().size() > 0) {
|
||||
int count = magicData.getMagics().size();
|
||||
float stdSplit = 1.0f / count;
|
||||
for (Integer key : magicData.getMagics().keySet()) {
|
||||
magicData.getMagic(key).split = stdSplit;
|
||||
}
|
||||
// Update field if something is selected
|
||||
if (assignedScroll.getSelected() != null && splitField != null) {
|
||||
Integer id = resolveAssignedId(assignedScroll.getSelected());
|
||||
if (id != null && magicData.hasMagic(id)) {
|
||||
splitField.setText(magicData.getMagic(id).split + "");
|
||||
}
|
||||
}
|
||||
updateAssignedList();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Done
|
||||
if (guibutton.id == 80) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(char c, int i) {
|
||||
super.keyTyped(c, i);
|
||||
if (getTextField(4) != null && getTextField(4).isFocused()) {
|
||||
if (search.equals(getTextField(4).getText()))
|
||||
return;
|
||||
search = getTextField(4).getText().toLowerCase();
|
||||
availableScroll.setList(getSearchList());
|
||||
availableScroll.resetScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) {
|
||||
if (guiCustomScroll == assignedScroll) {
|
||||
if (assignedScroll.getSelected() != null) {
|
||||
Integer id = resolveAssignedId(assignedScroll.getSelected());
|
||||
if (id != null && magicData.hasMagic(id)) {
|
||||
if (splitField != null) {
|
||||
splitField.setText(magicData.getMagic(id).split + "");
|
||||
splitField.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unFocused(GuiNpcTextField textField) {
|
||||
if (textField.id == 73) {
|
||||
if (assignedScroll != null && assignedScroll.getSelected() != null) {
|
||||
Integer id = resolveAssignedId(assignedScroll.getSelected());
|
||||
if (id != null && magicData.hasMagic(id)) {
|
||||
try {
|
||||
float split = Float.parseFloat(textField.getText());
|
||||
magicData.getMagic(id).split = split;
|
||||
updateAssignedList();
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getSearchList() {
|
||||
if (search.isEmpty()) {
|
||||
return new ArrayList<>(allMagic.keySet());
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String name : allMagic.keySet()) {
|
||||
if (name.toLowerCase().contains(search))
|
||||
list.add(name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the magic ID from a right-scroll selection (which may be formatted as "Name - XX%").
|
||||
*/
|
||||
private Integer resolveAssignedId(String selection) {
|
||||
if (selection == null) return null;
|
||||
String baseName = assignedDisplayToName.get(selection);
|
||||
if (baseName == null) baseName = selection;
|
||||
return allMagic.get(baseName);
|
||||
}
|
||||
|
||||
private void updateAssignedList() {
|
||||
assignedDisplayToName.clear();
|
||||
List<String> selected = new ArrayList<>();
|
||||
for (Map.Entry<String, Integer> entry : allMagic.entrySet()) {
|
||||
if (magicData.hasMagic(entry.getValue())) {
|
||||
float split = magicData.getMagic(entry.getValue()).split;
|
||||
String display = entry.getKey() + " - " + Math.round(split * 100) + "%";
|
||||
assignedDisplayToName.put(display, entry.getKey());
|
||||
selected.add(display);
|
||||
}
|
||||
}
|
||||
if (assignedScroll != null) {
|
||||
assignedScroll.setList(selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,4 +76,15 @@ public class MagicData implements IMagicData {
|
||||
MagicEntry entry = magics.get(id);
|
||||
return entry == null ? 0 : entry.split;
|
||||
}
|
||||
|
||||
public MagicData copy() {
|
||||
MagicData copy = new MagicData();
|
||||
for (java.util.Map.Entry<Integer, MagicEntry> e : magics.entrySet()) {
|
||||
MagicEntry entryCopy = new MagicEntry();
|
||||
entryCopy.damage = e.getValue().damage;
|
||||
entryCopy.split = e.getValue().split;
|
||||
copy.magics.put(e.getKey(), entryCopy);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.api.INbt;
|
||||
import noppes.npcs.api.entity.IEnergyAbility;
|
||||
import noppes.npcs.api.entity.IEntity;
|
||||
import noppes.npcs.api.handler.data.IMagicData;
|
||||
import noppes.npcs.controllers.data.MagicData;
|
||||
import noppes.npcs.scripted.NpcAPI;
|
||||
|
||||
/**
|
||||
@@ -149,4 +151,16 @@ public abstract class ScriptEnergyAbility<T extends EntityEnergyAbility> extends
|
||||
entity.setCustomDamageData((NBTTagCompound) data.getMCNBT());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== MAGIC DATA ====================
|
||||
|
||||
public IMagicData getMagicData() {
|
||||
return entity.getMagicData();
|
||||
}
|
||||
|
||||
public void setMagicData(IMagicData data) {
|
||||
if (data instanceof MagicData) {
|
||||
entity.setMagicData((MagicData) data);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user