package ru.nts.gameserver.game.model;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ru.nts.core.config.core.ServerConfig;
import ru.nts.core.config.gameplay.OtherConfig;
import ru.nts.core.network.outgoing.*;
import ru.nts.gameserver.game.instancemanager.CursedWeaponsManager;
import ru.nts.gameserver.game.instancemanager.PartyRoomManager;
import ru.nts.gameserver.game.model.entity.duel.Duel;
import ru.nts.gameserver.game.model.enums.EPrivateStoreType;
import ru.nts.gameserver.game.skills.Skill;
import ru.nts.gameserver.game.stats.base.Stats;
import ru.nts.gameserver.system.enums.ClassID;
import ru.nts.gameserver.system.enums.FutureType;
import ru.nts.gameserver.system.interfaces.IGConstant;
import ru.nts.gameserver.system.interfaces.IVars;
import ru.nts.gameserver.system.world.World;
import ru.nts.gameserver.system.world.WorldRegion;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Aristo
* @since 11.07.2024
*/
@Slf4j
public class StatsChangeRecorder {
private final int player_object_id;
private String title = null;
//Пепердолы. Их проверяем в первую очередь, т.к если что-то из этого поменялось, то остальное уже можно не проверять.
private final int[] publicPaperDoll = new int[PcInventory.PLAYER_PUBLIC_INFO_PAPERDOLL_ORDER.length];
private final int[] privatePaperDoll = new int[PcInventory.PLAYER_PRIVATE_INFO_PAPERDOLL_ORDER.length];
private final SavedPublicInfo savedPublicInfo = new SavedPublicInfo();
private final SavedPrivateInfo savedPrivateInfo = new SavedPrivateInfo();
private final SavedStatusUpdate savedStatusUpdate = new SavedStatusUpdate();
private final SavedEtcStatusUpdate savedEtcStatusUpdate = new SavedEtcStatusUpdate();
private final SavedStorageMaxCount savedStorageMaxCount = new SavedStorageMaxCount();
private final AtomicBoolean refreshing = new AtomicBoolean(false);
public StatsChangeRecorder(Player player) {
this.player_object_id = player.getObjectId();
}
/**
* Основной метод автоматизации определения необходимости отсылать изменения информации в клиент.
* ВАЖНО: Сохраняемые тут данные никак не влияют на то, что отправляется в клиент.
* Они выполняют только роль снапшота и позволяют определить факт изменений в определенной категории информации и необходимость ее обновления в клиенте.
* При отправке пакетов, они заполняются актуальными данными.
*/
public void sendChanges() {
if (getPlayer() instanceof Player player && (player.isReal() || !player.isInPowerSaveMode())) { //Ботам в неактивных регионах не обновляем никогда
if (refreshing.compareAndSet(false,true)) {
try {
boolean needTitleRefresh = false;
boolean needStatusRefresh = false;
boolean needEtcStatusRefresh = false;
boolean needStorageRefresh = false;
boolean needPrivateRefresh = false;
boolean needPublicRefresh = false;
//Проверяем наличие отложенных тасков на рассылку инфы. Если они есть, то все связанные проверки можно скипать.
final boolean publicInfoBroadcastAlreadyRequested = player.hasTask(FutureType.PLAYER_BROADCAST_PUBLIC_INFO_TASK);
final boolean privateInfoSendingAlreadyRequested = player.hasTask(FutureType.PLAYER_SEND_PRIVATE_INFO_TASK);
//Если отложенный таск на обновление статусов уже есть, то повторные проверки можно скипать, т.к они уже были отправлены менее 100мс назад.
final boolean publicInfoRefreshAlreadyRequested = player.hasTask(FutureType.PLAYER_REFRESH_PUBLIC_STATS_TASK);
final boolean privateInfoRefreshAlreadyRequested = player.isFake() || player.hasTask(FutureType.PLAYER_REFRESH_PRIVATE_STATS_TASK); //Ботам не обновляем никогда
final boolean statusRefreshAlreadyRequested = player.hasTask(FutureType.PLAYER_REFRESH_STATUS_STATS_TASK);
final boolean etcStatusRefreshAlreadyRequested = player.isFake() || player.hasTask(FutureType.PLAYER_REFRESH_ETC_STATUS_STATS_TASK); //Ботам не обновляем никогда
final boolean titleRefreshAlreadyRequested = player.hasTask(FutureType.PLAYER_REFRESH_TITLE_STATS_TASK);
final boolean storageRefreshAlreadyRequested = player.isFake() || player.hasTask(FutureType.PLAYER_REFRESH_STORAGE_STATS_TASK); //Ботам не обновляем никогда
//Фактически, рассылка тут не производится, а только запрашивается.
boolean publicInfoChanged = !publicInfoBroadcastAlreadyRequested && sendPublicInfoChanges();
//Если ничего не поменялось в публичной инфе и нет отложенного таска, то пробуем забродкастить изменения статуса.
if (!publicInfoChanged && !publicInfoBroadcastAlreadyRequested) {
if (!statusRefreshAlreadyRequested && broadcastPublicStatusUpdate()) {
needStatusRefresh = true;
}
} else {
needPublicRefresh = true;
needStatusRefresh = true;
}
//Фактически, рассылка тут не производится, а только запрашивается.
boolean privateInfoChanged = !privateInfoSendingAlreadyRequested && sendPrivateInfoChanges(publicInfoChanged);
//Если ничего не поменялось в приватной инфе и нет отложенного таска, то пробуем отослать изменения статуса.
if (!privateInfoChanged && !privateInfoSendingAlreadyRequested) {
if (!statusRefreshAlreadyRequested && sendPrivateStatusUpdate()) {
needStatusRefresh = true;
}
} else {
needPrivateRefresh = true;
needStatusRefresh = true;
}
//Проверяем наличие существующих запросов на обновление статов. Если они есть, то все связанные проверки можно скипать.
if (!statusRefreshAlreadyRequested && broadcastPartyStatusUpdate()) {
needStatusRefresh = true;
}
if (!statusRefreshAlreadyRequested && broadcastOlympiadStatusUpdate()) {
needStatusRefresh = true;
}
if (!statusRefreshAlreadyRequested && broadcastDuelStatusUpdate()) {
needStatusRefresh = true;
}
if (!etcStatusRefreshAlreadyRequested && sendEtcStatus()) {
needEtcStatusRefresh = true;
}
if (!storageRefreshAlreadyRequested && sendStorageMaxCountChanges()) {
needStorageRefresh = true;
}
if (!titleRefreshAlreadyRequested && sendTitleChanges()) {
needTitleRefresh = true;
}
//Запускаем таски на обновление статов по итогам обнаружения изменений в сохраненных данных.
if (needPublicRefresh) {
player.addTaskForce(FutureType.PLAYER_REFRESH_PUBLIC_STATS_TASK, this::refreshPublicSaves, ServerConfig.PLAYER_PACKET_CACHE_LIFE_TIME);
}
if (needPrivateRefresh) {
player.addTaskForce(FutureType.PLAYER_REFRESH_PRIVATE_STATS_TASK, this::refreshPrivateSaves, ServerConfig.PLAYER_PACKET_CACHE_LIFE_TIME);
}
if (needStatusRefresh) {
player.addTaskForce(FutureType.PLAYER_REFRESH_STATUS_STATS_TASK, this::refreshStatusSaves, ServerConfig.PLAYER_PACKET_CACHE_LIFE_TIME);
}
if (needEtcStatusRefresh) {
player.addTaskForce(FutureType.PLAYER_REFRESH_ETC_STATUS_STATS_TASK, this::refreshEtcStatusSaves, ServerConfig.PLAYER_PACKET_CACHE_LIFE_TIME);
}
if (needTitleRefresh) {
player.addTaskForce(FutureType.PLAYER_REFRESH_TITLE_STATS_TASK, this::refreshTitleSaves, ServerConfig.PLAYER_PACKET_CACHE_LIFE_TIME);
}
if (needStorageRefresh) {
player.addTaskForce(FutureType.PLAYER_REFRESH_STORAGE_STATS_TASK, this::refreshStorageSaves, ServerConfig.PLAYER_PACKET_CACHE_LIFE_TIME);
}
} finally {
refreshing.compareAndSet(true,false);
}
}
}
}
private Player getPlayer() {
return World.getInstance().getPlayerByObjectID(player_object_id);
}
private void refreshTitleSaves() {
if (getPlayer() instanceof Player p && p.getInventory() instanceof Inventory inv) {
title = p.getTitle();
}
}
private void refreshStatusSaves() {
if (getPlayer() instanceof Player p && p.getInventory() instanceof Inventory inv) {
savedStatusUpdate.fill(p);
}
}
private void refreshEtcStatusSaves() {
if (getPlayer() instanceof Player p) {
savedEtcStatusUpdate.fill(p);
}
}
private void refreshStorageSaves() {
if (getPlayer() instanceof Player p && p.getInventory() instanceof Inventory inv) {
savedStorageMaxCount.fill(p);
}
}
private void refreshPublicSaves() {
if (getPlayer() instanceof Player p && p.getInventory() instanceof Inventory inv) {
for (int i = 0; i < PcInventory.PLAYER_PUBLIC_INFO_PAPERDOLL_ORDER.length; i++) {
publicPaperDoll[i] = inv.getPaperdollObjectId(PcInventory.PLAYER_PUBLIC_INFO_PAPERDOLL_ORDER[i]);
}
savedPublicInfo.fill(p);
}
}
private void refreshPrivateSaves() {
if (getPlayer() instanceof Player p && p.getInventory() instanceof Inventory inv) {
for (int i = 0; i < PcInventory.PLAYER_PRIVATE_INFO_PAPERDOLL_ORDER.length; i++) {
privatePaperDoll[i] = inv.getPaperdollObjectId(PcInventory.PLAYER_PRIVATE_INFO_PAPERDOLL_ORDER[i]);
}
savedPrivateInfo.fill(p);
}
}
private boolean sendPrivateStatusUpdate() {
if (getPlayer() instanceof Player p && p.isReal()) {
S_STATUS_UPDATE statusUpdate = savedStatusUpdate.makePrivateStatusUpdate(p);
if (statusUpdate != null && statusUpdate.hasAttributes()) {
p.sendPacket(statusUpdate);
return true;
}
}
return false;
}
private boolean broadcastPublicStatusUpdate() {
if (getPlayer() instanceof Player p) {
S_STATUS_UPDATE statusUpdate = savedStatusUpdate.makePublicStatusUpdate(p);
if (statusUpdate != null && statusUpdate.hasAttributes()) {
p.broadcastPacket(statusUpdate);
return true;
}
}
return false;
}
private boolean broadcastPartyStatusUpdate() {
if (getPlayer() instanceof Player p && (p.isReal() || p instanceof FakePlayer f && f.isSlave())) {
Party party = p.getParty();
if (party != null && party.getPartyRealPlayersCount() > 1) {
S_PARTY_SMALL_WINDOW_UPDATE partyUpdate = savedStatusUpdate.makePartyStatusUpdate(p);
if (partyUpdate != null) {
party.broadcastToPartyMembersExclude(p, partyUpdate);
return true;
}
}
}
return false;
}
private boolean broadcastOlympiadStatusUpdate() {
if (getPlayer() instanceof Player p && (p.isReal() || p instanceof FakePlayer f && f.isSlave())) {
if (p.isInOlympiadMode() && p.isOlympiadStart()) {
S_EX_OLYMPIAD_USER_INFO olympiadUpdate = savedStatusUpdate.makeOlympiadStatusUpdate(p, p.getOlympiadSide());
if (olympiadUpdate != null) {
p.broadcastPacket(olympiadUpdate);
return true;
}
}
}
return false;
}
private boolean broadcastDuelStatusUpdate() {
if (getPlayer() instanceof Player p && (p.isReal() || p instanceof FakePlayer f && f.isSlave())) {
if (p.isInDuel() && (p.getDuelState() == IGConstant.DUELSTATE_DUELLING || p.getDuelState() == IGConstant.DUELSTATE_DEAD)) {
S_EX_DUEL_UPDATE_USER_INFO duelUpdate = savedStatusUpdate.makeDuelStatusUpdate(p);
if (duelUpdate != null && p.getDuel() instanceof Duel duel) {
if (p.getDuelSide() == 1) {
duel.broadcastToTeam2(duelUpdate);
} else if (p.getDuelSide() == 2) {
duel.broadcastToTeam1(duelUpdate);
}
return true;
}
}
}
return false;
}
private boolean sendTitleChanges() {
if (getPlayer() instanceof Player p) {
if (title == null && p.getTitle() != null || title != null && !title.equals(p.getTitle())) {
p.broadcastPacketToOthers(new S_NICKNAME_CHANGED(p));
return true;
}
}
return false;
}
private boolean sendPrivateInfoChanges(boolean publicInfoChanged) {
if (getPlayer() instanceof Player p && p.isReal()) {
if (publicInfoChanged || privatePapperDollChanged() || privateInfoChanged()) {
p.requestSendPlayerPrivateInfo();
return true;
}
}
return false;
}
private boolean sendPublicInfoChanges() {
if (getPlayer() instanceof Player p) {
if (publicPapperDollChanged() || publicInfoChanged()) {
p.requestBroadcastPlayerInfo();
return true;
}
}
return false;
}
private boolean sendEtcStatus() {
if (getPlayer() instanceof Player p) {
S_ETC_STATUS_UPDATE etcStatusUpdate = savedEtcStatusUpdate.makeEtcStatusUpdate(p);
if (etcStatusUpdate != null) {
p.sendPacket(etcStatusUpdate);
return true;
}
}
return false;
}
private boolean sendStorageMaxCountChanges() {
if (getPlayer() instanceof Player p) {
S_EX_STORAGE_MAX_COUNT storageMaxCount = savedStorageMaxCount.makeStorageMaxCountUpdate(p);
if (storageMaxCount != null) {
p.sendPacket(storageMaxCount);
return true;
}
}
return false;
}
private boolean privatePapperDollChanged() {
if (getPlayer() instanceof Player p && p.isReal()) {
boolean changed = false;
for (int i = 0; i < PcInventory.PLAYER_PRIVATE_INFO_PAPERDOLL_ORDER.length; i++) {
if (p.getInventory().getPaperdollObjectId(PcInventory.PLAYER_PRIVATE_INFO_PAPERDOLL_ORDER[i]) != privatePaperDoll[i]) {
changed = true;
break;
}
}
return changed;
}
return false;
}
private boolean publicPapperDollChanged() {
if (getPlayer() instanceof Player p) {
boolean changed = false;
for (int i = 0; i < PcInventory.PLAYER_PUBLIC_INFO_PAPERDOLL_ORDER.length; i++) {
if (p.getInventory().getPaperdollObjectId(PcInventory.PLAYER_PUBLIC_INFO_PAPERDOLL_ORDER[i]) != publicPaperDoll[i]) {
changed = true;
break;
}
}
return changed;
}
return false;
}
private boolean publicInfoChanged() {
if (getPlayer() instanceof Player p) {
return !savedPublicInfo.equals(new SavedPublicInfo().fill(p));
}
return false;
}
private boolean privateInfoChanged() {
if (getPlayer() instanceof Player p && p.isReal()) {
return !savedPrivateInfo.equals(new SavedPrivateInfo().fill(p));
}
return false;
}
//Общие параметры. Могут быть как публичными, так и приватными.
//Если тут что-то поменялось, то нужно обновлять и приватную инфу.
@NoArgsConstructor
private static class SavedPublicInfo {
private WorldRegion region;
private String name;
private String title;
private int race;
private int sex;
private int expand_deco_slot;
private int cloak;
private int recomendation_points_received;
private int vehicle_game_object_id;
private double movement_speed_mul;
private double physical_attack_speed_mul;
private double collision_radius;
private double collision_height;
private int hair_style;
private int hair_color;
private int face;
private int abnormal_visual_effects;
private int abnormal_visual_effects_special;
private int clan_id;
private int clan_crest_id;
private int large_clan_crest_id;
private int ally_id;
private int ally_crest_id;
private int class_id;
private int sitting;
private int running;
private int combat;
private int like_dead;
private int invisible;
private EPrivateStoreType private_store_type;
private int weapon_enchant_effect;
private int team;
private int noble;
private int hero;
private int fishing;
private int mount_type;
private int pledge_class;
private int pledge_type;
private int clan_rep_score;
private int cursed_weapon_level;
private int mount_id;
private int name_color;
private int title_color;
private int transformation;
private int agathion;
private int[] cubics_id;
private int party_room_active;
private int floating;
private int battlefield_penalty;
private int mounted;
public synchronized SavedPublicInfo fill(Player player) {
if (player instanceof Player p) {
try {
region = p.getWorldRegion();
sitting = p.isSitting() ? 0 : 1;
combat = p.isInCombat() ? 1 : 0;
like_dead = p.isDead() ? 1 : 0;
invisible = p.isInvisible() ? 1 : 0;
recomendation_points_received = p.getRecSystem().getRecommendsHave();
if (clan_id > 0 && p.getClanId() != 0) {
clan_rep_score = p.getClan().getReputationScore();
} else {
clan_rep_score = 0;
}
if (p.isCursedWeaponEquipped()) {
name = p.getTransformationName();
clan_crest_id = 0;
ally_crest_id = 0;
large_clan_crest_id = 0;
cursed_weapon_level = CursedWeaponsManager.getInstance().getLevel(p.getCursedWeaponEquippedId());
} else {
name = p.getName();
clan_crest_id = p.getClanCrestId();
ally_crest_id = p.getAllyCrestId();
large_clan_crest_id = p.getClanCrestLargeId();
cursed_weapon_level = 0;
}
if (p.getMountEngine().isMounted()) {
weapon_enchant_effect = 0;
if (p.getTransformation() != 0) {
mount_id = 0;
mount_type = 0;
} else {
mount_id = p.getMountEngine().getMountNpcId() + 1000000;
mount_type = p.getMountEngine().getYongmaType();
}
} else {
weapon_enchant_effect = p.getEnchantEffect();
mount_id = 0;
mount_type = 0;
}
running = p.isRunning() ? 0x01 : 0x00; //changes the Speed display on Status Window
movement_speed_mul = p.getMovementSpeedMultiplier();
vehicle_game_object_id = p.isInBoat() ? p.getVehicle().getObjectId() : 0x00;
race = p.getRace().ordinal();
sex = p.getSex();
physical_attack_speed_mul = p.getStat().getAttackSpeedMultiplier();
collision_radius = p.getCurrentColRadius();
collision_height = p.getCurrentColHeight();
hair_style = p.getHairStyle();
hair_color = p.getHairColor();
face = p.getFace();
title = p.getTitle();
clan_id = p.getClanId();
ally_id = p.getAllyId();
private_store_type = p.getPrivateStore().getType();
cubics_id = p.getCubicsID();
abnormal_visual_effects = p.getAbnormalVisualEffects();
floating = p.isSwimming() ? 1 : (p.isFlying() ? 2 : 0);
abnormal_visual_effects_special = p.getAbnormalVisualEffectSpecial();
class_id = p.getClassId().getID();
team = p.getTeam(); //team circle around feet 1= Blue, 2 = red
noble = p.isNoble() || p.isGM() && OtherConfig.GM_HERO_AURA ? 1 : 0; //0x01: symbol on char menu ctrl+I
hero = p.isHero() || p.isGM() && OtherConfig.GM_HERO_AURA ? 1 : 0; //0x01: Hero Aura and symbol
fishing = p.isFishing() ? 1 : 0; // Fishing Mode
name_color = p.getNameColor();
pledge_class = p.getSocialClass();
pledge_type = p.getPledgeType();
title_color = p.getTitleColor();
transformation = p.getTransformation();
agathion = p.getAgathionId();
party_room_active = PartyRoomManager.getInstance().isLeader(p) ? 0x01 : 0x00;
expand_deco_slot = p.getInventory().getAllowedTalismans();
cloak = p.isStatActive(Stats.CLOAK) ? 1 : 0;
battlefield_penalty = p.getEffectBySkillId(Skill.SKILL_BATTLEFIELD_PENALTY) != null ? 0 : 1;
mounted = p.getMountEngine().isMounted() ? p.getMountEngine().getMountNpcId() : 0;
} catch (Exception e) {
log.error("Cannot fill public info saves for {}", p.toIDString(), e);
}
}
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof SavedPublicInfo that) {
return region == that.region && mounted == that.mounted && race == that.race && sex == that.sex && expand_deco_slot == that.expand_deco_slot && cloak == that.cloak && recomendation_points_received == that.recomendation_points_received && vehicle_game_object_id == that.vehicle_game_object_id && Double.compare(movement_speed_mul, that.movement_speed_mul) == 0 && Double.compare(physical_attack_speed_mul, that.physical_attack_speed_mul) == 0 && Double.compare(collision_radius, that.collision_radius) == 0 && Double.compare(collision_height, that.collision_height) == 0 && hair_style == that.hair_style && hair_color == that.hair_color && face == that.face && abnormal_visual_effects == that.abnormal_visual_effects && abnormal_visual_effects_special == that.abnormal_visual_effects_special && clan_id == that.clan_id && clan_crest_id == that.clan_crest_id && large_clan_crest_id == that.large_clan_crest_id && ally_id == that.ally_id && ally_crest_id == that.ally_crest_id && class_id == that.class_id && sitting == that.sitting && running == that.running && combat == that.combat && like_dead == that.like_dead && invisible == that.invisible && weapon_enchant_effect == that.weapon_enchant_effect && team == that.team && noble == that.noble && hero == that.hero && fishing == that.fishing && mount_type == that.mount_type && pledge_class == that.pledge_class && pledge_type == that.pledge_type && clan_rep_score == that.clan_rep_score && cursed_weapon_level == that.cursed_weapon_level && mount_id == that.mount_id && name_color == that.name_color && title_color == that.title_color && transformation == that.transformation && agathion == that.agathion && party_room_active == that.party_room_active && floating == that.floating && battlefield_penalty == that.battlefield_penalty && Objects.equals(name, that.name) && Objects.equals(title, that.title) && private_store_type == that.private_store_type && Objects.deepEquals(cubics_id, that.cubics_id);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(region, mounted, name, title, race, sex, expand_deco_slot, cloak, recomendation_points_received, vehicle_game_object_id, movement_speed_mul, physical_attack_speed_mul, collision_radius, collision_height, hair_style, hair_color, face, abnormal_visual_effects, abnormal_visual_effects_special, clan_id, clan_crest_id, large_clan_crest_id, ally_id, ally_crest_id, class_id, sitting, running, combat, like_dead, invisible, private_store_type, weapon_enchant_effect, team, noble, hero, fishing, mount_type, pledge_class, pledge_type, clan_rep_score, cursed_weapon_level, mount_id, name_color, title_color, transformation, agathion, Arrays.hashCode(cubics_id), party_room_active, floating, battlefield_penalty);
}
}
//Только приватные параметры. Другие персонажи о них не знают.
@NoArgsConstructor
private static class SavedPrivateInfo {
private int level;
private long experience_points;
private int str_value;
private int con_value;
private int dex_value;
private int int_value;
private int wit_value;
private int men_value;
private int current_load;
private int maximum_load;
private int physical_attack;
private int physical_attack_speed;
private int physical_defence;
private int avoid;
private int hit;
private int critical_rate;
private int magical_attack;
private int magical_attack_speed;
private int magical_defence;
private int weapon_equipped_flag;
private int recommendation_points_remaining;
private int recommendation_points_received;
private int skill_points;
private int pledge_power;
private int inventory_limit;
private int can_crystalize;
private int pk_points;
private int pvp_points;
private int defence_attribute_fire;
private int defence_attribute_water;
private int defence_attribute_wind;
private int defence_attribute_earth;
private int defence_attribute_holy;
private int defence_attribute_unholy;
private int fame;
private int vitality;
private int territory_id;
private int disguised;
private int attack_attribute_type;
private int attack_attribute_value;
public synchronized SavedPrivateInfo fill(Player player) {
if (player instanceof Player p && p.isReal()) {
try {
level = p.getLevel();
experience_points = p.getExp();
str_value = p.getStat().getSTR();
dex_value = p.getStat().getDEX();
con_value = p.getStat().getCON();
int_value = p.getStat().getINT();
wit_value = p.getStat().getWIT();
men_value = p.getStat().getMEN();
current_load = p.getCurrentLoad();
maximum_load = p.getMaxLoad();
physical_attack = (int) p.getStat().getPAtk();
physical_attack_speed = (int) p.getStat().getPAtkSpd();
physical_defence = (int) (p.getStat().getPDef());
avoid = (int) p.getStat().getEvasionRate();
hit = (int) p.getStat().getAccuracy();
critical_rate = (int) p.getStat().getCriticalHit();
magical_attack = (int) (p.getStat().getMAtk(null));
magical_attack_speed = (int) (p.getStat().getMAtkSpd());
magical_defence = (int) (p.getStat().getMDef(null));
weapon_equipped_flag = p.getActiveWeaponInstance() != null ? 0x28 : 0x14;
skill_points = (int)p.getSp();
can_crystalize = p.calcStat(Stats.CRYSTALLIZE, 0, null, null) > 0 ? 1 : 0;
pk_points = p.getPkKills();
pvp_points = p.getPvpKills();
pledge_power = p.getClanPrivileges();
recommendation_points_remaining = p.getRecSystem().getRecommendsLeft(); //c2 recommendations remaining
recommendation_points_received = p.getRecSystem().getRecommendsHave(); //c2 recommendations received
inventory_limit = p.getInventoryLimit();
attack_attribute_type = p.getStat().getAttackElement()[0];
attack_attribute_value = p.getStat().getAttackElement()[1];
defence_attribute_fire = (int) (p.getStat().getDefenceFire());
defence_attribute_water = (int) (p.getStat().getDefenceWater());
defence_attribute_wind = (int) (p.getStat().getDefenceWind());
defence_attribute_earth = (int) (p.getStat().getDefenceEarth());
defence_attribute_holy = (int) (p.getStat().getDefenceHoly());
defence_attribute_unholy = (int) (p.getStat().getDefenceDark());
fame = p.getFame();
vitality = p.getVitality().getPoints();
disguised = p.getVarInt(IVars.USER_DISGUISED);
territory_id = p.getDominionID();
} catch (Exception e) {
log.error("Can't fill private info saves for {}", p.toIDString(), e);
}
}
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof SavedPrivateInfo that) {
return level == that.level && experience_points == that.experience_points && str_value == that.str_value && con_value == that.con_value && dex_value == that.dex_value && int_value == that.int_value && wit_value == that.wit_value && men_value == that.men_value && current_load == that.current_load && maximum_load == that.maximum_load && physical_attack == that.physical_attack && physical_attack_speed == that.physical_attack_speed && physical_defence == that.physical_defence && avoid == that.avoid && hit == that.hit && critical_rate == that.critical_rate && magical_attack == that.magical_attack && magical_attack_speed == that.magical_attack_speed && magical_defence == that.magical_defence && weapon_equipped_flag == that.weapon_equipped_flag && recommendation_points_remaining == that.recommendation_points_remaining && recommendation_points_received == that.recommendation_points_received && skill_points == that.skill_points && pledge_power == that.pledge_power && inventory_limit == that.inventory_limit && can_crystalize == that.can_crystalize && pk_points == that.pk_points && pvp_points == that.pvp_points && defence_attribute_fire == that.defence_attribute_fire && defence_attribute_water == that.defence_attribute_water && defence_attribute_wind == that.defence_attribute_wind && defence_attribute_earth == that.defence_attribute_earth && defence_attribute_holy == that.defence_attribute_holy && defence_attribute_unholy == that.defence_attribute_unholy && fame == that.fame && vitality == that.vitality && territory_id == that.territory_id && disguised == that.disguised && attack_attribute_type == that.attack_attribute_type && attack_attribute_value == that.attack_attribute_value;
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(level, experience_points, str_value, con_value, dex_value, int_value, wit_value, men_value, current_load, maximum_load, physical_attack, physical_attack_speed, physical_defence, avoid, hit, critical_rate, magical_attack, magical_attack_speed, magical_defence, weapon_equipped_flag, recommendation_points_remaining, recommendation_points_received, skill_points, pledge_power, inventory_limit, can_crystalize, pk_points, pvp_points, defence_attribute_fire, defence_attribute_water, defence_attribute_wind, defence_attribute_earth, defence_attribute_holy, defence_attribute_unholy, fame, vitality, territory_id, disguised, attack_attribute_type, attack_attribute_value);
}
}
@NoArgsConstructor
private static class SavedStatusUpdate {
//Явно изменяют значения в клиенте.
private int current_cp;
private int maximum_cp;
private int current_hp;
private int maximum_hp;
private int current_mp;
private int maximum_mp;
private int karma;
private int pvp_flag;
//Не изменяют значения или изменяют не явно.
private int level;
private long experience_points;
private int str_value;
private int con_value;
private int dex_value;
private int int_value;
private int wit_value;
private int men_value;
private int current_load;
private int maximum_load;
private int physical_attack;
private int physical_attack_speed;
private int physical_defence;
private int avoid;
private int hit;
private int critical_rate;
private int magical_attack;
private int magical_attack_speed;
private int magical_defence;
private ClassID class_id;
private String name;
public synchronized void fill(Player player) {
if (player instanceof Player p) {
try {
maximum_cp = (int) (p.getStat().getMaxCp());
current_cp = (int) (p.getCurrentCp());
current_hp = (int) p.getCurrentHp();
maximum_hp = (int) p.getStat().getMaxHp();
current_mp = (int) p.getCurrentMp();
maximum_mp = (int) p.getStat().getMaxMp();
karma = p.getKarma();
pvp_flag = p.getPvpFlag();
level = p.getLevel();
experience_points = p.getExp();
str_value = p.getStat().getSTR();
dex_value = p.getStat().getDEX();
con_value = p.getStat().getCON();
int_value = p.getStat().getINT();
wit_value = p.getStat().getWIT();
men_value = p.getStat().getMEN();
current_load = p.getCurrentLoad();
maximum_load = p.getMaxLoad();
physical_attack = (int) p.getStat().getPAtk();
physical_attack_speed = (int) p.getStat().getPAtkSpd();
physical_defence = (int) (p.getStat().getPDef());
avoid = (int) p.getStat().getEvasionRate();
hit = (int) p.getStat().getAccuracy();
critical_rate = (int) p.getStat().getCriticalHit();
magical_attack = (int) (p.getStat().getMAtk(null));
magical_attack_speed = (int) (p.getStat().getMAtkSpd());
magical_defence = (int) (p.getStat().getMDef(null));
class_id = p.getClassId();
name = p.getName();
} catch (Exception e) {
log.error("Can't fill saved status update for {}", p.toIDString(), e);
}
}
}
public synchronized S_STATUS_UPDATE makePublicStatusUpdate(Player player) {
if (player instanceof Player p) {
try {
S_STATUS_UPDATE sa = new S_STATUS_UPDATE(p.getObjectId());
if (level != p.getLevel()) {
sa.addAttribute(S_STATUS_UPDATE.LEVEL, p.getLevel());
}
if (karma != p.getKarma()) {
sa.addAttribute(S_STATUS_UPDATE.KARMA, p.getKarma());
}
if (pvp_flag != p.getPvpFlag()) {
sa.addAttribute(S_STATUS_UPDATE.PVP_FLAG, p.getPvpFlag());
}
return sa;
} catch (Exception e) {
log.error("Can't make public status update for {}", p.toIDString(), e);
}
}
return null;
}
public synchronized S_STATUS_UPDATE makePrivateStatusUpdate(Player player) {
if (player instanceof Player p) {
try {
S_STATUS_UPDATE sa = new S_STATUS_UPDATE(p.getObjectId());
//В клиенте HF эти данные обрабатываются явно.
if (current_cp != p.getCurrentCp()) {
sa.addAttribute(S_STATUS_UPDATE.CUR_CP, p.getCurrentCp());
}
if (maximum_cp != p.getStat().getMaxCp()) {
sa.addAttribute(S_STATUS_UPDATE.MAX_CP, p.getStat().getMaxCp());
}
if (current_hp != (int) (p.getCurrentHp())) {
sa.addAttribute(S_STATUS_UPDATE.CUR_HP, p.getCurrentHp());
}
if (maximum_hp != p.getStat().getMaxHp()) {
sa.addAttribute(S_STATUS_UPDATE.MAX_HP, p.getStat().getMaxHp());
}
if (current_mp != (int) (p.getCurrentMp())) {
sa.addAttribute(S_STATUS_UPDATE.CUR_MP, p.getCurrentMp());
}
if (maximum_mp != p.getStat().getMaxMp()) {
sa.addAttribute(S_STATUS_UPDATE.MAX_MP, p.getStat().getMaxMp());
}
if (karma != p.getKarma()) {
sa.addAttribute(S_STATUS_UPDATE.KARMA, p.getKarma());
}
if (pvp_flag != p.getPvpFlag()) {
sa.addAttribute(S_STATUS_UPDATE.PVP_FLAG, p.getPvpFlag());
}
//В клиенте HF эти данные не обрабатываются или обрабатываются не явно.
if (level != p.getLevel()) {
sa.addAttribute(S_STATUS_UPDATE.LEVEL, p.getLevel());
}
if (experience_points != p.getExp()) {
sa.addAttribute(S_STATUS_UPDATE.EXP, p.getExp());
}
if (str_value != p.getStat().getSTR()) {
sa.addAttribute(S_STATUS_UPDATE.STR, p.getStat().getSTR());
}
if (dex_value != p.getStat().getDEX()) {
sa.addAttribute(S_STATUS_UPDATE.DEX, p.getStat().getDEX());
}
if (con_value != p.getStat().getCON()) {
sa.addAttribute(S_STATUS_UPDATE.CON, p.getStat().getCON());
}
if (int_value != p.getStat().getINT()) {
sa.addAttribute(S_STATUS_UPDATE.INT, p.getStat().getINT());
}
if (wit_value != p.getStat().getWIT()) {
sa.addAttribute(S_STATUS_UPDATE.WIT, p.getStat().getWIT());
}
if (men_value != p.getStat().getMEN()) {
sa.addAttribute(S_STATUS_UPDATE.MEN, p.getStat().getMEN());
}
if (current_load != p.getCurrentLoad()) {
sa.addAttribute(S_STATUS_UPDATE.CUR_LOAD, p.getCurrentLoad());
}
if (maximum_load != p.getMaxLoad()) {
sa.addAttribute(S_STATUS_UPDATE.MAX_LOAD, p.getMaxLoad());
}
if (physical_attack != p.getStat().getPAtk()) {
sa.addAttribute(S_STATUS_UPDATE.P_ATK, p.getStat().getPAtk());
}
if (physical_attack_speed != p.getStat().getPAtkSpd()) {
sa.addAttribute(S_STATUS_UPDATE.ATK_SPD, p.getStat().getPAtkSpd());
}
if (physical_defence != p.getStat().getPDef()) {
sa.addAttribute(S_STATUS_UPDATE.P_DEF, p.getStat().getPDef());
}
if (avoid != p.getStat().getEvasionRate()) {
sa.addAttribute(S_STATUS_UPDATE.EVASION, p.getStat().getAccuracy());
}
if (hit != p.getStat().getAccuracy()) {
sa.addAttribute(S_STATUS_UPDATE.ACCURACY, p.getStat().getAccuracy());
}
if (critical_rate != p.getStat().getCriticalHit()) {
sa.addAttribute(S_STATUS_UPDATE.CRITICAL, p.getStat().getCriticalHit());
}
if (magical_attack != p.getStat().getMAtk(null)) {
sa.addAttribute(S_STATUS_UPDATE.M_ATK, p.getStat().getMAtk(null));
}
if (magical_attack_speed != p.getStat().getMAtkSpd()) {
sa.addAttribute(S_STATUS_UPDATE.CAST_SPD, p.getStat().getMAtkSpd());
}
if (magical_defence != p.getStat().getMDef(null)) {
sa.addAttribute(S_STATUS_UPDATE.M_DEF, p.getStat().getMDef(null));
}
return sa;
} catch (Exception e) {
log.error("Can't make private status update for {}", p.toIDString(), e);
}
}
return null;
}
public synchronized S_PARTY_SMALL_WINDOW_UPDATE makePartyStatusUpdate(Player player) {
if (player instanceof Player p) {
try {
boolean update = false;
if (level != p.getLevel()) {
update = true;
}
if (!update && current_hp != (int) (p.getCurrentHp())) {
update = true;
}
if (!update && maximum_hp != p.getStat().getMaxHp()) {
update = true;
}
if (!update && current_mp != (int) (p.getCurrentMp())) {
update = true;
}
if (!update && maximum_mp != p.getStat().getMaxMp()) {
update = true;
}
if (!update && current_cp != p.getCurrentCp()) {
update = true;
}
if (!update && maximum_cp != p.getStat().getMaxCp()) {
update = true;
}
if (!update && class_id != p.getClassId()) {
update = true;
}
if (!update && name != p.getName()) {
update = true;
}
if (update) {
return new S_PARTY_SMALL_WINDOW_UPDATE(p);
}
} catch (Exception e) {
log.error("Can't make party status update for {}", p.toIDString(), e);
}
}
return null;
}
public synchronized S_EX_OLYMPIAD_USER_INFO makeOlympiadStatusUpdate(Player player, int side) {
if (player instanceof Player p) {
try {
boolean update = false;
if (level != p.getLevel()) {
update = true;
}
if (!update && current_hp != (int) (p.getCurrentHp())) {
update = true;
}
if (!update && maximum_hp != p.getStat().getMaxHp()) {
update = true;
}
if (!update && current_cp != p.getCurrentCp()) {
update = true;
}
if (!update && maximum_cp != p.getStat().getMaxCp()) {
update = true;
}
if (!update && class_id != p.getClassId()) {
update = true;
}
if (!update && name != p.getName()) {
update = true;
}
if (update) {
return new S_EX_OLYMPIAD_USER_INFO(p, side);
}
} catch (Exception e) {
log.error("Can't make olympiad status update for {}", p.toIDString(), e);
}
}
return null;
}
public synchronized S_EX_DUEL_UPDATE_USER_INFO makeDuelStatusUpdate(Player player) {
if (player instanceof Player p) {
try {
boolean update = false;
if (level != p.getLevel()) {
update = true;
}
if (!update && current_hp != (int) (p.getCurrentHp())) {
update = true;
}
if (!update && maximum_hp != p.getStat().getMaxHp()) {
update = true;
}
if (!update && current_mp != (int) (p.getCurrentMp())) {
update = true;
}
if (!update && maximum_mp != p.getStat().getMaxMp()) {
update = true;
}
if (!update && current_cp != p.getCurrentCp()) {
update = true;
}
if (!update && maximum_cp != p.getStat().getMaxCp()) {
update = true;
}
if (!update && class_id != p.getClassId()) {
update = true;
}
if (!update && name != p.getName()) {
update = true;
}
if (update) {
return new S_EX_DUEL_UPDATE_USER_INFO(p);
}
} catch (Exception e) {
log.error("Can't make duel status update for {}", p.toIDString(), e);
}
}
return null;
}
}
@NoArgsConstructor
private static class SavedEtcStatusUpdate {
private int increasedForce;
private int weightPenalty;
private int messageRefusal;
private int dangerArea;
private int weaponPenalty;
private int armorPenalty;
private int charmOfCourage;
private int deathPenaltyLevel;
private int consumedSouls;
public synchronized void fill(Player player) {
if (player instanceof Player p) {
try {
increasedForce = p.getEnergySaved();
weightPenalty = p.getWeightPenalty();
messageRefusal = p.getMessageRefusal() || p.getNoChannel() != 0 ? 1 : 0;
dangerArea = p.isInAreaDanger() ? 1 : 0;
weaponPenalty = p.getWeaponExpertisePenalty();
armorPenalty = p.getArmourExpertisePenalty();
charmOfCourage = p.isCharmOfCourage() ? 1 : 0;
deathPenaltyLevel = p.getDeathPenalty() != null ? p.getDeathPenalty().getLevel() : 0;
consumedSouls = p.getSoulSaved();
} catch (Exception e) {
log.error("Can't fill etc status saves for {}", p.toIDString(), e);
}
}
}
public synchronized S_ETC_STATUS_UPDATE makeEtcStatusUpdate(Player player) {
if (player instanceof Player p) {
try {
boolean update = false;
if (consumedSouls != p.getSoulSaved()) {
update = true;
}
if (!update && increasedForce != p.getEnergySaved()) {
update = true;
}
if (!update && weightPenalty != p.getWeightPenalty()) {
update = true;
}
if (!update && messageRefusal != (p.getMessageRefusal() || p.getNoChannel() != 0 ? 1 : 0)) {
update = true;
}
if (!update && dangerArea != (p.isInAreaDanger() ? 1 : 0)) {
update = true;
}
if (!update && weaponPenalty != p.getWeaponExpertisePenalty()) {
update = true;
}
if (!update && armorPenalty != p.getArmourExpertisePenalty()) {
update = true;
}
if (!update && charmOfCourage != (p.isCharmOfCourage() ? 1 : 0)) {
update = true;
}
if (!update && deathPenaltyLevel != (p.getDeathPenalty() != null ? p.getDeathPenalty().getLevel() : 0)) {
update = true;
}
if (update) {
return new S_ETC_STATUS_UPDATE(p);
}
} catch (Exception e) {
log.error("Can't make etc status update for {}", p.toIDString(), e);
}
}
return null;
}
}
@NoArgsConstructor
private static class SavedStorageMaxCount {
private int inventory_limit;
private int warehouse_limit;
private int trade_sell_limit;
private int dwarven_recipe_limit;
private int common_recipe_limit;
private int freight_limit;
private int trade_buy_limit;
private int inventory_extra_slots;
public synchronized void fill(Player player) {
if (player instanceof Player p && p.isReal()) {
try {
inventory_limit = p.getInventoryLimit();
warehouse_limit = p.getWarehouseLimit();
trade_sell_limit = p.getTradeSellLimit();
dwarven_recipe_limit = p.getDwarvenRecipeLimit();
common_recipe_limit = p.getCommonRecipeLimit();
freight_limit = p.getFreightLimit();
trade_buy_limit = p.getTradeBuyLimit();
inventory_extra_slots = (int) p.calcStat(Stats.INVENTORY_LIMIT, 0, null, null);
} catch (Exception e) {
log.error("Can't fill storage max count for {}", p.toIDString(), e);
}
}
}
public synchronized S_EX_STORAGE_MAX_COUNT makeStorageMaxCountUpdate(Player player) {
if (player instanceof Player p) {
try {
boolean update = false;
if (inventory_limit != p.getInventoryLimit()) {
update = true;
}
if (!update && warehouse_limit != p.getWarehouseLimit()) {
update = true;
}
if (!update && trade_sell_limit != p.getTradeSellLimit()) {
update = true;
}
if (!update && dwarven_recipe_limit != p.getDwarvenRecipeLimit()) {
update = true;
}
if (!update && common_recipe_limit != p.getCommonRecipeLimit()) {
update = true;
}
if (!update && freight_limit != p.getFreightLimit()) {
update = true;
}
if (!update && trade_buy_limit != p.getTradeBuyLimit()) {
update = true;
}
if (!update && inventory_extra_slots != (int) p.calcStat(Stats.INVENTORY_LIMIT, 0, null, null)) {
update = true;
}
if (update) {
return new S_EX_STORAGE_MAX_COUNT(p);
}
} catch (Exception e) {
log.error("Can't make storage max count update for {}", p.toIDString(), e);
}
}
return null;
}
}
}