Files
Server/src/engine/objects/Race.java
T

451 lines
14 KiB
Java
Raw Normal View History

2026-07-26 19:08:44 -04:00
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
2022-04-30 09:41:17 -04:00
package engine.objects;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.mbEnums.RaceType;
2022-04-30 09:41:17 -04:00
import engine.net.ByteBufferWriter;
import engine.parsers.cache.CacheParserUtil;
import engine.parsers.objects.RuneTemplate;
2022-04-30 09:41:17 -04:00
import engine.server.MBServerStatics;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
2022-04-30 09:41:17 -04:00
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
public class Race {
2023-07-15 09:23:48 -04:00
// Local class cache
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
private static ConcurrentHashMap<Integer, Race> _raceByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
2022-04-30 09:41:17 -04:00
// Creation points before the race rune's rune_creation_cost is taken off
private static final int STARTING_POINT_POOL = 30;
2023-07-15 09:23:48 -04:00
private final String name;
private final String description;
private final int raceRuneID;
private final short strStart;
private final short strMin;
private final short strMax;
private final short dexStart;
private final short dexMin;
private final short dexMax;
private final short conStart;
private final short conMin;
private final short conMax;
private final short intStart;
private final short intMin;
private final short intMax;
private final short spiStart;
private final short spiMin;
private final short spiMax;
private final short healthBonus;
private final short manaBonus;
private final short staminaBonus;
private final byte startingPoints;
private final float minHeight;
private final float strHeightMod;
private final HashSet<Integer> skinColors;
private final HashSet<Integer> beardColors;
private final HashSet<Integer> hairColors;
private final ArrayList<BaseClass> baseClasses;
private final ArrayList<SkillReq> skillsGranted;
private final ArrayList<PowerReq> powersGranted;
private mbEnums.RaceType raceType;
2023-07-15 09:23:48 -04:00
private int token = 0;
private HashSet<Integer> hairStyles;
private HashSet<Integer> beardStyles;
private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
/**
* ResultSet Constructor
*/
public Race(ResultSet rs) throws SQLException {
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
this.raceRuneID = rs.getInt("ID");
this.raceType = mbEnums.RaceType.getRaceTypebyRuneID(raceRuneID);
2023-07-15 09:23:48 -04:00
this.name = rs.getString("name");
this.description = rs.getString("description");
this.strStart = rs.getShort("strStart");
this.strMin = rs.getShort("strMin");
this.strMax = rs.getShort("strMax");
this.dexStart = rs.getShort("dexStart");
this.dexMin = rs.getShort("dexMin");
this.dexMax = rs.getShort("dexMax");
this.conStart = rs.getShort("conStart");
this.conMin = rs.getShort("conMin");
this.conMax = rs.getShort("conMax");
this.intStart = rs.getShort("intStart");
this.intMin = rs.getShort("intMin");
this.intMax = rs.getShort("intMax");
this.spiStart = rs.getShort("spiStart");
this.spiMin = rs.getShort("spiMin");
this.spiMax = rs.getShort("spiMax");
this.token = rs.getInt("token");
this.healthBonus = rs.getShort("healthBonus");
this.manaBonus = rs.getShort("manaBonus");
this.staminaBonus = rs.getShort("staminaBonus");
this.startingPoints = rs.getByte("startingPoints");
this.raceType = RaceType.getRaceTypebyRuneID(this.raceRuneID);
this.minHeight = rs.getFloat("minHeight");
this.strHeightMod = rs.getFloat("strHeightMod");
this.hairStyles = DbManager.RaceQueries.HAIR_STYLES_FOR_RACE(raceRuneID);
this.beardStyles = DbManager.RaceQueries.BEARD_STYLES_FOR_RACE(raceRuneID);
this.skinColors = DbManager.RaceQueries.SKIN_COLOR_FOR_RACE(raceRuneID);
this.beardColors = DbManager.RaceQueries.BEARD_COLORS_FOR_RACE(raceRuneID);
this.hairColors = DbManager.RaceQueries.HAIR_COLORS_FOR_RACE(raceRuneID);
this.baseClasses = DbManager.BaseClassQueries.GET_BASECLASS_FOR_RACE(raceRuneID);
this.skillsGranted = SkillReq.getSkillReqsForRune(raceRuneID);
2023-07-15 09:23:48 -04:00
this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID);
this.effectsList = MobBaseEffects.getEffectsForRune(this.raceRuneID);
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
}
2022-04-30 09:41:17 -04:00
/**
* Rune Template Constructor
* minHeight and strHeightMod are server-only and come from the static_rune_race row.
*/
public Race(RuneTemplate rune, Race dbRace) {
this.raceRuneID = rune.template_id;
this.raceType = mbEnums.RaceType.getRaceTypebyRuneID(raceRuneID);
this.name = rune.obj_name;
this.description = rune.rune_dsc;
this.strStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0).shortValue();
this.strMin = 0;
this.strMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0).shortValue();
this.dexStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0).shortValue();
this.dexMin = 0;
this.dexMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0).shortValue();
this.conStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0).shortValue();
this.conMin = 0;
this.conMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0).shortValue();
this.intStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0).shortValue();
this.intMin = 0;
this.intMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0).shortValue();
this.spiStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0).shortValue();
this.spiMin = 0;
this.spiMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0).shortValue();
this.token = Hasher.SBStringHash(rune.rune_sub_type);
this.healthBonus = (short) rune.rune_health;
this.manaBonus = (short) rune.rune_mana;
this.staminaBonus = (short) rune.rune_stamina;
this.startingPoints = (byte) (STARTING_POINT_POOL - rune.rune_creation_cost);
this.minHeight = dbRace.minHeight;
this.strHeightMod = dbRace.strHeightMod;
this.hairStyles = new HashSet<>(rune.rune_hair);
this.beardStyles = new HashSet<>(rune.rune_beard);
this.skinColors = DbManager.RaceQueries.SKIN_COLOR_FOR_RACE(raceRuneID);
this.beardColors = DbManager.RaceQueries.BEARD_COLORS_FOR_RACE(raceRuneID);
this.hairColors = DbManager.RaceQueries.HAIR_COLORS_FOR_RACE(raceRuneID);
this.baseClasses = getBaseClassesForRace(rune);
this.skillsGranted = SkillReq.getSkillReqsForRune(raceRuneID);
this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID);
this.effectsList = MobBaseEffects.getEffectsForRune(this.raceRuneID);
// Races without hair styles (Aracoix, Shade) select style 0, which creation validates
if (this.hairStyles.isEmpty())
this.hairStyles.add(0);
}
// Base classes are the creation class runes whose item_race_req names this race's sub type.
// Every creation class rune's race list is a whitelist (restrict false).
private static ArrayList<BaseClass> getBaseClassesForRace(RuneTemplate rune) {
ArrayList<BaseClass> baseClasses = new ArrayList<>();
mbEnums.MonsterType monsterType;
BaseClass baseClass;
monsterType = CacheParserUtil.readEnum(mbEnums.MonsterType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type");
for (RuneTemplate classRune : RuneTemplate._runes.values()) {
if (classRune.rune_type != mbEnums.RuneType.CLASS || !classRune.rune_is_standard_character_creation)
continue;
if (!classRune.item_race_req.contains(monsterType))
continue;
baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(classRune.template_id);
if (baseClass != null)
baseClasses.add(baseClass);
}
return baseClasses;
}
// Iterates the static_rune_race rows: no rune field separates 2018-2021 from the mob race runes,
// and each row still holds the server-only heights.
2023-07-15 09:23:48 -04:00
public static void loadAllRaces() {
ConcurrentHashMap<Integer, Race> dbRaces;
ConcurrentHashMap<Integer, Race> races;
RuneTemplate rune;
dbRaces = DbManager.RaceQueries.LOAD_ALL_RACES();
races = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
for (Race dbRace : dbRaces.values()) {
rune = RuneTemplate._runes.get(dbRace.raceRuneID);
if (rune == null || rune.rune_type != mbEnums.RuneType.RACE) {
Logger.warn("Race " + dbRace.raceRuneID + " has no race rune; loaded from static_rune_race");
races.put(dbRace.raceRuneID, dbRace);
continue;
}
races.put(dbRace.raceRuneID, new Race(rune, dbRace));
}
Race._raceByID = races;
Logger.info("Loaded " + races.size() + " races from runes");
2023-07-15 09:23:48 -04:00
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public static Race getRace(int id) {
return _raceByID.get(id);
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
@Override
public boolean equals(Object object) {
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
if ((object instanceof Race) == false)
return false;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
Race race = (Race) object;
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
return this.raceRuneID == race.raceRuneID;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
@Override
public int hashCode() {
return this.raceRuneID;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
/*
* Getters
*/
public String getName() {
return name;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public String getDescription() {
return description;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getStrStart() {
return strStart;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getStrMin() {
return strMin;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getStrMax() {
return strMax;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getDexStart() {
return dexStart;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getDexMin() {
return dexMin;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getDexMax() {
return dexMax;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getConStart() {
return conStart;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getConMin() {
return conMin;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getConMax() {
return conMax;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getIntStart() {
return intStart;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getIntMin() {
return intMin;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getIntMax() {
return intMax;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getSpiStart() {
return spiStart;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getSpiMin() {
return spiMin;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public short getSpiMax() {
return spiMax;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public byte getStartingPoints() {
return startingPoints;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public int getToken() {
return token;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public final HashSet<Integer> getHairStyles() {
return hairStyles;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public final HashSet<Integer> getBeardStyles() {
return beardStyles;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public final HashSet<Integer> getBeardColors() {
return beardColors;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public HashSet<Integer> getHairColors() {
return hairColors;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public HashSet<Integer> getSkinColors() {
return skinColors;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public int getNumSkinColors() {
return this.skinColors.size();
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public int getNumHairColors() {
return this.hairColors.size();
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public int getNumBeardColors() {
return this.beardColors.size();
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public final ArrayList<BaseClass> getValidBaseClasses() {
return baseClasses;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public ArrayList<Integer> getAllowedRunes() {
2022-04-30 09:41:17 -04:00
return RuneBase.AllowedRaceRunesMap.get(raceRuneID);
2023-07-15 09:23:48 -04:00
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public ArrayList<SkillReq> getSkillsGranted() {
return this.skillsGranted;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public ArrayList<PowerReq> getPowersGranted() {
return this.powersGranted;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
/*
* Validators
*/
public boolean isValidBeardStyle(int id) {
return this.beardStyles.contains(id);
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public boolean isValidBeardColor(int id) {
return this.beardColors.contains(id);
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public boolean isValidHairStyle(int id) {
return this.hairStyles.contains(id);
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public boolean isValidHairColor(int id) {
return this.hairColors.contains(id);
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public boolean isValidSkinColor(int id) {
return this.skinColors.contains(id);
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public boolean isAllowedRune(RuneBase rb) {
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
if (this.getAllowedRunes() != null)
if (this.getAllowedRunes().contains(rb.getObjectUUID()))
return true;
return false;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public float getHealthBonus() {
return this.healthBonus;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public float getManaBonus() {
return this.manaBonus;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public float getStaminaBonus() {
return this.staminaBonus;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public float getMinHeight() {
return this.minHeight;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public float getStrHeightMod() {
return this.strHeightMod;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public float getHeight(short str) {
return this.minHeight + (this.strHeightMod * str);
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
/*
* Serializing
*/
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public float getCenterHeight(short str) {
return getHeight(str) / 2f;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public void serializeForClientMsg(ByteBufferWriter writer) {
writer.putInt(1); // For Race
writer.putInt(0); // Pad
writer.putInt(this.raceRuneID);
2022-04-30 09:41:17 -04:00
writer.putInt(mbEnums.GameObjectType.Race.ordinal());
2023-07-15 09:23:48 -04:00
writer.putInt(raceRuneID);
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public int getRaceRuneID() {
return raceRuneID;
}
2022-04-30 09:41:17 -04:00
public mbEnums.RaceType getRaceType() {
2023-07-15 09:23:48 -04:00
return raceType;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
public ArrayList<MobBaseEffects> getEffectsList() {
return effectsList;
}
2022-04-30 09:41:17 -04:00
}