Подскажите по пакету StatusUpdate

  • Автор темы Автор темы nesss
  • Дата начала Дата начала

nesss

Единомышленник
Участник
Сообщения
163
Розыгрыши
0
Решения
3
Репутация
-2
Реакции
14
Баллы
130
Хроники
  1. Interlude
Исходники
Присутствуют
Сборка
Собственная
Всем привет, подскажите пожалуйста, вот при обновлении SP, когда я шлю один это параметр, обновления в клиенте не происходит, НО если скажем работает регенерация, и идет регенерация CP, то после отправки того же пакета, но уже с HP MP и CP как максимальных так и текущих, идет обновление полосок все хорошо, и обновляется SP. То есть клиент запоминает полученное SP, но в UI не отображает до того как не придет инфа о CP, пробовал слать уровень, EXP, не помогает, вот CurCP шлю, и именно в последовательности SP и CurCP в одном пакете, тогда SP в UI отлично обновляется, такое ощущение, что у CurCP в клиенте есть какой-то триггер, обнаружив который идет обновление других статов. Или может я что за чем не верно отсылаю, вот этот момент подскажите. Я смотрел в других сборках, там на такое не обращают внимание и шлют сразу UserInfo, так работает, но если можно пакет с 2 позициями отослать вместо большого UserInfo, лучше же StatusUpdate.
 
На счет интерлюда не знаю, но на ХФ отдельно должен быть отослан StatusUpdate на пару CP\HP, отдельно на MP. Ну т.е первый пакет 4 параметра: текущее HP, максимальное HP, текущее СP, максимальное СP, пакет МР 2 параметра: текущее МP, максимальное МP. Разумеется, по мере изменения, но всегда в таком формате.
Вот фрагмент моего кода:
Java:
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;
        }

Остальные параметры отсылаются по мере изменений, но в клиенте HF большая часть из них не используется явно, хотя некоторые работают. Например VCP_CARRINGWEIGHT и VCP_CARRYWEIGHT

UPD: Хотя, после твоих данных переделал вот так)))


Java:
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;
        }
 
Последнее редактирование:
Перепробовал все возможное, если тебе интересно, смотри, 2 параметра идут как триггеры, это CurCP и CurMP, все что перед ними было отправлено обновится в UI, как будь-то там идет запись в список все параметров для обновления, и как только CurCP или CurMP получает, сразу применяет все параметры из списка полученного от сервера применяет и обновляет UI. все что ты отправишь без CurCP или CurMP моментально не обновится в UI но в списке будет, и как только получит клиент CurCP или CurMP может даже другим пакетом, тогда произойдет обновление всего списка.

То есть таким образом можно много где в коде отправлять маленький StatusUpdate, вместо громоздкого UserInfo. Вот к примеру, идет фарм, убиваешь моба, идет перерасчет exp, если уровень меняется, тут понятно UserInfo так как там и статы пересчитываются и т.д. Но если уровень не меняется, а опыт меняется, ты же не будешь после каждого моба слать UserInfo, или как у тебя данный узел реализован?
 
или как у тебя данный узел реализован?
У меня отдельный объект, хранящий слепок базовых параметров игрока, каждый из которых имеет 3 флага: Публичная инфа, приватная инфа и статус апдейт(ну или сразу несколько, например как коэффициент скорости движения)
В коде я вызываю единственный метод player.sendChanges(), после вызова которого, проходит получение актульных значений и сверка их с сохраненными, создавая маску значений. После этого, исходя из посчитанной маски решается какие пакеты и кому разослать. После этого создаются атомарные задачи на определенный тайминг и после короткого ожидания в 1-2 тика, происходит обновление сохраненных статов и рассылка пакетов получателям. Если достаточно обновления S_STATUS_UPDATE, то полетит только он. Если изменилась только приватная инфа, то будет отправка UserInfo, если изменилась публичная инфа, то идет и отправка S_USER_INFO, и бродкаст S_CHAR_INFO. Также там и пати инфа, и олимп пакеты и куча всего другого.
 
