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.

628 lines
22 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.objects;
import engine.Enum;
import engine.gameManager.DbManager;
import engine.gameManager.PowersManager;
import engine.net.ByteBufferWriter;
import engine.net.client.msg.ErrorPopupMsg;
import engine.powers.PowersBase;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class CharacterPower extends AbstractGameObject {
private final PowersBase power;
private AtomicInteger trains = new AtomicInteger();
private short grantedTrains;
private int ownerUID;
private boolean trained = false;
private int requiredLevel = 0;
/**
* No Table ID Constructor
*/
public CharacterPower(PowersBase power, PlayerCharacter pc) {
super();
this.power = power;
this.trains.set(0);
this.grantedTrains = this.grantedTrains;
this.ownerUID = pc.getObjectUUID();
}
/**
* Normal Constructor
*/
public CharacterPower(PowersBase power, PlayerCharacter pc, int newUUID) {
super(newUUID);
this.power = power;
this.trains.set(0);
this.grantedTrains = this.grantedTrains;
this.ownerUID = pc.getObjectUUID();
}
/**
* ResultSet Constructor
*/
public CharacterPower(ResultSet rs, PlayerCharacter pc) throws SQLException {
super(rs);
int powersBaseToken = rs.getInt("PowersBaseToken");
this.power = PowersManager.getPowerByToken(powersBaseToken);
if (this.power != null && this.power.isWeaponPower())
this.trains.set(0);
else
this.trains.set(rs.getInt("trains"));
this.grantedTrains = this.grantedTrains;
this.ownerUID = pc.getObjectUUID();
}
public CharacterPower(ResultSet rs) throws SQLException {
super(rs);
int powersBaseToken = rs.getInt("PowersBaseToken");
this.power = PowersManager.getPowerByToken(powersBaseToken);
this.trains.set(rs.getInt("trains"));
this.grantedTrains = this.grantedTrains;
this.ownerUID = rs.getInt("CharacterID");
// this.owner = DbManager.PlayerCharacterQueries.GET_PLAYER_CHARACTER(rs.getInt("CharacterID"));
}
public static PlayerCharacter getOwner(CharacterPower cp) {
return PlayerCharacter.getFromCache(cp.ownerUID);
}
/*
* This iterates through players runes and adds and removes powers as needed
* Don't Call this directly. Instead call pc.calculateSkills().
*/
public static void calculatePowers(AbstractCharacter absChar) {
if (absChar == null || absChar.getObjectType().equals(Enum.GameObjectType.PlayerCharacter) == false)
return;
PlayerCharacter pc = (PlayerCharacter) absChar;
// First add powers that don't exist
ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
// ArrayList<PowerReq> genericPowers = PowerReq.getPowerReqsForAll();
// CharacterPower.grantPowers(genericPowers, powers, pc);
Race race = pc.race;
if (race != null) {
CharacterPower.grantPowers(race.getPowersGranted(), powers, pc);
} else
Logger.error("Failed to find Race for player " + pc.getObjectUUID());
BaseClass bc = pc.baseClass;
if (bc != null) {
CharacterPower.grantPowers(bc.getPowersGranted(), powers, pc);
} else
Logger.error("Failed to find BaseClass for player " + pc.getObjectUUID());
PromotionClass promo = pc.promotionClass;
if (promo != null)
CharacterPower.grantPowers(promo.getPowersGranted(), powers, pc);
ArrayList<CharacterRune> runes = pc.runes;
if (runes != null) {
for (CharacterRune rune : runes) {
CharacterPower.grantPowers(rune.getPowersGranted(), powers, pc);
}
} else
Logger.error("Failed to find Runes list for player " + pc.getObjectUUID());
// next remove any skills that no longer belong
Iterator<Integer> it = powers.keySet().iterator();
while (it.hasNext()) {
Integer token = it.next();
boolean valid = false;
// if (CharacterPower.powerAllowed(token, genericPowers, pc))
// continue;
if (CharacterPower.powerAllowed(token, race.getPowersGranted(), pc))
continue;
if (CharacterPower.powerAllowed(token, bc.getPowersGranted(), pc))
continue;
if (promo != null)
if (CharacterPower.powerAllowed(token, promo.getPowersGranted(), pc))
continue;
for (CharacterRune rune : runes) {
if (CharacterPower.powerAllowed(token, rune.getPowersGranted(), pc)) {
valid = true;
continue;
}
}
// if power doesn't belong to any runes or skill, then remove it
if (!valid) {
CharacterPower cp = powers.get(token);
DbManager.CharacterPowerQueries.DELETE_CHARACTER_POWER(cp.getObjectUUID());
it.remove();
}
}
}
/*
* This grants powers for specific runes
*/
private static void grantPowers(ArrayList<PowerReq> powersGranted, ConcurrentHashMap<Integer, CharacterPower> powers, PlayerCharacter pc) {
ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
for (PowerReq powerreq : powersGranted) {
PowersBase powersBase = powerreq.getPowersBase();
if (powersBase == null)
continue;
// skip if player already has power
if (powers.containsKey(powerreq.getToken())) {
CharacterPower cp = powers.get(powersBase.getToken());
if (cp != null)
if (cp.requiredLevel == 0) {
cp.requiredLevel = (int) powerreq.getLevel();
}
continue;
}
// If player not high enough level for power, then skip
if (pc.getLevel() < powerreq.getLevel())
continue;
// See if any prereq powers needed
boolean valid = true;
ConcurrentHashMap<Integer, Byte> preqs = powerreq.getPowerReqs();
for (Integer tok : preqs.keySet()) {
if (!powers.containsKey(tok))
valid = false;
else {
CharacterPower cpp = powers.get(tok);
if ((cpp.getTrains() + cpp.grantedTrains) < preqs.get(tok))
valid = false;
}
}
if (!valid)
continue;
// See if any prereq skills needed
preqs = powerreq.getSkillReqs();
for (Integer tok : preqs.keySet()) {
if (tok == 0)
continue;
CharacterSkill found = null;
for (CharacterSkill sk : skills.values()) {
if (sk.getToken() == tok) {
found = sk;
continue;
}
}
if (found != null) {
if (found.getModifiedAmountBeforeMods() < preqs.get(tok))
valid = false;
} else
valid = false;
}
if (!valid)
continue;
if (!powers.containsKey(powersBase.getToken())) {
CharacterPower newPower = new CharacterPower(powersBase, pc);
CharacterPower cp = null;
try {
cp = DbManager.CharacterPowerQueries.ADD_CHARACTER_POWER(newPower);
} catch (Exception e) {
cp = null;
}
if (cp != null) {
cp.requiredLevel = (int) powerreq.getLevel();
powers.put(powersBase.getToken(), cp);
} else
Logger.error("Failed to add CharacterPower to player " + pc.getObjectUUID());
} else {
CharacterPower cp = powers.get(powersBase.getToken());
if (cp != null)
if (cp.requiredLevel == 0) {
cp.requiredLevel = (int) powerreq.getLevel();
}
}
}
}
public static void grantTrains(AbstractCharacter absChar) {
if (absChar == null || absChar.getObjectType().equals(Enum.GameObjectType.PlayerCharacter) == false)
return;
PlayerCharacter pc = (PlayerCharacter) absChar;
ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
for (CharacterPower cp : powers.values()) {
cp.grantedTrains = cp.getGrantedTrains(pc);
}
}
/*
* This verifies if a power is valid for a players rune
*/
private static boolean powerAllowed(Integer token, ArrayList<PowerReq> powersGranted, PlayerCharacter pc) {
ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
if (skills == null || powers == null)
return false;
for (PowerReq powerreq : powersGranted) {
PowersBase pb = powerreq.getPowersBase();
if (pb != null) {
if (pb.getToken() == token) {
//test level requirements
if (powerreq.getLevel() > pc.getLevel()) {
return false;
}
//test skill requirements are met
ConcurrentHashMap<Integer, Byte> skillReqs = powerreq.getSkillReqs();
for (int tok : skillReqs.keySet()) {
boolean valid = false;
if (tok == 0)
continue;
for (CharacterSkill skill : skills.values()) {
if (skill.getToken() == tok) {
if (skill.getModifiedAmountBeforeMods() < skillReqs.get(tok))
return false;
valid = true;
break;
}
}
if (!valid)
return false;
}
//test power prerequisites are met
ConcurrentHashMap<Integer, Byte> powerReqs = powerreq.getPowerReqs();
for (int tok : powerReqs.keySet()) {
if (!powers.containsKey(tok))
return false;
CharacterPower cp = powers.get(tok);
if (cp.getTotalTrains() < powerReqs.get(tok))
return false;
}
//everything passed. power is valid
return true;
}
}
}
return false;
}
public static void serializeForClientMsg(CharacterPower characterPower, ByteBufferWriter writer) {
if (characterPower.power != null)
writer.putInt(characterPower.power.getToken());
else
writer.putInt(0);
writer.putInt(characterPower.getTrains());
}
public static CharacterPower getPower(int tableId) {
return DbManager.CharacterPowerQueries.GET_CHARACTER_POWER(tableId);
}
private short getGrantedTrains(PlayerCharacter pc) {
if (this.power != null && pc != null) {
// if (this.power.isWeaponPower()) {
// SkillsBase sb = null;
// try {
// sb = SkillsBase.getSkillsBaseByName(this.power.getSkillName());
// } catch (SQLException e) {}
// if (sb != null) {
// return pc.getBonuses().getByte("gt." + sb.getToken());
// } else
// return pc.getBonuses().getByte("gt." + this.power.getToken());
// } else
// return pc.getBonuses().getByte("gt." + this.power.getToken());
return PowerGrant.getGrantedTrains(this.power.getToken(), pc);
} else
return 0;
}
/*
* Getters
*/
public PowersBase getPower() {
return power;
}
public int getPowerID() {
return power.getUUID();
}
public boolean isTrained() {
return trained;
}
public void setTrained(boolean b) {
trained = b;
}
public int getTrains() {
return this.trains.get();
}
/*
* Utils
*/
public short getGrantedTrains() {
return this.grantedTrains;
}
public int getTotalTrains() {
return (this.trains.get() + this.grantedTrains);
}
public float getTrainingCost(PlayerCharacter pc, NPC trainer) {
int charLevel = pc.getLevel();
int skillRank = this.trains.get() - 1 + this.requiredLevel;
float baseCost = 50 * this.requiredLevel; //TODO GET BASE COSTS OF SKILLS.
float sellPercent = -4f; //NOT SELL PERCENT!
float cost;
float const5;
int const2 = 1;
float const3 = 50;
float const4 = const3 + const2;
if (charLevel > 50)
const5 = 50 / const4;
else
const5 = charLevel / const4;
const5 = 1 - const5;
const5 = (float) (Math.log(const5) / Math.log(2) * .75f);
float rounded5 = Math.round(const5);
const5 = rounded5 - const5;
const5 *= -1;
const5 = (float) (Math.pow(2, const5) - 1);
const5 += 1;
const5 = Math.scalb(const5, (int) rounded5);
const5 *= (charLevel - skillRank);
const5 *= sellPercent;
const5 = (float) (Math.log(const5) / Math.log(2) * 3);
rounded5 = Math.round(const5);
const5 = rounded5 - const5;
const5 *= -1;
const5 = (float) (Math.pow(2, const5) - 1);
const5 += 1;
const5 = Math.scalb(const5, (int) rounded5);
const5 += 1;
cost = const5 * baseCost;
if (Float.isNaN(cost))
cost = baseCost;
return cost;
}
public synchronized boolean train(PlayerCharacter pc) {
if (pc == null || this.power == null)
return false;
//see if any prereqs to train this power is met
if (!canTrain(pc))
return false;
boolean succeeded = true;
int oldTrains = this.trains.get();
int tr = oldTrains + this.grantedTrains;
if (pc.getTrainsAvailable() <= 0)
return false;
if (tr == this.power.getMaxTrains()) //at max, stop here
return false;
else if (tr > this.power.getMaxTrains()) //catch incase we somehow go over
this.trains.set((this.power.getMaxTrains() - this.grantedTrains));
else //add the train
succeeded = this.trains.compareAndSet(oldTrains, oldTrains + 1);
if (this.trains.get() > this.power.getMaxTrains()) { //double check not over max trains
this.trains.set(this.power.getMaxTrains());
succeeded = false;
}
if (succeeded) {
this.trained = true;
//update database
pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
//subtract from trains available
pc.modifyTrainsAvailable(-1);
pc.calculateSkills();
return true;
} else
return false;
}
public boolean reset(PlayerCharacter pc) {
if (pc == null || this.power == null)
return false;
//see if any prereqs to refine this power is met
boolean succeeded = true;
int oldTrains = this.trains.get();
int tr = oldTrains + this.grantedTrains;
if (oldTrains < 1)
return false;
else //subtract the train
succeeded = this.trains.compareAndSet(oldTrains, 0);
if (succeeded) {
this.trained = true;
//update database
pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
//subtract from trains available
pc.modifyTrainsAvailable(oldTrains);
pc.calculateSkills();
return true;
} else
return false;
}
public boolean refine(PlayerCharacter pc) {
if (pc == null || this.power == null)
return false;
//see if any prereqs to refine this power is met
if (!canRefine(pc))
return false;
boolean succeeded = true;
int oldTrains = this.trains.get();
int tr = oldTrains + this.grantedTrains;
if (oldTrains < 1)
return false;
else //subtract the train
succeeded = this.trains.compareAndSet(oldTrains, oldTrains - 1);
if (succeeded) {
this.trained = true;
//update database
pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
//subtract from trains available
pc.modifyTrainsAvailable(1);
pc.calculateSkills();
return true;
} else
return false;
}
//This verifies the power is not blocked from refining by prereqs on other powers.
private boolean canRefine(PlayerCharacter pc) {
if (this.power == null || pc == null)
return false;
ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
Race race = pc.race;
if (race != null) {
if (!canRefine(race.getPowersGranted(), powers, pc))
return false;
} else
return false;
BaseClass bc = pc.baseClass;
if (bc != null) {
if (!canRefine(bc.getPowersGranted(), powers, pc))
return false;
} else
return false;
PromotionClass promo = pc.getPromotionClass();
if (promo != null)
if (!canRefine(promo.getPowersGranted(), powers, pc))
return false;
ArrayList<CharacterRune> runes = pc.getRunes();
if (runes != null) {
for (CharacterRune rune : runes) {
if (!canRefine(rune.getPowersGranted(), powers, pc))
return false;
}
}
//all tests passed. Can refine
return true;
}
/*
* Serializing
*/
private boolean canRefine(ArrayList<PowerReq> powersGranted, ConcurrentHashMap<Integer, CharacterPower> powers, PlayerCharacter pc) {
for (PowerReq pr : powersGranted) {
ConcurrentHashMap<Integer, Byte> powerReqs = pr.getPowerReqs();
for (int token : powerReqs.keySet()) {
if (token == this.power.getToken()) {
//this is a prereq, find the power and make sure it has enough trains
int trainsReq = (int) powerReqs.get(token);
for (CharacterPower cp : powers.values()) {
if (cp.power.getToken() == pr.getToken()) {
if (this.getTotalTrains() <= trainsReq && cp.getTrains() > 0) {
ErrorPopupMsg.sendErrorMsg(pc, "You must refine " + cp.power.getName() + " to 0 before refining any more from this power.");
return false;
}
}
}
}
}
}
return true;
}
private boolean canTrain(PlayerCharacter pc) {
if (this.power == null || pc == null)
return false;
int token = this.power.getToken();
boolean valid = false;
Race race = pc.race;
if (race != null) {
if (CharacterPower.powerAllowed(token, race.getPowersGranted(), pc))
return true;
} else
return false;
BaseClass bc = pc.baseClass;
if (bc != null) {
if (CharacterPower.powerAllowed(token, bc.getPowersGranted(), pc))
return true;
} else
return false;
PromotionClass promo = pc.getPromotionClass();
if (promo != null)
if (CharacterPower.powerAllowed(token, promo.getPowersGranted(), pc))
return true;
ArrayList<CharacterRune> runes = pc.getRunes();
for (CharacterRune rune : runes)
if (CharacterPower.powerAllowed(token, rune.getPowersGranted(), pc))
return true;
return false;
}
@Override
public void updateDatabase() {
DbManager.CharacterPowerQueries.updateDatabase(this);
}
public int getRequiredLevel() {
return requiredLevel;
}
public void setRequiredLevel(int requiredLevel) {
this.requiredLevel = requiredLevel;
}
}