// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.objects; import engine.gameManager.DbManager; import engine.gameManager.PowersManager; import engine.mbEnums; 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(mbEnums.GameObjectType.PlayerCharacter) == false) return; PlayerCharacter pc = (PlayerCharacter) absChar; // First add powers that don't exist ConcurrentHashMap powers = pc.getPowers(); // ArrayList 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 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 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 powersGranted, ConcurrentHashMap powers, PlayerCharacter pc) { ConcurrentHashMap 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 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(mbEnums.GameObjectType.PlayerCharacter) == false) return; PlayerCharacter pc = (PlayerCharacter) absChar; ConcurrentHashMap 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 powersGranted, PlayerCharacter pc) { ConcurrentHashMap skills = pc.getSkills(); ConcurrentHashMap 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 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 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 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 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 powersGranted, ConcurrentHashMap powers, PlayerCharacter pc) { for (PowerReq pr : powersGranted) { ConcurrentHashMap 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 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; } }