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.
623 lines
21 KiB
623 lines
21 KiB
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . |
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· |
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ |
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ |
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ |
|
// Magicbane Emulator Project © 2013 - 2022 |
|
// www.magicbane.com |
|
|
|
|
|
package engine.objects; |
|
|
|
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(PlayerCharacter pc) { |
|
if (pc == null) |
|
return; |
|
|
|
// 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.getRace(); |
|
if (race != null) { |
|
CharacterPower.grantPowers(race.getPowersGranted(), powers, pc); |
|
} else |
|
Logger.error("Failed to find Race for player " + pc.getObjectUUID()); |
|
BaseClass bc = pc.getBaseClass(); |
|
if (bc != null) { |
|
CharacterPower.grantPowers(bc.getPowersGranted(), powers, pc); |
|
} else |
|
Logger.error("Failed to find BaseClass for player " + pc.getObjectUUID()); |
|
PromotionClass promo = pc.getPromotionClass(); |
|
if (promo != null) |
|
CharacterPower.grantPowers(promo.getPowersGranted(), powers, pc); |
|
ArrayList<CharacterRune> runes = pc.getRunes(); |
|
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(PlayerCharacter pc) { |
|
if (pc == null) |
|
return; |
|
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.getRace(); |
|
if (race != null) { |
|
if (!canRefine(race.getPowersGranted(), powers, pc)) |
|
return false; |
|
} else |
|
return false; |
|
BaseClass bc = pc.getBaseClass(); |
|
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.getRace(); |
|
if (race != null) { |
|
if (CharacterPower.powerAllowed(token, race.getPowersGranted(), pc)) |
|
return true; |
|
} else |
|
return false; |
|
BaseClass bc = pc.getBaseClass(); |
|
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; |
|
} |
|
}
|
|
|