У меня отдельный объект, хранящий слепок базовых параметров игрока, каждый из которых имеет 3 флага: Публичная инфа, приватная инфа и статус апдейт(ну или сразу несколько, например как коэффициент скорости движения)
В коде я вызываю единственный метод player.sendChanges(), после вызова которого, проходит получение актульных значений и сверка их с сохраненными, создавая маску значений. После этого, исходя из посчитанной маски решается какие пакеты и кому разослать. После этого создаются атомарные задачи на определенный тайминг и после короткого ожидания в 1-2 тика, происходит обновление сохраненных статов и рассылка пакетов получателям. Если достаточно обновления S_STATUS_UPDATE, то полетит только он. Если изменилась только приватная инфа, то будет отправка UserInfo, если изменилась публичная инфа, то идет и отправка S_USER_INFO, и бродкаст S_CHAR_INFO. Также там и пати инфа, и олимп пакеты и куча всего другого.
А ты говоришь про коэффициент скорости движения, там же есть еще коэф скорости атаки, он же меняется при изменении стата скорости бега и соответственно скорости атаки, эти коэф публичные, они как для персонажа, так и для всех видимых, что-бы правильно анимацию клиенты рассчитывали. Так вот ты как их отсылаешь? пакетом UserInfo, а другим чарам CharInfo?
 
А ты говоришь про коэффициент скорости движения, там же есть еще коэф скорости атаки, он же меняется при изменении стата скорости бега и соответственно скорости атаки, эти коэф публичные, они как для персонажа, так и для всех видимых, что-бы правильно анимацию клиенты рассчитывали. Так вот ты как их отсылаешь? пакетом UserInfo, а другим чарам CharInfo?
Да, я тебе про это и говорю, что публичную инфу ты не разошлешь никак точечно, т.к у тебя нет информации о том, имеет ли конкретный клиент-получатель у тебя ПОЛНУЮ инфу или не имеет. Ты не можешь это гарантировать, из-за сетевых задержек, неконсистентностью данных о текущем игровом регионе игрока, а также еще кучи факторов. Поэтому обновление публичной инфы других чаров - всегда через полный CharInfo, без исключений. Себе инфу ты можешь слать точечно, кроме тех случаев, когда поменялся параметр, который есть только в UserInfo. Тогда без вариантов. В новых клиентах используется похожий механизм компрессии приватных пакетов, когда у тебя формируется полный UserInfo только в первый раз, а дальше обновления приходят порционно, по мере изменений. Там пакет разделен на блоки данных, каждый из которых может обновляться независимо.

У меня просто отправка пакетов инфы кумулятивная и происходит не чаще, чем раз в 100мс. Этого достаточно, чтобы собрать все изменения в рамках какого-то события и не устраивать клиенту шторм из десятка UserInfo

StatusUpdate используется для обновления параметров HP\MP текущей NPC цели, которые она бродкастит всем, кто держит ее в таргете.
 
Ну я 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.
То есть беру по максимуму, что-бы не использовать большие пакеты.
 
Ну я 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.
То есть беру по максимуму, что-бы не использовать большие пакеты.
Ну я обновляю так, как это делает ПТС, судя по тому, что я видел в снифере. Отдельно пакет для пар HP/CP, отдельно пакт для пары MP. Остальное шлю общим пакетом, что изменилось.

Вот параметры:
Java:
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());
}
 
То есть таким образом можно много где в коде отправлять маленький StatusUpdate, вместо громоздкого UserInfo. Вот к примеру, идет фарм, убиваешь моба, идет перерасчет exp, если уровень меняется, тут понятно UserInfo так как там и статы пересчитываются и т.д. Но если уровень не меняется, а опыт меняется, ты же не будешь после каждого моба слать UserInfo, или как у тебя данный узел реализован?
Ну в новых хрониках выбор есть - в такой ситуации можно просто маленький блок с этими данными отправить, а не весь UI. У меня в целом так и работает, но вот насчет того чтобы слать изменения экспы/сп чисто через SU надо будет подумать, если оно конечно будет корректно обновлять в окне стат данные...

Проверил - и вобщем да, походу даже в хомке такая проблема до сих пор имеется. Ну или я что-то не так сделал...
Если послать в SU данные по опыту/сп, то в окне стат у перса это не обновится, тогда как отправка чисто нужного блока UI обновляет сразу же.
 
