PowersManager.loadPowerActions builds every AbstractPowerAction from PowerActionTemplate (template ctor on all subclasses), in id order with IDs 1..3046; OPENGATE keeps 5000. The ResultSet loader stays until removal. The parser now reads the SimpleDamage (min, max), Teleport (bool), Track (two bools) and TransferStat (bool at token 8) header fields. Parity (JSON-Refactor-Plan/scripts/p47.py, every column): ids 3046/3046 plus the DB-only AELFBORN row, which nothing references (the cfg has BOON-AELFBORN). Effect ids, deferred powers, RemoveEffect source/REMOVEALL, track filter/max, transfer types/ramps/ramp-add, mob level identical. The old flags match the new header fields: Track 1024/2048 = tokens 3/4, TransferStat 16384 = token 8, Teleport 32768 = SAB-001A's TRUE. Value changes (PowerActions.cfg wins): - DamageOverTime/TransferStatOT: the cfg value is the tick count; tick length = action duration / ticks. The DB stored cfg+1 as tick seconds, which only worked for 30s DoTs (5 x 6s, unchanged). HOLY-DOT-17 (60s) goes 10 -> 5 ticks, BLEED-DOT (4s) 0 -> 4, TransferStatOT 30s 6 -> 5 ticks. - TransferStat amounts are read with ToInt as the client does: 16 actions drop by 0.5 (BEL-001A 31.5 -> 31, CON-001A 13.5 -> 13, ...). - PRE-334..337 become ApplyEffects with one effect; ApplyEffectsPowerAction now allows a missing second effect on baked-in items. - Charm level cap ramp from the LEVELCAP= curve (same values as the DB). - Charm actions with effect FEAR/CONFUSION (12, DB type Confusion) stay ConfusionPowerAction no-ops; CHARM ones use CharmPowerAction (decided). - CreateMob: the cfg client mob id (49..74, 3500, 3501) maps to the mobbase through a map in CreateMobPowerAction, from the old DB values. - The 50 Charm/Block/SpireDisable rows with no DB effect id get the cfg id (unread by those classes); isAggressive differs on 330 rows (unread). - SimpleDamage keeps min/max instead of the always-0 simpleDamage (unread). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
268 lines
11 KiB
Java
268 lines
11 KiB
Java
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
// https://www.magicbane.com
|
|
|
|
|
|
package engine.powers.poweractions;
|
|
|
|
import engine.InterestManagement.WorldGrid;
|
|
import engine.gameManager.ChatManager;
|
|
import engine.gameManager.DispatchManager;
|
|
import engine.jobs.ChantJob;
|
|
import engine.jobs.DeferredPowerJob;
|
|
import engine.jobs.FinishEffectTimeJob;
|
|
import engine.math.Vector3fImmutable;
|
|
import engine.mbEnums.GameObjectType;
|
|
import engine.mbEnums.ModType;
|
|
import engine.mbEnums.SourceType;
|
|
import engine.net.client.msg.chat.ChatSystemMsg;
|
|
import engine.objects.*;
|
|
import engine.parsers.objects.wpak.PowerActionTemplate;
|
|
import engine.powers.ActionsBase;
|
|
import engine.powers.EffectsBase;
|
|
import engine.powers.PowersBase;
|
|
import engine.server.MBServerStatics;
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
import java.sql.ResultSet;
|
|
import java.sql.SQLException;
|
|
import java.util.HashMap;
|
|
|
|
public class ApplyEffectPowerAction extends AbstractPowerAction {
|
|
|
|
private String effectID;
|
|
private EffectsBase effect;
|
|
private String effectParentID;
|
|
private EffectsBase effectParent;
|
|
|
|
public ApplyEffectPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
|
|
super(rs);
|
|
this.effectParentID = rs.getString("IDString");
|
|
this.effectParent = effects.get(this.effectParentID);
|
|
this.effectID = rs.getString("effectID");
|
|
this.effect = effects.get(this.effectID);
|
|
}
|
|
|
|
public ApplyEffectPowerAction(PowerActionTemplate powerActionTemplate, int id, HashMap<String, EffectsBase> effects) {
|
|
super(powerActionTemplate, id);
|
|
this.effectParentID = powerActionTemplate.action_id;
|
|
this.effectParent = effects.get(this.effectParentID);
|
|
this.effectID = effectIDAt(powerActionTemplate, 0);
|
|
this.effect = effects.get(this.effectID);
|
|
}
|
|
|
|
private static boolean blockInvul(PowersBase pb, AbstractCharacter source, AbstractWorldObject awo) {
|
|
if (awo == null || pb == null || source == null)
|
|
return false;
|
|
|
|
if (source.getObjectUUID() == awo.getObjectUUID())
|
|
return false;
|
|
|
|
if (!AbstractWorldObject.IsAbstractCharacter(awo))
|
|
return false;
|
|
|
|
AbstractCharacter ac = (AbstractCharacter) awo;
|
|
|
|
return false;
|
|
}
|
|
|
|
public String getEffectID() {
|
|
return this.effectID;
|
|
}
|
|
|
|
public EffectsBase getEffect() {
|
|
return this.effect;
|
|
}
|
|
|
|
@Override
|
|
protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
|
|
if (this.effect == null || pb == null || ab == null) {
|
|
//TODO log error here
|
|
return;
|
|
}
|
|
|
|
//add schedule job to end it if needed and add effect to pc
|
|
int duration = 0;
|
|
// if (pb.isChant())
|
|
// duration = (int)pb.getChantDuration() * 1000;
|
|
// else
|
|
duration = ab.getDuration(trains);
|
|
String stackType = ab.getStackType();
|
|
if (stackType.equals("WeaponMove")) {
|
|
DeferredPowerJob eff = new DeferredPowerJob(source, awo, stackType, trains, ab, pb, this.effect, this);
|
|
if (stackType.equals("IgnoreStack"))
|
|
awo.addEffect(Integer.toString(ab.getUUID()), 10000, eff, this.effect, trains);
|
|
else
|
|
awo.addEffect(stackType, 10000, eff, this.effect, trains);
|
|
if (awo.getObjectType().equals(GameObjectType.PlayerCharacter))
|
|
((PlayerCharacter) awo).setWeaponPower(eff);
|
|
this.effect.startEffect(source, awo, trains, eff);
|
|
} else {
|
|
FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, effect);
|
|
|
|
if (blockInvul(pb, source, awo)) {
|
|
this.effect.endEffect(source, awo, trains, pb, eff);
|
|
return;
|
|
}
|
|
|
|
// Effect lastEff = awo.getEffects().get(eff.getStackType());
|
|
//
|
|
// if (lastEff != null && lastEff.getPowerToken() == eff.getPowerToken())
|
|
// lastEff.cancelJob(true);
|
|
|
|
if (duration > 0) {
|
|
if (stackType.equals("IgnoreStack"))
|
|
awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, effect, trains);
|
|
else
|
|
awo.addEffect(stackType, duration, eff, effect, trains);
|
|
} else
|
|
awo.applyAllBonuses();
|
|
|
|
if (this.effectID.equals("TAUNT")) {
|
|
|
|
if (awo != null && awo.getObjectType() == GameObjectType.Mob) {
|
|
ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
|
|
DispatchManager.sendToAllInRange(source, msg);
|
|
}
|
|
}
|
|
if (awo != null && awo.getObjectType() == GameObjectType.Mob) {
|
|
if (((Mob) awo).isPet()) {
|
|
((Mob) awo).recalculateStats();
|
|
}
|
|
}
|
|
this.effect.startEffect(source, awo, trains, eff);
|
|
}
|
|
//apply effects to mobs within range for chants
|
|
if (pb.isChant) {
|
|
for (AbstractGameObject ago : WorldGrid.getObjectsInRangePartial(awo.loc, pb.range, MBServerStatics.MASK_MOB)) {
|
|
Mob mob = (Mob) ago;
|
|
if (mob.playerAgroMap.containsKey(source.getObjectUUID()))
|
|
mob.playerAgroMap.put(source.getObjectUUID(), mob.playerAgroMap.get(source.getObjectUUID()).floatValue() + pb.hateValue);
|
|
|
|
}
|
|
} else {
|
|
if (awo != null && awo.getObjectType() == GameObjectType.Mob) {
|
|
Mob mob = (Mob) awo;
|
|
if (mob.playerAgroMap.containsKey(source.getObjectUUID()))
|
|
mob.playerAgroMap.put(source.getObjectUUID(), mob.playerAgroMap.get(source.getObjectUUID()).floatValue() + pb.hateValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void _applyEffectForItem(Item item, int trains) {
|
|
if (item == null || this.effect == null)
|
|
return;
|
|
item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains, false);
|
|
item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains, false);
|
|
}
|
|
|
|
protected void _applyBakedInStatsForItem(Item item, int trains) {
|
|
if (item == null)
|
|
return;
|
|
if (this.effect == null) {
|
|
Logger.error("Unknown Token: EffectBase ID " + this.effectID + '.');
|
|
return;
|
|
}
|
|
|
|
if (this.effectParent == null) {
|
|
Logger.error("Unknown Token: EffectBase ID " + this.effectParentID + '.');
|
|
return;
|
|
}
|
|
Effect eff = item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains, false);
|
|
Effect eff3 = item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains, false);
|
|
if (eff != null && eff3 != null) {
|
|
eff3.setBakedInStat(true);
|
|
item.getEffectNames().add(this.effect.getIDString());
|
|
item.getEffectNames().add(this.effectParent.getIDString());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
|
|
if (source != null) {
|
|
PlayerBonuses bonuses = source.getBonuses();
|
|
|
|
if (bonuses == null)
|
|
return;
|
|
|
|
boolean noSilence = bonuses.getBool(ModType.Silenced, SourceType.None);
|
|
|
|
if (noSilence)
|
|
return;
|
|
|
|
}
|
|
String stackType = ab.stackType;
|
|
stackType = stackType.equals("IgnoreStack") ? Integer.toString(ab.getUUID()) : stackType;
|
|
|
|
FinishEffectTimeJob eff = new FinishEffectTimeJob(source, target, stackType, trains, ab, pb, effect);
|
|
ChantJob cj = new ChantJob(source, target, stackType, trains, ab, pb, effect, eff);
|
|
source.setLastChant((int) (pb.getChantDuration()) * 1000, cj);
|
|
eff.setChant(true);
|
|
}
|
|
|
|
@Override
|
|
protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
|
|
int trains, ActionsBase ab, PowersBase pb, int duration) {
|
|
if (this.effect == null || pb == null || ab == null) {
|
|
//TODO log error here
|
|
return;
|
|
}
|
|
|
|
//add schedule job to end it if needed and add effect to pc
|
|
// if (pb.isChant())
|
|
// duration = (int)pb.getChantDuration() * 1000;
|
|
// else
|
|
|
|
String stackType = ab.getStackType();
|
|
if (stackType.equals("WeaponMove")) {
|
|
DeferredPowerJob eff = new DeferredPowerJob(source, awo, stackType, trains, ab, pb, this.effect, this);
|
|
if (stackType.equals("IgnoreStack"))
|
|
awo.addEffect(Integer.toString(ab.getUUID()), 10000, eff, this.effect, trains);
|
|
else
|
|
awo.addEffect(stackType, 10000, eff, this.effect, trains);
|
|
if (awo.getObjectType().equals(GameObjectType.PlayerCharacter))
|
|
((PlayerCharacter) awo).setWeaponPower(eff);
|
|
this.effect.startEffect(source, awo, trains, eff);
|
|
} else {
|
|
FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, effect);
|
|
|
|
if (blockInvul(pb, source, awo)) {
|
|
this.effect.endEffect(source, awo, trains, pb, eff);
|
|
return;
|
|
}
|
|
|
|
if (duration > 0) {
|
|
if (stackType.equals("IgnoreStack"))
|
|
awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, effect, trains);
|
|
else
|
|
awo.addEffect(stackType, duration, eff, effect, trains);
|
|
} else
|
|
awo.applyAllBonuses();
|
|
// //TODO if chant, start cycle
|
|
// if (pb.isChant() && source.equals(awo)) {
|
|
// ChantJob cj = new ChantJob(source, awo, stackType, trains, ab, pb, effect, eff);
|
|
// source.setLastChant((int)(pb.getChantDuration()-2) * 1000, cj);
|
|
// eff.setChant(true);
|
|
// }
|
|
|
|
if (this.effectID.equals("TAUNT")) {
|
|
|
|
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth
|
|
|
|
if (awo != null && awo.getObjectType() == GameObjectType.Mob && !((Mob) awo).isSiege()) {
|
|
((Mob) awo).setCombatTarget(source);
|
|
ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
|
|
DispatchManager.sendToAllInRange(source, msg);
|
|
}
|
|
}
|
|
this.effect.startEffect(source, awo, trains, eff);
|
|
}
|
|
|
|
}
|
|
|
|
}
|