private synchronized boolean trySendPrivateStatusUpdate(Player player) {
if (player instanceof Player p && p.getClient() instanceof GameClient client && client.checkState(EConnectionState.IN_GAME)) {
try {
final CreatureStats stats = p.getStats();
boolean changed = false;
S_STATUS_UPDATE su = new S_STATUS_UPDATE(p.getObjectId());
//В клиенте HF эти данные не обрабатываются или обрабатываются не явно.
EClassID nclass_id = p.getClassId();
if (this.class_id != nclass_id) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CLASS, nclass_id.getId());
}
final int nlevel = p.getLevel();
if (this.level != nlevel) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_LEVEL, nlevel);
}
final long nexp = p.getExp();
if (this.experience_points != nexp) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_EXP, nexp);
}
final int nstr = stats.getSTR();
if (this.str_value != nstr) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_STR, nstr);
}
final int ndex = stats.getDEX();
if (this.dex_value != ndex) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_DEX, ndex);
}
final int ncon = stats.getCON();
if (this.con_value != ncon) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CON, ncon);
}
final int nint = stats.getINT();
if (this.int_value != nint) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_INT, nint);
}
final int nwit = stats.getWIT();
if (this.wit_value != nwit) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_WIT, nwit);
}
final int nmen = stats.getMEN();
if (this.men_value != nmen) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_MEN, nmen);
}
//ПТС шлет обновления HP\CP отдельным пакетом с 4 параметрами, а MP отдельным пакетом с 2 параметрами.
//HP\CP отправляются отдельным пакетом.
final int ncurrent_hp = (int) stats.getCurrentHp();
final int nmaximum_hp = (int) stats.getMaxHp();
final int ncurrent_cp = (int) stats.getCurrentCp();
final int nmaximum_cp = (int) stats.getMaxCp();
if (this.current_hp != ncurrent_hp ||this. maximum_hp != nmaximum_hp || this.current_cp != ncurrent_cp || this.maximum_cp != nmaximum_cp) {
final S_STATUS_UPDATE su_hpcp = new S_STATUS_UPDATE(p.getObjectId());
su_hpcp.addAttribute(ECHAR_PARAM_TYPE.VCP_HP, ncurrent_hp);
su_hpcp.addAttribute(ECHAR_PARAM_TYPE.VCP_MAXHP, nmaximum_hp);
su_hpcp.addAttribute(ECHAR_PARAM_TYPE.VCP_CP, ncurrent_cp);
su_hpcp.addAttribute(ECHAR_PARAM_TYPE.VCP_MAXCP, nmaximum_cp);
player.sendPacket(su_hpcp);
changed = true;
}
//MP отправляются другим отдельным пакетом.
final int ncurrent_mp = (int) stats.getCurrentMp();
final int nmaximum_mp = (int) stats.getMaxMp();
if (this.current_mp != ncurrent_mp || maximum_mp != nmaximum_mp) {
S_STATUS_UPDATE su_mp = new S_STATUS_UPDATE(p.getObjectId());
su_mp.addAttribute(ECHAR_PARAM_TYPE.VCP_MP, ncurrent_mp);
su_mp.addAttribute(ECHAR_PARAM_TYPE.VCP_MAXMP, nmaximum_mp);
player.sendPacket(su_mp);
changed = true;
}
final long nsp = p.getSp();
if (this.sp != nsp) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_SP, nsp);
}
final int ncurrent_load = p.getCurrentLoad();
if (this.current_load != ncurrent_load) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CARRINGWEIGHT, ncurrent_load);
}
final int nmaximum_load = p.getMaxLoad();
if (this.maximum_load != nmaximum_load) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CARRYWEIGHT, nmaximum_load);
}
final int nattack_range = stats.getPhysicalAttackRange();
if (this.physical_attack_range != nattack_range) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_ATTACKRANGE, nattack_range);
}
final int nphysical_attack = (int) stats.getPAtk();
if (this.physical_attack != nphysical_attack) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_PHYSICALATTACK, nphysical_attack);
}
final int nphysical_attack_speed = (int) stats.getPAtkSpd();
if (this.physical_attack_speed != nphysical_attack_speed) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_PHYSICALATTACKSPEED, nphysical_attack_speed);
}
final int nphysical_defence = (int) stats.getPDef();
if (this.physical_defence != nphysical_defence) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_PHYSICALDEFENSE, nphysical_defence);
}
final int navoid = (int) stats.getEvasionRate();
if (this.avoid != navoid) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_PHYSICALAVOID, navoid);
}
final int nhit = (int) stats.getAccuracy();
if (this.hit != nhit) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_HITRATE, nhit);
}
final int ncritical_rate = (int) stats.getCriticalHit();
if (this.critical_rate != ncritical_rate) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CRITICALRATE, ncritical_rate);
}
final int nmagical_attack = (int) stats.getMAtk(null);
if (this.magical_attack != nmagical_attack) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_MAGICALATTACK, nmagical_attack);
}
final int nmagical_attack_speed = (int) stats.getMAtkSpd();
if (this.magical_attack_speed != nmagical_attack_speed) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_MAGICCASTINGSPEED, nmagical_attack_speed);
}
final int nmagical_defence = (int) stats.getMDef(null);
if (this.magical_defence != nmagical_defence) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_MAGICDEFENSE, nmagical_defence);
}
final int npvp_flag = p.isGuilty();
if (this.pvp_flag != npvp_flag) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_ISGUILTY, npvp_flag);
}
final int nkarma = p.getKarma();
if (this.karma != nkarma) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CRIMINAL_RATE, nkarma);
}
final double dcol_radius = p.getCurrentColRadius();
if (this.col_radius != dcol_radius) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_COLLISION_RADIUS, dcol_radius);
}
final double dcol_height = p.getCurrentColHeight();
if (this.col_height != dcol_height) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_COLLISION_HEIGHT, dcol_height);
}
final double dbase_speed = stats.getBaseSpeed();
if (this.base_speed != dbase_speed) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_BASE_SPEED, dbase_speed);
}
final double dspeed_modifier = stats.getMovementSpeedMultiplier();
if (this.speed_modifier != dspeed_modifier) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_SPEED_MODIFIER, dspeed_modifier);
}
final EAttackType nattack_type = p.getAttackType();
if (this.attack_type != nattack_type) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_ATTACK_TYPE, nattack_type.getValue());
}
final double dhp_regen = stats.getOrgHpRegen();
if (this.hp_regen != dhp_regen) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_HP_REGEN, dhp_regen);
}
if (su.hasAttributes()) {
player.sendPacket(su);
changed = true;
}
return changed;
} catch (Exception e) {
log.error("Can't make private status update for {}", p.toIDString(), e);
}
}
return false;
}
private synchronized boolean trySendPrivateStatusUpdate(Player player) {
if (player instanceof Player p && p.getClient() instanceof GameClient client && client.checkState(EConnectionState.IN_GAME)) {
try {
final CreatureStats stats = p.getStats();
boolean changed = false;
S_STATUS_UPDATE su = new S_STATUS_UPDATE(p.getObjectId());
//В клиенте HF эти данные не обрабатываются или обрабатываются не явно.
EClassID nclass_id = p.getClassId();
if (this.class_id != nclass_id) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CLASS, nclass_id.getId());
}
final int nlevel = p.getLevel();
if (this.level != nlevel) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_LEVEL, nlevel);
}
final long nexp = p.getExp();
if (this.experience_points != nexp) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_EXP, nexp);
}
final int nstr = stats.getSTR();
if (this.str_value != nstr) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_STR, nstr);
}
final int ndex = stats.getDEX();
if (this.dex_value != ndex) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_DEX, ndex);
}
final int ncon = stats.getCON();
if (this.con_value != ncon) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CON, ncon);
}
final int nint = stats.getINT();
if (this.int_value != nint) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_INT, nint);
}
final int nwit = stats.getWIT();
if (this.wit_value != nwit) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_WIT, nwit);
}
final int nmen = stats.getMEN();
if (this.men_value != nmen) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_MEN, nmen);
}
final long nsp = p.getSp();
if (this.sp != nsp) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_SP, nsp);
}
final int ncurrent_load = p.getCurrentLoad();
if (this.current_load != ncurrent_load) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CARRINGWEIGHT, ncurrent_load);
}
final int nmaximum_load = p.getMaxLoad();
if (this.maximum_load != nmaximum_load) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CARRYWEIGHT, nmaximum_load);
}
final int nattack_range = stats.getPhysicalAttackRange();
if (this.physical_attack_range != nattack_range) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_ATTACKRANGE, nattack_range);
}
final int nphysical_attack = (int) stats.getPAtk();
if (this.physical_attack != nphysical_attack) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_PHYSICALATTACK, nphysical_attack);
}
final int nphysical_attack_speed = (int) stats.getPAtkSpd();
if (this.physical_attack_speed != nphysical_attack_speed) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_PHYSICALATTACKSPEED, nphysical_attack_speed);
}
final int nphysical_defence = (int) stats.getPDef();
if (this.physical_defence != nphysical_defence) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_PHYSICALDEFENSE, nphysical_defence);
}
final int navoid = (int) stats.getEvasionRate();
if (this.avoid != navoid) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_PHYSICALAVOID, navoid);
}
final int nhit = (int) stats.getAccuracy();
if (this.hit != nhit) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_HITRATE, nhit);
}
final int ncritical_rate = (int) stats.getCriticalHit();
if (this.critical_rate != ncritical_rate) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CRITICALRATE, ncritical_rate);
}
final int nmagical_attack = (int) stats.getMAtk(null);
if (this.magical_attack != nmagical_attack) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_MAGICALATTACK, nmagical_attack);
}
final int nmagical_attack_speed = (int) stats.getMAtkSpd();
if (this.magical_attack_speed != nmagical_attack_speed) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_MAGICCASTINGSPEED, nmagical_attack_speed);
}
final int nmagical_defence = (int) stats.getMDef(null);
if (this.magical_defence != nmagical_defence) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_MAGICDEFENSE, nmagical_defence);
}
final int npvp_flag = p.isGuilty();
if (this.pvp_flag != npvp_flag) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_ISGUILTY, npvp_flag);
}
final int nkarma = p.getKarma();
if (this.karma != nkarma) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_CRIMINAL_RATE, nkarma);
}
final double dcol_radius = p.getCurrentColRadius();
if (this.col_radius != dcol_radius) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_COLLISION_RADIUS, dcol_radius);
}
final double dcol_height = p.getCurrentColHeight();
if (this.col_height != dcol_height) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_COLLISION_HEIGHT, dcol_height);
}
final double dbase_speed = stats.getBaseSpeed();
if (this.base_speed != dbase_speed) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_BASE_SPEED, dbase_speed);
}
final double dspeed_modifier = stats.getMovementSpeedMultiplier();
if (this.speed_modifier != dspeed_modifier) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_SPEED_MODIFIER, dspeed_modifier);
}
final EAttackType nattack_type = p.getAttackType();
if (this.attack_type != nattack_type) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_ATTACK_TYPE, nattack_type.getValue());
}
final double dhp_regen = stats.getOrgHpRegen();
if (this.hp_regen != dhp_regen) {
su.addAttribute(ECHAR_PARAM_TYPE.VCP_HP_REGEN, dhp_regen);
}
if (su.hasAttributes()) {
player.sendPacket(su);
changed = true;
}
//ПТС шлет обновления HP\CP отдельным пакетом с 4 параметрами, а MP отдельным пакетом с 2 параметрами.
//HP\CP отправляются отдельным пакетом.
final int ncurrent_hp = (int) stats.getCurrentHp();
final int nmaximum_hp = (int) stats.getMaxHp();
final int ncurrent_cp = (int) stats.getCurrentCp();
final int nmaximum_cp = (int) stats.getMaxCp();
if (this.current_hp != ncurrent_hp ||this. maximum_hp != nmaximum_hp || this.current_cp != ncurrent_cp || this.maximum_cp != nmaximum_cp) {
final S_STATUS_UPDATE su_hpcp = new S_STATUS_UPDATE(p.getObjectId());
su_hpcp.addAttribute(ECHAR_PARAM_TYPE.VCP_HP, ncurrent_hp);
su_hpcp.addAttribute(ECHAR_PARAM_TYPE.VCP_MAXHP, nmaximum_hp);
su_hpcp.addAttribute(ECHAR_PARAM_TYPE.VCP_CP, ncurrent_cp);
su_hpcp.addAttribute(ECHAR_PARAM_TYPE.VCP_MAXCP, nmaximum_cp);
player.sendPacket(su_hpcp);
changed = true;
}
//MP отправляются другим отдельным пакетом.
final int ncurrent_mp = (int) stats.getCurrentMp();
final int nmaximum_mp = (int) stats.getMaxMp();
if (this.current_mp != ncurrent_mp || maximum_mp != nmaximum_mp) {
S_STATUS_UPDATE su_mp = new S_STATUS_UPDATE(p.getObjectId());
su_mp.addAttribute(ECHAR_PARAM_TYPE.VCP_MP, ncurrent_mp);
su_mp.addAttribute(ECHAR_PARAM_TYPE.VCP_MAXMP, nmaximum_mp);
player.sendPacket(su_mp);
changed = true;
}
return changed;
} catch (Exception e) {
log.error("Can't make private status update for {}", p.toIDString(), e);
}
}
return false;
}
У меня отдельный объект, хранящий слепок базовых параметров игрока, каждый из которых имеет 3 флага: Публичная инфа, приватная инфа и статус апдейт(ну или сразу несколько, например как коэффициент скорости движения)или как у тебя данный узел реализован?
А ты говоришь про коэффициент скорости движения, там же есть еще коэф скорости атаки, он же меняется при изменении стата скорости бега и соответственно скорости атаки, эти коэф публичные, они как для персонажа, так и для всех видимых, что-бы правильно анимацию клиенты рассчитывали. Так вот ты как их отсылаешь? пакетом UserInfo, а другим чарам CharInfo?У меня отдельный объект, хранящий слепок базовых параметров игрока, каждый из которых имеет 3 флага: Публичная инфа, приватная инфа и статус апдейт(ну или сразу несколько, например как коэффициент скорости движения)
В коде я вызываю единственный метод player.sendChanges(), после вызова которого, проходит получение актульных значений и сверка их с сохраненными, создавая маску значений. После этого, исходя из посчитанной маски решается какие пакеты и кому разослать. После этого создаются атомарные задачи на определенный тайминг и после короткого ожидания в 1-2 тика, происходит обновление сохраненных статов и рассылка пакетов получателям. Если достаточно обновления S_STATUS_UPDATE, то полетит только он. Если изменилась только приватная инфа, то будет отправка UserInfo, если изменилась публичная инфа, то идет и отправка S_USER_INFO, и бродкаст S_CHAR_INFO. Также там и пати инфа, и олимп пакеты и куча всего другого.
Да, я тебе про это и говорю, что публичную инфу ты не разошлешь никак точечно, т.к у тебя нет информации о том, имеет ли конкретный клиент-получатель у тебя ПОЛНУЮ инфу или не имеет. Ты не можешь это гарантировать, из-за сетевых задержек, неконсистентностью данных о текущем игровом регионе игрока, а также еще кучи факторов. Поэтому обновление публичной инфы других чаров - всегда через полный CharInfo, без исключений. Себе инфу ты можешь слать точечно, кроме тех случаев, когда поменялся параметр, который есть только в UserInfo. Тогда без вариантов. В новых клиентах используется похожий механизм компрессии приватных пакетов, когда у тебя формируется полный UserInfo только в первый раз, а дальше обновления приходят порционно, по мере изменений. Там пакет разделен на блоки данных, каждый из которых может обновляться независимо.А ты говоришь про коэффициент скорости движения, там же есть еще коэф скорости атаки, он же меняется при изменении стата скорости бега и соответственно скорости атаки, эти коэф публичные, они как для персонажа, так и для всех видимых, что-бы правильно анимацию клиенты рассчитывали. Так вот ты как их отсылаешь? пакетом UserInfo, а другим чарам CharInfo?
Ну я обновляю так, как это делает ПТС, судя по тому, что я видел в снифере. Отдельно пакет для пар HP/CP, отдельно пакт для пары MP. Остальное шлю общим пакетом, что изменилось.Ну я StatusUpdate обновляю :
LEVEL(1), // Уровень.
EXP(2), // Опыт.
CUR_HP(9), // Текущее количество HP.
MAX_HP(10), // Максимальное количество HP.
CUR_MP(11), // Текущее количество MP.
MAX_MP(12), // Максимальное количество MP.
SP(13), // Очки умений.
CUR_LOAD(14), // Текущая загруженность.
MAX_LOAD(15), // Максимальная грузоподъемность.
PVP_FLAG(26), // PvP-флаг.
KARMA(27), // Карма.
CUR_CP(33), // Текущее количество CP.
MAX_CP(34); // Максимальное количество CP.
То есть беру по максимуму, что-бы не использовать большие пакеты.
package ru.nts.gameserver.system.enums;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
/**
* @author Aristo
* @since 01.06.2025 00:47
*/
public enum ECHAR_PARAM_TYPE {
VCP_CLASS,
VCP_LEVEL,
VCP_EXP,
VCP_STR,
VCP_DEX,
VCP_CON,
VCP_INT,
VCP_WIT,
VCP_MEN,
VCP_HP,
VCP_MAXHP,
VCP_MP,
VCP_MAXMP,
VCP_SP,
VCP_CARRINGWEIGHT,
VCP_CARRYWEIGHT,
VCP_ATTACKRANGE,
VCP_PHYSICALATTACK,
VCP_PHYSICALATTACKSPEED,
VCP_PHYSICALDEFENSE,
VCP_PHYSICALAVOID,
VCP_HITRATE,
VCP_CRITICALRATE,
VCP_MAGICALATTACK,
VCP_MAGICCASTINGSPEED,
VCP_MAGICDEFENSE,
VCP_ISGUILTY,
VCP_CRIMINAL_RATE,
VCP_COLLISION_RADIUS,
VCP_COLLISION_HEIGHT,
VCP_BASE_SPEED,
VCP_SPEED_MODIFIER,
VCP_ATTACK_TYPE,
VCP_CP,
VCP_MAXCP,
VCP_HP_REGEN,
VCP_ULTIMATE_SKILL_POINT,
VCP_SMALLWINDOW_UPDATE,
VCP_LUC,
VCP_CHA,
VCP_MAX;
public static final ObjectArrayList<ECHAR_PARAM_TYPE> list = ObjectArrayList.wrap(values());
}
Ну в новых хрониках выбор есть - в такой ситуации можно просто маленький блок с этими данными отправить, а не весь UI. У меня в целом так и работает, но вот насчет того чтобы слать изменения экспы/сп чисто через SU надо будет подумать, если оно конечно будет корректно обновлять в окне стат данные...То есть таким образом можно много где в коде отправлять маленький StatusUpdate, вместо громоздкого UserInfo. Вот к примеру, идет фарм, убиваешь моба, идет перерасчет exp, если уровень меняется, тут понятно UserInfo так как там и статы пересчитываются и т.д. Но если уровень не меняется, а опыт меняется, ты же не будешь после каждого моба слать UserInfo, или как у тебя данный узел реализован?
final var statusUpdate = new StatusUpdate(getOwner().getId());
statusUpdate.addAttribute(StatusType.SP, newSp);
statusUpdate.addAttribute(StatusType.MAX_CP, getOwner().getStats().getStat(StatType.MAX_CP));
statusUpdate.addAttribute(StatusType.CUR_CP, getOwner().getVitality().getCurrentCp());
getOwner().sendPackets(false, statusUpdate);
/**
* Обновляет очки опыта, очки умений и уровень при необходимости.
*
* @param deltaExp Положительные/отрицательные очки опыта.
* @param deltaSp Положительные/отрицательные очки умений.
*/
public void updateAll(long deltaExp, int deltaSp) {
lock.writeLock().lock();
try {
final var oldLevel = level;
final var oldExp = exp;
final int oldSp = sp;
final var newExp = Math.max(0, Math.min(oldExp + deltaExp, 6299994999L));
final int newSp = (int) Math.max(0, Math.min((long) sp + deltaSp, Integer.MAX_VALUE));
if (oldExp == newExp && oldSp == newSp) {
return;
}
exp = newExp;
sp = newSp;
var newLevel = oldLevel;
while (newLevel < 80 && exp >= DataLevel.getExp(newLevel + 1)) {
newLevel++;
}
while (newLevel > 1 && exp < DataLevel.getExp(newLevel)) {
newLevel--;
}
if (newLevel != oldLevel) {
level = newLevel;
final var skills = getOwner().getSkill();
final var baseClassID = getOwner().getClazz().getClassType().getBaseId();
final var baseClassTemplate = ClassData.getInstance().getClassTemplate(baseClassID);
var updateSkills = false;
if (baseClassTemplate != null) {
for (final var skillEntry : baseClassTemplate.getBaseSkills().entrySet()) {
final var skillId = skillEntry.getKey();
final var levelsArray = skillEntry.getValue();
var availableLevel = 0;
for (int i = 0; i < levelsArray.length; i++) {
if (levelsArray[i][1] <= newLevel) {
availableLevel = i + 1;
}
}
final var skill = skills.getSkillById(skillId);
if (skill != null) {
if (availableLevel == 0) {
skills.removeSkill(skillId, true);
updateSkills = true;
} else if (availableLevel != skill.getLevel()) {
skill.setLevel(availableLevel);
skills.updateDatabase(skillId, availableLevel);
updateSkills = true;
}
} else if (availableLevel > 0) {
skills.addSkill(skillId, availableLevel, true);
updateSkills = true;
}
}
}
if (updateSkills) {
skills.calculateStats();
getOwner().getStats().calculateStats(List.of(StatType.VALUES));
getOwner().sendPackets(false, new UserInfo(getOwner()), new SkillList(getOwner()));
} else {
getOwner().getStats().calculateStats(List.of(StatType.VALUES));
getOwner().sendPackets(false, new UserInfo(getOwner()));
}
if (newLevel > oldLevel) {
getOwner().sendPacketsVisible(true, false, 1500, new MagicSkillUse(getOwner(), getOwner(), 2124, 1, 0, 0, false));
}
} else {
final var diffExp = newExp - oldExp;
if (diffExp >= Integer.MIN_VALUE && diffExp <= Integer.MAX_VALUE) {
final var statusUpdate = new StatusUpdate(getOwner().getId());
if (oldSp != newSp) {
statusUpdate.addAttribute(StatusType.SP, newSp);
}
statusUpdate.addAttribute(StatusType.EXP, (int) diffExp);
statusUpdate.addAttribute(StatusType.MAX_CP, getOwner().getStats().getStat(StatType.MAX_CP));
statusUpdate.addAttribute(StatusType.CUR_CP, getOwner().getVitality().getCurrentCp());
getOwner().sendPackets(false, statusUpdate);
} else {
getOwner().sendPackets(false, new UserInfo(getOwner()));
}
}
} finally {
lock.writeLock().unlock();
}
}
final var diffExp = newExp - oldExp;
if (diffExp >= Integer.MIN_VALUE && diffExp <= Integer.MAX_VALUE) {
final var statusUpdate = new StatusUpdate(getOwner().getId());
if (oldSp != newSp) {
statusUpdate.addAttribute(StatusType.SP, newSp);
}
statusUpdate.addAttribute(StatusType.EXP, (int) diffExp);
statusUpdate.addAttribute(StatusType.MAX_CP, getOwner().getStats().getStat(StatType.MAX_CP));
statusUpdate.addAttribute(StatusType.CUR_CP, getOwner().getVitality().getCurrentCp());
getOwner().sendPackets(false, statusUpdate);
} else {
getOwner().sendPackets(false, new UserInfo(getOwner()));
}
_activeChar.sendUserInfo(UserInfoType.CUR_HPMPCP_EXP_SP);
if (containsMask(UserInfoType.CUR_HPMPCP_EXP_SP))
{
writeH(38);
writeD((int) Math.round(_player.getCurrentHp()));
writeD((int) Math.round(_player.getCurrentMp()));
writeD((int) Math.round(_player.getCurrentCp()));
writeQ(_player.getSp());
writeQ(_player.getExp());
writeF((float) ExpDataHolder.getExpPercent(_player.getLevel(), _player.getExp()));
}
Я сейчас этим занимаюсь, но вроде как должно быть так, скажем у тебя уровень 10, есть табличка :Ну в моих реалиях реально проще просто послать
и не паритьсяJava:_activeChar.sendUserInfo(UserInfoType.CUR_HPMPCP_EXP_SP);
если что там в итоге уйдет только маленький кусочек UI
З.Ы. не забывай что там еще кроме значений экспы/сп надо изменение процента опыта на текущем уровне отобразить, а как ты это через SU сделаешь?Java:if (containsMask(UserInfoType.CUR_HPMPCP_EXP_SP)) { writeH(38); writeD((int) Math.round(_player.getCurrentHp())); writeD((int) Math.round(_player.getCurrentMp())); writeD((int) Math.round(_player.getCurrentCp())); writeQ(_player.getSp()); writeQ(_player.getExp()); writeF((float) ExpDataHolder.getExpPercent(_player.getLevel(), _player.getExp())); }
// Таблица нижнего придела опыта.
private static final long[] LEVEL_EXP = {
0, 68, 363, 1168, 2884,
6038, 11287, 19423, 31378, 48229,
71201, 101676, 141192, 191452, 254327,
331864, 426284, 539995, 675590, 835854,
1023775, 1242536, 1495531, 1786365, 2118860,
2497059, 2925229, 3407873, 3949727, 4555766,
5231213, 5981539, 6812472, 7729999, 8740372,
9850111, 11066012, 12395149, 13844879, 15422851,
17137002, 18995573, 21007103, 23180442, 25524751,
28049509, 30764519, 33679907, 36806133, 40153995,
45524865, 51262204, 57383682, 63907585, 70852742,
80700339, 91162131, 102265326, 114038008, 126509030,
146307211, 167243291, 189363788, 212716741, 237351413,
271973532, 308441375, 346825235, 387197529, 429632402,
474205751, 532692055, 606319094, 696376867, 804219972,
931275828, 1151275834, 1511275834, 2099275834, 4200000000L
};
да, в интерлюде клиент сам процент считал на уровнеНу в старых хрониках, если не ошибаюсь, включая и интерлюд вроде, таблица опыта в самом клиенте вроде бы была захардкодена и клиент сам, по текущему значению экспы, полученной в UI/SU высчитывал уровень и процент опыта на нем.
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?