forked from MagicBane/Server
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2804 lines
106 KiB
2804 lines
106 KiB
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . |
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· |
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ |
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ |
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ |
|
// Magicbane Emulator Project © 2013 - 2022 |
|
// www.magicbane.com |
|
|
|
package engine.gameManager; |
|
|
|
import engine.Enum.*; |
|
import engine.InterestManagement.HeightMap; |
|
import engine.InterestManagement.WorldGrid; |
|
import engine.db.handlers.dbEffectsBaseHandler; |
|
import engine.db.handlers.dbSkillReqHandler; |
|
import engine.job.AbstractJob; |
|
import engine.job.AbstractScheduleJob; |
|
import engine.job.JobContainer; |
|
import engine.job.JobScheduler; |
|
import engine.jobs.*; |
|
import engine.math.Vector3fImmutable; |
|
import engine.net.ByteBufferWriter; |
|
import engine.net.Dispatch; |
|
import engine.net.DispatchMessage; |
|
import engine.net.client.ClientConnection; |
|
import engine.net.client.msg.*; |
|
import engine.objects.*; |
|
import engine.powers.*; |
|
import engine.powers.effectmodifiers.AbstractEffectModifier; |
|
import engine.powers.poweractions.AbstractPowerAction; |
|
import engine.powers.poweractions.TrackPowerAction; |
|
import engine.server.MBServerStatics; |
|
import org.pmw.tinylog.Logger; |
|
|
|
import java.sql.ResultSet; |
|
import java.util.ArrayList; |
|
import java.util.HashMap; |
|
import java.util.HashSet; |
|
import java.util.Iterator; |
|
import java.util.concurrent.ConcurrentHashMap; |
|
import java.util.concurrent.ThreadLocalRandom; |
|
|
|
import static engine.math.FastMath.sqr; |
|
|
|
public enum PowersManager { |
|
|
|
POWERMANAGER; |
|
|
|
public static HashMap<String, PowersBase> powersBaseByIDString = new HashMap<>(); |
|
public static HashMap<Integer, PowersBase> powersBaseByToken = new HashMap<>(); |
|
public static HashMap<String, EffectsBase> effectsBaseByIDString = new HashMap<>(); |
|
public static HashMap<Integer, EffectsBase> effectsBaseByToken = new HashMap<>(); |
|
public static HashMap<String, AbstractPowerAction> powerActionsByIDString = new HashMap<>(); |
|
public static HashMap<Integer, AbstractPowerAction> powerActionsByID = new HashMap<>(); |
|
public static HashMap<String, Integer> ActionTokenByIDString = new HashMap<>(); |
|
public static HashMap<Integer, AbstractEffectModifier> modifiersByToken = new HashMap<>(); |
|
public static HashMap<String, Integer> AnimationOverrides = new HashMap<>(); |
|
public static HashMap<Integer, HashMap<Integer, Integer>> AllMobPowers = new HashMap<>(); |
|
private static JobScheduler js; |
|
|
|
private PowersManager() { |
|
|
|
} |
|
|
|
public static void initPowersManager(boolean fullPowersLoad) { |
|
|
|
if (fullPowersLoad) |
|
PowersManager.InitializePowers(); |
|
else |
|
PowersManager.InitializeLoginPowers(); |
|
|
|
PowersManager.js = JobScheduler.getInstance(); |
|
|
|
} |
|
|
|
public static PowersBase getPowerByToken(int token) { |
|
return PowersManager.powersBaseByToken.get(token); |
|
} |
|
|
|
public static PowersBase getPowerByIDString(String IDString) { |
|
return PowersManager.powersBaseByIDString.get(IDString); |
|
} |
|
|
|
public static EffectsBase getEffectByIDString(String IDString) { |
|
return PowersManager.effectsBaseByIDString.get(IDString); |
|
} |
|
|
|
public static AbstractPowerAction getPowerActionByID(Integer id) { |
|
return PowersManager.powerActionsByID.get(id); |
|
} |
|
|
|
public static AbstractPowerAction getPowerActionByIDString(String IDString) { |
|
return PowersManager.powerActionsByIDString.get(IDString); |
|
} |
|
|
|
public static EffectsBase getEffectByToken(int token) { |
|
return PowersManager.effectsBaseByToken.get(token); |
|
} |
|
|
|
// This pre-loads only PowersBase for login |
|
public static void InitializeLoginPowers() { |
|
|
|
// get all PowersBase |
|
ArrayList<PowersBase> pbList = dbSkillReqHandler.getAllPowersBase(); |
|
|
|
for (PowersBase pb : pbList) { |
|
if (pb.getToken() != 0) |
|
PowersManager.powersBaseByToken.put(pb.getToken(), pb); |
|
} |
|
} |
|
|
|
// This pre-loads all powers and effects |
|
public static void InitializePowers() { |
|
|
|
// Add EffectsBase |
|
ArrayList<EffectsBase> ebList = dbEffectsBaseHandler.getAllEffectsBase(); |
|
|
|
for (EffectsBase eb : ebList) { |
|
PowersManager.effectsBaseByToken.put(eb.getToken(), eb); |
|
PowersManager.effectsBaseByIDString.put(eb.getIDString(), eb); |
|
|
|
} |
|
|
|
// Add Fail Conditions |
|
EffectsBase.getFailConditions(PowersManager.effectsBaseByIDString); |
|
|
|
// Add Modifiers to Effects |
|
dbEffectsBaseHandler.cacheAllEffectModifiers(); |
|
|
|
// Add Source Types to Effects |
|
PowersManager.addAllSourceTypes(); |
|
PowersManager.addAllAnimationOverrides(); |
|
|
|
// Add PowerActions |
|
AbstractPowerAction.getAllPowerActions(PowersManager.powerActionsByIDString, PowersManager.powerActionsByID, PowersManager.effectsBaseByIDString); |
|
|
|
// Load valid Item Flags |
|
// AbstractPowerAction.loadValidItemFlags(PowersManager.powerActionsByIDString); |
|
|
|
// get all PowersBase |
|
ArrayList<PowersBase> pbList = dbSkillReqHandler.getAllPowersBase(); |
|
for (PowersBase pb : pbList) { |
|
if (pb.getToken() != 0) { |
|
PowersManager.powersBaseByIDString.put(pb.getIDString(), pb); |
|
PowersManager.powersBaseByToken.put(pb.getToken(), pb); |
|
} |
|
} |
|
|
|
// Add Power Prereqs |
|
PowerPrereq.getAllPowerPrereqs(PowersManager.powersBaseByIDString); |
|
// Add Fail Conditions |
|
dbSkillReqHandler.getFailConditions(PowersManager.powersBaseByIDString); |
|
// Add Actions Base |
|
ActionsBase.getActionsBase(PowersManager.powersBaseByIDString, |
|
PowersManager.powerActionsByIDString); |
|
|
|
} |
|
|
|
private static void addAllSourceTypes() { |
|
PreparedStatementShared ps = null; |
|
try { |
|
ps = new PreparedStatementShared("SELECT * FROM static_power_sourcetype"); |
|
ResultSet rs = ps.executeQuery(); |
|
String IDString, source; |
|
while (rs.next()) { |
|
IDString = rs.getString("IDString"); |
|
int token = DbManager.hasher.SBStringHash(IDString); |
|
|
|
|
|
source = rs.getString("source").replace("-", "").trim(); |
|
EffectSourceType effectSourceType = EffectSourceType.GetEffectSourceType(source); |
|
|
|
if (EffectsBase.effectSourceTypeMap.containsKey(token) == false) |
|
EffectsBase.effectSourceTypeMap.put(token, new HashSet<>()); |
|
|
|
EffectsBase.effectSourceTypeMap.get(token).add(effectSourceType); |
|
} |
|
rs.close(); |
|
} catch (Exception e) { |
|
Logger.error(e); |
|
} finally { |
|
ps.release(); |
|
} |
|
} |
|
|
|
private static void addAllAnimationOverrides() { |
|
PreparedStatementShared ps = null; |
|
try { |
|
ps = new PreparedStatementShared("SELECT * FROM static_power_animation_override"); |
|
ResultSet rs = ps.executeQuery(); |
|
String IDString; |
|
int animation; |
|
while (rs.next()) { |
|
IDString = rs.getString("IDString"); |
|
|
|
EffectsBase eb = PowersManager.getEffectByIDString(IDString); |
|
if (eb != null) |
|
IDString = eb.getIDString(); |
|
|
|
animation = rs.getInt("animation"); |
|
PowersManager.AnimationOverrides.put(IDString, animation); |
|
|
|
} |
|
rs.close(); |
|
} catch (Exception e) { |
|
Logger.error(e); |
|
} finally { |
|
ps.release(); |
|
} |
|
} |
|
|
|
public static EffectsBase setEffectToken(int ID, int token) { |
|
for (EffectsBase eb : PowersManager.effectsBaseByIDString.values()) { |
|
if (eb.getUUID() == ID) { |
|
eb.setToken(token); |
|
return eb; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
public static void usePower(final PerformActionMsg msg, ClientConnection origin, |
|
boolean sendCastToSelf) { |
|
|
|
if (usePowerA(msg, origin, sendCastToSelf)) { |
|
// Cast failed for some reason, reset timer |
|
|
|
RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); |
|
Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg); |
|
DispatchMessage.dispatchMsgDispatch(dispatch, 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); |
|
} |
|
|
|
} |
|
} |
|
|
|
public static void useMobPower(Mob caster, AbstractCharacter target, PowersBase pb, int rank) { |
|
|
|
PerformActionMsg msg = createPowerMsg(pb, rank, caster, target); |
|
msg.setUnknown04(1); |
|
|
|
if (useMobPowerA(msg, caster)) { |
|
//sendMobPowerMsg(caster,2,msg); //Lol wtf was i thinking sending msg's to mobs... ZZZZ |
|
} |
|
} |
|
|
|
public static boolean usePowerA(final PerformActionMsg msg, ClientConnection origin, |
|
boolean sendCastToSelf) { |
|
PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter( |
|
origin); |
|
if (playerCharacter == null) |
|
return false; |
|
|
|
boolean CSRCast = false; |
|
|
|
|
|
if (MBServerStatics.POWERS_DEBUG) { |
|
ChatManager.chatSayInfo( |
|
playerCharacter, |
|
"Using Power: " + Integer.toHexString(msg.getPowerUsedID()) |
|
+ " (" + msg.getPowerUsedID() + ')'); |
|
Logger.info("Using Power: " |
|
+ Integer.toHexString(msg.getPowerUsedID()) + " (" |
|
+ msg.getPowerUsedID() + ')'); |
|
} |
|
|
|
//Sending recycle message to player if died while casting. |
|
if (!playerCharacter.isAlive() && msg.getPowerUsedID() != 428589216) { //succor |
|
|
|
RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID()); |
|
Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg); |
|
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); |
|
|
|
return false; |
|
} |
|
|
|
|
|
// if (!pc.getPowers().contains(msg.getPowerUsedID())) { |
|
// sendPowerMsg(pc, 10, msg); |
|
// return false; |
|
// } |
|
// verify recycle timer is not active for power, skip for a CSR |
|
if (playerCharacter.getRecycleTimers().containsKey(msg.getPowerUsedID()) && !playerCharacter.isCSR()) { |
|
// ChatManager.chatSayInfo(pc, "Recycle timer not finished!"); |
|
|
|
Logger.warn("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' recycle timer not finished " + playerCharacter.getName()); |
|
return false; |
|
} |
|
|
|
// get power |
|
PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); |
|
if (pb == null) { |
|
ChatManager.chatSayInfo(playerCharacter, |
|
"This power is not implemented yet."); |
|
|
|
// Logger.error("usePowerA(): Power '" + |
|
// msg.getPowerUsedID() |
|
// + "' was not found on powersBaseByToken map."); |
|
return true; |
|
// return false; |
|
} |
|
|
|
if (playerCharacter.getLastPower() != null) |
|
return true; |
|
|
|
//Check if Power Target is allowed to cast. |
|
|
|
|
|
// Check powers for normal users |
|
if (playerCharacter.getPowers() == null || !playerCharacter.getPowers().containsKey(msg.getPowerUsedID())) |
|
if (!playerCharacter.isCSR()) { |
|
if (!MBServerStatics.POWERS_DEBUG) { |
|
// ChatManager.chatSayInfo(pc, "You may not cast that spell!"); |
|
|
|
Logger.info("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' was not associated with " + playerCharacter.getName()); |
|
return true; |
|
} |
|
} else |
|
CSRCast = true; |
|
|
|
// get numTrains for power |
|
int trains = msg.getNumTrains(); |
|
|
|
// can't go over the max trains for the power, unless CSR |
|
if (trains > pb.getMaxTrains() && !playerCharacter.isCSR()) { |
|
trains = pb.getMaxTrains(); |
|
msg.setNumTrains(trains); |
|
} |
|
|
|
// can't go over total trains by player |
|
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 && !playerCharacter.isCSR()) |
|
return false; |
|
if (trains != tot && !playerCharacter.isCSR()) { |
|
trains = tot; |
|
msg.setNumTrains(trains); |
|
} |
|
} |
|
} |
|
|
|
// get recycle time in ms |
|
int time = pb.getRecycleTime(trains); |
|
|
|
// verify player is in correct mode (combat/nonCombat) |
|
if (playerCharacter.isCombat()) { |
|
if (!pb.allowedInCombat()) |
|
// ChatManager.chatPowerError(pc, |
|
// "This power is not allowed in combat mode."); |
|
return true; |
|
} else if (!pb.allowedOutOfCombat()) |
|
// ChatManager.chatPowerError(pc, |
|
// "You must be in combat mode to use this power."); |
|
return true; |
|
|
|
// verify player is not stunned or prohibited from casting |
|
PlayerBonuses bonus = playerCharacter.getBonuses(); |
|
SourceType sourceType = SourceType.GetSourceType(pb.getCategory()); |
|
if (bonus != null && (bonus.getBool(ModType.Stunned, SourceType.None) || bonus.getBool(ModType.CannotCast, SourceType.None) || bonus.getBool(ModType.BlockedPowerType, sourceType))) |
|
return true; |
|
|
|
// if moving make sure spell valid for movement |
|
Vector3fImmutable endLoc = playerCharacter.getEndLoc(); |
|
|
|
|
|
if (!pb.canCastWhileMoving()) |
|
if (playerCharacter.isMoving()) { |
|
|
|
// if movement left is less then 1 seconds worth then let cast |
|
// go through. |
|
float distanceLeftSquared = endLoc.distanceSquared2D(playerCharacter.getLoc()); |
|
|
|
if (distanceLeftSquared > sqr(playerCharacter.getSpeed())) |
|
return true; |
|
} |
|
// if flying, make sure spell valid for flying. |
|
// if (pc.getAltitude() > 0) |
|
// if (!pb.canCastWhileFlying()) |
|
// return true; |
|
|
|
int targetLiveCounter = -1; |
|
|
|
// get target based on targetType; |
|
if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target |
|
if (pb.isAOE()) { |
|
if (!pb.usePointBlank()) { |
|
AbstractWorldObject target = getTarget(msg); |
|
|
|
|
|
if (target != null && target.getObjectType() == GameObjectType.Building && !pb.targetBuilding()) { |
|
PowersManager.sendPowerMsg(playerCharacter, 9, new PerformActionMsg(msg)); |
|
return true; |
|
} |
|
|
|
if (target == null) { |
|
if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb |
|
.getRange())) |
|
return true; |
|
} else if (verifyInvalidRange(playerCharacter, target, pb.getRange())) |
|
// pc.getLoc().distance(target.getLoc()) > |
|
// pb.getRange()) |
|
return true; |
|
|
|
|
|
} |
|
} else { |
|
// get target |
|
AbstractWorldObject target = getTarget(msg); |
|
|
|
if (target == null) |
|
return true; |
|
|
|
if (!target.isAlive() && target.getObjectType().equals(GameObjectType.Building) == false && msg.getPowerUsedID() != 428589216) |
|
return true; |
|
|
|
float range = pb.getRange(); |
|
// verify target is in range |
|
|
|
|
|
if (verifyInvalidRange(playerCharacter, target, range)) |
|
// (pc.getLoc().distance(target.getLoc()) > pb.getRange()) { |
|
// TODO send message that target is out of range |
|
return true; |
|
|
|
// verify target is valid type |
|
if (!validateTarget(target, playerCharacter, pb)) |
|
return true; |
|
|
|
|
|
if (AbstractWorldObject.IsAbstractCharacter(target)) |
|
targetLiveCounter = ((AbstractCharacter) target).getLiveCounter(); |
|
} |
|
|
|
// verify regular player can cast spell, otherwise authenticate |
|
if (!pb.regularPlayerCanCast()) { |
|
Account a = SessionManager.getAccount(playerCharacter); |
|
if (a.status.equals(AccountStatus.ADMIN) == false) { |
|
Logger.warn("Player '" + playerCharacter.getName() |
|
+ "' is attempting to cast a spell outside of their own access level."); |
|
return true; |
|
} |
|
} |
|
|
|
// verify player has proper effects applied to use power |
|
if (pb.getEffectPrereqs().size() > 0 && playerCharacter.getEffects() != null) { |
|
|
|
boolean passed = false; |
|
for (PowerPrereq pp : pb.getEffectPrereqs()) { |
|
|
|
EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect()); |
|
|
|
if (playerCharacter.containsEffect(eb.getToken())) { |
|
passed = true; |
|
break; |
|
} |
|
} |
|
|
|
if (!passed) |
|
return true; |
|
} |
|
|
|
//verify player has proper equipment to use power |
|
if (pb.getEquipPrereqs().size() > 0) { |
|
|
|
boolean passed = false; |
|
|
|
for (PowerPrereq pp : pb.getEquipPrereqs()) { |
|
|
|
int slot = pp.mainHand() ? MBServerStatics.SLOT_MAINHAND : MBServerStatics.SLOT_OFFHAND; |
|
|
|
if (playerCharacter.validEquip(slot, pp.getMessage())) { |
|
passed = true; //should have item in slot |
|
break; |
|
} else if (!pp.isRequired()) { |
|
passed = true; //should not have item in slot |
|
break; |
|
} |
|
} |
|
if (!passed) |
|
return true; |
|
} |
|
|
|
// TODO if target immune to powers, cancel unless aoe |
|
// Also make sure No.ImmuneToPowers is false for target |
|
// if there's a different power waiting to finish, stop here |
|
|
|
|
|
//get power cost and calculate any power cost modifiers |
|
float cost = pb.getCost(trains); |
|
|
|
if (playerCharacter.isCSR()) |
|
cost = 0; |
|
|
|
if (bonus != null) |
|
cost *= (1 + bonus.getFloatPercentAll(ModType.PowerCost, SourceType.None)); |
|
|
|
if (playerCharacter.getAltitude() > 0) |
|
cost *= 1.5f; |
|
|
|
// Verify player can afford to use power |
|
//CCR toons dont use cost |
|
|
|
if (!playerCharacter.isCSR()) { |
|
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); |
|
DispatchMessage.dispatchMsgToInterestArea(playerCharacter, mhm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
} |
|
else if (pb.useMana()) |
|
if (playerCharacter.getMana() < cost) |
|
return true; |
|
else |
|
playerCharacter.modifyMana(-cost, playerCharacter, true); |
|
else if (pb.useStamina()) |
|
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); |
|
} |
|
|
|
|
|
// Validity checks passed, move on to casting spell |
|
//get caster's live counter |
|
int casterLiveCounter = playerCharacter.getLiveCounter(); |
|
|
|
// run recycle job for when cast is available again, don't bother adding the timer for CSRs |
|
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); |
|
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); |
|
} |
|
|
|
//what the fuck? |
|
// // Send cast to other players |
|
// if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0) |
|
// msg.setUnknown04(2); // Vampire Race, use health? |
|
// else |
|
// msg.setUnknown04(1); // Regular Race, use mana? |
|
int tr = msg.getNumTrains(); |
|
DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, 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 (pb.isSpell() && !pb.isChant() && playerCharacter.isSit()) { |
|
playerCharacter.update(); |
|
playerCharacter.setSit(false); |
|
UpdateStateMsg updateStateMsg = new UpdateStateMsg(playerCharacter); |
|
DispatchMessage.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, 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 (pb.isSpell()) |
|
playerCharacter.cancelOnSpell(); |
|
|
|
// get cast time in ms. |
|
time = pb.getCastTime(trains); |
|
|
|
// set player is casting for regens |
|
|
|
|
|
if (time > 100) { |
|
playerCharacter.update(); |
|
playerCharacter.setIsCasting(true); |
|
} |
|
|
|
|
|
playerCharacter.setLastMovementState(playerCharacter.getMovementState()); |
|
// update used power timer |
|
playerCharacter.setLastUsedPowerTime(); |
|
|
|
// run timer job to end cast |
|
if (time < 1) // run immediately |
|
finishUsePower(copyMsg, playerCharacter, casterLiveCounter, targetLiveCounter); |
|
else { |
|
UsePowerJob upj = new UsePowerJob(playerCharacter, copyMsg, copyMsg.getPowerUsedID(), pb, casterLiveCounter, targetLiveCounter); |
|
JobContainer jc = js.scheduleJob(upj, time); |
|
|
|
// make lastPower |
|
playerCharacter.setLastPower(jc); |
|
} |
|
|
|
if (CSRCast) |
|
Logger.info("CSR " + playerCharacter.getName() + " cast power " + msg.getPowerUsedID() + '.'); |
|
|
|
return false; |
|
} |
|
|
|
public static void testPowers(ByteBufferWriter writer) { |
|
writer.putInt(powersBaseByToken.size()); |
|
for (int token : powersBaseByToken.keySet()) { |
|
writer.putInt(token); |
|
writer.putInt(40); |
|
} |
|
} |
|
|
|
public static boolean useMobPowerA(PerformActionMsg msg, Mob caster) { |
|
if (caster == null) |
|
return false; |
|
|
|
|
|
if (!caster.isAlive() && msg.getPowerUsedID() != 428589216) //succor |
|
return false; |
|
|
|
// get power |
|
PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); |
|
if (pb == null) |
|
return true; |
|
|
|
// Check powers for normal users |
|
// get numTrains for power |
|
int trains = msg.getNumTrains(); |
|
|
|
// can't go over the max trains for the power, unless CSR |
|
// can't go over total trains by player |
|
// get recycle time in ms |
|
int time = pb.getRecycleTime(trains); |
|
|
|
// verify player is in correct mode (combat/nonCombat) |
|
// verify player is not stunned or prohibited from casting |
|
PlayerBonuses bonus = caster.getBonuses(); |
|
SourceType sourceType = SourceType.GetSourceType(pb.getCategory()); |
|
if (bonus != null && (bonus.getBool(ModType.Stunned, SourceType.None) || bonus.getBool(ModType.CannotCast, SourceType.None) || bonus.getBool(ModType.BlockedPowerType, sourceType))) |
|
return true; |
|
|
|
// if moving make sure spell valid for movement |
|
// if flying, make sure spell valid for flying. |
|
// if (pc.getAltitude() > 0) |
|
// if (!pb.canCastWhileFlying()) |
|
// return true; |
|
int targetLiveCounter = -1; |
|
|
|
// get target based on targetType; |
|
if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target |
|
if (pb.isAOE()) { |
|
if (!pb.usePointBlank()) { |
|
AbstractWorldObject target = getTarget(msg); |
|
|
|
|
|
if (target == null) { |
|
|
|
if (caster.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb |
|
.getRange())) |
|
return true; |
|
} else if (verifyInvalidRange(caster, target, pb.getRange())) |
|
// pc.getLoc().distance(target.getLoc()) > |
|
// pb.getRange()) |
|
return true; |
|
} |
|
} else { |
|
// get target |
|
AbstractWorldObject target = getTarget(msg); |
|
|
|
if (target == null) |
|
return true; |
|
|
|
// verify target is in range |
|
if (verifyInvalidRange(caster, target, pb.getRange())) |
|
// (pc.getLoc().distance(target.getLoc()) > pb.getRange()) { |
|
// TODO send message that target is out of range |
|
return true; |
|
|
|
// verify target is valid type |
|
if (AbstractWorldObject.IsAbstractCharacter(target)) |
|
targetLiveCounter = ((AbstractCharacter) target).getLiveCounter(); |
|
} |
|
|
|
// TODO if target immune to powers, cancel unless aoe |
|
// Also make sure No.ImmuneToPowers is false for target |
|
// if there's a different power waiting to finish, stop here |
|
if (caster.getLastMobPowerToken() != 0) |
|
return true; |
|
|
|
//get power cost and calculate any power cost modifiers |
|
// Validity checks passed, move on to casting spell |
|
//get caster's live counter |
|
int casterLiveCounter = caster.getLiveCounter(); |
|
|
|
// run recycle job for when cast is available again, don't bother adding the timer for CSRs |
|
// Send cast to other players |
|
if (caster.getObjectTypeMask() == MBServerStatics.MASK_UNDEAD) |
|
msg.setUnknown05(0); // Regular Race, use mana? |
|
else |
|
msg.setUnknown05(0); |
|
|
|
int tr = msg.getNumTrains(); |
|
|
|
msg.setNumTrains(9999); |
|
|
|
DispatchMessage.sendToAllInRange(caster, msg); |
|
DispatchMessage.sendToAllInRange(caster, msg); |
|
|
|
msg.setNumTrains(tr); |
|
|
|
// make person casting stand up if spell (unless they're casting a chant which does not make them stand up) |
|
// update cast (use skill) fail condition |
|
caster.cancelOnCast(); |
|
|
|
// update castSpell (use spell) fail condition if spell |
|
if (pb.isSpell()) |
|
caster.cancelOnSpell(); |
|
|
|
// get cast time in ms. |
|
time = pb.getCastTime(trains); |
|
|
|
// set player is casting for regens |
|
caster.setIsCasting(true); |
|
caster.setLastMobPowerToken(pb.getToken()); |
|
|
|
// run timer job to end cast |
|
if (time < 1 || pb.getToken() == -1994153779) { |
|
// run immediately |
|
finishUseMobPower(msg, caster, casterLiveCounter, targetLiveCounter); |
|
caster.setLastMobPowerToken(0); |
|
} else { |
|
caster.setLastMobPowerToken(pb.getToken()); |
|
caster.setTimeStamp("FinishCast", System.currentTimeMillis() + (pb.getCastTime(trains))); |
|
} |
|
// finishUseMobPower(msg, caster, casterLiveCounter, targetLiveCounter); // UseMobPowerJob upj = new UseMobPowerJob(caster, msg, msg.getPowerUsedID(), pb, casterLiveCounter, targetLiveCounter); |
|
// JobContainer jc = js.scheduleJob(upj, time); |
|
// // make lastPower |
|
|
|
|
|
return false; |
|
} |
|
|
|
// called when a spell finishes casting. perform actions |
|
public static void finishUsePower(final PerformActionMsg msg, PlayerCharacter playerCharacter, int casterLiveCounter, int targetLiveCounter) { |
|
|
|
PerformActionMsg performActionMsg; |
|
Dispatch dispatch; |
|
|
|
if (playerCharacter == null || msg == null) |
|
return; |
|
|
|
if (playerCharacter.isCasting()) { |
|
playerCharacter.update(); |
|
playerCharacter.updateStamRegen(-100); |
|
} |
|
|
|
playerCharacter.resetLastSetLocUpdate(); |
|
playerCharacter.setIsCasting(false); |
|
// can't go over total trains by player |
|
|
|
|
|
if (!playerCharacter.isAlive() || playerCharacter.getLiveCounter() != casterLiveCounter) { |
|
playerCharacter.clearLastPower(); |
|
finishRecycleTime(msg.getPowerUsedID(), playerCharacter, true); |
|
|
|
|
|
// Let's do good OO. Clone message don't modify it. |
|
|
|
performActionMsg = new PerformActionMsg(msg); |
|
performActionMsg.setNumTrains(9999); |
|
performActionMsg.setUnknown04(2); |
|
|
|
dispatch = Dispatch.borrow(playerCharacter, performActionMsg); |
|
DispatchMessage.dispatchMsgToInterestArea(playerCharacter, performActionMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
return; |
|
} |
|
|
|
// set player is not casting for regens |
|
|
|
|
|
// clear power. |
|
playerCharacter.clearLastPower(); |
|
|
|
PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); |
|
|
|
if (pb == null) { |
|
Logger.error( |
|
"finishUsePower(): Power '" + msg.getPowerUsedID() |
|
+ "' was not found on powersBaseByToken map."); |
|
return; |
|
} |
|
|
|
int trains = msg.getNumTrains(); |
|
|
|
// update used power timer |
|
playerCharacter.setLastUsedPowerTime(); |
|
|
|
// verify player is not stunned or power type is blocked |
|
PlayerBonuses bonus = playerCharacter.getBonuses(); |
|
|
|
if (bonus != null) { |
|
if (bonus.getBool(ModType.Stunned, SourceType.None)) |
|
return; |
|
|
|
SourceType sourceType = SourceType.GetSourceType(pb.getCategory()); |
|
if (bonus.getBool(ModType.BlockedPowerType, sourceType)) { |
|
finishRecycleTime(msg.getPowerUsedID(), playerCharacter, true); |
|
return; |
|
} |
|
} |
|
|
|
// get target loc |
|
Vector3fImmutable targetLoc = msg.getTargetLoc(); |
|
|
|
|
|
if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target |
|
if (pb.isAOE()) { |
|
if (!pb.usePointBlank()) { |
|
AbstractWorldObject mainTarget = getTarget(msg); |
|
|
|
float speedRange = 0; |
|
if (AbstractWorldObject.IsAbstractCharacter(mainTarget)) { |
|
speedRange = ((AbstractCharacter) mainTarget).getSpeed() * (pb.getCastTime(trains) * .001f); |
|
} |
|
|
|
if (mainTarget != null && mainTarget.getObjectType() == GameObjectType.Building && !pb.targetBuilding()) { |
|
PowersManager.sendPowerMsg(playerCharacter, 8, new PerformActionMsg(msg)); |
|
return; |
|
} |
|
|
|
if (mainTarget == null) { |
|
|
|
if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb |
|
.getRange())) { |
|
sendPowerMsg(playerCharacter, 8, msg); |
|
return; |
|
} |
|
} else if (verifyInvalidRange(playerCharacter, mainTarget, speedRange + pb.getRange())) { |
|
|
|
sendPowerMsg(playerCharacter, 8, msg); |
|
return; |
|
} |
|
} |
|
} else { |
|
|
|
// get target |
|
AbstractWorldObject mainTarget = getTarget(msg); |
|
|
|
if (mainTarget == null) |
|
return; |
|
|
|
float speedRange = 0; |
|
if (AbstractWorldObject.IsAbstractCharacter(mainTarget)) { |
|
speedRange = ((AbstractCharacter) mainTarget).getSpeed() * (pb.getCastTime(trains) * .001f); |
|
} |
|
float range = pb.getRange() + speedRange; |
|
|
|
|
|
if (verifyInvalidRange(playerCharacter, mainTarget, range)) { |
|
|
|
sendPowerMsg(playerCharacter, 8, msg); |
|
return; |
|
} |
|
// (pc.getLoc().distance(target.getLoc()) > pb.getRange()) { |
|
// TODO send message that target is out of range |
|
|
|
|
|
} |
|
|
|
if (targetLoc.x == 0f || targetLoc.z == 0f) { |
|
AbstractWorldObject tar = getTarget(msg); |
|
if (tar != null) |
|
targetLoc = tar.getLoc(); |
|
} |
|
|
|
|
|
// get list of targets |
|
HashSet<AbstractWorldObject> allTargets = getAllTargets( |
|
getTarget(msg), msg.getTargetLoc(), playerCharacter, pb); |
|
|
|
// no targets found. send error message |
|
|
|
if (allTargets.size() == 0) { |
|
sendPowerMsg(playerCharacter, 9, msg); |
|
return; |
|
} |
|
|
|
playerCharacter.setHateValue(pb.getHateValue(trains)); |
|
|
|
//Send Cast Message. |
|
// PerformActionMsg castMsg = new PerformActionMsg(msg); |
|
// castMsg.setNumTrains(9999); |
|
// castMsg.setUnknown04(3); |
|
// DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
// |
|
boolean msgCasted = false; |
|
|
|
for (AbstractWorldObject target : allTargets) { |
|
|
|
if (target == null) |
|
continue; |
|
|
|
//Hacky Pyschic healing cross heal |
|
|
|
//make sure target hasn't respawned since we began casting |
|
//skip this if liveCounter = -1 (from aoe) |
|
|
|
if (targetLiveCounter != -1) |
|
if (AbstractWorldObject.IsAbstractCharacter(target)) |
|
if (targetLiveCounter != ((AbstractCharacter) target).getLiveCounter()) |
|
continue; |
|
|
|
if (!target.isAlive() && target.getObjectType() != GameObjectType.Building && pb.getToken() != 428589216 && pb.getToken() != 429425915) |
|
continue; |
|
|
|
//make sure mob is awake to respond. |
|
//if (target instanceof AbstractIntelligenceAgent) |
|
//((AbstractIntelligenceAgent)target).enableIntelligence(); |
|
// If Hit roll required, test hit |
|
|
|
boolean miss = false; |
|
if (pb.requiresHitRoll() && !pb.isWeaponPower() && testAttack(playerCharacter, target, pb, msg)) { |
|
miss = true; |
|
//aggro mob even on a miss |
|
if (target.getObjectType() == GameObjectType.Mob) { |
|
Mob mobTarget = (Mob) target; |
|
if (pb.isHarmful()) |
|
mobTarget.handleDirectAggro(playerCharacter); |
|
} |
|
continue; |
|
} |
|
if (target.getObjectType() == GameObjectType.Mob) { |
|
Mob mobTarget = (Mob) target; |
|
if (pb.isHarmful()) |
|
mobTarget.handleDirectAggro(playerCharacter); |
|
} |
|
//Power is aiding a target, handle aggro if combat target is a Mob. |
|
if (!pb.isHarmful() && target.getObjectType() == GameObjectType.PlayerCharacter) { |
|
PlayerCharacter pcTarget = (PlayerCharacter) target; |
|
if (!pb.isHarmful()) |
|
Mob.HandleAssistedAggro(playerCharacter, pcTarget); |
|
} |
|
|
|
// update target of used power timer |
|
|
|
if (pb.isHarmful()) |
|
if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && target.getObjectUUID() != playerCharacter.getObjectUUID()) { |
|
|
|
((PlayerCharacter) target).setLastTargetOfUsedPowerTime(); |
|
((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis()); |
|
playerCharacter.setTimeStamp("LastCombatPlayer", System.currentTimeMillis()); |
|
} |
|
|
|
|
|
//Player didn't miss power, send finish cast. Miss cast already sent. |
|
|
|
|
|
// finally Apply actions |
|
for (ActionsBase ab : pb.getActions()) { |
|
// get numTrains for power, skip action if invalid |
|
|
|
if (trains < ab.getMinTrains() || trains > ab.getMaxTrains()) |
|
continue; |
|
// If something blocks the action, then stop |
|
|
|
if (ab.blocked(target, pb, trains)) { |
|
|
|
PowersManager.sendEffectMsg(playerCharacter, 5, ab, pb); |
|
continue; |
|
} |
|
|
|
// TODO handle overwrite stack order here |
|
String stackType = ab.getStackType(); |
|
stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType; |
|
// if (!stackType.equals("IgnoreStack")) { |
|
if (target.getEffects().containsKey(stackType)) { |
|
// remove any existing power that overrides |
|
Effect ef = target.getEffects().get(stackType); |
|
AbstractEffectJob effect = null; |
|
if (ef != null) { |
|
JobContainer jc = ef.getJobContainer(); |
|
if (jc != null) |
|
effect = (AbstractEffectJob) jc.getJob(); |
|
} |
|
ActionsBase overwrite = effect.getAction(); |
|
|
|
if (overwrite == null) { |
|
Logger.error("NULL ACTION FOR POWER " + effect.getPowerToken()); |
|
continue; |
|
} |
|
|
|
if (ab.getStackOrder() < overwrite.getStackOrder()) |
|
continue; // not high enough to overwrite |
|
else if (ab.getStackOrder() > overwrite.getStackOrder()) { |
|
effect.setNoOverwrite(true); |
|
removeEffect(target, overwrite, true, false); |
|
} else if (ab.getStackOrder() == overwrite.getStackOrder()) |
|
if (ab.greaterThanEqual() |
|
&& (trains >= effect.getTrains())) { |
|
effect.setNoOverwrite(true); |
|
removeEffect(target, overwrite, true, false); |
|
} else if (ab.always()) |
|
removeEffect(target, overwrite, true, false); |
|
else if (ab.greaterThan() |
|
&& (trains > effect.getTrains())) { |
|
effect.setNoOverwrite(true); |
|
removeEffect(target, overwrite, true, false); |
|
} else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken()) |
|
removeEffect(target, overwrite, true, false); |
|
|
|
else |
|
continue; // trains not high enough to overwrite |
|
|
|
} |
|
|
|
// } |
|
|
|
|
|
runPowerAction(playerCharacter, target, targetLoc, ab, trains, pb); |
|
if (!miss && !msgCasted) { |
|
PerformActionMsg castMsg = new PerformActionMsg(msg); |
|
castMsg.setNumTrains(9999); |
|
castMsg.setUnknown04(2); |
|
DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
msgCasted = true; |
|
} |
|
} |
|
} |
|
|
|
if (!msgCasted) { |
|
PerformActionMsg castMsg = new PerformActionMsg(msg); |
|
castMsg.setNumTrains(9999); |
|
castMsg.setUnknown04(2); |
|
DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
msgCasted = true; |
|
} |
|
|
|
//Handle chant |
|
if (pb != null && pb.isChant()) |
|
for (ActionsBase ab : pb.getActions()) { |
|
AbstractPowerAction pa = ab.getPowerAction(); |
|
if (pa != null) |
|
if (pb.getToken() != 428950414 && pb.getToken() != 428884878) |
|
pa.handleChant(playerCharacter, playerCharacter, targetLoc, trains, ab, pb); |
|
else if (PowersManager.getTarget(msg) != null && PowersManager.getTarget(msg).isAlive()) |
|
pa.handleChant(playerCharacter, PowersManager.getTarget(msg), targetLoc, trains, ab, pb); |
|
else |
|
pa.handleChant(playerCharacter, null, targetLoc, trains, ab, pb); |
|
} |
|
|
|
|
|
//DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
|
|
|
|
} |
|
|
|
public static void finishUseMobPower(PerformActionMsg msg, Mob caster, int casterLiveCounter, int targetLiveCounter) { |
|
|
|
if (caster == null || msg == null) |
|
return; |
|
|
|
if (!caster.isAlive() || caster.getLiveCounter() != casterLiveCounter) |
|
return; |
|
|
|
// set player is not casting for regens |
|
caster.setIsCasting(false); |
|
|
|
|
|
PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID()); |
|
// clear power. |
|
caster.setLastMobPowerToken(0); |
|
|
|
if (pb == null) { |
|
Logger.error( |
|
"finishUsePower(): Power '" + msg.getPowerUsedID() |
|
+ "' was not found on powersBaseByToken map."); |
|
return; |
|
} |
|
|
|
int trains = msg.getNumTrains(); |
|
|
|
// update used power timer |
|
// verify player is not stunned or power type is blocked |
|
PlayerBonuses bonus = caster.getBonuses(); |
|
if (bonus != null) { |
|
if (bonus.getBool(ModType.Stunned, SourceType.None)) |
|
return; |
|
SourceType sourceType = SourceType.GetSourceType(pb.getCategory()); |
|
if (bonus.getBool(ModType.BlockedPowerType, sourceType)) |
|
return; |
|
} |
|
|
|
msg.setNumTrains(9999); |
|
msg.setUnknown04(2); |
|
DispatchMessage.sendToAllInRange(caster, msg); |
|
|
|
// get target loc |
|
Vector3fImmutable targetLoc = msg.getTargetLoc(); |
|
if (targetLoc.x == 0f || targetLoc.z == 0f) { |
|
AbstractWorldObject tar = getTarget(msg); |
|
if (tar != null) |
|
targetLoc = tar.getLoc(); |
|
} |
|
|
|
// get list of targets |
|
HashSet<AbstractWorldObject> allTargets = getAllTargets( |
|
getTarget(msg), msg.getTargetLoc(), caster, pb); |
|
for (AbstractWorldObject target : allTargets) { |
|
|
|
if (target == null) |
|
continue; |
|
|
|
|
|
//make sure target hasn't respawned since we began casting |
|
//skip this if liveCounter = -1 (from aoe) |
|
if (targetLiveCounter != -1) |
|
if (AbstractWorldObject.IsAbstractCharacter(target)) |
|
if (targetLiveCounter != ((AbstractCharacter) target).getLiveCounter()) |
|
continue; |
|
|
|
//make sure mob is awake to respond. |
|
//if (target instanceof AbstractIntelligenceAgent) |
|
//((AbstractIntelligenceAgent)target).enableIntelligence(); |
|
// If Hit roll required, test hit |
|
if (pb.requiresHitRoll() && !pb.isWeaponPower() && testAttack(caster, target, pb, msg)) |
|
//aggro mob even on a miss |
|
continue; |
|
|
|
// update target of used power timer |
|
|
|
if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) { |
|
|
|
((PlayerCharacter) target).setLastTargetOfUsedPowerTime(); |
|
((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis()); |
|
} |
|
|
|
|
|
// finally Apply actions |
|
for (ActionsBase ab : pb.getActions()) { |
|
// get numTrains for power, skip action if invalid |
|
|
|
if (trains < ab.getMinTrains() || trains > ab.getMaxTrains()) |
|
continue; |
|
// If something blocks the action, then stop |
|
|
|
if (ab.blocked(target, pb, trains)) |
|
continue; |
|
// TODO handle overwrite stack order here |
|
String stackType = ab.getStackType(); |
|
stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType; |
|
// if (!stackType.equals("IgnoreStack")) { |
|
if (target.getEffects().containsKey(stackType)) { |
|
// remove any existing power that overrides |
|
Effect ef = target.getEffects().get(stackType); |
|
AbstractEffectJob effect = null; |
|
if (ef != null) { |
|
JobContainer jc = ef.getJobContainer(); |
|
if (jc != null) |
|
effect = (AbstractEffectJob) jc.getJob(); |
|
} |
|
ActionsBase overwrite = effect.getAction(); |
|
|
|
if (overwrite == null) { |
|
Logger.error("NULL ACTION FOR EFFECT " + effect.getPowerToken()); |
|
continue; |
|
} |
|
if (ab.getStackOrder() < overwrite.getStackOrder()) |
|
continue; // not high enough to overwrite |
|
else if (ab.getStackOrder() > overwrite.getStackOrder()) { |
|
effect.setNoOverwrite(true); |
|
removeEffect(target, overwrite, true, false); |
|
} else if (ab.getStackOrder() == overwrite.getStackOrder()) |
|
if (ab.greaterThanEqual() |
|
&& (trains >= effect.getTrains())) { |
|
effect.setNoOverwrite(true); |
|
removeEffect(target, overwrite, true, false); |
|
} else if (ab.always()) |
|
removeEffect(target, overwrite, true, false); |
|
else if (ab.greaterThan() |
|
&& (trains > effect.getTrains())) { |
|
effect.setNoOverwrite(true); |
|
removeEffect(target, overwrite, true, false); |
|
} else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken()) |
|
removeEffect(target, overwrite, true, false); |
|
|
|
else |
|
continue; // trains not high enough to overwrite |
|
} |
|
|
|
// } |
|
runPowerAction(caster, target, targetLoc, ab, trains, pb); |
|
} |
|
} |
|
|
|
//Handle chant |
|
if (pb != null && pb.isChant()) |
|
for (ActionsBase ab : pb.getActions()) { |
|
AbstractPowerAction pa = ab.getPowerAction(); |
|
if (pa != null) |
|
pa.handleChant(caster, caster, targetLoc, trains, ab, pb); |
|
} |
|
|
|
// TODO echo power use to everyone else |
|
msg.setNumTrains(9999); |
|
msg.setUnknown04(2); |
|
DispatchMessage.sendToAllInRange(caster, msg); |
|
|
|
} |
|
|
|
// *** Refactor : Wtf is this mess? |
|
|
|
private static boolean validMonsterType(AbstractWorldObject target, PowersBase pb) { |
|
|
|
if (pb == null || target == null) |
|
return false; |
|
|
|
String mtp = pb.getMonsterTypePrereq(); |
|
|
|
if (mtp.length() == 0) |
|
return true; |
|
|
|
if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) { |
|
|
|
PlayerCharacter pc = (PlayerCharacter) target; |
|
int raceID = 0; |
|
|
|
if (pc.getRace() != null) |
|
raceID = pc.getRace().getRaceRuneID(); |
|
|
|
switch (mtp) { |
|
case "Shade": |
|
return raceID == 2015 || raceID == 2016; |
|
case "Elf": |
|
return raceID == 2008 || raceID == 2009; |
|
case "Dwarf": |
|
return raceID == 2006; |
|
case "Aracoix": |
|
return raceID == 2002 || raceID == 2003; |
|
case "Irekei": |
|
return raceID == 2013 || raceID == 2014; |
|
case "Vampire": |
|
return raceID == 2028 || raceID == 2029; |
|
} |
|
} else if (target.getObjectType().equals(GameObjectType.Mob)) { |
|
Mob mob = (Mob) target; |
|
|
|
if (pb.targetMob() && !mob.isMob() && !mob.isSiege()) |
|
return false; |
|
else if (pb.targetPet() && !mob.isPet() && !mob.isSiege()) |
|
return false; |
|
|
|
switch (mtp) { |
|
case "Animal": |
|
if ((mob.getObjectTypeMask() & MBServerStatics.MASK_BEAST) == 0) |
|
return false; |
|
break; |
|
case "NPC": |
|
if ((mob.getObjectTypeMask() & MBServerStatics.MASK_HUMANOID) == 0) |
|
return false; |
|
break; |
|
case "Rat": |
|
if ((mob.getObjectTypeMask() & MBServerStatics.MASK_RAT) == 0) |
|
return false; |
|
break; |
|
case "Siege": |
|
if (!mob.isSiege()) |
|
return false; |
|
break; |
|
case "Undead": |
|
if ((mob.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) == 0) |
|
return false; |
|
break; |
|
} |
|
return true; |
|
} else |
|
return target.getObjectType().equals(GameObjectType.Building) && mtp.equals("Siege"); |
|
return false; |
|
} |
|
|
|
public static void summon(SendSummonsRequestMsg msg, ClientConnection origin) { |
|
PlayerCharacter pc = SessionManager.getPlayerCharacter( |
|
origin); |
|
if (pc == null) |
|
return; |
|
|
|
PlayerCharacter target = SessionManager |
|
.getPlayerCharacterByLowerCaseName(msg.getTargetName()); |
|
if (target == null || target.equals(pc) || target.isCombat()) { |
|
|
|
if (target == null) // Player not found. Send not found message |
|
ChatManager.chatInfoError(pc, |
|
"Cannot find that player to summon."); |
|
else if (target.isCombat()) |
|
ChatManager.chatInfoError(pc, |
|
"Cannot summon player in combat."); |
|
// else trying to summon self, just fail |
|
|
|
// recycle summon |
|
sendRecyclePower(msg.getPowerToken(), origin); |
|
|
|
// TODO: client already subtracted 200 mana.. need to correct it |
|
// end cast |
|
PerformActionMsg pam = new PerformActionMsg(msg.getPowerToken(), |
|
msg.getTrains(), msg.getSourceType(), msg.getSourceID(), 0, |
|
0, 0f, 0f, 0f, 1, 0); |
|
sendPowerMsg(pc, 2, pam); |
|
|
|
return; |
|
} |
|
|
|
PerformActionMsg pam = new PerformActionMsg(msg.getPowerToken(), msg |
|
.getTrains(), msg.getSourceType(), msg.getSourceID(), target |
|
.getObjectType().ordinal(), target.getObjectUUID(), 0f, 0f, 0f, 1, 0); |
|
|
|
// Client removes 200 mana on summon use.. so don't send message to self |
|
target.addSummoner(pc.getObjectUUID(), System.currentTimeMillis() + MBServerStatics.FOURTYFIVE_SECONDS); |
|
usePower(pam, origin, false); |
|
} |
|
|
|
public static void recvSummon(RecvSummonsRequestMsg msg, ClientConnection origin) { |
|
PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); |
|
if (pc == null) |
|
return; |
|
|
|
PlayerCharacter source = PlayerCharacter.getFromCache(msg.getSourceID()); |
|
if (source == null) |
|
return; |
|
|
|
long tooLate = pc.getSummoner(source.getObjectUUID()); |
|
if (tooLate < System.currentTimeMillis()) { |
|
ChatManager.chatInfoError(pc, "You waited too long to " + (msg.accepted() ? "accept" : "decline") + " the summons."); |
|
pc.removeSummoner(source.getObjectUUID()); |
|
return; |
|
} |
|
|
|
if (pc.getBonuses() != null && pc.getBonuses().getBool(ModType.BlockedPowerType, SourceType.SUMMON)) { |
|
ErrorPopupMsg.sendErrorMsg(pc, "You have been blocked from receiving summons!"); |
|
ErrorPopupMsg.sendErrorMsg(source, "Target is blocked from receiving summons!"); |
|
pc.removeSummoner(source.getObjectUUID()); |
|
return; |
|
} |
|
pc.removeSummoner(source.getObjectUUID()); |
|
|
|
// Handle Accepting or Denying a summons. |
|
// set timer based on summon type. |
|
boolean wentThrough = false; |
|
if (msg.accepted()) |
|
// summons accepted, let's move the player if within time |
|
if (source.isAlive()) { |
|
|
|
// //make sure summons handled in time |
|
ConcurrentHashMap<String, JobContainer> timers = source.getTimers(); |
|
// if (timers == null || !timers.containsKey("SummonSend")) { |
|
// ChatManager.chatInfoError(pc, "You waited too long to " + (msg.accepted() ? "accept" : "decline") + " the summons."); |
|
// return; |
|
// } |
|
|
|
// // clear last summons accept timer |
|
// timers.get("SummonSend").cancelJob(); |
|
//timers.remove("SummonSend"); |
|
// cancel any other summons waiting |
|
timers = pc.getTimers(); |
|
if (timers != null && timers.containsKey("Summon")) |
|
timers.get("Summon").cancelJob(); |
|
|
|
// get time to wait before summons goes through |
|
BaseClass base = source.getBaseClass(); |
|
PromotionClass promo = source.getPromotionClass(); |
|
int duration; |
|
|
|
|
|
//determine if in combat with another player |
|
|
|
|
|
//comment out this block to disable combat timer |
|
// if (lastAttacked < 60000) { |
|
// if (pc.inSafeZone()) //player in safe zone, no need for combat timer |
|
// combat = false; |
|
// else if (source.inSafeZone()) //summoner in safe zone, apply combat timer |
|
// combat = true; |
|
// else if ((source.getLoc().distance2D(pc.getLoc())) > 6144f) |
|
// combat = true; //more than 1.5x width of zone, not tactical summons |
|
// } |
|
|
|
if (promo != null && promo.getObjectUUID() == 2519) |
|
duration = 10000; // Priest summons, 10 seconds |
|
else if (base != null && base.getObjectUUID() == 2501) |
|
duration = 15000; // Healer Summons, 15 seconds |
|
else |
|
duration = 45000; // Belgosh Summons, 45 seconds |
|
|
|
|
|
// Teleport to summoners location |
|
FinishSummonsJob fsj = new FinishSummonsJob(source, pc); |
|
JobContainer jc = JobScheduler.getInstance().scheduleJob(fsj, |
|
duration); |
|
if (timers != null) |
|
timers.put("Summon", jc); |
|
wentThrough = true; |
|
} |
|
|
|
// Summons failed |
|
if (!wentThrough) |
|
// summons refused. Let's be nice and reset recycle timer |
|
if (source != null) { |
|
|
|
// Send summons refused Message |
|
ErrorPopupMsg.sendErrorPopup(source, 29); |
|
|
|
// recycle summons power |
|
//finishRecycleTime(428523680, source, true); |
|
} |
|
} |
|
|
|
public static void trackWindow(TrackWindowMsg msg, ClientConnection origin) { |
|
|
|
PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter( |
|
origin); |
|
|
|
if (playerCharacter == null) |
|
return; |
|
|
|
if (MBServerStatics.POWERS_DEBUG) { |
|
ChatManager.chatSayInfo( |
|
playerCharacter, |
|
"Using Power: " + Integer.toHexString(msg.getPowerToken()) |
|
+ " (" + msg.getPowerToken() + ')'); |
|
Logger.info("Using Power: " |
|
+ Integer.toHexString(msg.getPowerToken()) + " (" |
|
+ msg.getPowerToken() + ')'); |
|
} |
|
|
|
// get track power used |
|
PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerToken()); |
|
|
|
if (pb == null || !pb.isTrack()) |
|
return; |
|
|
|
//check track threshold timer to prevent spam |
|
long currentTime = System.currentTimeMillis(); |
|
long timestamp = playerCharacter.getTimeStamp("trackWindow"); |
|
long dif = currentTime - timestamp; |
|
if (dif < MBServerStatics.TRACK_WINDOW_THRESHOLD) |
|
return; |
|
playerCharacter.setTimeStamp("trackWindow", currentTime); |
|
|
|
ArrayList<ActionsBase> ablist = pb.getActions(); |
|
if (ablist == null) |
|
return; |
|
|
|
TrackPowerAction tpa = null; |
|
for (ActionsBase ab : ablist) { |
|
AbstractPowerAction apa = ab.getPowerAction(); |
|
if (apa != null && apa instanceof TrackPowerAction) |
|
tpa = (TrackPowerAction) apa; |
|
} |
|
if (tpa == null) |
|
return; |
|
|
|
// Check powers for normal users |
|
if (playerCharacter.getPowers() == null || !playerCharacter.getPowers().containsKey(msg.getPowerToken())) |
|
if (!playerCharacter.isCSR()) |
|
if (!MBServerStatics.POWERS_DEBUG) { |
|
// ChatManager.chatSayInfo(pc, "You may not cast that spell!"); |
|
// this.logEXPLOIT("usePowerA(): Cheat attempted? '" + msg.getPowerToken() + "' was not associated with " + pc.getName()); |
|
return; |
|
} |
|
|
|
// Get search mask for track |
|
int mask = 0; |
|
if (pb.targetPlayer()) |
|
if (tpa.trackVampire()) // track vampires |
|
mask = MBServerStatics.MASK_PLAYER | MBServerStatics.MASK_UNDEAD; |
|
else |
|
// track all players |
|
mask = MBServerStatics.MASK_PLAYER; |
|
else if (pb.targetCorpse()) // track corpses |
|
mask = MBServerStatics.MASK_CORPSE; |
|
else if (tpa.trackNPC()) // Track NPCs |
|
mask = MBServerStatics.MASK_NPC; |
|
else if (tpa.trackUndead()) // Track Undead |
|
mask = MBServerStatics.MASK_MOB | MBServerStatics.MASK_UNDEAD; |
|
else |
|
// Track All |
|
mask = MBServerStatics.MASK_MOB | MBServerStatics.MASK_NPC; |
|
|
|
// Find characters in range |
|
HashSet<AbstractWorldObject> allTargets; |
|
allTargets = WorldGrid.getObjectsInRangeContains(playerCharacter.getLoc(), |
|
pb.getRange(), mask); |
|
|
|
//remove anyone who can't be tracked |
|
Iterator<AbstractWorldObject> it = allTargets.iterator(); |
|
while (it.hasNext()) { |
|
AbstractWorldObject awo = it.next(); |
|
if (awo == null) |
|
continue; |
|
else if (!awo.isAlive()) |
|
it.remove(); |
|
else if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { |
|
PlayerBonuses bonus = ((PlayerCharacter) awo).getBonuses(); |
|
if (bonus != null && bonus.getBool(ModType.CannotTrack, SourceType.None)) |
|
it.remove(); |
|
} |
|
} |
|
|
|
// get max charcters for window |
|
int maxTargets = 20; |
|
PromotionClass promo = playerCharacter.getPromotionClass(); |
|
if (promo != null) { |
|
int tableID = promo.getObjectUUID(); |
|
if (tableID == 2512 || tableID == 2514 || tableID == 2515) |
|
maxTargets = 40; |
|
} |
|
|
|
// create list of characters |
|
HashSet<AbstractCharacter> trackChars = RangeBasedAwo.getTrackList( |
|
allTargets, playerCharacter, maxTargets); |
|
|
|
TrackWindowMsg trackWindowMsg = new TrackWindowMsg(msg); |
|
|
|
// send track window |
|
trackWindowMsg.setSource(playerCharacter); |
|
trackWindowMsg.setCharacters(trackChars); |
|
|
|
Dispatch dispatch = Dispatch.borrow(playerCharacter, trackWindowMsg); |
|
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); |
|
|
|
} |
|
|
|
private static void sendRecyclePower(int token, ClientConnection origin) { |
|
RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token); |
|
|
|
Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg); |
|
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); |
|
|
|
} |
|
|
|
public static boolean verifyInvalidRange(AbstractCharacter ac, |
|
AbstractWorldObject target, float range) { |
|
Vector3fImmutable sl = ac.getLoc(); |
|
Vector3fImmutable tl = target.getLoc(); |
|
if (target.getObjectType().equals(GameObjectType.Item)) { |
|
|
|
Item item = (Item) target; |
|
AbstractGameObject owner = item.getOwner(); |
|
|
|
if (owner == null || owner.getObjectType().equals(GameObjectType.Account)) |
|
return true; |
|
|
|
if (owner.getObjectType().equals(GameObjectType.PlayerCharacter) || owner.getObjectType().equals(GameObjectType.Mob)) { |
|
AbstractCharacter acOwner = (AbstractCharacter) owner; |
|
CharacterItemManager itemMan = acOwner.getCharItemManager(); |
|
if (itemMan == null) |
|
return true; |
|
if (itemMan.inventoryContains(item)) { |
|
tl = acOwner.getLoc(); |
|
return !(sl.distanceSquared(tl) <= sqr(range)); |
|
} |
|
return true; |
|
} |
|
return true; |
|
} |
|
|
|
range += (calcHitBox(ac) + calcHitBox(target)); |
|
|
|
|
|
float distanceToTarget = sl.distanceSquared(tl);//distance to center of target |
|
|
|
return distanceToTarget > range * range; |
|
|
|
} |
|
|
|
public static float calcHitBox(AbstractWorldObject ac) { |
|
//TODO Figure out how Str Affects HitBox |
|
float hitBox = 1; |
|
switch (ac.getObjectType()) { |
|
case PlayerCharacter: |
|
PlayerCharacter pc = (PlayerCharacter) ac; |
|
if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) |
|
Logger.info("Hit box radius for " + pc.getFirstName() + " is " + ((int) pc.statStrBase / 200f)); |
|
hitBox = 2f + (int) ((PlayerCharacter) ac).statStrBase / 50f; |
|
break; |
|
|
|
case Mob: |
|
Mob mob = (Mob) ac; |
|
if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) |
|
Logger.info("Hit box radius for " + mob.getFirstName() |
|
+ " is " + ((Mob) ac).getMobBase().getHitBoxRadius()); |
|
|
|
hitBox = ((Mob) ac).getMobBase().getHitBoxRadius(); |
|
break; |
|
case Building: |
|
Building building = (Building) ac; |
|
if (building.getBlueprint() == null) |
|
return 32; |
|
hitBox = Math.max(building.getBlueprint().getBuildingGroup().getExtents().x, |
|
building.getBlueprint().getBuildingGroup().getExtents().y); |
|
if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) |
|
Logger.info("Hit box radius for " + building.getName() + " is " + hitBox); |
|
break; |
|
|
|
} |
|
return hitBox; |
|
} |
|
|
|
// Apply a power based on it's IDString |
|
public static void applyPower(AbstractCharacter ac, AbstractWorldObject target, |
|
Vector3fImmutable targetLoc, String ID, int trains, boolean fromItem) { |
|
if (ac == null || target == null || !ac.isAlive()) |
|
return; |
|
PowersBase pb = powersBaseByIDString.get(ID); |
|
if (pb == null) { |
|
Logger.error( |
|
"applyPower(): Got NULL on powersBaseByIDString table lookup for: " |
|
+ ID); |
|
return; |
|
} |
|
applyPowerA(ac, target, targetLoc, pb, trains, fromItem); |
|
} |
|
|
|
// Apply a power based on it's Token |
|
public static void applyPower(AbstractCharacter ac, AbstractWorldObject target, |
|
Vector3fImmutable targetLoc, int token, int trains, boolean fromItem) { |
|
if (ac == null || target == null) |
|
return; |
|
|
|
//Don't apply power if ac is dead, unless death shroud or safe mode |
|
if (!ac.isAlive()) |
|
if (!(token == -1661758934 || token == 1672601862)) |
|
return; |
|
|
|
PowersBase pb = powersBaseByToken.get(token); |
|
if (pb == null) { |
|
Logger.error( |
|
"applyPower(): Got NULL on powersBaseByToken table lookup for: " |
|
+ token); |
|
return; |
|
} |
|
applyPowerA(ac, target, targetLoc, pb, trains, fromItem); |
|
} |
|
|
|
private static void applyPowerA(AbstractCharacter ac, AbstractWorldObject target, |
|
Vector3fImmutable targetLoc, PowersBase pb, int trains, |
|
boolean fromItem) { |
|
int time = pb.getCastTime(trains); |
|
if (!fromItem) |
|
finishApplyPowerA(ac, target, targetLoc, pb, trains, false); |
|
else if (time == 0) |
|
finishApplyPower(ac, target, targetLoc, pb, trains, ac.getLiveCounter()); |
|
else { |
|
|
|
ac.setItemCasting(true); |
|
int tarType = (target == null) ? 0 : target.getObjectType().ordinal(); |
|
int tarID = (target == null) ? 0 : target.getObjectUUID(); |
|
|
|
// start the action animation |
|
PerformActionMsg msg = new PerformActionMsg(pb.getToken(), |
|
trains, ac.getObjectType().ordinal(), ac.getObjectUUID(), tarType, tarID, 0, |
|
0, 0, 1, 0); |
|
DispatchMessage.sendToAllInRange(target, msg); |
|
|
|
|
|
ConcurrentHashMap<String, JobContainer> timers = ac.getTimers(); |
|
|
|
if (timers.containsKey(Integer.toString(pb.getToken()))) { |
|
JobContainer jc = timers.get(Integer.toString(pb.getToken())); |
|
if (jc != null) |
|
jc.cancelJob(); |
|
} |
|
|
|
// // clear any other items being used |
|
// JobContainer jc = ac.getLastItem(); |
|
// if (jc != null) { |
|
// jc.cancelJob(); |
|
// ac.clearLastItem(); |
|
// } |
|
// run timer job to end cast |
|
UseItemJob uij = new UseItemJob(ac, target, pb, trains, ac.getLiveCounter()); |
|
JobContainer jc = js.scheduleJob(uij, time); |
|
|
|
// make lastItem |
|
timers.put(Integer.toString(pb.getToken()), jc); |
|
} |
|
} |
|
|
|
public static void finishApplyPower(AbstractCharacter ac, |
|
AbstractWorldObject target, Vector3fImmutable targetLoc, |
|
PowersBase pb, int trains, int liveCounter) { |
|
|
|
if (ac != null) |
|
ac.setItemCasting(false); |
|
if (ac == null || target == null || pb == null) |
|
return; |
|
|
|
ac.clearTimer(Integer.toString(pb.getToken())); |
|
if (liveCounter == ac.getLiveCounter()) |
|
finishApplyPowerA(ac, target, targetLoc, pb, trains, false); |
|
} |
|
|
|
public static void finishApplyPowerA(AbstractCharacter ac, |
|
AbstractWorldObject target, Vector3fImmutable targetLoc, |
|
PowersBase pb, int trains, boolean fromChant) { |
|
// finally Apply actions |
|
ArrayList<ActionsBase> actions = pb.getActions(); |
|
for (ActionsBase ab : actions) { |
|
// get numTrains for power, skip action if invalid |
|
if (trains < ab.getMinTrains() || trains > ab.getMaxTrains()) |
|
continue; |
|
// If something blocks the action, then stop |
|
if (ab.blocked(target, pb, trains)) |
|
// sendPowerMsg(pc, 5, msg); |
|
continue; |
|
// TODO handle overwrite stack order here |
|
String stackType = ab.getStackType(); |
|
stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType; |
|
if (target.getEffects().containsKey(stackType)) { |
|
// remove any existing power that overrides |
|
Effect ef = target.getEffects().get(stackType); |
|
AbstractEffectJob effect = null; |
|
if (ef != null) { |
|
JobContainer jc = ef.getJobContainer(); |
|
if (jc != null) |
|
effect = (AbstractEffectJob) jc.getJob(); |
|
} |
|
ActionsBase overwrite = effect.getAction(); |
|
PowersBase pbOverwrite = effect.getPower(); |
|
if (pbOverwrite != null && pbOverwrite.equals(pb) |
|
&& (trains >= effect.getTrains())) |
|
removeEffect(target, overwrite, true, fromChant); |
|
else if (ab.getStackOrder() < overwrite.getStackOrder()) |
|
continue; // not high enough to overwrite |
|
else if (ab.getStackOrder() > overwrite.getStackOrder()) |
|
removeEffect(target, overwrite, true, false); |
|
else if (ab.getStackOrder() == overwrite.getStackOrder()) |
|
if (ab.greaterThanEqual() |
|
&& (trains >= effect.getTrains())) |
|
removeEffect(target, overwrite, true, false); |
|
else if (ab.always()) |
|
removeEffect(target, overwrite, true, false); |
|
else if (ab.greaterThan() |
|
&& (trains > effect.getTrains())) |
|
removeEffect(target, overwrite, true, false); |
|
else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken()) |
|
removeEffect(target, overwrite, true, false); |
|
else |
|
continue; // trains not high enough to overwrite |
|
} |
|
if (fromChant) |
|
targetLoc = Vector3fImmutable.ZERO; |
|
runPowerAction(ac, target, targetLoc, ab, trains, pb); |
|
} |
|
|
|
//Handle chant |
|
if (pb != null && pb.isChant()) |
|
for (ActionsBase ab : pb.getActions()) { |
|
AbstractPowerAction pa = ab.getPowerAction(); |
|
if (pa != null) |
|
pa.handleChant(ac, target, targetLoc, trains, ab, pb); |
|
} |
|
|
|
// for chants, only send the animation if the character is not is not moving or casting |
|
boolean doAnimation = true; |
|
|
|
if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) { |
|
PlayerCharacter pc = (PlayerCharacter) target; |
|
if (pb != null && pb.isChant() && (pc.isMoving() || pc.isCasting())) |
|
doAnimation = false; |
|
} |
|
|
|
if (pb.getToken() == 428950414) |
|
doAnimation = true; |
|
|
|
if (doAnimation) { |
|
PerformActionMsg msg = new PerformActionMsg(pb.getToken(), 9999, ac |
|
.getObjectType().ordinal(), ac.getObjectUUID(), target.getObjectType().ordinal(), |
|
target.getObjectUUID(), 0, 0, 0, 2, 0); |
|
|
|
DispatchMessage.sendToAllInRange(ac, msg); |
|
|
|
} |
|
} |
|
|
|
public static void runPowerAction(AbstractCharacter source, |
|
AbstractWorldObject awo, Vector3fImmutable targetLoc, |
|
ActionsBase ab, int trains, PowersBase pb) { |
|
AbstractPowerAction pa = ab.getPowerAction(); |
|
if (pa == null) { |
|
Logger.error( |
|
"runPowerAction(): PowerAction not found of IDString: " |
|
+ ab.getEffectID()); |
|
return; |
|
} |
|
pa.startAction(source, awo, targetLoc, trains, ab, pb); |
|
} |
|
|
|
public static void runPowerAction(AbstractCharacter source, |
|
AbstractWorldObject awo, Vector3fImmutable targetLoc, |
|
ActionsBase ab, int trains, PowersBase pb, int duration) { |
|
AbstractPowerAction pa = ab.getPowerAction(); |
|
if (pa == null) { |
|
Logger.error( |
|
"runPowerAction(): PowerAction not found of IDString: " |
|
+ ab.getEffectID()); |
|
return; |
|
} |
|
pa.startAction(source, awo, targetLoc, trains, ab, pb, duration); |
|
} |
|
|
|
public static HashSet<AbstractWorldObject> getAllTargets( |
|
AbstractWorldObject target, Vector3fImmutable tl, |
|
PlayerCharacter pc, PowersBase pb) { |
|
HashSet<AbstractWorldObject> allTargets; |
|
if (pb.isAOE()) { |
|
Vector3fImmutable targetLoc = null; |
|
if (pb.usePointBlank()) { |
|
targetLoc = pc.getLoc(); |
|
} else { |
|
if (target != null) { |
|
targetLoc = target.getLoc(); |
|
} else { |
|
targetLoc = tl; |
|
try { |
|
targetLoc = targetLoc.setY(HeightMap.getWorldHeight(targetLoc)); //on ground |
|
} catch (Exception e) { |
|
Logger.error(e); |
|
targetLoc = tl; |
|
} |
|
|
|
} |
|
} |
|
|
|
if (targetLoc.x == 0f || targetLoc.z == 0f) |
|
return new HashSet<>(); // invalid loc, |
|
// return |
|
// nothing |
|
|
|
//first find targets in range quickly with QTree |
|
if (pb.targetPlayer() && pb.targetMob()) |
|
// Player and mobs |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), MBServerStatics.MASK_MOBILE); |
|
else if (pb.targetPlayer()) |
|
// Player only |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), MBServerStatics.MASK_PLAYER); |
|
else if (pb.targetMob()) |
|
// Mob only |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), MBServerStatics.MASK_MOB |
|
| MBServerStatics.MASK_PET); |
|
else if (pb.targetPet()) |
|
//Pet only |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), MBServerStatics.MASK_PET); |
|
else if (pb.targetNecroPet()) |
|
allTargets = WorldGrid.getObjectsInRangePartialNecroPets( |
|
targetLoc, pb.getRadius()); |
|
else |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), 0); |
|
|
|
// cleanup self, group and nation targets if needed |
|
Iterator<AbstractWorldObject> awolist = allTargets.iterator(); |
|
while (awolist.hasNext()) { |
|
AbstractWorldObject awo = awolist.next(); |
|
if (awo == null) { |
|
awolist.remove(); // won't hit a null |
|
continue; |
|
} |
|
|
|
//see if targets are within 3D range of each other |
|
Vector3fImmutable tloc = awo.getLoc(); |
|
|
|
if (tloc.distanceSquared(targetLoc) > sqr(pb.getRadius())) { |
|
awolist.remove(); // too far away |
|
continue; |
|
} |
|
|
|
if (pb.isCasterFriendly() && pc.equals(awo)) { |
|
awolist.remove(); // won't hit self |
|
continue; |
|
} |
|
|
|
if (!awo.isAlive()) { |
|
awolist.remove(); // too far away |
|
continue; |
|
} |
|
|
|
if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { |
|
|
|
PlayerCharacter pcc = (PlayerCharacter) awo; |
|
|
|
if (pb.isGroupFriendly() && GroupManager.getGroup(pc) != null && GroupManager.getGroup(pcc) != null) |
|
if (GroupManager.getGroup(pc).equals(GroupManager.getGroup(pcc))) { |
|
awolist.remove(); // Won't hit group members |
|
continue; |
|
} |
|
if (pb.isNationFriendly() && pc.getGuild() != null && |
|
pc.getGuild().getNation() != null && pcc.getGuild() != null && |
|
pc.getGuild().getNation() != null) |
|
if (pc.getGuild().getNation().equals(pcc.getGuild().getNation())) { |
|
awolist.remove(); // Won't hit nation members |
|
continue; |
|
} |
|
|
|
// Remove players for non-friendly spells in safe zone |
|
if (pb.isHarmful() && (pcc.inSafeZone() || pc.inSafeZone())) { |
|
awolist.remove(); |
|
continue; |
|
} |
|
} |
|
} |
|
// Trim list down to max size closest targets, limited by max |
|
// Player/Mob amounts |
|
allTargets = RangeBasedAwo.getSortedList(allTargets, targetLoc, pb |
|
.getMaxNumPlayerTargets(), pb.getMaxNumMobTargets()); |
|
} else if (pb.targetGroup()) { |
|
|
|
if (GroupManager.getGroup(pc) != null) { |
|
allTargets = WorldGrid.getObjectsInRangePartial(pc |
|
.getLoc(), pb.getRange(), MBServerStatics.MASK_PLAYER); |
|
Iterator<AbstractWorldObject> awolist = allTargets.iterator(); |
|
while (awolist.hasNext()) { |
|
|
|
AbstractWorldObject awo = awolist.next(); |
|
|
|
if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter))) { |
|
awolist.remove(); // remove non players if there are any |
|
continue; |
|
} |
|
PlayerCharacter pcc = (PlayerCharacter) awo; |
|
|
|
if (GroupManager.getGroup(pcc) == null) |
|
awolist.remove(); // remove players not in a group |
|
else if (!GroupManager.getGroup(pcc).equals(GroupManager.getGroup(pc))) |
|
awolist.remove(); // remove if not same group |
|
|
|
} |
|
} else { |
|
allTargets = new HashSet<>(); |
|
allTargets.add(pc); // no group, use only self |
|
} |
|
} else { |
|
allTargets = new HashSet<>(); |
|
if (pb.targetSelf()) |
|
allTargets.add(pc); |
|
else if (pb.targetFromLastTarget()) |
|
allTargets.add(target); |
|
else if (pb.targetFromNearbyMobs()) |
|
allTargets.add(target); // need better way to do this later |
|
else |
|
// targetByName |
|
allTargets.add(target); // need to get name later |
|
// can't target self if caster friendly |
|
if (pb.isCasterFriendly() && allTargets.contains(pc)) |
|
allTargets.remove(0); |
|
} |
|
|
|
Iterator<AbstractWorldObject> awolist = allTargets.iterator(); |
|
while (awolist.hasNext()) { |
|
AbstractWorldObject awo = awolist.next(); |
|
|
|
//See if target is valid type |
|
if (!validMonsterType(awo, pb)) { |
|
awolist.remove(); |
|
continue; |
|
} |
|
|
|
if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { |
|
|
|
// Remove players who are in safe mode |
|
PlayerCharacter pcc = (PlayerCharacter) awo; |
|
PlayerBonuses bonuses = pcc.getBonuses(); |
|
|
|
if (bonuses != null && bonuses.getBool(ModType.ImmuneToPowers, SourceType.None)) { |
|
awolist.remove(); |
|
continue; |
|
} |
|
|
|
//remove if power is harmful and caster or target is in safe zone |
|
if (pb.isHarmful() && (pcc.inSafeZone() || pc.inSafeZone())) { |
|
awolist.remove(); |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
// verify target has proper effects applied to receive power |
|
if (pb.getTargetEffectPrereqs().size() > 0) { |
|
Iterator<AbstractWorldObject> it = allTargets.iterator(); |
|
while (it.hasNext()) { |
|
boolean passed = false; |
|
AbstractWorldObject awo = it.next(); |
|
if (awo.getEffects() != null) { |
|
for (PowerPrereq pp : pb.getTargetEffectPrereqs()) { |
|
EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect()); |
|
if (awo.containsEffect(eb.getToken())) { |
|
passed = true; |
|
break; |
|
} |
|
} |
|
if (!passed) |
|
it.remove(); |
|
} else |
|
it.remove(); //awo is missing it's effects list |
|
} |
|
} |
|
return allTargets; |
|
} |
|
|
|
public static HashSet<AbstractWorldObject> getAllTargets( |
|
AbstractWorldObject target, Vector3fImmutable tl, |
|
AbstractCharacter caster, PowersBase pb) { |
|
HashSet<AbstractWorldObject> allTargets; |
|
if (pb.isAOE()) { |
|
Vector3fImmutable targetLoc = tl; |
|
if (pb.usePointBlank()) { |
|
targetLoc = caster.getLoc(); |
|
} else { |
|
if (target != null) { |
|
targetLoc = target.getLoc(); |
|
} else { |
|
targetLoc = tl; |
|
try { |
|
targetLoc = targetLoc.setY(HeightMap.getWorldHeight(targetLoc)); //on ground |
|
} catch (Exception e) { |
|
Logger.error(e); |
|
} |
|
|
|
} |
|
} |
|
|
|
if (targetLoc.x == 0f || targetLoc.z == 0f) |
|
return new HashSet<>(); // invalid loc, |
|
|
|
//first find targets in range quickly with QTree |
|
if (pb.targetPlayer() && pb.targetMob()) |
|
// Player and mobs |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), MBServerStatics.MASK_MOBILE); |
|
else if (pb.targetPlayer()) |
|
// Player only |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), MBServerStatics.MASK_PLAYER); |
|
else if (pb.targetMob()) |
|
// Mob only |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), MBServerStatics.MASK_MOB |
|
| MBServerStatics.MASK_PET); |
|
else if (pb.targetPet()) |
|
//Pet only |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), MBServerStatics.MASK_PET); |
|
else if (pb.targetNecroPet()) |
|
allTargets = WorldGrid.getObjectsInRangePartialNecroPets( |
|
targetLoc, pb.getRadius()); |
|
else |
|
allTargets = WorldGrid.getObjectsInRangePartial( |
|
targetLoc, pb.getRadius(), 0); |
|
|
|
// cleanup self, group and nation targets if needed |
|
Iterator<AbstractWorldObject> awolist = allTargets.iterator(); |
|
while (awolist.hasNext()) { |
|
AbstractWorldObject awo = awolist.next(); |
|
if (awo == null) { |
|
awolist.remove(); // won't hit a null |
|
continue; |
|
} |
|
|
|
//see if targets are within 3D range of each other |
|
Vector3fImmutable tloc = awo.getLoc(); |
|
|
|
if (tloc.distanceSquared(targetLoc) > sqr(pb.getRadius())) { |
|
awolist.remove(); // too far away |
|
continue; |
|
} |
|
|
|
if (pb.isCasterFriendly() && caster.equals(awo)) { |
|
awolist.remove(); // won't hit self |
|
continue; |
|
} |
|
|
|
if (awo.getObjectType() == GameObjectType.Mob) { |
|
awolist.remove(); // Won't hit other mobs. |
|
continue; |
|
} |
|
} |
|
// Trim list down to max size closest targets, limited by max |
|
// Player/Mob amounts |
|
allTargets = RangeBasedAwo.getSortedList(allTargets, targetLoc, pb |
|
.getMaxNumPlayerTargets(), pb.getMaxNumMobTargets()); |
|
} else if (pb.targetGroup()) { |
|
allTargets = new HashSet<>(); |
|
allTargets.add(caster); // no group, use only self |
|
} else { |
|
allTargets = new HashSet<>(); |
|
if (pb.targetSelf()) |
|
allTargets.add(caster); |
|
else if (pb.targetFromLastTarget()) |
|
allTargets.add(target); |
|
else if (pb.targetFromNearbyMobs()) |
|
allTargets.add(target); // need better way to do this later |
|
else |
|
// targetByName |
|
allTargets.add(target); // need to get name later |
|
// can't target self if caster friendly |
|
if (pb.isCasterFriendly() && allTargets.contains(caster)) |
|
allTargets.remove(caster); |
|
} |
|
|
|
Iterator<AbstractWorldObject> awolist = allTargets.iterator(); |
|
while (awolist.hasNext()) { |
|
AbstractWorldObject awo = awolist.next(); |
|
|
|
//See if target is valid type |
|
if (!validMonsterType(awo, pb)) { |
|
awolist.remove(); |
|
continue; |
|
} |
|
|
|
if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { |
|
// Remove players who are in safe mode |
|
PlayerCharacter pcc = (PlayerCharacter) awo; |
|
PlayerBonuses bonuses = pcc.getBonuses(); |
|
if (bonuses != null && bonuses.getBool(ModType.ImmuneToPowers, SourceType.None)) { |
|
awolist.remove(); |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
// verify target has proper effects applied to receive power |
|
if (pb.getTargetEffectPrereqs().size() > 0) { |
|
Iterator<AbstractWorldObject> it = allTargets.iterator(); |
|
while (it.hasNext()) { |
|
boolean passed = false; |
|
AbstractWorldObject awo = it.next(); |
|
if (awo.getEffects() != null) { |
|
for (PowerPrereq pp : pb.getTargetEffectPrereqs()) { |
|
EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect()); |
|
if (awo.containsEffect(eb.getToken())) { |
|
passed = true; |
|
break; |
|
} |
|
} |
|
if (!passed) |
|
it.remove(); |
|
} else |
|
it.remove(); //awo is missing it's effects list |
|
} |
|
} |
|
return allTargets; |
|
} |
|
|
|
// removes an effect before time is finished |
|
public static void removeEffect(AbstractWorldObject awo, ActionsBase toRemove, |
|
boolean overwrite, boolean fromChant) { |
|
if (toRemove == null) |
|
return; |
|
|
|
String stackType = toRemove.getStackType(); |
|
stackType = (stackType.equals("IgnoreStack")) ? Integer |
|
.toString(toRemove.getUUID()) : stackType; |
|
if (fromChant) { |
|
Effect eff = awo.getEffects().get(stackType); |
|
if (eff != null) |
|
eff.cancelJob(true); |
|
} else |
|
awo.cancelEffect(stackType, overwrite); |
|
} |
|
|
|
// removes an effect when timer finishes |
|
public static void finishEffectTime(AbstractWorldObject source, |
|
AbstractWorldObject awo, ActionsBase toRemove, int trains) { |
|
if (awo == null || toRemove == null) |
|
return; |
|
|
|
// remove effect from player |
|
String stackType = toRemove.getStackType(); |
|
if (stackType.equals("IgnoreStack")) |
|
stackType = Integer.toString(toRemove.getUUID()); |
|
awo.endEffect(stackType); |
|
} |
|
|
|
// removes an effect when timer is canceled |
|
public static void cancelEffectTime(AbstractWorldObject source, |
|
AbstractWorldObject awo, PowersBase pb, EffectsBase eb, |
|
ActionsBase toRemove, int trains, AbstractEffectJob efj) { |
|
if (awo == null || pb == null || eb == null || toRemove == null) |
|
return; |
|
eb.endEffect(source, awo, trains, pb, efj); |
|
} |
|
|
|
// called when cooldown ends letting player cast next spell |
|
public static void finishCooldownTime(PerformActionMsg msg, PlayerCharacter pc) { |
|
// clear spell so player can cast again |
|
// if (pc != null) |
|
// pc.clearLastPower(); |
|
} |
|
|
|
// called when recycle time ends letting player cast spell again |
|
public static void finishRecycleTime(PerformActionMsg msg, PlayerCharacter pc, |
|
boolean canceled) { |
|
finishRecycleTime(msg.getPowerUsedID(), pc, canceled); |
|
} |
|
|
|
public static void finishRecycleTime(int token, PlayerCharacter pc, |
|
boolean canceled) { |
|
if (pc == null) |
|
return; |
|
|
|
ConcurrentHashMap<Integer, JobContainer> recycleTimers = pc |
|
.getRecycleTimers(); |
|
// clear recycle time |
|
if (recycleTimers != null) |
|
if (recycleTimers.containsKey(token)) { |
|
if (canceled) { |
|
JobContainer jc = recycleTimers.get(token); |
|
if (jc != null) |
|
jc.cancelJob(); |
|
} |
|
recycleTimers.remove(token); |
|
} |
|
|
|
// send recycle message to unlock power |
|
|
|
RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token); |
|
Dispatch dispatch = Dispatch.borrow(pc, recyclePowerMsg); |
|
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); |
|
|
|
} |
|
|
|
// Called when a fail condition is met by player |
|
// such as moving, taking damage, ect. |
|
|
|
public static void cancelUseLastPower(PlayerCharacter pc) { |
|
|
|
if (pc == null) |
|
return; |
|
|
|
// set player is not casting for regens |
|
if (pc.isCasting()) { |
|
pc.update(); |
|
} |
|
pc.setIsCasting(false); |
|
|
|
UsePowerJob lastPower = null; |
|
JobContainer jc = pc.getLastPower(); |
|
|
|
if (jc != null) |
|
lastPower = ((UsePowerJob) jc.getJob()); |
|
|
|
if (lastPower == null) |
|
return; |
|
|
|
// clear recycle timer |
|
int token = lastPower.getToken(); |
|
|
|
if (pc.getRecycleTimers().contains(token)) |
|
finishRecycleTime(token, pc, true); |
|
|
|
// pc.getRecycleTimers().remove(token); |
|
// Cancel power |
|
js.cancelScheduledJob(lastPower); |
|
|
|
// clear last power |
|
pc.clearLastPower(); |
|
|
|
} |
|
|
|
private static AbstractWorldObject getTarget(PerformActionMsg msg) { |
|
|
|
int type = msg.getTargetType(); |
|
int UUID = msg.getTargetID(); |
|
|
|
if (type == -1 || type == 0 || UUID == -1 || UUID == 0) |
|
return null; |
|
|
|
return (AbstractWorldObject) DbManager.getObject(GameObjectType.values()[type], UUID); |
|
} |
|
|
|
public static boolean testAttack(PlayerCharacter pc, AbstractWorldObject awo, |
|
PowersBase pb, PerformActionMsg msg) { |
|
// Get defense for target |
|
float atr = CharacterSkill.getATR(pc, pb.getSkillName()); |
|
float defense; |
|
|
|
if (AbstractWorldObject.IsAbstractCharacter(awo)) { |
|
AbstractCharacter tar = (AbstractCharacter) awo; |
|
defense = tar.getDefenseRating(); |
|
} else |
|
defense = 0f; |
|
// Get hit chance |
|
|
|
if (pc.getDebug(16)) { |
|
String smsg = "ATR: " + atr + ", Defense: " + defense; |
|
ChatManager.chatSystemInfo(pc, smsg); |
|
} |
|
|
|
int chance; |
|
|
|
if (atr > defense || defense == 0) |
|
chance = 94; |
|
else { |
|
float dif = atr / defense; |
|
if (dif <= 0.8f) |
|
chance = 4; |
|
else |
|
chance = ((int) (450 * (dif - 0.8f)) + 4); |
|
} |
|
|
|
// calculate hit/miss |
|
int roll = ThreadLocalRandom.current().nextInt(100); |
|
|
|
boolean disable = true; |
|
if (roll < chance) { |
|
// Hit, check if dodge kicked in |
|
if (awo instanceof AbstractCharacter) { |
|
AbstractCharacter tarAc = (AbstractCharacter) awo; |
|
// Handle Dodge passive |
|
if (testPassive(pc, tarAc, "Dodge")) { |
|
// Dodge fired, send dodge message |
|
PerformActionMsg dodgeMsg = new PerformActionMsg(msg); |
|
dodgeMsg.setTargetType(awo.getObjectType().ordinal()); |
|
dodgeMsg.setTargetID(awo.getObjectUUID()); |
|
sendPowerMsg(pc, 4, dodgeMsg); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} else { |
|
// Miss. Send miss message |
|
PerformActionMsg missMsg = new PerformActionMsg(msg); |
|
|
|
missMsg.setTargetType(awo.getObjectType().ordinal()); |
|
missMsg.setTargetID(awo.getObjectUUID()); |
|
sendPowerMsg(pc, 3, missMsg); |
|
return true; |
|
} |
|
} |
|
|
|
public static boolean testAttack(Mob caster, AbstractWorldObject awo, |
|
PowersBase pb, PerformActionMsg msg) { |
|
// Get defense for target |
|
float atr = 2000; |
|
float defense; |
|
|
|
if (AbstractWorldObject.IsAbstractCharacter(awo)) { |
|
AbstractCharacter tar = (AbstractCharacter) awo; |
|
defense = tar.getDefenseRating(); |
|
} else |
|
defense = 0f; |
|
// Get hit chance |
|
|
|
int chance; |
|
|
|
if (atr > defense || defense == 0) |
|
chance = 94; |
|
else { |
|
float dif = atr / defense; |
|
if (dif <= 0.8f) |
|
chance = 4; |
|
else |
|
chance = ((int) (450 * (dif - 0.8f)) + 4); |
|
} |
|
|
|
// calculate hit/miss |
|
int roll = ThreadLocalRandom.current().nextInt(100); |
|
|
|
if (roll < chance) { |
|
// Hit, check if dodge kicked in |
|
if (AbstractWorldObject.IsAbstractCharacter(awo)) { |
|
AbstractCharacter tarAc = (AbstractCharacter) awo; |
|
// Handle Dodge passive |
|
return testPassive(caster, tarAc, "Dodge"); |
|
} |
|
return false; |
|
} else |
|
return true; |
|
} |
|
|
|
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); |
|
DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
break; |
|
default: |
|
msg.setUnknown04(1); |
|
Dispatch dispatch = Dispatch.borrow(playerCharacter, msg); |
|
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); |
|
} |
|
} |
|
|
|
public static void sendEffectMsg(PlayerCharacter pc, int type, ActionsBase ab, PowersBase pb) { |
|
|
|
if (pc == null) |
|
return; |
|
|
|
try { |
|
|
|
EffectsBase eb = PowersManager.effectsBaseByIDString.get(ab.getEffectID()); |
|
|
|
if (eb == null) |
|
return; |
|
|
|
ApplyEffectMsg aem = new ApplyEffectMsg(pc, pc, 0, eb.getToken(), 9, pb.getToken(), pb.getName()); |
|
aem.setUnknown03(type); |
|
DispatchMessage.dispatchMsgToInterestArea(pc, aem, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
|
|
|
|
} catch (Exception e) { |
|
Logger.error(e.getMessage()); |
|
} |
|
|
|
} |
|
|
|
public static void sendEffectMsg(PlayerCharacter pc, int type, EffectsBase eb) { |
|
|
|
if (pc == null) |
|
return; |
|
try { |
|
|
|
if (eb == null) |
|
return; |
|
ApplyEffectMsg aem = new ApplyEffectMsg(pc, pc, 0, eb.getToken(), 0, eb.getToken(), ""); |
|
aem.setUnknown03(type); |
|
aem.setUnknown05(1); |
|
|
|
DispatchMessage.dispatchMsgToInterestArea(pc, aem, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); |
|
|
|
|
|
} catch (Exception e) { |
|
Logger.error(e.getMessage()); |
|
} |
|
|
|
} |
|
|
|
public static void sendMobPowerMsg(Mob mob, int type, PerformActionMsg msg) { |
|
|
|
msg.setUnknown05(type); |
|
switch (type) { |
|
case 3: |
|
case 4: |
|
DispatchMessage.sendToAllInRange(mob, msg); |
|
|
|
} |
|
} |
|
|
|
private static boolean testPassive(AbstractCharacter source, |
|
AbstractCharacter target, String type) { |
|
|
|
float chance = target.getPassiveChance(type, source.getLevel(), false); |
|
|
|
if (chance == 0f) |
|
return false; |
|
|
|
// max 75% chance of passive to fire |
|
if (chance > 75f) |
|
chance = 75f; |
|
|
|
int roll = ThreadLocalRandom.current().nextInt(100); |
|
// Passive fired |
|
// TODO send message |
|
// Passive did not fire |
|
return roll < chance; |
|
} |
|
|
|
private static boolean validateTarget(AbstractWorldObject target, |
|
PlayerCharacter pc, PowersBase pb) { |
|
|
|
//group target. uses pbaoe rules |
|
if (pb.targetGroup()) |
|
return true; |
|
|
|
// target is player |
|
else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PLAYER) != 0) { |
|
if (pb.targetPlayer()) |
|
if (pb.isGroupOnly()) { //single target group only power |
|
PlayerCharacter trg = (PlayerCharacter) target; |
|
|
|
if (GroupManager.getGroup(trg) != null && GroupManager.getGroup(pc) != null) |
|
if (GroupManager.getGroup(trg).getObjectUUID() == GroupManager.getGroup(pc).getObjectUUID()) |
|
return true; // both in same group, good to go |
|
return trg != null && pc.getObjectUUID() == trg.getObjectUUID(); |
|
} else |
|
return true; // can target player, good to go. |
|
else if (target.getObjectUUID() == pc.getObjectUUID() && pb.targetSelf()) |
|
return true; // can target self, good to go |
|
else if (pb.targetCorpse()) { |
|
//target is dead player |
|
PlayerCharacter trg = (PlayerCharacter) target; |
|
return !trg.isAlive(); |
|
} else { |
|
PlayerCharacter trg = (PlayerCharacter) target; |
|
|
|
if (pb.targetGroup()) |
|
if (GroupManager.getGroup(trg) != null && GroupManager.getGroup(pc) != null) |
|
if (GroupManager.getGroup(trg).getObjectUUID() == GroupManager.getGroup(pc) |
|
.getObjectUUID()) |
|
return true; // both in same group, good to go |
|
if (pb.targetGuildLeader()) |
|
if (pc.getGuild() != null) |
|
if (pc.getGuild().getGuildLeaderUUID() == trg.getObjectUUID()) |
|
return true; // can hit guild leader, good to go |
|
} |
|
String outmsg = "Invalid Target"; |
|
ChatManager.chatSystemInfo(pc, outmsg); |
|
return false; // can't target player, stop here |
|
} // target is mob |
|
else if ((target.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0) |
|
return pb.targetMob(); |
|
|
|
// target is pet |
|
else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0) |
|
return pb.targetPet(); |
|
|
|
// target is Building |
|
else if ((target.getObjectTypeMask() & MBServerStatics.MASK_BUILDING) != 0) |
|
return pb.targetBuilding(); |
|
|
|
else if (target.getObjectType().equals(GameObjectType.Item)) { |
|
Item item = (Item) target; |
|
if (pb.targetItem()) |
|
return true; |
|
// TODO add these checks later |
|
else if (pb.targetArmor() && item.getItemBase().getType().equals(ItemType.ARMOR)) |
|
return true; |
|
else if (pb.targetJewelry() && item.getItemBase().getType().equals(ItemType.JEWELRY)) |
|
return true; |
|
else |
|
return pb.targetWeapon() && item.getItemBase().getType().equals(ItemType.WEAPON); |
|
} // How did we get here? all valid targets have been covered |
|
else |
|
return false; |
|
} |
|
|
|
/* |
|
* Cancel spell upon actions |
|
*/ |
|
public static void cancelOnAttack(AbstractCharacter ac) { |
|
ac.cancelTimer("Stuck"); |
|
} |
|
|
|
public static void cancelOnAttackSwing(AbstractCharacter ac) { |
|
} |
|
|
|
public static void cancelOnCast(AbstractCharacter ac) { |
|
|
|
} |
|
|
|
public static void cancelOnSpell(AbstractCharacter ac) { |
|
|
|
PowersBase power = getLastPower(ac); |
|
|
|
if (power != null && power.cancelOnCastSpell()) |
|
cancelPower(ac, false); |
|
ac.cancelLastChant(); |
|
} |
|
|
|
public static void cancelOnEquipChange(AbstractCharacter ac) { |
|
|
|
} |
|
|
|
public static void cancelOnLogout(AbstractCharacter ac) { |
|
|
|
} |
|
|
|
public static void cancelOnMove(AbstractCharacter ac) { |
|
|
|
PowersBase power = getLastPower(ac); |
|
|
|
if (power != null && !power.canCastWhileMoving()) |
|
cancelPower(ac, false); |
|
|
|
//cancel items |
|
cancelItems(ac, true, false); |
|
ac.cancelTimer("Stuck"); |
|
} |
|
|
|
|
|
public static void cancelOnNewCharm(AbstractCharacter ac) { |
|
|
|
} |
|
|
|
public static void cancelOnSit(AbstractCharacter ac) { |
|
cancelPower(ac, false); // Always cancel casts on sit |
|
} |
|
|
|
public static void cancelOnTakeDamage(AbstractCharacter ac) { |
|
|
|
PowersBase power = getLastPower(ac); |
|
|
|
if (power != null && power.cancelOnTakeDamage()) |
|
cancelPower(ac, true); |
|
cancelItems(ac, false, true); |
|
ac.cancelTimer("Stuck"); |
|
} |
|
|
|
public static void cancelOnTerritoryClaim(AbstractCharacter ac) { |
|
|
|
} |
|
|
|
public static void cancelOnUnEquip(AbstractCharacter ac) { |
|
|
|
} |
|
|
|
public static void cancelOnStun(AbstractCharacter ac) { |
|
|
|
} |
|
|
|
private static PowersBase getLastPower(AbstractCharacter ac) { |
|
if (ac == null) |
|
return null; |
|
|
|
JobContainer jc = ac.getLastPower(); |
|
|
|
if (jc == null) |
|
return null; |
|
|
|
AbstractJob aj = jc.getJob(); |
|
|
|
if (aj == null) |
|
return null; |
|
|
|
if (aj instanceof UsePowerJob) { |
|
UsePowerJob upj = (UsePowerJob) aj; |
|
return upj.getPowersBase(); |
|
} |
|
return null; |
|
} |
|
|
|
private static PowersBase getLastItem(AbstractCharacter ac) { |
|
|
|
if (ac == null) |
|
return null; |
|
|
|
JobContainer jc = ac.getLastItem(); |
|
|
|
if (jc == null) |
|
return null; |
|
|
|
AbstractJob aj = jc.getJob(); |
|
|
|
if (aj == null) |
|
return null; |
|
|
|
if (aj instanceof UseItemJob) { |
|
UseItemJob uij = (UseItemJob) aj; |
|
return uij.getPowersBase(); |
|
} |
|
return null; |
|
} |
|
|
|
//cancels last casted power |
|
private static void cancelPower(AbstractCharacter ac, boolean cancelCastAnimation) { |
|
|
|
if (ac == null) |
|
return; |
|
|
|
JobContainer jc = ac.getLastPower(); |
|
|
|
if (jc == null) |
|
return; |
|
|
|
AbstractJob aj = jc.getJob(); |
|
|
|
if (aj == null) |
|
return; |
|
|
|
if (aj instanceof AbstractScheduleJob) |
|
((AbstractScheduleJob) aj).cancelJob(); |
|
|
|
ac.clearLastPower(); |
|
|
|
//clear cast animation for everyone in view range |
|
if (aj instanceof UsePowerJob && cancelCastAnimation) { |
|
|
|
PerformActionMsg pam = ((UsePowerJob) aj).getMsg(); |
|
|
|
if (pam != null) { |
|
pam.setNumTrains(9999); |
|
pam.setUnknown04(2); |
|
DispatchMessage.sendToAllInRange(ac, pam); |
|
} |
|
} |
|
} |
|
|
|
public static PerformActionMsg createPowerMsg(PowersBase pb, int trains, AbstractCharacter source, AbstractCharacter target) { |
|
return new PerformActionMsg(pb.getToken(), trains, source.getObjectType().ordinal(), source.getObjectUUID(), target.getObjectType().ordinal(), target.getObjectUUID(), target.getLoc().x, target.getLoc().y, target.getLoc().z, 0, 0); |
|
|
|
} |
|
|
|
//cancels any casts from using an item |
|
|
|
private static void cancelItems(AbstractCharacter ac, boolean cancelOnMove, boolean cancelOnTakeDamage) { |
|
JobContainer jc; |
|
AbstractJob aj; |
|
ConcurrentHashMap<String, JobContainer> timers; |
|
UseItemJob uij; |
|
PowersBase pb; |
|
AbstractWorldObject target; |
|
|
|
if (ac == null) |
|
return; |
|
|
|
timers = ac.getTimers(); |
|
|
|
if (timers == null) |
|
return; |
|
|
|
for (String name : timers.keySet()) { |
|
|
|
jc = timers.get(name); |
|
|
|
if (jc == null) |
|
continue; |
|
|
|
aj = jc.getJob(); |
|
|
|
if (aj != null && aj instanceof UseItemJob) { |
|
uij = (UseItemJob) aj; |
|
pb = uij.getPowersBase(); |
|
|
|
if (pb == null) |
|
continue; |
|
|
|
if (!pb.canCastWhileMoving() && cancelOnMove) { |
|
uij.cancelJob(); |
|
timers.remove(name); |
|
continue; |
|
} |
|
|
|
if ((pb.cancelOnTakeDamage() == false) && |
|
(cancelOnTakeDamage == false)) |
|
continue; |
|
|
|
uij.cancelJob(); |
|
timers.remove(name); |
|
|
|
//clear cast animation for everyone in view range |
|
target = uij.getTarget(); |
|
|
|
if (target != null) { |
|
PerformActionMsg pam = new PerformActionMsg(pb.getToken(), 9999, ac |
|
.getObjectType().ordinal(), ac.getObjectUUID(), target.getObjectType().ordinal(), |
|
target.getObjectUUID(), 0, 0, 0, 2, 0); |
|
DispatchMessage.sendToAllInRange(ac, pam); |
|
|
|
} |
|
} |
|
} |
|
} |
|
|
|
public static void LoadAllMobPowers() { |
|
|
|
int count = 0; |
|
|
|
for (AbstractGameObject mobBaseAgo : DbManager.getList(GameObjectType.MobBase)) { |
|
|
|
int mobBaseID = ((MobBase) mobBaseAgo).getLoadID(); |
|
|
|
HashMap powersList = DbManager.MobBaseQueries.LOAD_STATIC_POWERS(mobBaseID); |
|
|
|
if (powersList.isEmpty()) |
|
continue; |
|
; |
|
|
|
AllMobPowers.put(mobBaseID, powersList); |
|
count++; |
|
} |
|
|
|
Logger.info("Powers loaded for " + count + " Mobbases/"); |
|
} |
|
} |
|
|
|
|
|
|
|
|