Последнее редактирование модератором:
Оверпостинг
Ну отправь SP и следом SP, Вот я так формирую,
Java:
            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);
Вот после StatusType.CUR_CP все обновится мгновенно, и SP и все все что ты перед ним отправишь, то есть и LEVEL и EXP. Дело в том, что я же говорю это триггер для клиента. Конечно отправка CP выглядит не логичной в таком случае, причем тут SP к CP, но, если отправлять UserInfo, как это сейчас практически во всех сборках, ты все равно CP в UserInfo будешь передавать, и + кучу инфы которая не актуальна для обновления, а это лишняя нагрузка на сервер, проще вот такой маленький пакет отправить и добиться того-же результата. Вот как я реализовал изменение уровня, еще немного правда не доделал просчет одетых вещей на персонаже к базовым умениям, таких как штраф на ношение вещей грейдом выше чем скилл пассивный:

Java:
    /**
     * Обновляет очки опыта, очки умений и уровень при необходимости.
     *
     * @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();

        }

    }

Не успел добавить, вот еще на что внимание обрати:
Java:
               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()));

                }

В EXP в UserInfo идет параметр long, и на больших уровнях персонажа он реально long, если скажем у тебя 1 уровень и твой exp 0, и ты хочешь сразу 80 уровень, тебе нужно exp передать в клиент для 80го уровня, а это значение long, а пакет StatusUpdate передает значение только INT, то есть все шлют UserInfo, вот сейчас тесты делаю, к вечеру определюсь, но вроде как там нужно разницу передавать изменения EXP.
 
Последнее редактирование модератором:
Ну в моих реалиях реально проще просто послать
Java:
_activeChar.sendUserInfo(UserInfoType.CUR_HPMPCP_EXP_SP);
и не париться

если что там в итоге уйдет только маленький кусочек UI
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()));
        }
З.Ы. не забывай что там еще кроме значений экспы/сп надо изменение процента опыта на текущем уровне отобразить, а как ты это через SU сделаешь?
 
Оверпостинг
Ну у тебя на хрониках выше да, согласен, ты кусок послал и поменял что нужно, но я то Interlude делаю, тут так не пройдет, но вижу где можно очень оптимизировать код, так как в сборках в шаре, там делалось все без учета оптимизации, то есть - работает и хорошо.

Ну в моих реалиях реально проще просто послать
Java:
_activeChar.sendUserInfo(UserInfoType.CUR_HPMPCP_EXP_SP);
и не париться

если что там в итоге уйдет только маленький кусочек UI
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()));
        }
З.Ы. не забывай что там еще кроме значений экспы/сп надо изменение процента опыта на текущем уровне отобразить, а как ты это через SU сделаешь?
Я сейчас этим занимаюсь, но вроде как должно быть так, скажем у тебя уровень 10, есть табличка :
Код:
    // Таблица нижнего придела опыта.
    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

    };
Смотрим, на 10 уровень 48229 это нижний придел уровня, то есть полоска в % в клиенте будет 0%, если ты пошлешь в клиент уровень 10 и 59715, это уже будет 50% полоски, так как ты к начальному 48229 порогу добавил половину разницы от следующего значения нижнего порога 11го уровня. А нижний порог считается для текущего уровня 0.00 процентов, а для уровня меньше на 1, 100%
 
Последнее редактирование модератором:
Ну в старых хрониках, если не ошибаюсь, включая и интерлюд вроде, таблица опыта в самом клиенте вроде бы была захардкодена и клиент сам, по текущему значению экспы, полученной в UI/SU высчитывал уровень и процент опыта на нем.
 
Ну в старых хрониках, если не ошибаюсь, включая и интерлюд вроде, таблица опыта в самом клиенте вроде бы была захардкодена и клиент сам, по текущему значению экспы, полученной в UI/SU высчитывал уровень и процент опыта на нем.
да, в интерлюде клиент сам процент считал на уровне
 
Поработал с EXP, много вариантов перепробовал, не получается обновить в UI через StatusUpdate, может кто знает что именно туда нужно положить? тут же нюанс в том, что в UserInfo передается общий EXP, а он идет как long, а в StatusUpdate можно положить только int.
 
Назад
Сверху