Public Repository for the Magicbane Shadowbane Emulator
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.

368 lines
14 KiB

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.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 java.util.concurrent.ConcurrentHashMap;
import static engine.math.FastMath.sqr;
public class WpakPowerManager {
public static HashMap<String, Effect> effect_data = new HashMap<>();
public static HashMap<Integer, PowerAction> power_actions = new HashMap<>();
public static HashMap<Integer, Power> powers = 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 = powers.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 = powerCast.getRecycleTime(trains);
//combat mode sanity check
if (playerCharacter.isCombat()) {
if (!powerCast.allowedInCombat())
return true;
} else if (!powerCast.allowedOutOfCombat())
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 = powerCast.getCost(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 = powerCast.getCastTime(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 = powers.get(msg.getPowerUsedID());
if(powerUsed == null)
return;
if(powerUsed.maxMobTargets > 1 || powerUsed.maxPlayerTargets > 1){
AoeHandler(caster, target, powerUsed, msg.getNumTrains());
}else{
applyAllPowerEffects(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<>();
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);
int count = 1;
for(AbstractWorldObject mob : mobTargets){
if(count < powerUsed.maxMobTargets + 1){
applyAllPowerEffects(powerUsed, rank, mob);
count ++;
}else{
break;
}
}
count = 1;
for(AbstractWorldObject pc : pcTargets){
if(count < powerUsed.maxPlayerTargets + 1){
applyAllPowerEffects(powerUsed, rank, pc);
count ++;
}else{
break;
}
}
}
public static void applyAllPowerEffects(Power power, int rank, AbstractWorldObject target) {
for(ActionEntry powerAction: power.actionEntries){
Effect effect = effect_data.get(Hasher.SBStringHash(powerAction.effect_id));
if (effect == null)
continue;
// New entry for this power effect?
if (target._effects.containsKey(effect) == false)
target._effects.put(effect, new ConcurrentHashMap<>());
// Write modifier values
for (ModifierEntry modifierEntry : effect.mods)
target._effects.get(effect).put(modifierEntry.type, rank);
}
}
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);
}
}
}