Advertisement
toko214

Untitled

Jan 11th, 2020
294
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 147.71 KB | None | 0 0
  1. package net.swordie.ms.client.character;
  2.  
  3. import net.swordie.ms.Server;
  4. import net.swordie.ms.ServerConfig;
  5. import net.swordie.ms.client.Account;
  6. import net.swordie.ms.client.Client;
  7. import net.swordie.ms.client.LinkSkill;
  8. import net.swordie.ms.client.User;
  9. import net.swordie.ms.client.alliance.Alliance;
  10. import net.swordie.ms.client.alliance.AllianceResult;
  11. import net.swordie.ms.client.anticheat.OffenseManager;
  12. import net.swordie.ms.client.character.avatar.AvatarData;
  13. import net.swordie.ms.client.character.avatar.AvatarLook;
  14. import net.swordie.ms.client.character.cards.MonsterBookInfo;
  15. import net.swordie.ms.client.character.damage.DamageCalc;
  16. import net.swordie.ms.client.character.damage.DamageSkinSaveData;
  17. import net.swordie.ms.client.character.info.*;
  18. import net.swordie.ms.client.character.items.*;
  19. import net.swordie.ms.client.character.keys.FuncKeyMap;
  20. import net.swordie.ms.client.character.monsterbattle.MonsterBattleLadder;
  21. import net.swordie.ms.client.character.monsterbattle.MonsterBattleMobInfo;
  22. import net.swordie.ms.client.character.monsterbattle.MonsterBattleRankInfo;
  23. import net.swordie.ms.client.character.potential.CharacterPotential;
  24. import net.swordie.ms.client.character.potential.CharacterPotentialMan;
  25. import net.swordie.ms.client.character.quest.Quest;
  26. import net.swordie.ms.client.character.quest.QuestManager;
  27. import net.swordie.ms.client.character.runestones.RuneStone;
  28. import net.swordie.ms.client.character.skills.*;
  29. import net.swordie.ms.client.character.skills.info.SkillInfo;
  30. import net.swordie.ms.client.character.skills.temp.TemporaryStatManager;
  31. import net.swordie.ms.client.friend.Friend;
  32. import net.swordie.ms.client.friend.FriendFlag;
  33. import net.swordie.ms.client.friend.FriendRecord;
  34. import net.swordie.ms.client.friend.FriendshipRingRecord;
  35. import net.swordie.ms.client.guild.Guild;
  36. import net.swordie.ms.client.guild.GuildMember;
  37. import net.swordie.ms.client.guild.result.GuildResult;
  38. import net.swordie.ms.client.jobs.Job;
  39. import net.swordie.ms.client.jobs.JobManager;
  40. import net.swordie.ms.client.jobs.legend.Evan;
  41. import net.swordie.ms.client.jobs.resistance.Demon;
  42. import net.swordie.ms.client.jobs.resistance.WildHunterInfo;
  43. import net.swordie.ms.client.jobs.sengoku.Kanna;
  44. import net.swordie.ms.client.party.Party;
  45. import net.swordie.ms.client.party.PartyMember;
  46. import net.swordie.ms.client.party.PartyResult;
  47. import net.swordie.ms.connection.OutPacket;
  48. import net.swordie.ms.connection.db.DatabaseManager;
  49. import net.swordie.ms.connection.db.InlinedIntArrayConverter;
  50. import net.swordie.ms.connection.packet.*;
  51. import net.swordie.ms.constants.GameConstants;
  52. import net.swordie.ms.constants.ItemConstants;
  53. import net.swordie.ms.constants.JobConstants;
  54. import net.swordie.ms.constants.SkillConstants;
  55. import net.swordie.ms.enums.*;
  56. import net.swordie.ms.handlers.ClientSocket;
  57. import net.swordie.ms.handlers.EventManager;
  58. import net.swordie.ms.handlers.PsychicLock;
  59. import net.swordie.ms.life.*;
  60. import net.swordie.ms.life.Merchant.EmployeeTrunk;
  61. import net.swordie.ms.life.Merchant.Merchant;
  62. import net.swordie.ms.life.Merchant.MerchantItem;
  63. import net.swordie.ms.life.drop.Drop;
  64. import net.swordie.ms.life.mob.Mob;
  65. import net.swordie.ms.life.pet.Pet;
  66. import net.swordie.ms.loaders.EtcData;
  67. import net.swordie.ms.loaders.ItemData;
  68. import net.swordie.ms.loaders.SkillData;
  69. import net.swordie.ms.loaders.StringData;
  70. import net.swordie.ms.loaders.containerclasses.AndroidInfo;
  71. import net.swordie.ms.loaders.containerclasses.ItemInfo;
  72. import net.swordie.ms.scripts.ScriptInfo;
  73. import net.swordie.ms.scripts.ScriptManagerImpl;
  74. import net.swordie.ms.scripts.ScriptType;
  75. import net.swordie.ms.util.*;
  76. import net.swordie.ms.world.Channel;
  77. import net.swordie.ms.world.World;
  78. import net.swordie.ms.world.field.*;
  79. import net.swordie.ms.world.field.fieldeffect.FieldEffect;
  80. import net.swordie.ms.world.gach.GachaponManager;
  81. import net.swordie.ms.world.shop.NpcShopDlg;
  82. import org.apache.log4j.Logger;
  83. import org.hibernate.Session;
  84. import org.hibernate.Transaction;
  85.  
  86. import javax.persistence.*;
  87. import java.awt.*;
  88. import java.io.IOException;
  89. import java.time.LocalDateTime;
  90. import java.util.List;
  91. import java.util.*;
  92. import java.util.concurrent.ScheduledFuture;
  93. import java.util.concurrent.TimeUnit;
  94. import java.util.function.Predicate;
  95. import java.util.stream.Collectors;
  96.  
  97. import static net.swordie.ms.client.character.skills.temp.CharacterTemporaryStat.*;
  98. import static net.swordie.ms.enums.ChatType.SpeakerChannel;
  99. import static net.swordie.ms.enums.ChatType.SystemNotice;
  100. import static net.swordie.ms.enums.InvType.EQUIP;
  101. import static net.swordie.ms.enums.InvType.EQUIPPED;
  102. import static net.swordie.ms.enums.InventoryOperation.*;
  103. import static net.swordie.ms.world.field.FieldInstanceType.CHANNEL;
  104.  
  105. /**
  106.  * Created on 11/17/2017.
  107.  */
  108. @Entity
  109. @Table(name = "characters")
  110. public class Char {
  111.  
  112.     @Transient
  113.     private static final Logger log = Logger.getLogger(Char.class);
  114.  
  115.     @Transient
  116.     private Client client;
  117.     private int rewardPoints;
  118.     @Id
  119.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  120.     @Column(name = "id")
  121.     private int id;
  122.  
  123.     @Column(name = "accId")
  124.     private int accId;
  125.  
  126.     @JoinColumn(name = "questManager")
  127.     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
  128.     private QuestManager questManager;
  129.  
  130.     @JoinColumn(name = "equippedInventory")
  131.     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
  132.     private Inventory equippedInventory = new Inventory(EQUIPPED, 52);
  133.  
  134.     @JoinColumn(name = "equipInventory")
  135.     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
  136.     private Inventory equipInventory = new Inventory(EQUIP, 52);
  137.  
  138.     @JoinColumn(name = "consumeInventory")
  139.     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
  140.     private Inventory consumeInventory = new Inventory(InvType.CONSUME, 52);
  141.  
  142.     @JoinColumn(name = "etcInventory")
  143.     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
  144.     private Inventory etcInventory = new Inventory(InvType.ETC, 52);
  145.  
  146.     @JoinColumn(name = "installInventory")
  147.     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
  148.     private Inventory installInventory = new Inventory(InvType.INSTALL, 52);
  149.  
  150.     @JoinColumn(name = "cashInventory")
  151.     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
  152.     private Inventory cashInventory = new Inventory(InvType.CASH, 52);
  153.  
  154.     @JoinColumn(name = "avatarData")
  155.     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
  156.     private AvatarData avatarData;
  157.  
  158.     @OneToMany(cascade = CascadeType.ALL)
  159.     @JoinColumn(name = "charId")
  160.     @OrderColumn(name = "ord")
  161.     private List<FuncKeyMap> funcKeyMaps;
  162.  
  163.     @JoinColumn(name = "charId")
  164.     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
  165.     private Set<Skill> skills;
  166.  
  167.     @JoinColumn(name = "ownerID")
  168.     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
  169.     private Set<Friend> friends;
  170.  
  171.     @JoinColumn(name = "charID")
  172.     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
  173.     private Set<CharacterPotential> potentials;
  174.  
  175.     @JoinColumn(name = "charID")
  176.     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
  177.     private Set<Familiar> familiars;
  178.  
  179.     @JoinColumn(name = "charID")
  180.     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  181.     private List<Macro> macros = new ArrayList<>();
  182.  
  183.     @JoinColumn(name = "guild")
  184.     @OneToOne(cascade = CascadeType.ALL)
  185.     private Guild guild;
  186.  
  187.     @JoinColumn(name = "monsterBook")
  188.     @OneToOne(cascade = CascadeType.ALL)
  189.     private MonsterBookInfo monsterBookInfo;
  190.  
  191.     @JoinColumn(name = "charID")
  192.     @OneToMany(cascade = CascadeType.ALL)
  193.     private Set<StolenSkill> stolenSkills;
  194.  
  195.     @JoinColumn(name = "charID")
  196.     @OneToMany(cascade = CascadeType.ALL)
  197.     private Set<ChosenSkill> chosenSkills;
  198.  
  199.     @ElementCollection(fetch = FetchType.EAGER)
  200.     @CollectionTable(name = "hyperrockfields", joinColumns = @JoinColumn(name = "CharID"))
  201.     @Column(name = "fieldid")
  202.     @OrderColumn(name = "ord")
  203.     private int[] hyperrockfields = new int[13];
  204.  
  205.     @Column(name = "monsterparkcount")
  206.     private byte monsterParkCount;
  207.  
  208.     private int partyID = 0; // Just for DB purposes
  209.     private int previousFieldID;
  210.  
  211.     @Transient
  212.     private int previousPortalID; // not super important so we wont save to db
  213.  
  214.     @ElementCollection(fetch = FetchType.EAGER)
  215.     @CollectionTable(name = "skillcooltimes", joinColumns = @JoinColumn(name = "charID"))
  216.     @MapKeyColumn(name = "skillid")
  217.     @Column(name = "nextusabletime")
  218.     private Map<Integer, Long> skillCoolTimes;
  219.     @ElementCollection
  220.     @CollectionTable(name = "itemsbuylimit", joinColumns = @JoinColumn(name = "charID"))
  221.     @MapKeyColumn(name = "shopitemid")
  222.     @Column(name = "amountbought")
  223.     private Map<Long, Integer> itemBoughtAmounts;
  224.  
  225.     @Transient
  226.     private CharacterPotentialMan potentialMan;
  227.  
  228.     @Transient
  229.     private Ranking ranking;
  230.     @Transient
  231.     private int combatOrders;
  232.     @Transient
  233.     private List<ItemPot> itemPots;
  234.     @Transient
  235.     private List<Pet> pets;
  236.     @Transient
  237.     private List<FriendRecord> friendRecords;
  238.     @Transient
  239.     private List<ExpConsumeItem> expConsumeItems;
  240.     @Transient
  241.     private List<MonsterBattleMobInfo> monsterBattleMobInfos;
  242.     @Transient
  243.     private MonsterBattleLadder monsterBattleLadder;
  244.     @Transient
  245.     private MonsterBattleRankInfo monsterBattleRankInfo;
  246.     @Transient
  247.     private Position position;
  248.     @Transient
  249.     private Position oldPosition;
  250.     @Transient
  251.     private Field field;
  252.     @Transient
  253.     private byte moveAction;
  254.     @Transient
  255.     private TemporaryStatManager temporaryStatManager;
  256.     @Transient
  257.     private GachaponManager gachaponManager;
  258.     @Transient
  259.     private Job jobHandler;
  260.     @Transient
  261.     private MarriageRecord marriageRecord;
  262.     @Transient
  263.     private WildHunterInfo wildHunterInfo;
  264.     @Transient
  265.     private ZeroInfo zeroInfo;
  266.     @Transient
  267.     private int nickItem;
  268.     @Transient
  269.     private DamageSkinSaveData damageSkin = new DamageSkinSaveData(18, 2433063, false, "Je moeder");
  270.     @Transient
  271.     private DamageSkinSaveData premiumDamageSkin = new DamageSkinSaveData();
  272.     @Transient
  273.     private boolean partyInvitable;
  274.     @Transient
  275.     private ScriptManagerImpl scriptManagerImpl = new ScriptManagerImpl(this);
  276.     @Transient
  277.     private int driverID;
  278.     @Transient
  279.     private int passengerID;
  280.     @Transient
  281.     private int chocoCount;
  282.     @Transient
  283.     private int activeEffectItemID;
  284.     @Transient
  285.     private int monkeyEffectItemID;
  286.     @Transient
  287.     private int completedSetItemID;
  288.     @Transient
  289.     private short fieldSeatID;
  290.     @Transient
  291.     private int portableChairID;
  292.     @Transient
  293.     private String portableChairMsg;
  294.     @Transient
  295.     private short foothold;
  296.     @Transient
  297.     private int tamingMobLevel;
  298.     @Transient
  299.     private int tamingMobExp;
  300.     @Transient
  301.     private int tamingMobFatigue;
  302.     @Transient
  303.     private MiniRoom miniRoom;
  304.     @Transient
  305.     private String ADBoardRemoteMsg;
  306.     @Transient
  307.     private boolean inCouple;
  308.     @Transient
  309.     private CoupleRecord couple;
  310.     @Transient
  311.     private FriendshipRingRecord friendshipRingRecord;
  312.     @Transient
  313.     private int evanDragonGlide;
  314.     @Transient
  315.     private int kaiserMorphRotateHueExtern;
  316.     @Transient
  317.     private int kaiserMorphPrimiumBlack;
  318.     @Transient
  319.     private int kaiserMorphRotateHueInnner;
  320.     @Transient
  321.     private int makingMeisterSkillEff;
  322.     @Transient
  323.     private FarmUserInfo farmUserInfo;
  324.     @Transient
  325.     private int customizeEffect;
  326.     @Transient
  327.     private String customizeEffectMsg;
  328.     @Transient
  329.     private byte soulEffect;
  330.     @Transient
  331.     private FreezeHotEventInfo freezeHotEventInfo;
  332.     @Transient
  333.     private int eventBestFriendAID;
  334.     @Transient
  335.     private int mesoChairCount;
  336.     @Transient
  337.     private boolean beastFormWingOn;
  338.     @Transient
  339.     private int activeNickItemID;
  340.     @Transient
  341.     private int mechanicHue;
  342.     @Transient
  343.     private boolean online;
  344.     @Transient
  345.     private Party party;
  346.     @Transient
  347.     private FieldInstanceType fieldInstanceType;
  348.     @Transient
  349.     private Map<Integer, Field> fields = new HashMap<>();
  350.     @Transient
  351.     private int bulletIDForAttack;
  352.     @Transient
  353.     private NpcShopDlg shop;
  354.     @Transient // yes
  355.     private User user;
  356.     @Transient // yes
  357.     private Account account;
  358.     @Transient
  359.     private Client chatClient;
  360.     @Transient
  361.     private DamageCalc damageCalc;
  362.     @Transient
  363.     private boolean buffProtector;
  364.     @Transient
  365.     private int comboCounter;
  366.     @Transient
  367.     private ScheduledFuture comboKillResetTimer;
  368.     @Transient
  369.     private ScheduledFuture timeLimitTimer;
  370.     @Transient
  371.     private int deathCount = -1;
  372.     @Transient
  373.     private long runeStoneCooldown;
  374.     @Transient
  375.     private MemorialCubeInfo memorialCubeInfo;
  376.     @Transient
  377.     private Familiar activeFamiliar;
  378.     @Transient
  379.     private boolean skillCDBypass = false;
  380.     // TODO Move this to CharacterStat?
  381.     @Transient
  382.     private Map<BaseStat, Long> baseStats = new HashMap<>();
  383.     @Transient
  384.     private boolean changingChannel;
  385.     @Transient
  386.     private TownPortal townPortal;
  387.     @Transient
  388.     private TradeRoom tradeRoom;
  389.     @Transient
  390.     private boolean battleRecordOn;
  391.     @Transient
  392.     private long nextRandomPortalTime;
  393.     @Transient
  394.     private Map<Integer, Integer> currentDirectionNode;
  395.     @Transient
  396.     private String lieDetectorAnswer = "";
  397.     @Transient
  398.     private long lastLieDetector = 0;
  399.     // TOOD: count and log lie detector passes and fails
  400.     @Transient
  401.     private boolean tutor = false;
  402.     @Transient
  403.     private int transferField = 0;
  404.     @Transient
  405.     private int transferFieldReq = 0;
  406.     @Transient
  407.     private String blessingOfFairy = null;
  408.     @Transient
  409.     private String blessingOfEmpress = null;
  410.     @Transient
  411.     private Map<Integer, Integer> hyperPsdSkillsCooltimeR = new HashMap<>();
  412.     @Transient
  413.     private boolean isInvincible = false;
  414.     @Convert(converter = InlinedIntArrayConverter.class)
  415.     private List<Integer> quickslotKeys;
  416.     @Transient
  417.     private Android android;
  418.     @Transient
  419.     private Map<Integer, PsychicArea> psychicAreas;
  420.     @Transient
  421.     private Map<Integer, PsychicLock> psychicLocks;
  422.     @Transient
  423.     private Map<Integer, PsychicLockBall> psychicLockBalls;
  424.     @Transient
  425.     private Instance instance;
  426.     @Transient
  427.     private Merchant merchant;
  428.     @Transient
  429.     private Merchant visitingmerchant;
  430.  
  431.  
  432.     public Char() {
  433.         this(0, "", 0, 0, 0, (short) 0, (byte) -1, (byte) -1, 0, 0, new int[]{});
  434.     }
  435.  
  436.     public Char(int accId, String name, int keySettingType, int eventNewCharSaleJob, int job, short curSelectedSubJob,
  437.                 byte gender, byte skin, int face, int hair, int[] items) {
  438.         this.accId = accId;
  439.         avatarData = new AvatarData();
  440.         avatarData.setAvatarLook(new AvatarLook());
  441.         AvatarLook avatarLook = avatarData.getAvatarLook();
  442.         avatarLook.setGender(gender);
  443.         avatarLook.setSkin(skin);
  444.         avatarLook.setFace(face);
  445.         avatarLook.setHair(hair);
  446.         List<Integer> hairEquips = new ArrayList<>();
  447.         for (int itemId : items) {
  448.             Equip equip = ItemData.getEquipDeepCopyFromID(itemId, false);
  449.             if (equip != null && ItemConstants.isEquip(itemId)) {
  450.                 hairEquips.add(itemId);
  451.                 if ("Wp".equals(equip.getiSlot())) {
  452.                     if (!equip.isCash()) {
  453.                         avatarLook.setWeaponId(itemId);
  454.                     } else {
  455.                         avatarLook.setWeaponStickerId(itemId);
  456.                     }
  457.                 }
  458.             }
  459.         }
  460.         avatarLook.setHairEquips(hairEquips);
  461.         avatarLook.setJob(job);
  462.         CharacterStat characterStat = new CharacterStat(name, job);
  463.         getAvatarData().setCharacterStat(characterStat);
  464.         characterStat.setGender(gender);
  465.         characterStat.setSkin(skin);
  466.         characterStat.setFace(items.length > 0 ? items[0] : 0);
  467.         characterStat.setHair(items.length > 1 ? items[1] : 0);
  468.         characterStat.setSubJob(curSelectedSubJob);
  469.         setFieldInstanceType(CHANNEL);
  470.         ranking = new Ranking();
  471.         pets = new ArrayList<>();
  472.         questManager = new QuestManager(this);
  473.         itemPots = new ArrayList<>();
  474.         friendRecords = new ArrayList<>();
  475.         expConsumeItems = new ArrayList<>();
  476.         skills = new HashSet<>();
  477.         temporaryStatManager = new TemporaryStatManager(this);
  478.         gachaponManager = new GachaponManager();
  479.         friends = new HashSet<>();
  480.         monsterBookInfo = new MonsterBookInfo();
  481.         potentialMan = new CharacterPotentialMan(this);
  482.         skillCoolTimes = new HashMap<>();
  483.         familiars = new HashSet<>();
  484.         hyperrockfields = new int[]{
  485.                 999999999,
  486.                 999999999,
  487.                 999999999,
  488.  
  489.                 999999999,
  490.                 999999999,
  491.                 999999999,
  492.  
  493.                 999999999,
  494.                 999999999,
  495.                 999999999,
  496.  
  497.                 999999999,
  498.                 999999999,
  499.                 999999999,
  500.  
  501.                 999999999,
  502.         };
  503.         monsterParkCount = 0;
  504.         currentDirectionNode = new HashMap<>();
  505.         potentials = new HashSet<>();
  506. //        monsterBattleMobInfos = new ArrayList<>();
  507. //        monsterBattleLadder = new MonsterBattleLadder();
  508. //        monsterBattleRankInfo = new MonsterBattleRankInfo();
  509.         psychicAreas = new HashMap<>();
  510.         psychicLocks = new HashMap<>();
  511.         psychicLockBalls = new HashMap<>();
  512.         funcKeyMaps = new ArrayList<FuncKeyMap>();
  513.     }
  514.  
  515.     public static Char getFromDBById(int userId) {
  516.         return (Char) DatabaseManager.getObjFromDB(Char.class, userId);
  517.     }
  518.  
  519.     public static Char getFromDBByName(String name) {
  520.         log.info(String.format("%s: Trying to get Char by name (%s).", LocalDateTime.now(), name));
  521.         // DAO?
  522.         Session session = DatabaseManager.getSession();
  523.         Transaction transaction = session.beginTransaction();
  524.         Query query = session.createQuery("FROM Char chr WHERE chr.avatarData.characterStat.name = :name");
  525.         query.setParameter("name", name);
  526.         List l = ((org.hibernate.query.Query) query).list();
  527.         Char chr = null;
  528.         if (l != null && l.size() > 0) {
  529.             chr = (Char) l.get(0);
  530.         }
  531.         transaction.commit();
  532.         session.close();
  533.         return chr;
  534.     }
  535.  
  536.     public static Char getFromDBByNameAndWorld(String name, int worldId) {
  537.         Session session = DatabaseManager.getSession();
  538.         Transaction transaction = session.beginTransaction();
  539.         Query query = session.createQuery("FROM Char chr " +
  540.                 "WHERE chr.avatarData.characterStat.name = :name AND chr.avatarData.characterStat.worldIdForLog = :world");
  541.         query.setParameter("name", name);
  542.         query.setParameter("world", worldId);
  543.         List l = ((org.hibernate.query.Query) query).list();
  544.         Char chr = null;
  545.         if (l != null && l.size() > 0) {
  546.             chr = (Char) l.get(0);
  547.         }
  548.         transaction.commit();
  549.         session.close();
  550.         return chr;
  551.     }
  552.  
  553.     public AvatarData getAvatarData() {
  554.         return avatarData;
  555.     }
  556.  
  557.     public Ranking getRanking() {
  558.         return ranking;
  559.     }
  560.  
  561.     public int getId() {
  562.         return id;
  563.     }
  564.  
  565.     public void setAvatarData(AvatarData avatarData) {
  566.         this.avatarData = avatarData;
  567.     }
  568.  
  569.     public void setRanking(Ranking ranking) {
  570.         this.ranking = ranking;
  571.     }
  572.  
  573.     public int getAccId() {
  574.         return accId;
  575.     }
  576.  
  577.     public void setAccId(int accId) {
  578.         this.accId = accId;
  579.     }
  580.  
  581.     public Inventory getEquippedInventory() {
  582.         return equippedInventory;
  583.     }
  584.  
  585.     public void addItemToInventory(InvType type, Item item, boolean hasCorrectBagIndex) {
  586.         if (item == null) {
  587.             return;
  588.         }
  589.         Inventory inventory = getInventoryByType(type);
  590.         ItemInfo ii = ItemData.getItemInfoByID(item.getItemId());
  591.         int quantity = item.getQuantity();
  592.         if (inventory != null) {
  593.             Item existingItem = inventory.getItemByItemIDAndStackable(item.getItemId());
  594.             boolean rec = false;
  595.             if (existingItem != null && existingItem.getInvType().isStackable() && existingItem.getQuantity() < ii.getSlotMax()) {
  596.                 if (quantity + existingItem.getQuantity() > ii.getSlotMax()) {
  597.                     quantity = ii.getSlotMax() - existingItem.getQuantity();
  598.                     item.setQuantity(item.getQuantity() - quantity);
  599.                     rec = true;
  600.                 }
  601.                 existingItem.addQuantity(quantity);
  602.                 write(WvsContext.inventoryOperation(true, false,
  603.                         UpdateQuantity, (short) existingItem.getBagIndex(), (byte) -1, 0, existingItem));
  604.                 Item copy = item.deepCopy();
  605.                 copy.setQuantity(quantity);
  606.                 if (rec) {
  607.                     addItemToInventory(item);
  608.                 }
  609.             } else {
  610.                 if (!hasCorrectBagIndex) {
  611.                     item.setBagIndex(inventory.getFirstOpenSlot());
  612.                 }
  613.                 Item itemCopy = null;
  614.                 if (item.getInvType().isStackable() && ii != null && item.getQuantity() > ii.getSlotMax()) {
  615.                     itemCopy = item.deepCopy();
  616.                     quantity = quantity - ii.getSlotMax();
  617.                     itemCopy.setQuantity(quantity);
  618.                     item.setQuantity(ii.getSlotMax());
  619.                     rec = true;
  620.                 }
  621.                 inventory.addItem(item);
  622.                 write(WvsContext.inventoryOperation(true, false,
  623.                         Add, (short) item.getBagIndex(), (byte) -1, 0, item));
  624.                 if (rec) {
  625.                     addItemToInventory(itemCopy);
  626.                 }
  627.             }
  628.             setBulletIDForAttack(calculateBulletIDForAttack(1));
  629.         }
  630.     }
  631.  
  632.     public void addItemToInventory(Item item) {
  633.         addItemToInventory(item.getInvType(), item, false);
  634.     }
  635.  
  636.     public void setEquippedInventory(Inventory equippedInventory) {
  637.         this.equippedInventory = equippedInventory;
  638.     }
  639.  
  640.     public Inventory getEquipInventory() {
  641.         return equipInventory;
  642.     }
  643.  
  644.     public void setEquipInventory(Inventory equipInventory) {
  645.         this.equipInventory = equipInventory;
  646.     }
  647.  
  648.     public Inventory getConsumeInventory() {
  649.         return consumeInventory;
  650.     }
  651.  
  652.     public void setConsumeInventory(Inventory consumeInventory) {
  653.         this.consumeInventory = consumeInventory;
  654.     }
  655.  
  656.     public Inventory getEtcInventory() {
  657.         return etcInventory;
  658.     }
  659.  
  660.     public void setEtcInventory(Inventory etcInventory) {
  661.         this.etcInventory = etcInventory;
  662.     }
  663.  
  664.     public Inventory getInstallInventory() {
  665.         return installInventory;
  666.     }
  667.  
  668.     public void setInstallInventory(Inventory installInventory) {
  669.         this.installInventory = installInventory;
  670.     }
  671.  
  672.     public Inventory getCashInventory() {
  673.         return cashInventory;
  674.     }
  675.  
  676.     public void setCashInventory(Inventory cashInventory) {
  677.         this.cashInventory = cashInventory;
  678.     }
  679.  
  680.     /**
  681.      * Encodes this Char's info inside a given {@link OutPacket}, with given info.
  682.      *
  683.      * @param outPacket The OutPacket this method should encode to.
  684.      * @param mask      Which info should be encoded.
  685.      */
  686.     public void encode(OutPacket outPacket, DBChar mask) {
  687.  
  688.         // CharacterData::Decode
  689.         outPacket.encodeLong(mask.get());
  690.         outPacket.encodeByte(getCombatOrders());
  691.         for (int i = 0; i < GameConstants.MAX_PET_AMOUNT; i++) {
  692.             if (i < getPets().size()) {
  693.                 outPacket.encodeInt(getPets().get(i).getActiveSkillCoolTime());
  694.             } else {
  695.                 outPacket.encodeInt(0);
  696.             }
  697.         }
  698.         outPacket.encodeByte(0); // unk, not in kmst
  699.         byte sizeByte = 0;
  700.         outPacket.encodeByte(sizeByte);
  701.         for (int i = 0; i < sizeByte; i++) {
  702.             outPacket.encodeInt(0);
  703.         }
  704.  
  705.         int sizee = 0;
  706.         outPacket.encodeInt(sizee);
  707.         for (int i = 0; i < sizee; i++) {
  708.             outPacket.encodeInt(0); // nKey
  709.             outPacket.encodeLong(0); // pInfo
  710.         }
  711.         outPacket.encodeByte(0); // again unsure
  712.         if (mask.isInMask(DBChar.Character)) {
  713.             getAvatarData().getCharacterStat().encode(outPacket);
  714.             outPacket.encodeByte(getFriendRecords().size());
  715.             boolean hasBlessingOfFairy = getBlessingOfFairy() != null;
  716.             outPacket.encodeByte(hasBlessingOfFairy);
  717.             if (hasBlessingOfFairy) {
  718.                 outPacket.encodeString(getBlessingOfFairy());
  719.             }
  720.             boolean hasBlessingOfEmpress = getBlessingOfEmpress() != null;
  721.             outPacket.encodeByte(hasBlessingOfEmpress);
  722.             if (hasBlessingOfEmpress) {
  723.                 outPacket.encodeString(getBlessingOfEmpress());
  724.             }
  725.             outPacket.encodeByte(false); // ultimate explorer, deprecated
  726.         }
  727.         if (mask.isInMask(DBChar.Money)) {
  728.             outPacket.encodeLong(getMoney());
  729.         }
  730.         if (mask.isInMask(DBChar.ItemSlotConsume) || mask.isInMask(DBChar.ExpConsumeItem)) {
  731.             outPacket.encodeInt(getExpConsumeItems().size());
  732.             for (ExpConsumeItem eci : getExpConsumeItems()) {
  733.                 eci.encode(outPacket);
  734.             }
  735.         }
  736.         if (mask.isInMask(DBChar.ItemSlotConsume) || mask.isInMask(DBChar.ShopBuyLimit)) {
  737.             int size = 0;
  738.             outPacket.encodeInt(size);
  739.             for (int i = 0; i < size; i++) {
  740.                 outPacket.encodeInt(0);
  741.                 outPacket.encodeInt(0);
  742.                 outPacket.encodeInt(0);
  743.                 outPacket.encodeInt(0);
  744.                 outPacket.encodeInt(0);
  745.                 outPacket.encodeLong(0);
  746.                 outPacket.encodeLong(0);
  747.             }
  748.         }
  749.         if (mask.isInMask(DBChar.MonsterBattleInfo)) {
  750.             int count = 0; // MonsterBattle_MobInfo
  751.             outPacket.encodeInt(count);
  752.             if (getMonsterBattleMobInfos() != null) {
  753.                 for (MonsterBattleMobInfo mbmi : getMonsterBattleMobInfos()) {
  754.                     mbmi.encode(outPacket);
  755.                     // TODO int int int int int int byte int int
  756.                 }
  757.             }
  758.             outPacket.encodeInt(getId());
  759.  
  760.             outPacket.encodeInt(0);
  761.             outPacket.encodeInt(0);
  762.             outPacket.encodeInt(0);
  763.             outPacket.encodeInt(0);
  764.             outPacket.encodeInt(0);
  765.             outPacket.encodeInt(0);
  766.             outPacket.encodeInt(0);
  767.  
  768.             boolean hasMonsterBattleLadder = getMonsterBattleLadder() != null;
  769.             outPacket.encodeByte(hasMonsterBattleLadder);
  770.             if (hasMonsterBattleLadder) {
  771.                 getMonsterBattleLadder().encode(outPacket); // TODO GW_MonsterBattleLadder_UserInfo::Decode
  772.             }
  773.             boolean hasMonsterBattleRankInfo = getMonsterBattleRankInfo() != null;
  774.             outPacket.encodeByte(hasMonsterBattleRankInfo);
  775.             if (hasMonsterBattleRankInfo) {
  776.                 getMonsterBattleRankInfo().encode(outPacket); // TODO GW_MonsterBattleRankInfo::Decode(&dummyBLD, nSlotHyper);
  777.             }
  778.             outPacket.encodeByte(hasMonsterBattleRankInfo);
  779.             // again?
  780.             if (hasMonsterBattleRankInfo) {
  781.                 getMonsterBattleRankInfo().encode(outPacket); // TODO GW_MonsterBattleRankInfo::Decode(&dummyBLD, nSlotHyper);
  782.             }
  783.         }
  784.         if (mask.isInMask(DBChar.InventorySize)) {
  785.             outPacket.encodeByte(getEquipInventory().getSlots());
  786.             outPacket.encodeByte(getConsumeInventory().getSlots());
  787.             outPacket.encodeByte(getEtcInventory().getSlots());
  788.             outPacket.encodeByte(getInstallInventory().getSlots());
  789.             outPacket.encodeByte(getCashInventory().getSlots());
  790.         }
  791.  
  792.         if (mask.isInMask(DBChar.AdminShopCount)) {
  793.             outPacket.encodeInt(0); // ???
  794.             outPacket.encodeInt(0);
  795.         }
  796.         if (mask.isInMask(DBChar.ItemSlotEquip)) {
  797.             outPacket.encodeByte(0); // ?
  798.             List<Item> equippedItems = new ArrayList<>(getEquippedInventory().getItems());
  799.             equippedItems.sort(Comparator.comparingInt(Item::getBagIndex));
  800.             // Normal equipped items
  801.             for (Item item : equippedItems) {
  802.                 Equip equip = (Equip) item;
  803.                 if (item.getBagIndex() > BodyPart.BPBase.getVal() && item.getBagIndex() < BodyPart.BPEnd.getVal()) {
  804.                     outPacket.encodeShort(equip.getBagIndex());
  805.                     equip.encode(outPacket);
  806.                 }
  807.             }
  808.             outPacket.encodeShort(0);
  809.             // Cash equipped items
  810.             for (Item item : getEquippedInventory().getItems()) {
  811.                 Equip equip = (Equip) item;
  812.                 if (item.getBagIndex() >= BodyPart.CBPBase.getVal() && item.getBagIndex() <= BodyPart.CBPEnd.getVal()) {
  813.                     outPacket.encodeShort(equip.getBagIndex() - 100);
  814.                     equip.encode(outPacket);
  815.                 }
  816.             }
  817.             outPacket.encodeShort(0);
  818.             // Equip inventory
  819.             for (Item item : getEquipInventory().getItems()) {
  820.                 Equip equip = (Equip) item;
  821.                 outPacket.encodeShort(equip.getBagIndex());
  822.                 equip.encode(outPacket);
  823.             }
  824.             outPacket.encodeShort(0);
  825.             // NonBPEquip::Decode (Evan)
  826.             for (Item item : getEquippedInventory().getItems()) {
  827.                 Equip equip = (Equip) item;
  828.                 if (item.getBagIndex() >= BodyPart.EvanBase.getVal() && item.getBagIndex() < BodyPart.EvanEnd.getVal()) {
  829.                     outPacket.encodeShort(equip.getBagIndex());
  830.                     equip.encode(outPacket);
  831.                 }
  832.             }
  833.             outPacket.encodeShort(0);
  834.             // VirtualEquipInventory::Decode (Android)
  835.             // >= 20k < 200024?
  836.             for (Item item : getEquippedInventory().getItems()) {
  837.                 Equip equip = (Equip) item;
  838.                 if (item.getBagIndex() >= BodyPart.MechBase.getVal() && item.getBagIndex() < BodyPart.MechEnd.getVal()) {
  839.                     outPacket.encodeShort(equip.getBagIndex());
  840.                     equip.encode(outPacket);
  841.                 }
  842.             }
  843.             outPacket.encodeShort(0);
  844.             // Guessing pet consume items, could very well be wrong
  845.             for (Item item : getEquippedInventory().getItems()) {
  846.                 Equip equip = (Equip) item;
  847.                 if (item.getBagIndex() >= 200 && item.getBagIndex() <= 300) {
  848.                     outPacket.encodeShort(equip.getBagIndex());
  849.                     equip.encode(outPacket);
  850.                 }
  851.             }
  852.             outPacket.encodeShort(0);
  853.             // Android
  854.             for (Item item : getEquippedInventory().getItems()) {
  855.                 Equip equip = (Equip) item;
  856.                 if (item.getBagIndex() >= BodyPart.APBase.getVal() && item.getBagIndex() <= BodyPart.APEnd.getVal()) {
  857.                     outPacket.encodeShort(equip.getBagIndex());
  858.                     equip.encode(outPacket);
  859.                 }
  860.             }
  861.             outPacket.encodeShort(0);
  862.             // Angelic Buster
  863.             for (Item item : getEquippedInventory().getItems()) {
  864.                 Equip equip = (Equip) item;
  865.                 if (item.getBagIndex() >= BodyPart.DUBase.getVal() && item.getBagIndex() < BodyPart.DUEnd.getVal()) {
  866.                     outPacket.encodeShort(equip.getBagIndex());
  867.                     equip.encode(outPacket);
  868.                 }
  869.             }
  870.             outPacket.encodeShort(0);
  871.             // Bits
  872.             for (Item item : getEquippedInventory().getItems()) {
  873.                 Equip equip = (Equip) item;
  874.                 if (item.getBagIndex() >= BodyPart.BitsBase.getVal() && item.getBagIndex() < BodyPart.BitsEnd.getVal()) {
  875.                     outPacket.encodeShort(equip.getBagIndex());
  876.                     equip.encode(outPacket);
  877.                 }
  878.             }
  879.             outPacket.encodeShort(0);
  880.             // Zero
  881.             for (Item item : getEquippedInventory().getItems()) {
  882.                 Equip equip = (Equip) item;
  883.                 if (item.getBagIndex() >= BodyPart.ZeroBase.getVal() && item.getBagIndex() < BodyPart.ZeroEnd.getVal()) {
  884.                     outPacket.encodeShort(equip.getBagIndex());
  885.                     equip.encode(outPacket);
  886.                 }
  887.             }
  888.             outPacket.encodeShort(0);
  889.             // Totems
  890.             for (Item item : getEquippedInventory().getItems()) {
  891.                 Equip equip = (Equip) item;
  892.                 if (item.getBagIndex() >= BodyPart.TotemBase.getVal() && item.getBagIndex() < BodyPart.TotemEnd.getVal()) {
  893.                     outPacket.encodeShort(equip.getBagIndex());
  894.                     equip.encode(outPacket);
  895.                 }
  896.             }
  897.             outPacket.encodeShort(0);
  898.             // Maybe zero beta cash?
  899.             for (Item item : getEquippedInventory().getItems()) {
  900.                 Equip equip = (Equip) item;
  901.                 if (item.getBagIndex() >= BodyPart.MBPBase.getVal() && item.getBagIndex() < BodyPart.MBPEnd.getVal()) {
  902.                     outPacket.encodeShort(equip.getBagIndex());
  903.                     equip.encode(outPacket);
  904.                 }
  905.             }
  906.             outPacket.encodeShort(0);
  907.             // Haku
  908.             for (Item item : getEquippedInventory().getItems()) {
  909.                 Equip equip = (Equip) item;
  910.                 if (item.getBagIndex() >= BodyPart.HakuStart.getVal() && item.getBagIndex() < BodyPart.HakuEnd.getVal()) {
  911.                     outPacket.encodeShort(equip.getBagIndex());
  912.                     equip.encode(outPacket);
  913.                 }
  914.             }
  915.             outPacket.encodeShort(0);
  916.             outPacket.encodeShort(0);
  917.             outPacket.encodeShort(0);
  918.         }
  919.         if (mask.isInMask(DBChar.ItemSlotConsume)) {
  920.             for (Item item : getConsumeInventory().getItems()) {
  921.                 outPacket.encodeByte(item.getBagIndex());
  922.                 item.encode(outPacket);
  923.             }
  924.             outPacket.encodeByte(0);
  925.         }
  926.         if (mask.isInMask(DBChar.ItemSlotInstall)) {
  927.             for (Item item : getInstallInventory().getItems()) {
  928.                 outPacket.encodeByte(item.getBagIndex());
  929.                 item.encode(outPacket);
  930.             }
  931.             outPacket.encodeByte(0);
  932.         }
  933.         if (mask.isInMask(DBChar.ItemSlotEtc)) {
  934.             for (Item item : getEtcInventory().getItems()) {
  935.                 outPacket.encodeByte(item.getBagIndex());
  936.                 item.encode(outPacket);
  937.             }
  938.             outPacket.encodeByte(0);
  939.         }
  940.         if (mask.isInMask(DBChar.ItemSlotCash)) {
  941.             for (Item item : getCashInventory().getItems()) {
  942.                 outPacket.encodeByte(item.getBagIndex());
  943.                 item.encode(outPacket);
  944.             }
  945.             outPacket.encodeByte(0);
  946.         }
  947.         // BagDatas
  948.         if (mask.isInMask(DBChar.ItemSlotConsume)) {
  949.             // TODO
  950.             outPacket.encodeInt(0);
  951.         }
  952.         if (mask.isInMask(DBChar.ItemSlotInstall)) {
  953.             // TODO
  954.             outPacket.encodeInt(0);
  955.         }
  956.         if (mask.isInMask(DBChar.ItemSlotEtc)) {
  957.             // TODO
  958.             outPacket.encodeInt(0);
  959.         }
  960.         if (mask.isInMask(DBChar.ItemSlotCash)) {
  961.             // TODO
  962.             outPacket.encodeInt(0);
  963.         }
  964.         // End bagdatas
  965.         if (mask.isInMask(DBChar.CoreAura)) {
  966.             int val = 0;
  967.             outPacket.encodeInt(val);
  968.             for (int i = 0; i < val; i++) {
  969.                 outPacket.encodeInt(0);
  970.                 outPacket.encodeLong(0);
  971.             }
  972.         }
  973.         if (mask.isInMask(DBChar.ItemPot)) {
  974.             boolean hasItemPot = getItemPots() != null;
  975.             outPacket.encodeByte(hasItemPot);
  976.             if (hasItemPot) {
  977.                 for (int i = 0; i < getItemPots().size(); i++) {
  978.                     getItemPots().get(i).encode(outPacket);
  979.                     outPacket.encodeByte(i != getItemPots().size() - 1);
  980.                 }
  981.             }
  982.         }
  983.  
  984.         if (mask.isInMask(DBChar.SkillRecord)) {
  985.             boolean encodeSkills = getSkills().size() > 0;
  986.             outPacket.encodeByte(encodeSkills);
  987.             if (encodeSkills) {
  988.                 Set<LinkSkill> linkSkills = getLinkSkills();
  989.                 short size = (short) (getSkills().size() + linkSkills.size());
  990.                 outPacket.encodeShort(size);
  991.                 for (Skill skill : getSkills()) {
  992.                     outPacket.encodeInt(skill.getSkillId());
  993.                     outPacket.encodeInt(skill.getCurrentLevel());
  994.                     outPacket.encodeFT(FileTime.fromType(FileTime.Type.MAX_TIME));
  995.                     if (SkillConstants.isSkillNeedMasterLevel(skill.getSkillId())) {
  996.                         outPacket.encodeInt(skill.getMasterLevel());
  997.                     }
  998.                 }
  999.                 for (LinkSkill linkSkill : linkSkills) {
  1000.                     outPacket.encodeInt(linkSkill.getLinkSkillID());
  1001.                     outPacket.encodeInt(linkSkill.getOwnerID());
  1002.                     outPacket.encodeFT(FileTime.fromType(FileTime.Type.MAX_TIME));
  1003.                     if (SkillConstants.isSkillNeedMasterLevel(linkSkill.getLinkSkillID())) {
  1004.                         outPacket.encodeInt(3); // whatevs
  1005.                     }
  1006.                 }
  1007.                 outPacket.encodeShort(linkSkills.size());
  1008.                 for (LinkSkill linkSkill : linkSkills) {
  1009.                     outPacket.encodeInt(linkSkill.getLinkSkillID()); // another nCount
  1010.                     outPacket.encodeShort(linkSkill.getLevel() - 1); // idk
  1011.                 }
  1012.             } else {
  1013.                 short size = 0;
  1014.                 outPacket.encodeShort(size);
  1015.                 for (int i = 0; i < size; i++) {
  1016.                     outPacket.encodeInt(0); // nTI
  1017.                     outPacket.encodeInt(0); // sValue
  1018.                 }
  1019.                 short size2 = 0;
  1020.                 outPacket.encodeShort(size2);
  1021.                 for (int i = 0; i < size2; i++) {
  1022.                     outPacket.encodeInt(0); // nTI
  1023.                 }
  1024.  
  1025.                 short size3 = 0;
  1026.                 outPacket.encodeShort(size3);
  1027.                 for (int i = 0; i < size3; i++) {
  1028.                     outPacket.encodeInt(0); // nTI
  1029.                     outPacket.encodeFT(new FileTime(0)); // pInfo
  1030.                 }
  1031.                 short size4 = 0;
  1032.                 outPacket.encodeShort(size2);
  1033.                 for (int i = 0; i < size2; i++) {
  1034.                     outPacket.encodeInt(0); // nTI
  1035.                 }
  1036.  
  1037.                 short size5 = 0;
  1038.                 outPacket.encodeShort(size);
  1039.                 for (int i = 0; i < size; i++) {
  1040.                     outPacket.encodeInt(0); // nTI
  1041.                     outPacket.encodeInt(0); // sValue
  1042.                 }
  1043.                 short size6 = 0;
  1044.                 outPacket.encodeShort(size2);
  1045.                 for (int i = 0; i < size2; i++) {
  1046.                     outPacket.encodeInt(0); // nTI
  1047.                 }
  1048.             }
  1049.         }
  1050.  
  1051.         if (mask.isInMask(DBChar.SkillCooltime)) {
  1052.             long curTime = System.currentTimeMillis();
  1053.             Map<Integer, Long> cooltimes = new HashMap<>();
  1054.             getSkillCoolTimes().forEach((key, value) -> {
  1055.                 if (value - curTime > 0) {
  1056.                     cooltimes.put(key, value);
  1057.                 }
  1058.             });
  1059.             outPacket.encodeShort(cooltimes.size());
  1060.             for (Map.Entry<Integer, Long> cooltime : cooltimes.entrySet()) {
  1061.                 outPacket.encodeInt(cooltime.getKey()); // nSkillId
  1062.                 outPacket.encodeInt((int) ((cooltime.getValue() - curTime) / 1000)); // nSkillCooltime
  1063.             }
  1064.         }
  1065.         if (mask.isInMask(DBChar.QuestRecord)) {
  1066.             // modified/deleted, not completed anyway
  1067.             boolean removeAllOldEntries = true;
  1068.             outPacket.encodeByte(removeAllOldEntries);
  1069.             short size = (short) getQuestManager().getQuestsInProgress().size();
  1070.             outPacket.encodeShort(size);
  1071.             for (Quest quest : getQuestManager().getQuestsInProgress()) {
  1072.                 outPacket.encodeInt(quest.getQRKey());
  1073.                 outPacket.encodeString(quest.getQRValue());
  1074.             }
  1075.             if (!removeAllOldEntries) {
  1076.                 // blacklisted quests
  1077.                 short size2 = 0;
  1078.                 outPacket.encodeShort(size2);
  1079.                 for (int i = 0; i < size2; i++) {
  1080.                     outPacket.encodeInt(0); // nQRKey
  1081.                 }
  1082.             }
  1083.             size = 0;
  1084.             outPacket.encodeShort(size);
  1085.             // Not sure what this is for
  1086.             for (int i = 0; i < size; i++) {
  1087.                 outPacket.encodeString("");
  1088.                 outPacket.encodeString("");
  1089.             }
  1090.         }
  1091.         if (mask.isInMask(DBChar.QuestComplete)) {
  1092.             boolean removeAllOldEntries = true;
  1093.             outPacket.encodeByte(removeAllOldEntries);
  1094.             Set<Quest> completedQuests = getQuestManager().getCompletedQuests();
  1095.             outPacket.encodeShort(completedQuests.size());
  1096.             for (Quest quest : completedQuests) {
  1097.                 outPacket.encodeInt(quest.getQRKey());
  1098.                 outPacket.encodeInt(0); // Timestamp of completion
  1099.             }
  1100.             if (!removeAllOldEntries) {
  1101.                 short size = 0;
  1102.                 outPacket.encodeShort(size);
  1103.                 for (int i = 0; i < size; i++) {
  1104.                     outPacket.encodeInt(0); // nQRKey?
  1105.                 }
  1106.             }
  1107.         }
  1108.         if (mask.isInMask(DBChar.MinigameRecord)) {
  1109.             int size = 0;
  1110.             outPacket.encodeShort(size);
  1111.             for (int i = 0; i < size; i++) {
  1112.                 new MiniGameRecord().encode(outPacket);
  1113.             }
  1114.         }
  1115.         if (mask.isInMask(DBChar.CoupleRecord)) {
  1116.             int coupleSize = 0;
  1117.             outPacket.encodeShort(coupleSize);
  1118.             for (int i = 0; i < coupleSize; i++) {
  1119.                 new CoupleRecord().encode(outPacket);
  1120.             }
  1121.             int friendSize = 0;
  1122.             outPacket.encodeShort(friendSize);
  1123.             for (int i = 0; i < friendSize; i++) {
  1124.                 new FriendRecord().encode(outPacket);
  1125.             }
  1126.             int marriageSize = 0;
  1127.             outPacket.encodeShort(marriageSize);
  1128.             for (int i = 0; i < marriageSize; i++) {
  1129.                 new MarriageRecord().encode(outPacket);
  1130.             }
  1131.         }
  1132.  
  1133.         if (mask.isInMask(DBChar.MapTransfer)) {
  1134.             for (int i = 0; i < 5; i++) {
  1135.                 outPacket.encodeInt(0);
  1136.             }
  1137.             for (int i = 0; i < 10; i++) {
  1138.                 outPacket.encodeInt(0);
  1139.             }
  1140.             for (int i = 0; i < 13; i++) {
  1141.                 outPacket.encodeInt(0);
  1142.             }
  1143.             for (int i = 0; i < 13; i++) {
  1144.                 outPacket.encodeInt(0);
  1145.             }
  1146.         }
  1147.         if (mask.isInMask(DBChar.MonsterBookCover)) {
  1148.             outPacket.encodeInt(getMonsterBookInfo().getCoverID());
  1149.         }
  1150.         if (mask.isInMask(DBChar.MonsterBookCard)) {
  1151.             boolean isCompleted = false;
  1152.             outPacket.encodeByte(isCompleted);
  1153.             if (!isCompleted) {
  1154.                 short size = (short) getMonsterBookInfo().getCards().size();
  1155.                 outPacket.encodeShort(size);
  1156.                 for (int card : getMonsterBookInfo().getCards()) {
  1157.                     outPacket.encodeShort(card);
  1158.                     outPacket.encodeByte(true); // bEnabled?
  1159.                 }
  1160.             } else {
  1161.                 outPacket.encodeShort(0); // card list size
  1162.                 short encSize = 0;
  1163.                 outPacket.encodeShort(encSize);
  1164.                 outPacket.encodeArr(new byte[encSize]);
  1165.                 encSize = 0;
  1166.                 outPacket.encodeShort(encSize);
  1167.                 outPacket.encodeArr(new byte[encSize]);
  1168.             }
  1169.             outPacket.encodeInt(getMonsterBookInfo().getSetID()); // monsterbook set
  1170.         }
  1171.         if (mask.isInMask(DBChar.QuestCompleteOld)) {
  1172.             short size = 0;
  1173.             outPacket.encodeShort(size);
  1174.             for (int i = 0; i < size; i++) {
  1175.                 outPacket.encodeShort(0);
  1176.             }
  1177.         }
  1178.         if (mask.isInMask(DBChar.Familiar)) {
  1179.             outPacket.encodeInt(getFamiliars().size());
  1180.             for (Familiar familiar : getFamiliars()) {
  1181.                 familiar.encode(outPacket);
  1182.             }
  1183.         }
  1184.         if (mask.isInMask(DBChar.NewYearCard)) {
  1185.             short size = 0;
  1186.             outPacket.encodeShort(size);
  1187.             for (int i = 0; i < size; i++) {
  1188.                 outPacket.encodeInt(0);
  1189.                 outPacket.encodeInt(0);
  1190.                 outPacket.encodeString("");
  1191.                 outPacket.encodeByte(0);
  1192.                 outPacket.encodeLong(0);
  1193.                 outPacket.encodeInt(0);
  1194.                 outPacket.encodeString("");
  1195.                 outPacket.encodeByte(0);
  1196.                 outPacket.encodeByte(0);
  1197.                 outPacket.encodeLong(0);
  1198.                 outPacket.encodeString("");
  1199.             }
  1200.         }
  1201.         if (mask.isInMask(DBChar.QuestRecordEx)) {
  1202.             outPacket.encodeShort(getQuestManager().getEx().size());
  1203.             for (Quest quest : getQuestManager().getEx()) {
  1204.                 outPacket.encodeInt(quest.getQRKey());
  1205.                 outPacket.encodeString(quest.getQRValue());
  1206.             }
  1207.         }
  1208.         if (mask.isInMask(DBChar.Avatar)) {
  1209.  
  1210.             short size = 0;
  1211.             outPacket.encodeShort(size);
  1212.             for (int i = 0; i < size; i++) {
  1213.                 outPacket.encodeInt(0); // sValue
  1214.                 new AvatarLook().encode(outPacket);
  1215.             }
  1216.         }
  1217.         if (mask.isInMask(DBChar.MapTransfer)) {
  1218.             int size = 0;
  1219.             outPacket.encodeInt(0);
  1220.             for (int i = 0; i < size; i++) {
  1221.                 outPacket.encodeInt(0);
  1222.                 outPacket.encodeInt(0);
  1223.             }
  1224.         }
  1225.         if (mask.isInMask(DBChar.WildHunterInfo)) {
  1226.             if (JobConstants.isWildHunter(getAvatarData().getCharacterStat().getJob())) {
  1227.                 getWildHunterInfo().encode(outPacket); // GW_WildHunterInfo::Decode
  1228.             }
  1229.         }
  1230.         if (mask.isInMask(DBChar.ZeroInfo)) {
  1231.             if (JobConstants.isZero(getAvatarData().getCharacterStat().getJob())) {
  1232.                 if (getZeroInfo() == null) {
  1233.                     initZeroInfo();
  1234.                 }
  1235.                 getZeroInfo().encode(outPacket); // ZeroInfo::Decode
  1236.             }
  1237.         }
  1238.         if (mask.isInMask(DBChar.ShopBuyLimit)) {
  1239.             short size = 0;
  1240.             outPacket.encodeShort(size);
  1241.             for (int i = 0; i < size; i++) {
  1242.                 // Encode shop buy limit
  1243.             }
  1244.         }
  1245.         if (mask.isInMask(DBChar.StolenSkills)) {
  1246.             if (JobConstants.isPhantom(getAvatarData().getCharacterStat().getJob())) {
  1247.                 for (int i = 0; i < 15; i++) {
  1248.                     StolenSkill stolenSkill = getStolenSkillByPosition(i);
  1249.                     outPacket.encodeInt(stolenSkill == null ? 0 : stolenSkill.getSkillid());
  1250.                 }
  1251.             } else {
  1252.                 outPacket.encodeInt(0);
  1253.                 outPacket.encodeInt(0);
  1254.                 outPacket.encodeInt(0);
  1255.                 outPacket.encodeInt(0);
  1256.  
  1257.                 outPacket.encodeInt(0);
  1258.                 outPacket.encodeInt(0);
  1259.                 outPacket.encodeInt(0);
  1260.                 outPacket.encodeInt(0);
  1261.  
  1262.                 outPacket.encodeInt(0);
  1263.                 outPacket.encodeInt(0);
  1264.                 outPacket.encodeInt(0);
  1265.  
  1266.                 outPacket.encodeInt(0);
  1267.                 outPacket.encodeInt(0);
  1268.  
  1269.                 outPacket.encodeInt(0);
  1270.                 outPacket.encodeInt(0);
  1271.             }
  1272.         }
  1273.         if (mask.isInMask(DBChar.ChosenSkills)) {
  1274.             if (JobConstants.isPhantom(getAvatarData().getCharacterStat().getJob())) {
  1275.                 for (int i = 1; i <= 5; i++) { //Shifted by +1 to accomodate the Skill Management Tabs
  1276.                     ChosenSkill chosenSkill = getChosenSkillByPosition(i);
  1277.                     outPacket.encodeInt(chosenSkill == null
  1278.                             ? 0
  1279.                             : isChosenSkillInStolenSkillList(chosenSkill.getSkillId())
  1280.                             ? chosenSkill.getSkillId()
  1281.                             : 0
  1282.                     );
  1283.                 }
  1284.             } else {
  1285.                 for (int i = 0; i < 5; i++) {
  1286.                     outPacket.encodeInt(0);
  1287.                 }
  1288.             }
  1289.         }
  1290.         if (mask.isInMask(DBChar.CharacterPotentialSkill)) {
  1291.             outPacket.encodeShort(getPotentials().size());
  1292.             for (CharacterPotential cp : getPotentials()) {
  1293.                 cp.encode(outPacket);
  1294.             }
  1295.         }
  1296.         if (mask.isInMask(DBChar.SoulCollection)) {
  1297.             short size = 0;
  1298.             outPacket.encodeShort(size);
  1299.             for (int i = 0; i < size; i++) {
  1300.                 outPacket.encodeInt(0); //
  1301.                 outPacket.encodeInt(0); //
  1302.             }
  1303.         }
  1304.         sizee = 0;
  1305.         outPacket.encodeInt(sizee);
  1306.         for (int i = 0; i < sizee; i++) {
  1307.             outPacket.encodeString("");
  1308.             // sub_73A1A0
  1309.             outPacket.encodeInt(0);
  1310.             outPacket.encodeString("");
  1311.             int size = 0;
  1312.             outPacket.encodeInt(size);
  1313.             for (int j = 0; j < size; j++) {
  1314.                 outPacket.encodeByte(0);
  1315.             }
  1316.         }
  1317.         outPacket.encodeByte(0); // idk
  1318.         if (mask.isInMask(DBChar.Character)) {
  1319.             outPacket.encodeInt(0); // honor level, deprecated
  1320.             outPacket.encodeInt(getHonorExp()); // honor exp
  1321.         }
  1322.         if (mask.isInMask(DBChar.Money)) {
  1323.             boolean shouldIEncodeThis = true;
  1324.             outPacket.encodeByte(shouldIEncodeThis);
  1325.             if (shouldIEncodeThis) {
  1326.                 short size = 0;
  1327.                 outPacket.encodeShort(size);
  1328.                 for (int i = 0; i < size; i++) {
  1329.                     short category = 0;
  1330.                     outPacket.encodeShort(category);
  1331.                     short size2 = 0;
  1332.                     outPacket.encodeShort(size2);
  1333.                     for (int i2 = 0; i2 < size2; i2++) {
  1334.                         outPacket.encodeInt(0); // nItemId
  1335.                         outPacket.encodeInt(0); // nCount
  1336.                     }
  1337.                 }
  1338.             } else {
  1339.                 short size2 = 0;
  1340.                 outPacket.encodeShort(size2);
  1341.                 for (int i2 = 0; i2 < size2; i2++) {
  1342.                     outPacket.encodeShort(0); // nCategory
  1343.                     outPacket.encodeInt(0); // nItemId
  1344.                     outPacket.encodeInt(0); // nCount
  1345.                 }
  1346.  
  1347.             }
  1348.         }
  1349.         if (mask.isInMask(DBChar.ReturnEffectInfo)) {
  1350. //            getReturnEffectInfo().encode(outPacket); // ReturnEffectInfo::Decode
  1351.             outPacket.encodeByte(0);
  1352.         }
  1353.         if (mask.isInMask(DBChar.DressUpInfo)) {
  1354.             new DressUpInfo().encode(outPacket); // GW_DressUpInfo::Decode
  1355.         }
  1356.         if (mask.isInMask(DBChar.MonsterCollection)) {
  1357.             outPacket.encodeInt(1);
  1358.             outPacket.encodeInt(0);
  1359.             outPacket.encodeLong(0);
  1360.             outPacket.encodeString("");
  1361.             outPacket.encodeInt(0);
  1362.         }
  1363.         if (mask.isInMask(DBChar.CoreInfo)) {
  1364.             // GW_Core
  1365.             short size = 0;
  1366.             outPacket.encodeShort(size);
  1367.             for (int i = 0; i < size; i++) {
  1368.                 outPacket.encodeShort(0); // nPos
  1369.                 outPacket.encodeInt(0); // nCoreID
  1370.                 outPacket.encodeInt(0); // nLeftCount
  1371.             }
  1372.  
  1373.             size = 0;
  1374.             outPacket.encodeShort(size);
  1375.             for (int i = 0; i < size; i++) {
  1376.                 outPacket.encodeShort(0); // nPos
  1377.                 outPacket.encodeInt(0); // nCoreID
  1378.                 outPacket.encodeInt(0); // nLeftCount
  1379.             }
  1380.         }
  1381.         if (mask.isInMask(DBChar.FarmPotential)) {
  1382.             new FarmPotential().encode(outPacket); // FARM_POTENTIAL::Decode
  1383.         }
  1384.         if (mask.isInMask(DBChar.FarmUserInfo)) {
  1385.             new FarmUserInfo().encode(outPacket); // FarmUserInfo::Decode
  1386.             outPacket.encodeInt(0);
  1387.             outPacket.encodeInt(0);
  1388.         }
  1389.         if (mask.isInMask(DBChar.MemorialCubeInfo)) {
  1390.             new MemorialCubeInfo().encode(outPacket); // MemorialCubeInfo::Decode
  1391.         }
  1392.         if (mask.isInMask(DBChar.LikePoint)) {
  1393.             new LikePoint().encode(outPacket);
  1394.         }
  1395.         if (mask.isInMask(DBChar.RunnerGameRecord)) {
  1396.             new RunnerGameRecord().encode(outPacket); // RunnerGameRecord::Decode
  1397.         }
  1398.         short sizeO = 0;
  1399.         outPacket.encodeShort(sizeO);
  1400.         for (int i = 0; i < sizeO; i++) {
  1401.             outPacket.encodeInt(0);
  1402.             outPacket.encodeString("");
  1403.         }
  1404.         if (mask.isInMask(DBChar.MonsterCollection)) {
  1405.             Set<MonsterCollectionExploration> mces = getAccount().getMonsterCollection().getMonsterCollectionExplorations();
  1406.             outPacket.encodeShort(mces.size());
  1407.             for (MonsterCollectionExploration mce : mces) {
  1408.                 outPacket.encodeInt(mce.getPosition());
  1409.                 outPacket.encodeString(mce.getValue(true));
  1410.             }
  1411.         }
  1412.         boolean farmOnline = false;
  1413.         outPacket.encodeByte(farmOnline);
  1414.         int sizeInt = 0;
  1415.         // CharacterData::DecodeTextEquipInfo
  1416.         outPacket.encodeInt(sizeInt);
  1417.         for (int i = 0; i < sizeInt; i++) {
  1418.             outPacket.encodeInt(0);
  1419.             outPacket.encodeString("");
  1420.         }
  1421.  
  1422.         if (mask.isInMask(DBChar.VisitorLog4)) {
  1423.             // mushy
  1424.             outPacket.encodeByte(1);
  1425.             outPacket.encodeByte(0);
  1426.             outPacket.encodeInt(1);
  1427.             outPacket.encodeInt(0);
  1428.             outPacket.encodeInt(100);
  1429.             outPacket.encodeFT(FileTime.fromType(FileTime.Type.MAX_TIME));
  1430.             outPacket.encodeShort(0);
  1431.             outPacket.encodeShort(0);
  1432.         }
  1433.  
  1434.         if (mask.isInMask(DBChar.Unk4)) {
  1435.             outPacket.encodeByte(0);
  1436.         }
  1437.  
  1438.         if (mask.isInMask(DBChar.Unk)) {
  1439.             outPacket.encodeInt(0);
  1440.             outPacket.encodeInt(0);
  1441.         }
  1442.  
  1443.         if (mask.isInMask(DBChar.CoreAura)) {
  1444.             outPacket.encodeInt(0);
  1445.             outPacket.encodeInt(0);
  1446.             outPacket.encodeInt(0);
  1447.             outPacket.encodeInt(0);
  1448.             outPacket.encodeInt(0);
  1449.  
  1450.             outPacket.encodeInt(0);
  1451.             outPacket.encodeInt(0);
  1452.             outPacket.encodeInt(0);
  1453.             outPacket.encodeInt(0);
  1454.             outPacket.encodeInt(0);
  1455.             outPacket.encodeInt(0);
  1456.  
  1457.             outPacket.encodeInt(0);
  1458.             outPacket.encodeInt(0);
  1459.             outPacket.encodeInt(0);
  1460.             outPacket.encodeInt(0);
  1461.  
  1462.             outPacket.encodeLong(0);
  1463.             outPacket.encodeByte(0);
  1464.  
  1465.             outPacket.encodeByte(1);
  1466.         }
  1467.  
  1468.         if (mask.isInMask(DBChar.EquipExt)) {
  1469.             short size = 0;
  1470.             outPacket.encodeShort(size);
  1471.             for (int i = 0; i < size; i++) {
  1472.                 outPacket.encodeShort(0);
  1473.                 outPacket.encodeShort(0);
  1474.             }
  1475.         }
  1476.  
  1477.         if (mask.isInMask(DBChar.RedLeafInfo)) {
  1478.             // red leaf information
  1479.             outPacket.encodeInt(getAccId());
  1480.             outPacket.encodeInt(getId());
  1481.             outPacket.encodeInt(0);
  1482.             outPacket.encodeInt(0);
  1483.         }
  1484.         outPacket.encodeArr(new byte[32]); // real
  1485.  
  1486.     }
  1487.  
  1488.     @Override
  1489.     public boolean equals(Object other) {
  1490.         return other instanceof Char && ((Char) other).getId() == getId();
  1491.     }
  1492.  
  1493.     @Override
  1494.     public int hashCode() {
  1495.         return Objects.hash(id);
  1496.     }
  1497.  
  1498.     private String getBlessingOfEmpress() {
  1499.         return blessingOfEmpress;
  1500.     }
  1501.  
  1502.     public void setBlessingOfEmpress(String blessingOfEmpress) {
  1503.         this.blessingOfEmpress = blessingOfEmpress;
  1504.     }
  1505.  
  1506.     private String getBlessingOfFairy() {
  1507.         return blessingOfFairy;
  1508.     }
  1509.  
  1510.     public void setBlessingOfFairy(String blessingOfFairy) {
  1511.         this.blessingOfFairy = blessingOfFairy;
  1512.     }
  1513.  
  1514.     public void setCombatOrders(int combatOrders) {
  1515.         this.combatOrders = combatOrders;
  1516.     }
  1517.  
  1518.     public int getCombatOrders() {
  1519.         return combatOrders;
  1520.     }
  1521.  
  1522.     public QuestManager getQuestManager() {
  1523.         if (questManager.getChr() == null) {
  1524.             questManager.setChr(this);
  1525.         }
  1526.         return questManager;
  1527.     }
  1528.  
  1529.     public void setQuests(QuestManager questManager) {
  1530.         this.questManager = questManager;
  1531.     }
  1532.  
  1533.     public List<ItemPot> getItemPots() {
  1534.         return null;
  1535.     }
  1536.  
  1537.     public void setItemPots(List<ItemPot> itemPots) {
  1538.         this.itemPots = itemPots;
  1539.     }
  1540.  
  1541.     public List<Pet> getPets() {
  1542.         return pets;
  1543.     }
  1544.  
  1545.     public void setPets(List<Pet> pets) {
  1546.         this.pets = pets;
  1547.     }
  1548.  
  1549.     public List<FriendRecord> getFriendRecords() {
  1550.         return friendRecords;
  1551.     }
  1552.  
  1553.     public void setFriendRecords(List<FriendRecord> friendRecords) {
  1554.         this.friendRecords = friendRecords;
  1555.     }
  1556.  
  1557.     public long getMoney() {
  1558.         return getAvatarData().getCharacterStat().getMoney();
  1559.     }
  1560.  
  1561.     public List<ExpConsumeItem> getExpConsumeItems() {
  1562.         return expConsumeItems;
  1563.     }
  1564.  
  1565.     public void setExpConsumeItems(List<ExpConsumeItem> expConsumeItems) {
  1566.         this.expConsumeItems = expConsumeItems;
  1567.     }
  1568.  
  1569.     public List<MonsterBattleMobInfo> getMonsterBattleMobInfos() {
  1570.         return monsterBattleMobInfos;
  1571.     }
  1572.  
  1573.     public void setMonsterBattleMobInfos(List<MonsterBattleMobInfo> monsterBattleMobInfos) {
  1574.         this.monsterBattleMobInfos = monsterBattleMobInfos;
  1575.     }
  1576.  
  1577.     public MonsterBattleLadder getMonsterBattleLadder() {
  1578.         return monsterBattleLadder;
  1579.     }
  1580.  
  1581.     public void setMonsterBattleLadder(MonsterBattleLadder monsterBattleLadder) {
  1582.         this.monsterBattleLadder = monsterBattleLadder;
  1583.     }
  1584.  
  1585.     public MonsterBattleRankInfo getMonsterBattleRankInfo() {
  1586.         return monsterBattleRankInfo;
  1587.     }
  1588.  
  1589.     public void setMonsterBattleRankInfo(MonsterBattleRankInfo monsterBattleRankInfo) {
  1590.         this.monsterBattleRankInfo = monsterBattleRankInfo;
  1591.     }
  1592.  
  1593.     public List<Inventory> getInventories() {
  1594.         return new ArrayList<>(Arrays.asList(getEquippedInventory(), getEquipInventory(),
  1595.                 getConsumeInventory(), getEtcInventory(), getInstallInventory(), getCashInventory()));
  1596.     }
  1597.  
  1598.     public Inventory getInventoryByType(InvType invType) {
  1599.         switch (invType) {
  1600.             case EQUIPPED:
  1601.                 return getEquippedInventory();
  1602.             case EQUIP:
  1603.                 return getEquipInventory();
  1604.             case CONSUME:
  1605.                 return getConsumeInventory();
  1606.             case ETC:
  1607.                 return getEtcInventory();
  1608.             case INSTALL:
  1609.                 return getInstallInventory();
  1610.             case CASH:
  1611.                 return getCashInventory();
  1612.             default:
  1613.                 return null;
  1614.         }
  1615.     }
  1616.  
  1617.     public Client getClient() {
  1618.         return client;
  1619.     }
  1620.  
  1621.     public void setClient(Client client) {
  1622.         this.client = client;
  1623.     }
  1624.  
  1625.     public int getFieldID() {
  1626.         return (int) getAvatarData().getCharacterStat().getPosMap();
  1627.     }
  1628.  
  1629.     private void setFieldID(int fieldID) {
  1630.         getAvatarData().getCharacterStat().setPosMap(fieldID);
  1631.     }
  1632.  
  1633.     public Position getPosition() {
  1634.         return position;
  1635.     }
  1636.  
  1637.     public void setPosition(Position position) {
  1638.         this.position = position;
  1639.     }
  1640.  
  1641.     public void setField(Field field) {
  1642.         this.field = field;
  1643.         setFieldID(field.getId());
  1644.     }
  1645.  
  1646.     public Field getField() {
  1647.         return field;
  1648.     }
  1649.  
  1650.     /**
  1651.      * Sets the job of this Char with a given id. Does nothing if the id is invalid.
  1652.      * If it is valid, will set this Char's job, add all Skills that the job should have by default,
  1653.      * and sends the info to the client.
  1654.      *
  1655.      * @param id
  1656.      */
  1657.     public void setJob(int id) {
  1658.         JobConstants.JobEnum job = JobConstants.JobEnum.getJobById((short) id);
  1659.         if (job == null) {
  1660.             return;
  1661.         }
  1662.         getAvatarData().getCharacterStat().setJob(id);
  1663.         setJobHandler(JobManager.getJobById((short) id, this));
  1664.         List<Skill> skills = SkillData.getSkillsByJob((short) id);
  1665.         skills.forEach(skill -> addSkill(skill, true));
  1666.         getClient().write(WvsContext.changeSkillRecordResult(skills, true, false, false, false));
  1667.         notifyChanges();
  1668.     }
  1669.  
  1670.     public short getJob() {
  1671.         return getAvatarData().getCharacterStat().getJob();
  1672.     }
  1673.  
  1674.     /**
  1675.      * Sets the SP to the current job level.
  1676.      *
  1677.      * @param num The new SP amount.
  1678.      */
  1679.     public void setSpToCurrentJob(int num) {
  1680.         if (JobConstants.isExtendSpJob(getJob())) {
  1681.             byte jobLevel = (byte) JobConstants.getJobLevel(getJob());
  1682.             getAvatarData().getCharacterStat().getExtendSP().setSpToJobLevel(jobLevel, num);
  1683.         } else {
  1684.             getAvatarData().getCharacterStat().setSp(num);
  1685.         }
  1686.     }
  1687.  
  1688.     /**
  1689.      * Sets the SP to the job level according to the current level.
  1690.      *
  1691.      * @param num The amount of SP to add
  1692.      */
  1693.     public void addSpToJobByCurrentLevel(int num) {
  1694.         CharacterStat cs = getAvatarData().getCharacterStat();
  1695.         if (JobConstants.isExtendSpJob(getJob())) {
  1696.             byte jobLevel = (byte) JobConstants.getJobLevelByCharLevel(getJob(), getLevel());
  1697.             num += cs.getExtendSP().getSpByJobLevel(jobLevel);
  1698.             getAvatarData().getCharacterStat().getExtendSP().setSpToJobLevel(jobLevel, num);
  1699.         } else {
  1700.             num += cs.getSp();
  1701.             getAvatarData().getCharacterStat().setSp(num);
  1702.         }
  1703.     }
  1704.  
  1705.     public Set<Skill> getSkills() {
  1706.         return skills;
  1707.     }
  1708.  
  1709.     public void setSkills(Set<Skill> skills) {
  1710.         this.skills = skills;
  1711.     }
  1712.  
  1713.     /**
  1714.      * Adds a {@link Skill} to this Char. Changes the old Skill if the Char already has a Skill
  1715.      * with the same id. Removes the skill if the given skill's id is 0.
  1716.      *
  1717.      * @param skill The Skill this Char should get.
  1718.      */
  1719.     public void addSkill(Skill skill) {
  1720.         addSkill(skill, false);
  1721.     }
  1722.  
  1723.     /**
  1724.      * Adds a {@link Skill} to this Char. Changes the old Skill if the Char already has a Skill
  1725.      * with the same id. Removes the skill if the given skill's id is 0.
  1726.      *
  1727.      * @param skill The Skill this Char should get.
  1728.      * @param addRegardlessOfLevel if this is true, the skill will not be removed from the char, even if the cur level
  1729.      *                             of the given skill is 0.
  1730.      */
  1731.     public void addSkill(Skill skill, boolean addRegardlessOfLevel) {
  1732.         if (!addRegardlessOfLevel && skill.getCurrentLevel() == 0) {
  1733.             removeSkill(skill.getSkillId());
  1734.             return;
  1735.         }
  1736.         skill.setCharId(getId());
  1737.         boolean isPassive = SkillConstants.isPassiveSkill(skill.getSkillId());
  1738.         boolean isChanged;
  1739.         if (getSkills().stream().noneMatch(s -> s.getSkillId() == skill.getSkillId())) {
  1740.             getSkills().add(skill);
  1741.             isChanged = true;
  1742.         } else {
  1743.             Skill oldSkill = getSkill(skill.getSkillId());
  1744.             isChanged = oldSkill.getCurrentLevel() != skill.getCurrentLevel();
  1745.             if (isPassive && isChanged) {
  1746.                 removeFromBaseStatCache(oldSkill);
  1747.             }
  1748.             oldSkill.setCurrentLevel(skill.getCurrentLevel());
  1749.             oldSkill.setMasterLevel(skill.getMasterLevel());
  1750.         }
  1751.         // Change cache accordingly
  1752.         if (isPassive && isChanged) {
  1753.             addToBaseStatCache(skill);
  1754.         }
  1755.     }
  1756.  
  1757.     /**
  1758.      * Removes a Skill from this Char.
  1759.      * @param skillID the id of the skill that should be removed
  1760.      */
  1761.     public void removeSkill(int skillID) {
  1762.         Skill skill = Util.findWithPred(getSkills(), s -> s.getSkillId() == skillID);
  1763.         if (skill != null) {
  1764.             if (SkillConstants.isPassiveSkill(skillID)) {
  1765.                 removeFromBaseStatCache(skill);
  1766.             }
  1767.             getSkills().remove(skill);
  1768.  
  1769.         }
  1770.     }
  1771.  
  1772.     /**
  1773.      * Removes a Skill from this Char.
  1774.      * Sends change skill record to remove the skill from the client.
  1775.      * @param skillID the id of the skill that should be removed
  1776.      */
  1777.     public void removeSkillAndSendPacket(int skillID) {
  1778.         Skill skill = getSkill(skillID);
  1779.         if (skill != null) {
  1780.             removeSkill(skillID);
  1781.             skill.setCurrentLevel(-1);
  1782.             skill.setMasterLevel(-1);
  1783.             write(WvsContext.changeSkillRecordResult(Collections.singletonList(skill), true, false, false, false));
  1784.         }
  1785.     }
  1786.  
  1787.     /**
  1788.      * Initializes the BaseStat cache, by going through all the needed passive stat changers.
  1789.      */
  1790.     public void initBaseStats() {
  1791.         getBaseStats().clear();
  1792.         Map<BaseStat, Long> stats = getBaseStats();
  1793.         stats.put(BaseStat.cr, 5L);
  1794.         stats.put(BaseStat.minCd, 20L);
  1795.         stats.put(BaseStat.maxCd, 50L);
  1796.         stats.put(BaseStat.pdd, 9L);
  1797.         stats.put(BaseStat.mdd, 9L);
  1798.         stats.put(BaseStat.acc, 11L);
  1799.         stats.put(BaseStat.eva, 8L);
  1800.         stats.put(BaseStat.buffTimeR, 100L);
  1801.         getSkills().stream().filter(skill -> SkillConstants.isPassiveSkill_NoPsdSkillsCheck(skill.getSkillId())).
  1802.                 forEach(this::addToBaseStatCache);
  1803.     }
  1804.  
  1805.     /**
  1806.      * Adds a Skill's info to the current base stat cache.
  1807.      *
  1808.      * @param skill The skill to add
  1809.      */
  1810.     public void addToBaseStatCache(Skill skill) {
  1811.         SkillInfo si = SkillData.getSkillInfoById(skill.getSkillId());
  1812.         if(SkillConstants.isPassiveSkill(skill.getSkillId())) {
  1813.             Map<BaseStat, Integer> stats = si.getBaseStatValues(this, skill.getCurrentLevel());
  1814.             stats.forEach(this::addBaseStat);
  1815.         }
  1816.         if (si.isPsd() && si.getSkillStatInfo().containsKey(SkillStat.coolTimeR)) {
  1817.             for(int psdSkill : si.getPsdSkills()) {
  1818.                 getHyperPsdSkillsCooltimeR().put(psdSkill, si.getValue(SkillStat.coolTimeR, 1));
  1819.             }
  1820.         }
  1821.     }
  1822.  
  1823.     /**
  1824.      * Removes a Skill's info from the current base stat cache.
  1825.      *
  1826.      * @param skill The skill to remove
  1827.      */
  1828.     public void removeFromBaseStatCache(Skill skill) {
  1829.         SkillInfo si = SkillData.getSkillInfoById(skill.getSkillId());
  1830.         Map<BaseStat, Integer> stats = si.getBaseStatValues(this, skill.getCurrentLevel());
  1831.         stats.forEach(this::removeBaseStat);
  1832.     }
  1833.  
  1834.     /**
  1835.      * Returns whether or not this Char has a {@link Skill} with a given id.
  1836.      *
  1837.      * @param id The id of the Skill.
  1838.      * @return Whether or not this Char has a Skill with the given id.
  1839.      */
  1840.     public boolean hasSkill(int id) {
  1841.         return getSkills().stream().anyMatch(s -> s.getSkillId() == id) && getSkill(id, false).getCurrentLevel() > 0;
  1842.     }
  1843.  
  1844.     /**
  1845.      * Gets a {@link Skill} of this Char with a given id.
  1846.      *
  1847.      * @param id The id of the requested Skill.
  1848.      * @return The Skill corresponding to the given id of this Char, or null if there is none.
  1849.      */
  1850.     public Skill getSkill(int id) {
  1851.         return getSkill(id, false);
  1852.     }
  1853.  
  1854.     /**
  1855.      * Gets a {@link Skill} with a given ID. If <code>createIfNull</code> is true, creates the Skill
  1856.      * if it doesn't exist yet.
  1857.      * If it is false, will return null if this Char does not have the given Skill.
  1858.      *
  1859.      * @param id           The id of the requested Skill.
  1860.      * @param createIfNull Whether or not this method should create the Skill if it doesn't exist.
  1861.      * @return The Skill that the Char has, or <code>null</code> if there is no such skill and
  1862.      * <code>createIfNull</code> is false.
  1863.      */
  1864.     public Skill getSkill(int id, boolean createIfNull) {
  1865.         for (Skill s : getSkills()) {
  1866.             if (s.getSkillId() == id) {
  1867.                 return s;
  1868.             }
  1869.         }
  1870.         return createIfNull ? createAndReturnSkill(id) : null;
  1871.     }
  1872.  
  1873.     public int getSkillLevel(int skillID) {
  1874.         Skill skill = getSkill(skillID);
  1875.         if (skill != null) {
  1876.             return skill.getCurrentLevel();
  1877.         }
  1878.         return 0;
  1879.     }
  1880.  
  1881.     public int getRemainRecipeUseCount(int recipeID) {
  1882.         if (SkillConstants.isMakingSkillRecipe(recipeID)) {
  1883.             return getSkillLevel(recipeID);
  1884.         }
  1885.         return 0;
  1886.     }
  1887.  
  1888.     /**
  1889.      * Creates a new {@link Skill} for this Char.
  1890.      *
  1891.      * @param id The skillID of the Skill to be created.
  1892.      * @return The new Skill.
  1893.      */
  1894.     private Skill createAndReturnSkill(int id) {
  1895.         Skill skill = SkillData.getSkillDeepCopyById(id);
  1896.         addSkill(skill);
  1897.         return skill;
  1898.     }
  1899.  
  1900.     public void setStat(Stat charStat, int amount) {
  1901.         CharacterStat cs = getAvatarData().getCharacterStat();
  1902.         switch (charStat) {
  1903.             case str:
  1904.                 cs.setStr(amount);
  1905.                 break;
  1906.             case dex:
  1907.                 cs.setDex(amount);
  1908.                 break;
  1909.             case inte:
  1910.                 cs.setInt(amount);
  1911.                 break;
  1912.             case luk:
  1913.                 cs.setLuk(amount);
  1914.                 break;
  1915.             case hp:
  1916.                 cs.setHp(amount);
  1917.                 break;
  1918.             case mhp:
  1919.                 cs.setMaxHp(amount);
  1920.                 if (JobConstants.isDemonAvenger(getJob())) {
  1921.                     ((Demon) getJobHandler()).sendHpUpdate();
  1922.                 }
  1923.                 break;
  1924.             case mp:
  1925.                 cs.setMp(amount);
  1926.                 break;
  1927.             case mmp:
  1928.                 cs.setMaxMp(amount);
  1929.                 break;
  1930.             case ap:
  1931.                 cs.setAp(amount);
  1932.                 break;
  1933.             case level:
  1934.                 cs.setLevel(amount);
  1935.                 notifyChanges();
  1936.                 break;
  1937.             case skin:
  1938.                 cs.setSkin(amount);
  1939.                 break;
  1940.             case face:
  1941.                 cs.setFace(amount);
  1942.                 break;
  1943.             case hair:
  1944.                 cs.setHair(amount);
  1945.                 break;
  1946.             case pop:
  1947.                 cs.setPop(amount);
  1948.                 break;
  1949.             case charismaEXP:
  1950.                 cs.setCharismaExp(amount);
  1951.                 break;
  1952.             case charmEXP:
  1953.                 cs.setCharmExp(amount);
  1954.                 break;
  1955.             case craftEXP:
  1956.                 cs.setCraftExp(amount);
  1957.                 break;
  1958.             case insightEXP:
  1959.                 cs.setInsightExp(amount);
  1960.                 break;
  1961.             case senseEXP:
  1962.                 cs.setSenseExp(amount);
  1963.                 break;
  1964.             case willEXP:
  1965.                 cs.setWillExp(amount);
  1966.                 break;
  1967.             case fatigue:
  1968.                 cs.setFatigue(amount);
  1969.                 break;
  1970.         }
  1971.     }
  1972.  
  1973.     /**
  1974.      * Notifies all groups (such as party, guild) about all your changes, such as level and job.
  1975.      */
  1976.     private void notifyChanges() {
  1977.         Party party = getParty();
  1978.         if (party != null) {
  1979.             party.updatePartyMemberInfoByChr(this);
  1980.             party.broadcast(WvsContext.partyResult(PartyResult.userMigration(party)));
  1981.         }
  1982.         Guild guild = getGuild();
  1983.         if (guild != null) {
  1984.             GuildMember gm = guild.getMemberByCharID(getId());
  1985.             gm.setLevel(getLevel());
  1986.             gm.setJob(getJob());
  1987.             guild.broadcast(WvsContext.guildResult(GuildResult.changeLevelOrJob(guild, gm)));
  1988.             Alliance ally = guild.getAlliance();
  1989.             if (ally != null) {
  1990.                 ally.broadcast(WvsContext.allianceResult(AllianceResult.changeLevelOrJob(ally, guild, gm)));
  1991.             }
  1992.         }
  1993.     }
  1994.  
  1995.     /**
  1996.      * Gets a raw Stat from this Char, unaffected by things such as equips and skills.
  1997.      *
  1998.      * @param charStat The requested Stat
  1999.      * @return the requested stat's value
  2000.      */
  2001.     public int getStat(Stat charStat) {
  2002.         CharacterStat cs = getAvatarData().getCharacterStat();
  2003.         switch (charStat) {
  2004.             case str:
  2005.                 return cs.getStr();
  2006.             case dex:
  2007.                 return cs.getDex();
  2008.             case inte:
  2009.                 return cs.getInt();
  2010.             case luk:
  2011.                 return cs.getLuk();
  2012.             case hp:
  2013.                 return cs.getHp();
  2014.             case mhp:
  2015.                 return cs.getMaxHp();
  2016.             case mp:
  2017.                 return cs.getMp();
  2018.             case mmp:
  2019.                 return cs.getMaxMp();
  2020.             case ap:
  2021.                 return cs.getAp();
  2022.             case level:
  2023.                 return cs.getLevel();
  2024.             case skin:
  2025.                 return cs.getSkin();
  2026.             case face:
  2027.                 return cs.getFace();
  2028.             case hair:
  2029.                 return cs.getHair();
  2030.             case pop:
  2031.                 return cs.getPop();
  2032.             case charismaEXP:
  2033.                 return cs.getCharismaExp();
  2034.             case charmEXP:
  2035.                 return cs.getCharmExp();
  2036.             case craftEXP:
  2037.                 return cs.getCraftExp();
  2038.             case insightEXP:
  2039.                 return cs.getInsightExp();
  2040.             case senseEXP:
  2041.                 return cs.getSenseExp();
  2042.             case willEXP:
  2043.                 return cs.getWillExp();
  2044.             case fatigue:
  2045.                 return cs.getFatigue();
  2046.         }
  2047.         return -1;
  2048.     }
  2049.  
  2050.     /**
  2051.      * Adds a Stat to this Char.
  2052.      *
  2053.      * @param charStat which Stat to add
  2054.      * @param amount   the amount of Stat to add
  2055.      */
  2056.     public void addStat(Stat charStat, int amount) {
  2057.         setStat(charStat, getStat(charStat) + amount);
  2058.     }
  2059.  
  2060.     /**
  2061.      * Adds a Stat to this Char, and immediately sends the packet to the client notifying the change.
  2062.      *
  2063.      * @param charStat which Stat to change
  2064.      * @param amount   the amount of Stat to add
  2065.      */
  2066.     public void addStatAndSendPacket(Stat charStat, int amount) {
  2067.         setStatAndSendPacket(charStat, getStat(charStat) + amount);
  2068.     }
  2069.  
  2070.     /**
  2071.      * Adds a Stat to this Char, and immediately sends the packet to the client notifying the change.
  2072.      *
  2073.      * @param charStat which Stat to change
  2074.      * @param value    the value of Stat to set
  2075.      */
  2076.     public void setStatAndSendPacket(Stat charStat, int value) {
  2077.         setStat(charStat, value);
  2078.         Map<Stat, Object> stats = new HashMap<>();
  2079.         switch (charStat) {
  2080.             case level:
  2081.             case skin:
  2082.             case fatigue:
  2083.                 stats.put(charStat, (byte) getStat(charStat));
  2084.                 break;
  2085.             case str:
  2086.             case dex:
  2087.             case inte:
  2088.             case luk:
  2089.             case ap:
  2090.             case subJob:
  2091.                 stats.put(charStat, (short) getStat(charStat));
  2092.                 break;
  2093.             case hp:
  2094.             case mhp:
  2095.             case mp:
  2096.             case mmp:
  2097.             case face:
  2098.             case hair:
  2099.             case pop:
  2100.             case charismaEXP:
  2101.             case insightEXP:
  2102.             case willEXP:
  2103.             case craftEXP:
  2104.             case senseEXP:
  2105.             case charmEXP:
  2106.             case eventPoints:
  2107.                 stats.put(charStat, getStat(charStat));
  2108.                 break;
  2109.         }
  2110.         write(WvsContext.statChanged(stats));
  2111.     }
  2112.  
  2113.     /**
  2114.      * Adds a certain amount of money to the current character. Also sends the
  2115.      * packet to update the client's state.
  2116.      *
  2117.      * @param amount The amount of money to add. May be negative.
  2118.      */
  2119.     public void addMoney(long amount) {
  2120.         CharacterStat cs = getAvatarData().getCharacterStat();
  2121.         long money = cs.getMoney();
  2122.         long newMoney = money + amount;
  2123.         if (newMoney >= 0) {
  2124.             newMoney = Math.min(GameConstants.MAX_MONEY, newMoney);
  2125.             Map<Stat, Object> stats = new HashMap<>();
  2126.             cs.setMoney(newMoney);
  2127.             stats.put(Stat.money, newMoney);
  2128.             write(WvsContext.statChanged(stats));
  2129.         }
  2130.     }
  2131.  
  2132.     /**
  2133.      * The same as addMoney, but negates the amount.
  2134.      *
  2135.      * @param amount The money to deduct. May be negative.
  2136.      */
  2137.     public void deductMoney(long amount) {
  2138.         addMoney(-amount);
  2139.     }
  2140.  
  2141.     public Position getOldPosition() {
  2142.         return oldPosition;
  2143.     }
  2144.  
  2145.     public void setOldPosition(Position oldPosition) {
  2146.         this.oldPosition = oldPosition;
  2147.     }
  2148.  
  2149.     public void setMoveAction(byte moveAction) {
  2150.         this.moveAction = moveAction;
  2151.     }
  2152.  
  2153.     public byte getMoveAction() {
  2154.         return moveAction;
  2155.     }
  2156.  
  2157.     /**
  2158.      * Sends a message to this Char through the ScriptProgress packet.
  2159.      *
  2160.      * @param msg The message to display.
  2161.      */
  2162.     public void chatScriptMessage(String msg) {
  2163.         write(UserPacket.scriptProgressMessage(msg));
  2164.     }
  2165.  
  2166.     /**
  2167.      * Sends a message to this Char with a default colour {@link ChatType#SystemNotice}.
  2168.      *
  2169.      * @param msg The message to display.
  2170.      */
  2171.     public void chatMessage(String msg) {
  2172.         chatMessage(SystemNotice, msg);
  2173.     }
  2174.  
  2175.     /**
  2176.      * Sends a formatted message to this Char with a default color {@link ChatType#SystemNotice}.
  2177.      * @param msg The message to display
  2178.      * @param args The format arguments
  2179.      */
  2180.     public void chatMessage(String msg, Object... args) {
  2181.         chatMessage(SystemNotice, msg, args);
  2182.     }
  2183.  
  2184.     /**
  2185.      * Sends a formatted message to this Char with a given {@link ChatType colour}.
  2186.      *
  2187.      * @param clr The Colour this message should be in.
  2188.      * @param msg The message to display.
  2189.      * @param args The format arguments
  2190.      */
  2191.     public void chatMessage(ChatType clr, String msg, Object... args) {
  2192.         write(UserLocal.chatMsg(clr, String.format(msg, args)));
  2193.     }
  2194.  
  2195.     /**
  2196.      * Sends a message to this Char with a given {@link ChatType colour}.
  2197.      *
  2198.      * @param clr The Colour this message should be in.
  2199.      * @param msg The message to display.
  2200.      */
  2201.     public void chatMessage(ChatType clr, String msg) {
  2202.         getClient().write(UserLocal.chatMsg(clr, msg));
  2203.     }
  2204.  
  2205.     /**
  2206.      * Sends a message to the character if the debug config flag is turned on.
  2207.      *
  2208.      * @param message message to send
  2209.      */
  2210.     public void dbgChatMsg(String message) {
  2211.         if (ServerConfig.DEBUG_MODE)
  2212.             chatMessage(message);
  2213.     }
  2214.  
  2215.     /**
  2216.      * Unequips an {@link Item}. Ensures that the hairEquips and both inventories get updated.
  2217.      *
  2218.      * @param item The Item to equip.
  2219.      */
  2220.     public void unequip(Item item) {
  2221.         AvatarLook al = getAvatarData().getAvatarLook();
  2222.         int itemID = item.getItemId();
  2223.         getInventoryByType(EQUIPPED).removeItem(item);
  2224.         getInventoryByType(EQUIP).addItem(item);
  2225.         al.removeItem(itemID);
  2226.         byte maskValue = AvatarModifiedMask.AvatarLook.getVal();
  2227.         getField().broadcastPacket(UserRemote.avatarModified(this, maskValue, (byte) 0), this);
  2228.         if (getTemporaryStatManager().hasStat(SoulMP) && ItemConstants.isWeapon(item.getItemId())) {
  2229.             getTemporaryStatManager().removeStat(SoulMP, false);
  2230.             getTemporaryStatManager().removeStat(FullSoulMP, false);
  2231.             getTemporaryStatManager().sendResetStatPacket();
  2232.         }
  2233.         List<Skill> skills = new ArrayList<>();
  2234.         for (ItemSkill itemSkill : ItemData.getEquipById(item.getItemId()).getItemSkills()) {
  2235.             Skill skill = getSkill(itemSkill.getSkill());
  2236.             skill.setCurrentLevel(0);
  2237.             removeSkill(itemSkill.getSkill());
  2238.             skill.setCurrentLevel(-1); // workaround to remove skill from window without a cc
  2239.             skills.add(skill);
  2240.         }
  2241.         if (skills.size() > 0) {
  2242.             getClient().write(WvsContext.changeSkillRecordResult(skills, true, false, false, false));
  2243.         }
  2244.         int equippedSummonSkill = ItemConstants.getEquippedSummonSkillItem(item.getItemId(), getJob());
  2245.         if (equippedSummonSkill != 0) {
  2246.             getField().removeSummon(equippedSummonSkill, getId());
  2247.  
  2248.             getTemporaryStatManager().removeStatsBySkill(equippedSummonSkill);
  2249.             getTemporaryStatManager().removeStatsBySkill(getTemporaryStatManager().getOption(RepeatEffect).rOption);
  2250.         }
  2251.         if (JobConstants.isDemonAvenger(getJob())) {
  2252.             ((Demon) getJobHandler()).sendHpUpdate();
  2253.         }
  2254.         if (ItemConstants.isAndroid(itemID) || ItemConstants.isMechanicalHeart(itemID)) {
  2255.             if (getAndroid() != null) {
  2256.                 getField().removeLife(getAndroid());
  2257.             }
  2258.             setAndroid(null);
  2259.         }
  2260.     }
  2261.  
  2262.     /**
  2263.      * Equips an {@link Item}. Ensures that the hairEquips and both inventories get updated.
  2264.      *
  2265.      * @param item The Item to equip.
  2266.      */
  2267.     public boolean equip(Item item) {
  2268.         Equip equip = (Equip) item;
  2269.         if (equip.hasSpecialAttribute(EquipSpecialAttribute.Vestige)) {
  2270.             return false;
  2271.         }
  2272.         if (equip.isEquipTradeBlock()) {
  2273.             equip.setTradeBlock(true);
  2274.             equip.setEquipTradeBlock(false);
  2275.             equip.setEquippedDate(FileTime.currentTime());
  2276.             equip.addAttribute(EquipAttribute.Untradable);
  2277.         }
  2278.         AvatarLook al = getAvatarData().getAvatarLook();
  2279.         int itemID = item.getItemId();
  2280.         getInventoryByType(EQUIP).removeItem(item);
  2281.         getInventoryByType(EQUIPPED).addItem(item);
  2282.         List<Integer> hairEquips = getAvatarData().getAvatarLook().getHairEquips();
  2283.         if (item.getBagIndex() < BodyPart.APBase.getVal() || item.getBagIndex() > BodyPart.APEnd.getVal()){
  2284.             // only add if not part of your own body
  2285.             if (ItemConstants.isWeapon(itemID)) {
  2286.                 al.setWeaponId(itemID);
  2287.             }
  2288.             if (!hairEquips.contains(itemID)) {
  2289.                 hairEquips.add(itemID);
  2290.             }
  2291.         }
  2292.         if (!equip.hasAttribute(EquipAttribute.NoNonCombatStatGain) && equip.getCharmEXP() != 0) {
  2293.             addStatAndSendPacket(Stat.charmEXP, equip.getCharmEXP());
  2294.             equip.addAttribute(EquipAttribute.NoNonCombatStatGain);
  2295.         }
  2296.         List<Skill> skills = new ArrayList<>();
  2297.         for (ItemSkill itemSkill : ItemData.getEquipById(equip.getItemId()).getItemSkills()) {
  2298.             Skill skill = SkillData.getSkillDeepCopyById(itemSkill.getSkill());
  2299.             byte slv = itemSkill.getSlv();
  2300.             // support for Tower of Oz rings
  2301.             if (equip.getLevel() > 0) {
  2302.                 slv = (byte) Math.min(equip.getLevel(), skill.getMaxLevel());
  2303.             }
  2304.             skill.setCurrentLevel(slv);
  2305.             skills.add(skill);
  2306.             addSkill(skill);
  2307.         }
  2308.         if (skills.size() > 0) {
  2309.             getClient().write(WvsContext.changeSkillRecordResult(skills, true, false, false, false));
  2310.         }
  2311.         int equippedSummonSkill = ItemConstants.getEquippedSummonSkillItem(equip.getItemId(), getJob());
  2312.         if (equippedSummonSkill != 0) {
  2313.             getJobHandler().handleSkill(getClient(), equippedSummonSkill, (byte) 1, null);
  2314.         }
  2315.         byte maskValue = AvatarModifiedMask.AvatarLook.getVal();
  2316.         getField().broadcastPacket(UserRemote.avatarModified(this, maskValue, (byte) 0), this);
  2317.         initSoulMP();
  2318.         if (JobConstants.isDemonAvenger(getJob())) {
  2319.             ((Demon) getJobHandler()).sendHpUpdate();
  2320.         }
  2321.         // check android status
  2322.         if (ItemConstants.isAndroid(itemID) || ItemConstants.isMechanicalHeart(itemID)) {
  2323.             initAndroid(true);
  2324.             if (getAndroid() != null) {
  2325.                 getField().spawnLife(getAndroid(), null);
  2326.             }
  2327.         }
  2328.         return true;
  2329.     }
  2330.  
  2331.     public TemporaryStatManager getTemporaryStatManager() {
  2332.         return temporaryStatManager;
  2333.     }
  2334.  
  2335.     public void setTemporaryStatManager(TemporaryStatManager temporaryStatManager) {
  2336.         this.temporaryStatManager = temporaryStatManager;
  2337.     }
  2338.  
  2339.     public GachaponManager getGachaponManager() {
  2340.         return gachaponManager;
  2341.     }
  2342.  
  2343.     public void setId(int id) {
  2344.         this.id = id;
  2345.     }
  2346.  
  2347.     public void setJobHandler(Job jobHandler) {
  2348.         this.jobHandler = jobHandler;
  2349.     }
  2350.  
  2351.     public Job getJobHandler() {
  2352.         return jobHandler;
  2353.     }
  2354.  
  2355.     public FuncKeyMap getFuncKeyMap() {
  2356.         return funcKeyMaps.get(0);
  2357.     }
  2358.  
  2359.     public List<FuncKeyMap> getFuncKeyMaps() {
  2360.         return funcKeyMaps;
  2361.     }
  2362.  
  2363.     public void initFuncKeyMaps(int keySettingType, boolean beastTamer) {
  2364.         int amount = beastTamer ? 5 : 1;
  2365.         for (int i = 0; i < amount; i++) {
  2366.             FuncKeyMap funcKeyMap = FuncKeyMap.getDefaultMapping(keySettingType);
  2367.             funcKeyMaps.add(funcKeyMap);
  2368.         }
  2369.     }
  2370.  
  2371.     /**
  2372.      * Creates a {@link Rect} with regard to this character. Adds all values to this Char's
  2373.      * position.
  2374.      *
  2375.      * @param rect The rectangle to use.
  2376.      * @return The new rectangle.
  2377.      */
  2378.     public Rect getRectAround(Rect rect) {
  2379.         int x = getPosition().getX();
  2380.         int y = getPosition().getY();
  2381.         return new Rect(x + rect.getLeft(), y + rect.getTop(), x + rect.getRight(), y + rect.getBottom());
  2382.     }
  2383.  
  2384.     /**
  2385.      * Returns the Equip equipped at a certain {@link BodyPart}.
  2386.      *
  2387.      * @param bodyPart The requested bodyPart.
  2388.      * @return The Equip corresponding to <code>bodyPart</code>. Null if there is none.
  2389.      */
  2390.     public Item getEquippedItemByBodyPart(BodyPart bodyPart) {
  2391.         List<Item> items = getEquippedInventory().getItemsByBodyPart(bodyPart);
  2392.         return items.size() > 0 ? items.get(0) : null;
  2393.     }
  2394.  
  2395.     public boolean isLeft() {
  2396.         return moveAction > 0 && (moveAction % 2) == 1;
  2397.     }
  2398.  
  2399.     public MarriageRecord getMarriageRecord() {
  2400.         return marriageRecord;
  2401.     }
  2402.  
  2403.     public void setMarriageRecord(MarriageRecord marriageRecord) {
  2404.         this.marriageRecord = marriageRecord;
  2405.     }
  2406.  
  2407.     /**
  2408.      * Returns a {@link Field} based on the current {@link FieldInstanceType} of this Char (channel,
  2409.      * expedition,
  2410.      * party or solo).
  2411.      *
  2412.      * @return The Field corresponding to the current FieldInstanceType.
  2413.      */
  2414.     public Field getOrCreateFieldByCurrentInstanceType(int fieldID) {
  2415.         Field res;
  2416.         if (getInstance() == null) {
  2417.             System.out.print("get instance is null\r\n");
  2418.             res = getClient().getChannelInstance().getField(fieldID);
  2419.         } else {
  2420.             System.out.print("get instance isnt null\r\n");
  2421.             res = getInstance().getField(fieldID);
  2422.             res.setRuneStone(null);
  2423.         }
  2424.         return res;
  2425.     }
  2426.  
  2427.     /**
  2428.      * Warps this Char to a given field at the starting portal.
  2429.      *
  2430.      * @param fieldId the ID of the field to warp to
  2431.      */
  2432.     public void warp(int fieldId) {
  2433.         warp(getOrCreateFieldByCurrentInstanceType(fieldId));
  2434.     }
  2435.  
  2436.     /**
  2437.      * Warps this Char to a given field at the given portal. If the portal doesn't exist, takes the starting portal.
  2438.      *
  2439.      * @param fieldId the ID of the field to warp to
  2440.      * @param portalId the ID of the portal where the Char should spawn
  2441.      */
  2442.     public void warp(int fieldId, int portalId) {
  2443.         Field field = getOrCreateFieldByCurrentInstanceType(fieldId);
  2444.         Portal portal = field.getPortalByID(portalId);
  2445.         if (portal == null) {
  2446.             portal = field.getDefaultPortal();
  2447.         }
  2448.         warp(field, portal);
  2449.     }
  2450.  
  2451.     /**
  2452.      * Warps this character to a given field, at the starting position.
  2453.      * See {@link #warp(Field, Portal) warp}.
  2454.      *
  2455.      * @param toField The field to warp to.
  2456.      */
  2457.     public void warp(Field toField) {
  2458.         warp(toField, toField.getPortalByName("sp"), false, true);
  2459.     }
  2460.  
  2461.     /**
  2462.      * Warps this Char to a given {@link Field}, with the Field's "sp" portal as spawn position.
  2463.      *
  2464.      * @param toField       The Field to warp to.
  2465.      * @param characterData Whether or not the character data should be encoded.
  2466.      */
  2467.     public void warp(Field toField, boolean characterData) {
  2468.         if (toField == null) {
  2469.             System.out.print("to field is null\r\n");
  2470.             toField = getOrCreateFieldByCurrentInstanceType(100000000);
  2471.         }
  2472.         warp(toField, toField.getPortalByName("sp"), characterData, true);
  2473.     }
  2474.  
  2475.     public void warp(int fieldId, int portalId, boolean saveReturnMap) {
  2476.         Field field = getOrCreateFieldByCurrentInstanceType(fieldId);
  2477.         Portal portal = field.getPortalByID(portalId);
  2478.         if (portal == null) {
  2479.             portal = field.getDefaultPortal();
  2480.         }
  2481.         warp(field, portal, false, saveReturnMap);
  2482.     }
  2483.  
  2484.     /**
  2485.      * Warps this Char to a given {@link Field} and {@link Portal}. Will not include character data.
  2486.      *
  2487.      * @param toField  The Field to warp to.
  2488.      * @param toPortal The Portal to spawn at.
  2489.      */
  2490.     public void warp(Field toField, Portal toPortal) {
  2491.         warp(toField, toPortal, false, true);
  2492.     }
  2493.  
  2494.     /**
  2495.      * Sets the return portal to the nearest current portal.
  2496.      */
  2497.     public void setNearestReturnPortal() {
  2498.         Rect rect = new Rect(
  2499.                 new Position(
  2500.                         getPosition().getX() - 30,
  2501.                         getPosition().getY() - 30),
  2502.                 new Position(
  2503.                         getPosition().getX() + 50, // wide girth
  2504.                         getPosition().getY() + 50)
  2505.         );
  2506.  
  2507.         List<Portal> portals = getField().getClosestPortal(rect);
  2508.  
  2509.         if (portals.size() > 0) {
  2510.             setPreviousPortalID(portals.get(0).getId());
  2511.         } else {
  2512.             setPreviousPortalID(0);
  2513.         }
  2514.     }
  2515.  
  2516.     /**
  2517.      * Warps this character to a given field, at a given portal.
  2518.      * Ensures that the previous map does not contain this Char anymore, and that the new field
  2519.      * does.
  2520.      * Ensures that all Lifes are immediately spawned for the new player.
  2521.      *
  2522.      * @param toField The {@link Field} to warp to.
  2523.      * @param portal  The {@link Portal} where to spawn at.
  2524.      */
  2525.     public void warp(Field toField, Portal portal, boolean characterData, boolean saveReturnMap) {
  2526.         if (toField == null) {
  2527.             return;
  2528.         }
  2529.         TemporaryStatManager tsm = getTemporaryStatManager();
  2530.         for (AffectedArea aa : tsm.getAffectedAreas()) {
  2531.             tsm.removeStatsBySkill(aa.getSkillID());
  2532.         }
  2533.         Field currentField = getField();
  2534.  
  2535.         if (currentField != null) {
  2536.             if (saveReturnMap) {
  2537.                 setPreviousFieldID(currentField.getId()); // this may be a bad idea in some cases? idk
  2538.                 setNearestReturnPortal();
  2539.             }
  2540.             currentField.removeChar(this);
  2541.         }
  2542.  
  2543.         setField(toField);
  2544.         toField.addChar(this);
  2545.         getAvatarData().getCharacterStat().setPortal(portal.getId());
  2546.         setPosition(new Position(portal.getX(), portal.getY()));
  2547.         getClient().write(Stage.setField(this, toField, getClient().getChannel(), false, 0, characterData, hasBuffProtector(),
  2548.                 (byte) (portal != null ? portal.getId() : 0), false, 100, null, true, -1));
  2549.         showProperUI(currentField != null ? currentField.getId() : -1, toField.getId());
  2550.         if (characterData) {
  2551.             initSoulMP();
  2552.             Party party = getParty();
  2553.             if (party != null) {
  2554.                 write(WvsContext.partyResult(PartyResult.loadParty(party)));
  2555.             }
  2556.             if (getGuild() != null) {
  2557.                 write(WvsContext.guildResult(GuildResult.loadGuild(getGuild())));
  2558.                 if (getGuild().getAlliance() != null) {
  2559.                     write(WvsContext.allianceResult(AllianceResult.loadDone(getGuild().getAlliance())));
  2560.                     write(WvsContext.allianceResult(AllianceResult.loadGuildDone(getGuild().getAlliance())));
  2561.                 }
  2562.             }
  2563.             for (Friend f : getFriends()) {
  2564.                 f.setFlag(getClient().getWorld().getCharByID(f.getFriendID()) != null
  2565.                         ? FriendFlag.FriendOnline
  2566.                         : FriendFlag.FriendOffline);
  2567.             }
  2568.             for (Friend f : getAccount().getFriends()) {
  2569.                 f.setFlag(getClient().getWorld().getAccountByID(f.getFriendAccountID()) != null
  2570.                         ? FriendFlag.AccountFriendOnline
  2571.                         : FriendFlag.AccountFriendOffline);
  2572.             }
  2573.         }
  2574.         toField.spawnLifesForChar(this);
  2575.  
  2576.         if (JobConstants.isEvan(getJob()) && getJob() != JobConstants.JobEnum.EVAN_NOOB.getJobId()) {
  2577.             ((Evan) getJobHandler()).spawnMir();
  2578.         }
  2579.         if (JobConstants.isKanna(getJob())) {
  2580.             ((Kanna) getJobHandler()).spawnHaku();
  2581.         }
  2582.         if (tsm.hasStat(IndieEmpty)) {
  2583.             for (Iterator<Option> iterator = tsm.getCurrentStats().getOrDefault(IndieEmpty, new ArrayList<>()).iterator(); iterator.hasNext(); ) {
  2584.                 Summon summon = iterator.next().summon;
  2585.                 if (summon != null) {
  2586.                     if (summon.getMoveAbility().changeFieldWithOwner()) {
  2587.                         summon.setObjectId(getField().getNewObjectID());
  2588.                         getField().spawnSummon(summon);
  2589.                     } else {
  2590.                         iterator.remove();
  2591.                     }
  2592.                 }
  2593.             }
  2594.         }
  2595.         for (int skill : Job.REMOVE_ON_WARP) {
  2596.             if (tsm.hasStatBySkillId(skill)) {
  2597.                 tsm.removeStatsBySkill(skill);
  2598.             }
  2599.         }
  2600.         if (tsm.hasStat(Flying) && !toField.isFly()) {
  2601.             tsm.removeStat(Flying, false);
  2602.         }
  2603.         notifyChanges();
  2604.         toField.execUserEnterScript(this);
  2605.         initPets();
  2606.         if (toField.getTimeLimit() > 0) {
  2607.             Field warpTo = getOrCreateFieldByCurrentInstanceType(toField.getReturnMap());
  2608.             if (warpTo != null && toField.getReturnMap() != toField.getId()) {
  2609.                 if (timeLimitTimer != null && !timeLimitTimer.isDone()) {
  2610.                     timeLimitTimer.cancel(true);
  2611.                 }
  2612.                 new Clock(ClockType.SecondsClock, getField(), toField.getTimeLimit());
  2613.                 timeLimitTimer = EventManager.addEvent(() -> warp(warpTo), toField.getTimeLimit(), TimeUnit.SECONDS);
  2614.             }
  2615.         }
  2616.         if (getDeathCount() > 0) {
  2617.             write(UserLocal.deathCountInfo(getDeathCount()));
  2618.         }
  2619.         if (field.getEliteState() == EliteState.EliteBoss) {
  2620.             write(FieldPacket.eliteState(EliteState.EliteBoss, true, GameConstants.ELITE_BOSS_BGM, null, null));
  2621.         }
  2622.         if (getActiveFamiliar() != null) {
  2623.             getField().broadcastPacket(CFamiliar.familiarEnterField(getId(), true, getActiveFamiliar(), true, false));
  2624.         }
  2625.         Dragon dragon = getDragon();
  2626.         if (dragon != null) {
  2627.             toField.spawnLife(dragon, null);
  2628.         }
  2629.         Android android = getAndroid();
  2630.         if (android != null) {
  2631.             toField.spawnLife(android, null);
  2632.         }
  2633.         for (Mob mob : toField.getMobs()) {
  2634.             mob.addObserver(getScriptManager());
  2635.         }
  2636.         if (getFieldInstanceType() == CHANNEL) {
  2637.             write(FieldPacket.setQuickMoveInfo(GameConstants.getQuickMoveInfos().stream().filter(qmi -> !qmi.isNoInstances() || getField().isChannelField()).collect(Collectors.toList())));
  2638.         }
  2639.         if (JobConstants.isAngelicBuster(getJob())) {
  2640.             write(UserLocal.setDressChanged(false, true));
  2641.         }
  2642.     }
  2643.  
  2644.     /**
  2645.      * Adds a given amount of exp to this Char. Immediately checks for level-up possibility, and
  2646.      * sends the updated
  2647.      * stats to the client. Allows multi-leveling.
  2648.      *
  2649.      * @param amount The amount of exp to add.
  2650.      */
  2651.     public void addExp(long amount) {
  2652.         ExpIncreaseInfo eii = new ExpIncreaseInfo();
  2653.         eii.setLastHit(true);
  2654.         eii.setIncEXP(Util.maxInt(amount));
  2655.         addExp(amount, eii);
  2656.     }
  2657.  
  2658.     /**
  2659.      * Adds exp to this Char. Will calculate the extra exp gained from buffs and the exp rate of the server.
  2660.      * Also takes an argument to show this info to the client. Will not send anything if this argument (eii) is null.
  2661.      *
  2662.      * @param amount The amount of exp to add
  2663.      * @param eii    The info to send to the client
  2664.      */
  2665.     public void addExp(long amount, ExpIncreaseInfo eii) {
  2666.         if (amount <= 0) {
  2667.             return;
  2668.         }
  2669.         if (getGuild() != null) {
  2670.             getGuild().addCommitmentToChar(this, (int) Math.min(amount, Integer.MAX_VALUE)); // independant of any xp buffs
  2671.         }
  2672.         int expFromExpR = (int) (amount * (getTotalStat(BaseStat.expR) / 100D));
  2673.         amount += expFromExpR;
  2674.         int level = getLevel();
  2675.         CharacterStat cs = getAvatarData().getCharacterStat();
  2676.         long curExp = cs.getExp();
  2677.         if (level >= GameConstants.charExp.length - 1) {
  2678.             return;
  2679.         }
  2680.         long newExp = curExp + amount;
  2681.         Map<Stat, Object> stats = new HashMap<>();
  2682.         while (newExp >= GameConstants.charExp[level] && level < GameConstants.charExp.length) {
  2683.             newExp -= GameConstants.charExp[level];
  2684.             addStat(Stat.level, 1);
  2685.             stats.put(Stat.level, (byte) getStat(Stat.level));
  2686.             getJobHandler().handleLevelUp();
  2687.             level++;
  2688.             getField().broadcastPacket(UserRemote.effect(getId(), Effect.levelUpEffect()));
  2689.             heal(getMaxHP());
  2690.             healMP(getMaxMP());
  2691.         }
  2692.         cs.setExp(newExp);
  2693.         stats.put(Stat.exp, newExp);
  2694.         if (eii != null) {
  2695.             eii.setIndieBonusExp(expFromExpR);
  2696.             write(WvsContext.incExpMessage(eii));
  2697.         }
  2698.         getClient().write(WvsContext.statChanged(stats));
  2699.     }
  2700.  
  2701.     /**
  2702.      * Adds a given amount of exp to this Char, however it does not display the Exp Message.
  2703.      * Immediately checks for level-up possibility, and sends the updated
  2704.      * stats to the client. Allows multi-leveling.
  2705.      *
  2706.      * @param amount The amount of exp to add.
  2707.      */
  2708.     public void addExpNoMsg(long amount) {
  2709.         addExp(amount, null);
  2710.     }
  2711.  
  2712.     public void addTraitExp(Stat traitStat, int amount) {
  2713.         if (amount <= 0) {
  2714.             return;
  2715.         }
  2716.         Map<Stat, Object> stats = new HashMap<>();
  2717.         addStat(traitStat, amount);
  2718.         stats.put(traitStat, getStat(traitStat));
  2719.         stats.put(Stat.dayLimit, getAvatarData().getCharacterStat().getNonCombatStatDayLimit());
  2720.         write(WvsContext.statChanged(stats));
  2721.         write(WvsContext.incNonCombatStatEXPMessage(traitStat, amount));
  2722.     }
  2723.  
  2724.     /**
  2725.      * Writes a packet to this Char's client.
  2726.      *
  2727.      * @param outPacket The OutPacket to write.
  2728.      */
  2729.     public void write(OutPacket outPacket) {
  2730.         if (getClient() != null) {
  2731.             getClient().write(outPacket);
  2732.         }
  2733.     }
  2734.  
  2735.     public ExpIncreaseInfo getExpIncreaseInfo() {
  2736.         return new ExpIncreaseInfo();
  2737.     }
  2738.  
  2739.     public WildHunterInfo getWildHunterInfo() {
  2740.         return wildHunterInfo;
  2741.     }
  2742.  
  2743.  
  2744.     public void setWildHunterInfo(WildHunterInfo wildHunterInfo) {
  2745.         this.wildHunterInfo = wildHunterInfo;
  2746.     }
  2747.  
  2748.     public ZeroInfo getZeroInfo() {
  2749.         return zeroInfo;
  2750.     }
  2751.  
  2752.     public void setZeroInfo(ZeroInfo zeroInfo) {
  2753.         this.zeroInfo = zeroInfo;
  2754.     }
  2755.  
  2756.     public int getNickItem() {
  2757.         return nickItem;
  2758.     }
  2759.  
  2760.     public void setNickItem(int nickItem) {
  2761.         this.nickItem = nickItem;
  2762.     }
  2763.  
  2764.     public void setDamageSkin(int itemID) {
  2765.         setDamageSkin(new DamageSkinSaveData(ItemConstants.getDamageSkinIDByItemID(itemID), itemID, false,
  2766.                 StringData.getItemStringById(itemID)));
  2767.     }
  2768.  
  2769.     public void setDamageSkin(DamageSkinSaveData damageSkin) {
  2770.         this.damageSkin = damageSkin;
  2771.     }
  2772.  
  2773.     public DamageSkinSaveData getDamageSkin() {
  2774.         return damageSkin;
  2775.     }
  2776.  
  2777.     public DamageSkinSaveData getPremiumDamageSkin() {
  2778.         return premiumDamageSkin;
  2779.     }
  2780.  
  2781.     public void setPremiumDamageSkin(DamageSkinSaveData premiumDamageSkin) {
  2782.         this.premiumDamageSkin = premiumDamageSkin;
  2783.     }
  2784.  
  2785.     public void setPremiumDamageSkin(int itemID) {
  2786.         setPremiumDamageSkin(new DamageSkinSaveData(ItemConstants.getDamageSkinIDByItemID(itemID), itemID, false,
  2787.                 StringData.getItemStringById(itemID)));
  2788.     }
  2789.  
  2790.     public void setPartyInvitable(boolean partyInvitable) {
  2791.         this.partyInvitable = partyInvitable;
  2792.     }
  2793.  
  2794.     /**
  2795.      * Returns if this Char can be invited to a party.
  2796.      *
  2797.      * @return Whether or not this Char can be invited to a party.
  2798.      */
  2799.     public boolean isPartyInvitable() {
  2800.         return partyInvitable;
  2801.     }
  2802.  
  2803.     /**
  2804.      * Returns if this character is currently in its beta state.
  2805.      *
  2806.      * @return true if this Char is in a beta state.
  2807.      */
  2808.     public boolean isZeroBeta() {
  2809.         return getZeroInfo() != null && getZeroInfo().isZeroBetaState();
  2810.     }
  2811.  
  2812.     /**
  2813.      * Zero only.
  2814.      * Goes into Beta form if Alpha, and into Alpha if Beta.
  2815.      */
  2816.     public void swapZeroState() {
  2817.         if (!(JobConstants.isZero(getJob())) || getZeroInfo() == null) {
  2818.             return;
  2819.         }
  2820.         ZeroInfo oldInfo = getZeroInfo().deepCopy();
  2821.         ZeroInfo currentInfo = getZeroInfo();
  2822.         CharacterStat cs = getAvatarData().getCharacterStat();
  2823.         currentInfo.setZeroBetaState(!oldInfo.isZeroBetaState());
  2824.         currentInfo.setSubHP(cs.getHp());
  2825.         currentInfo.setSubMHP(cs.getMaxHp());
  2826.         currentInfo.setSubMP(cs.getMp());
  2827.         currentInfo.setSubMMP(cs.getMaxMp());
  2828.         cs.setHp(oldInfo.getSubHP());
  2829.         cs.setMaxHp(oldInfo.getSubMHP());
  2830.         cs.setMp(oldInfo.getSubMP());
  2831.         cs.setMaxMp(oldInfo.getSubMMP());
  2832.         Map<Stat, Object> updatedStats = new HashMap<>();
  2833.         updatedStats.put(Stat.hp, cs.getHp());
  2834.         updatedStats.put(Stat.mhp, cs.getMaxHp());
  2835.         updatedStats.put(Stat.mp, cs.getMp());
  2836.         updatedStats.put(Stat.mmp, cs.getMaxMp());
  2837.         write(WvsContext.statChanged(updatedStats));
  2838. //        write(WvsContext.zeroInfo(currentInfo));
  2839.     }
  2840.  
  2841.     /**
  2842.      * Initializes zero info with HP values.
  2843.      */
  2844.     public void initZeroInfo() {
  2845.         ZeroInfo zeroInfo = new ZeroInfo();
  2846.         CharacterStat cs = getAvatarData().getCharacterStat();
  2847.         zeroInfo.setSubHP(cs.getHp());
  2848.         zeroInfo.setSubMHP(cs.getMaxHp());
  2849.         zeroInfo.setSubMP(cs.getMp());
  2850.         zeroInfo.setSubMMP(cs.getMaxMp());
  2851.         setZeroInfo(zeroInfo);
  2852.     }
  2853.  
  2854.     public ScriptManagerImpl getScriptManager() {
  2855.         return scriptManagerImpl;
  2856.     }
  2857.  
  2858.     /**
  2859.      * Adds a {@link Drop} to this Char.
  2860.      *
  2861.      * @param drop The Drop that has been picked up.
  2862.      */
  2863.     public boolean addDrop(Drop drop) {
  2864.         if (drop.isMoney()) {
  2865.             addMoney(drop.getMoney());
  2866.             getQuestManager().handleMoneyGain(drop.getMoney());
  2867.             write(WvsContext.dropPickupMessage(drop.getMoney(), (short) 0, (short) 0));
  2868.             dispose();
  2869.             return true;
  2870.         } else {
  2871.             Item item = drop.getItem();
  2872.             int itemID = item.getItemId();
  2873.             boolean isConsume = false;
  2874.             boolean isRunOnPickUp = false;
  2875.             if (itemID == GameConstants.BLUE_EXP_ORB_ID || itemID == GameConstants.PURPLE_EXP_ORB_ID ||
  2876.                     itemID == GameConstants.RED_EXP_ORB_ID) {
  2877.                 long expGain = (long) (drop.getMobExp() * GameConstants.getExpOrbExpModifierById(itemID));
  2878.  
  2879.                 write(UserPacket.effect(Effect.fieldItemConsumed((int) (expGain > Integer.MAX_VALUE ? Integer.MAX_VALUE : expGain))));
  2880.                 addExpNoMsg(expGain);
  2881.  
  2882.                 // Exp Orb Buff On Pickup
  2883.                 TemporaryStatManager tsm = getTemporaryStatManager();
  2884.                 ItemBuffs.giveItemBuffsFromItemID(this, tsm, itemID);
  2885.             }
  2886.             if (!ItemConstants.isEquip(itemID)) {
  2887.                 ItemInfo ii = ItemData.getItemInfoByID(itemID);
  2888.                 isConsume = ii.getSpecStats().getOrDefault(SpecStat.consumeOnPickup, 0) != 0;
  2889.                 isRunOnPickUp = ii.getSpecStats().getOrDefault(SpecStat.runOnPickup, 0) != 0;
  2890.             }
  2891.             if (isConsume) {
  2892.                 consumeItemOnPickup(item);
  2893.                 dispose();
  2894.                 return true;
  2895.             } else if (isRunOnPickUp) {
  2896.                 String script = String.valueOf(itemID);
  2897.                 ItemInfo ii = ItemData.getItemInfoByID(itemID);
  2898.                 if (ii.getScript() != null && !"".equals(ii.getScript())) {
  2899.                     script = ii.getScript();
  2900.                 }
  2901.                 getScriptManager().startScript(itemID, script, ScriptType.Item);
  2902.                 return true;
  2903.             } else if (getInventoryByType(item.getInvType()).canPickUp(item)) {
  2904.                 if (item instanceof Equip) {
  2905.                     Equip equip = (Equip) item;
  2906.                     if (equip.hasAttribute(EquipAttribute.UntradableAfterTransaction)) {
  2907.                         equip.removeAttribute(EquipAttribute.UntradableAfterTransaction);
  2908.                         equip.addAttribute(EquipAttribute.Untradable);
  2909.                     }
  2910.                 }
  2911.                 addItemToInventory(item);
  2912.                 write(WvsContext.dropPickupMessage(item, (short) item.getQuantity()));
  2913.                 return true;
  2914.             } else {
  2915.                 write(WvsContext.dropPickupMessage(0, (byte) -1, (short) 0, (short) 0, (short) 0));
  2916.                 return false;
  2917.             }
  2918.         }
  2919.     }
  2920.  
  2921.     private void consumeItemOnPickup(Item item) {
  2922.         int itemID = item.getItemId();
  2923.         if (ItemConstants.isMobCard(itemID)) {
  2924.             MonsterBookInfo mbi = getMonsterBookInfo();
  2925.             int id = 0;
  2926.             if (!mbi.hasCard(itemID)) {
  2927.                 mbi.addCard(itemID);
  2928.                 id = itemID;
  2929.             }
  2930.             write(WvsContext.monsterBookSetCard(id));
  2931.         }
  2932.     }
  2933.  
  2934.     /**
  2935.      * Returns the Char's name.
  2936.      *
  2937.      * @return The Char's name.
  2938.      */
  2939.     public String getName() {
  2940.         return getAvatarData().getCharacterStat().getName();
  2941.     }
  2942.  
  2943.     /**
  2944.      * Checks whether or not this Char has a given quest in progress.
  2945.      *
  2946.      * @param questReq The quest ID of the requested quest.
  2947.      * @return Whether or not this char is in progress with the quest.
  2948.      */
  2949.     public boolean hasQuestInProgress(int questReq) {
  2950.         return getQuestManager().hasQuestInProgress(questReq);
  2951.     }
  2952.  
  2953.     /**
  2954.      * Disposes this Char, allowing it to send packets to the server again.
  2955.      */
  2956.     public void dispose() {
  2957.         write(WvsContext.exclRequest());
  2958.     }
  2959.  
  2960.     /**
  2961.      * Returns the current HP of this Char.
  2962.      *
  2963.      * @return the current HP of this Char.
  2964.      */
  2965.     public int getHP() {
  2966.         return getStat(Stat.hp);
  2967.     }
  2968.  
  2969.     /**
  2970.      * Returns the current MP of this Char.
  2971.      *
  2972.      * @return the current MP of this Char.
  2973.      */
  2974.     public int getMP() {
  2975.         return getStat(Stat.mp);
  2976.     }
  2977.  
  2978.     /**
  2979.      * Gets the max hp of this Char.
  2980.      *
  2981.      * @return The max hp of this Char
  2982.      */
  2983.     public int getMaxHP() {
  2984.         return getTotalStat(BaseStat.mhp);
  2985.     }
  2986.  
  2987.     /**
  2988.      * Gets the max mp of this Char.
  2989.      *
  2990.      * @return The max mp of this Char
  2991.      */
  2992.     public int getMaxMP() {
  2993.         return getTotalStat(BaseStat.mmp);
  2994.     }
  2995.  
  2996.     /**
  2997.      * Heals character's MP and HP completely.
  2998.      */
  2999.     public void healHPMP() {
  3000.         heal(getMaxHP());
  3001.         healMP(getMaxMP());
  3002.     }
  3003.  
  3004.     /**
  3005.      * Heals this Char's HP for a certain amount. Caps off at maximum HP.
  3006.      *
  3007.      * @param amount The amount to heal.
  3008.      */
  3009.     public void heal(int amount) {
  3010.         int curHP = getHP();
  3011.         int maxHP = getMaxHP();
  3012.         int newHP = curHP + amount > maxHP ? maxHP : curHP + amount;
  3013.         Map<Stat, Object> stats = new HashMap<>();
  3014.         setStat(Stat.hp, newHP);
  3015.         stats.put(Stat.hp, newHP);
  3016.         write(WvsContext.statChanged(stats));
  3017.         if (getParty() != null) {
  3018.             getParty().broadcast(UserRemote.receiveHP(this), this);
  3019.         }
  3020.     }
  3021.  
  3022.     /**
  3023.      * "Heals" this Char's MP for a certain amount. Caps off at maximum MP.
  3024.      *
  3025.      * @param amount The amount to heal.
  3026.      */
  3027.     public void healMP(int amount) {
  3028.         int curMP = getMP();
  3029.         int maxMP = getMaxMP();
  3030.         int newMP = curMP + amount > maxMP ? maxMP : curMP + amount;
  3031.         Map<Stat, Object> stats = new HashMap<>();
  3032.         setStat(Stat.mp, newMP);
  3033.         stats.put(Stat.mp, newMP);
  3034.         write(WvsContext.statChanged(stats));
  3035.     }
  3036.  
  3037.     /**
  3038.      * Consumes a single {@link Item} from this Char's {@link Inventory}. Will remove the Item if it
  3039.      * has a quantity of 1.
  3040.      *
  3041.      * @param item The Item to consume, which is currently in the Char's inventory.
  3042.      */
  3043.     public void consumeItem(Item item) {
  3044.         Inventory inventory = getInventoryByType(item.getInvType());
  3045.         // data race possible
  3046.         if (item.getQuantity() <= 1 && !ItemConstants.isThrowingItem(item.getItemId())) {
  3047.             item.setQuantity(0);
  3048.             inventory.removeItem(item);
  3049.             short bagIndex = (short) item.getBagIndex();
  3050.             if (item.getInvType() == EQUIPPED) {
  3051.                 getAvatarData().getAvatarLook().removeItem(item.getItemId());
  3052.                 bagIndex = (short) -bagIndex;
  3053.             }
  3054.             write(WvsContext.inventoryOperation(true, false,
  3055.                     Remove, bagIndex, (byte) 0, 0, item));
  3056.         } else {
  3057.             item.setQuantity(item.getQuantity() - 1);
  3058.             write(WvsContext.inventoryOperation(true, false,
  3059.                     UpdateQuantity, (short) item.getBagIndex(), (byte) -1, 0, item));
  3060.         }
  3061.         setBulletIDForAttack(calculateBulletIDForAttack(1));
  3062.     }
  3063.  
  3064.     /**
  3065.      * Consumes an item of this Char with the given id. Will do nothing if the Char doesn't have the
  3066.      * Item.
  3067.      * Only works for non-Equip (i.e., type is not EQUIPPED or EQUIP, CASH is fine) items.
  3068.      * Calls {@link #consumeItem(Item)} if an Item is found.
  3069.      *
  3070.      * @param id       The Item's id.
  3071.      * @param quantity The amount to consume.
  3072.      */
  3073.     public void consumeItem(int id, int quantity) {
  3074.         Item checkItem = ItemData.getItemDeepCopy(id);
  3075.         if (checkItem != null) {
  3076.             Item item = getInventoryByType(checkItem.getInvType()).getItemByItemID(id);
  3077.             if (item != null) {
  3078.                 int itemQuantity = item.getQuantity();
  3079.                 int consumed = quantity > itemQuantity ? 0 : itemQuantity - quantity;
  3080.                 item.setQuantity(consumed + 1); // +1 because 1 gets consumed by consumeItem(item)
  3081.                 consumeItem(item);
  3082.                 if (quantity > itemQuantity) {
  3083.                     consumeItem(id, quantity - itemQuantity);
  3084.                 }
  3085.             }
  3086.         }
  3087.     }
  3088.  
  3089.     public boolean hasItem(int itemID) {
  3090.         return getInventories().stream().anyMatch(inv -> inv.containsItem(itemID));
  3091.     }
  3092.  
  3093.     public boolean hasItemCount(int itemID, int count) {
  3094.         Inventory inv = getInventoryByType(ItemData.getItemDeepCopy(itemID).getInvType());
  3095.         return inv.getItems().stream()
  3096.                 .filter(i -> i.getItemId() == itemID)
  3097.                 .mapToInt(Item::getQuantity)
  3098.                 .sum() >= count;
  3099.     }
  3100.  
  3101.     public short getLevel() {
  3102.         return getAvatarData().getCharacterStat().getLevel();
  3103.     }
  3104.  
  3105.     public boolean isMarried() {
  3106.         // TODO
  3107.         return false;
  3108.     }
  3109.  
  3110.     public Guild getGuild() {
  3111.         return guild;
  3112.     }
  3113.  
  3114.     public void setGuild(Guild guild) {
  3115.         if (guild != null) {
  3116.             // to ensure that the same instance of a guild is retrieved for all characters
  3117.             this.guild = getClient().getWorld().getGuildByID(guild.getId());
  3118.         } else {
  3119.             this.guild = null;
  3120.         }
  3121.     }
  3122.  
  3123.     public int getTotalChuc() {
  3124.         return getInventoryByType(EQUIPPED).getItems().stream().mapToInt(i -> ((Equip) i).getChuc()).sum();
  3125.     }
  3126.  
  3127.     public int getDriverID() {
  3128.         return driverID;
  3129.     }
  3130.  
  3131.     public void setDriverID(int driverID) {
  3132.         this.driverID = driverID;
  3133.     }
  3134.  
  3135.     public int getPassengerID() {
  3136.         return passengerID;
  3137.     }
  3138.  
  3139.     public void setPassengerID(int passengerID) {
  3140.         this.passengerID = passengerID;
  3141.     }
  3142.  
  3143.     public int getChocoCount() {
  3144.         return chocoCount;
  3145.     }
  3146.  
  3147.     public void setChocoCount(int chocoCount) {
  3148.         this.chocoCount = chocoCount;
  3149.     }
  3150.  
  3151.     public int getActiveEffectItemID() {
  3152.         return activeEffectItemID;
  3153.     }
  3154.  
  3155.     public void setActiveEffectItemID(int activeEffectItemID) {
  3156.         this.activeEffectItemID = activeEffectItemID;
  3157.     }
  3158.  
  3159.     public int getMonkeyEffectItemID() {
  3160.         return monkeyEffectItemID;
  3161.     }
  3162.  
  3163.     public void setMonkeyEffectItemID(int monkeyEffectItemID) {
  3164.         this.monkeyEffectItemID = monkeyEffectItemID;
  3165.     }
  3166.  
  3167.     public int getCompletedSetItemID() {
  3168.         return completedSetItemID;
  3169.     }
  3170.  
  3171.     public void setCompletedSetItemID(int completedSetItemID) {
  3172.         this.completedSetItemID = completedSetItemID;
  3173.     }
  3174.  
  3175.     public short getFieldSeatID() {
  3176.         return -1;
  3177.     }
  3178.  
  3179.     public void setFieldSeatID(short fieldSeatID) {
  3180.         this.fieldSeatID = fieldSeatID;
  3181.     }
  3182.  
  3183.     public int getPortableChairID() {
  3184.         return portableChairID;
  3185.     }
  3186.  
  3187.     public void setPortableChairID(int portableChairID) {
  3188.         this.portableChairID = portableChairID;
  3189.     }
  3190.  
  3191.     public String getPortableChairMsg() {
  3192.         return portableChairMsg;
  3193.     }
  3194.  
  3195.     public void setPortableChairMsg(String portableChairMsg) {
  3196.         this.portableChairMsg = portableChairMsg;
  3197.     }
  3198.  
  3199.     public short getFoothold() {
  3200.         return foothold;
  3201.     }
  3202.  
  3203.     public void setFoothold(short foothold) {
  3204.         this.foothold = foothold;
  3205.     }
  3206.  
  3207.     public int getTamingMobLevel() {
  3208.         return tamingMobLevel;
  3209.     }
  3210.  
  3211.     public void setTamingMobLevel(int tamingMobLevel) {
  3212.         this.tamingMobLevel = tamingMobLevel;
  3213.     }
  3214.  
  3215.     public int getTamingMobExp() {
  3216.         return tamingMobExp;
  3217.     }
  3218.  
  3219.     public void setTamingMobExp(int tamingMobExp) {
  3220.         this.tamingMobExp = tamingMobExp;
  3221.     }
  3222.  
  3223.     public int getTamingMobFatigue() {
  3224.         return tamingMobFatigue;
  3225.     }
  3226.  
  3227.     public void setTamingMobFatigue(int tamingMobFatigue) {
  3228.         this.tamingMobFatigue = tamingMobFatigue;
  3229.     }
  3230.  
  3231.     public MiniRoom getMiniRoom() {
  3232.         return miniRoom;
  3233.     }
  3234.  
  3235.     public void setMiniRoom(MiniRoom miniRoom) {
  3236.         this.miniRoom = miniRoom;
  3237.     }
  3238.  
  3239.     public String getADBoardRemoteMsg() {
  3240.         return ADBoardRemoteMsg;
  3241.     }
  3242.  
  3243.     public void setADBoardRemoteMsg(String ADBoardRemoteMsg) {
  3244.         this.ADBoardRemoteMsg = ADBoardRemoteMsg;
  3245.     }
  3246.  
  3247.     public boolean isInCouple() {
  3248.         return inCouple;
  3249.     }
  3250.  
  3251.     public void setInCouple(boolean inCouple) {
  3252.         this.inCouple = inCouple;
  3253.     }
  3254.  
  3255.     public CoupleRecord getCouple() {
  3256.         return couple;
  3257.     }
  3258.  
  3259.     public void setCouple(CoupleRecord couple) {
  3260.         this.couple = couple;
  3261.     }
  3262.  
  3263.     public boolean hasFriendshipItem() {
  3264.         return false;
  3265.     }
  3266.  
  3267.     public FriendshipRingRecord getFriendshipRingRecord() {
  3268.         return friendshipRingRecord;
  3269.     }
  3270.  
  3271.     public void setFriendshipRingRecord(FriendshipRingRecord friendshipRingRecord) {
  3272.         this.friendshipRingRecord = friendshipRingRecord;
  3273.     }
  3274.  
  3275.     public int getComboCounter() {
  3276.         return comboCounter;
  3277.     }
  3278.  
  3279.     public void setComboCounter(int comboCounter) {
  3280.         this.comboCounter = comboCounter;
  3281.     }
  3282.  
  3283.     public int getEvanDragonGlide() {
  3284.         return evanDragonGlide;
  3285.     }
  3286.  
  3287.     public void setEvanDragonGlide(int evanDragonGlide) {
  3288.         this.evanDragonGlide = evanDragonGlide;
  3289.     }
  3290.  
  3291.     public int getKaiserMorphRotateHueExtern() {
  3292.         return kaiserMorphRotateHueExtern;
  3293.     }
  3294.  
  3295.     public void setKaiserMorphRotateHueExtern(int kaiserMorphRotateHueExtern) {
  3296.         this.kaiserMorphRotateHueExtern = kaiserMorphRotateHueExtern;
  3297.     }
  3298.  
  3299.     public int getKaiserMorphPrimiumBlack() {
  3300.         return kaiserMorphPrimiumBlack;
  3301.     }
  3302.  
  3303.     public void setKaiserMorphPrimiumBlack(int kaiserMorphPrimiumBlack) {
  3304.         this.kaiserMorphPrimiumBlack = kaiserMorphPrimiumBlack;
  3305.     }
  3306.  
  3307.     public int getKaiserMorphRotateHueInnner() {
  3308.         return kaiserMorphRotateHueInnner;
  3309.     }
  3310.  
  3311.     public void setKaiserMorphRotateHueInnner(int kaiserMorphRotateHueInnner) {
  3312.         this.kaiserMorphRotateHueInnner = kaiserMorphRotateHueInnner;
  3313.     }
  3314.  
  3315.     public int getMakingMeisterSkillEff() {
  3316.         return makingMeisterSkillEff;
  3317.     }
  3318.  
  3319.     public void setMakingMeisterSkillEff(int makingMeisterSkillEff) {
  3320.         this.makingMeisterSkillEff = makingMeisterSkillEff;
  3321.     }
  3322.  
  3323.     public FarmUserInfo getFarmUserInfo() {
  3324.         if (farmUserInfo == null) {
  3325.             return new FarmUserInfo();
  3326.         }
  3327.         return farmUserInfo;
  3328.     }
  3329.  
  3330.     public void setFarmUserInfo(FarmUserInfo farmUserInfo) {
  3331.         this.farmUserInfo = farmUserInfo;
  3332.     }
  3333.  
  3334.     public int getCustomizeEffect() {
  3335.         return customizeEffect;
  3336.     }
  3337.  
  3338.     public void setCustomizeEffect(int customizeEffect) {
  3339.         this.customizeEffect = customizeEffect;
  3340.     }
  3341.  
  3342.     public String getCustomizeEffectMsg() {
  3343.         return customizeEffectMsg;
  3344.     }
  3345.  
  3346.     public void setCustomizeEffectMsg(String customizeEffectMsg) {
  3347.         this.customizeEffectMsg = customizeEffectMsg;
  3348.     }
  3349.  
  3350.     public byte getSoulEffect() {
  3351.         return soulEffect;
  3352.     }
  3353.  
  3354.     public void setSoulEffect(byte soulEffect) {
  3355.         this.soulEffect = soulEffect;
  3356.     }
  3357.  
  3358.     public FreezeHotEventInfo getFreezeHotEventInfo() {
  3359.         if (freezeHotEventInfo == null) {
  3360.             return new FreezeHotEventInfo();
  3361.         }
  3362.         return freezeHotEventInfo;
  3363.     }
  3364.  
  3365.     public void setFreezeHotEventInfo(FreezeHotEventInfo freezeHotEventInfo) {
  3366.         this.freezeHotEventInfo = freezeHotEventInfo;
  3367.     }
  3368.  
  3369.     public int getEventBestFriendAID() {
  3370.         return eventBestFriendAID;
  3371.     }
  3372.  
  3373.     public void setEventBestFriendAID(int eventBestFriendAID) {
  3374.         this.eventBestFriendAID = eventBestFriendAID;
  3375.     }
  3376.  
  3377.     public int getMesoChairCount() {
  3378.         return mesoChairCount;
  3379.     }
  3380.  
  3381.     public void setMesoChairCount(int mesoChairCount) {
  3382.         this.mesoChairCount = mesoChairCount;
  3383.     }
  3384.  
  3385.     public boolean isBeastFormWingOn() {
  3386.         return beastFormWingOn;
  3387.     }
  3388.  
  3389.     public void setBeastFormWingOn(boolean beastFormWingOn) {
  3390.         this.beastFormWingOn = beastFormWingOn;
  3391.     }
  3392.  
  3393.     public int getActiveNickItemID() {
  3394.         return activeNickItemID;
  3395.     }
  3396.  
  3397.     public void setActiveNickItemID(int activeNickItemID) {
  3398.         this.activeNickItemID = activeNickItemID;
  3399.     }
  3400.  
  3401.     public int getMechanicHue() {
  3402.         return mechanicHue;
  3403.     }
  3404.  
  3405.     public void setMechanicHue(int mechanicHue) {
  3406.         this.mechanicHue = mechanicHue;
  3407.     }
  3408.  
  3409.     public boolean isOnline() {
  3410.         return online;
  3411.     }
  3412.  
  3413.     public void setOnline(boolean online) {
  3414.         if (getGuild() != null) {
  3415.             setGuild(getGuild()); // Hack to ensure that all chars have the same instance of a guild
  3416.             Guild g = getGuild();
  3417.             GuildMember gm = g.getMemberByCharID(getId());
  3418.             gm.setOnline(online);
  3419.             gm.setChr(online ? this : null);
  3420.             Alliance ally = getGuild().getAlliance();
  3421.             if (ally != null) {
  3422.                 ally.broadcast(WvsContext.allianceResult(
  3423.                         AllianceResult.notifyLoginOrLogout(ally, g, gm, !this.online && online)), this);
  3424.             } else {
  3425.                 getGuild().broadcast(WvsContext.guildResult(
  3426.                         GuildResult.notifyLoginOrLogout(g, gm, online, online)), this);
  3427.             }
  3428.         }
  3429.         this.online = online;
  3430.         if (getParty() != null) {
  3431.             PartyMember pm = getParty().getPartyMemberByID(getId());
  3432.             if (pm != null) {
  3433.                 pm.setChr(online ? this : null);
  3434.                 pm.updateInfoByChar(this);
  3435.                 getParty().updateFull();
  3436.             }
  3437.         }
  3438.     }
  3439.  
  3440.     public void setParty(Party party) {
  3441.         if (party != null) {
  3442.             setPartyID(party.getId());
  3443.         } else {
  3444.             setPartyID(0);
  3445.         }
  3446.         this.party = party;
  3447.     }
  3448.  
  3449.     public Party getParty() {
  3450.         return party;
  3451.     }
  3452.  
  3453.     public void logout() {
  3454.         punishLieDetectorEvasion();
  3455.         log.info("Logging out " + getName());
  3456.         if (getField().getForcedReturn() != GameConstants.NO_MAP_ID) {
  3457.             setFieldID(getField().getForcedReturn());
  3458.         }
  3459.         if (getTradeRoom() != null) {
  3460.             Char other = getTradeRoom().getOther();
  3461.             getTradeRoom().cancelTrade();
  3462.             other.chatMessage("Your trade partner disconnected.");
  3463.         }
  3464.         getScriptManager().getScripts().values().forEach(ScriptInfo::reset);
  3465.         getWorld().getConnectedChatClients().remove(getAccId());
  3466.         setOnline(false);
  3467.         getJobHandler().handleCancelTimer(this);
  3468.         getField().removeChar(this);
  3469.         getUser().setCurrentChr(null);
  3470.         if (!isChangingChannel()) {
  3471.             getClient().getChannelInstance().removeChar(this);
  3472.             Server.getInstance().removeUser(getUser()); // don't unstuck, as that would save the account (twice)
  3473.         } else {
  3474.             getClient().setChr(null);
  3475.         }
  3476.         DatabaseManager.saveToDB(getAccount());
  3477.     }
  3478.  
  3479.     public int getSubJob() {
  3480.         return getAvatarData().getCharacterStat().getSubJob();
  3481.     }
  3482.  
  3483.     public FieldInstanceType getFieldInstanceType() {
  3484.         return fieldInstanceType;
  3485.     }
  3486.  
  3487.     public void setFieldInstanceType(FieldInstanceType fieldInstanceType) {
  3488.         this.fieldInstanceType = fieldInstanceType;
  3489.     }
  3490.  
  3491.     /**
  3492.      * Returns the current Set of Fields that this Char holds as personal instances.
  3493.      *
  3494.      * @return the list of personal Field instances.
  3495.      */
  3496.     public Map<Integer, Field> getFields() {
  3497.         return fields;
  3498.     }
  3499.  
  3500.     public void addField(Field field) {
  3501.         getFields().put(field.getId(), field);
  3502.     }
  3503.  
  3504.     public Field getPersonalById(int id) {
  3505.         return getFields().get(id);
  3506.     }
  3507.  
  3508.     public void setInstance(Instance instance) {
  3509.         if (this.instance != null && this.instance.getChars().size() == 0 && instance == null) {
  3510.             this.instance.stopEvents();
  3511.         }
  3512.         this.instance = instance;
  3513.     }
  3514.  
  3515.     public Instance getInstance() {
  3516.         if (party != null && party.getInstance() != null && party.getInstance().getChars().contains(this)) {
  3517.             System.out.print("get instance if");
  3518.             return party.getInstance();
  3519.         }
  3520.         return instance;
  3521.     }
  3522.  
  3523.     private void showProperUI(int fromField, int toField) {
  3524.         if (GameConstants.getMaplerunnerField(toField) > 0 && GameConstants.getMaplerunnerField(fromField) <= 0) {
  3525.             write(FieldPacket.openUI(UIType.UI_PLATFORM_STAGE_LEAVE));
  3526.         } else if (GameConstants.getMaplerunnerField(fromField) > 0 && GameConstants.getMaplerunnerField(toField) <= 0) {
  3527.             write(FieldPacket.closeUI(UIType.UI_PLATFORM_STAGE_LEAVE));
  3528.         }
  3529.     }
  3530.  
  3531.     public int calculateBulletIDForAttack(int requiredAmount) {
  3532.         Item weapon = getEquippedInventory().getFirstItemByBodyPart(BodyPart.Weapon);
  3533.         if (weapon == null) {
  3534.             return 0;
  3535.         }
  3536.         Predicate<Item> kindOfBulletPred;
  3537.         int id = weapon.getItemId();
  3538.  
  3539.         if (ItemConstants.isClaw(id)) {
  3540.             kindOfBulletPred = i -> ItemConstants.isThrowingStar(i.getItemId());
  3541.         } else if (ItemConstants.isBow(id)) {
  3542.             kindOfBulletPred = i -> ItemConstants.isBowArrow(i.getItemId());
  3543.         } else if (ItemConstants.isXBow(id)) {
  3544.             kindOfBulletPred = i -> ItemConstants.isXBowArrow(i.getItemId());
  3545.         } else if (ItemConstants.isGun(id)) {
  3546.             kindOfBulletPred = i -> ItemConstants.isBullet(i.getItemId());
  3547.         } else {
  3548.             return 0;
  3549.         }
  3550.         Item i = getConsumeInventory().getItems().stream().sorted(Comparator.comparing(Item::getBagIndex)).filter(kindOfBulletPred).filter(item -> item.getQuantity() >= requiredAmount).findFirst().orElse(null);
  3551.         return i != null ? i.getItemId() : 0;
  3552.     }
  3553.  
  3554.     public int getBulletIDForAttack() {
  3555.         return bulletIDForAttack;
  3556.     }
  3557.  
  3558.     public void setBulletIDForAttack(int bulletIDForAttack) {
  3559.         this.bulletIDForAttack = bulletIDForAttack;
  3560.     }
  3561.  
  3562.     public void setShop(NpcShopDlg shop) {
  3563.         this.shop = shop;
  3564.     }
  3565.  
  3566.     public NpcShopDlg getShop() {
  3567.         return shop;
  3568.     }
  3569.  
  3570.     /**
  3571.      * Checks if this Char can hold an Item in their inventory, assuming that its quantity is 1.
  3572.      *
  3573.      * @param id the item's itemID
  3574.      * @return whether or not this Char can hold an item in their inventory
  3575.      */
  3576.     public boolean canHold(int id) {
  3577.         boolean canHold;
  3578.         if (ItemConstants.isEquip(id)) {  //Equip
  3579.             canHold = getEquipInventory().getSlots() > getEquipInventory().getItems().size();
  3580.         } else {    //Item
  3581.             ItemInfo ii = ItemData.getItemInfoByID(id);
  3582.             Inventory inv = getInventoryByType(ii.getInvType());
  3583.             Item curItem = inv.getItemByItemID(id);
  3584.             canHold = (curItem != null && curItem.getQuantity() + 1 < ii.getSlotMax()) || inv.getSlots() > inv.getItems().size();
  3585.         }
  3586.         return canHold;
  3587.     }
  3588.  
  3589.     public boolean canHold(int id, int quantity) {
  3590.         int slotMax = ItemData.getItemInfoByID(id).getSlotMax();
  3591.         List<Item> items = new ArrayList<>();
  3592.         for(int i = quantity; i > 0; i -= slotMax){
  3593.             Item item = ItemData.getItemDeepCopy(id);
  3594.             item.setQuantity(i > slotMax ? slotMax : i);
  3595.             items.add(item);
  3596.         }
  3597.         return canHold(items);
  3598.     }
  3599.  
  3600.     /**
  3601.      * Recursive function that checks if this Char can hold a list of items in their inventory.
  3602.      *
  3603.      * @param items the list of items this char should be able to hold
  3604.      * @return whether or not this Char can hold the list of items
  3605.      */
  3606.     public boolean canHold(List<Item> items) {
  3607.         return canHold(items, deepCopyForInvCheck());
  3608.     }
  3609.  
  3610.     private boolean canHold(List<Item> items, Char deepCopiedChar) {
  3611.         // explicitly use a Char param to avoid accidentally adding items
  3612.         if (items.size() == 0) {
  3613.             return true;
  3614.         }
  3615.         Item item = items.get(0);
  3616.         if (canHold(item.getItemId())) {
  3617.             Inventory inv = deepCopiedChar.getInventoryByType(item.getInvType());
  3618.             inv.addItem(item);
  3619.             items.remove(item);
  3620.             return deepCopiedChar.canHold(items, deepCopiedChar);
  3621.         } else {
  3622.             return false;
  3623.         }
  3624.  
  3625.     }
  3626.  
  3627.     private Char deepCopyForInvCheck() {
  3628.         Char chr = new Char();
  3629.         chr.setEquippedInventory(getEquippedInventory().deepCopy());
  3630.         chr.setEquipInventory(getEquipInventory().deepCopy());
  3631.         chr.setConsumeInventory(getConsumeInventory().deepCopy());
  3632.         chr.setEtcInventory(getEtcInventory().deepCopy());
  3633.         chr.setInstallInventory(getInstallInventory().deepCopy());
  3634.         chr.setCashInventory(getCashInventory().deepCopy());
  3635.         return chr;
  3636.     }
  3637.  
  3638.     /**
  3639.      * Returns the set of personal (i.e., non-account) friends of this Char.
  3640.      *
  3641.      * @return The set of personal friends
  3642.      */
  3643.     public Set<Friend> getFriends() {
  3644.         return friends;
  3645.     }
  3646.  
  3647.     public void setFriends(Set<Friend> friends) {
  3648.         this.friends = friends;
  3649.     }
  3650.  
  3651.     /**
  3652.      * Returns the total list of friends of this Char + the owning Account's friends.
  3653.      *
  3654.      * @return The total list of friends
  3655.      */
  3656.     public Set<Friend> getAllFriends() {
  3657.         Set<Friend> res = new HashSet<>(getFriends());
  3658.         res.addAll(getAccount().getFriends());
  3659.         return res;
  3660.     }
  3661.  
  3662.     public Friend getFriendByCharID(int charID) {
  3663.         return getFriends().stream().filter(f -> f.getFriendID() == charID).findAny().orElse(null);
  3664.     }
  3665.  
  3666.     public User getUser() {
  3667.         return user;
  3668.     }
  3669.  
  3670.     public void setUser(User user) {
  3671.         this.user = user;
  3672.     }
  3673.  
  3674.     public Account getAccount() {
  3675.         return account;
  3676.     }
  3677.  
  3678.     public void setAccount(Account account) {
  3679.         this.account = account;
  3680.     }
  3681.  
  3682.     public void removeFriend(Friend friend) {
  3683.         if (friend != null) {
  3684.             getFriends().remove(friend);
  3685.         }
  3686.     }
  3687.  
  3688.     public void removeFriendByID(int charID) {
  3689.         removeFriend(getFriendByCharID(charID));
  3690.     }
  3691.  
  3692.     public void addFriend(Friend friend) {
  3693.         if (getFriendByCharID(friend.getFriendID()) == null) {
  3694.             getFriends().add(friend);
  3695.         }
  3696.     }
  3697.  
  3698.     public void setChatClient(Client chatClient) {
  3699.         this.chatClient = chatClient;
  3700.     }
  3701.  
  3702.     public Client getChatClient() {
  3703.         return chatClient;
  3704.     }
  3705.  
  3706.     public List<Macro> getMacros() {
  3707.         return macros;
  3708.     }
  3709.  
  3710.     public void setMacros(List<Macro> macros) {
  3711.         this.macros = macros;
  3712.     }
  3713.  
  3714.     public void encodeDamageSkins(OutPacket outPacket) {
  3715.         outPacket.encodeByte(true); // hasDamageSkins. Always true in this design.
  3716.         // check ida for structure
  3717.         getDamageSkin().encode(outPacket);
  3718.         getPremiumDamageSkin().encode(outPacket);
  3719.         outPacket.encodeShort(getAccount().getDamageSkins().size() + 10); // slotCount
  3720.         outPacket.encodeShort(getAccount().getDamageSkins().size());
  3721.         for (DamageSkinSaveData dssd : getAccount().getDamageSkins()) {
  3722.             dssd.encode(outPacket);
  3723.         }
  3724.     }
  3725.  
  3726.     public boolean canAddMoney(long reqMoney) {
  3727.         return getMoney() + reqMoney > 0 && getMoney() + reqMoney < GameConstants.MAX_MONEY;
  3728.     }
  3729.  
  3730.     public void addPet(Pet pet) {
  3731.         getPets().add(pet);
  3732.     }
  3733.  
  3734.     public void removePet(Pet pet) {
  3735.         getPets().remove(pet);
  3736.     }
  3737.  
  3738.     public void initPets() {
  3739.         for (PetItem pi : getCashInventory().getItems()
  3740.                 .stream()
  3741.                 .filter(i -> i instanceof PetItem && ((PetItem) i).getActiveState() > 0)
  3742.                 .map(i -> (PetItem) i).collect(Collectors.toList())) {
  3743.             Pet p = getPets().stream().filter(pet -> pet.getItem().equals(pi)).findAny().orElse(null);
  3744.             if (p == null) {
  3745.                 // only create a new pet if the active state is > 0 (active), but isn't added to our own list yet
  3746.                 p = pi.createPet(this);
  3747.                 addPet(p);
  3748.             }
  3749.             getField().broadcastPacket(UserLocal.petActivateChange(p, true, (byte) 0));
  3750.         }
  3751.     }
  3752.  
  3753.     public Pet getPetByIdx(int idx) {
  3754.         return getPets().stream()
  3755.                 .filter(p -> p.getIdx() == idx)
  3756.                 .findAny()
  3757.                 .orElse(null);
  3758.     }
  3759.  
  3760.     public int getFirstPetIdx() {
  3761.         int chosenIdx = -1;
  3762.         for (int i = 0; i < GameConstants.MAX_PET_AMOUNT; i++) {
  3763.             Pet p = getPetByIdx(i);
  3764.             if (p == null) {
  3765.                 chosenIdx = i;
  3766.                 break;
  3767.             }
  3768.         }
  3769.         return chosenIdx;
  3770.     }
  3771.  
  3772.     /**
  3773.      * Initializes the equips' enchantment stats.
  3774.      */
  3775.     public void initEquips() {
  3776.         for (Equip e : getEquippedInventory().getItems().stream().map(e -> (Equip) e).collect(Collectors.toList())) {
  3777.             e.recalcEnchantmentStats();
  3778.         }
  3779.         for (Equip e : getEquipInventory().getItems().stream().map(e -> (Equip) e).collect(Collectors.toList())) {
  3780.             e.recalcEnchantmentStats();
  3781.         }
  3782.     }
  3783.  
  3784.     public void initSoulMP() {
  3785.         Equip weapon = (Equip) getEquippedItemByBodyPart(BodyPart.Weapon);
  3786.         TemporaryStatManager tsm = getTemporaryStatManager();
  3787.         if (weapon != null && weapon.getSoulSocketId() != 0 && !tsm.hasStat(SoulMP)) {
  3788.             Option o = new Option();
  3789.             o.rOption = ItemConstants.getSoulSkillFromSoulID(weapon.getSoulOptionId());
  3790.             o.xOption = ItemConstants.MAX_SOUL_CAPACITY;
  3791.             tsm.putCharacterStatValue(SoulMP, o);
  3792.             tsm.sendSetStatPacket();
  3793.         }
  3794.     }
  3795.  
  3796.     public MonsterBookInfo getMonsterBookInfo() {
  3797.         return monsterBookInfo;
  3798.     }
  3799.  
  3800.     public void setMonsterBookInfo(MonsterBookInfo monsterBookInfo) {
  3801.         this.monsterBookInfo = monsterBookInfo;
  3802.     }
  3803.  
  3804.     public void setDamageCalc(DamageCalc damageCalc) {
  3805.         this.damageCalc = damageCalc;
  3806.     }
  3807.  
  3808.     public DamageCalc getDamageCalc() {
  3809.         return damageCalc;
  3810.     }
  3811.  
  3812.     /**
  3813.      * Gets the current amount of a given stat the character has. Includes things such as skills, items, etc...
  3814.      *
  3815.      * @param baseStat the requested stat
  3816.      * @return the amount of stat
  3817.      */
  3818.     private double getTotalStatAsDouble(BaseStat baseStat) {
  3819.         // TODO cache this completely
  3820.         double stat = 0;
  3821.         // Stat allocated by sp
  3822.         stat += baseStat.toStat() == null ? 0 : getStat(baseStat.toStat());
  3823.         // Stat gained by passives
  3824.         stat += getBaseStats().getOrDefault(baseStat, 0L);
  3825.         // Stat gained by buffs
  3826.         int ctsStat = getTemporaryStatManager().getBaseStats().getOrDefault(baseStat, 0);
  3827.         stat += ctsStat;
  3828.         // Stat gained by the stat's corresponding "per level" value
  3829.         if (baseStat.getLevelVar() != null) {
  3830.             stat += getTotalStatAsDouble(baseStat.getLevelVar()) * getLevel();
  3831.         }
  3832.         // Stat gained by equips
  3833.         for (Item item : getEquippedInventory().getItems()) {
  3834.             Equip equip = (Equip) item;
  3835.             stat += equip.getBaseStat(baseStat);
  3836.         }
  3837.         // Stat gained by the stat's corresponding rate value
  3838.         if (baseStat.getRateVar() != null) {
  3839.             stat += stat * (getTotalStat(baseStat.getRateVar()) / 100D);
  3840.         }
  3841.         // Stat gained by set effects
  3842.         stat += getStatAmountSetEffect(baseStat);
  3843.         // --- Everything below this doesn't get affected by the rate var
  3844.         // Character potential
  3845.         for (CharacterPotential cp : getPotentials()) {
  3846.             Skill skill = cp.getSkill();
  3847.             SkillInfo si = SkillData.getSkillInfoById(skill.getSkillId());
  3848.             Map<BaseStat, Integer> stats = si.getBaseStatValues(this, skill.getCurrentLevel());
  3849.             stat += stats.getOrDefault(baseStat, 0);
  3850.         }
  3851.         return stat;
  3852.     }
  3853.  
  3854.     public int getTotalStat(BaseStat stat) {
  3855.         return (int) getTotalStatAsDouble(stat);
  3856.     }
  3857.  
  3858.     /**
  3859.      * Gets a total list of basic stats that a character has, including from skills, items, etc...
  3860.      *
  3861.      * @return the total list of basic stats
  3862.      */
  3863.     public Map<BaseStat, Integer> getTotalBasicStats() {
  3864.         Map<BaseStat, Integer> stats = new HashMap<>();
  3865.         for (BaseStat bs : BaseStat.values()) {
  3866.             stats.put(bs, getTotalStat(bs));
  3867.         }
  3868.         return stats;
  3869.     }
  3870.  
  3871.     /**
  3872.      * Sets whether or not this user has chosen to use up an item to protect their buffs upon next respawn.
  3873.      *
  3874.      * @param buffProtector buff protectability
  3875.      */
  3876.     public void setBuffProtector(boolean buffProtector) {
  3877.         this.buffProtector = buffProtector;
  3878.     }
  3879.  
  3880.     /**
  3881.      * Returns whether this user has chosen to activate a buff protector for their next respawn.
  3882.      *
  3883.      * @return buff protectability
  3884.      */
  3885.     public boolean hasBuffProtector() {
  3886.         return buffProtector;
  3887.     }
  3888.  
  3889.     /**
  3890.      * Returns the item the user has for protecting buffs.
  3891.      *
  3892.      * @return the Item the user has for prtoecting buffs, or null if there is none.
  3893.      */
  3894.     public Item getBuffProtectorItem() {
  3895.         int[] buffItems = {5133000, 5133001, 4143000};
  3896.         Item item = null;
  3897.         for (int id : buffItems) {
  3898.             item = getConsumeInventory().getItemByItemID(id);
  3899.             if (item == null) {
  3900.                 item = getCashInventory().getItemByItemID(id);
  3901.             }
  3902.             if (item != null) {
  3903.                 // just break when an item was found.
  3904.                 break;
  3905.             }
  3906.         }
  3907.         return item;
  3908.     }
  3909.  
  3910.     /**
  3911.      * Resets the combo kill's timer. Interrupts the previous timer if there was one.
  3912.      */
  3913.     public void comboKillResetTimer() {
  3914.         if (comboKillResetTimer != null && !comboKillResetTimer.isDone()) {
  3915.             comboKillResetTimer.cancel(true);
  3916.         }
  3917.         comboKillResetTimer = EventManager.addEvent(() -> setComboCounter(0), GameConstants.COMBO_KILL_RESET_TIMER, TimeUnit.SECONDS);
  3918.     }
  3919.  
  3920.     public Map<Integer, Long> getSkillCoolTimes() {
  3921.         return skillCoolTimes;
  3922.     }
  3923.  
  3924.     public void addSkillCoolTime(int skillId, long nextusabletime) {
  3925.         getSkillCoolTimes().put(skillId, nextusabletime);
  3926.     }
  3927.  
  3928.     public void removeSkillCoolTime(int skillId) {
  3929.         getSkillCoolTimes().remove(skillId);
  3930.     }
  3931.  
  3932.     public void resetSkillCoolTime(int skillId) {
  3933.         if(hasSkillOnCooldown(skillId)) {
  3934.             addSkillCoolTime(skillId, 0);
  3935.             write(UserLocal.skillCooltimeSetM(skillId, 0));
  3936.         }
  3937.     }
  3938.  
  3939.     public void reduceSkillCoolTime(int skillId, long amountInMS) {
  3940.         if (hasSkillOnCooldown(skillId)) {
  3941.             long nextUsableTime = getSkillCoolTimes().get(skillId);
  3942.             addSkillCoolTime(skillId, nextUsableTime - amountInMS);
  3943.             write(UserLocal.skillCooltimeSetM(skillId, (int) ((nextUsableTime - amountInMS) - System.currentTimeMillis() < 0 ? 0 : (nextUsableTime - amountInMS) - System.currentTimeMillis())));
  3944.         }
  3945.     }
  3946.  
  3947.     public long getRemainingCoolTime(int skillId) {
  3948.         if (hasSkillOnCooldown(skillId)) {
  3949.             return getSkillCoolTimes().getOrDefault(skillId, System.currentTimeMillis()) - System.currentTimeMillis();
  3950.         }
  3951.         return 0L;
  3952.     }
  3953.  
  3954.     /**
  3955.      * Checks whether or not a skill is currently on cooldown.
  3956.      *
  3957.      * @param skillID the skill's id to check
  3958.      * @return whether or not a skill is currently on cooldown
  3959.      */
  3960.     public boolean hasSkillOnCooldown(int skillID) {
  3961.         return System.currentTimeMillis() < getSkillCoolTimes().getOrDefault(skillID, 0L);
  3962.     }
  3963.  
  3964.     /**
  3965.      * Checks if a skill is allowed to be cast, according to its cooltime. If it is allowed, it immediately sets
  3966.      * the cooltime and stores the next moment where the skill is allowed. Skills without cooltime are always allowed.
  3967.      *
  3968.      * @param skillID the skill id of the skill to put on cooldown
  3969.      * @return whether or not the skill was allowed
  3970.      */
  3971.     public boolean checkAndSetSkillCooltime(int skillID) {
  3972.         if (hasSkillOnCooldown(skillID)) {
  3973.             return false;
  3974.         } else {
  3975.             Skill skill = getSkill(skillID);
  3976.             if (skill != null && SkillData.getSkillInfoById(skillID).hasCooltime()) {
  3977.                 setSkillCooldown(skillID, (byte) skill.getCurrentLevel());
  3978.             }
  3979.             return true;
  3980.         }
  3981.     }
  3982.  
  3983.     /**
  3984.      * Sets a skill's cooltime according to their property in the WZ files, and stores the moment where the skill
  3985.      * comes off of cooldown.
  3986.      *
  3987.      * @param skillID the skill's id to set
  3988.      * @param slv     the current skill level
  3989.      */
  3990.     public void setSkillCooldown(int skillID, byte slv) {
  3991.         SkillInfo si = SkillData.getSkillInfoById(skillID);
  3992.         if (si != null) {
  3993.             int cdInSec = si.getValue(SkillStat.cooltime, slv);
  3994.             int cdInMillis = cdInSec > 0 ? cdInSec * 1000 : si.getValue(SkillStat.cooltimeMS, slv);
  3995.             int alteredcd = getJobHandler().alterCooldownSkill(skillID);
  3996.             if (alteredcd >= 0) {
  3997.                 cdInMillis = alteredcd;
  3998.             }
  3999.             // RuneStone of Skill
  4000.             if (getTemporaryStatManager().hasStatBySkillId(RuneStone.LIBERATE_THE_RUNE_OF_SKILL) && cdInMillis > 5000 && !si.isNotCooltimeReset()) {
  4001.                 cdInMillis = 5000;
  4002.             }
  4003.  
  4004.  
  4005.             // Customized  skill cooldowns
  4006.             int fixedSkillCD = SkillConstants.getSkillCooldown(skillID);
  4007.             if(fixedSkillCD != -1) {
  4008.                 cdInMillis = fixedSkillCD;
  4009.             }
  4010.  
  4011.             if (!hasSkillCDBypass() && cdInMillis > 0) {
  4012.                 addSkillCoolTime(skillID, System.currentTimeMillis() + cdInMillis);
  4013.                 write(UserLocal.skillCooltimeSetM(skillID, cdInMillis));
  4014.             }
  4015.         }
  4016.     }
  4017.  
  4018.     public CharacterPotentialMan getPotentialMan() {
  4019.         return potentialMan;
  4020.     }
  4021.  
  4022.     public Set<CharacterPotential> getPotentials() {
  4023.         return potentials;
  4024.     }
  4025.  
  4026.     public void setPotentials(Set<CharacterPotential> potentials) {
  4027.         this.potentials = potentials;
  4028.     }
  4029.  
  4030.     public int getHonorExp() {
  4031.         return getAvatarData().getCharacterStat().getHonorExp();
  4032.     }
  4033.  
  4034.     public void setHonorExp(int honorExp) {
  4035.         getAvatarData().getCharacterStat().setHonorExp(honorExp);
  4036.     }
  4037.  
  4038.     /**
  4039.      * Adds honor exp to this Char, and sends a packet to the client with the new honor exp.
  4040.      * Honor exp added may be negative, but the total honor exp will never go below 0.
  4041.      *
  4042.      * @param exp the exp to add (may be negative)
  4043.      */
  4044.     public void addHonorExp(int exp) {
  4045.         setHonorExp(Math.max(0, getHonorExp() + exp));
  4046.         write(WvsContext.characterHonorExp(getHonorExp()));
  4047.     }
  4048.  
  4049.     public int getDeathCount() {
  4050.         return deathCount;
  4051.     }
  4052.  
  4053.     public void setDeathCount(int deathCount) {
  4054.         this.deathCount = deathCount;
  4055.     }
  4056.  
  4057.     public Set<LinkSkill> getLinkSkills() {
  4058.         return getAccount().getLinkSkills().stream()
  4059.                 .filter(ls -> ls.getOwnerID() != getId())
  4060.                 .collect(Collectors.toSet());
  4061.     }
  4062.  
  4063.     /**
  4064.      * Adds a skill to this Char. If the Char already has this skill, just changes the levels.
  4065.      *
  4066.      * @param skillID      the skill's id to add
  4067.      * @param currentLevel the current level of the skill
  4068.      * @param masterLevel  the master level of the skill
  4069.      */
  4070.     public void addSkill(int skillID, int currentLevel, int masterLevel) {
  4071.         Skill skill = SkillData.getSkillDeepCopyById(skillID);
  4072.         if (skill == null && !SkillConstants.isMakingSkillRecipe(skillID)) {
  4073.             log.error("No such skill found.");
  4074.             return;
  4075.         }
  4076.         skill.setCurrentLevel(currentLevel);
  4077.         skill.setMasterLevel(masterLevel);
  4078.         List<Skill> list = new ArrayList<>();
  4079.         list.add(skill);
  4080.         addSkill(skill);
  4081.         write(WvsContext.changeSkillRecordResult(list, true, false, false, false));
  4082.     }
  4083.  
  4084.     public long getRuneCooldown() {
  4085.         return runeStoneCooldown;
  4086.     }
  4087.  
  4088.     public void setRuneCooldown(long runeCooldown) {
  4089.         this.runeStoneCooldown = runeCooldown;
  4090.     }
  4091.  
  4092.     public MemorialCubeInfo getMemorialCubeInfo() {
  4093.         return memorialCubeInfo;
  4094.     }
  4095.  
  4096.     public void setMemorialCubeInfo(MemorialCubeInfo memorialCubeInfo) {
  4097.         this.memorialCubeInfo = memorialCubeInfo;
  4098.     }
  4099.  
  4100.     public Set<Familiar> getFamiliars() {
  4101.         return familiars;
  4102.     }
  4103.  
  4104.     public void setFamiliars(Set<Familiar> familiars) {
  4105.         this.familiars = familiars;
  4106.     }
  4107.  
  4108.     public boolean hasFamiliar(int familiarID) {
  4109.         return getFamiliars().stream().anyMatch(f -> f.getFamiliarID() == familiarID);
  4110.     }
  4111.  
  4112.     public Familiar getFamiliarByID(int familiarID) {
  4113.         return getFamiliars().stream().filter(f -> f.getFamiliarID() == familiarID).findAny().orElse(null);
  4114.     }
  4115.  
  4116.     public void addFamiliar(Familiar familiar) {
  4117.         getFamiliars().add(familiar);
  4118.     }
  4119.  
  4120.     public void removeFamiliarByID(int familiarID) {
  4121.         removeFamiliar(getFamiliarByID(familiarID));
  4122.     }
  4123.  
  4124.     public void removeFamiliar(Familiar familiar) {
  4125.         if (familiar != null) {
  4126.             getFamiliars().remove(familiar);
  4127.         }
  4128.     }
  4129.  
  4130.     public void setActiveFamiliar(Familiar activeFamiliar) {
  4131.         this.activeFamiliar = activeFamiliar;
  4132.     }
  4133.  
  4134.     public Familiar getActiveFamiliar() {
  4135.         return activeFamiliar;
  4136.     }
  4137.  
  4138.     public boolean hasSkillCDBypass() {
  4139.         return skillCDBypass;
  4140.     }
  4141.  
  4142.     public void setSkillCDBypass(boolean skillCDBypass) {
  4143.         this.skillCDBypass = skillCDBypass;
  4144.     }
  4145.  
  4146.  
  4147.     public Set<StolenSkill> getStolenSkills() {
  4148.         return stolenSkills;
  4149.     }
  4150.  
  4151.     public void setStolenSkills(Set<StolenSkill> stolenSkills) {
  4152.         this.stolenSkills = stolenSkills;
  4153.     }
  4154.  
  4155.     public void addStolenSkill(StolenSkill stolenSkill) {
  4156.         getStolenSkills().add(stolenSkill);
  4157.     }
  4158.  
  4159.     public void removeStolenSkill(StolenSkill stolenSkill) {
  4160.         if (stolenSkill != null) {
  4161.             getStolenSkills().remove(stolenSkill);
  4162.         }
  4163.     }
  4164.  
  4165.     public StolenSkill getStolenSkillByPosition(int position) {
  4166.         return getStolenSkills().stream().filter(ss -> ss.getPosition() == position).findAny().orElse(null);
  4167.     }
  4168.  
  4169.     public StolenSkill getStolenSkillBySkillId(int skillId) {
  4170.         return getStolenSkills().stream().filter(ss -> ss.getSkillid() == skillId).findAny().orElse(null);
  4171.     }
  4172.  
  4173.  
  4174.     public Set<ChosenSkill> getChosenSkills() {
  4175.         return chosenSkills;
  4176.     }
  4177.  
  4178.     public void setChosenSkills(Set<ChosenSkill> chosenSkills) {
  4179.         this.chosenSkills = chosenSkills;
  4180.     }
  4181.  
  4182.     public void addChosenSkill(ChosenSkill chosenSkill) {
  4183.         getChosenSkills().add(chosenSkill);
  4184.     }
  4185.  
  4186.     public void removeChosenSkill(ChosenSkill chosenSkill) {
  4187.         if (chosenSkill != null) {
  4188.             getChosenSkills().remove(chosenSkill);
  4189.         }
  4190.     }
  4191.  
  4192.     public ChosenSkill getChosenSkillByPosition(int position) {
  4193.         return getChosenSkills().stream().filter(ss -> ss.getPosition() == position).findAny().orElse(null);
  4194.     }
  4195.  
  4196.     public boolean isChosenSkillInStolenSkillList(int skillId) {
  4197.         return getStolenSkills().stream().filter(ss -> ss.getSkillid() == skillId).findAny().orElse(null) != null;
  4198.     }
  4199.  
  4200.     public Map<BaseStat, Long> getBaseStats() {
  4201.         return baseStats;
  4202.     }
  4203.  
  4204.     /**
  4205.      * Adds a BaseStat's amount to this Char's BaseStat cache.
  4206.      *
  4207.      * @param bs     The BaseStat
  4208.      * @param amount the amount of BaseStat to add
  4209.      */
  4210.     public void addBaseStat(BaseStat bs, int amount) {
  4211.         getBaseStats().put(bs, getBaseStats().getOrDefault(bs, 0L) + amount);
  4212.     }
  4213.  
  4214.     /**
  4215.      * Removes a BaseStat's amount from this Char's BaseStat cache.
  4216.      *
  4217.      * @param bs     The BaseStat
  4218.      * @param amount the amount of BaseStat to remove
  4219.      */
  4220.     public void removeBaseStat(BaseStat bs, int amount) {
  4221.         addBaseStat(bs, -amount);
  4222.     }
  4223.  
  4224.     public void addItemToInventory(int id, int quantity) {
  4225.         if (ItemConstants.isEquip(id)) {  //Equip
  4226.             Equip equip = ItemData.getEquipDeepCopyFromID(id, false);
  4227.             addItemToInventory(equip.getInvType(), equip, false);
  4228.             getClient().write(WvsContext.inventoryOperation(true, false,
  4229.                     Add, (short) equip.getBagIndex(), (byte) -1, 0, equip));
  4230.  
  4231.         } else {    //Item
  4232.             Item item = ItemData.getItemDeepCopy(id);
  4233.             item.setQuantity(quantity);
  4234.             addItemToInventory(item);
  4235.             getClient().write(WvsContext.inventoryOperation(true, false,
  4236.                     Add, (short) item.getBagIndex(), (byte) -1, 0, item));
  4237.  
  4238.         }
  4239.     }
  4240.  
  4241.     public int getSpentHyperSp() {
  4242.         int sp = 0;
  4243.         for (int skillID = 80000400; skillID <= 80000418; skillID++) {
  4244.             Skill skill = getSkill(skillID);
  4245.             if (skill != null) {
  4246.                 sp += SkillConstants.getTotalNeededSpForHyperStatSkill(skill.getCurrentLevel());
  4247.             }
  4248.         }
  4249.         return sp;
  4250.     }
  4251.  
  4252.     public int getRewardPoints() {
  4253.         return rewardPoints;
  4254.     }
  4255.  
  4256.     public void setRewardPoints(int rewardPoints) {
  4257.         this.rewardPoints = rewardPoints;
  4258.     }
  4259.  
  4260.     public int[] getHyperRockFields() {
  4261.         return hyperrockfields;
  4262.     }
  4263.  
  4264.     public void setHyperRockFields(int[] hyperrockfields) {
  4265.         this.hyperrockfields = hyperrockfields;
  4266.     }
  4267.  
  4268.     public boolean isChangingChannel() {
  4269.         return changingChannel;
  4270.     }
  4271.  
  4272.     public void setChangingChannel(boolean changingChannel) {
  4273.         this.changingChannel = changingChannel;
  4274.     }
  4275.  
  4276.     public int getPartyID() {
  4277.         return partyID;
  4278.     }
  4279.  
  4280.     public void setPartyID(int partyID) {
  4281.         this.partyID = partyID;
  4282.     }
  4283.  
  4284.     public byte getMonsterParkCount() {
  4285.         return monsterParkCount;
  4286.     }
  4287.  
  4288.     public void setMonsterParkCount(byte monsterParkCount) {
  4289.         this.monsterParkCount = monsterParkCount;
  4290.     }
  4291.  
  4292.     public TownPortal getTownPortal() {
  4293.         return townPortal;
  4294.     }
  4295.  
  4296.     public void setTownPortal(TownPortal townPortal) {
  4297.         this.townPortal = townPortal;
  4298.     }
  4299.  
  4300.     public TradeRoom getTradeRoom() {
  4301.         return tradeRoom;
  4302.     }
  4303.  
  4304.     public void setTradeRoom(TradeRoom tradeRoom) {
  4305.         this.tradeRoom = tradeRoom;
  4306.     }
  4307.  
  4308.     public void damage(int damage) {
  4309.         HitInfo hi = new HitInfo();
  4310.         hi.hpDamage = damage;
  4311.         getJobHandler().handleHit(getClient(), hi);
  4312.     }
  4313.  
  4314.     public void changeChannel(byte channelId) {
  4315.         changeChannelAndWarp(channelId, getFieldID());
  4316.     }
  4317.  
  4318.     public void changeChannelAndWarp(byte channelId, int fieldId) {
  4319.         logout();
  4320.         setChangingChannel(true);
  4321.         Field field = getField();
  4322.         if (getFieldID() != fieldId) {
  4323.             setField(getOrCreateFieldByCurrentInstanceType(fieldId));
  4324.         }
  4325.         DatabaseManager.saveToDB(getAccount());
  4326.         int worldID = getClient().getChannelInstance().getWorldId();
  4327.         World world = Server.getInstance().getWorldById(worldID);
  4328.         field.removeChar(this);
  4329.         Channel channel = world.getChannelById(channelId);
  4330.         channel.addClientInTransfer(channelId, getId(), getClient());
  4331.         short port = (short) channel.getPort();
  4332.         write(ClientSocket.migrateCommand(true, port));
  4333.     }
  4334.  
  4335.     @Override
  4336.     public String toString() {
  4337.         return "Char{" +
  4338.                 "(" + super.toString() +
  4339.                 ")id=" + id +
  4340.                 ", accId=" + accId +
  4341.                 ", name=" + getName() +
  4342.                 '}';
  4343.     }
  4344.  
  4345.     public void setBattleRecordOn(boolean battleRecordOn) {
  4346.         this.battleRecordOn = battleRecordOn;
  4347.     }
  4348.  
  4349.     public boolean isBattleRecordOn() {
  4350.         return battleRecordOn;
  4351.     }
  4352.  
  4353.     public void checkAndRemoveExpiredItems() {
  4354.         Inventory[] inventories = new Inventory[]{getEquippedInventory(), getEquipInventory(), getConsumeInventory(),
  4355.                 getEtcInventory(), getInstallInventory(), getCashInventory()};
  4356.         Set<Item> expiredItems = new HashSet<>();
  4357.         for (Inventory inv : inventories) {
  4358.             expiredItems.addAll(
  4359.                     inv.getItems().stream()
  4360.                             .filter(item -> item.getDateExpire().isExpired())
  4361.                             .collect(Collectors.toSet())
  4362.             );
  4363.         }
  4364.         List<Integer> expiredItemIDs = expiredItems.stream().map(Item::getItemId).collect(Collectors.toList());
  4365.         write(WvsContext.message(MessageType.GENERAL_ITEM_EXPIRE_MESSAGE, expiredItemIDs));
  4366.         for (Item item : expiredItems) {
  4367.             consumeItem(item);
  4368.         }
  4369.     }
  4370.  
  4371.     public boolean isGuildMaster() {
  4372.         return getGuild() != null && getGuild().getLeaderID() == getId();
  4373.     }
  4374.  
  4375.     /**
  4376.      * Checks if this Char has any of the given quests in progress. Also true if the size of the given set is 0.
  4377.      *
  4378.      * @param quests the set of quest ids to check
  4379.      * @return whether or not this Char has any of the given quests
  4380.      */
  4381.     public boolean hasAnyQuestsInProgress(Set<Integer> quests) {
  4382.         return quests.size() == 0 || quests.stream().anyMatch(this::hasQuestInProgress);
  4383.     }
  4384.  
  4385.     public int getPreviousFieldID() {
  4386.         return previousFieldID == 0 || previousFieldID == 999999999 ? 100000000 : previousFieldID;
  4387.     }
  4388.  
  4389.     public void setPreviousFieldID(int previousFieldID) {
  4390.         this.previousFieldID = previousFieldID;
  4391.     }
  4392.  
  4393.     public int getPreviousPortalID() {
  4394.         return previousPortalID;
  4395.     }
  4396.  
  4397.     public void setPreviousPortalID(int portalId) { previousPortalID = portalId; }
  4398.  
  4399.     public long getNextRandomPortalTime() {
  4400.         return nextRandomPortalTime;
  4401.     }
  4402.  
  4403.     public void setNextRandomPortalTime(long nextRandomPortalTime) {
  4404.         this.nextRandomPortalTime = nextRandomPortalTime;
  4405.     }
  4406.  
  4407.     public void clearCurrentDirectionNode() { this.currentDirectionNode.clear(); }
  4408.  
  4409.     public int getCurrentDirectionNode(int node) {
  4410.         Integer direction = currentDirectionNode.getOrDefault(node, null);
  4411.         if (direction == null) {
  4412.             currentDirectionNode.put(node, 0);
  4413.         }
  4414.         return currentDirectionNode.get(node);
  4415.     }
  4416.  
  4417.     public void increaseCurrentDirectionNode(int node) {
  4418.         Integer direction = currentDirectionNode.getOrDefault(node, null);
  4419.         if (direction == null) {
  4420.             currentDirectionNode.put(node, 1);
  4421.         } else {
  4422.             currentDirectionNode.put(node, direction + 1);
  4423.         }
  4424.     }
  4425.  
  4426.     public void punishLieDetectorEvasion() {
  4427.         if (getLieDetectorAnswer().length() > 0) {
  4428.             failedLieDetector();
  4429.         }
  4430.     }
  4431.  
  4432.     public String getLieDetectorAnswer() {
  4433.         return lieDetectorAnswer;
  4434.     }
  4435.  
  4436.     public void setLieDetectorAnswer(String answer) {
  4437.         lieDetectorAnswer = answer;
  4438.     }
  4439.  
  4440.     public void failedLieDetector() {
  4441.         setLieDetectorAnswer("");
  4442.         chatMessage(SpeakerChannel, "You have failed the Lie Detector test.");
  4443.  
  4444.         getClient().write(WvsContext.antiMacroResult(null, AntiMacro.AntiMacroResultType.AntiMacroRes_Fail.getVal(), AntiMacro.AntiMacroType.AntiMacroFieldRequest.getVal()));
  4445.  
  4446.         // TODO: handle fail
  4447.     }
  4448.  
  4449.     public void passedLieDetector() {
  4450.         setLieDetectorAnswer("");
  4451.         chatMessage(SpeakerChannel, "You have passed the Lie Detector test!");
  4452.  
  4453.         getClient().write(WvsContext.antiMacroResult(null, AntiMacro.AntiMacroResultType.AntiMacroRes_Success.getVal(), AntiMacro.AntiMacroType.AntiMacroFieldRequest.getVal()));
  4454.  
  4455.         // TODO: handle pass
  4456.     }
  4457.  
  4458.     public boolean sendLieDetector() {
  4459.         return sendLieDetector(false);
  4460.     }
  4461.  
  4462.     public boolean sendLieDetector(boolean force) {
  4463.         // LD ran too recently (15 min)
  4464.         if (!force && lastLieDetector != 0 && System.currentTimeMillis() - lastLieDetector < 900_000L) {
  4465.             return false;
  4466.         }
  4467.  
  4468.         // TODO: don't allow more than 3 refreshes
  4469.  
  4470.         lieDetectorAnswer = "";
  4471.         String font = AntiMacro.FONTS[Util.getRandom(AntiMacro.FONTS.length - 1)];
  4472.  
  4473.         String options = "abcdefghijklmnopqrstuvwxyz0123456789";
  4474.  
  4475.         for (int i = 1; i <= 6; i++) {
  4476.             if (Util.getRandom(1) == 0) {
  4477.                 options = options.toUpperCase();
  4478.             } else {
  4479.                 options = options.toLowerCase();
  4480.             }
  4481.  
  4482.             lieDetectorAnswer += options.charAt(Util.getRandom(options.length() - 1));
  4483.         }
  4484.  
  4485.         try {
  4486.             AntiMacro am = new AntiMacro(font, lieDetectorAnswer);
  4487.             lastLieDetector = System.currentTimeMillis();
  4488.  
  4489.             byte[] image = am.generateImage(196, 44, Color.BLACK, AntiMacro.getRandomColor());
  4490.             getClient().write(WvsContext.antiMacroResult(image, AntiMacro.AntiMacroResultType.AntiMacroRes.getVal(), AntiMacro.AntiMacroType.AntiMacroFieldRequest.getVal()));
  4491.         } catch (IOException|FontFormatException e) {
  4492.             e.printStackTrace();
  4493.  
  4494.             return false;
  4495.         }
  4496.  
  4497.         return true;
  4498.     }
  4499.  
  4500.     public OffenseManager getOffenseManager() {
  4501.         return getUser().getOffenseManager();
  4502.     }
  4503.  
  4504.     /**
  4505.      * Applies the mp consumption of a skill.
  4506.      * @param skillID the skill's id
  4507.      * @param slv the current skill level
  4508.      * @return whether the consumption was successful (unsuccessful = not enough mp)
  4509.      */
  4510.     public boolean applyMpCon(int skillID, byte slv) {
  4511.         int curMp = getStat(Stat.mp);
  4512.         SkillInfo si = SkillData.getSkillInfoById(skillID);
  4513.         if (si == null) {
  4514.             return true;
  4515.         }
  4516.         int mpCon = si.getValue(SkillStat.mpCon, slv);
  4517.         boolean hasEnough = curMp >= mpCon;
  4518.         if (hasEnough) {
  4519.             addStatAndSendPacket(Stat.mp, -mpCon);
  4520.         }
  4521.         return hasEnough;
  4522.     }
  4523.  
  4524.     public boolean applyBulletCon(int skillID, byte slv) {
  4525.         if (getTemporaryStatManager().hasStat(NoBulletConsume) || JobConstants.isPhantom(getJob())) {
  4526.             return true;
  4527.         }
  4528.         SkillInfo si = SkillData.getSkillInfoById(skillID);
  4529.         if (si == null) {
  4530.             return true;
  4531.         }
  4532.         int bulletCon = si.getValue(SkillStat.bulletCount, slv) + si.getValue(SkillStat.bulletConsume, slv);
  4533.         if (bulletCon <= 0) {
  4534.             return true;
  4535.         }
  4536.         int bulletItemId = getBulletIDForAttack();
  4537.         if (bulletItemId == 0) {
  4538.             return false;
  4539.         }
  4540.         if (!hasItemCount(getBulletIDForAttack(), bulletCon)) {
  4541.             setBulletIDForAttack(calculateBulletIDForAttack(bulletCon));
  4542.         }
  4543.         boolean hasEnough = hasItemCount(bulletItemId, bulletCon);
  4544.         if (hasEnough) {
  4545.             consumeItem(bulletItemId, bulletCon);
  4546.         }
  4547.         return hasEnough;
  4548.     }
  4549.  
  4550.     public boolean hasTutor() {
  4551.         return tutor;
  4552.     }
  4553.  
  4554.     public void hireTutor(boolean set) {
  4555.         tutor = set;
  4556.         write(UserLocal.hireTutor(set));
  4557.     }
  4558.  
  4559.     /**
  4560.      * Shows tutor automated message (the client is taking the message information from wz).
  4561.      * @param id the id of the message.
  4562.      * @param duration message duration
  4563.      */
  4564.     public void tutorAutomatedMsg(int id, int duration) {
  4565.         if (!tutor) {
  4566.             hireTutor(true);
  4567.         }
  4568.         write(UserLocal.tutorMsg(id, duration));
  4569.     }
  4570.  
  4571.     /**
  4572.      * Shows tutor custom message (you decide which message the tutor will say).
  4573.      * @param message your custom message
  4574.      * @param width size of the message box
  4575.      * @param duration message duration
  4576.      */
  4577.     public void tutorCustomMsg(String message, int width, int duration) {
  4578.         if (!tutor) {
  4579.             hireTutor(true);
  4580.         }
  4581.         write(UserLocal.tutorMsg(message, width, duration));
  4582.     }
  4583.  
  4584.     public void setTransferField(int fieldID) {
  4585.         this.transferField = fieldID;
  4586.         this.transferFieldReq = fieldID == 0 ? 0 : getField().getId();
  4587.     }
  4588.  
  4589.     public int getTransferField() {
  4590.         return transferField;
  4591.     }
  4592.  
  4593.     public int getTransferFieldReq() {
  4594.         return transferFieldReq;
  4595.     }
  4596.  
  4597.     public void setMakingSkillLevel(int skillID, int level) {
  4598.         Skill skill = getSkill(skillID);
  4599.         if (skill != null) {
  4600.             skill.setCurrentLevel((level << 24) + getMakingSkillProficiency(skillID));
  4601.             addSkill(skill);
  4602.             write(WvsContext.changeSkillRecordResult(skill));
  4603.         }
  4604.     }
  4605.  
  4606.     public int getMakingSkillLevel(int skillID) {
  4607.         return (getSkillLevel(skillID) >> 24) <= 0 ? 0 : getSkillLevel(skillID) >> 24;
  4608.     }
  4609.  
  4610.     public void setMakingSkillProficiency(int skillID, int proficiency) {
  4611.         Skill skill = getSkill(skillID);
  4612.         if (skill != null) {
  4613.             skill.setCurrentLevel((getMakingSkillLevel(skillID) << 24) + proficiency);
  4614.             addSkill(skill);
  4615.             write(WvsContext.changeSkillRecordResult(skill));
  4616.         }
  4617.     }
  4618.  
  4619.     public int getMakingSkillProficiency(int skillID) {
  4620.         return (getSkillLevel(skillID) & 0xFFFFFF) <= 0 ? 0 : getSkillLevel(skillID) & 0xFFFFFF;
  4621.     }
  4622.  
  4623.     public void addMakingSkillProficiency(int skillID, int amount) {
  4624.         int makingSkillID = SkillConstants.recipeCodeToMakingSkillCode(skillID);
  4625.         int level = getMakingSkillLevel(makingSkillID);
  4626.  
  4627.         int neededExp = SkillConstants.getNeededProficiency(level);
  4628.         if (neededExp <= 0) {
  4629.             return;
  4630.         }
  4631.         int exp = getMakingSkillProficiency(makingSkillID);
  4632.         if (exp >= neededExp) {
  4633.             write(UserLocal.chatMsg(ChatType.GameDesc, "You can't gain any more Herbalism mastery until you level your skill."));
  4634.             write(UserLocal.chatMsg(ChatType.GameDesc, "See the appropriate NPC in Ardentmill to level up."));
  4635.             setMakingSkillProficiency(makingSkillID, neededExp);
  4636.             return;
  4637.         }
  4638.         int newExp = exp + amount;
  4639.         write(UserLocal.chatMsg(ChatType.GameDesc, SkillConstants.getMakingSkillName(makingSkillID) + "'s mastery increased. (+" + amount + ")"));
  4640.         if (newExp >= neededExp) {
  4641.             write(UserLocal.noticeMsg("You've accumulated " + SkillConstants.getMakingSkillName(makingSkillID) + " mastery. See an NPC in town to level up.", true));
  4642.             setMakingSkillProficiency(makingSkillID, neededExp);
  4643.         } else {
  4644.             setMakingSkillProficiency(makingSkillID, newExp);
  4645.         }
  4646.     }
  4647.  
  4648.     public void makingSkillLevelUp(int skillID) {
  4649.         int level = getMakingSkillLevel(skillID);
  4650.         int neededExp = SkillConstants.getNeededProficiency(level);
  4651.         if (neededExp <= 0) {
  4652.             return;
  4653.         }
  4654.         int exp = getMakingSkillProficiency(skillID);
  4655.         if (exp >= neededExp) {
  4656.             setMakingSkillProficiency(skillID, 0);
  4657.             setMakingSkillLevel(skillID, level + 1);
  4658.             Stat trait = Stat.craftEXP;
  4659.             switch (skillID) {
  4660.                 case 92000000:
  4661.                     trait = Stat.senseEXP;
  4662.                     break;
  4663.                 case 92010000:
  4664.                     trait = Stat.willEXP;
  4665.                     break;
  4666.             }
  4667.             addTraitExp(trait, (int) Math.pow(2, (level + 1) + 2));
  4668.             write(FieldPacket.fieldEffect(FieldEffect.playSound("profession/levelup", 100)));
  4669.         }
  4670.     }
  4671.  
  4672.     public void addNx(int nx) {
  4673.         getAccount().addNXCredit(nx);
  4674.         chatScriptMessage("You have gained " + nx + " NX.");
  4675.     }
  4676.  
  4677.     public void initBlessingSkillNames() {
  4678.         Account account = getAccount();
  4679.         Char fairyChar = null;
  4680.         for (Char chr : account.getCharacters()) {
  4681.             if (!chr.equals(this)
  4682.                     && chr.getLevel() >= 10
  4683.                     && (fairyChar == null || chr.getLevel() > fairyChar.getLevel())) {
  4684.                 fairyChar = chr;
  4685.             }
  4686.         }
  4687.         if (fairyChar != null) {
  4688.             setBlessingOfFairy(fairyChar.getName());
  4689.         }
  4690.         Char empressChar = null;
  4691.         for (Char chr : account.getCharacters()) {
  4692.             if (!chr.equals(this)
  4693.                     && (JobConstants.isCygnusKnight(chr.getJob()) || JobConstants.isMihile(chr.getJob())
  4694.                     && chr.getLevel() >= 5
  4695.                     && (empressChar == null || chr.getLevel() > empressChar.getLevel()))) {
  4696.                 empressChar = chr;
  4697.             }
  4698.         }
  4699.         if (empressChar != null) {
  4700.             setBlessingOfEmpress(empressChar.getName());
  4701.         }
  4702.     }
  4703.  
  4704.     public void initBlessingSkills() {
  4705.         Char fairyChar = getAccount().getCharByName(getBlessingOfFairy());
  4706.         if (fairyChar != null) {
  4707.             addSkill(SkillConstants.getFairyBlessingByJob(getJob()),
  4708.                     Math.min(20, fairyChar.getLevel() / 10), 20);
  4709.         }
  4710.         Char empressChar = getAccount().getCharByName(getBlessingOfEmpress());
  4711.         if (empressChar != null) {
  4712.             addSkill(SkillConstants.getEmpressBlessingByJob(getJob()),
  4713.                     Math.min(30, empressChar.getLevel() / 5), 30);
  4714.         }
  4715.     }
  4716.  
  4717.     public Map<Integer, Integer> getHyperPsdSkillsCooltimeR() {
  4718.         return hyperPsdSkillsCooltimeR;
  4719.     }
  4720.  
  4721.     public void setHyperPsdSkillsCooltimeR(Map<Integer, Integer> hyperPsdSkillsCooltimeR) {
  4722.         this.hyperPsdSkillsCooltimeR = hyperPsdSkillsCooltimeR;
  4723.     }
  4724.  
  4725.     public boolean isInvincible() {
  4726.         return isInvincible;
  4727.     }
  4728.  
  4729.     public void setInvincible(boolean invincible) {
  4730.         isInvincible = invincible;
  4731.     }
  4732.  
  4733.     public void setQuickslotKeys(List<Integer> quickslotKeys) {
  4734.         this.quickslotKeys = quickslotKeys;
  4735.     }
  4736.  
  4737.     public List<Integer> getQuickslotKeys() {
  4738.         return quickslotKeys;
  4739.     }
  4740.  
  4741.     public Dragon getDragon() {
  4742.         Dragon dragon = null;
  4743.         if (getJobHandler() instanceof Evan) {
  4744.             dragon = ((Evan) getJobHandler()).getDragon();
  4745.         }
  4746.         return dragon;
  4747.     }
  4748.  
  4749.     /**
  4750.      * Checks if this Char has a skill with at least a given level.
  4751.      * @param skillID the skill to get
  4752.      * @param slv the minimum skill level
  4753.      * @return whether or not this Char has the skill with the given skill level
  4754.      */
  4755.     public boolean hasSkillWithSlv(int skillID, short slv) {
  4756.         Skill skill = getSkill(skillID);
  4757.         return skill != null && skill.getCurrentLevel() >= slv;
  4758.     }
  4759.  
  4760.     public World getWorld() {
  4761.         return getClient().getWorld();
  4762.     }
  4763.  
  4764.     public Android getAndroid() {
  4765.         return android;
  4766.     }
  4767.  
  4768.     public void setAndroid(Android android) {
  4769.         this.android = android;
  4770.     }
  4771.  
  4772.     /**
  4773.      * Initializes this Char's Android according to their heart + android equips. Will not do anything if an Android
  4774.      * already exists.
  4775.      * @param override Whether or not to override the old Android if one exists.
  4776.      */
  4777.     public void initAndroid(boolean override) {
  4778.         if (getAndroid() == null || override) {
  4779.             Item heart = getEquippedItemByBodyPart(BodyPart.MechanicalHeart);
  4780.             Item android = getEquippedItemByBodyPart(BodyPart.Android);
  4781.             if (heart != null && android != null && ((Equip) heart).getAndroidGrade() + 3 >= ((Equip) android).getAndroidGrade()) {
  4782.                 int androidId = ((Equip) android).getAndroid();
  4783.                 AndroidInfo androidInfo = EtcData.getAndroidInfoById(androidId);
  4784.                 if (getAndroid() != null) {
  4785.                     getField().removeLife(getAndroid());
  4786.                 }
  4787.                 Android newAndroid = new Android(this, androidInfo);
  4788.                 if (getPosition() != null) {
  4789.                     newAndroid.setPosition(getPosition().deepCopy());
  4790.                 }
  4791.                 setAndroid(newAndroid);
  4792.             }
  4793.         }
  4794.     }
  4795.  
  4796.     public void useStatChangeItem(Item item, boolean consume) {
  4797.         TemporaryStatManager tsm = getTemporaryStatManager();
  4798.         int itemID = item.getItemId();
  4799.         Map<SpecStat, Integer> specStats = ItemData.getItemInfoByID(itemID).getSpecStats();
  4800.         if (specStats.size() > 0) {
  4801.             ItemBuffs.giveItemBuffsFromItemID(this, tsm, itemID);
  4802.         } else {
  4803.             switch (itemID) {
  4804.                 case 2050004: // All cure
  4805.                     tsm.removeAllDebuffs();
  4806.                     break;
  4807.                 default:
  4808.                     chatMessage(ChatType.Mob, String.format("Unhandled stat change item %d", itemID));
  4809.             }
  4810.         }
  4811.         if (consume) {
  4812.             consumeItem(item);
  4813.         }
  4814.         dispose();
  4815.     }
  4816.  
  4817.     public int getSpentActiveHyperSkillSp() {
  4818.         int sp = 0;
  4819.         for (Skill skill : getSkills()) {
  4820.             SkillInfo si = SkillData.getSkillInfoById(skill.getSkillId());
  4821.             if (si.getHyper() == 2) {
  4822.                 sp += skill.getCurrentLevel();
  4823.             }
  4824.         }
  4825.         return sp;
  4826.     }
  4827.  
  4828.     public int getSpentPassiveHyperSkillSp() {
  4829.         int sp = 0;
  4830.         for (Skill skill : getSkills()) {
  4831.             SkillInfo si = SkillData.getSkillInfoById(skill.getSkillId());
  4832.             if (si.getHyper() == 1) {
  4833.                 sp += skill.getCurrentLevel();
  4834.             }
  4835.         }
  4836.         return sp;
  4837.     }
  4838.     public PsychicArea addPsychicArea(PsychicArea pa) {
  4839.         psychicAreas.put(pa.localPsychicAreaKey, pa);
  4840.         return pa;
  4841.     }
  4842.  
  4843.     public void removePsychicArea(int psychicAreaKey) {
  4844.         this.psychicAreas.remove(psychicAreaKey);
  4845.     }
  4846.  
  4847.     public PsychicArea getPsychicArea(int psychicAreaKey) {
  4848.         return psychicAreas.getOrDefault(psychicAreaKey, null);
  4849.     }
  4850.  
  4851.     public void addPsychicLock(PsychicLock pl) {
  4852.         psychicLocks.put(pl.key, pl);
  4853.     }
  4854.  
  4855.     public void removePsychicLock(int key) {
  4856.         this.psychicLocks.remove(key);
  4857.     }
  4858.  
  4859.     public Map<Long, Integer> getItemBoughtAmounts() {
  4860.         return itemBoughtAmounts;
  4861.     }
  4862.  
  4863.     public void addItemBoughtAmount(long itemId, int amount) {
  4864.         getItemBoughtAmounts().put(itemId, amount);
  4865.     }
  4866.  
  4867.     public void increaseGolluxStack() {
  4868.         int maxStack = 5;
  4869.         TemporaryStatManager tsm = getTemporaryStatManager();
  4870.         int stack = tsm.getCurrentStats().get(Stigma) != null ? tsm.getCurrentStats().get(Stigma).get(0).nOption : 0;
  4871.         stack++;
  4872.         Option o = new Option();
  4873.         if (stack >= maxStack) {
  4874.             this.damage(getHP());
  4875.             stack = maxStack;
  4876.         }
  4877.         o.nOption = stack;
  4878.         o.rOption = 800;
  4879.         o.bOption = maxStack;
  4880.         tsm.putCharacterStatValue(Stigma, o);
  4881.         // no tOption  as it would probably be permanent (till death)
  4882.         tsm.sendSetStatPacket();
  4883.     }
  4884.  
  4885.     public Merchant getVisitingmerchant() {
  4886.         return visitingmerchant;
  4887.     }
  4888.  
  4889.     public void setVisitingmerchant(Merchant visitingmerchant) {
  4890.         this.visitingmerchant = visitingmerchant;
  4891.     }
  4892.  
  4893.     public Merchant getMerchant() {
  4894.         if (merchant == null) {
  4895.             findMerchant();
  4896.         }
  4897.         return merchant;
  4898.     }
  4899.  
  4900.     public void setMerchant(Merchant merchant) {
  4901.         this.merchant = merchant;
  4902.     }
  4903.  
  4904.     public void findMerchant() {
  4905.         ArrayList<Merchant> allmerchants = this.getWorld().getMerchants();
  4906.         for (Merchant m : allmerchants) {
  4907.             if (m.getOwnerID() == this.getId()) {
  4908.                 this.setMerchant(m);
  4909.                 break;
  4910.             }
  4911.         }
  4912.     }
  4913.  
  4914.     public void getItemsFromEmployeeTrunk() {
  4915.         EmployeeTrunk employeeTrunk = getAccount().getEmployeeTrunk();
  4916.         long earnings = employeeTrunk.getMoney();
  4917.         if (getMerchant() != null) {
  4918.             chatMessage("You have still got an open merchant at room: " + getMerchant().getField().getId() % 10 + " at channel: " + getMerchant().getField().getChannel());
  4919.             return;
  4920.         }
  4921.         if (!canAddMoney(earnings)) {
  4922.             chatMessage("You cannot hold that much mesos.");
  4923.             return;
  4924.         }
  4925.         List<MerchantItem> itemsMoved = new ArrayList<MerchantItem>();
  4926.         for (MerchantItem mi : employeeTrunk.getItems()) {
  4927.             Item i = mi.item.deepCopy();
  4928.             i.setQuantity(mi.bundles * i.getQuantity());
  4929.             if (getInventoryByType(i.getInvType()).canPickUp(i)) {
  4930.                 if (mi.bundles > 0) {    //remove MerchantItem from merchant and database but if bundles <= 0 but don't add to char inv
  4931.                     addItemToInventory(i);
  4932.                 }
  4933.                 itemsMoved.add(mi);
  4934.             }
  4935.         }
  4936.         employeeTrunk.getItems().removeAll(itemsMoved);
  4937.         if (getMerchant() != null) { //merchant can be null after server restart
  4938.             merchant.getItems().removeAll(itemsMoved);
  4939.         }
  4940.         addMoney(earnings);
  4941.         employeeTrunk.setMoney(0);
  4942.         DatabaseManager.saveToDB(employeeTrunk);
  4943.     }
  4944.  
  4945.     public Map<ScrollStat, Integer> getStatsBySetEffects() {
  4946.         HashMap<ScrollStat, Integer> stats = new HashMap<>();
  4947.         HashMap<Integer, Integer> setIdToLevel = new HashMap<>();
  4948.         for (Item item : getEquippedInventory().getItems()) {
  4949.             Equip equip = (Equip) item;
  4950.             int setItemId = equip.getSetItemID();
  4951.             if (setItemId > 0) {
  4952.                 int level = setIdToLevel.getOrDefault(setItemId, 0);
  4953.                 level++;
  4954.                 setIdToLevel.put(setItemId, level);
  4955.             }
  4956.         }
  4957.         for (Map.Entry<Integer, Integer> entry : setIdToLevel.entrySet()) {
  4958.             int setId = entry.getKey();
  4959.             int setLevel = entry.getValue();
  4960.             SetEffect setEffect = EtcData.getSetEffectInfoById(setId);
  4961.             for (int i = 1; i <= setLevel; i++) {
  4962.                 if (setEffect.getStatsByLevel(i) == null) {
  4963.                     continue;
  4964.                 }
  4965.                 for (Object effect : setEffect.getStatsByLevel(i)) {
  4966.                     if (effect instanceof net.swordie.ms.util.container.Tuple) {
  4967.                         ScrollStat ss = (ScrollStat) (((net.swordie.ms.util.container.Tuple) effect).getLeft());
  4968.                         int amount = (int) (((net.swordie.ms.util.container.Tuple) effect).getRight());
  4969.                         stats.put(ss, amount);
  4970.                     }
  4971.                 }
  4972.             }
  4973.         }
  4974.         return stats;
  4975.     }
  4976.  
  4977.     public List<ItemOption> getItemOptionsBySetEffects() {
  4978.         List<ItemOption> options = new ArrayList<>();
  4979.         HashMap<Integer, Integer> setIdToLevel = new HashMap<>();
  4980.         for (Item item : getEquippedInventory().getItems()) {
  4981.             Equip equip = (Equip) item;
  4982.             int setItemId = equip.getSetItemID();
  4983.             if (setItemId > 0) {
  4984.                 int level = setIdToLevel.getOrDefault(setItemId, 0);
  4985.                 level++;
  4986.                 setIdToLevel.put(setItemId, level);
  4987.             }
  4988.         }
  4989.         for (Map.Entry<Integer, Integer> entry : setIdToLevel.entrySet()) {
  4990.             int setId = entry.getKey();
  4991.             int setLevel = entry.getValue();
  4992.             SetEffect setEffect = EtcData.getSetEffectInfoById(setId);
  4993.             for (int i = 1; i <= setLevel; i++) {
  4994.                 if (setEffect.getStatsByLevel(i) == null) {
  4995.                     continue;
  4996.                 }
  4997.                 for (Object effect : setEffect.getStatsByLevel(i)) {
  4998.                     if (effect instanceof ItemOption) {
  4999.                         options.add((ItemOption) effect);
  5000.                     }
  5001.                 }
  5002.             }
  5003.         }
  5004.         return options;
  5005.     }
  5006.  
  5007.  
  5008.  
  5009.     public int getStatAmountSetEffect(BaseStat baseStat) {
  5010.         int amount = 0;
  5011.         Map<ScrollStat, Integer> stats = getStatsBySetEffects();
  5012.         for (Map.Entry<ScrollStat, Integer> entry : stats.entrySet()) {
  5013.             if (entry.getKey().getBaseStat() == baseStat) {
  5014.                 amount += entry.getValue();
  5015.             }
  5016.         }
  5017.  
  5018.         List<ItemOption> options = getItemOptionsBySetEffects();
  5019.         for (ItemOption option : options) {
  5020.             int id = option.getId();
  5021.             int level = option.getReqLevel();
  5022.             ItemOption io = ItemData.getItemOptionById(id);
  5023.             if (io != null) {
  5024.                 Map<BaseStat, Double> valMap = io.getStatValuesByLevel(level);
  5025.                 amount += valMap.getOrDefault(baseStat, 0D);
  5026.             }
  5027.         }
  5028.  
  5029.         return amount;
  5030.     }
  5031. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement