|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
// Magicbane Emulator Project © 2013 - 2024
|
|
|
|
// www.magicbane.com
|
|
|
|
|
|
|
|
package engine.wpakpowers;
|
|
|
|
|
|
|
|
import engine.InterestManagement.WorldGrid;
|
|
|
|
import engine.gameManager.ChatManager;
|
|
|
|
import engine.gameManager.DbManager;
|
|
|
|
import engine.gameManager.DispatchManager;
|
|
|
|
import engine.gameManager.SessionManager;
|
|
|
|
import engine.job.JobContainer;
|
|
|
|
import engine.job.JobScheduler;
|
|
|
|
import engine.jobs.FinishRecycleTimeJob;
|
|
|
|
import engine.jobs.WpakUsePowerJob;
|
|
|
|
import engine.math.Vector3fImmutable;
|
|
|
|
import engine.mbEnums;
|
|
|
|
import engine.net.Dispatch;
|
|
|
|
import engine.net.client.ClientConnection;
|
|
|
|
import engine.net.client.msg.ModifyHealthMsg;
|
|
|
|
import engine.net.client.msg.PerformActionMsg;
|
|
|
|
import engine.net.client.msg.RecyclePowerMsg;
|
|
|
|
import engine.net.client.msg.UpdateStateMsg;
|
|
|
|
import engine.objects.*;
|
|
|
|
import engine.server.MBServerStatics;
|
|
|
|
import engine.util.Hasher;
|
|
|
|
import engine.util.Pair;
|
|
|
|
import engine.wpak.EffectsParser;
|
|
|
|
import engine.wpak.PowerActionParser;
|
|
|
|
import engine.wpak.PowersParser;
|
|
|
|
import engine.wpak.data.Effect;
|
|
|
|
import engine.wpak.data.*;
|
|
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
|
|
|
import static engine.math.FastMath.sqr;
|
|
|
|
|
|
|
|
public class WpakPowerManager {
|
|
|
|
public static HashMap<String, Effect> _effectsLookup = new HashMap<>();
|
|
|
|
public static HashMap<Integer, PowerAction> _powerActionLookup = new HashMap<>();
|
|
|
|
public static HashMap<Integer, Power> _powersLookup = new HashMap<>();
|
|
|
|
|
|
|
|
private static JobScheduler js;
|
|
|
|
|
|
|
|
public static void init() {
|
|
|
|
EffectsParser.parseWpakFile();
|
|
|
|
PowersParser.parseWpakFile();
|
|
|
|
PowerActionParser.parseWpakFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void beginCast(final PerformActionMsg msg, ClientConnection origin, boolean sendCastToSelf) {
|
|
|
|
|
|
|
|
if (executePower(msg, origin, sendCastToSelf)) {
|
|
|
|
// Cast failed for some reason, reset timer
|
|
|
|
|
|
|
|
RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
|
|
|
|
Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg);
|
|
|
|
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY);
|
|
|
|
|
|
|
|
// Send Fail to cast message
|
|
|
|
PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
|
|
|
|
|
|
|
|
if (pc != null) {
|
|
|
|
sendPowerMsg(pc, 2, msg);
|
|
|
|
if (pc.isCasting()) {
|
|
|
|
pc.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
pc.setIsCasting(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean executePower(final PerformActionMsg msg, ClientConnection origin, boolean sendCastToSelf) {
|
|
|
|
|
|
|
|
//check to see if the caster is valid
|
|
|
|
PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin);
|
|
|
|
if (playerCharacter == null)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
//make sure player is still alive
|
|
|
|
if (!playerCharacter.isAlive() && msg.getPowerUsedID() != 428589216) { //succor
|
|
|
|
RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
|
|
|
|
Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg);
|
|
|
|
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//make sure the recycle timer has actually elapsed
|
|
|
|
if (playerCharacter.getRecycleTimers().containsKey(msg.getPowerUsedID())) {
|
|
|
|
Logger.warn("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' recycle timer not finished " + playerCharacter.getName());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//lookup the power that was cast
|
|
|
|
Power powerCast = _powersLookup.get(msg.getPowerUsedID());
|
|
|
|
if (powerCast == null) {
|
|
|
|
ChatManager.chatSayInfo(playerCharacter, "This power is not implemented yet.");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (playerCharacter.getLastPower() != null)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// get numTrains for power
|
|
|
|
int trains = msg.getNumTrains();
|
|
|
|
|
|
|
|
if (trains > powerCast.maxLevel) {
|
|
|
|
trains = powerCast.maxLevel;
|
|
|
|
msg.setNumTrains(trains);
|
|
|
|
}
|
|
|
|
|
|
|
|
//sanity check for amount of trains in spell cast
|
|
|
|
if (playerCharacter.getPowers() != null && playerCharacter.getPowers().containsKey(msg.getPowerUsedID())) {
|
|
|
|
CharacterPower cp = playerCharacter.getPowers().get(msg.getPowerUsedID());
|
|
|
|
if (cp != null) {
|
|
|
|
int tot = cp.getTotalTrains();
|
|
|
|
if (tot == 0)
|
|
|
|
return false;
|
|
|
|
if (trains != tot) {
|
|
|
|
trains = tot;
|
|
|
|
msg.setNumTrains(trains);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//get casting time
|
|
|
|
int time = getRecycleTime(powerCast, trains);
|
|
|
|
|
|
|
|
//combat mode sanity check
|
|
|
|
if (playerCharacter.isCombat()) {
|
|
|
|
if (!allowedInCombat(powerCast))
|
|
|
|
return true;
|
|
|
|
} else if (!allowedOutOfCombat(powerCast))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
//stunned check
|
|
|
|
PlayerBonuses bonus = playerCharacter.getBonuses();
|
|
|
|
// mbEnums.SourceType sourceType = mbEnums.SourceType.GetSourceType(powerCast.category);
|
|
|
|
|
|
|
|
// if (bonus != null && (bonus.getBool(mbEnums.ModType.Stunned, mbEnums.SourceType.None) || bonus.getBool(mbEnums.ModType.CannotCast, mbEnums.SourceType.None) || bonus.getBool(mbEnums.ModType.BlockedPowerType, sourceType)))
|
|
|
|
// return true;
|
|
|
|
|
|
|
|
//sanity check for casting while moving
|
|
|
|
Vector3fImmutable endLoc = playerCharacter.getEndLoc();
|
|
|
|
|
|
|
|
if (!powerCast.canCastWhileMoving)
|
|
|
|
if (playerCharacter.isMoving()) {
|
|
|
|
float distanceLeftSquared = endLoc.distanceSquared2D(playerCharacter.getLoc());
|
|
|
|
if (distanceLeftSquared > sqr(playerCharacter.getSpeed()))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//get the actual target form the message
|
|
|
|
int type = msg.getTargetType();
|
|
|
|
int UUID = msg.getTargetID();
|
|
|
|
|
|
|
|
if (type == -1 || type == 0 || UUID == -1 || UUID == 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
AbstractWorldObject target = (AbstractWorldObject) DbManager.getObject(mbEnums.GameObjectType.values()[type], UUID);
|
|
|
|
|
|
|
|
//check to make sure power can be cast on building if target is a building
|
|
|
|
if (target != null && target.getObjectType() == mbEnums.GameObjectType.Building && !powerCast.target_type.equals(mbEnums.PowerTargetType.BUILDING)) {
|
|
|
|
sendPowerMsg(playerCharacter, 9, new PerformActionMsg(msg));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//validate casting range
|
|
|
|
if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > (powerCast.range * powerCast.range))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
//validate prereqs for power cast
|
|
|
|
//equipment prereqs
|
|
|
|
if (!powerCast.equipmentPreReq.isEmpty())
|
|
|
|
for (EquipmentPreReq prereq : powerCast.equipmentPreReq) {
|
|
|
|
String requiredSkill = prereq.skill;
|
|
|
|
|
|
|
|
if (playerCharacter.charItemManager.equipped.get(prereq.slot) != null) {
|
|
|
|
Item equippedItem = playerCharacter.charItemManager.equipped.get(prereq.slot);
|
|
|
|
if (!equippedItem.template.item_skill_mastery_used.equals(requiredSkill) && !equippedItem.template.item_skill_used.equals(requiredSkill))
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//effect prereqs
|
|
|
|
if (!powerCast.effectPreReqs.isEmpty()) {
|
|
|
|
for (Effect prereq : powerCast.effectPreReqs) {
|
|
|
|
if (!playerCharacter.effects.contains(prereq.effect_id) && !playerCharacter.effects.contains(prereq.effect_name))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
float cost = getCost(powerCast, trains);
|
|
|
|
if (bonus != null)
|
|
|
|
cost *= (1 + bonus.getFloatPercentAll(mbEnums.ModType.PowerCost, mbEnums.SourceType.None));
|
|
|
|
|
|
|
|
if (playerCharacter.getAltitude() > 0)
|
|
|
|
cost *= 1.5f;
|
|
|
|
|
|
|
|
if (cost > 0)
|
|
|
|
if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0)
|
|
|
|
if (playerCharacter.getHealth() <= cost)
|
|
|
|
return true;
|
|
|
|
else {
|
|
|
|
playerCharacter.modifyHealth(-cost, playerCharacter, true);
|
|
|
|
ModifyHealthMsg mhm = new ModifyHealthMsg(playerCharacter, playerCharacter, -cost, 0f, 0f, 0, null, 9999, 0);
|
|
|
|
mhm.setOmitFromChat(1);
|
|
|
|
DispatchManager.dispatchMsgToInterestArea(playerCharacter, mhm, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
|
|
|
|
}
|
|
|
|
else if (powerCast.costType.name().equals("MANA"))
|
|
|
|
if (playerCharacter.getMana() < cost)
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
playerCharacter.modifyMana(-cost, playerCharacter, true);
|
|
|
|
else if (powerCast.costType.name().equals("STAMINA"))
|
|
|
|
if (playerCharacter.getStamina() < cost)
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
playerCharacter.modifyStamina(-cost, playerCharacter, true);
|
|
|
|
else if (playerCharacter.getHealth() <= cost)
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
playerCharacter.modifyHealth(-cost, playerCharacter, true);
|
|
|
|
|
|
|
|
if (time > 0) {
|
|
|
|
FinishRecycleTimeJob frtj = new FinishRecycleTimeJob(playerCharacter, msg);
|
|
|
|
playerCharacter.getRecycleTimers().put(msg.getPowerUsedID(), js.scheduleJob(frtj, time));
|
|
|
|
} else {
|
|
|
|
// else send recycle message to unlock power
|
|
|
|
RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
|
|
|
|
Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg);
|
|
|
|
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY);
|
|
|
|
}
|
|
|
|
|
|
|
|
int tr = msg.getNumTrains();
|
|
|
|
DispatchManager.dispatchMsgToInterestArea(playerCharacter, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, sendCastToSelf, false);
|
|
|
|
|
|
|
|
//Make new msg..
|
|
|
|
PerformActionMsg copyMsg = new PerformActionMsg(msg);
|
|
|
|
copyMsg.setNumTrains(tr);
|
|
|
|
|
|
|
|
// make person casting stand up if spell (unless they're casting a chant which does not make them stand up)
|
|
|
|
if (powerCast.isSpell() && !powerCast.isChant() && playerCharacter.isSit()) {
|
|
|
|
playerCharacter.update();
|
|
|
|
playerCharacter.setSit(false);
|
|
|
|
UpdateStateMsg updateStateMsg = new UpdateStateMsg(playerCharacter);
|
|
|
|
DispatchManager.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// update cast (use skill) fail condition
|
|
|
|
playerCharacter.cancelOnCast();
|
|
|
|
|
|
|
|
// update castSpell (use spell) fail condition if spell
|
|
|
|
if (powerCast.isSpell())
|
|
|
|
playerCharacter.cancelOnSpell();
|
|
|
|
|
|
|
|
// get cast time in ms.
|
|
|
|
time = getCastTime(powerCast, trains);
|
|
|
|
|
|
|
|
// set player is casting for regens
|
|
|
|
|
|
|
|
if (time > 100) {
|
|
|
|
playerCharacter.update();
|
|
|
|
playerCharacter.setIsCasting(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
playerCharacter.setLastMovementState(playerCharacter.getMovementState());
|
|
|
|
|
|
|
|
// run timer job to end cast
|
|
|
|
if (time < 1) // run immediately
|
|
|
|
finishUsePower(copyMsg, playerCharacter, target);
|
|
|
|
else {
|
|
|
|
WpakUsePowerJob upj = new WpakUsePowerJob(playerCharacter, copyMsg, target);
|
|
|
|
JobContainer jc = js.scheduleJob(upj, time);
|
|
|
|
|
|
|
|
// make lastPower
|
|
|
|
playerCharacter.setLastPower(jc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void finishUsePower(PerformActionMsg msg, PlayerCharacter caster, AbstractWorldObject target) {
|
|
|
|
|
|
|
|
Power powerUsed = _powersLookup.get(msg.getPowerUsedID());
|
|
|
|
|
|
|
|
if (powerUsed == null) {
|
|
|
|
Logger.error("Invalid power: " + msg.getPowerUsedID());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (powerUsed.maxMobTargets > 1 || powerUsed.maxPlayerTargets > 1)
|
|
|
|
AoeHandler(caster, target, powerUsed, msg.getNumTrains());
|
|
|
|
else
|
|
|
|
executeActionsForPower(caster, powerUsed, msg.getNumTrains(), target);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void AoeHandler(PlayerCharacter caster, AbstractWorldObject target, Power powerUsed, int rank) {
|
|
|
|
|
|
|
|
HashSet<AbstractWorldObject> mobTargets = new HashSet<>();
|
|
|
|
HashSet<AbstractWorldObject> pcTargets = new HashSet<>();
|
|
|
|
int count = 1;
|
|
|
|
|
|
|
|
if (powerUsed.maxMobTargets > 0)
|
|
|
|
mobTargets = WorldGrid.getObjectsInRangePartial(target.loc, powerUsed.areaRange, MBServerStatics.MASK_MOB);
|
|
|
|
|
|
|
|
if (powerUsed.maxPlayerTargets > 0)
|
|
|
|
pcTargets = WorldGrid.getObjectsInRangePartial(target.loc, powerUsed.areaRange, MBServerStatics.MASK_PLAYER);
|
|
|
|
|
|
|
|
for (AbstractWorldObject mob : mobTargets) {
|
|
|
|
if (count < powerUsed.maxMobTargets + 1) {
|
|
|
|
executeActionsForPower(caster, powerUsed, rank, mob);
|
|
|
|
count++;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
count = 1;
|
|
|
|
for (AbstractWorldObject pc : pcTargets) {
|
|
|
|
if (count < powerUsed.maxPlayerTargets + 1) {
|
|
|
|
executeActionsForPower(caster, powerUsed, rank, pc);
|
|
|
|
count++;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void executeActionsForPower(AbstractCharacter caster, Power power, int rank, AbstractWorldObject target) {
|
|
|
|
|
|
|
|
// Iterate through the poweractions for this power
|
|
|
|
// and execute them according to PowerActionType.
|
|
|
|
|
|
|
|
for (ActionEntry actionEntry : power.actionEntries) {
|
|
|
|
|
|
|
|
PowerAction powerAction = _powerActionLookup.get(Hasher.SBStringHash(actionEntry.action_id));
|
|
|
|
|
|
|
|
if (powerAction == null) {
|
|
|
|
Logger.error("Null PowerAction for " + actionEntry.action_id);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
powerAction.action_type.execute(caster, power, rank, target,
|
|
|
|
powerAction);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void sendPowerMsg(PlayerCharacter playerCharacter, int type, PerformActionMsg msg) {
|
|
|
|
|
|
|
|
if (playerCharacter == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
msg.setUnknown05(type);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 3:
|
|
|
|
case 4:
|
|
|
|
msg.setUnknown04(2);
|
|
|
|
DispatchManager.dispatchMsgToInterestArea(playerCharacter, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
msg.setUnknown04(1);
|
|
|
|
Dispatch dispatch = Dispatch.borrow(playerCharacter, msg);
|
|
|
|
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static int getRecycleTime(Power power, int trains) { // returns cast time in ms
|
|
|
|
if (power.curves.get("RECYCLETIME") != null)
|
|
|
|
return (int) (((power.recycle_time + (power.curves.get("RECYCLETIME").getValue() * trains)) * 1000) + getCastTime(power, trains));
|
|
|
|
else
|
|
|
|
return (int) (((power.recycle_time * (1 + (power.curves.get("RECYCLETIME").getValue() * trains))) * 1000) + getCastTime(power, trains));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static int getCastTime(Power power, int trains) { // returns cast time in ms
|
|
|
|
if (power.curves.get("INITTIME") != null)
|
|
|
|
return (int) ((power.init_time + (power.curves.get("INITTIME").getValue() * trains)) * 1000);
|
|
|
|
else
|
|
|
|
return (int) ((power.init_time * (1 + (power.curves.get("INITTIME").getValue() * trains))) * 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static float getCost(Power power, int trains) {
|
|
|
|
if (power.curves.get("COSTAMT") != null)
|
|
|
|
return power.cost + (power.curves.get("COSTAMT").getValue() * trains);
|
|
|
|
else
|
|
|
|
return power.cost * (1 + (power.curves.get("COSTAMT").getValue() * trains));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean allowedInCombat(Power power) {
|
|
|
|
switch (power.castingMode.name()) {
|
|
|
|
case "NONE":
|
|
|
|
case "BOTH":
|
|
|
|
case "COMBAT":
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean allowedOutOfCombat(Power power) {
|
|
|
|
switch (power.castingMode.name()) {
|
|
|
|
case "NONE":
|
|
|
|
case "BOTH":
|
|
|
|
case "NONCOMBAT":
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Pair<Float, Float> getModifierValues(ModifierEntry modifierEntry, int rank) {
|
|
|
|
|
|
|
|
Pair<Float, Float> outData = new Pair<>(0f, 0f);
|
|
|
|
|
|
|
|
if (modifierEntry.percentage != 0f) {
|
|
|
|
outData.first = modifierEntry.percentage + (modifierEntry.compoundCurveType.getValue() * rank);
|
|
|
|
outData.first = outData.first * 0.01f;
|
|
|
|
return outData;
|
|
|
|
}
|
|
|
|
|
|
|
|
// As there is a min/max we return both as a pair enabling
|
|
|
|
// use in the DD modifier.
|
|
|
|
|
|
|
|
// MB Dev Note:
|
|
|
|
// A subset of health/mana/stam modifiers are multiplicative.
|
|
|
|
// These all have "SIVL" in the curve name suggesting
|
|
|
|
// that SB interpolates between min max.
|
|
|
|
|
|
|
|
outData.first = modifierEntry.compoundCurveType.name().contains("SIVL") ?
|
|
|
|
modifierEntry.min * (1 + (modifierEntry.compoundCurveType.getValue() * rank)) :
|
|
|
|
modifierEntry.min + (modifierEntry.compoundCurveType.getValue() * rank);
|
|
|
|
|
|
|
|
if (modifierEntry.max != 0)
|
|
|
|
outData.second = modifierEntry.compoundCurveType.name().contains("SIVL") ?
|
|
|
|
modifierEntry.max * (1 + (modifierEntry.compoundCurveType.getValue() * rank)) :
|
|
|
|
modifierEntry.max + (modifierEntry.compoundCurveType.getValue() * rank);
|
|
|
|
return outData;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|