7402 lines
260 KiB
Java
7402 lines
260 KiB
Java
/*
|
|
This file is part of the OdinMS Maple Story Server
|
|
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
|
|
Matthias Butz <matze@odinms.de>
|
|
Jan Christian Meyer <vimes@odinms.de>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation version 3 as published by
|
|
the Free Software Foundation. You may not use, modify or distribute
|
|
this program under any other version of the GNU Affero General Public
|
|
License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package tools;
|
|
|
|
import client.Character;
|
|
import client.*;
|
|
import client.Character.SkillEntry;
|
|
import client.inventory.*;
|
|
import client.inventory.Equip.ScrollResult;
|
|
import client.keybind.KeyBinding;
|
|
import client.keybind.QuickslotBinding;
|
|
import client.newyear.NewYearCardRecord;
|
|
import client.status.MonsterStatus;
|
|
import client.status.MonsterStatusEffect;
|
|
import config.YamlConfig;
|
|
import constants.game.ExpTable;
|
|
import constants.game.GameConstants;
|
|
import constants.inventory.ItemConstants;
|
|
import constants.skills.Buccaneer;
|
|
import constants.skills.Corsair;
|
|
import constants.skills.ThunderBreaker;
|
|
import net.encryption.InitializationVector;
|
|
import net.opcodes.SendOpcode;
|
|
import net.packet.ByteBufOutPacket;
|
|
import net.packet.InPacket;
|
|
import net.packet.OutPacket;
|
|
import net.packet.Packet;
|
|
import net.server.PlayerCoolDownValueHolder;
|
|
import net.server.Server;
|
|
import net.server.channel.Channel;
|
|
import net.server.channel.handlers.PlayerInteractionHandler;
|
|
import net.server.channel.handlers.SummonDamageHandler.SummonAttackEntry;
|
|
import net.server.channel.handlers.WhisperHandler;
|
|
import net.server.guild.Alliance;
|
|
import net.server.guild.Guild;
|
|
import net.server.guild.MapleGuildSummary;
|
|
import net.server.world.MapleParty;
|
|
import net.server.world.MaplePartyCharacter;
|
|
import net.server.world.PartyOperation;
|
|
import net.server.world.World;
|
|
import server.CashShop.CashItem;
|
|
import server.CashShop.CashItemFactory;
|
|
import server.CashShop.SpecialCashItem;
|
|
import server.*;
|
|
import server.events.gm.MapleSnowball;
|
|
import server.life.MapleMonster;
|
|
import server.life.MapleNPC;
|
|
import server.life.MaplePlayerNPC;
|
|
import server.life.MobSkill;
|
|
import server.maps.*;
|
|
import server.maps.MapleMiniGame.MiniGameResult;
|
|
import server.movement.LifeMovementFragment;
|
|
|
|
import java.awt.*;
|
|
import java.net.InetAddress;
|
|
import java.sql.ResultSet;
|
|
import java.sql.SQLException;
|
|
import java.util.List;
|
|
import java.util.*;
|
|
import java.util.Map.Entry;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* @author Frz
|
|
*/
|
|
public class PacketCreator {
|
|
|
|
public static final List<Pair<Stat, Integer>> EMPTY_STATUPDATE = Collections.emptyList();
|
|
private final static long FT_UT_OFFSET = 116444736010800000L + (10000L * TimeZone.getDefault().getOffset(System.currentTimeMillis())); // normalize with timezone offset suggested by Ari
|
|
private final static long DEFAULT_TIME = 150842304000000000L;//00 80 05 BB 46 E6 17 02
|
|
public final static long ZERO_TIME = 94354848000000000L;//00 40 E0 FD 3B 37 4F 01
|
|
private final static long PERMANENT = 150841440000000000L; // 00 C0 9B 90 7D E5 17 02
|
|
|
|
public static long getTime(long utcTimestamp) {
|
|
if (utcTimestamp < 0 && utcTimestamp >= -3) {
|
|
if (utcTimestamp == -1) {
|
|
return DEFAULT_TIME; //high number ll
|
|
} else if (utcTimestamp == -2) {
|
|
return ZERO_TIME;
|
|
} else {
|
|
return PERMANENT;
|
|
}
|
|
}
|
|
|
|
return utcTimestamp * 10000 + FT_UT_OFFSET;
|
|
}
|
|
|
|
public static Packet showHpHealed(int cid, int amount) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(cid);
|
|
p.writeByte(0x0A); //Type
|
|
p.writeByte(amount);
|
|
return p;
|
|
}
|
|
|
|
private static void addRemainingSkillInfo(final OutPacket p, Character chr) {
|
|
int[] remainingSp = chr.getRemainingSps();
|
|
int effectiveLength = 0;
|
|
for (int j : remainingSp) {
|
|
if (j > 0) {
|
|
effectiveLength++;
|
|
}
|
|
}
|
|
|
|
p.writeByte(effectiveLength);
|
|
for (int i = 0; i < remainingSp.length; i++) {
|
|
if (remainingSp[i] > 0) {
|
|
p.writeByte(i + 1);
|
|
p.writeByte(remainingSp[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void addCharStats(OutPacket p, Character chr) {
|
|
p.writeInt(chr.getId()); // character id
|
|
p.writeFixedString(StringUtil.getRightPaddedStr(chr.getName(), '\0', 13));
|
|
p.writeByte(chr.getGender()); // gender (0 = male, 1 = female)
|
|
p.writeByte(chr.getSkinColor().getId()); // skin color
|
|
p.writeInt(chr.getFace()); // face
|
|
p.writeInt(chr.getHair()); // hair
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Pet pet = chr.getPet(i);
|
|
if (pet != null) //Checked GMS.. and your pets stay when going into the cash shop.
|
|
{
|
|
p.writeLong(pet.getUniqueId());
|
|
} else {
|
|
p.writeLong(0);
|
|
}
|
|
}
|
|
|
|
p.writeByte(chr.getLevel()); // level
|
|
p.writeShort(chr.getJob().getId()); // job
|
|
p.writeShort(chr.getStr()); // str
|
|
p.writeShort(chr.getDex()); // dex
|
|
p.writeShort(chr.getInt()); // int
|
|
p.writeShort(chr.getLuk()); // luk
|
|
p.writeShort(chr.getHp()); // hp (?)
|
|
p.writeShort(chr.getClientMaxHp()); // maxhp
|
|
p.writeShort(chr.getMp()); // mp (?)
|
|
p.writeShort(chr.getClientMaxMp()); // maxmp
|
|
p.writeShort(chr.getRemainingAp()); // remaining ap
|
|
if (GameConstants.hasSPTable(chr.getJob())) {
|
|
addRemainingSkillInfo(p, chr);
|
|
} else {
|
|
p.writeShort(chr.getRemainingSp()); // remaining sp
|
|
}
|
|
p.writeInt(chr.getExp()); // current exp
|
|
p.writeShort(chr.getFame()); // fame
|
|
p.writeInt(chr.getGachaExp()); //Gacha Exp
|
|
p.writeInt(chr.getMapId()); // current map id
|
|
p.writeByte(chr.getInitialSpawnpoint()); // spawnpoint
|
|
p.writeInt(0);
|
|
}
|
|
|
|
protected static void addCharLook(final OutPacket p, Character chr, boolean mega) {
|
|
p.writeByte(chr.getGender());
|
|
p.writeByte(chr.getSkinColor().getId()); // skin color
|
|
p.writeInt(chr.getFace()); // face
|
|
p.writeBool(!mega);
|
|
p.writeInt(chr.getHair()); // hair
|
|
addCharEquips(p, chr);
|
|
}
|
|
|
|
private static void addCharacterInfo(OutPacket p, Character chr) {
|
|
p.writeLong(-1);
|
|
p.writeByte(0);
|
|
addCharStats(p, chr);
|
|
p.writeByte(chr.getBuddylist().getCapacity());
|
|
|
|
if (chr.getLinkedName() == null) {
|
|
p.writeByte(0);
|
|
} else {
|
|
p.writeByte(1);
|
|
p.writeString(chr.getLinkedName());
|
|
}
|
|
|
|
p.writeInt(chr.getMeso());
|
|
addInventoryInfo(p, chr);
|
|
addSkillInfo(p, chr);
|
|
addQuestInfo(p, chr);
|
|
addMiniGameInfo(p, chr);
|
|
addRingInfo(p, chr);
|
|
addTeleportInfo(p, chr);
|
|
addMonsterBookInfo(p, chr);
|
|
addNewYearInfo(p, chr);
|
|
addAreaInfo(p, chr);//assuming it stayed here xd
|
|
p.writeShort(0);
|
|
}
|
|
|
|
private static void addNewYearInfo(OutPacket p, Character chr) {
|
|
Set<NewYearCardRecord> received = chr.getReceivedNewYearRecords();
|
|
|
|
p.writeShort(received.size());
|
|
for (NewYearCardRecord nyc : received) {
|
|
encodeNewYearCard(nyc, p);
|
|
}
|
|
}
|
|
|
|
private static void addTeleportInfo(OutPacket p, Character chr) {
|
|
final List<Integer> tele = chr.getTrockMaps();
|
|
final List<Integer> viptele = chr.getVipTrockMaps();
|
|
for (int i = 0; i < 5; i++) {
|
|
p.writeInt(tele.get(i));
|
|
}
|
|
for (int i = 0; i < 10; i++) {
|
|
p.writeInt(viptele.get(i));
|
|
}
|
|
}
|
|
|
|
private static void addMiniGameInfo(OutPacket p, Character chr) {
|
|
p.writeShort(0);
|
|
/*for (int m = size; m > 0; m--) {//nexon does this :P
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
}*/
|
|
}
|
|
|
|
private static void addAreaInfo(OutPacket p, Character chr) {
|
|
Map<Short, String> areaInfos = chr.getAreaInfos();
|
|
p.writeShort(areaInfos.size());
|
|
for (Short area : areaInfos.keySet()) {
|
|
p.writeShort(area);
|
|
p.writeString(areaInfos.get(area));
|
|
}
|
|
}
|
|
|
|
private static void addCharEquips(final OutPacket p, Character chr) {
|
|
Inventory equip = chr.getInventory(InventoryType.EQUIPPED);
|
|
Collection<Item> ii = MapleItemInformationProvider.getInstance().canWearEquipment(chr, equip.list());
|
|
Map<Short, Integer> myEquip = new LinkedHashMap<>();
|
|
Map<Short, Integer> maskedEquip = new LinkedHashMap<>();
|
|
for (Item item : ii) {
|
|
short pos = (byte) (item.getPosition() * -1);
|
|
if (pos < 100 && myEquip.get(pos) == null) {
|
|
myEquip.put(pos, item.getItemId());
|
|
} else if (pos > 100 && pos != 111) { // don't ask. o.o
|
|
pos -= 100;
|
|
if (myEquip.get(pos) != null) {
|
|
maskedEquip.put(pos, myEquip.get(pos));
|
|
}
|
|
myEquip.put(pos, item.getItemId());
|
|
} else if (myEquip.get(pos) != null) {
|
|
maskedEquip.put(pos, item.getItemId());
|
|
}
|
|
}
|
|
for (Entry<Short, Integer> entry : myEquip.entrySet()) {
|
|
p.writeByte(entry.getKey());
|
|
p.writeInt(entry.getValue());
|
|
}
|
|
p.writeByte(0xFF);
|
|
for (Entry<Short, Integer> entry : maskedEquip.entrySet()) {
|
|
p.writeByte(entry.getKey());
|
|
p.writeInt(entry.getValue());
|
|
}
|
|
p.writeByte(0xFF);
|
|
Item cWeapon = equip.getItem((short) -111);
|
|
p.writeInt(cWeapon != null ? cWeapon.getItemId() : 0);
|
|
for (int i = 0; i < 3; i++) {
|
|
if (chr.getPet(i) != null) {
|
|
p.writeInt(chr.getPet(i).getItemId());
|
|
} else {
|
|
p.writeInt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Packet setExtraPendantSlot(boolean toggleExtraSlot) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SET_EXTRA_PENDANT_SLOT);
|
|
p.writeBool(toggleExtraSlot);
|
|
return p;
|
|
}
|
|
|
|
private static void addCharEntry(OutPacket p, Character chr, boolean viewall) {
|
|
addCharStats(p, chr);
|
|
addCharLook(p, chr, false);
|
|
if (!viewall) {
|
|
p.writeByte(0);
|
|
}
|
|
if (chr.isGM() || chr.isGmJob()) { // thanks Daddy Egg (Ubaware), resinate for noticing GM jobs crashing on non-GM players account
|
|
p.writeByte(0);
|
|
return;
|
|
}
|
|
p.writeByte(1); // world rank enabled (next 4 ints are not sent if disabled) Short??
|
|
p.writeInt(chr.getRank()); // world rank
|
|
p.writeInt(chr.getRankMove()); // move (negative is downwards)
|
|
p.writeInt(chr.getJobRank()); // job rank
|
|
p.writeInt(chr.getJobRankMove()); // move (negative is downwards)
|
|
}
|
|
|
|
private static void addQuestInfo(OutPacket p, Character chr) {
|
|
List<QuestStatus> started = chr.getStartedQuests();
|
|
int startedSize = 0;
|
|
for (QuestStatus qs : started) {
|
|
if (qs.getInfoNumber() > 0) {
|
|
startedSize++;
|
|
}
|
|
startedSize++;
|
|
}
|
|
p.writeShort(startedSize);
|
|
for (QuestStatus qs : started) {
|
|
p.writeShort(qs.getQuest().getId());
|
|
p.writeString(qs.getProgressData());
|
|
|
|
short infoNumber = qs.getInfoNumber();
|
|
if (infoNumber > 0) {
|
|
QuestStatus iqs = chr.getQuest(infoNumber);
|
|
p.writeShort(infoNumber);
|
|
p.writeString(iqs.getProgressData());
|
|
}
|
|
}
|
|
List<QuestStatus> completed = chr.getCompletedQuests();
|
|
p.writeShort(completed.size());
|
|
for (QuestStatus qs : completed) {
|
|
p.writeShort(qs.getQuest().getId());
|
|
p.writeLong(getTime(qs.getCompletionTime()));
|
|
}
|
|
}
|
|
|
|
private static void addExpirationTime(final OutPacket p, long time) {
|
|
p.writeLong(getTime(time)); // offset expiration time issue found thanks to Thora
|
|
}
|
|
|
|
private static void addItemInfo(OutPacket p, Item item) {
|
|
addItemInfo(p, item, false);
|
|
}
|
|
|
|
protected static void addItemInfo(final OutPacket p, Item item, boolean zeroPosition) {
|
|
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
|
|
boolean isCash = ii.isCash(item.getItemId());
|
|
boolean isPet = item.getPetId() > -1;
|
|
boolean isRing = false;
|
|
Equip equip = null;
|
|
short pos = item.getPosition();
|
|
byte itemType = item.getItemType();
|
|
if (itemType == 1) {
|
|
equip = (Equip) item;
|
|
isRing = equip.getRingId() > -1;
|
|
}
|
|
if (!zeroPosition) {
|
|
if (equip != null) {
|
|
if (pos < 0) {
|
|
pos *= -1;
|
|
}
|
|
p.writeShort(pos > 100 ? pos - 100 : pos);
|
|
} else {
|
|
p.writeByte(pos);
|
|
}
|
|
}
|
|
p.writeByte(itemType);
|
|
p.writeInt(item.getItemId());
|
|
p.writeBool(isCash);
|
|
if (isCash) {
|
|
p.writeLong(isPet ? item.getPetId() : isRing ? equip.getRingId() : item.getCashId());
|
|
}
|
|
addExpirationTime(p, item.getExpiration());
|
|
if (isPet) {
|
|
Pet pet = item.getPet();
|
|
p.writeFixedString(StringUtil.getRightPaddedStr(pet.getName(), '\0', 13));
|
|
p.writeByte(pet.getLevel());
|
|
p.writeShort(pet.getCloseness());
|
|
p.writeByte(pet.getFullness());
|
|
addExpirationTime(p, item.getExpiration());
|
|
p.writeInt(pet.getPetFlag()); /* pet flags noticed by lrenex & Spoon */
|
|
|
|
p.writeBytes(new byte[]{(byte) 0x50, (byte) 0x46}); //wonder what this is
|
|
p.writeInt(0);
|
|
return;
|
|
}
|
|
if (equip == null) {
|
|
p.writeShort(item.getQuantity());
|
|
p.writeString(item.getOwner());
|
|
p.writeShort(item.getFlag()); // flag
|
|
|
|
if (ItemConstants.isRechargeable(item.getItemId())) {
|
|
p.writeInt(2);
|
|
p.writeBytes(new byte[]{(byte) 0x54, 0, 0, (byte) 0x34});
|
|
}
|
|
return;
|
|
}
|
|
p.writeByte(equip.getUpgradeSlots()); // upgrade slots
|
|
p.writeByte(equip.getLevel()); // level
|
|
p.writeShort(equip.getStr()); // str
|
|
p.writeShort(equip.getDex()); // dex
|
|
p.writeShort(equip.getInt()); // int
|
|
p.writeShort(equip.getLuk()); // luk
|
|
p.writeShort(equip.getHp()); // hp
|
|
p.writeShort(equip.getMp()); // mp
|
|
p.writeShort(equip.getWatk()); // watk
|
|
p.writeShort(equip.getMatk()); // matk
|
|
p.writeShort(equip.getWdef()); // wdef
|
|
p.writeShort(equip.getMdef()); // mdef
|
|
p.writeShort(equip.getAcc()); // accuracy
|
|
p.writeShort(equip.getAvoid()); // avoid
|
|
p.writeShort(equip.getHands()); // hands
|
|
p.writeShort(equip.getSpeed()); // speed
|
|
p.writeShort(equip.getJump()); // jump
|
|
p.writeString(equip.getOwner()); // owner name
|
|
p.writeShort(equip.getFlag()); //Item Flags
|
|
|
|
if (isCash) {
|
|
for (int i = 0; i < 10; i++) {
|
|
p.writeByte(0x40);
|
|
}
|
|
} else {
|
|
int itemLevel = equip.getItemLevel();
|
|
|
|
long expNibble = (ExpTable.getExpNeededForLevel(ii.getEquipLevelReq(item.getItemId())) * equip.getItemExp());
|
|
expNibble /= ExpTable.getEquipExpNeededForLevel(itemLevel);
|
|
|
|
p.writeByte(0);
|
|
p.writeByte(itemLevel); //Item Level
|
|
p.writeInt((int) expNibble);
|
|
p.writeInt(equip.getVicious()); //WTF NEXON ARE YOU SERIOUS?
|
|
p.writeLong(0);
|
|
}
|
|
p.writeLong(getTime(-2));
|
|
p.writeInt(-1);
|
|
|
|
}
|
|
|
|
private static void addInventoryInfo(OutPacket p, Character chr) {
|
|
for (byte i = 1; i <= 5; i++) {
|
|
p.writeByte(chr.getInventory(InventoryType.getByType(i)).getSlotLimit());
|
|
}
|
|
p.writeLong(getTime(-2));
|
|
Inventory iv = chr.getInventory(InventoryType.EQUIPPED);
|
|
Collection<Item> equippedC = iv.list();
|
|
List<Item> equipped = new ArrayList<>(equippedC.size());
|
|
List<Item> equippedCash = new ArrayList<>(equippedC.size());
|
|
for (Item item : equippedC) {
|
|
if (item.getPosition() <= -100) {
|
|
equippedCash.add(item);
|
|
} else {
|
|
equipped.add(item);
|
|
}
|
|
}
|
|
for (Item item : equipped) { // equipped doesn't actually need sorting, thanks Pllsz
|
|
addItemInfo(p, item);
|
|
}
|
|
p.writeShort(0); // start of equip cash
|
|
for (Item item : equippedCash) {
|
|
addItemInfo(p, item);
|
|
}
|
|
p.writeShort(0); // start of equip inventory
|
|
for (Item item : chr.getInventory(InventoryType.EQUIP).list()) {
|
|
addItemInfo(p, item);
|
|
}
|
|
p.writeInt(0);
|
|
for (Item item : chr.getInventory(InventoryType.USE).list()) {
|
|
addItemInfo(p, item);
|
|
}
|
|
p.writeByte(0);
|
|
for (Item item : chr.getInventory(InventoryType.SETUP).list()) {
|
|
addItemInfo(p, item);
|
|
}
|
|
p.writeByte(0);
|
|
for (Item item : chr.getInventory(InventoryType.ETC).list()) {
|
|
addItemInfo(p, item);
|
|
}
|
|
p.writeByte(0);
|
|
for (Item item : chr.getInventory(InventoryType.CASH).list()) {
|
|
addItemInfo(p, item);
|
|
}
|
|
}
|
|
|
|
private static void addSkillInfo(OutPacket p, Character chr) {
|
|
p.writeByte(0); // start of skills
|
|
Map<Skill, Character.SkillEntry> skills = chr.getSkills();
|
|
int skillsSize = skills.size();
|
|
// We don't want to include any hidden skill in this, so subtract them from the size list and ignore them.
|
|
for (Entry<Skill, SkillEntry> skill : skills.entrySet()) {
|
|
if (GameConstants.isHiddenSkills(skill.getKey().getId())) {
|
|
skillsSize--;
|
|
}
|
|
}
|
|
p.writeShort(skillsSize);
|
|
for (Entry<Skill, SkillEntry> skill : skills.entrySet()) {
|
|
if (GameConstants.isHiddenSkills(skill.getKey().getId())) {
|
|
continue;
|
|
}
|
|
p.writeInt(skill.getKey().getId());
|
|
p.writeInt(skill.getValue().skillevel);
|
|
addExpirationTime(p, skill.getValue().expiration);
|
|
if (skill.getKey().isFourthJob()) {
|
|
p.writeInt(skill.getValue().masterlevel);
|
|
}
|
|
}
|
|
p.writeShort(chr.getAllCooldowns().size());
|
|
for (PlayerCoolDownValueHolder cooling : chr.getAllCooldowns()) {
|
|
p.writeInt(cooling.skillId);
|
|
int timeLeft = (int) (cooling.length + cooling.startTime - System.currentTimeMillis());
|
|
p.writeShort(timeLeft / 1000);
|
|
}
|
|
}
|
|
|
|
private static void addMonsterBookInfo(OutPacket p, Character chr) {
|
|
p.writeInt(chr.getMonsterBookCover()); // cover
|
|
p.writeByte(0);
|
|
Map<Integer, Integer> cards = chr.getMonsterBook().getCards();
|
|
p.writeShort(cards.size());
|
|
for (Entry<Integer, Integer> all : cards.entrySet()) {
|
|
p.writeShort(all.getKey() % 10000); // Id
|
|
p.writeByte(all.getValue()); // Level
|
|
}
|
|
}
|
|
|
|
public static Packet sendGuestTOS() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.GUEST_ID_LOGIN);
|
|
p.writeShort(0x100);
|
|
p.writeInt(Randomizer.nextInt(999999));
|
|
p.writeLong(0);
|
|
p.writeLong(getTime(-2));
|
|
p.writeLong(getTime(System.currentTimeMillis()));
|
|
p.writeInt(0);
|
|
p.writeString("http://maplesolaxia.com");
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a hello packet.
|
|
*
|
|
* @param mapleVersion The maple client version.
|
|
* @param sendIv the IV in use by the server for sending
|
|
* @param recvIv the IV in use by the server for receiving
|
|
* @return
|
|
*/
|
|
public static Packet getHello(short mapleVersion, InitializationVector sendIv, InitializationVector recvIv) {
|
|
OutPacket p = new ByteBufOutPacket();
|
|
p.writeShort(0x0E);
|
|
p.writeShort(mapleVersion);
|
|
p.writeShort(1);
|
|
p.writeByte(49);
|
|
p.writeBytes(recvIv.getBytes());
|
|
p.writeBytes(sendIv.getBytes());
|
|
p.writeByte(8);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a ping packet.
|
|
*
|
|
* @return The packet.
|
|
*/
|
|
public static Packet getPing() {
|
|
return OutPacket.create(SendOpcode.PING);
|
|
}
|
|
|
|
/**
|
|
* Gets a login failed packet.
|
|
* <p>
|
|
* Possible values for <code>reason</code>:<br> 3: ID deleted or blocked<br>
|
|
* 4: Incorrect password<br> 5: Not a registered id<br> 6: System error<br>
|
|
* 7: Already logged in<br> 8: System error<br> 9: System error<br> 10:
|
|
* Cannot process so many connections<br> 11: Only users older than 20 can
|
|
* use this channel<br> 13: Unable to log on as master at this ip<br> 14:
|
|
* Wrong gateway or personal info and weird korean button<br> 15: Processing
|
|
* request with that korean button!<br> 16: Please verify your account
|
|
* through email...<br> 17: Wrong gateway or personal info<br> 21: Please
|
|
* verify your account through email...<br> 23: License agreement<br> 25:
|
|
* Maple Europe notice =[ FUCK YOU NEXON<br> 27: Some weird full client
|
|
* notice, probably for trial versions<br>
|
|
*
|
|
* @param reason The reason logging in failed.
|
|
* @return The login failed packet.
|
|
*/
|
|
public static Packet getLoginFailed(int reason) {
|
|
OutPacket p = OutPacket.create(SendOpcode.LOGIN_STATUS);
|
|
p.writeByte(reason);
|
|
p.writeByte(0);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a login failed packet.
|
|
* <p>
|
|
* Possible values for <code>reason</code>:<br> 2: ID deleted or blocked<br>
|
|
* 3: ID deleted or blocked<br> 4: Incorrect password<br> 5: Not a
|
|
* registered id<br> 6: Trouble logging into the game?<br> 7: Already logged
|
|
* in<br> 8: Trouble logging into the game?<br> 9: Trouble logging into the
|
|
* game?<br> 10: Cannot process so many connections<br> 11: Only users older
|
|
* than 20 can use this channel<br> 12: Trouble logging into the game?<br>
|
|
* 13: Unable to log on as master at this ip<br> 14: Wrong gateway or
|
|
* personal info and weird korean button<br> 15: Processing request with
|
|
* that korean button!<br> 16: Please verify your account through
|
|
* email...<br> 17: Wrong gateway or personal info<br> 21: Please verify
|
|
* your account through email...<br> 23: Crashes<br> 25: Maple Europe notice
|
|
* =[ FUCK YOU NEXON<br> 27: Some weird full client notice, probably for
|
|
* trial versions<br>
|
|
*
|
|
* @param reason The reason logging in failed.
|
|
* @return The login failed packet.
|
|
*/
|
|
public static Packet getAfterLoginError(int reason) {//same as above o.o
|
|
OutPacket p = OutPacket.create(SendOpcode.SELECT_CHARACTER_BY_VAC);
|
|
p.writeShort(reason);//using other types than stated above = CRASH
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendPolice() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAKE_GM_NOTICE);
|
|
p.writeByte(0);//doesn't even matter what value
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendPolice(String text) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DATA_CRC_CHECK_FAILED);
|
|
p.writeString(text);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getPermBan(byte reason) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.LOGIN_STATUS);
|
|
p.writeByte(2); // Account is banned
|
|
p.writeByte(0);
|
|
p.writeInt(0);
|
|
p.writeByte(0);
|
|
p.writeLong(getTime(-1));
|
|
return p;
|
|
}
|
|
|
|
public static Packet getTempBan(long timestampTill, byte reason) {
|
|
OutPacket p = OutPacket.create(SendOpcode.LOGIN_STATUS);
|
|
p.writeByte(2);
|
|
p.writeByte(0);
|
|
p.writeInt(0);
|
|
p.writeByte(reason);
|
|
p.writeLong(getTime(timestampTill)); // Tempban date is handled as a 64-bit long, number of 100NS intervals since 1/1/1601. Lulz.
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a successful authentication packet.
|
|
*
|
|
* @param c
|
|
* @return the successful authentication packet
|
|
*/
|
|
public static Packet getAuthSuccess(Client c) {
|
|
Server.getInstance().loadAccountCharacters(c); // locks the login session until data is recovered from the cache or the DB.
|
|
Server.getInstance().loadAccountStorages(c);
|
|
|
|
final OutPacket p = OutPacket.create(SendOpcode.LOGIN_STATUS);
|
|
p.writeInt(0);
|
|
p.writeShort(0);
|
|
p.writeInt(c.getAccID());
|
|
p.writeByte(c.getGender());
|
|
|
|
boolean canFly = Server.getInstance().canFly(c.getAccID());
|
|
p.writeBool((YamlConfig.config.server.USE_ENFORCE_ADMIN_ACCOUNT || canFly) && c.getGMLevel() > 1); // thanks Steve(kaito1410) for pointing the GM account boolean here
|
|
p.writeByte(((YamlConfig.config.server.USE_ENFORCE_ADMIN_ACCOUNT || canFly) && c.getGMLevel() > 1) ? 0x80 : 0); // Admin Byte. 0x80,0x40,0x20.. Rubbish.
|
|
p.writeByte(0); // Country Code.
|
|
|
|
p.writeString(c.getAccountName());
|
|
p.writeByte(0);
|
|
|
|
p.writeByte(0); // IsQuietBan
|
|
p.writeLong(0);//IsQuietBanTimeStamp
|
|
p.writeLong(0); //CreationTimeStamp
|
|
|
|
p.writeInt(1); // 1: Remove the "Select the world you want to play in"
|
|
|
|
p.writeByte(YamlConfig.config.server.ENABLE_PIN && !c.canBypassPin() ? 0 : 1); // 0 = Pin-System Enabled, 1 = Disabled
|
|
p.writeByte(YamlConfig.config.server.ENABLE_PIC && !c.canBypassPic() ? (c.getPic() == null || c.getPic().equals("") ? 0 : 1) : 2); // 0 = Register PIC, 1 = Ask for PIC, 2 = Disabled
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet detailing a PIN operation.
|
|
* <p>
|
|
* Possible values for <code>mode</code>:<br> 0 - PIN was accepted<br> 1 -
|
|
* Register a new PIN<br> 2 - Invalid pin / Reenter<br> 3 - Connection
|
|
* failed due to system error<br> 4 - Enter the pin
|
|
*
|
|
* @param mode The mode.
|
|
* @return
|
|
*/
|
|
private static Packet pinOperation(byte mode) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CHECK_PINCODE);
|
|
p.writeByte(mode);
|
|
return p;
|
|
}
|
|
|
|
public static Packet pinRegistered() {
|
|
OutPacket p = OutPacket.create(SendOpcode.UPDATE_PINCODE);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet requestPin() {
|
|
return pinOperation((byte) 4);
|
|
}
|
|
|
|
public static Packet requestPinAfterFailure() {
|
|
return pinOperation((byte) 2);
|
|
}
|
|
|
|
public static Packet registerPin() {
|
|
return pinOperation((byte) 1);
|
|
}
|
|
|
|
public static Packet pinAccepted() {
|
|
return pinOperation((byte) 0);
|
|
}
|
|
|
|
public static Packet wrongPic() {
|
|
OutPacket p = OutPacket.create(SendOpcode.CHECK_SPW_RESULT);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet detailing a server and its channels.
|
|
*
|
|
* @param serverId
|
|
* @param serverName The name of the server.
|
|
* @param flag
|
|
* @param eventmsg
|
|
* @param channelLoad Load of the channel - 1200 seems to be max.
|
|
* @return The server info packet.
|
|
*/
|
|
public static Packet getServerList(int serverId, String serverName, int flag, String eventmsg, List<Channel> channelLoad) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SERVERLIST);
|
|
p.writeByte(serverId);
|
|
p.writeString(serverName);
|
|
p.writeByte(flag);
|
|
p.writeString(eventmsg);
|
|
p.writeByte(100); // rate modifier, don't ask O.O!
|
|
p.writeByte(0); // event xp * 2.6 O.O!
|
|
p.writeByte(100); // rate modifier, don't ask O.O!
|
|
p.writeByte(0); // drop rate * 2.6
|
|
p.writeByte(0);
|
|
p.writeByte(channelLoad.size());
|
|
for (Channel ch : channelLoad) {
|
|
p.writeString(serverName + "-" + ch.getId());
|
|
p.writeInt(ch.getChannelCapacity());
|
|
|
|
// thanks GabrielSin for this channel packet structure part
|
|
p.writeByte(1);// nWorldID
|
|
p.writeByte(ch.getId() - 1);// nChannelID
|
|
p.writeBool(false);// bAdultChannel
|
|
}
|
|
p.writeShort(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet saying that the server list is over.
|
|
*
|
|
* @return The end of server list packet.
|
|
*/
|
|
public static Packet getEndOfServerList() {
|
|
OutPacket p = OutPacket.create(SendOpcode.SERVERLIST);
|
|
p.writeByte(0xFF);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet detailing a server status message.
|
|
* <p>
|
|
* Possible values for <code>status</code>:<br> 0 - Normal<br> 1 - Highly
|
|
* populated<br> 2 - Full
|
|
*
|
|
* @param status The server status.
|
|
* @return The server status packet.
|
|
*/
|
|
public static Packet getServerStatus(int status) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SERVERSTATUS);
|
|
p.writeShort(status);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client the IP of the channel server.
|
|
*
|
|
* @param inetAddr The InetAddress of the requested channel server.
|
|
* @param port The port the channel is on.
|
|
* @param clientId The ID of the client.
|
|
* @return The server IP packet.
|
|
*/
|
|
public static Packet getServerIP(InetAddress inetAddr, int port, int clientId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SERVER_IP);
|
|
p.writeShort(0);
|
|
byte[] addr = inetAddr.getAddress();
|
|
p.writeBytes(addr);
|
|
p.writeShort(port);
|
|
p.writeInt(clientId);
|
|
p.writeBytes(new byte[]{0, 0, 0, 0, 0});
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client the IP of the new channel.
|
|
*
|
|
* @param inetAddr The InetAddress of the requested channel server.
|
|
* @param port The port the channel is on.
|
|
* @return The server IP packet.
|
|
*/
|
|
public static Packet getChannelChange(InetAddress inetAddr, int port) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CHANGE_CHANNEL);
|
|
p.writeByte(1);
|
|
byte[] addr = inetAddr.getAddress();
|
|
p.writeBytes(addr);
|
|
p.writeShort(port);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet with a list of characters.
|
|
*
|
|
* @param c The Client to load characters of.
|
|
* @param serverId The ID of the server requested.
|
|
* @param status The charlist request result.
|
|
* @return The character list packet.
|
|
* <p>
|
|
* Possible values for <code>status</code>:
|
|
* <br> 2: ID deleted or blocked<br>
|
|
* <br> 3: ID deleted or blocked<br>
|
|
* <br> 4: Incorrect password<br>
|
|
* <br> 5: Not an registered ID<br>
|
|
* <br> 6: Trouble logging in?<br>
|
|
* <br> 10: Server handling too many connections<br>
|
|
* <br> 11: Only 20 years or older<br>
|
|
* <br> 13: Unable to log as master at IP<br>
|
|
* <br> 14: Wrong gateway or personal info<br>
|
|
* <br> 15: Still processing request<br>
|
|
* <br> 16: Verify account via email<br>
|
|
* <br> 17: Wrong gateway or personal info<br>
|
|
* <br> 21: Verify account via email<br>
|
|
*/
|
|
public static Packet getCharList(Client c, int serverId, int status) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CHARLIST);
|
|
p.writeByte(status);
|
|
List<Character> chars = c.loadCharacters(serverId);
|
|
p.writeByte((byte) chars.size());
|
|
for (Character chr : chars) {
|
|
addCharEntry(p, chr, false);
|
|
}
|
|
|
|
p.writeByte(YamlConfig.config.server.ENABLE_PIC && !c.canBypassPic() ? (c.getPic() == null || c.getPic().equals("") ? 0 : 1) : 2);
|
|
p.writeInt(YamlConfig.config.server.COLLECTIVE_CHARSLOT ? chars.size() + c.getAvailableCharacterSlots() : c.getCharacterSlots());
|
|
return p;
|
|
}
|
|
|
|
public static Packet enableTV() {
|
|
OutPacket p = OutPacket.create(SendOpcode.ENABLE_TV);
|
|
p.writeInt(0);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Removes TV
|
|
*
|
|
* @return The Remove TV Packet
|
|
*/
|
|
public static Packet removeTV() {
|
|
return OutPacket.create(SendOpcode.REMOVE_TV);
|
|
}
|
|
|
|
/**
|
|
* Sends MapleTV
|
|
*
|
|
* @param chr The character shown in TV
|
|
* @param messages The message sent with the TV
|
|
* @param type The type of TV
|
|
* @param partner The partner shown with chr
|
|
* @return the SEND_TV packet
|
|
*/
|
|
public static Packet sendTV(Character chr, List<String> messages, int type, Character partner) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SEND_TV);
|
|
p.writeByte(partner != null ? 3 : 1);
|
|
p.writeByte(type); //Heart = 2 Star = 1 Normal = 0
|
|
addCharLook(p, chr, false);
|
|
p.writeString(chr.getName());
|
|
if (partner != null) {
|
|
p.writeString(partner.getName());
|
|
} else {
|
|
p.writeShort(0);
|
|
}
|
|
for (int i = 0; i < messages.size(); i++) {
|
|
if (i == 4 && messages.get(4).length() > 15) {
|
|
p.writeString(messages.get(4).substring(0, 15));
|
|
} else {
|
|
p.writeString(messages.get(i));
|
|
}
|
|
}
|
|
p.writeInt(1337); // time limit shit lol 'Your thing still start in blah blah seconds'
|
|
if (partner != null) {
|
|
addCharLook(p, partner, false);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets character info for a character.
|
|
*
|
|
* @param chr The character to get info about.
|
|
* @return The character info packet.
|
|
*/
|
|
public static Packet getCharInfo(Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SET_FIELD);
|
|
p.writeInt(chr.getClient().getChannel() - 1);
|
|
p.writeByte(1);
|
|
p.writeByte(1);
|
|
p.writeShort(0);
|
|
for (int i = 0; i < 3; i++) {
|
|
p.writeInt(Randomizer.nextInt());
|
|
}
|
|
addCharacterInfo(p, chr);
|
|
p.writeLong(getTime(System.currentTimeMillis()));
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets an empty stat update.
|
|
*
|
|
* @return The empty stat update packet.
|
|
*/
|
|
public static Packet enableActions() {
|
|
return updatePlayerStats(EMPTY_STATUPDATE, true, null);
|
|
}
|
|
|
|
/**
|
|
* Gets an update for specified stats.
|
|
*
|
|
* @param stats The list of stats to update.
|
|
* @param enableActions Allows actions after the update.
|
|
* @param chr The update target.
|
|
* @return The stat update packet.
|
|
*/
|
|
public static Packet updatePlayerStats(List<Pair<Stat, Integer>> stats, boolean enableActions, Character chr) {
|
|
OutPacket p = OutPacket.create(SendOpcode.STAT_CHANGED);
|
|
p.writeBool(enableActions);
|
|
int updateMask = 0;
|
|
for (Pair<Stat, Integer> statupdate : stats) {
|
|
updateMask |= statupdate.getLeft().getValue();
|
|
}
|
|
List<Pair<Stat, Integer>> mystats = stats;
|
|
if (mystats.size() > 1) {
|
|
mystats.sort((o1, o2) -> {
|
|
int val1 = o1.getLeft().getValue();
|
|
int val2 = o2.getLeft().getValue();
|
|
return (val1 < val2 ? -1 : (val1 == val2 ? 0 : 1));
|
|
});
|
|
}
|
|
p.writeInt(updateMask);
|
|
for (Pair<Stat, Integer> statupdate : mystats) {
|
|
if (statupdate.getLeft().getValue() >= 1) {
|
|
if (statupdate.getLeft().getValue() == 0x1) {
|
|
p.writeByte(statupdate.getRight().byteValue());
|
|
} else if (statupdate.getLeft().getValue() <= 0x4) {
|
|
p.writeInt(statupdate.getRight());
|
|
} else if (statupdate.getLeft().getValue() < 0x20) {
|
|
p.writeByte(statupdate.getRight().shortValue());
|
|
} else if (statupdate.getLeft().getValue() == 0x8000) {
|
|
if (GameConstants.hasSPTable(chr.getJob())) {
|
|
addRemainingSkillInfo(p, chr);
|
|
} else {
|
|
p.writeShort(statupdate.getRight().shortValue());
|
|
}
|
|
} else if (statupdate.getLeft().getValue() < 0xFFFF) {
|
|
p.writeShort(statupdate.getRight().shortValue());
|
|
} else if (statupdate.getLeft().getValue() == 0x20000) {
|
|
p.writeShort(statupdate.getRight().shortValue());
|
|
} else {
|
|
p.writeInt(statupdate.getRight());
|
|
}
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client to change maps.
|
|
*
|
|
* @param to The <code>MapleMap</code> to warp to.
|
|
* @param spawnPoint The spawn portal number to spawn at.
|
|
* @param chr The character warping to <code>to</code>
|
|
* @return The map change packet.
|
|
*/
|
|
public static Packet getWarpToMap(MapleMap to, int spawnPoint, Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SET_FIELD);
|
|
p.writeInt(chr.getClient().getChannel() - 1);
|
|
p.writeInt(0);//updated
|
|
p.writeByte(0);//updated
|
|
p.writeInt(to.getId());
|
|
p.writeByte(spawnPoint);
|
|
p.writeShort(chr.getHp());
|
|
p.writeBool(chr.isChasing());
|
|
if (chr.isChasing()) {
|
|
chr.setChasing(false);
|
|
p.writeInt(chr.getPosition().x);
|
|
p.writeInt(chr.getPosition().y);
|
|
}
|
|
p.writeLong(getTime(Server.getInstance().getCurrentTime()));
|
|
return p;
|
|
}
|
|
|
|
public static Packet getWarpToMap(MapleMap to, int spawnPoint, Point spawnPosition, Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SET_FIELD);
|
|
p.writeInt(chr.getClient().getChannel() - 1);
|
|
p.writeInt(0);//updated
|
|
p.writeByte(0);//updated
|
|
p.writeInt(to.getId());
|
|
p.writeByte(spawnPoint);
|
|
p.writeShort(chr.getHp());
|
|
p.writeBool(true);
|
|
p.writeInt(spawnPosition.x); // spawn position placement thanks to Arnah (Vertisy)
|
|
p.writeInt(spawnPosition.y);
|
|
p.writeLong(getTime(Server.getInstance().getCurrentTime()));
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet to spawn a portal.
|
|
*
|
|
* @param townId The ID of the town the portal goes to.
|
|
* @param targetId The ID of the target.
|
|
* @param pos Where to put the portal.
|
|
* @return The portal spawn packet.
|
|
*/
|
|
public static Packet spawnPortal(int townId, int targetId, Point pos) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_PORTAL);
|
|
p.writeInt(townId);
|
|
p.writeInt(targetId);
|
|
p.writePos(pos);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet to spawn a door.
|
|
*
|
|
* @param ownerid The door's owner ID.
|
|
* @param pos The position of the door.
|
|
* @param launched Already deployed the door.
|
|
* @return The remove door packet.
|
|
*/
|
|
public static Packet spawnDoor(int ownerid, Point pos, boolean launched) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_DOOR);
|
|
p.writeBool(launched);
|
|
p.writeInt(ownerid);
|
|
p.writePos(pos);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet to remove a door.
|
|
*
|
|
* @param ownerId The door's owner ID.
|
|
* @param town
|
|
* @return The remove door packet.
|
|
*/
|
|
public static Packet removeDoor(int ownerId, boolean town) {
|
|
final OutPacket p;
|
|
if (town) {
|
|
p = OutPacket.create(SendOpcode.SPAWN_PORTAL);
|
|
p.writeInt(999999999);
|
|
p.writeInt(999999999);
|
|
} else {
|
|
p = OutPacket.create(SendOpcode.REMOVE_DOOR);
|
|
p.writeByte(0);
|
|
p.writeInt(ownerId);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet to spawn a special map object.
|
|
*
|
|
* @param summon
|
|
* @param skillLevel The level of the skill used.
|
|
* @param animated Animated spawn?
|
|
* @return The spawn packet for the map object.
|
|
*/
|
|
public static Packet spawnSummon(MapleSummon summon, boolean animated) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_SPECIAL_MAPOBJECT);
|
|
p.writeInt(summon.getOwner().getId());
|
|
p.writeInt(summon.getObjectId());
|
|
p.writeInt(summon.getSkill());
|
|
p.writeByte(0x0A); //v83
|
|
p.writeByte(summon.getSkillLevel());
|
|
p.writePos(summon.getPosition());
|
|
p.writeByte(summon.getStance()); //bMoveAction & foothold, found thanks to Rien dev team
|
|
p.writeShort(0);
|
|
p.writeByte(summon.getMovementType().getValue()); // 0 = don't move, 1 = follow (4th mage summons?), 2/4 = only tele follow, 3 = bird follow
|
|
p.writeBool(!summon.isPuppet()); // 0 and the summon can't attack - but puppets don't attack with 1 either ^.-
|
|
p.writeBool(!animated);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet to remove a special map object.
|
|
*
|
|
* @param summon
|
|
* @param animated Animated removal?
|
|
* @return The packet removing the object.
|
|
*/
|
|
public static Packet removeSummon(MapleSummon summon, boolean animated) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REMOVE_SPECIAL_MAPOBJECT);
|
|
p.writeInt(summon.getOwner().getId());
|
|
p.writeInt(summon.getObjectId());
|
|
p.writeByte(animated ? 4 : 1); // ?
|
|
return p;
|
|
}
|
|
|
|
public static Packet spawnKite(int objId, int itemId, String name, String msg, Point pos, int ft) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_KITE);
|
|
p.writeInt(objId);
|
|
p.writeInt(itemId);
|
|
p.writeString(msg);
|
|
p.writeString(name);
|
|
p.writeShort(pos.x);
|
|
p.writeShort(ft);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeKite(int objId, int animationType) { // thanks to Arnah (Vertisy)
|
|
OutPacket p = OutPacket.create(SendOpcode.REMOVE_KITE);
|
|
p.writeByte(animationType); // 0 is 10/10, 1 just vanishes
|
|
p.writeInt(objId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendCannotSpawnKite() {
|
|
return OutPacket.create(SendOpcode.CANNOT_SPAWN_KITE);
|
|
}
|
|
|
|
/**
|
|
* Gets the response to a relog request.
|
|
*
|
|
* @return The relog response packet.
|
|
*/
|
|
public static Packet getRelogResponse() {
|
|
OutPacket p = OutPacket.create(SendOpcode.RELOG_RESPONSE);
|
|
p.writeByte(1);//1 O.O Must be more types ):
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a server message packet.
|
|
*
|
|
* @param message The message to convey.
|
|
* @return The server message packet.
|
|
*/
|
|
public static Packet serverMessage(String message) {
|
|
return serverMessage(4, (byte) 0, message, true, false, 0);
|
|
}
|
|
|
|
/**
|
|
* Gets a server notice packet.
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 0: [Notice]<br> 1: Popup<br>
|
|
* 2: Megaphone<br> 3: Super Megaphone<br> 4: Scrolling message at top<br>
|
|
* 5: Pink Text<br> 6: Lightblue Text
|
|
*
|
|
* @param type The type of the notice.
|
|
* @param message The message to convey.
|
|
* @return The server notice packet.
|
|
*/
|
|
public static Packet serverNotice(int type, String message) {
|
|
return serverMessage(type, (byte) 0, message, false, false, 0);
|
|
}
|
|
|
|
/**
|
|
* Gets a server notice packet.
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 0: [Notice]<br> 1: Popup<br>
|
|
* 2: Megaphone<br> 3: Super Megaphone<br> 4: Scrolling message at top<br>
|
|
* 5: Pink Text<br> 6: Lightblue Text
|
|
*
|
|
* @param type The type of the notice.
|
|
* @param channel The channel this notice was sent on.
|
|
* @param message The message to convey.
|
|
* @return The server notice packet.
|
|
*/
|
|
public static Packet serverNotice(int type, String message, int npc) {
|
|
return serverMessage(type, 0, message, false, false, npc);
|
|
}
|
|
|
|
public static Packet serverNotice(int type, int channel, String message) {
|
|
return serverMessage(type, channel, message, false, false, 0);
|
|
}
|
|
|
|
public static Packet serverNotice(int type, int channel, String message, boolean smegaEar) {
|
|
return serverMessage(type, channel, message, false, smegaEar, 0);
|
|
}
|
|
|
|
/**
|
|
* Gets a server message packet.
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 0: [Notice]<br> 1: Popup<br>
|
|
* 2: Megaphone<br> 3: Super Megaphone<br> 4: Scrolling message at top<br>
|
|
* 5: Pink Text<br> 6: Lightblue Text<br> 7: BroadCasting NPC
|
|
*
|
|
* @param type The type of the notice.
|
|
* @param channel The channel this notice was sent on.
|
|
* @param message The message to convey.
|
|
* @param servermessage Is this a scrolling ticker?
|
|
* @return The server notice packet.
|
|
*/
|
|
private static Packet serverMessage(int type, int channel, String message, boolean servermessage, boolean megaEar, int npc) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SERVERMESSAGE);
|
|
p.writeByte(type);
|
|
if (servermessage) {
|
|
p.writeByte(1);
|
|
}
|
|
p.writeString(message);
|
|
if (type == 3) {
|
|
p.writeByte(channel - 1); // channel
|
|
p.writeBool(megaEar);
|
|
} else if (type == 6) {
|
|
p.writeInt(0);
|
|
} else if (type == 7) { // npc
|
|
p.writeInt(npc);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a Avatar Super Megaphone packet.
|
|
*
|
|
* @param chr The character name.
|
|
* @param medal The medal text.
|
|
* @param channel Which channel.
|
|
* @param itemId Which item used.
|
|
* @param message The message sent.
|
|
* @param ear Whether or not the ear is shown for whisper.
|
|
* @return
|
|
*/
|
|
public static Packet getAvatarMega(Character chr, String medal, int channel, int itemId, List<String> message, boolean ear) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SET_AVATAR_MEGAPHONE);
|
|
p.writeInt(itemId);
|
|
p.writeString(medal + chr.getName());
|
|
for (String s : message) {
|
|
p.writeString(s);
|
|
}
|
|
p.writeInt(channel - 1); // channel
|
|
p.writeBool(ear);
|
|
addCharLook(p, chr, true);
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Sends a packet to remove the tiger megaphone
|
|
* @return
|
|
*/
|
|
public static Packet byeAvatarMega() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CLEAR_AVATAR_MEGAPHONE);
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends the Gachapon green message when a user uses a gachapon ticket.
|
|
*
|
|
* @param item
|
|
* @param town
|
|
* @param player
|
|
* @return
|
|
*/
|
|
public static Packet gachaponMessage(Item item, String town, Character player) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SERVERMESSAGE);
|
|
p.writeByte(0x0B);
|
|
p.writeString(player.getName() + " : got a(n)");
|
|
p.writeInt(0); //random?
|
|
p.writeString(town);
|
|
addItemInfo(p, item, true);
|
|
return p;
|
|
}
|
|
|
|
public static Packet spawnNPC(MapleNPC life) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_NPC);
|
|
p.writeInt(life.getObjectId());
|
|
p.writeInt(life.getId());
|
|
p.writeShort(life.getPosition().x);
|
|
p.writeShort(life.getCy());
|
|
p.writeBool(life.getF() != 1);
|
|
p.writeShort(life.getFh());
|
|
p.writeShort(life.getRx0());
|
|
p.writeShort(life.getRx1());
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet spawnNPCRequestController(MapleNPC life, boolean miniMap) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_NPC_REQUEST_CONTROLLER);
|
|
p.writeByte(1);
|
|
p.writeInt(life.getObjectId());
|
|
p.writeInt(life.getId());
|
|
p.writeShort(life.getPosition().x);
|
|
p.writeShort(life.getCy());
|
|
p.writeBool(life.getF() != 1);
|
|
p.writeShort(life.getFh());
|
|
p.writeShort(life.getRx0());
|
|
p.writeShort(life.getRx1());
|
|
p.writeBool(miniMap);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a spawn monster packet.
|
|
*
|
|
* @param life The monster to spawn.
|
|
* @param newSpawn Is it a new spawn?
|
|
* @return The spawn monster packet.
|
|
*/
|
|
public static Packet spawnMonster(MapleMonster life, boolean newSpawn) {
|
|
return spawnMonsterInternal(life, false, newSpawn, false, 0, false);
|
|
}
|
|
|
|
/**
|
|
* Gets a spawn monster packet.
|
|
*
|
|
* @param life The monster to spawn.
|
|
* @param newSpawn Is it a new spawn?
|
|
* @param effect The spawn effect.
|
|
* @return The spawn monster packet.
|
|
*/
|
|
public static Packet spawnMonster(MapleMonster life, boolean newSpawn, int effect) {
|
|
return spawnMonsterInternal(life, false, newSpawn, false, effect, false);
|
|
}
|
|
|
|
/**
|
|
* Gets a control monster packet.
|
|
*
|
|
* @param life The monster to give control to.
|
|
* @param newSpawn Is it a new spawn?
|
|
* @param aggro Aggressive monster?
|
|
* @return The monster control packet.
|
|
*/
|
|
public static Packet controlMonster(MapleMonster life, boolean newSpawn, boolean aggro) {
|
|
return spawnMonsterInternal(life, true, newSpawn, aggro, 0, false);
|
|
}
|
|
|
|
/**
|
|
* Removes a monster invisibility.
|
|
*
|
|
* @param life
|
|
* @return
|
|
*/
|
|
public static Packet removeMonsterInvisibility(MapleMonster life) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SPAWN_MONSTER_CONTROL);
|
|
p.writeByte(1);
|
|
p.writeInt(life.getObjectId());
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Makes a monster invisible for Ariant PQ.
|
|
*
|
|
* @param life
|
|
* @return
|
|
*/
|
|
public static Packet makeMonsterInvisible(MapleMonster life) {
|
|
return spawnMonsterInternal(life, true, false, false, 0, true);
|
|
}
|
|
|
|
private static void encodeParentlessMobSpawnEffect(OutPacket p, boolean newSpawn, int effect) {
|
|
if (effect > 0) {
|
|
p.writeByte(effect);
|
|
p.writeByte(0);
|
|
p.writeShort(0);
|
|
if (effect == 15) {
|
|
p.writeByte(0);
|
|
}
|
|
}
|
|
p.writeByte(newSpawn ? -2 : -1);
|
|
}
|
|
|
|
private static void encodeTemporary(OutPacket p, Map<MonsterStatus, MonsterStatusEffect> stati) {
|
|
int pCounter = -1;
|
|
int mCounter = -1;
|
|
|
|
stati = stati.entrySet() // to patch some status crashing players
|
|
.stream()
|
|
.filter(e -> !(e.getKey().equals(MonsterStatus.WATK) || e.getKey().equals(MonsterStatus.WDEF)))
|
|
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
|
|
|
|
writeLongEncodeTemporaryMask(p, stati.keySet()); // packet structure mapped thanks to Eric
|
|
|
|
for (Entry<MonsterStatus, MonsterStatusEffect> s : stati.entrySet()) {
|
|
MonsterStatusEffect mse = s.getValue();
|
|
p.writeShort(mse.getStati().get(s.getKey()));
|
|
|
|
MobSkill mobSkill = mse.getMobSkill();
|
|
if (mobSkill != null) {
|
|
p.writeShort(mobSkill.getSkillId());
|
|
p.writeShort(mobSkill.getSkillLevel());
|
|
|
|
switch (s.getKey()) {
|
|
case WEAPON_REFLECT -> pCounter = mobSkill.getX();
|
|
case MAGIC_REFLECT -> mCounter = mobSkill.getY();
|
|
}
|
|
} else {
|
|
Skill skill = mse.getSkill();
|
|
p.writeInt(skill != null ? skill.getId() : 0);
|
|
}
|
|
|
|
p.writeShort(-1); // duration
|
|
}
|
|
|
|
// reflect packet structure found thanks to Arnah (Vertisy)
|
|
if (pCounter != -1) {
|
|
p.writeInt(pCounter);// wPCounter_
|
|
}
|
|
if (mCounter != -1) {
|
|
p.writeInt(mCounter);// wMCounter_
|
|
}
|
|
if (pCounter != -1 || mCounter != -1) {
|
|
p.writeInt(100);// nCounterProb_
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal function to handler monster spawning and controlling.
|
|
*
|
|
* @param life The mob to perform operations with.
|
|
* @param requestController Requesting control of mob?
|
|
* @param newSpawn New spawn (fade in?)
|
|
* @param aggro Aggressive mob?
|
|
* @param effect The spawn effect to use.
|
|
* @return The spawn/control packet.
|
|
*/
|
|
private static Packet spawnMonsterInternal(MapleMonster life, boolean requestController, boolean newSpawn, boolean aggro, int effect, boolean makeInvis) {
|
|
if (makeInvis) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_MONSTER_CONTROL);
|
|
p.writeByte(0);
|
|
p.writeInt(life.getObjectId());
|
|
return p;
|
|
}
|
|
|
|
final OutPacket p;
|
|
if (requestController) {
|
|
p = OutPacket.create(SendOpcode.SPAWN_MONSTER_CONTROL);
|
|
p.writeByte(aggro ? 2 : 1);
|
|
} else {
|
|
p = OutPacket.create(SendOpcode.SPAWN_MONSTER);
|
|
}
|
|
|
|
p.writeInt(life.getObjectId());
|
|
p.writeByte(life.getController() == null ? 5 : 1);
|
|
p.writeInt(life.getId());
|
|
|
|
if (requestController) {
|
|
encodeTemporary(p, life.getStati()); // thanks shot for noticing encode temporary buffs missing
|
|
} else {
|
|
p.skip(16);
|
|
}
|
|
|
|
p.writePos(life.getPosition());
|
|
p.writeByte(life.getStance());
|
|
p.writeShort(0); //Origin FH //life.getStartFh()
|
|
p.writeShort(life.getFh());
|
|
|
|
|
|
/**
|
|
* -4: Fake -3: Appear after linked mob is dead -2: Fade in 1: Smoke 3:
|
|
* King Slime spawn 4: Summoning rock thing, used for 3rd job? 6:
|
|
* Magical shit 7: Smoke shit 8: 'The Boss' 9/10: Grim phantom shit?
|
|
* 11/12: Nothing? 13: Frankenstein 14: Angry ^ 15: Orb animation thing,
|
|
* ?? 16: ?? 19: Mushroom castle boss thing
|
|
*/
|
|
|
|
if (life.getParentMobOid() != 0) {
|
|
MapleMonster parentMob = life.getMap().getMonsterByOid(life.getParentMobOid());
|
|
if (parentMob != null && parentMob.isAlive()) {
|
|
p.writeByte(effect != 0 ? effect : -3);
|
|
p.writeInt(life.getParentMobOid());
|
|
} else {
|
|
encodeParentlessMobSpawnEffect(p, newSpawn, effect);
|
|
}
|
|
} else {
|
|
encodeParentlessMobSpawnEffect(p, newSpawn, effect);
|
|
}
|
|
|
|
p.writeByte(life.getTeam());
|
|
p.writeInt(0); // getItemEffect
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Handles monsters not being targettable, such as Zakum's first body.
|
|
*
|
|
* @param life The mob to spawn as non-targettable.
|
|
* @param effect The effect to show when spawning.
|
|
* @return The packet to spawn the mob as non-targettable.
|
|
*/
|
|
public static Packet spawnFakeMonster(MapleMonster life, int effect) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_MONSTER_CONTROL);
|
|
p.writeByte(1);
|
|
p.writeInt(life.getObjectId());
|
|
p.writeByte(5);
|
|
p.writeInt(life.getId());
|
|
encodeTemporary(p, life.getStati());
|
|
p.writePos(life.getPosition());
|
|
p.writeByte(life.getStance());
|
|
p.writeShort(0);//life.getStartFh()
|
|
p.writeShort(life.getFh());
|
|
if (effect > 0) {
|
|
p.writeByte(effect);
|
|
p.writeByte(0);
|
|
p.writeShort(0);
|
|
}
|
|
p.writeShort(-2);
|
|
p.writeByte(life.getTeam());
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Makes a monster previously spawned as non-targettable, targettable.
|
|
*
|
|
* @param life The mob to make targettable.
|
|
* @return The packet to make the mob targettable.
|
|
*/
|
|
public static Packet makeMonsterReal(MapleMonster life) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_MONSTER);
|
|
p.writeInt(life.getObjectId());
|
|
p.writeByte(5);
|
|
p.writeInt(life.getId());
|
|
encodeTemporary(p, life.getStati());
|
|
p.writePos(life.getPosition());
|
|
p.writeByte(life.getStance());
|
|
p.writeShort(0);//life.getStartFh()
|
|
p.writeShort(life.getFh());
|
|
p.writeShort(-1);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a stop control monster packet.
|
|
*
|
|
* @param oid The ObjectID of the monster to stop controlling.
|
|
* @return The stop control monster packet.
|
|
*/
|
|
public static Packet stopControllingMonster(int oid) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_MONSTER_CONTROL);
|
|
p.writeByte(0);
|
|
p.writeInt(oid);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a response to a move monster packet.
|
|
*
|
|
* @param objectid The ObjectID of the monster being moved.
|
|
* @param moveid The movement ID.
|
|
* @param currentMp The current MP of the monster.
|
|
* @param useSkills Can the monster use skills?
|
|
* @return The move response packet.
|
|
*/
|
|
public static Packet moveMonsterResponse(int objectid, short moveid, int currentMp, boolean useSkills) {
|
|
return moveMonsterResponse(objectid, moveid, currentMp, useSkills, 0, 0);
|
|
}
|
|
|
|
/**
|
|
* Gets a response to a move monster packet.
|
|
*
|
|
* @param objectid The ObjectID of the monster being moved.
|
|
* @param moveid The movement ID.
|
|
* @param currentMp The current MP of the monster.
|
|
* @param useSkills Can the monster use skills?
|
|
* @param skillId The skill ID for the monster to use.
|
|
* @param skillLevel The level of the skill to use.
|
|
* @return The move response packet.
|
|
*/
|
|
|
|
public static Packet moveMonsterResponse(int objectid, short moveid, int currentMp, boolean useSkills, int skillId, int skillLevel) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MOVE_MONSTER_RESPONSE);
|
|
p.writeInt(objectid);
|
|
p.writeShort(moveid);
|
|
p.writeBool(useSkills);
|
|
p.writeShort(currentMp);
|
|
p.writeByte(skillId);
|
|
p.writeByte(skillLevel);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a general chat packet.
|
|
*
|
|
* @param cidfrom The character ID who sent the chat.
|
|
* @param text The text of the chat.
|
|
* @param whiteBG
|
|
* @param show
|
|
* @return The general chat packet.
|
|
*/
|
|
public static Packet getChatText(int cidfrom, String text, boolean gm, int show) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CHATTEXT);
|
|
p.writeInt(cidfrom);
|
|
p.writeBool(gm);
|
|
p.writeString(text);
|
|
p.writeByte(show);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client to show an EXP increase.
|
|
*
|
|
* @param gain The amount of EXP gained.
|
|
* @param inChat In the chat box?
|
|
* @param white White text or yellow?
|
|
* @return The exp gained packet.
|
|
*/
|
|
public static Packet getShowExpGain(int gain, int equip, int party, boolean inChat, boolean white) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(3); // 3 = exp, 4 = fame, 5 = mesos, 6 = guildpoints
|
|
p.writeBool(white);
|
|
p.writeInt(gain);
|
|
p.writeBool(inChat);
|
|
p.writeInt(0); // bonus event exp
|
|
p.writeByte(0); // third monster kill event
|
|
p.writeByte(0); // RIP byte, this is always a 0
|
|
p.writeInt(0); //wedding bonus
|
|
if (inChat) { // quest bonus rate stuff
|
|
p.writeByte(0);
|
|
}
|
|
|
|
p.writeByte(0); //0 = party bonus, 100 = 1x Bonus EXP, 200 = 2x Bonus EXP
|
|
p.writeInt(party); // party bonus
|
|
p.writeInt(equip); //equip bonus
|
|
p.writeInt(0); //Internet Cafe Bonus
|
|
p.writeInt(0); //Rainbow Week Bonus
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client to show a fame gain.
|
|
*
|
|
* @param gain How many fame gained.
|
|
* @return The meso gain packet.
|
|
*/
|
|
public static Packet getShowFameGain(int gain) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(4);
|
|
p.writeInt(gain);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client to show a meso gain.
|
|
*
|
|
* @param gain How many mesos gained.
|
|
* @return The meso gain packet.
|
|
*/
|
|
public static Packet getShowMesoGain(int gain) {
|
|
return getShowMesoGain(gain, false);
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client to show a meso gain.
|
|
*
|
|
* @param gain How many mesos gained.
|
|
* @param inChat Show in the chat window?
|
|
* @return The meso gain packet.
|
|
*/
|
|
public static Packet getShowMesoGain(int gain, boolean inChat) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
if (!inChat) {
|
|
p.writeByte(0);
|
|
p.writeShort(1); //v83
|
|
} else {
|
|
p.writeByte(5);
|
|
}
|
|
p.writeInt(gain);
|
|
p.writeShort(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client to show a item gain.
|
|
*
|
|
* @param itemId The ID of the item gained.
|
|
* @param quantity How many items gained.
|
|
* @return The item gain packet.
|
|
*/
|
|
public static Packet getShowItemGain(int itemId, short quantity) {
|
|
return getShowItemGain(itemId, quantity, false);
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client to show an item gain.
|
|
*
|
|
* @param itemId The ID of the item gained.
|
|
* @param quantity The number of items gained.
|
|
* @param inChat Show in the chat window?
|
|
* @return The item gain packet.
|
|
*/
|
|
public static Packet getShowItemGain(int itemId, short quantity, boolean inChat) {
|
|
final OutPacket p;
|
|
if (inChat) {
|
|
p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(3);
|
|
p.writeByte(1);
|
|
p.writeInt(itemId);
|
|
p.writeInt(quantity);
|
|
} else {
|
|
p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeShort(0);
|
|
p.writeInt(itemId);
|
|
p.writeInt(quantity);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet killMonster(int objId, boolean animation) {
|
|
return killMonster(objId, animation ? 1 : 0);
|
|
}
|
|
|
|
/**
|
|
* Gets a packet telling the client that a monster was killed.
|
|
*
|
|
* @param objId The objectID of the killed monster.
|
|
* @param animation 0 = dissapear, 1 = fade out, 2+ = special
|
|
* @return The kill monster packet.
|
|
*/
|
|
public static Packet killMonster(int objId, int animation) {
|
|
OutPacket p = OutPacket.create(SendOpcode.KILL_MONSTER);
|
|
p.writeInt(objId);
|
|
p.writeByte(animation);
|
|
p.writeByte(animation);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateMapItemObject(MapleMapItem drop, boolean giveOwnership) {
|
|
OutPacket p = OutPacket.create(SendOpcode.DROP_ITEM_FROM_MAPOBJECT);
|
|
p.writeByte(2);
|
|
p.writeInt(drop.getObjectId());
|
|
p.writeBool(drop.getMeso() > 0);
|
|
p.writeInt(drop.getItemId());
|
|
p.writeInt(giveOwnership ? 0 : -1);
|
|
p.writeByte(drop.hasExpiredOwnershipTime() ? 2 : drop.getDropType());
|
|
p.writePos(drop.getPosition());
|
|
p.writeInt(giveOwnership ? 0 : -1);
|
|
|
|
if (drop.getMeso() == 0) {
|
|
addExpirationTime(p, drop.getItem().getExpiration());
|
|
}
|
|
p.writeBool(!drop.isPlayerDrop());
|
|
return p;
|
|
}
|
|
|
|
public static Packet dropItemFromMapObject(Character player, MapleMapItem drop, Point dropfrom, Point dropto, byte mod) {
|
|
int dropType = drop.getDropType();
|
|
if (drop.hasClientsideOwnership(player) && dropType < 3) {
|
|
dropType = 2;
|
|
}
|
|
|
|
OutPacket p = OutPacket.create(SendOpcode.DROP_ITEM_FROM_MAPOBJECT);
|
|
p.writeByte(mod);
|
|
p.writeInt(drop.getObjectId());
|
|
p.writeBool(drop.getMeso() > 0); // 1 mesos, 0 item, 2 and above all item meso bag,
|
|
p.writeInt(drop.getItemId()); // drop object ID
|
|
p.writeInt(drop.getClientsideOwnerId()); // owner charid/partyid :)
|
|
p.writeByte(dropType); // 0 = timeout for non-owner, 1 = timeout for non-owner's party, 2 = FFA, 3 = explosive/FFA
|
|
p.writePos(dropto);
|
|
p.writeInt(drop.getDropper().getObjectId()); // dropper oid, found thanks to Li Jixue
|
|
|
|
if (mod != 2) {
|
|
p.writePos(dropfrom);
|
|
p.writeShort(0);//Fh?
|
|
}
|
|
if (drop.getMeso() == 0) {
|
|
addExpirationTime(p, drop.getItem().getExpiration());
|
|
}
|
|
p.writeByte(drop.isPlayerDrop() ? 0 : 1); //pet EQP pickup
|
|
return p;
|
|
}
|
|
|
|
private static void writeForeignBuffs(OutPacket p, Character chr) {
|
|
p.writeInt(0);
|
|
p.writeShort(0); //v83
|
|
p.writeByte(0xFC);
|
|
p.writeByte(1);
|
|
if (chr.getBuffedValue(BuffStat.MORPH) != null) {
|
|
p.writeInt(2);
|
|
} else {
|
|
p.writeInt(0);
|
|
}
|
|
long buffmask = 0;
|
|
Integer buffvalue = null;
|
|
if ((chr.getBuffedValue(BuffStat.DARKSIGHT) != null || chr.getBuffedValue(BuffStat.WIND_WALK) != null) && !chr.isHidden()) {
|
|
buffmask |= BuffStat.DARKSIGHT.getValue();
|
|
}
|
|
if (chr.getBuffedValue(BuffStat.COMBO) != null) {
|
|
buffmask |= BuffStat.COMBO.getValue();
|
|
buffvalue = Integer.valueOf(chr.getBuffedValue(BuffStat.COMBO));
|
|
}
|
|
if (chr.getBuffedValue(BuffStat.SHADOWPARTNER) != null) {
|
|
buffmask |= BuffStat.SHADOWPARTNER.getValue();
|
|
}
|
|
if (chr.getBuffedValue(BuffStat.SOULARROW) != null) {
|
|
buffmask |= BuffStat.SOULARROW.getValue();
|
|
}
|
|
if (chr.getBuffedValue(BuffStat.MORPH) != null) {
|
|
buffvalue = Integer.valueOf(chr.getBuffedValue(BuffStat.MORPH));
|
|
}
|
|
p.writeInt((int) ((buffmask >> 32) & 0xffffffffL));
|
|
if (buffvalue != null) {
|
|
if (chr.getBuffedValue(BuffStat.MORPH) != null) { //TEST
|
|
p.writeShort(buffvalue);
|
|
} else {
|
|
p.writeByte(buffvalue.byteValue());
|
|
}
|
|
}
|
|
p.writeInt((int) (buffmask & 0xffffffffL));
|
|
|
|
// Energy Charge
|
|
p.writeInt(chr.getEnergyBar() == 15000 ? 1 : 0);
|
|
p.writeShort(0);
|
|
p.skip(4);
|
|
|
|
boolean dashBuff = chr.getBuffedValue(BuffStat.DASH) != null;
|
|
// Dash Speed
|
|
p.writeInt(dashBuff ? 1 << 24 : 0);
|
|
p.skip(11);
|
|
p.writeShort(0);
|
|
// Dash Jump
|
|
p.skip(9);
|
|
p.writeInt(dashBuff ? 1 << 24 : 0);
|
|
p.writeShort(0);
|
|
p.writeByte(0);
|
|
|
|
// Monster Riding
|
|
Integer bv = chr.getBuffedValue(BuffStat.MONSTER_RIDING);
|
|
if (bv != null) {
|
|
Mount mount = chr.getMount();
|
|
if (mount != null) {
|
|
p.writeInt(mount.getItemId());
|
|
p.writeInt(mount.getSkillId());
|
|
} else {
|
|
p.writeLong(0);
|
|
}
|
|
} else {
|
|
p.writeLong(0);
|
|
}
|
|
|
|
int CHAR_MAGIC_SPAWN = Randomizer.nextInt(); // skill references found thanks to Rien dev team
|
|
p.writeInt(CHAR_MAGIC_SPAWN);
|
|
// Speed Infusion
|
|
p.skip(8);
|
|
p.writeInt(CHAR_MAGIC_SPAWN);
|
|
p.writeByte(0);
|
|
p.writeInt(CHAR_MAGIC_SPAWN);
|
|
p.writeShort(0);
|
|
// Homing Beacon
|
|
p.skip(9);
|
|
p.writeInt(CHAR_MAGIC_SPAWN);
|
|
p.writeInt(0);
|
|
// Zombify
|
|
p.skip(9);
|
|
p.writeInt(CHAR_MAGIC_SPAWN);
|
|
p.writeShort(0);
|
|
p.writeShort(0);
|
|
}
|
|
|
|
/**
|
|
* Gets a packet spawning a player as a mapobject to other clients.
|
|
*
|
|
* @param target The client receiving this packet.
|
|
* @param chr The character to spawn to other clients.
|
|
* @param enteringField Whether the character to spawn is not yet present in the map or already is.
|
|
* @return The spawn player packet.
|
|
*/
|
|
public static Packet spawnPlayerMapObject(Client target, Character chr, boolean enteringField) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_PLAYER);
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(chr.getLevel()); //v83
|
|
p.writeString(chr.getName());
|
|
if (chr.getGuildId() < 1) {
|
|
p.writeString("");
|
|
p.writeBytes(new byte[6]);
|
|
} else {
|
|
MapleGuildSummary gs = chr.getClient().getWorldServer().getGuildSummary(chr.getGuildId(), chr.getWorld());
|
|
if (gs != null) {
|
|
p.writeString(gs.getName());
|
|
p.writeShort(gs.getLogoBG());
|
|
p.writeByte(gs.getLogoBGColor());
|
|
p.writeShort(gs.getLogo());
|
|
p.writeByte(gs.getLogoColor());
|
|
} else {
|
|
p.writeString("");
|
|
p.writeBytes(new byte[6]);
|
|
}
|
|
}
|
|
|
|
writeForeignBuffs(p, chr);
|
|
|
|
p.writeShort(chr.getJob().getId());
|
|
|
|
/* replace "p.writeShort(chr.getJob().getId())" with this snippet for 3rd person FJ animation on all classes
|
|
if (chr.getJob().isA(Job.HERMIT) || chr.getJob().isA(Job.DAWNWARRIOR2) || chr.getJob().isA(Job.NIGHTWALKER2)) {
|
|
p.writeShort(chr.getJob().getId());
|
|
} else {
|
|
p.writeShort(412);
|
|
}*/
|
|
|
|
addCharLook(p, chr, false);
|
|
p.writeInt(chr.getInventory(InventoryType.CASH).countById(5110000));
|
|
p.writeInt(chr.getItemEffect());
|
|
p.writeInt(ItemConstants.getInventoryType(chr.getChair()) == InventoryType.SETUP ? chr.getChair() : 0);
|
|
|
|
if (enteringField) {
|
|
Point spawnPos = new Point(chr.getPosition());
|
|
spawnPos.y -= 42;
|
|
p.writePos(spawnPos);
|
|
p.writeByte(6);
|
|
} else {
|
|
p.writePos(chr.getPosition());
|
|
p.writeByte(chr.getStance());
|
|
}
|
|
|
|
p.writeShort(0);//chr.getFh()
|
|
p.writeByte(0);
|
|
Pet[] pet = chr.getPets();
|
|
for (int i = 0; i < 3; i++) {
|
|
if (pet[i] != null) {
|
|
addPetInfo(p, pet[i], false);
|
|
}
|
|
}
|
|
p.writeByte(0); //end of pets
|
|
if (chr.getMount() == null) {
|
|
p.writeInt(1); // mob level
|
|
p.writeLong(0); // mob exp + tiredness
|
|
} else {
|
|
p.writeInt(chr.getMount().getLevel());
|
|
p.writeInt(chr.getMount().getExp());
|
|
p.writeInt(chr.getMount().getTiredness());
|
|
}
|
|
|
|
MaplePlayerShop mps = chr.getPlayerShop();
|
|
if (mps != null && mps.isOwner(chr)) {
|
|
if (mps.hasFreeSlot()) {
|
|
addAnnounceBox(p, mps, mps.getVisitors().length);
|
|
} else {
|
|
addAnnounceBox(p, mps, 1);
|
|
}
|
|
} else {
|
|
MapleMiniGame miniGame = chr.getMiniGame();
|
|
if (miniGame != null && miniGame.isOwner(chr)) {
|
|
if (miniGame.hasFreeSlot()) {
|
|
addAnnounceBox(p, miniGame, 1, 0);
|
|
} else {
|
|
addAnnounceBox(p, miniGame, 2, miniGame.isMatchInProgress() ? 1 : 0);
|
|
}
|
|
} else {
|
|
p.writeByte(0);
|
|
}
|
|
}
|
|
|
|
if (chr.getChalkboard() != null) {
|
|
p.writeByte(1);
|
|
p.writeString(chr.getChalkboard());
|
|
} else {
|
|
p.writeByte(0);
|
|
}
|
|
addRingLook(p, chr, true); // crush
|
|
addRingLook(p, chr, false); // friendship
|
|
addMarriageRingLook(target, p, chr);
|
|
encodeNewYearCardInfo(p, chr); // new year seems to crash sometimes...
|
|
p.writeByte(0);
|
|
p.writeByte(0);
|
|
p.writeByte(chr.getTeam());//only needed in specific fields
|
|
return p;
|
|
}
|
|
|
|
private static void encodeNewYearCardInfo(OutPacket p, Character chr) {
|
|
Set<NewYearCardRecord> newyears = chr.getReceivedNewYearRecords();
|
|
if (!newyears.isEmpty()) {
|
|
p.writeByte(1);
|
|
|
|
p.writeInt(newyears.size());
|
|
for (NewYearCardRecord nyc : newyears) {
|
|
p.writeInt(nyc.getId());
|
|
}
|
|
} else {
|
|
p.writeByte(0);
|
|
}
|
|
}
|
|
|
|
public static Packet onNewYearCardRes(Character user, int cardId, int mode, int msg) {
|
|
NewYearCardRecord newyear = user.getNewYearRecord(cardId);
|
|
return onNewYearCardRes(user, newyear, mode, msg);
|
|
}
|
|
|
|
public static Packet onNewYearCardRes(Character user, NewYearCardRecord newyear, int mode, int msg) {
|
|
OutPacket p = OutPacket.create(SendOpcode.NEW_YEAR_CARD_RES);
|
|
p.writeByte(mode);
|
|
switch (mode) {
|
|
case 4: // Successfully sent a New Year Card\r\n to %s.
|
|
case 6: // Successfully received a New Year Card.
|
|
encodeNewYearCard(newyear, p);
|
|
break;
|
|
|
|
case 8: // Successfully deleted a New Year Card.
|
|
p.writeInt(newyear.getId());
|
|
break;
|
|
|
|
case 5: // Nexon's stupid and makes 4 modes do the same operation..
|
|
case 7:
|
|
case 9:
|
|
case 0xB:
|
|
// 0x10: You have no free slot to store card.\r\ntry later on please.
|
|
// 0x11: You have no card to send.
|
|
// 0x12: Wrong inventory information !
|
|
// 0x13: Cannot find such character !
|
|
// 0x14: Incoherent Data !
|
|
// 0x15: An error occured during DB operation.
|
|
// 0x16: An unknown error occured !
|
|
// 0xF: You cannot send a card to yourself !
|
|
p.writeByte(msg);
|
|
break;
|
|
|
|
case 0xA: // GetUnreceivedList_Done
|
|
int nSN = 1;
|
|
p.writeInt(nSN);
|
|
if ((nSN - 1) <= 98 && nSN > 0) {//lol nexon are you kidding
|
|
for (int i = 0; i < nSN; i++) {
|
|
p.writeInt(newyear.getId());
|
|
p.writeInt(newyear.getSenderId());
|
|
p.writeString(newyear.getSenderName());
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0xC: // NotiArrived
|
|
p.writeInt(newyear.getId());
|
|
p.writeString(newyear.getSenderName());
|
|
break;
|
|
|
|
case 0xD: // BroadCast_AddCardInfo
|
|
p.writeInt(newyear.getId());
|
|
p.writeInt(user.getId());
|
|
break;
|
|
|
|
case 0xE: // BroadCast_RemoveCardInfo
|
|
p.writeInt(newyear.getId());
|
|
break;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
private static void encodeNewYearCard(NewYearCardRecord newyear, OutPacket p) {
|
|
p.writeInt(newyear.getId());
|
|
p.writeInt(newyear.getSenderId());
|
|
p.writeString(newyear.getSenderName());
|
|
p.writeBool(newyear.isSenderCardDiscarded());
|
|
p.writeLong(newyear.getDateSent());
|
|
p.writeInt(newyear.getReceiverId());
|
|
p.writeString(newyear.getReceiverName());
|
|
p.writeBool(newyear.isReceiverCardDiscarded());
|
|
p.writeBool(newyear.isReceiverCardReceived());
|
|
p.writeLong(newyear.getDateReceived());
|
|
p.writeString(newyear.getMessage());
|
|
}
|
|
|
|
private static void addRingLook(final OutPacket p, Character chr, boolean crush) {
|
|
List<Ring> rings;
|
|
if (crush) {
|
|
rings = chr.getCrushRings();
|
|
} else {
|
|
rings = chr.getFriendshipRings();
|
|
}
|
|
boolean yes = false;
|
|
for (Ring ring : rings) {
|
|
if (ring.equipped()) {
|
|
if (yes == false) {
|
|
yes = true;
|
|
p.writeByte(1);
|
|
}
|
|
p.writeInt(ring.getRingId());
|
|
p.writeInt(0);
|
|
p.writeInt(ring.getPartnerRingId());
|
|
p.writeInt(0);
|
|
p.writeInt(ring.getItemId());
|
|
}
|
|
}
|
|
if (yes == false) {
|
|
p.writeByte(0);
|
|
}
|
|
}
|
|
|
|
private static void addMarriageRingLook(Client target, final OutPacket p, Character chr) {
|
|
Ring ring = chr.getMarriageRing();
|
|
|
|
if (ring == null || !ring.equipped()) {
|
|
p.writeByte(0);
|
|
} else {
|
|
p.writeByte(1);
|
|
|
|
Character targetChr = target.getPlayer();
|
|
if (targetChr != null && targetChr.getPartnerId() == chr.getId()) {
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
} else {
|
|
p.writeInt(chr.getId());
|
|
p.writeInt(ring.getPartnerChrId());
|
|
}
|
|
|
|
p.writeInt(ring.getItemId());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds an announcement box to an existing OutPacket.
|
|
*
|
|
* @param p The OutPacket to add an announcement box
|
|
* to.
|
|
* @param shop The shop to announce.
|
|
*/
|
|
private static void addAnnounceBox(final OutPacket p, MaplePlayerShop shop, int availability) {
|
|
p.writeByte(4);
|
|
p.writeInt(shop.getObjectId());
|
|
p.writeString(shop.getDescription());
|
|
p.writeByte(0);
|
|
p.writeByte(0);
|
|
p.writeByte(1);
|
|
p.writeByte(availability);
|
|
p.writeByte(0);
|
|
}
|
|
|
|
private static void addAnnounceBox(final OutPacket p, MapleMiniGame game, int ammount, int joinable) {
|
|
p.writeByte(game.getGameType().getValue());
|
|
p.writeInt(game.getObjectId()); // gameid/shopid
|
|
p.writeString(game.getDescription()); // desc
|
|
p.writeBool(!game.getPassword().isEmpty()); // password here, thanks GabrielSin
|
|
p.writeByte(game.getPieceType());
|
|
p.writeByte(ammount);
|
|
p.writeByte(2); //player capacity
|
|
p.writeByte(joinable);
|
|
}
|
|
|
|
private static void updateHiredMerchantBoxInfo(OutPacket p, MapleHiredMerchant hm) {
|
|
byte[] roomInfo = hm.getShopRoomInfo();
|
|
|
|
p.writeByte(5);
|
|
p.writeInt(hm.getObjectId());
|
|
p.writeString(hm.getDescription());
|
|
p.writeByte(hm.getItemId() % 100);
|
|
p.writeBytes(roomInfo); // visitor capacity here, thanks GabrielSin
|
|
}
|
|
|
|
public static Packet updateHiredMerchantBox(MapleHiredMerchant hm) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_HIRED_MERCHANT);
|
|
p.writeInt(hm.getOwnerId());
|
|
updateHiredMerchantBoxInfo(p, hm);
|
|
return p;
|
|
}
|
|
|
|
private static void updatePlayerShopBoxInfo(OutPacket p, MaplePlayerShop shop) {
|
|
byte[] roomInfo = shop.getShopRoomInfo();
|
|
|
|
p.writeByte(4);
|
|
p.writeInt(shop.getObjectId());
|
|
p.writeString(shop.getDescription());
|
|
p.writeByte(0); // pw
|
|
p.writeByte(shop.getItemId() % 100);
|
|
p.writeByte(roomInfo[0]); // curPlayers
|
|
p.writeByte(roomInfo[1]); // maxPlayers
|
|
p.writeByte(0);
|
|
}
|
|
|
|
public static Packet updatePlayerShopBox(MaplePlayerShop shop) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_CHAR_BOX);
|
|
p.writeInt(shop.getOwner().getId());
|
|
updatePlayerShopBoxInfo(p, shop);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removePlayerShopBox(MaplePlayerShop shop) {
|
|
OutPacket p = OutPacket.create(SendOpcode.UPDATE_CHAR_BOX);
|
|
p.writeInt(shop.getOwner().getId());
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet facialExpression(Character from, int expression) {
|
|
OutPacket p = OutPacket.create(SendOpcode.FACIAL_EXPRESSION);
|
|
p.writeInt(from.getId());
|
|
p.writeInt(expression);
|
|
return p;
|
|
}
|
|
|
|
private static void rebroadcastMovementList(OutPacket op, InPacket ip, long movementDataLength) {
|
|
//movement command length is sent by client, probably not a big issue? (could be calculated on server)
|
|
//if multiple write/reads are slow, could use (and cache?) a byte[] buffer
|
|
for (long i = 0; i < movementDataLength; i++) {
|
|
op.writeByte(ip.readByte());
|
|
}
|
|
}
|
|
|
|
private static void serializeMovementList(OutPacket p, List<LifeMovementFragment> moves) {
|
|
p.writeByte(moves.size());
|
|
for (LifeMovementFragment move : moves) {
|
|
move.serialize(p);
|
|
}
|
|
}
|
|
|
|
public static Packet movePlayer(int chrId, InPacket movementPacket, long movementDataLength) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MOVE_PLAYER);
|
|
p.writeInt(chrId);
|
|
p.writeInt(0);
|
|
rebroadcastMovementList(p, movementPacket, movementDataLength);
|
|
return p;
|
|
}
|
|
|
|
public static Packet moveSummon(int cid, int oid, Point startPos, InPacket movementPacket, long movementDataLength) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MOVE_SUMMON);
|
|
p.writeInt(cid);
|
|
p.writeInt(oid);
|
|
p.writePos(startPos);
|
|
rebroadcastMovementList(p, movementPacket, movementDataLength);
|
|
return p;
|
|
}
|
|
|
|
public static Packet moveMonster(int oid, boolean skillPossible, int skill, int skillId, int skillLevel, int pOption,
|
|
Point startPos, InPacket movementPacket, long movementDataLength) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MOVE_MONSTER);
|
|
p.writeInt(oid);
|
|
p.writeByte(0);
|
|
p.writeBool(skillPossible);
|
|
p.writeByte(skill);
|
|
p.writeByte(skillId);
|
|
p.writeByte(skillLevel);
|
|
p.writeShort(pOption);
|
|
p.writePos(startPos);
|
|
rebroadcastMovementList(p, movementPacket, movementDataLength);
|
|
return p;
|
|
}
|
|
|
|
public static Packet summonAttack(int cid, int summonOid, byte direction, List<SummonAttackEntry> allDamage) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SUMMON_ATTACK);
|
|
//b2 00 29 f7 00 00 9a a3 04 00 c8 04 01 94 a3 04 00 06 ff 2b 00
|
|
p.writeInt(cid);
|
|
p.writeInt(summonOid);
|
|
p.writeByte(0); // char level
|
|
p.writeByte(direction);
|
|
p.writeByte(allDamage.size());
|
|
for (SummonAttackEntry attackEntry : allDamage) {
|
|
p.writeInt(attackEntry.getMonsterOid()); // oid
|
|
p.writeByte(6); // who knows
|
|
p.writeInt(attackEntry.getDamage()); // damage
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
public static Packet summonAttack(int cid, int summonSkillId, byte direction, List<SummonAttackEntry> allDamage) {
|
|
OutPacket p = OutPacket.create(SendOpcode);
|
|
//b2 00 29 f7 00 00 9a a3 04 00 c8 04 01 94 a3 04 00 06 ff 2b 00
|
|
SUMMON_ATTACK);
|
|
p.writeInt(cid);
|
|
p.writeInt(summonSkillId);
|
|
p.writeByte(direction);
|
|
p.writeByte(4);
|
|
p.writeByte(allDamage.size());
|
|
for (SummonAttackEntry attackEntry : allDamage) {
|
|
p.writeInt(attackEntry.getMonsterOid()); // oid
|
|
p.writeByte(6); // who knows
|
|
p.writeInt(attackEntry.getDamage()); // damage
|
|
}
|
|
return p;
|
|
}
|
|
*/
|
|
|
|
public static Packet closeRangeAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, Map<Integer, List<Integer>> damage, int speed, int direction, int display) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CLOSE_RANGE_ATTACK);
|
|
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, damage, speed, direction, display);
|
|
return p;
|
|
}
|
|
|
|
public static Packet rangedAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, int projectile, Map<Integer, List<Integer>> damage, int speed, int direction, int display) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.RANGED_ATTACK);
|
|
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, projectile, damage, speed, direction, display);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet magicAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, Map<Integer, List<Integer>> damage, int charge, int speed, int direction, int display) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MAGIC_ATTACK);
|
|
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, damage, speed, direction, display);
|
|
if (charge != -1) {
|
|
p.writeInt(charge);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
private static void addAttackBody(OutPacket p, Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, int projectile, Map<Integer, List<Integer>> damage, int speed, int direction, int display) {
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(numAttackedAndDamage);
|
|
p.writeByte(0x5B);//?
|
|
p.writeByte(skilllevel);
|
|
if (skilllevel > 0) {
|
|
p.writeInt(skill);
|
|
}
|
|
p.writeByte(display);
|
|
p.writeByte(direction);
|
|
p.writeByte(stance);
|
|
p.writeByte(speed);
|
|
p.writeByte(0x0A);
|
|
p.writeInt(projectile);
|
|
for (Integer oned : damage.keySet()) {
|
|
List<Integer> onedList = damage.get(oned);
|
|
if (onedList != null) {
|
|
p.writeInt(oned);
|
|
p.writeByte(0x0);
|
|
if (skill == 4211006) {
|
|
p.writeByte(onedList.size());
|
|
}
|
|
for (Integer eachd : onedList) {
|
|
p.writeInt(eachd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Packet throwGrenade(int cid, Point pos, int keyDown, int skillId, int skillLevel) { // packets found thanks to GabrielSin
|
|
OutPacket p = OutPacket.create(SendOpcode.THROW_GRENADE);
|
|
p.writeInt(cid);
|
|
p.writeInt(pos.x);
|
|
p.writeInt(pos.y);
|
|
p.writeInt(keyDown);
|
|
p.writeInt(skillId);
|
|
p.writeInt(skillLevel);
|
|
return p;
|
|
}
|
|
|
|
// someone thought it was a good idea to handle floating point representation through packets ROFL
|
|
private static int doubleToShortBits(double d) {
|
|
return (int) (Double.doubleToLongBits(d) >> 48);
|
|
}
|
|
|
|
public static Packet getNPCShop(Client c, int sid, List<MapleShopItem> items) {
|
|
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
|
|
final OutPacket p = OutPacket.create(SendOpcode.OPEN_NPC_SHOP);
|
|
p.writeInt(sid);
|
|
p.writeShort(items.size()); // item count
|
|
for (MapleShopItem item : items) {
|
|
p.writeInt(item.getItemId());
|
|
p.writeInt(item.getPrice());
|
|
p.writeInt(item.getPrice() == 0 ? item.getPitch() : 0); //Perfect Pitch
|
|
p.writeInt(0); //Can be used x minutes after purchase
|
|
p.writeInt(0); //Hmm
|
|
if (!ItemConstants.isRechargeable(item.getItemId())) {
|
|
p.writeShort(1); // stacksize o.o
|
|
p.writeShort(item.getBuyable());
|
|
} else {
|
|
p.writeShort(0);
|
|
p.writeInt(0);
|
|
p.writeShort(doubleToShortBits(ii.getUnitPrice(item.getItemId())));
|
|
p.writeShort(ii.getSlotMax(c, item.getItemId()));
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/* 00 = /
|
|
* 01 = You don't have enough in stock
|
|
* 02 = You do not have enough mesos
|
|
* 03 = Please check if your inventory is full or not
|
|
* 05 = You don't have enough in stock
|
|
* 06 = Due to an error, the trade did not happen
|
|
* 07 = Due to an error, the trade did not happen
|
|
* 08 = /
|
|
* 0D = You need more items
|
|
* 0E = CRASH; LENGTH NEEDS TO BE LONGER :O
|
|
*/
|
|
public static Packet shopTransaction(byte code) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CONFIRM_SHOP_TRANSACTION);
|
|
p.writeByte(code);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateInventorySlotLimit(int type, int newLimit) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.INVENTORY_GROW);
|
|
p.writeByte(type);
|
|
p.writeByte(newLimit);
|
|
return p;
|
|
}
|
|
|
|
public static Packet modifyInventory(boolean updateTick, final List<ModifyInventory> mods) {
|
|
OutPacket p = OutPacket.create(SendOpcode.INVENTORY_OPERATION);
|
|
p.writeBool(updateTick);
|
|
p.writeByte(mods.size());
|
|
//p.writeByte(0); v104 :)
|
|
int addMovement = -1;
|
|
for (ModifyInventory mod : mods) {
|
|
p.writeByte(mod.getMode());
|
|
p.writeByte(mod.getInventoryType());
|
|
p.writeShort(mod.getMode() == 2 ? mod.getOldPosition() : mod.getPosition());
|
|
switch (mod.getMode()) {
|
|
case 0: {//add item
|
|
addItemInfo(p, mod.getItem(), true);
|
|
break;
|
|
}
|
|
case 1: {//update quantity
|
|
p.writeShort(mod.getQuantity());
|
|
break;
|
|
}
|
|
case 2: {//move
|
|
p.writeShort(mod.getPosition());
|
|
if (mod.getPosition() < 0 || mod.getOldPosition() < 0) {
|
|
addMovement = mod.getOldPosition() < 0 ? 1 : 2;
|
|
}
|
|
break;
|
|
}
|
|
case 3: {//remove
|
|
if (mod.getPosition() < 0) {
|
|
addMovement = 2;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
mod.clear();
|
|
}
|
|
if (addMovement > -1) {
|
|
p.writeByte(addMovement);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet getScrollEffect(int chr, ScrollResult scrollSuccess, boolean legendarySpirit, boolean whiteScroll) { // thanks to Rien dev team
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_SCROLL_EFFECT);
|
|
p.writeInt(chr);
|
|
p.writeBool(scrollSuccess == ScrollResult.SUCCESS);
|
|
p.writeBool(scrollSuccess == ScrollResult.CURSE);
|
|
p.writeBool(legendarySpirit);
|
|
p.writeBool(whiteScroll);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removePlayerFromMap(int chrId) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REMOVE_PLAYER_FROM_MAP);
|
|
p.writeInt(chrId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet catchMessage(int message) { // not done, I guess
|
|
final OutPacket p = OutPacket.create(SendOpcode.BRIDLE_MOB_CATCH_FAIL);
|
|
p.writeByte(message); // 1 = too strong, 2 = Elemental Rock
|
|
p.writeInt(0);//Maybe itemid?
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showAllCharacter(int chars, int unk) {
|
|
OutPacket p = OutPacket.create(SendOpcode.VIEW_ALL_CHAR);
|
|
p.writeByte(chars > 0 ? 1 : 5); // 2: already connected to server, 3 : unk error (view-all-characters), 5 : cannot find any
|
|
p.writeInt(chars);
|
|
p.writeInt(unk);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showAriantScoreBoard() { // thanks lrenex for pointing match's end scoreboard packet
|
|
return OutPacket.create(SendOpcode.ARIANT_ARENA_SHOW_RESULT);
|
|
}
|
|
|
|
public static Packet updateAriantPQRanking(final Character chr, final int score) {
|
|
return updateAriantPQRanking(new LinkedHashMap<Character, Integer>() {{
|
|
put(chr, score);
|
|
}});
|
|
}
|
|
|
|
public static Packet updateAriantPQRanking(Map<Character, Integer> playerScore) {
|
|
OutPacket p = OutPacket.create(SendOpcode.ARIANT_ARENA_USER_SCORE);
|
|
p.writeByte(playerScore.size());
|
|
for (Entry<Character, Integer> e : playerScore.entrySet()) {
|
|
p.writeString(e.getKey().getName());
|
|
p.writeInt(e.getValue());
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateWitchTowerScore(int score) {
|
|
OutPacket p = OutPacket.create(SendOpcode.WITCH_TOWER_SCORE_UPDATE);
|
|
p.writeByte(score);
|
|
return p;
|
|
}
|
|
|
|
public static Packet silentRemoveItemFromMap(int objId) {
|
|
return removeItemFromMap(objId, 1, 0);
|
|
}
|
|
|
|
/**
|
|
* animation: 0 - expire<br/> 1 - without animation<br/> 2 - pickup<br/> 4 -
|
|
* explode<br/> cid is ignored for 0 and 1
|
|
*
|
|
* @param objId
|
|
* @param animation
|
|
* @param chrId
|
|
* @return
|
|
*/
|
|
public static Packet removeItemFromMap(int objId, int animation, int chrId) {
|
|
return removeItemFromMap(objId, animation, chrId, false, 0);
|
|
}
|
|
|
|
/**
|
|
* animation: 0 - expire<br/> 1 - without animation<br/> 2 - pickup<br/> 4 -
|
|
* explode<br/> cid is ignored for 0 and 1.<br /><br />Flagging pet as true
|
|
* will make a pet pick up the item.
|
|
*
|
|
* @param objId
|
|
* @param animation
|
|
* @param chrId
|
|
* @param pet
|
|
* @param slot
|
|
* @return
|
|
*/
|
|
public static Packet removeItemFromMap(int objId, int animation, int chrId, boolean pet, int slot) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REMOVE_ITEM_FROM_MAP);
|
|
p.writeByte(animation); // expire
|
|
p.writeInt(objId);
|
|
if (animation >= 2) {
|
|
p.writeInt(chrId);
|
|
if (pet) {
|
|
p.writeByte(slot);
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateCharLook(Client target, Character chr) {
|
|
OutPacket p = OutPacket.create(SendOpcode.UPDATE_CHAR_LOOK);
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(1);
|
|
addCharLook(p, chr, false);
|
|
addRingLook(p, chr, true);
|
|
addRingLook(p, chr, false);
|
|
addMarriageRingLook(target, p, chr);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet damagePlayer(int skill, int monsteridfrom, int cid, int damage, int fake, int direction, boolean pgmr, int pgmr_1, boolean is_pg, int oid, int pos_x, int pos_y) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DAMAGE_PLAYER);
|
|
p.writeInt(cid);
|
|
p.writeByte(skill);
|
|
if (skill == -3) {
|
|
p.writeInt(0);
|
|
}
|
|
p.writeInt(damage);
|
|
if (skill != -4) {
|
|
p.writeInt(monsteridfrom);
|
|
p.writeByte(direction);
|
|
if (pgmr) {
|
|
p.writeByte(pgmr_1);
|
|
p.writeByte(is_pg ? 1 : 0);
|
|
p.writeInt(oid);
|
|
p.writeByte(6);
|
|
p.writeShort(pos_x);
|
|
p.writeShort(pos_y);
|
|
p.writeByte(0);
|
|
} else {
|
|
p.writeShort(0);
|
|
}
|
|
p.writeInt(damage);
|
|
if (fake > 0) {
|
|
p.writeInt(fake);
|
|
}
|
|
} else {
|
|
p.writeInt(damage);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendMapleLifeCharacterInfo() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MAPLELIFE_RESULT);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendMapleLifeNameError() {
|
|
OutPacket p = OutPacket.create(SendOpcode.MAPLELIFE_RESULT);
|
|
p.writeInt(2);
|
|
p.writeInt(3);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendMapleLifeError(int code) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MAPLELIFE_ERROR);
|
|
p.writeByte(0);
|
|
p.writeInt(code);
|
|
return p;
|
|
}
|
|
|
|
public static Packet charNameResponse(String charname, boolean nameUsed) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CHAR_NAME_RESPONSE);
|
|
p.writeString(charname);
|
|
p.writeByte(nameUsed ? 1 : 0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet addNewCharEntry(Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.ADD_NEW_CHAR_ENTRY);
|
|
p.writeByte(0);
|
|
addCharEntry(p, chr, false);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* State :
|
|
* 0x00 = success
|
|
* 0x06 = Trouble logging into the game?
|
|
* 0x09 = Unknown error
|
|
* 0x0A = Could not be processed due to too many connection requests to the server.
|
|
* 0x12 = invalid bday
|
|
* 0x14 = incorrect pic
|
|
* 0x16 = Cannot delete a guild master.
|
|
* 0x18 = Cannot delete a character with a pending wedding.
|
|
* 0x1A = Cannot delete a character with a pending world transfer.
|
|
* 0x1D = Cannot delete a character that has a family.
|
|
*
|
|
* @param cid
|
|
* @param state
|
|
* @return
|
|
*/
|
|
public static Packet deleteCharResponse(int cid, int state) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DELETE_CHAR_RESPONSE);
|
|
p.writeInt(cid);
|
|
p.writeByte(state);
|
|
return p;
|
|
}
|
|
|
|
public static Packet selectWorld(int world) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.LAST_CONNECTED_WORLD);
|
|
p.writeInt(world);//According to GMS, it should be the world that contains the most characters (most active)
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendRecommended(List<Pair<Integer, String>> worlds) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.RECOMMENDED_WORLD_MESSAGE);
|
|
p.writeByte(worlds.size());//size
|
|
for (Pair<Integer, String> world : worlds) {
|
|
p.writeInt(world.getLeft());
|
|
p.writeString(world.getRight());
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* @param chr
|
|
* @param isSelf
|
|
* @return
|
|
*/
|
|
public static Packet charInfo(Character chr) {
|
|
//3D 00 0A 43 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
|
final OutPacket p = OutPacket.create(SendOpcode.CHAR_INFO);
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(chr.getLevel());
|
|
p.writeShort(chr.getJob().getId());
|
|
p.writeShort(chr.getFame());
|
|
p.writeByte(chr.getMarriageRing() != null ? 1 : 0);
|
|
String guildName = "";
|
|
String allianceName = "";
|
|
if (chr.getGuildId() > 0) {
|
|
Guild mg = Server.getInstance().getGuild(chr.getGuildId());
|
|
guildName = mg.getName();
|
|
|
|
Alliance alliance = Server.getInstance().getAlliance(chr.getGuild().getAllianceId());
|
|
if (alliance != null) {
|
|
allianceName = alliance.getName();
|
|
}
|
|
}
|
|
p.writeString(guildName);
|
|
p.writeString(allianceName); // does not seem to work
|
|
p.writeByte(0); // pMedalInfo, thanks to Arnah (Vertisy)
|
|
|
|
Pet[] pets = chr.getPets();
|
|
Item inv = chr.getInventory(InventoryType.EQUIPPED).getItem((short) -114);
|
|
for (int i = 0; i < 3; i++) {
|
|
if (pets[i] != null) {
|
|
p.writeByte(pets[i].getUniqueId());
|
|
p.writeInt(pets[i].getItemId()); // petid
|
|
p.writeString(pets[i].getName());
|
|
p.writeByte(pets[i].getLevel()); // pet level
|
|
p.writeShort(pets[i].getCloseness()); // pet closeness
|
|
p.writeByte(pets[i].getFullness()); // pet fullness
|
|
p.writeShort(0);
|
|
p.writeInt(inv != null ? inv.getItemId() : 0);
|
|
}
|
|
}
|
|
p.writeByte(0); //end of pets
|
|
|
|
Item mount; //mounts can potentially crash the client if the player's level is not properly checked
|
|
if (chr.getMount() != null && (mount = chr.getInventory(InventoryType.EQUIPPED).getItem((short) -18)) != null && MapleItemInformationProvider.getInstance().getEquipLevelReq(mount.getItemId()) <= chr.getLevel()) {
|
|
Mount mmount = chr.getMount();
|
|
p.writeByte(mmount.getId()); //mount
|
|
p.writeInt(mmount.getLevel()); //level
|
|
p.writeInt(mmount.getExp()); //exp
|
|
p.writeInt(mmount.getTiredness()); //tiredness
|
|
} else {
|
|
p.writeByte(0);
|
|
}
|
|
p.writeByte(chr.getCashShop().getWishList().size());
|
|
for (int sn : chr.getCashShop().getWishList()) {
|
|
p.writeInt(sn);
|
|
}
|
|
|
|
MonsterBook book = chr.getMonsterBook();
|
|
p.writeInt(book.getBookLevel());
|
|
p.writeInt(book.getNormalCard());
|
|
p.writeInt(book.getSpecialCard());
|
|
p.writeInt(book.getTotalCards());
|
|
p.writeInt(chr.getMonsterBookCover() > 0 ? MapleItemInformationProvider.getInstance().getCardMobId(chr.getMonsterBookCover()) : 0);
|
|
Item medal = chr.getInventory(InventoryType.EQUIPPED).getItem((short) -49);
|
|
if (medal != null) {
|
|
p.writeInt(medal.getItemId());
|
|
} else {
|
|
p.writeInt(0);
|
|
}
|
|
ArrayList<Short> medalQuests = new ArrayList<>();
|
|
List<QuestStatus> completed = chr.getCompletedQuests();
|
|
for (QuestStatus qs : completed) {
|
|
if (qs.getQuest().getId() >= 29000) { // && q.getQuest().getId() <= 29923
|
|
medalQuests.add(qs.getQuest().getId());
|
|
}
|
|
}
|
|
|
|
Collections.sort(medalQuests);
|
|
p.writeShort(medalQuests.size());
|
|
for (Short s : medalQuests) {
|
|
p.writeShort(s);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* It is important that statups is in the correct order (see declaration
|
|
* order in BuffStat) since this method doesn't do automagical
|
|
* reordering.
|
|
*
|
|
* @param buffid
|
|
* @param bufflength
|
|
* @param statups
|
|
* @return
|
|
*/
|
|
//1F 00 00 00 00 00 03 00 00 40 00 00 00 E0 00 00 00 00 00 00 00 00 E0 01 8E AA 4F 00 00 C2 EB 0B E0 01 8E AA 4F 00 00 C2 EB 0B 0C 00 8E AA 4F 00 00 C2 EB 0B 44 02 8E AA 4F 00 00 C2 EB 0B 44 02 8E AA 4F 00 00 C2 EB 0B 00 00 E0 7A 1D 00 8E AA 4F 00 00 00 00 00 00 00 00 03
|
|
public static Packet giveBuff(int buffid, int bufflength, List<Pair<BuffStat, Integer>> statups) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.GIVE_BUFF);
|
|
boolean special = false;
|
|
writeLongMask(p, statups);
|
|
for (Pair<BuffStat, Integer> statup : statups) {
|
|
if (statup.getLeft().equals(BuffStat.MONSTER_RIDING) || statup.getLeft().equals(BuffStat.HOMING_BEACON)) {
|
|
special = true;
|
|
}
|
|
p.writeShort(statup.getRight().shortValue());
|
|
p.writeInt(buffid);
|
|
p.writeInt(bufflength);
|
|
}
|
|
p.writeInt(0);
|
|
p.writeByte(0);
|
|
p.writeInt(statups.get(0).getRight()); //Homing beacon ...
|
|
|
|
if (special) {
|
|
p.skip(3);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* @param cid
|
|
* @param statups
|
|
* @param mount
|
|
* @return
|
|
*/
|
|
public static Packet showMonsterRiding(int cid, Mount mount) { //Gtfo with this, this is just giveForeignBuff
|
|
final OutPacket p = OutPacket.create(SendOpcode.GIVE_FOREIGN_BUFF);
|
|
p.writeInt(cid);
|
|
p.writeLong(BuffStat.MONSTER_RIDING.getValue());
|
|
p.writeLong(0);
|
|
p.writeShort(0);
|
|
p.writeInt(mount.getItemId());
|
|
p.writeInt(mount.getSkillId());
|
|
p.writeInt(0); //Server Tick value.
|
|
p.writeShort(0);
|
|
p.writeByte(0); //Times you have been buffed
|
|
return p;
|
|
}
|
|
/* p.writeInt(cid);
|
|
writeLongMask(mplew, statups);
|
|
for (Pair<BuffStat, Integer> statup : statups) {
|
|
if (morph) {
|
|
p.writeInt(statup.getRight().intValue());
|
|
} else {
|
|
p.writeShort(statup.getRight().shortValue());
|
|
}
|
|
}
|
|
p.writeShort(0);
|
|
p.writeByte(0);*/
|
|
|
|
/**
|
|
* @param c
|
|
* @param quest
|
|
* @return
|
|
*/
|
|
public static Packet forfeitQuest(short quest) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(1);
|
|
p.writeShort(quest);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* @param c
|
|
* @param quest
|
|
* @return
|
|
*/
|
|
public static Packet completeQuest(short quest, long time) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(1);
|
|
p.writeShort(quest);
|
|
p.writeByte(2);
|
|
p.writeLong(getTime(time));
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* @param c
|
|
* @param quest
|
|
* @param npc
|
|
* @param progress
|
|
* @return
|
|
*/
|
|
|
|
public static Packet updateQuestInfo(short quest, int npc) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_QUEST_INFO);
|
|
p.writeByte(8); //0x0A in v95
|
|
p.writeShort(quest);
|
|
p.writeInt(npc);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet addQuestTimeLimit(final short quest, final int time) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_QUEST_INFO);
|
|
p.writeByte(6);
|
|
p.writeShort(1);//Size but meh, when will there be 2 at the same time? And it won't even replace the old one :)
|
|
p.writeShort(quest);
|
|
p.writeInt(time);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeQuestTimeLimit(final short quest) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_QUEST_INFO);
|
|
p.writeByte(7);
|
|
p.writeShort(1);//Position
|
|
p.writeShort(quest);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateQuest(Character chr, QuestStatus qs, boolean infoUpdate) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(1);
|
|
if (infoUpdate) {
|
|
QuestStatus iqs = chr.getQuest(qs.getInfoNumber());
|
|
p.writeShort(iqs.getQuestID());
|
|
p.writeByte(1);
|
|
p.writeString(iqs.getProgressData());
|
|
} else {
|
|
p.writeShort(qs.getQuest().getId());
|
|
p.writeByte(qs.getStatus().getId());
|
|
p.writeString(qs.getProgressData());
|
|
}
|
|
p.skip(5);
|
|
return p;
|
|
}
|
|
|
|
private static void writeLongMaskD(final OutPacket p, List<Pair<Disease, Integer>> statups) {
|
|
long firstmask = 0;
|
|
long secondmask = 0;
|
|
for (Pair<Disease, Integer> statup : statups) {
|
|
if (statup.getLeft().isFirst()) {
|
|
firstmask |= statup.getLeft().getValue();
|
|
} else {
|
|
secondmask |= statup.getLeft().getValue();
|
|
}
|
|
}
|
|
p.writeLong(firstmask);
|
|
p.writeLong(secondmask);
|
|
}
|
|
|
|
public static Packet giveDebuff(List<Pair<Disease, Integer>> statups, MobSkill skill) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.GIVE_BUFF);
|
|
writeLongMaskD(p, statups);
|
|
for (Pair<Disease, Integer> statup : statups) {
|
|
p.writeShort(statup.getRight().shortValue());
|
|
p.writeShort(skill.getSkillId());
|
|
p.writeShort(skill.getSkillLevel());
|
|
p.writeInt((int) skill.getDuration());
|
|
}
|
|
p.writeShort(0); // ??? wk charges have 600 here o.o
|
|
p.writeShort(900);//Delay
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet giveForeignDebuff(int chrId, List<Pair<Disease, Integer>> statups, MobSkill skill) {
|
|
// Poison damage visibility and missing diseases status visibility, extended through map transitions thanks to Ronan
|
|
OutPacket p = OutPacket.create(SendOpcode.GIVE_FOREIGN_BUFF);
|
|
p.writeInt(chrId);
|
|
writeLongMaskD(p, statups);
|
|
for (Pair<Disease, Integer> statup : statups) {
|
|
if (statup.getLeft() == Disease.POISON) {
|
|
p.writeShort(statup.getRight().shortValue());
|
|
}
|
|
p.writeShort(skill.getSkillId());
|
|
p.writeShort(skill.getSkillLevel());
|
|
}
|
|
p.writeShort(0); // same as give_buff
|
|
p.writeShort(900);//Delay
|
|
return p;
|
|
}
|
|
|
|
public static Packet cancelForeignFirstDebuff(int cid, long mask) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CANCEL_FOREIGN_BUFF);
|
|
p.writeInt(cid);
|
|
p.writeLong(mask);
|
|
p.writeLong(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet cancelForeignDebuff(int cid, long mask) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CANCEL_FOREIGN_BUFF);
|
|
p.writeInt(cid);
|
|
p.writeLong(0);
|
|
p.writeLong(mask);
|
|
return p;
|
|
}
|
|
|
|
public static Packet giveForeignBuff(int chrId, List<Pair<BuffStat, Integer>> statups) {
|
|
OutPacket p = OutPacket.create(SendOpcode.GIVE_FOREIGN_BUFF);
|
|
p.writeInt(chrId);
|
|
writeLongMask(p, statups);
|
|
for (Pair<BuffStat, Integer> statup : statups) {
|
|
p.writeShort(statup.getRight().shortValue());
|
|
}
|
|
p.writeInt(0);
|
|
p.writeShort(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet cancelForeignBuff(int chrId, List<BuffStat> statups) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CANCEL_FOREIGN_BUFF);
|
|
p.writeInt(chrId);
|
|
writeLongMaskFromList(p, statups);
|
|
return p;
|
|
}
|
|
|
|
public static Packet cancelBuff(List<BuffStat> statups) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CANCEL_BUFF);
|
|
writeLongMaskFromList(p, statups);
|
|
p.writeByte(1);//?
|
|
return p;
|
|
}
|
|
|
|
private static void writeLongMask(final OutPacket p, List<Pair<BuffStat, Integer>> statups) {
|
|
long firstmask = 0;
|
|
long secondmask = 0;
|
|
for (Pair<BuffStat, Integer> statup : statups) {
|
|
if (statup.getLeft().isFirst()) {
|
|
firstmask |= statup.getLeft().getValue();
|
|
} else {
|
|
secondmask |= statup.getLeft().getValue();
|
|
}
|
|
}
|
|
p.writeLong(firstmask);
|
|
p.writeLong(secondmask);
|
|
}
|
|
|
|
private static void writeLongMaskFromList(OutPacket p, List<BuffStat> statups) {
|
|
long firstmask = 0;
|
|
long secondmask = 0;
|
|
for (BuffStat statup : statups) {
|
|
if (statup.isFirst()) {
|
|
firstmask |= statup.getValue();
|
|
} else {
|
|
secondmask |= statup.getValue();
|
|
}
|
|
}
|
|
p.writeLong(firstmask);
|
|
p.writeLong(secondmask);
|
|
}
|
|
|
|
private static void writeLongEncodeTemporaryMask(final OutPacket p, Collection<MonsterStatus> stati) {
|
|
int[] masks = new int[4];
|
|
|
|
for (MonsterStatus statup : stati) {
|
|
int pos = statup.isFirst() ? 0 : 2;
|
|
for (int i = 0; i < 2; i++) {
|
|
masks[pos + i] |= statup.getValue() >> 32 * i;
|
|
}
|
|
}
|
|
|
|
for (int mask : masks) {
|
|
p.writeInt(mask);
|
|
}
|
|
}
|
|
|
|
public static Packet cancelDebuff(long mask) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CANCEL_BUFF);
|
|
p.writeLong(0);
|
|
p.writeLong(mask);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
private static void writeLongMaskSlowD(final OutPacket p) {
|
|
p.writeInt(0);
|
|
p.writeInt(2048);
|
|
p.writeLong(0);
|
|
}
|
|
|
|
public static Packet giveForeignSlowDebuff(int chrId, List<Pair<Disease, Integer>> statups, MobSkill skill) {
|
|
OutPacket p = OutPacket.create(SendOpcode.GIVE_FOREIGN_BUFF);
|
|
p.writeInt(chrId);
|
|
writeLongMaskSlowD(p);
|
|
for (Pair<Disease, Integer> statup : statups) {
|
|
if (statup.getLeft() == Disease.POISON) {
|
|
p.writeShort(statup.getRight().shortValue());
|
|
}
|
|
p.writeShort(skill.getSkillId());
|
|
p.writeShort(skill.getSkillLevel());
|
|
}
|
|
p.writeShort(0); // same as give_buff
|
|
p.writeShort(900);//Delay
|
|
return p;
|
|
}
|
|
|
|
public static Packet cancelForeignSlowDebuff(int chrId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CANCEL_FOREIGN_BUFF);
|
|
p.writeInt(chrId);
|
|
writeLongMaskSlowD(p);
|
|
return p;
|
|
}
|
|
|
|
private static void writeLongMaskChair(OutPacket p) {
|
|
p.writeInt(0);
|
|
p.writeInt(262144);
|
|
p.writeLong(0);
|
|
}
|
|
|
|
public static Packet giveForeignChairSkillEffect(int cid) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.GIVE_FOREIGN_BUFF);
|
|
p.writeInt(cid);
|
|
writeLongMaskChair(p);
|
|
|
|
p.writeShort(0);
|
|
p.writeShort(0);
|
|
p.writeShort(100);
|
|
p.writeShort(1);
|
|
|
|
p.writeShort(0);
|
|
p.writeShort(900);
|
|
|
|
p.skip(7);
|
|
|
|
return p;
|
|
}
|
|
|
|
// packet found thanks to Ronan
|
|
public static Packet giveForeignWKChargeEffect(int cid, int buffid, List<Pair<BuffStat, Integer>> statups) {
|
|
OutPacket p = OutPacket.create(SendOpcode.GIVE_FOREIGN_BUFF);
|
|
p.writeInt(cid);
|
|
writeLongMask(p, statups);
|
|
p.writeInt(buffid);
|
|
p.writeShort(600);
|
|
p.writeShort(1000);//Delay
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet cancelForeignChairSkillEffect(int chrId) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CANCEL_FOREIGN_BUFF);
|
|
p.writeInt(chrId);
|
|
writeLongMaskChair(p);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getPlayerShopChat(Character chr, String chat, boolean owner) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.CHAT.getCode());
|
|
p.writeByte(PlayerInteractionHandler.Action.CHAT_THING.getCode());
|
|
p.writeBool(!owner);
|
|
p.writeString(chr.getName() + " : " + chat);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getPlayerShopNewVisitor(Character chr, int slot) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.VISIT.getCode());
|
|
p.writeByte(slot);
|
|
addCharLook(p, chr, false);
|
|
p.writeString(chr.getName());
|
|
return p;
|
|
}
|
|
|
|
public static Packet getPlayerShopRemoveVisitor(int slot) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.EXIT.getCode());
|
|
if (slot != 0) {
|
|
p.writeShort(slot);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet getTradePartnerAdd(Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.VISIT.getCode());
|
|
p.writeByte(1);
|
|
addCharLook(p, chr, false);
|
|
p.writeString(chr.getName());
|
|
return p;
|
|
}
|
|
|
|
public static Packet tradeInvite(Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.INVITE.getCode());
|
|
p.writeByte(3);
|
|
p.writeString(chr.getName());
|
|
p.writeBytes(new byte[]{(byte) 0xB7, (byte) 0x50, 0, 0});
|
|
return p;
|
|
}
|
|
|
|
public static Packet getTradeMesoSet(byte number, int meso) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.SET_MESO.getCode());
|
|
p.writeByte(number);
|
|
p.writeInt(meso);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getTradeItemAdd(byte number, Item item) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.SET_ITEMS.getCode());
|
|
p.writeByte(number);
|
|
p.writeByte(item.getPosition());
|
|
addItemInfo(p, item, true);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getPlayerShopItemUpdate(MaplePlayerShop shop) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.UPDATE_MERCHANT.getCode());
|
|
p.writeByte(shop.getItems().size());
|
|
for (MaplePlayerShopItem item : shop.getItems()) {
|
|
p.writeShort(item.getBundles());
|
|
p.writeShort(item.getItem().getQuantity());
|
|
p.writeInt(item.getPrice());
|
|
addItemInfo(p, item.getItem(), true);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet getPlayerShopOwnerUpdate(MaplePlayerShop.SoldItem item, int position) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.UPDATE_PLAYERSHOP.getCode());
|
|
p.writeByte(position);
|
|
p.writeShort(item.getQuantity());
|
|
p.writeString(item.getBuyer());
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* @param c
|
|
* @param shop
|
|
* @param owner
|
|
* @return
|
|
*/
|
|
public static Packet getPlayerShop(MaplePlayerShop shop, boolean owner) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.ROOM.getCode());
|
|
p.writeByte(4);
|
|
p.writeByte(4);
|
|
p.writeByte(owner ? 0 : 1);
|
|
|
|
if (owner) {
|
|
List<MaplePlayerShop.SoldItem> sold = shop.getSold();
|
|
p.writeByte(sold.size());
|
|
for (MaplePlayerShop.SoldItem s : sold) {
|
|
p.writeInt(s.getItemId());
|
|
p.writeShort(s.getQuantity());
|
|
p.writeInt(s.getMesos());
|
|
p.writeString(s.getBuyer());
|
|
}
|
|
} else {
|
|
p.writeByte(0);
|
|
}
|
|
|
|
addCharLook(p, shop.getOwner(), false);
|
|
p.writeString(shop.getOwner().getName());
|
|
|
|
Character[] visitors = shop.getVisitors();
|
|
for (int i = 0; i < 3; i++) {
|
|
if (visitors[i] != null) {
|
|
p.writeByte(i + 1);
|
|
addCharLook(p, visitors[i], false);
|
|
p.writeString(visitors[i].getName());
|
|
}
|
|
}
|
|
|
|
p.writeByte(0xFF);
|
|
p.writeString(shop.getDescription());
|
|
List<MaplePlayerShopItem> items = shop.getItems();
|
|
p.writeByte(0x10); //TODO SLOTS, which is 16 for most stores...slotMax
|
|
p.writeByte(items.size());
|
|
for (MaplePlayerShopItem item : items) {
|
|
p.writeShort(item.getBundles());
|
|
p.writeShort(item.getItem().getQuantity());
|
|
p.writeInt(item.getPrice());
|
|
addItemInfo(p, item.getItem(), true);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet getTradeStart(Client c, MapleTrade trade, byte number) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.ROOM.getCode());
|
|
p.writeByte(3);
|
|
p.writeByte(2);
|
|
p.writeByte(number);
|
|
if (number == 1) {
|
|
p.writeByte(0);
|
|
addCharLook(p, trade.getPartner().getChr(), false);
|
|
p.writeString(trade.getPartner().getChr().getName());
|
|
}
|
|
p.writeByte(number);
|
|
addCharLook(p, c.getPlayer(), false);
|
|
p.writeString(c.getPlayer().getName());
|
|
p.writeByte(0xFF);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getTradeConfirmation() {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.CONFIRM.getCode());
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Possible values for <code>operation</code>:<br> 2: Trade cancelled, by the
|
|
* other character<br> 7: Trade successful<br> 8: Trade unsuccessful<br>
|
|
* 9: Cannot carry more one-of-a-kind items<br> 12: Cannot trade on different maps<br>
|
|
* 13: Cannot trade, game files damaged<br>
|
|
*
|
|
* @param number
|
|
* @param operation
|
|
* @return
|
|
*/
|
|
public static Packet getTradeResult(byte number, byte operation) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.EXIT.getCode());
|
|
p.writeByte(number);
|
|
p.writeByte(operation);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Possible values for <code>speaker</code>:<br> 0: Npc talking (left)<br>
|
|
* 1: Npc talking (right)<br> 2: Player talking (left)<br> 3: Player talking
|
|
* (left)<br>
|
|
*
|
|
* @param npc Npcid
|
|
* @param msgType
|
|
* @param talk
|
|
* @param endBytes
|
|
* @param speaker
|
|
* @return
|
|
*/
|
|
public static Packet getNPCTalk(int npc, byte msgType, String talk, String endBytes, byte speaker) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.NPC_TALK);
|
|
p.writeByte(4); // ?
|
|
p.writeInt(npc);
|
|
p.writeByte(msgType);
|
|
p.writeByte(speaker);
|
|
p.writeString(talk);
|
|
p.writeBytes(HexTool.getByteArrayFromHexString(endBytes));
|
|
return p;
|
|
}
|
|
|
|
public static Packet getDimensionalMirror(String talk) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.NPC_TALK);
|
|
p.writeByte(4); // ?
|
|
p.writeInt(9010022);
|
|
p.writeByte(0x0E);
|
|
p.writeByte(0);
|
|
p.writeInt(0);
|
|
p.writeString(talk);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getNPCTalkStyle(int npc, String talk, int[] styles) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.NPC_TALK);
|
|
p.writeByte(4); // ?
|
|
p.writeInt(npc);
|
|
p.writeByte(7);
|
|
p.writeByte(0); //speaker
|
|
p.writeString(talk);
|
|
p.writeByte(styles.length);
|
|
for (int style : styles) {
|
|
p.writeInt(style);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet getNPCTalkNum(int npc, String talk, int def, int min, int max) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.NPC_TALK);
|
|
p.writeByte(4); // ?
|
|
p.writeInt(npc);
|
|
p.writeByte(3);
|
|
p.writeByte(0); //speaker
|
|
p.writeString(talk);
|
|
p.writeInt(def);
|
|
p.writeInt(min);
|
|
p.writeInt(max);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getNPCTalkText(int npc, String talk, String def) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.NPC_TALK);
|
|
p.writeByte(4); // Doesn't matter
|
|
p.writeInt(npc);
|
|
p.writeByte(2);
|
|
p.writeByte(0); //speaker
|
|
p.writeString(talk);
|
|
p.writeString(def);//:D
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
// NPC Quiz packets thanks to Eric
|
|
public static Packet OnAskQuiz(int nSpeakerTypeID, int nSpeakerTemplateID, int nResCode, String sTitle, String sProblemText, String sHintText, int nMinInput, int nMaxInput, int tRemainInitialQuiz) {
|
|
OutPacket p = OutPacket.create(SendOpcode.NPC_TALK);
|
|
p.writeByte(nSpeakerTypeID);
|
|
p.writeInt(nSpeakerTemplateID);
|
|
p.writeByte(0x6);
|
|
p.writeByte(0);
|
|
p.writeByte(nResCode);
|
|
if (nResCode == 0x0) {//fail has no bytes <3
|
|
p.writeString(sTitle);
|
|
p.writeString(sProblemText);
|
|
p.writeString(sHintText);
|
|
p.writeShort(nMinInput);
|
|
p.writeShort(nMaxInput);
|
|
p.writeInt(tRemainInitialQuiz);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet OnAskSpeedQuiz(int nSpeakerTypeID, int nSpeakerTemplateID, int nResCode, int nType, int dwAnswer, int nCorrect, int nRemain, int tRemainInitialQuiz) {
|
|
OutPacket p = OutPacket.create(SendOpcode.NPC_TALK);
|
|
p.writeByte(nSpeakerTypeID);
|
|
p.writeInt(nSpeakerTemplateID);
|
|
p.writeByte(0x7);
|
|
p.writeByte(0);
|
|
p.writeByte(nResCode);
|
|
if (nResCode == 0x0) {//fail has no bytes <3
|
|
p.writeInt(nType);
|
|
p.writeInt(dwAnswer);
|
|
p.writeInt(nCorrect);
|
|
p.writeInt(nRemain);
|
|
p.writeInt(tRemainInitialQuiz);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBuffEffect(int chrId, int skillId, int effectId) {
|
|
return showBuffEffect(chrId, skillId, effectId, (byte) 3);
|
|
}
|
|
|
|
public static Packet showBuffEffect(int chrId, int skillId, int effectId, byte direction) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(chrId);
|
|
p.writeByte(effectId); //buff level
|
|
p.writeInt(skillId);
|
|
p.writeByte(direction);
|
|
p.writeByte(1);
|
|
p.writeLong(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBuffEffect(int chrId, int skillId, int skillLv, int effectId, byte direction) { // updated packet structure found thanks to Rien dev team
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(chrId);
|
|
p.writeByte(effectId);
|
|
p.writeInt(skillId);
|
|
p.writeByte(0);
|
|
p.writeByte(skillLv);
|
|
p.writeByte(direction);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showOwnBuffEffect(int skillId, int effectId) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(effectId);
|
|
p.writeInt(skillId);
|
|
p.writeByte(0xA9);
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showOwnBerserk(int skilllevel, boolean Berserk) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(1);
|
|
p.writeInt(1320006);
|
|
p.writeByte(0xA9);
|
|
p.writeByte(skilllevel);
|
|
p.writeByte(Berserk ? 1 : 0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBerserk(int chrId, int skillLv, boolean berserk) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(chrId);
|
|
p.writeByte(1);
|
|
p.writeInt(1320006);
|
|
p.writeByte(0xA9);
|
|
p.writeByte(skillLv);
|
|
p.writeBool(berserk);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateSkill(int skillId, int level, int masterlevel, long expiration) {
|
|
OutPacket p = OutPacket.create(SendOpcode.UPDATE_SKILLS);
|
|
p.writeByte(1);
|
|
p.writeShort(1);
|
|
p.writeInt(skillId);
|
|
p.writeInt(level);
|
|
p.writeInt(masterlevel);
|
|
addExpirationTime(p, expiration);
|
|
p.writeByte(4);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getShowQuestCompletion(int id) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.QUEST_CLEAR);
|
|
p.writeShort(id);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getKeymap(Map<Integer, KeyBinding> keybindings) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.KEYMAP);
|
|
p.writeByte(0);
|
|
for (int x = 0; x < 90; x++) {
|
|
KeyBinding binding = keybindings.get(x);
|
|
if (binding != null) {
|
|
p.writeByte(binding.getType());
|
|
p.writeInt(binding.getAction());
|
|
} else {
|
|
p.writeByte(0);
|
|
p.writeInt(0);
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet QuickslotMappedInit(QuickslotBinding pQuickslot) {
|
|
OutPacket p = OutPacket.create(SendOpcode.QUICKSLOT_INIT);
|
|
pQuickslot.encode(p);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getInventoryFull() {
|
|
return modifyInventory(true, Collections.emptyList());
|
|
}
|
|
|
|
public static Packet getShowInventoryFull() {
|
|
return getShowInventoryStatus(0xff);
|
|
}
|
|
|
|
public static Packet showItemUnavailable() {
|
|
return getShowInventoryStatus(0xfe);
|
|
}
|
|
|
|
public static Packet getShowInventoryStatus(int mode) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(0);
|
|
p.writeByte(mode);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getStorage(int npcId, byte slots, Collection<Item> items, int meso) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.STORAGE);
|
|
p.writeByte(0x16);
|
|
p.writeInt(npcId);
|
|
p.writeByte(slots);
|
|
p.writeShort(0x7E);
|
|
p.writeShort(0);
|
|
p.writeInt(0);
|
|
p.writeInt(meso);
|
|
p.writeShort(0);
|
|
p.writeByte((byte) items.size());
|
|
for (Item item : items) {
|
|
addItemInfo(p, item, true);
|
|
}
|
|
p.writeShort(0);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* 0x0A = Inv full
|
|
* 0x0B = You do not have enough mesos
|
|
* 0x0C = One-Of-A-Kind error
|
|
*/
|
|
public static Packet getStorageError(byte i) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.STORAGE);
|
|
p.writeByte(i);
|
|
return p;
|
|
}
|
|
|
|
public static Packet mesoStorage(byte slots, int meso) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.STORAGE);
|
|
p.writeByte(0x13);
|
|
p.writeByte(slots);
|
|
p.writeShort(2);
|
|
p.writeShort(0);
|
|
p.writeInt(0);
|
|
p.writeInt(meso);
|
|
return p;
|
|
}
|
|
|
|
public static Packet storeStorage(byte slots, InventoryType type, Collection<Item> items) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.STORAGE);
|
|
p.writeByte(0xD);
|
|
p.writeByte(slots);
|
|
p.writeShort(type.getBitfieldEncoding());
|
|
p.writeShort(0);
|
|
p.writeInt(0);
|
|
p.writeByte(items.size());
|
|
for (Item item : items) {
|
|
addItemInfo(p, item, true);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet takeOutStorage(byte slots, InventoryType type, Collection<Item> items) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.STORAGE);
|
|
p.writeByte(0x9);
|
|
p.writeByte(slots);
|
|
p.writeShort(type.getBitfieldEncoding());
|
|
p.writeShort(0);
|
|
p.writeInt(0);
|
|
p.writeByte(items.size());
|
|
for (Item item : items) {
|
|
addItemInfo(p, item, true);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet arrangeStorage(byte slots, Collection<Item> items) {
|
|
OutPacket p = OutPacket.create(SendOpcode.STORAGE);
|
|
p.writeByte(0xF);
|
|
p.writeByte(slots);
|
|
p.writeByte(124);
|
|
p.skip(10);
|
|
p.writeByte(items.size());
|
|
for (Item item : items) {
|
|
addItemInfo(p, item, true);
|
|
}
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* @param oid
|
|
* @param remhppercentage
|
|
* @return
|
|
*/
|
|
public static Packet showMonsterHP(int oid, int remhppercentage) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_MONSTER_HP);
|
|
p.writeInt(oid);
|
|
p.writeByte(remhppercentage);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBossHP(int oid, int currHP, int maxHP, byte tagColor, byte tagBgColor) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FIELD_EFFECT);
|
|
p.writeByte(5);
|
|
p.writeInt(oid);
|
|
p.writeInt(currHP);
|
|
p.writeInt(maxHP);
|
|
p.writeByte(tagColor);
|
|
p.writeByte(tagBgColor);
|
|
return p;
|
|
}
|
|
|
|
private static Pair<Integer, Integer> normalizedCustomMaxHP(long currHP, long maxHP) {
|
|
int sendHP, sendMaxHP;
|
|
|
|
if (maxHP <= Integer.MAX_VALUE) {
|
|
sendHP = (int) currHP;
|
|
sendMaxHP = (int) maxHP;
|
|
} else {
|
|
float f = ((float) currHP) / maxHP;
|
|
|
|
sendHP = (int) (Integer.MAX_VALUE * f);
|
|
sendMaxHP = Integer.MAX_VALUE;
|
|
}
|
|
|
|
return new Pair<>(sendHP, sendMaxHP);
|
|
}
|
|
|
|
public static Packet customShowBossHP(byte call, int oid, long currHP, long maxHP, byte tagColor, byte tagBgColor) {
|
|
Pair<Integer, Integer> customHP = normalizedCustomMaxHP(currHP, maxHP);
|
|
|
|
final OutPacket p = OutPacket.create(SendOpcode.FIELD_EFFECT);
|
|
p.writeByte(call);
|
|
p.writeInt(oid);
|
|
p.writeInt(customHP.left);
|
|
p.writeInt(customHP.right);
|
|
p.writeByte(tagColor);
|
|
p.writeByte(tagBgColor);
|
|
return p;
|
|
}
|
|
|
|
public static Packet giveFameResponse(int mode, String charname, int newfame) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAME_RESPONSE);
|
|
p.writeByte(0);
|
|
p.writeString(charname);
|
|
p.writeByte(mode);
|
|
p.writeShort(newfame);
|
|
p.writeShort(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* status can be: <br> 0: ok, use giveFameResponse<br> 1: the username is
|
|
* incorrectly entered<br> 2: users under level 15 are unable to toggle with
|
|
* fame.<br> 3: can't raise or drop fame anymore today.<br> 4: can't raise
|
|
* or drop fame for this character for this month anymore.<br> 5: received
|
|
* fame, use receiveFame()<br> 6: level of fame neither has been raised nor
|
|
* dropped due to an unexpected error
|
|
*
|
|
* @param status
|
|
* @return
|
|
*/
|
|
public static Packet giveFameErrorResponse(int status) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAME_RESPONSE);
|
|
p.writeByte(status);
|
|
return p;
|
|
}
|
|
|
|
public static Packet receiveFame(int mode, String charnameFrom) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAME_RESPONSE);
|
|
p.writeByte(5);
|
|
p.writeString(charnameFrom);
|
|
p.writeByte(mode);
|
|
return p;
|
|
}
|
|
|
|
public static Packet partyCreated(MapleParty party, int partycharid) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARTY_OPERATION);
|
|
p.writeByte(8);
|
|
p.writeInt(party.getId());
|
|
|
|
Map<Integer, MapleDoor> partyDoors = party.getDoors();
|
|
if (partyDoors.size() > 0) {
|
|
MapleDoor door = partyDoors.get(partycharid);
|
|
|
|
if (door != null) {
|
|
MapleDoorObject mdo = door.getAreaDoor();
|
|
p.writeInt(mdo.getTo().getId());
|
|
p.writeInt(mdo.getFrom().getId());
|
|
p.writeInt(mdo.getPosition().x);
|
|
p.writeInt(mdo.getPosition().y);
|
|
} else {
|
|
p.writeInt(999999999);
|
|
p.writeInt(999999999);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
}
|
|
} else {
|
|
p.writeInt(999999999);
|
|
p.writeInt(999999999);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet partyInvite(Character from) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARTY_OPERATION);
|
|
p.writeByte(4);
|
|
p.writeInt(from.getParty().getId());
|
|
p.writeString(from.getName());
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet partySearchInvite(Character from) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARTY_OPERATION);
|
|
p.writeByte(4);
|
|
p.writeInt(from.getParty().getId());
|
|
p.writeString("PS: " + from.getName());
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* 10: A beginner can't create a party. 1/5/6/11/14/19: Your request for a
|
|
* party didn't work due to an unexpected error. 12: Quit as leader of the
|
|
* party. 13: You have yet to join a party.
|
|
* 16: Already have joined a party. 17: The party you're trying to join is
|
|
* already in full capacity. 19: Unable to find the requested character in
|
|
* this channel. 25: Cannot kick another user in this map. 28/29: Leadership
|
|
* can only be given to a party member in the vicinity. 30: Change leadership
|
|
* only on same channel.
|
|
*
|
|
* @param message
|
|
* @return
|
|
*/
|
|
public static Packet partyStatusMessage(int message) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARTY_OPERATION);
|
|
p.writeByte(message);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* 21: Player is blocking any party invitations, 22: Player is taking care of
|
|
* another invitation, 23: Player have denied request to the party.
|
|
*
|
|
* @param message
|
|
* @param charname
|
|
* @return
|
|
*/
|
|
public static Packet partyStatusMessage(int message, String charname) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARTY_OPERATION);
|
|
p.writeByte(message);
|
|
p.writeString(charname);
|
|
return p;
|
|
}
|
|
|
|
private static void addPartyStatus(int forchannel, MapleParty party, OutPacket p, boolean leaving) {
|
|
List<MaplePartyCharacter> partymembers = new ArrayList<>(party.getMembers());
|
|
while (partymembers.size() < 6) {
|
|
partymembers.add(new MaplePartyCharacter());
|
|
}
|
|
for (MaplePartyCharacter partychar : partymembers) {
|
|
p.writeInt(partychar.getId());
|
|
}
|
|
for (MaplePartyCharacter partychar : partymembers) {
|
|
p.writeFixedString(getRightPaddedStr(partychar.getName(), '\0', 13));
|
|
}
|
|
for (MaplePartyCharacter partychar : partymembers) {
|
|
p.writeInt(partychar.getJobId());
|
|
}
|
|
for (MaplePartyCharacter partychar : partymembers) {
|
|
p.writeInt(partychar.getLevel());
|
|
}
|
|
for (MaplePartyCharacter partychar : partymembers) {
|
|
if (partychar.isOnline()) {
|
|
p.writeInt(partychar.getChannel() - 1);
|
|
} else {
|
|
p.writeInt(-2);
|
|
}
|
|
}
|
|
p.writeInt(party.getLeader().getId());
|
|
for (MaplePartyCharacter partychar : partymembers) {
|
|
if (partychar.getChannel() == forchannel) {
|
|
p.writeInt(partychar.getMapId());
|
|
} else {
|
|
p.writeInt(0);
|
|
}
|
|
}
|
|
|
|
Map<Integer, MapleDoor> partyDoors = party.getDoors();
|
|
for (MaplePartyCharacter partychar : partymembers) {
|
|
if (partychar.getChannel() == forchannel && !leaving) {
|
|
if (partyDoors.size() > 0) {
|
|
MapleDoor door = partyDoors.get(partychar.getId());
|
|
if (door != null) {
|
|
MapleDoorObject mdo = door.getTownDoor();
|
|
p.writeInt(mdo.getTown().getId());
|
|
p.writeInt(mdo.getArea().getId());
|
|
p.writeInt(mdo.getPosition().x);
|
|
p.writeInt(mdo.getPosition().y);
|
|
} else {
|
|
p.writeInt(999999999);
|
|
p.writeInt(999999999);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
}
|
|
} else {
|
|
p.writeInt(999999999);
|
|
p.writeInt(999999999);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
}
|
|
} else {
|
|
p.writeInt(999999999);
|
|
p.writeInt(999999999);
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Packet updateParty(int forChannel, MapleParty party, PartyOperation op, MaplePartyCharacter target) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARTY_OPERATION);
|
|
switch (op) {
|
|
case DISBAND:
|
|
case EXPEL:
|
|
case LEAVE:
|
|
p.writeByte(0x0C);
|
|
p.writeInt(party.getId());
|
|
p.writeInt(target.getId());
|
|
if (op == PartyOperation.DISBAND) {
|
|
p.writeByte(0);
|
|
p.writeInt(party.getId());
|
|
} else {
|
|
p.writeByte(1);
|
|
if (op == PartyOperation.EXPEL) {
|
|
p.writeByte(1);
|
|
} else {
|
|
p.writeByte(0);
|
|
}
|
|
p.writeString(target.getName());
|
|
addPartyStatus(forChannel, party, p, false);
|
|
}
|
|
break;
|
|
case JOIN:
|
|
p.writeByte(0xF);
|
|
p.writeInt(party.getId());
|
|
p.writeString(target.getName());
|
|
addPartyStatus(forChannel, party, p, false);
|
|
break;
|
|
case SILENT_UPDATE:
|
|
case LOG_ONOFF:
|
|
p.writeByte(0x7);
|
|
p.writeInt(party.getId());
|
|
addPartyStatus(forChannel, party, p, false);
|
|
break;
|
|
case CHANGE_LEADER:
|
|
p.writeByte(0x1B);
|
|
p.writeInt(target.getId());
|
|
p.writeByte(0);
|
|
break;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet partyPortal(int townId, int targetId, Point position) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARTY_OPERATION);
|
|
p.writeShort(0x23);
|
|
p.writeInt(townId);
|
|
p.writeInt(targetId);
|
|
p.writePos(position);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updatePartyMemberHP(int cid, int curhp, int maxhp) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_PARTYMEMBER_HP);
|
|
p.writeInt(cid);
|
|
p.writeInt(curhp);
|
|
p.writeInt(maxhp);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* mode: 0 buddychat; 1 partychat; 2 guildchat
|
|
*
|
|
* @param name
|
|
* @param chattext
|
|
* @param mode
|
|
* @return
|
|
*/
|
|
public static Packet multiChat(String name, String chattext, int mode) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MULTICHAT);
|
|
p.writeByte(mode);
|
|
p.writeString(name);
|
|
p.writeString(chattext);
|
|
return p;
|
|
}
|
|
|
|
private static void writeIntMask(OutPacket p, Map<MonsterStatus, Integer> stats) {
|
|
int firstmask = 0;
|
|
int secondmask = 0;
|
|
for (MonsterStatus stat : stats.keySet()) {
|
|
if (stat.isFirst()) {
|
|
firstmask |= stat.getValue();
|
|
} else {
|
|
secondmask |= stat.getValue();
|
|
}
|
|
}
|
|
p.writeInt(firstmask);
|
|
p.writeInt(secondmask);
|
|
}
|
|
|
|
public static Packet applyMonsterStatus(final int oid, final MonsterStatusEffect mse, final List<Integer> reflection) {
|
|
Map<MonsterStatus, Integer> stati = mse.getStati();
|
|
final OutPacket p = OutPacket.create(SendOpcode.APPLY_MONSTER_STATUS);
|
|
p.writeInt(oid);
|
|
p.writeLong(0);
|
|
writeIntMask(p, stati);
|
|
for (Map.Entry<MonsterStatus, Integer> stat : stati.entrySet()) {
|
|
p.writeShort(stat.getValue());
|
|
if (mse.isMonsterSkill()) {
|
|
p.writeShort(mse.getMobSkill().getSkillId());
|
|
p.writeShort(mse.getMobSkill().getSkillLevel());
|
|
} else {
|
|
p.writeInt(mse.getSkill().getId());
|
|
}
|
|
p.writeShort(-1); // might actually be the buffTime but it's not displayed anywhere
|
|
}
|
|
int size = stati.size(); // size
|
|
if (reflection != null) {
|
|
for (Integer ref : reflection) {
|
|
p.writeInt(ref);
|
|
}
|
|
if (reflection.size() > 0) {
|
|
size /= 2; // This gives 2 buffs per reflection but it's really one buff
|
|
}
|
|
}
|
|
p.writeByte(size); // size
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet cancelMonsterStatus(int oid, Map<MonsterStatus, Integer> stats) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CANCEL_MONSTER_STATUS);
|
|
p.writeInt(oid);
|
|
p.writeLong(0);
|
|
writeIntMask(p, stats);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getClock(int time) { // time in seconds
|
|
OutPacket p = OutPacket.create(SendOpcode.CLOCK);
|
|
p.writeByte(2); // clock type. if you send 3 here you have to send another byte (which does not matter at all) before the timestamp
|
|
p.writeInt(time);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getClockTime(int hour, int min, int sec) { // Current Time
|
|
OutPacket p = OutPacket.create(SendOpcode.CLOCK);
|
|
p.writeByte(1); //Clock-Type
|
|
p.writeByte(hour);
|
|
p.writeByte(min);
|
|
p.writeByte(sec);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeClock() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.STOP_CLOCK);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet spawnMist(int objId, int ownerChrId, int skill, int level, MapleMist mist) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_MIST);
|
|
p.writeInt(objId);
|
|
p.writeInt(mist.isMobMist() ? 0 : mist.isPoisonMist() ? 1 : mist.isRecoveryMist() ? 4 : 2); // mob mist = 0, player poison = 1, smokescreen = 2, unknown = 3, recovery = 4
|
|
p.writeInt(ownerChrId);
|
|
p.writeInt(skill);
|
|
p.writeByte(level);
|
|
p.writeShort(mist.getSkillDelay()); // Skill delay
|
|
p.writeInt(mist.getBox().x);
|
|
p.writeInt(mist.getBox().y);
|
|
p.writeInt(mist.getBox().x + mist.getBox().width);
|
|
p.writeInt(mist.getBox().y + mist.getBox().height);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeMist(int objId) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REMOVE_MIST);
|
|
p.writeInt(objId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet damageSummon(int cid, int oid, int damage, int monsterIdFrom) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DAMAGE_SUMMON);
|
|
p.writeInt(cid);
|
|
p.writeInt(oid);
|
|
p.writeByte(12);
|
|
p.writeInt(damage); // damage display doesn't seem to work...
|
|
p.writeInt(monsterIdFrom);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet damageMonster(int oid, int damage) {
|
|
return damageMonster(oid, damage, 0, 0);
|
|
}
|
|
|
|
public static Packet healMonster(int oid, int heal, int curhp, int maxhp) {
|
|
return damageMonster(oid, -heal, curhp, maxhp);
|
|
}
|
|
|
|
private static Packet damageMonster(int oid, int damage, int curhp, int maxhp) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DAMAGE_MONSTER);
|
|
p.writeInt(oid);
|
|
p.writeByte(0);
|
|
p.writeInt(damage);
|
|
p.writeInt(curhp);
|
|
p.writeInt(maxhp);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateBuddylist(Collection<BuddylistEntry> buddylist) {
|
|
OutPacket p = OutPacket.create(SendOpcode.BUDDYLIST);
|
|
p.writeByte(7);
|
|
p.writeByte(buddylist.size());
|
|
for (BuddylistEntry buddy : buddylist) {
|
|
if (buddy.isVisible()) {
|
|
p.writeInt(buddy.getCharacterId()); // cid
|
|
p.writeFixedString(getRightPaddedStr(buddy.getName(), '\0', 13));
|
|
p.writeByte(0); // opposite status
|
|
p.writeInt(buddy.getChannel() - 1);
|
|
p.writeFixedString(getRightPaddedStr(buddy.getGroup(), '\0', 13));
|
|
p.writeInt(0);//mapid?
|
|
}
|
|
}
|
|
for (int x = 0; x < buddylist.size(); x++) {
|
|
p.writeInt(0);//mapid?
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet buddylistMessage(byte message) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.BUDDYLIST);
|
|
p.writeByte(message);
|
|
return p;
|
|
}
|
|
|
|
public static Packet requestBuddylistAdd(int chrIdFrom, int chrId, String nameFrom) {
|
|
OutPacket p = OutPacket.create(SendOpcode.BUDDYLIST);
|
|
p.writeByte(9);
|
|
p.writeInt(chrIdFrom);
|
|
p.writeString(nameFrom);
|
|
p.writeInt(chrIdFrom);
|
|
p.writeFixedString(getRightPaddedStr(nameFrom, '\0', 11));
|
|
p.writeByte(0x09);
|
|
p.writeByte(0xf0);
|
|
p.writeByte(0x01);
|
|
p.writeInt(0x0f);
|
|
p.writeFixedString("Default Group");
|
|
p.writeByte(0);
|
|
p.writeInt(chrId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateBuddyChannel(int characterid, int channel) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.BUDDYLIST);
|
|
p.writeByte(0x14);
|
|
p.writeInt(characterid);
|
|
p.writeByte(0);
|
|
p.writeInt(channel);
|
|
return p;
|
|
}
|
|
|
|
public static Packet itemEffect(int characterid, int itemid) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_EFFECT);
|
|
p.writeInt(characterid);
|
|
p.writeInt(itemid);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateBuddyCapacity(int capacity) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.BUDDYLIST);
|
|
p.writeByte(0x15);
|
|
p.writeByte(capacity);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showChair(int characterid, int itemid) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_CHAIR);
|
|
p.writeInt(characterid);
|
|
p.writeInt(itemid);
|
|
return p;
|
|
}
|
|
|
|
public static Packet cancelChair(int id) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CANCEL_CHAIR);
|
|
if (id < 0) {
|
|
p.writeByte(0);
|
|
} else {
|
|
p.writeByte(1);
|
|
p.writeShort(id);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
// is there a way to spawn reactors non-animated?
|
|
public static Packet spawnReactor(MapleReactor reactor) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REACTOR_SPAWN);
|
|
p.writeInt(reactor.getObjectId());
|
|
p.writeInt(reactor.getId());
|
|
p.writeByte(reactor.getState());
|
|
p.writePos(reactor.getPosition());
|
|
p.writeByte(0);
|
|
p.writeShort(0);
|
|
return p;
|
|
}
|
|
|
|
// is there a way to trigger reactors without performing the hit animation?
|
|
public static Packet triggerReactor(MapleReactor reactor, int stance) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REACTOR_HIT);
|
|
p.writeInt(reactor.getObjectId());
|
|
p.writeByte(reactor.getState());
|
|
p.writePos(reactor.getPosition());
|
|
p.writeByte(stance);
|
|
p.writeShort(0);
|
|
p.writeByte(5); // frame delay, set to 5 since there doesn't appear to be a fixed formula for it
|
|
return p;
|
|
}
|
|
|
|
public static Packet destroyReactor(MapleReactor reactor) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REACTOR_DESTROY);
|
|
p.writeInt(reactor.getObjectId());
|
|
p.writeByte(reactor.getState());
|
|
p.writePos(reactor.getPosition());
|
|
return p;
|
|
}
|
|
|
|
public static Packet musicChange(String song) {
|
|
return environmentChange(song, 6);
|
|
}
|
|
|
|
public static Packet showEffect(String effect) {
|
|
return environmentChange(effect, 3);
|
|
}
|
|
|
|
public static Packet playSound(String sound) {
|
|
return environmentChange(sound, 4);
|
|
}
|
|
|
|
public static Packet environmentChange(String env, int mode) {
|
|
OutPacket p = OutPacket.create(SendOpcode.FIELD_EFFECT);
|
|
p.writeByte(mode);
|
|
p.writeString(env);
|
|
return p;
|
|
}
|
|
|
|
public static Packet environmentMove(String env, int mode) {
|
|
OutPacket p = OutPacket.create(SendOpcode.FIELD_OBSTACLE_ONOFF);
|
|
p.writeString(env);
|
|
p.writeInt(mode); // 0: stop and back to start, 1: move
|
|
return p;
|
|
}
|
|
|
|
public static Packet environmentMoveList(Set<Entry<String, Integer>> envList) {
|
|
OutPacket p = OutPacket.create(SendOpcode.FIELD_OBSTACLE_ONOFF_LIST);
|
|
p.writeInt(envList.size());
|
|
|
|
for (Entry<String, Integer> envMove : envList) {
|
|
p.writeString(envMove.getKey());
|
|
p.writeInt(envMove.getValue());
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet environmentMoveReset() {
|
|
return OutPacket.create(SendOpcode.FIELD_OBSTACLE_ALL_RESET);
|
|
}
|
|
|
|
public static Packet startMapEffect(String msg, int itemId, boolean active) {
|
|
OutPacket p = OutPacket.create(SendOpcode.BLOW_WEATHER);
|
|
p.writeBool(!active);
|
|
p.writeInt(itemId);
|
|
if (active) {
|
|
p.writeString(msg);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeMapEffect() {
|
|
OutPacket p = OutPacket.create(SendOpcode.BLOW_WEATHER);
|
|
p.writeByte(0);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet mapEffect(String path) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FIELD_EFFECT);
|
|
p.writeByte(3);
|
|
p.writeString(path);
|
|
return p;
|
|
}
|
|
|
|
public static Packet mapSound(String path) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FIELD_EFFECT);
|
|
p.writeByte(4);
|
|
p.writeString(path);
|
|
return p;
|
|
}
|
|
|
|
public static Packet skillEffect(Character from, int skillId, int level, byte flags, int speed, byte direction) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SKILL_EFFECT);
|
|
p.writeInt(from.getId());
|
|
p.writeInt(skillId);
|
|
p.writeByte(level);
|
|
p.writeByte(flags);
|
|
p.writeByte(speed);
|
|
p.writeByte(direction); //Mmmk
|
|
return p;
|
|
}
|
|
|
|
public static Packet skillCancel(Character from, int skillId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CANCEL_SKILL_EFFECT);
|
|
p.writeInt(from.getId());
|
|
p.writeInt(skillId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet catchMonster(int mobOid, byte success) { // updated packet structure found thanks to Rien dev team
|
|
final OutPacket p = OutPacket.create(SendOpcode.CATCH_MONSTER);
|
|
p.writeInt(mobOid);
|
|
p.writeByte(success);
|
|
return p;
|
|
}
|
|
|
|
public static Packet catchMonster(int mobOid, int itemid, byte success) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CATCH_MONSTER_WITH_ITEM);
|
|
p.writeInt(mobOid);
|
|
p.writeInt(itemid);
|
|
p.writeByte(success);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a player hint.
|
|
*
|
|
* @param hint The hint it's going to send.
|
|
* @param width How tall the box is going to be.
|
|
* @param height How long the box is going to be.
|
|
* @return The player hint packet.
|
|
*/
|
|
public static Packet sendHint(String hint, int width, int height) {
|
|
if (width < 1) {
|
|
width = hint.length() * 10;
|
|
if (width < 40) {
|
|
width = 40;
|
|
}
|
|
}
|
|
if (height < 5) {
|
|
height = 5;
|
|
}
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_HINT);
|
|
p.writeString(hint);
|
|
p.writeShort(width);
|
|
p.writeShort(height);
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet messengerInvite(String from, int messengerid) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MESSENGER);
|
|
p.writeByte(0x03);
|
|
p.writeString(from);
|
|
p.writeByte(0);
|
|
p.writeInt(messengerid);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
public static Packet sendSpouseChat(Character partner, String msg) {
|
|
OutPacket p = OutPacket.create(SendOpcode);
|
|
SPOUSE_CHAT);
|
|
p.writeString(partner.getName());
|
|
p.writeString(msg);
|
|
return p;
|
|
}
|
|
*/
|
|
|
|
public static Packet OnCoupleMessage(String fiance, String text, boolean spouse) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPOUSE_CHAT);
|
|
p.writeByte(spouse ? 5 : 4); // v2 = CInPacket::Decode1(a1) - 4;
|
|
if (spouse) { // if ( v2 ) {
|
|
p.writeString(fiance);
|
|
}
|
|
p.writeByte(spouse ? 5 : 1);
|
|
p.writeString(text);
|
|
return p;
|
|
}
|
|
|
|
public static Packet addMessengerPlayer(String from, Character chr, int position, int channel) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MESSENGER);
|
|
p.writeByte(0x00);
|
|
p.writeByte(position);
|
|
addCharLook(p, chr, true);
|
|
p.writeString(from);
|
|
p.writeByte(channel);
|
|
p.writeByte(0x00);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeMessengerPlayer(int position) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MESSENGER);
|
|
p.writeByte(0x02);
|
|
p.writeByte(position);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateMessengerPlayer(String from, Character chr, int position, int channel) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MESSENGER);
|
|
p.writeByte(0x07);
|
|
p.writeByte(position);
|
|
addCharLook(p, chr, true);
|
|
p.writeString(from);
|
|
p.writeByte(channel);
|
|
p.writeByte(0x00);
|
|
return p;
|
|
}
|
|
|
|
public static Packet joinMessenger(int position) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MESSENGER);
|
|
p.writeByte(0x01);
|
|
p.writeByte(position);
|
|
return p;
|
|
}
|
|
|
|
public static Packet messengerChat(String text) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MESSENGER);
|
|
p.writeByte(0x06);
|
|
p.writeString(text);
|
|
return p;
|
|
}
|
|
|
|
public static Packet messengerNote(String text, int mode, int mode2) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MESSENGER);
|
|
p.writeByte(mode);
|
|
p.writeString(text);
|
|
p.writeByte(mode2);
|
|
return p;
|
|
}
|
|
|
|
private static void addPetInfo(final OutPacket p, Pet pet, boolean showpet) {
|
|
p.writeByte(1);
|
|
if (showpet) {
|
|
p.writeByte(0);
|
|
}
|
|
|
|
p.writeInt(pet.getItemId());
|
|
p.writeString(pet.getName());
|
|
p.writeLong(pet.getUniqueId());
|
|
p.writePos(pet.getPos());
|
|
p.writeByte(pet.getStance());
|
|
p.writeInt(pet.getFh());
|
|
}
|
|
|
|
public static Packet showPet(Character chr, Pet pet, boolean remove, boolean hunger) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_PET);
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(chr.getPetIndex(pet));
|
|
if (remove) {
|
|
p.writeByte(0);
|
|
p.writeBool(hunger);
|
|
} else {
|
|
addPetInfo(p, pet, true);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet movePet(int cid, int pid, byte slot, List<LifeMovementFragment> moves) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MOVE_PET);
|
|
p.writeInt(cid);
|
|
p.writeByte(slot);
|
|
p.writeInt(pid);
|
|
serializeMovementList(p, moves);
|
|
return p;
|
|
}
|
|
|
|
public static Packet petChat(int cid, byte index, int act, String text) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PET_CHAT);
|
|
p.writeInt(cid);
|
|
p.writeByte(index);
|
|
p.writeByte(0);
|
|
p.writeByte(act);
|
|
p.writeString(text);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet petFoodResponse(int cid, byte index, boolean success, boolean balloonType) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PET_COMMAND);
|
|
p.writeInt(cid);
|
|
p.writeByte(index);
|
|
p.writeByte(1);
|
|
p.writeBool(success);
|
|
p.writeBool(balloonType);
|
|
return p;
|
|
}
|
|
|
|
public static Packet commandResponse(int cid, byte index, boolean talk, int animation, boolean balloonType) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PET_COMMAND);
|
|
p.writeInt(cid);
|
|
p.writeByte(index);
|
|
p.writeByte(0);
|
|
p.writeByte(animation);
|
|
p.writeBool(!talk);
|
|
p.writeBool(balloonType);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showOwnPetLevelUp(byte index) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(4);
|
|
p.writeByte(0);
|
|
p.writeByte(index); // Pet Index
|
|
return p;
|
|
}
|
|
|
|
public static Packet showPetLevelUp(Character chr, byte index) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(4);
|
|
p.writeByte(0);
|
|
p.writeByte(index);
|
|
return p;
|
|
}
|
|
|
|
public static Packet changePetName(Character chr, String newname, int slot) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PET_NAMECHANGE);
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(0);
|
|
p.writeString(newname);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet loadExceptionList(final int cid, final int petId, final byte petIdx, final List<Integer> data) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PET_EXCEPTION_LIST);
|
|
p.writeInt(cid);
|
|
p.writeByte(petIdx);
|
|
p.writeLong(petId);
|
|
p.writeByte(data.size());
|
|
for (final Integer ids : data) {
|
|
p.writeInt(ids);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet petStatUpdate(Character chr) {
|
|
// this actually does nothing... packet structure and stats needs to be uncovered
|
|
|
|
final OutPacket p = OutPacket.create(SendOpcode.STAT_CHANGED);
|
|
int mask = 0;
|
|
mask |= Stat.PET.getValue();
|
|
p.writeByte(0);
|
|
p.writeInt(mask);
|
|
Pet[] pets = chr.getPets();
|
|
for (int i = 0; i < 3; i++) {
|
|
if (pets[i] != null) {
|
|
p.writeLong(pets[i].getUniqueId());
|
|
} else {
|
|
p.writeLong(0);
|
|
}
|
|
}
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showForcedEquip(int team) {
|
|
OutPacket p = OutPacket.create(SendOpcode.FORCED_MAP_EQUIP);
|
|
if (team > -1) {
|
|
p.writeByte(team); // 00 = red, 01 = blue
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet summonSkill(int cid, int summonSkillId, int newStance) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SUMMON_SKILL);
|
|
p.writeInt(cid);
|
|
p.writeInt(summonSkillId);
|
|
p.writeByte(newStance);
|
|
return p;
|
|
}
|
|
|
|
public static Packet skillCooldown(int sid, int time) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.COOLDOWN);
|
|
p.writeInt(sid);
|
|
p.writeShort(time);//Int in v97
|
|
return p;
|
|
}
|
|
|
|
public static Packet skillBookResult(Character chr, int skillid, int maxlevel, boolean canuse, boolean success) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SKILL_LEARN_ITEM_RESULT);
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(1);
|
|
p.writeInt(skillid);
|
|
p.writeInt(maxlevel);
|
|
p.writeByte(canuse ? 1 : 0);
|
|
p.writeByte(success ? 1 : 0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMacros(SkillMacro[] macros) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MACRO_SYS_DATA_INIT);
|
|
int count = 0;
|
|
for (int i = 0; i < 5; i++) {
|
|
if (macros[i] != null) {
|
|
count++;
|
|
}
|
|
}
|
|
p.writeByte(count);
|
|
for (int i = 0; i < 5; i++) {
|
|
SkillMacro macro = macros[i];
|
|
if (macro != null) {
|
|
p.writeString(macro.getName());
|
|
p.writeByte(macro.getShout());
|
|
p.writeInt(macro.getSkill1());
|
|
p.writeInt(macro.getSkill2());
|
|
p.writeInt(macro.getSkill3());
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet showAllCharacterInfo(int worldid, List<Character> chars, boolean usePic) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.VIEW_ALL_CHAR);
|
|
p.writeByte(0);
|
|
p.writeByte(worldid);
|
|
p.writeByte(chars.size());
|
|
for (Character chr : chars) {
|
|
addCharEntry(p, chr, true);
|
|
}
|
|
p.writeByte(usePic ? 1 : 2);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateMount(int charid, Mount mount, boolean levelup) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SET_TAMING_MOB_INFO);
|
|
p.writeInt(charid);
|
|
p.writeInt(mount.getLevel());
|
|
p.writeInt(mount.getExp());
|
|
p.writeInt(mount.getTiredness());
|
|
p.writeByte(levelup ? (byte) 1 : (byte) 0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet crogBoatPacket(boolean type) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CONTI_MOVE);
|
|
p.writeByte(10);
|
|
p.writeByte(type ? 4 : 5);
|
|
return p;
|
|
}
|
|
|
|
public static Packet boatPacket(boolean type) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CONTI_STATE);
|
|
p.writeByte(type ? 1 : 2);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGame(Client c, MapleMiniGame minigame, boolean owner, int piece) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.ROOM.getCode());
|
|
p.writeByte(1);
|
|
p.writeByte(0);
|
|
p.writeBool(!owner);
|
|
p.writeByte(0);
|
|
addCharLook(p, minigame.getOwner(), false);
|
|
p.writeString(minigame.getOwner().getName());
|
|
if (minigame.getVisitor() != null) {
|
|
Character visitor = minigame.getVisitor();
|
|
p.writeByte(1);
|
|
addCharLook(p, visitor, false);
|
|
p.writeString(visitor.getName());
|
|
}
|
|
p.writeByte(0xFF);
|
|
p.writeByte(0);
|
|
p.writeInt(1);
|
|
p.writeInt(minigame.getOwner().getMiniGamePoints(MiniGameResult.WIN, true));
|
|
p.writeInt(minigame.getOwner().getMiniGamePoints(MiniGameResult.TIE, true));
|
|
p.writeInt(minigame.getOwner().getMiniGamePoints(MiniGameResult.LOSS, true));
|
|
p.writeInt(minigame.getOwnerScore());
|
|
if (minigame.getVisitor() != null) {
|
|
Character visitor = minigame.getVisitor();
|
|
p.writeByte(1);
|
|
p.writeInt(1);
|
|
p.writeInt(visitor.getMiniGamePoints(MiniGameResult.WIN, true));
|
|
p.writeInt(visitor.getMiniGamePoints(MiniGameResult.TIE, true));
|
|
p.writeInt(visitor.getMiniGamePoints(MiniGameResult.LOSS, true));
|
|
p.writeInt(minigame.getVisitorScore());
|
|
}
|
|
p.writeByte(0xFF);
|
|
p.writeString(minigame.getDescription());
|
|
p.writeByte(piece);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameReady(MapleMiniGame game) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.READY.getCode());
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameUnReady(MapleMiniGame game) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.UN_READY.getCode());
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameStart(MapleMiniGame game, int loser) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.START.getCode());
|
|
p.writeByte(loser);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameSkipOwner(MapleMiniGame game) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.SKIP.getCode());
|
|
p.writeByte(0x01);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameRequestTie(MapleMiniGame game) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.REQUEST_TIE.getCode());
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameDenyTie(MapleMiniGame game) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.ANSWER_TIE.getCode());
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* 1 = Room already closed 2 = Can't enter due full cappacity 3 = Other requests at this minute
|
|
* 4 = Can't do while dead 5 = Can't do while middle event 6 = This character unable to do it
|
|
* 7, 20 = Not allowed to trade anymore 9 = Can only trade on same map 10 = May not open store near portal
|
|
* 11, 14 = Can't start game here 12 = Can't open store at this channel 13 = Can't estabilish miniroom
|
|
* 15 = Stores only an the free market 16 = Lists the rooms at FM (?) 17 = You may not enter this store
|
|
* 18 = Owner undergoing store maintenance 19 = Unable to enter tournament room 21 = Not enough mesos to enter
|
|
* 22 = Incorrect password
|
|
*
|
|
* @param status
|
|
* @return
|
|
*/
|
|
public static Packet getMiniRoomError(int status) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.ROOM.getCode());
|
|
p.writeByte(0);
|
|
p.writeByte(status);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameSkipVisitor(MapleMiniGame game) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeShort(PlayerInteractionHandler.Action.SKIP.getCode());
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameMoveOmok(MapleMiniGame game, int move1, int move2, int move3) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.MOVE_OMOK.getCode());
|
|
p.writeInt(move1);
|
|
p.writeInt(move2);
|
|
p.writeByte(move3);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameNewVisitor(MapleMiniGame minigame, Character chr, int slot) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.VISIT.getCode());
|
|
p.writeByte(slot);
|
|
addCharLook(p, chr, false);
|
|
p.writeString(chr.getName());
|
|
p.writeInt(1);
|
|
p.writeInt(chr.getMiniGamePoints(MiniGameResult.WIN, true));
|
|
p.writeInt(chr.getMiniGamePoints(MiniGameResult.TIE, true));
|
|
p.writeInt(chr.getMiniGamePoints(MiniGameResult.LOSS, true));
|
|
p.writeInt(minigame.getVisitorScore());
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameRemoveVisitor() {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.EXIT.getCode());
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
private static Packet getMiniGameResult(MapleMiniGame game, int tie, int result, int forfeit) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.GET_RESULT.getCode());
|
|
|
|
int matchResultType;
|
|
if (tie == 0 && forfeit != 1) {
|
|
matchResultType = 0;
|
|
} else if (tie != 0) {
|
|
matchResultType = 1;
|
|
} else {
|
|
matchResultType = 2;
|
|
}
|
|
|
|
p.writeByte(matchResultType);
|
|
p.writeBool(result == 2); // host/visitor wins
|
|
|
|
boolean omok = game.isOmok();
|
|
if (matchResultType == 1) {
|
|
p.writeByte(0);
|
|
p.writeShort(0);
|
|
p.writeInt(game.getOwner().getMiniGamePoints(MiniGameResult.WIN, omok)); // wins
|
|
p.writeInt(game.getOwner().getMiniGamePoints(MiniGameResult.TIE, omok)); // ties
|
|
p.writeInt(game.getOwner().getMiniGamePoints(MiniGameResult.LOSS, omok)); // losses
|
|
p.writeInt(game.getOwnerScore()); // points
|
|
|
|
p.writeInt(0); // unknown
|
|
p.writeInt(game.getVisitor().getMiniGamePoints(MiniGameResult.WIN, omok)); // wins
|
|
p.writeInt(game.getVisitor().getMiniGamePoints(MiniGameResult.TIE, omok)); // ties
|
|
p.writeInt(game.getVisitor().getMiniGamePoints(MiniGameResult.LOSS, omok)); // losses
|
|
p.writeInt(game.getVisitorScore()); // points
|
|
p.writeByte(0);
|
|
} else {
|
|
p.writeInt(0);
|
|
p.writeInt(game.getOwner().getMiniGamePoints(MiniGameResult.WIN, omok)); // wins
|
|
p.writeInt(game.getOwner().getMiniGamePoints(MiniGameResult.TIE, omok)); // ties
|
|
p.writeInt(game.getOwner().getMiniGamePoints(MiniGameResult.LOSS, omok)); // losses
|
|
p.writeInt(game.getOwnerScore()); // points
|
|
p.writeInt(0);
|
|
p.writeInt(game.getVisitor().getMiniGamePoints(MiniGameResult.WIN, omok)); // wins
|
|
p.writeInt(game.getVisitor().getMiniGamePoints(MiniGameResult.TIE, omok)); // ties
|
|
p.writeInt(game.getVisitor().getMiniGamePoints(MiniGameResult.LOSS, omok)); // losses
|
|
p.writeInt(game.getVisitorScore()); // points
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMiniGameOwnerWin(MapleMiniGame game, boolean forfeit) {
|
|
return getMiniGameResult(game, 0, 1, forfeit ? 1 : 0);
|
|
}
|
|
|
|
public static Packet getMiniGameVisitorWin(MapleMiniGame game, boolean forfeit) {
|
|
return getMiniGameResult(game, 0, 2, forfeit ? 1 : 0);
|
|
}
|
|
|
|
public static Packet getMiniGameTie(MapleMiniGame game) {
|
|
return getMiniGameResult(game, 1, 3, 0);
|
|
}
|
|
|
|
public static Packet getMiniGameClose(boolean visitor, int type) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.EXIT.getCode());
|
|
p.writeBool(visitor);
|
|
p.writeByte(type); /* 2 : CRASH 3 : The room has been closed 4 : You have left the room 5 : You have been expelled */
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMatchCard(Client c, MapleMiniGame minigame, boolean owner, int piece) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.ROOM.getCode());
|
|
p.writeByte(2);
|
|
p.writeByte(2);
|
|
p.writeBool(!owner);
|
|
p.writeByte(0);
|
|
addCharLook(p, minigame.getOwner(), false);
|
|
p.writeString(minigame.getOwner().getName());
|
|
if (minigame.getVisitor() != null) {
|
|
Character visitor = minigame.getVisitor();
|
|
p.writeByte(1);
|
|
addCharLook(p, visitor, false);
|
|
p.writeString(visitor.getName());
|
|
}
|
|
p.writeByte(0xFF);
|
|
p.writeByte(0);
|
|
p.writeInt(2);
|
|
p.writeInt(minigame.getOwner().getMiniGamePoints(MiniGameResult.WIN, false));
|
|
p.writeInt(minigame.getOwner().getMiniGamePoints(MiniGameResult.TIE, false));
|
|
p.writeInt(minigame.getOwner().getMiniGamePoints(MiniGameResult.LOSS, false));
|
|
|
|
//set vs
|
|
p.writeInt(minigame.getOwnerScore());
|
|
if (minigame.getVisitor() != null) {
|
|
Character visitor = minigame.getVisitor();
|
|
p.writeByte(1);
|
|
p.writeInt(2);
|
|
p.writeInt(visitor.getMiniGamePoints(MiniGameResult.WIN, false));
|
|
p.writeInt(visitor.getMiniGamePoints(MiniGameResult.TIE, false));
|
|
p.writeInt(visitor.getMiniGamePoints(MiniGameResult.LOSS, false));
|
|
p.writeInt(minigame.getVisitorScore());
|
|
}
|
|
p.writeByte(0xFF);
|
|
p.writeString(minigame.getDescription());
|
|
p.writeByte(piece);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMatchCardStart(MapleMiniGame game, int loser) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.START.getCode());
|
|
p.writeByte(loser);
|
|
|
|
int last;
|
|
if (game.getMatchesToWin() > 10) {
|
|
last = 30;
|
|
} else if (game.getMatchesToWin() > 6) {
|
|
last = 20;
|
|
} else {
|
|
last = 12;
|
|
}
|
|
|
|
p.writeByte(last);
|
|
for (int i = 0; i < last; i++) {
|
|
p.writeInt(game.getCardId(i));
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMatchCardNewVisitor(MapleMiniGame minigame, Character chr, int slot) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.VISIT.getCode());
|
|
p.writeByte(slot);
|
|
addCharLook(p, chr, false);
|
|
p.writeString(chr.getName());
|
|
p.writeInt(1);
|
|
p.writeInt(chr.getMiniGamePoints(MiniGameResult.WIN, false));
|
|
p.writeInt(chr.getMiniGamePoints(MiniGameResult.TIE, false));
|
|
p.writeInt(chr.getMiniGamePoints(MiniGameResult.LOSS, false));
|
|
p.writeInt(minigame.getVisitorScore());
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMatchCardSelect(MapleMiniGame game, int turn, int slot, int firstslot, int type) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.SELECT_CARD.getCode());
|
|
p.writeByte(turn);
|
|
if (turn == 1) {
|
|
p.writeByte(slot);
|
|
} else if (turn == 0) {
|
|
p.writeByte(slot);
|
|
p.writeByte(firstslot);
|
|
p.writeByte(type);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
// RPS_GAME packets thanks to Arnah (Vertisy)
|
|
public static Packet openRPSNPC() {
|
|
OutPacket p = OutPacket.create(SendOpcode.RPS_GAME);
|
|
p.writeByte(8);// open npc
|
|
p.writeInt(9000019);
|
|
return p;
|
|
}
|
|
|
|
public static Packet rpsMesoError(int mesos) {
|
|
OutPacket p = OutPacket.create(SendOpcode.RPS_GAME);
|
|
p.writeByte(0x06);
|
|
if (mesos != -1) {
|
|
p.writeInt(mesos);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet rpsSelection(byte selection, byte answer) {
|
|
OutPacket p = OutPacket.create(SendOpcode.RPS_GAME);
|
|
p.writeByte(0x0B);// 11l
|
|
p.writeByte(selection);
|
|
p.writeByte(answer);
|
|
return p;
|
|
}
|
|
|
|
public static Packet rpsMode(byte mode) {
|
|
OutPacket p = OutPacket.create(SendOpcode.RPS_GAME);
|
|
p.writeByte(mode);
|
|
return p;
|
|
}
|
|
|
|
public static Packet fredrickMessage(byte operation) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FREDRICK_MESSAGE);
|
|
p.writeByte(operation);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getFredrick(byte op) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FREDRICK);
|
|
p.writeByte(op);
|
|
|
|
switch (op) {
|
|
case 0x24:
|
|
p.skip(8);
|
|
break;
|
|
default:
|
|
p.writeByte(0);
|
|
break;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet getFredrick(Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FREDRICK);
|
|
p.writeByte(0x23);
|
|
p.writeInt(9030000); // Fredrick
|
|
p.writeInt(32272); //id
|
|
p.skip(5);
|
|
p.writeInt(chr.getMerchantNetMeso());
|
|
p.writeByte(0);
|
|
try {
|
|
List<Pair<Item, InventoryType>> items = ItemFactory.MERCHANT.loadItems(chr.getId(), false);
|
|
p.writeByte(items.size());
|
|
|
|
for (Pair<Item, InventoryType> item : items) {
|
|
addItemInfo(p, item.getLeft(), true);
|
|
}
|
|
} catch (SQLException e) {
|
|
e.printStackTrace();
|
|
}
|
|
p.skip(3);
|
|
return p;
|
|
}
|
|
|
|
public static Packet addOmokBox(Character chr, int amount, int type) {
|
|
OutPacket p = OutPacket.create(SendOpcode.UPDATE_CHAR_BOX);
|
|
p.writeInt(chr.getId());
|
|
addAnnounceBox(p, chr.getMiniGame(), amount, type);
|
|
return p;
|
|
}
|
|
|
|
public static Packet addMatchCardBox(Character chr, int amount, int type) {
|
|
OutPacket p = OutPacket.create(SendOpcode.UPDATE_CHAR_BOX);
|
|
p.writeInt(chr.getId());
|
|
addAnnounceBox(p, chr.getMiniGame(), amount, type);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeMinigameBox(Character chr) {
|
|
OutPacket p = OutPacket.create(SendOpcode.UPDATE_CHAR_BOX);
|
|
p.writeInt(chr.getId());
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getPlayerShopChat(Character chr, String chat, byte slot) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.CHAT.getCode());
|
|
p.writeByte(PlayerInteractionHandler.Action.CHAT_THING.getCode());
|
|
p.writeByte(slot);
|
|
p.writeString(chr.getName() + " : " + chat);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getTradeChat(Character chr, String chat, boolean owner) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.CHAT.getCode());
|
|
p.writeByte(PlayerInteractionHandler.Action.CHAT_THING.getCode());
|
|
p.writeByte(owner ? 0 : 1);
|
|
p.writeString(chr.getName() + " : " + chat);
|
|
return p;
|
|
}
|
|
|
|
public static Packet hiredMerchantBox() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.ENTRUSTED_SHOP_CHECK_RESULT); // header.
|
|
p.writeByte(0x07);
|
|
return p;
|
|
}
|
|
|
|
// 0: Success
|
|
// 1: The room is already closed.
|
|
// 2: You can't enter the room due to full capacity.
|
|
// 3: Other requests are being fulfilled this minute.
|
|
// 4: You can't do it while you're dead.
|
|
// 7: You are not allowed to trade other items at this point.
|
|
// 17: You may not enter this store.
|
|
// 18: The owner of the store is currently undergoing store maintenance. Please try again in a bit.
|
|
// 23: This can only be used inside the Free Market.
|
|
// default: This character is unable to do it.
|
|
public static Packet getOwlMessage(int msg) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOP_LINK_RESULT);
|
|
p.writeByte(msg); // depending on the byte sent, a different message is sent.
|
|
return p;
|
|
}
|
|
|
|
public static Packet owlOfMinerva(Client c, int itemId, List<Pair<MaplePlayerShopItem, AbstractMapleMapObject>> hmsAvailable) {
|
|
byte itemType = ItemConstants.getInventoryType(itemId).getType();
|
|
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOP_SCANNER_RESULT);
|
|
p.writeByte(6);
|
|
p.writeInt(0);
|
|
p.writeInt(itemId);
|
|
p.writeInt(hmsAvailable.size());
|
|
for (Pair<MaplePlayerShopItem, AbstractMapleMapObject> hme : hmsAvailable) {
|
|
MaplePlayerShopItem item = hme.getLeft();
|
|
AbstractMapleMapObject mo = hme.getRight();
|
|
|
|
if (mo instanceof MaplePlayerShop ps) {
|
|
Character owner = ps.getOwner();
|
|
|
|
p.writeString(owner.getName());
|
|
p.writeInt(owner.getMapId());
|
|
p.writeString(ps.getDescription());
|
|
p.writeInt(item.getBundles());
|
|
p.writeInt(item.getItem().getQuantity());
|
|
p.writeInt(item.getPrice());
|
|
p.writeInt(owner.getId());
|
|
p.writeByte(owner.getClient().getChannel() - 1);
|
|
} else {
|
|
MapleHiredMerchant hm = (MapleHiredMerchant) mo;
|
|
|
|
p.writeString(hm.getOwner());
|
|
p.writeInt(hm.getMapId());
|
|
p.writeString(hm.getDescription());
|
|
p.writeInt(item.getBundles());
|
|
p.writeInt(item.getItem().getQuantity());
|
|
p.writeInt(item.getPrice());
|
|
p.writeInt(hm.getOwnerId());
|
|
p.writeByte(hm.getChannel() - 1);
|
|
}
|
|
|
|
p.writeByte(itemType);
|
|
if (itemType == InventoryType.EQUIP.getType()) {
|
|
addItemInfo(p, item.getItem(), true);
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet getOwlOpen(List<Integer> owlLeaderboards) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOP_SCANNER_RESULT);
|
|
p.writeByte(7);
|
|
p.writeByte(owlLeaderboards.size());
|
|
for (Integer i : owlLeaderboards) {
|
|
p.writeInt(i);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet retrieveFirstMessage() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.ENTRUSTED_SHOP_CHECK_RESULT); // header.
|
|
p.writeByte(0x09);
|
|
return p;
|
|
}
|
|
|
|
public static Packet remoteChannelChange(byte ch) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.ENTRUSTED_SHOP_CHECK_RESULT); // header.
|
|
p.writeByte(0x10);
|
|
p.writeInt(0);//No idea yet
|
|
p.writeByte(ch);
|
|
return p;
|
|
}
|
|
/*
|
|
* Possible things for ENTRUSTED_SHOP_CHECK_RESULT
|
|
* 0x0E = 00 = Renaming Failed - Can't find the merchant, 01 = Renaming successful
|
|
* 0x10 = Changes channel to the store (Store is open at Channel 1, do you want to change channels?)
|
|
* 0x11 = You cannot sell any items when managing.. blabla
|
|
* 0x12 = FKING POPUP LOL
|
|
*/
|
|
|
|
public static Packet getHiredMerchant(Character chr, MapleHiredMerchant hm, boolean firstTime) {//Thanks Dustin
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.ROOM.getCode());
|
|
p.writeByte(0x05);
|
|
p.writeByte(0x04);
|
|
p.writeShort(hm.getVisitorSlotThreadsafe(chr) + 1);
|
|
p.writeInt(hm.getItemId());
|
|
p.writeString("Hired Merchant");
|
|
|
|
Character[] visitors = hm.getVisitors();
|
|
for (int i = 0; i < 3; i++) {
|
|
if (visitors[i] != null) {
|
|
p.writeByte(i + 1);
|
|
addCharLook(p, visitors[i], false);
|
|
p.writeString(visitors[i].getName());
|
|
}
|
|
}
|
|
p.writeByte(-1);
|
|
if (hm.isOwner(chr)) {
|
|
List<Pair<String, Byte>> msgList = hm.getMessages();
|
|
|
|
p.writeShort(msgList.size());
|
|
for (Pair<String, Byte> stringBytePair : msgList) {
|
|
p.writeString(stringBytePair.getLeft());
|
|
p.writeByte(stringBytePair.getRight());
|
|
}
|
|
} else {
|
|
p.writeShort(0);
|
|
}
|
|
p.writeString(hm.getOwner());
|
|
if (hm.isOwner(chr)) {
|
|
p.writeShort(0);
|
|
p.writeShort(hm.getTimeOpen());
|
|
p.writeByte(firstTime ? 1 : 0);
|
|
List<MapleHiredMerchant.SoldItem> sold = hm.getSold();
|
|
p.writeByte(sold.size());
|
|
for (MapleHiredMerchant.SoldItem s : sold) {
|
|
p.writeInt(s.getItemId());
|
|
p.writeShort(s.getQuantity());
|
|
p.writeInt(s.getMesos());
|
|
p.writeString(s.getBuyer());
|
|
}
|
|
p.writeInt(chr.getMerchantMeso());//:D?
|
|
}
|
|
p.writeString(hm.getDescription());
|
|
p.writeByte(0x10); //TODO SLOTS, which is 16 for most stores...slotMax
|
|
p.writeInt(hm.isOwner(chr) ? chr.getMerchantMeso() : chr.getMeso());
|
|
p.writeByte(hm.getItems().size());
|
|
if (hm.getItems().isEmpty()) {
|
|
p.writeByte(0);//Hmm??
|
|
} else {
|
|
for (MaplePlayerShopItem item : hm.getItems()) {
|
|
p.writeShort(item.getBundles());
|
|
p.writeShort(item.getItem().getQuantity());
|
|
p.writeInt(item.getPrice());
|
|
addItemInfo(p, item.getItem(), true);
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateHiredMerchant(MapleHiredMerchant hm, Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.UPDATE_MERCHANT.getCode());
|
|
p.writeInt(hm.isOwner(chr) ? chr.getMerchantMeso() : chr.getMeso());
|
|
p.writeByte(hm.getItems().size());
|
|
for (MaplePlayerShopItem item : hm.getItems()) {
|
|
p.writeShort(item.getBundles());
|
|
p.writeShort(item.getItem().getQuantity());
|
|
p.writeInt(item.getPrice());
|
|
addItemInfo(p, item.getItem(), true);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet hiredMerchantChat(String message, byte slot) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.CHAT.getCode());
|
|
p.writeByte(PlayerInteractionHandler.Action.CHAT_THING.getCode());
|
|
p.writeByte(slot);
|
|
p.writeString(message);
|
|
return p;
|
|
}
|
|
|
|
public static Packet hiredMerchantVisitorLeave(int slot) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.EXIT.getCode());
|
|
if (slot != 0) {
|
|
p.writeByte(slot);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet hiredMerchantOwnerLeave() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.REAL_CLOSE_MERCHANT.getCode());
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet hiredMerchantOwnerMaintenanceLeave() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.REAL_CLOSE_MERCHANT.getCode());
|
|
p.writeByte(5);
|
|
return p;
|
|
}
|
|
|
|
public static Packet hiredMerchantMaintenanceMessage() {
|
|
OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.ROOM.getCode());
|
|
p.writeByte(0x00);
|
|
p.writeByte(0x12);
|
|
return p;
|
|
}
|
|
|
|
public static Packet leaveHiredMerchant(int slot, int status2) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.EXIT.getCode());
|
|
p.writeByte(slot);
|
|
p.writeByte(status2);
|
|
return p;
|
|
}
|
|
|
|
public static Packet hiredMerchantVisitorAdd(Character chr, int slot) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(PlayerInteractionHandler.Action.VISIT.getCode());
|
|
p.writeByte(slot);
|
|
addCharLook(p, chr, false);
|
|
p.writeString(chr.getName());
|
|
return p;
|
|
}
|
|
|
|
public static Packet spawnHiredMerchantBox(MapleHiredMerchant hm) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SPAWN_HIRED_MERCHANT);
|
|
p.writeInt(hm.getOwnerId());
|
|
p.writeInt(hm.getItemId());
|
|
p.writeShort((short) hm.getPosition().getX());
|
|
p.writeShort((short) hm.getPosition().getY());
|
|
p.writeShort(0);
|
|
p.writeString(hm.getOwner());
|
|
p.writeByte(0x05);
|
|
p.writeInt(hm.getObjectId());
|
|
p.writeString(hm.getDescription());
|
|
p.writeByte(hm.getItemId() % 100);
|
|
p.writeBytes(new byte[]{1, 4});
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeHiredMerchantBox(int id) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DESTROY_HIRED_MERCHANT);
|
|
p.writeInt(id);
|
|
return p;
|
|
}
|
|
|
|
public static Packet spawnPlayerNPC(MaplePlayerNPC npc) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SPAWN_NPC_REQUEST_CONTROLLER);
|
|
p.writeByte(1);
|
|
p.writeInt(npc.getObjectId());
|
|
p.writeInt(npc.getScriptId());
|
|
p.writeShort(npc.getPosition().x);
|
|
p.writeShort(npc.getCY());
|
|
p.writeByte(npc.getDirection());
|
|
p.writeShort(npc.getFH());
|
|
p.writeShort(npc.getRX0());
|
|
p.writeShort(npc.getRX1());
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getPlayerNPC(MaplePlayerNPC npc) { // thanks to Arnah
|
|
final OutPacket p = OutPacket.create(SendOpcode.IMITATED_NPC_DATA);
|
|
p.writeByte(0x01);
|
|
p.writeInt(npc.getScriptId());
|
|
p.writeString(npc.getName());
|
|
p.writeByte(npc.getGender());
|
|
p.writeByte(npc.getSkin());
|
|
p.writeInt(npc.getFace());
|
|
p.writeByte(0);
|
|
p.writeInt(npc.getHair());
|
|
Map<Short, Integer> equip = npc.getEquips();
|
|
Map<Short, Integer> myEquip = new LinkedHashMap<>();
|
|
Map<Short, Integer> maskedEquip = new LinkedHashMap<>();
|
|
for (short position : equip.keySet()) {
|
|
short pos = (byte) (position * -1);
|
|
if (pos < 100 && myEquip.get(pos) == null) {
|
|
myEquip.put(pos, equip.get(position));
|
|
} else if ((pos > 100 && pos != 111) || pos == -128) { // don't ask. o.o
|
|
pos -= 100;
|
|
if (myEquip.get(pos) != null) {
|
|
maskedEquip.put(pos, myEquip.get(pos));
|
|
}
|
|
myEquip.put(pos, equip.get(position));
|
|
} else if (myEquip.get(pos) != null) {
|
|
maskedEquip.put(pos, equip.get(position));
|
|
}
|
|
}
|
|
for (Entry<Short, Integer> entry : myEquip.entrySet()) {
|
|
p.writeByte(entry.getKey());
|
|
p.writeInt(entry.getValue());
|
|
}
|
|
p.writeByte(0xFF);
|
|
for (Entry<Short, Integer> entry : maskedEquip.entrySet()) {
|
|
p.writeByte(entry.getKey());
|
|
p.writeInt(entry.getValue());
|
|
}
|
|
p.writeByte(0xFF);
|
|
Integer cWeapon = equip.get((byte) -111);
|
|
if (cWeapon != null) {
|
|
p.writeInt(cWeapon);
|
|
} else {
|
|
p.writeInt(0);
|
|
}
|
|
for (int i = 0; i < 3; i++) {
|
|
p.writeInt(0);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet removePlayerNPC(int oid) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.IMITATED_NPC_DATA);
|
|
p.writeByte(0x00);
|
|
p.writeInt(oid);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendYellowTip(String tip) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SET_WEEK_EVENT_MESSAGE);
|
|
p.writeByte(0xFF);
|
|
p.writeString(tip);
|
|
p.writeShort(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet givePirateBuff(List<Pair<BuffStat, Integer>> statups, int buffid, int duration) {
|
|
OutPacket p = OutPacket.create(SendOpcode.GIVE_BUFF);
|
|
boolean infusion = buffid == Buccaneer.SPEED_INFUSION || buffid == ThunderBreaker.SPEED_INFUSION || buffid == Corsair.SPEED_INFUSION;
|
|
writeLongMask(p, statups);
|
|
p.writeShort(0);
|
|
for (Pair<BuffStat, Integer> stat : statups) {
|
|
p.writeInt(stat.getRight().shortValue());
|
|
p.writeInt(buffid);
|
|
p.skip(infusion ? 10 : 5);
|
|
p.writeShort(duration);
|
|
}
|
|
p.skip(3);
|
|
return p;
|
|
}
|
|
|
|
public static Packet giveForeignPirateBuff(int cid, int buffid, int time, List<Pair<BuffStat, Integer>> statups) {
|
|
OutPacket p = OutPacket.create(SendOpcode.GIVE_FOREIGN_BUFF);
|
|
boolean infusion = buffid == Buccaneer.SPEED_INFUSION || buffid == ThunderBreaker.SPEED_INFUSION || buffid == Corsair.SPEED_INFUSION;
|
|
p.writeInt(cid);
|
|
writeLongMask(p, statups);
|
|
p.writeShort(0);
|
|
for (Pair<BuffStat, Integer> statup : statups) {
|
|
p.writeInt(statup.getRight().shortValue());
|
|
p.writeInt(buffid);
|
|
p.skip(infusion ? 10 : 5);
|
|
p.writeShort(time);
|
|
}
|
|
p.writeShort(0);
|
|
p.writeByte(2);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendMTS(List<MTSItemInfo> items, int tab, int type, int page, int pages) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION);
|
|
p.writeByte(0x15); //operation
|
|
p.writeInt(pages * 16); //testing, change to 10 if fails
|
|
p.writeInt(items.size()); //number of items
|
|
p.writeInt(tab);
|
|
p.writeInt(type);
|
|
p.writeInt(page);
|
|
p.writeByte(1);
|
|
p.writeByte(1);
|
|
for (MTSItemInfo item : items) {
|
|
addItemInfo(p, item.getItem(), true);
|
|
p.writeInt(item.getID()); //id
|
|
p.writeInt(item.getTaxes()); //this + below = price
|
|
p.writeInt(item.getPrice()); //price
|
|
p.writeInt(0);
|
|
p.writeLong(getTime(item.getEndingDate()));
|
|
p.writeString(item.getSeller()); //account name (what was nexon thinking?)
|
|
p.writeString(item.getSeller()); //char name
|
|
for (int j = 0; j < 28; j++) {
|
|
p.writeByte(0);
|
|
}
|
|
}
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet noteSendMsg() {
|
|
OutPacket p = OutPacket.create(SendOpcode.MEMO_RESULT);
|
|
p.writeByte(4);
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* 0 = Player online, use whisper
|
|
* 1 = Check player's name
|
|
* 2 = Receiver inbox full
|
|
*/
|
|
public static Packet noteError(byte error) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MEMO_RESULT);
|
|
p.writeByte(5);
|
|
p.writeByte(error);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showNotes(ResultSet notes, int count) throws SQLException {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MEMO_RESULT);
|
|
p.writeByte(3);
|
|
p.writeByte(count);
|
|
for (int i = 0; i < count; i++) {
|
|
p.writeInt(notes.getInt("id"));
|
|
p.writeString(notes.getString("from") + " ");//Stupid nexon forgot space lol
|
|
p.writeString(notes.getString("message"));
|
|
p.writeLong(getTime(notes.getLong("timestamp")));
|
|
p.writeByte(notes.getByte("fame"));//FAME :D
|
|
notes.next();
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet useChalkboard(Character chr, boolean close) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CHALKBOARD);
|
|
p.writeInt(chr.getId());
|
|
if (close) {
|
|
p.writeByte(0);
|
|
} else {
|
|
p.writeByte(1);
|
|
p.writeString(chr.getChalkboard());
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet trockRefreshMapList(Character chr, boolean delete, boolean vip) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MAP_TRANSFER_RESULT);
|
|
p.writeByte(delete ? 2 : 3);
|
|
if (vip) {
|
|
p.writeByte(1);
|
|
List<Integer> map = chr.getVipTrockMaps();
|
|
for (int i = 0; i < 10; i++) {
|
|
p.writeInt(map.get(i));
|
|
}
|
|
} else {
|
|
p.writeByte(0);
|
|
List<Integer> map = chr.getTrockMaps();
|
|
for (int i = 0; i < 5; i++) {
|
|
p.writeInt(map.get(i));
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/* 1: cannot find char info,
|
|
2: cannot transfer under 20,
|
|
3: cannot send banned,
|
|
4: cannot send married,
|
|
5: cannot send guild leader,
|
|
6: cannot send if account already requested transfer,
|
|
7: cannot transfer within 30days,
|
|
8: must quit family,
|
|
9: unknown error
|
|
*/
|
|
public static Packet sendWorldTransferRules(int error, Client c) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_CHECK_TRANSFER_WORLD_POSSIBLE_RESULT);
|
|
p.writeInt(0); //ignored
|
|
p.writeByte(error);
|
|
p.writeInt(0);
|
|
p.writeBool(error == 0); //0 = ?, otherwise list servers
|
|
if (error == 0) {
|
|
List<World> worlds = Server.getInstance().getWorlds();
|
|
p.writeInt(worlds.size());
|
|
for (World world : worlds) {
|
|
p.writeString(GameConstants.WORLD_NAMES[world.getId()]);
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet showWorldTransferSuccess(Item item, int accountId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
p.writeByte(0xA0);
|
|
addCashItemInformation(p, item, accountId);
|
|
return p;
|
|
}
|
|
|
|
/* 0: no error, send rules
|
|
1: name change already submitted
|
|
2: name change within a month
|
|
3: recently banned
|
|
4: unknown error
|
|
*/
|
|
public static Packet sendNameTransferRules(int error) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_CHECK_NAME_CHANGE_POSSIBLE_RESULT);
|
|
p.writeInt(0);
|
|
p.writeByte(error);
|
|
p.writeInt(0);
|
|
|
|
return p;
|
|
}
|
|
|
|
/* 0: Name available
|
|
* >0: Name is in use
|
|
* <0: Unknown error
|
|
*/
|
|
|
|
public static Packet sendNameTransferCheck(String availableName, boolean canUseName) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_CHECK_NAME_CHANGE);
|
|
//Send provided name back to client to add to temporary cache of checked & accepted names
|
|
p.writeString(availableName);
|
|
p.writeBool(!canUseName);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showNameChangeSuccess(Item item, int accountId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
p.writeByte(0x9E);
|
|
addCashItemInformation(p, item, accountId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showNameChangeCancel(boolean success) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CANCEL_NAME_CHANGE_RESULT);
|
|
p.writeBool(success);
|
|
if (!success) {
|
|
p.writeByte(0);
|
|
}
|
|
//p.writeString("Custom message."); //only if ^ != 0
|
|
return p;
|
|
}
|
|
|
|
public static Packet showWorldTransferCancel(boolean success) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CANCEL_TRANSFER_WORLD_RESULT);
|
|
p.writeBool(success);
|
|
if (!success) {
|
|
p.writeByte(0);
|
|
}
|
|
//p.writeString("Custom message."); //only if ^ != 0
|
|
return p;
|
|
}
|
|
|
|
public static Packet showMTSCash(Character chr) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION2);
|
|
p.writeInt(chr.getCashShop().getCash(4));
|
|
p.writeInt(chr.getCashShop().getCash(2));
|
|
return p;
|
|
}
|
|
|
|
public static Packet MTSWantedListingOver(int nx, int items) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION);
|
|
p.writeByte(0x3D);
|
|
p.writeInt(nx);
|
|
p.writeInt(items);
|
|
return p;
|
|
}
|
|
|
|
public static Packet MTSConfirmSell() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION);
|
|
p.writeByte(0x1D);
|
|
return p;
|
|
}
|
|
|
|
public static Packet MTSConfirmBuy() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION);
|
|
p.writeByte(0x33);
|
|
return p;
|
|
}
|
|
|
|
public static Packet MTSFailBuy() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION);
|
|
p.writeByte(0x34);
|
|
p.writeByte(0x42);
|
|
return p;
|
|
}
|
|
|
|
public static Packet MTSConfirmTransfer(int quantity, int pos) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION);
|
|
p.writeByte(0x27);
|
|
p.writeInt(quantity);
|
|
p.writeInt(pos);
|
|
return p;
|
|
}
|
|
|
|
public static Packet notYetSoldInv(List<MTSItemInfo> items) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION);
|
|
p.writeByte(0x23);
|
|
p.writeInt(items.size());
|
|
if (!items.isEmpty()) {
|
|
for (MTSItemInfo item : items) {
|
|
addItemInfo(p, item.getItem(), true);
|
|
p.writeInt(item.getID()); //id
|
|
p.writeInt(item.getTaxes()); //this + below = price
|
|
p.writeInt(item.getPrice()); //price
|
|
p.writeInt(0);
|
|
p.writeLong(getTime(item.getEndingDate()));
|
|
p.writeString(item.getSeller()); //account name (what was nexon thinking?)
|
|
p.writeString(item.getSeller()); //char name
|
|
for (int i = 0; i < 28; i++) {
|
|
p.writeByte(0);
|
|
}
|
|
}
|
|
} else {
|
|
p.writeInt(0);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet transferInventory(List<MTSItemInfo> items) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION);
|
|
p.writeByte(0x21);
|
|
p.writeInt(items.size());
|
|
if (!items.isEmpty()) {
|
|
for (MTSItemInfo item : items) {
|
|
addItemInfo(p, item.getItem(), true);
|
|
p.writeInt(item.getID()); //id
|
|
p.writeInt(item.getTaxes()); //taxes
|
|
p.writeInt(item.getPrice()); //price
|
|
p.writeInt(0);
|
|
p.writeLong(getTime(item.getEndingDate()));
|
|
p.writeString(item.getSeller()); //account name (what was nexon thinking?)
|
|
p.writeString(item.getSeller()); //char name
|
|
for (int i = 0; i < 28; i++) {
|
|
p.writeByte(0);
|
|
}
|
|
}
|
|
}
|
|
p.writeByte(0xD0 + items.size());
|
|
p.writeBytes(new byte[]{-1, -1, -1, 0});
|
|
return p;
|
|
}
|
|
|
|
public static Packet showCouponRedeemedItems(int accountId, int maplePoints, int mesos, List<Item> cashItems, List<Pair<Integer, Integer>> items) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
p.writeByte(0x59);
|
|
p.writeByte((byte) cashItems.size());
|
|
for (Item item : cashItems) {
|
|
addCashItemInformation(p, item, accountId);
|
|
}
|
|
p.writeInt(maplePoints);
|
|
p.writeInt(items.size());
|
|
for (Pair<Integer, Integer> itemPair : items) {
|
|
int quantity = itemPair.getLeft();
|
|
p.writeShort((short) quantity); //quantity (0 = 1 for cash items)
|
|
p.writeShort(0x1F); //0 = ?, >=0x20 = ?, <0x20 = ? (does nothing?)
|
|
p.writeInt(itemPair.getRight());
|
|
}
|
|
p.writeInt(mesos);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showCash(Character mc) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.QUERY_CASH_RESULT);
|
|
p.writeInt(mc.getCashShop().getCash(1));
|
|
p.writeInt(mc.getCashShop().getCash(2));
|
|
p.writeInt(mc.getCashShop().getCash(4));
|
|
return p;
|
|
}
|
|
|
|
public static Packet enableCSUse(Character mc) {
|
|
return showCash(mc);
|
|
}
|
|
|
|
public static class WhisperFlag {
|
|
public static final byte LOCATION = 0x01;
|
|
public static final byte WHISPER = 0x02;
|
|
public static final byte REQUEST = 0x04;
|
|
public static final byte RESULT = 0x08;
|
|
public static final byte RECEIVE = 0x10;
|
|
public static final byte BLOCKED = 0x20;
|
|
public static final byte LOCATION_FRIEND = 0x40;
|
|
}
|
|
|
|
/**
|
|
* User for /find, buddy find and /c (chase)
|
|
* CField::OnWhisper
|
|
*
|
|
* @param target Name String from the command parameter
|
|
* @param type Location of the target
|
|
* @param fieldOrChannel If true & chr is not null, shows different channel message
|
|
* @param flag LOCATION or LOCATION_FRIEND
|
|
* @return packet structure
|
|
*/
|
|
public static Packet getFindResult(Character target, byte type, int fieldOrChannel, byte flag) {
|
|
OutPacket p = OutPacket.create(SendOpcode.WHISPER);
|
|
|
|
p.writeByte(flag | WhisperFlag.RESULT);
|
|
p.writeString(target.getName());
|
|
p.writeByte(type);
|
|
p.writeInt(fieldOrChannel);
|
|
|
|
if (type == WhisperHandler.RT_SAME_CHANNEL) {
|
|
p.writeInt(target.getPosition().x);
|
|
p.writeInt(target.getPosition().y);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet getWhisperResult(String target, boolean success) {
|
|
OutPacket p = OutPacket.create(SendOpcode.WHISPER);
|
|
p.writeByte(WhisperFlag.WHISPER | WhisperFlag.RESULT);
|
|
p.writeString(target);
|
|
p.writeBool(success);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getWhisperReceive(String sender, int channel, boolean fromAdmin, String message) {
|
|
OutPacket p = OutPacket.create(SendOpcode.WHISPER);
|
|
p.writeByte(WhisperFlag.WHISPER | WhisperFlag.RECEIVE);
|
|
p.writeString(sender);
|
|
p.writeByte(channel);
|
|
p.writeBool(fromAdmin);
|
|
p.writeString(message);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendAutoHpPot(int itemId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.AUTO_HP_POT);
|
|
p.writeInt(itemId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendAutoMpPot(int itemId) {
|
|
OutPacket p = OutPacket.create(SendOpcode.AUTO_MP_POT);
|
|
p.writeInt(itemId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showOXQuiz(int questionSet, int questionId, boolean askQuestion) {
|
|
OutPacket p = OutPacket.create(SendOpcode.OX_QUIZ);
|
|
p.writeByte(askQuestion ? 1 : 0);
|
|
p.writeByte(questionSet);
|
|
p.writeShort(questionId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateGender(Character chr) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SET_GENDER);
|
|
p.writeByte(chr.getGender());
|
|
return p;
|
|
}
|
|
|
|
public static Packet enableReport() { // thanks to snow
|
|
OutPacket p = OutPacket.create(SendOpcode.CLAIM_STATUS_CHANGED);
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet giveFinalAttack(int skillid, int time) { // packets found thanks to lailainoob
|
|
final OutPacket p = OutPacket.create(SendOpcode.GIVE_BUFF);
|
|
p.writeLong(0);
|
|
p.writeShort(0);
|
|
p.writeByte(0);//some 80 and 0 bs DIRECTION
|
|
p.writeByte(0x80);//let's just do 80, then 0
|
|
p.writeInt(0);
|
|
p.writeShort(1);
|
|
p.writeInt(skillid);
|
|
p.writeInt(time);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet loadFamily(Character player) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAMILY_PRIVILEGE_LIST);
|
|
p.writeInt(FamilyEntitlement.values().length);
|
|
for (int i = 0; i < FamilyEntitlement.values().length; i++) {
|
|
FamilyEntitlement entitlement = FamilyEntitlement.values()[i];
|
|
p.writeByte(i <= 1 ? 1 : 2); //type
|
|
p.writeInt(entitlement.getRepCost());
|
|
p.writeInt(entitlement.getUsageLimit());
|
|
p.writeString(entitlement.getName());
|
|
p.writeString(entitlement.getDescription());
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Family Result Message
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br>
|
|
* 64: You cannot add this character as a junior.
|
|
* 65: The name could not be found or is not online.
|
|
* 66: You belong to the same family.
|
|
* 67: You do not belong to the same family.<br>
|
|
* 69: The character you wish to add as\r\na Junior must be in the same
|
|
* map.<br>
|
|
* 70: This character is already a Junior of another character.<br>
|
|
* 71: The Junior you wish to add\r\nmust be at a lower rank.<br>
|
|
* 72: The gap between you and your\r\njunior must be within 20 levels.<br>
|
|
* 73: Another character has requested to add this character.\r\nPlease try
|
|
* again later.<br>
|
|
* 74: Another character has requested a summon.\r\nPlease try again
|
|
* later.<br>
|
|
* 75: The summons has failed. Your current location or state does not allow
|
|
* a summons.<br>
|
|
* 76: The family cannot extend more than 1000 generations from above and
|
|
* below.<br>
|
|
* 77: The Junior you wish to add\r\nmust be over Level 10.<br>
|
|
* 78: You cannot add a Junior \r\nthat has requested to change worlds.<br>
|
|
* 79: You cannot add a Junior \r\nsince you've requested to change
|
|
* worlds.<br>
|
|
* 80: Separation is not possible due to insufficient Mesos.\r\nYou will
|
|
* need %d Mesos to\r\nseparate with a Senior.<br>
|
|
* 81: Separation is not possible due to insufficient Mesos.\r\nYou will
|
|
* need %d Mesos to\r\nseparate with a Junior.<br>
|
|
* 82: The Entitlement does not apply because your level does not match the
|
|
* corresponding area.<br>
|
|
*
|
|
* @param type The type
|
|
* @return Family Result packet
|
|
*/
|
|
public static Packet sendFamilyMessage(int type, int mesos) {
|
|
OutPacket p = OutPacket.create(SendOpcode.FAMILY_RESULT);
|
|
p.writeInt(type);
|
|
p.writeInt(mesos);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getFamilyInfo(FamilyEntry f) {
|
|
if (f == null) {
|
|
return getEmptyFamilyInfo();
|
|
}
|
|
|
|
OutPacket p = OutPacket.create(SendOpcode.FAMILY_INFO_RESULT);
|
|
p.writeInt(f.getReputation()); // cur rep left
|
|
p.writeInt(f.getTotalReputation()); // tot rep left
|
|
p.writeInt(f.getTodaysRep()); // todays rep
|
|
p.writeShort(f.getJuniorCount()); // juniors added
|
|
p.writeShort(2); // juniors allowed
|
|
p.writeShort(0); //Unknown
|
|
p.writeInt(f.getFamily().getLeader().getChrId()); // Leader ID (Allows setting message)
|
|
p.writeString(f.getFamily().getName());
|
|
p.writeString(f.getFamily().getMessage()); //family message
|
|
p.writeInt(FamilyEntitlement.values().length); //Entitlement info count
|
|
for (FamilyEntitlement entitlement : FamilyEntitlement.values()) {
|
|
p.writeInt(entitlement.ordinal()); //ID
|
|
p.writeInt(f.isEntitlementUsed(entitlement) ? 1 : 0); //Used count
|
|
}
|
|
return p;
|
|
}
|
|
|
|
private static Packet getEmptyFamilyInfo() {
|
|
OutPacket p = OutPacket.create(SendOpcode.FAMILY_INFO_RESULT);
|
|
p.writeInt(0); // cur rep left
|
|
p.writeInt(0); // tot rep left
|
|
p.writeInt(0); // todays rep
|
|
p.writeShort(0); // juniors added
|
|
p.writeShort(2); // juniors allowed
|
|
p.writeShort(0); //Unknown
|
|
p.writeInt(0); // Leader ID (Allows setting message)
|
|
p.writeString("");
|
|
p.writeString(""); //family message
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showPedigree(FamilyEntry entry) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAMILY_CHART_RESULT);
|
|
p.writeInt(entry.getChrId()); //ID of viewed player's pedigree, can't be leader?
|
|
List<FamilyEntry> superJuniors = new ArrayList<>(4);
|
|
boolean hasOtherJunior = false;
|
|
int entryCount = 2; //2 guaranteed, leader and self
|
|
entryCount += Math.min(2, entry.getTotalSeniors());
|
|
//needed since OutPacket doesn't have any seek functionality
|
|
if (entry.getSenior() != null) {
|
|
if (entry.getSenior().getJuniorCount() == 2) {
|
|
entryCount++;
|
|
hasOtherJunior = true;
|
|
}
|
|
}
|
|
for (FamilyEntry junior : entry.getJuniors()) {
|
|
if (junior == null) {
|
|
continue;
|
|
}
|
|
entryCount++;
|
|
for (FamilyEntry superJunior : junior.getJuniors()) {
|
|
if (superJunior == null) {
|
|
continue;
|
|
}
|
|
entryCount++;
|
|
superJuniors.add(superJunior);
|
|
}
|
|
}
|
|
//write entries
|
|
boolean missingEntries = entryCount == 2; //pedigree requires at least 3 entries to show leader, might only have 2 if leader's juniors leave
|
|
if (missingEntries) {
|
|
entryCount++;
|
|
}
|
|
p.writeInt(entryCount); //player count
|
|
addPedigreeEntry(p, entry.getFamily().getLeader());
|
|
if (entry.getSenior() != null) {
|
|
if (entry.getSenior().getSenior() != null) {
|
|
addPedigreeEntry(p, entry.getSenior().getSenior());
|
|
}
|
|
addPedigreeEntry(p, entry.getSenior());
|
|
}
|
|
addPedigreeEntry(p, entry);
|
|
if (hasOtherJunior) { //must be sent after own entry
|
|
FamilyEntry otherJunior = entry.getSenior().getOtherJunior(entry);
|
|
if (otherJunior != null) {
|
|
addPedigreeEntry(p, otherJunior);
|
|
}
|
|
}
|
|
if (missingEntries) {
|
|
addPedigreeEntry(p, entry);
|
|
}
|
|
for (FamilyEntry junior : entry.getJuniors()) {
|
|
if (junior == null) {
|
|
continue;
|
|
}
|
|
addPedigreeEntry(p, junior);
|
|
for (FamilyEntry superJunior : junior.getJuniors()) {
|
|
if (superJunior != null) {
|
|
addPedigreeEntry(p, superJunior);
|
|
}
|
|
}
|
|
}
|
|
p.writeInt(2 + superJuniors.size()); //member info count
|
|
// 0 = total seniors, -1 = total members, otherwise junior count of ID
|
|
p.writeInt(-1);
|
|
p.writeInt(entry.getFamily().getTotalMembers());
|
|
p.writeInt(0);
|
|
p.writeInt(entry.getTotalSeniors()); //client subtracts provided seniors
|
|
for (FamilyEntry superJunior : superJuniors) {
|
|
p.writeInt(superJunior.getChrId());
|
|
p.writeInt(superJunior.getTotalJuniors());
|
|
}
|
|
p.writeInt(0); //another loop count (entitlements used)
|
|
//p.writeInt(1); //entitlement index
|
|
//p.writeInt(2); //times used
|
|
p.writeShort(entry.getJuniorCount() >= 2 ? 0 : 2); //0 disables Add button (only if viewing own pedigree)
|
|
return p;
|
|
}
|
|
|
|
private static void addPedigreeEntry(OutPacket p, FamilyEntry entry) {
|
|
Character chr = entry.getChr();
|
|
boolean isOnline = chr != null;
|
|
p.writeInt(entry.getChrId()); //ID
|
|
p.writeInt(entry.getSenior() != null ? entry.getSenior().getChrId() : 0); //parent ID
|
|
p.writeShort(entry.getJob().getId()); //job id
|
|
p.writeByte(entry.getLevel()); //level
|
|
p.writeBool(isOnline); //isOnline
|
|
p.writeInt(entry.getReputation()); //current rep
|
|
p.writeInt(entry.getTotalReputation()); //total rep
|
|
p.writeInt(entry.getRepsToSenior()); //reps recorded to senior
|
|
p.writeInt(entry.getTodaysRep());
|
|
p.writeInt(isOnline ? ((chr.isAwayFromWorld() || chr.getCashShop().isOpened()) ? -1 : chr.getClient().getChannel() - 1) : 0);
|
|
p.writeInt(isOnline ? (int) (chr.getLoggedInTime() / 60000) : 0); //time online in minutes
|
|
p.writeString(entry.getName()); //name
|
|
}
|
|
|
|
public static Packet updateAreaInfo(int area, String info) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(0x0A); //0x0B in v95
|
|
p.writeShort(area);//infoNumber
|
|
p.writeString(info);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getGPMessage(int gpChange) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(6);
|
|
p.writeInt(gpChange);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getItemMessage(int itemid) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(7);
|
|
p.writeInt(itemid);
|
|
return p;
|
|
}
|
|
|
|
public static Packet addCard(boolean full, int cardid, int level) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MONSTER_BOOK_SET_CARD);
|
|
p.writeByte(full ? 0 : 1);
|
|
p.writeInt(cardid);
|
|
p.writeInt(level);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showGainCard() {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(0x0D);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showForeignCardEffect(int id) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(id);
|
|
p.writeByte(0x0D);
|
|
return p;
|
|
}
|
|
|
|
public static Packet changeCover(int cardid) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MONSTER_BOOK_SET_COVER);
|
|
p.writeInt(cardid);
|
|
return p;
|
|
}
|
|
|
|
public static Packet aranGodlyStats() {
|
|
OutPacket p = OutPacket.create(SendOpcode.FORCED_STAT_SET);
|
|
p.writeBytes(new byte[]{
|
|
(byte) 0x1F, (byte) 0x0F, 0, 0,
|
|
(byte) 0xE7, 3, (byte) 0xE7, 3,
|
|
(byte) 0xE7, 3, (byte) 0xE7, 3,
|
|
(byte) 0xFF, 0, (byte) 0xE7, 3,
|
|
(byte) 0xE7, 3, (byte) 0x78, (byte) 0x8C});
|
|
return p;
|
|
}
|
|
|
|
public static Packet showIntro(String path) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(0x12);
|
|
p.writeString(path);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showInfo(String path) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(0x17);
|
|
p.writeString(path);
|
|
p.writeInt(1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showForeignInfo(int cid, String path) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(cid);
|
|
p.writeByte(0x17);
|
|
p.writeString(path);
|
|
p.writeInt(1);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a UI utility. 0x01 - Equipment Inventory. 0x02 - Stat Window. 0x03
|
|
* - Skill Window. 0x05 - Keyboard Settings. 0x06 - Quest window. 0x09 -
|
|
* Monsterbook Window. 0x0A - Char Info 0x0B - Guild BBS 0x12 - Monster
|
|
* Carnival Window 0x16 - Party Search. 0x17 - Item Creation Window. 0x1A -
|
|
* My Ranking O.O 0x1B - Family Window 0x1C - Family Pedigree 0x1D - GM
|
|
* Story Board /funny shet 0x1E - Envelop saying you got mail from an admin.
|
|
* lmfao 0x1F - Medal Window 0x20 - Maple Event (???) 0x21 - Invalid Pointer
|
|
* Crash
|
|
*
|
|
* @param ui
|
|
* @return
|
|
*/
|
|
public static Packet openUI(byte ui) {
|
|
OutPacket p = OutPacket.create(SendOpcode.OPEN_UI);
|
|
p.writeByte(ui);
|
|
return p;
|
|
}
|
|
|
|
public static Packet lockUI(boolean enable) {
|
|
OutPacket p = OutPacket.create(SendOpcode.LOCK_UI);
|
|
p.writeByte(enable ? 1 : 0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet disableUI(boolean enable) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DISABLE_UI);
|
|
p.writeByte(enable ? 1 : 0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet itemMegaphone(String msg, boolean whisper, int channel, Item item) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SERVERMESSAGE);
|
|
p.writeByte(8);
|
|
p.writeString(msg);
|
|
p.writeByte(channel - 1);
|
|
p.writeByte(whisper ? 1 : 0);
|
|
if (item == null) {
|
|
p.writeByte(0);
|
|
} else {
|
|
p.writeByte(item.getPosition());
|
|
addItemInfo(p, item, true);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeNPC(int objId) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REMOVE_NPC);
|
|
p.writeInt(objId);
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeNPCController(int objId) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_NPC_REQUEST_CONTROLLER);
|
|
p.writeByte(0);
|
|
p.writeInt(objId);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a report response
|
|
* <p>
|
|
* Possible values for <code>mode</code>:<br> 0: You have succesfully
|
|
* reported the user.<br> 1: Unable to locate the user.<br> 2: You may only
|
|
* report users 10 times a day.<br> 3: You have been reported to the GM's by
|
|
* a user.<br> 4: Your request did not go through for unknown reasons.
|
|
* Please try again later.<br>
|
|
*
|
|
* @param mode The mode
|
|
* @return Report Reponse packet
|
|
*/
|
|
public static Packet reportResponse(byte mode) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SUE_CHARACTER_RESULT);
|
|
p.writeByte(mode);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendHammerData(int hammerUsed) {
|
|
OutPacket p = OutPacket.create(SendOpcode.VICIOUS_HAMMER);
|
|
p.writeByte(0x39);
|
|
p.writeInt(0);
|
|
p.writeInt(hammerUsed);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendHammerMessage() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.VICIOUS_HAMMER);
|
|
p.writeByte(0x3D);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet playPortalSound() {
|
|
return showSpecialEffect(7);
|
|
}
|
|
|
|
public static Packet showMonsterBookPickup() {
|
|
return showSpecialEffect(14);
|
|
}
|
|
|
|
public static Packet showEquipmentLevelUp() {
|
|
return showSpecialEffect(15);
|
|
}
|
|
|
|
public static Packet showItemLevelup() {
|
|
return showSpecialEffect(15);
|
|
}
|
|
|
|
public static Packet showBuybackEffect() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(11);
|
|
p.writeInt(0);
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showForeignBuybackEffect(int cid) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(cid);
|
|
p.writeByte(11);
|
|
p.writeInt(0);
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* 0 = Levelup 6 = Exp did not drop (Safety Charms) 7 = Enter portal sound
|
|
* 8 = Job change 9 = Quest complete 10 = Recovery 11 = Buff effect
|
|
* 14 = Monster book pickup 15 = Equipment levelup 16 = Maker Skill Success
|
|
* 17 = Buff effect w/ sfx 19 = Exp card [500, 200, 50] 21 = Wheel of destiny
|
|
* 26 = Spirit Stone
|
|
*
|
|
* @param effect
|
|
* @return
|
|
*/
|
|
public static Packet showSpecialEffect(int effect) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(effect);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showMakerEffect(boolean makerSucceeded) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(16);
|
|
p.writeInt(makerSucceeded ? 0 : 1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showForeignMakerEffect(int cid, boolean makerSucceeded) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(cid);
|
|
p.writeByte(16);
|
|
p.writeInt(makerSucceeded ? 0 : 1);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showForeignEffect(int effect) {
|
|
return showForeignEffect(-1, effect);
|
|
}
|
|
|
|
public static Packet showForeignEffect(int chrId, int effect) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(chrId);
|
|
p.writeByte(effect);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showOwnRecovery(byte heal) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(0x0A);
|
|
p.writeByte(heal);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showRecovery(int chrId, byte amount) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_FOREIGN_EFFECT);
|
|
p.writeInt(chrId);
|
|
p.writeByte(0x0A);
|
|
p.writeByte(amount);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showWheelsLeft(int left) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_ITEM_GAIN_INCHAT);
|
|
p.writeByte(0x15);
|
|
p.writeByte(left);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateQuestFinish(short quest, int npc, short nextquest) { //Check
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_QUEST_INFO); //0xF2 in v95
|
|
p.writeByte(8);//0x0A in v95
|
|
p.writeShort(quest);
|
|
p.writeInt(npc);
|
|
p.writeShort(nextquest);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showInfoText(String text) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(9);
|
|
p.writeString(text);
|
|
return p;
|
|
}
|
|
|
|
public static Packet questError(short quest) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_QUEST_INFO);
|
|
p.writeByte(0x0A);
|
|
p.writeShort(quest);
|
|
return p;
|
|
}
|
|
|
|
public static Packet questFailure(byte type) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_QUEST_INFO);
|
|
p.writeByte(type);//0x0B = No meso, 0x0D = Worn by character, 0x0E = Not having the item ?
|
|
return p;
|
|
}
|
|
|
|
public static Packet questExpire(short quest) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.UPDATE_QUEST_INFO);
|
|
p.writeByte(0x0F);
|
|
p.writeShort(quest);
|
|
return p;
|
|
}
|
|
|
|
// MAKER_RESULT packets thanks to Arnah (Vertisy)
|
|
public static Packet makerResult(boolean success, int itemMade, int itemCount, int mesos, List<Pair<Integer, Integer>> itemsLost, int catalystID, List<Integer> INCBuffGems) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MAKER_RESULT);
|
|
p.writeInt(success ? 0 : 1); // 0 = success, 1 = fail
|
|
p.writeInt(1); // 1 or 2 doesn't matter, same methods
|
|
p.writeBool(!success);
|
|
if (success) {
|
|
p.writeInt(itemMade);
|
|
p.writeInt(itemCount);
|
|
}
|
|
p.writeInt(itemsLost.size()); // Loop
|
|
for (Pair<Integer, Integer> item : itemsLost) {
|
|
p.writeInt(item.getLeft());
|
|
p.writeInt(item.getRight());
|
|
}
|
|
p.writeInt(INCBuffGems.size());
|
|
for (Integer gem : INCBuffGems) {
|
|
p.writeInt(gem);
|
|
}
|
|
if (catalystID != -1) {
|
|
p.writeByte(1); // stimulator
|
|
p.writeInt(catalystID);
|
|
} else {
|
|
p.writeByte(0);
|
|
}
|
|
|
|
p.writeInt(mesos);
|
|
return p;
|
|
}
|
|
|
|
public static Packet makerResultCrystal(int itemIdGained, int itemIdLost) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MAKER_RESULT);
|
|
p.writeInt(0); // Always successful!
|
|
p.writeInt(3); // Monster Crystal
|
|
p.writeInt(itemIdGained);
|
|
p.writeInt(itemIdLost);
|
|
return p;
|
|
}
|
|
|
|
public static Packet makerResultDesynth(int itemId, int mesos, List<Pair<Integer, Integer>> itemsGained) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MAKER_RESULT);
|
|
p.writeInt(0); // Always successful!
|
|
p.writeInt(4); // Mode Desynth
|
|
p.writeInt(itemId); // Item desynthed
|
|
p.writeInt(itemsGained.size()); // Loop of items gained, (int, int)
|
|
for (Pair<Integer, Integer> item : itemsGained) {
|
|
p.writeInt(item.getLeft());
|
|
p.writeInt(item.getRight());
|
|
}
|
|
p.writeInt(mesos); // Mesos spent.
|
|
return p;
|
|
}
|
|
|
|
public static Packet makerEnableActions() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MAKER_RESULT);
|
|
p.writeInt(0); // Always successful!
|
|
p.writeInt(0); // Monster Crystal
|
|
p.writeInt(0);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getMultiMegaphone(String[] messages, int channel, boolean showEar) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SERVERMESSAGE);
|
|
p.writeByte(0x0A);
|
|
if (messages[0] != null) {
|
|
p.writeString(messages[0]);
|
|
}
|
|
p.writeByte(messages.length);
|
|
for (int i = 1; i < messages.length; i++) {
|
|
if (messages[i] != null) {
|
|
p.writeString(messages[i]);
|
|
}
|
|
}
|
|
for (int i = 0; i < 10; i++) {
|
|
p.writeByte(channel - 1);
|
|
}
|
|
p.writeByte(showEar ? 1 : 0);
|
|
p.writeByte(1);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a gm effect packet (ie. hide, banned, etc.)
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 0x04: You have successfully
|
|
* blocked access.<br>
|
|
* 0x05: The unblocking has been successful.<br> 0x06 with Mode 0: You have
|
|
* successfully removed the name from the ranks.<br> 0x06 with Mode 1: You
|
|
* have entered an invalid character name.<br> 0x10: GM Hide, mode
|
|
* determines whether or not it is on.<br> 0x1E: Mode 0: Failed to send
|
|
* warning Mode 1: Sent warning<br> 0x13 with Mode 0: + mapid 0x13 with Mode
|
|
* 1: + ch (FF = Unable to find merchant)
|
|
*
|
|
* @param type The type
|
|
* @param mode The mode
|
|
* @return The gm effect packet
|
|
*/
|
|
public static Packet getGMEffect(int type, byte mode) {
|
|
OutPacket p = OutPacket.create(SendOpcode.ADMIN_RESULT);
|
|
p.writeByte(type);
|
|
p.writeByte(mode);
|
|
return p;
|
|
}
|
|
|
|
public static Packet findMerchantResponse(boolean map, int extra) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.ADMIN_RESULT);
|
|
p.writeByte(0x13);
|
|
p.writeByte(map ? 0 : 1); //00 = mapid, 01 = ch
|
|
if (map) {
|
|
p.writeInt(extra);
|
|
} else {
|
|
p.writeByte(extra); //-1 = unable to find
|
|
}
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet disableMinimap() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.ADMIN_RESULT);
|
|
p.writeShort(0x1C);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendFamilyInvite(int playerId, String inviter) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAMILY_JOIN_REQUEST);
|
|
p.writeInt(playerId);
|
|
p.writeString(inviter);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendFamilySummonRequest(String familyName, String from) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAMILY_SUMMON_REQUEST);
|
|
p.writeString(from);
|
|
p.writeString(familyName);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendFamilyLoginNotice(String name, boolean loggedIn) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAMILY_NOTIFY_LOGIN_OR_LOGOUT);
|
|
p.writeBool(loggedIn);
|
|
p.writeString(name);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendFamilyJoinResponse(boolean accepted, String added) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAMILY_JOIN_REQUEST_RESULT);
|
|
p.writeByte(accepted ? 1 : 0);
|
|
p.writeString(added);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getSeniorMessage(String name) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAMILY_JOIN_ACCEPTED);
|
|
p.writeString(name);
|
|
p.writeInt(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendGainRep(int gain, String from) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FAMILY_REP_GAIN);
|
|
p.writeInt(gain);
|
|
p.writeString(from);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBoughtCashPackage(List<Item> cashPackage, int accountId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x89);
|
|
p.writeByte(cashPackage.size());
|
|
|
|
for (Item item : cashPackage) {
|
|
addCashItemInformation(p, item, accountId);
|
|
}
|
|
|
|
p.writeShort(0);
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBoughtQuestItem(int itemId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
p.writeByte(0x8D);
|
|
p.writeInt(1);
|
|
p.writeShort(1);
|
|
p.writeByte(0x0B);
|
|
p.writeByte(0);
|
|
p.writeInt(itemId);
|
|
return p;
|
|
}
|
|
|
|
// Cash Shop Surprise packets found thanks to Arnah (Vertisy)
|
|
public static Packet onCashItemGachaponOpenFailed() {
|
|
OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_CASH_ITEM_GACHAPON_RESULT);
|
|
p.writeByte(0xE4);
|
|
return p;
|
|
}
|
|
|
|
public static Packet onCashGachaponOpenSuccess(int accountid, long sn, int remainingBoxes, Item item, int itemid, int nSelectedItemCount, boolean bJackpot) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_CASH_ITEM_GACHAPON_RESULT);
|
|
p.writeByte(0xE5); // subopcode thanks to Ubaware
|
|
p.writeLong(sn);// sn of the box used
|
|
p.writeInt(remainingBoxes);
|
|
addCashItemInformation(p, item, accountid);
|
|
p.writeInt(itemid);// the itemid of the liSN?
|
|
p.writeByte(nSelectedItemCount);// the total count now? o.O
|
|
p.writeBool(bJackpot);// "CashGachaponJackpot"
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendMesoLimit() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.TRADE_MONEY_LIMIT); //Players under level 15 can only trade 1m per day
|
|
return p;
|
|
}
|
|
|
|
public static Packet removeItemFromDuey(boolean remove, int Package) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARCEL);
|
|
p.writeByte(0x17);
|
|
p.writeInt(Package);
|
|
p.writeByte(remove ? 3 : 4);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendDueyParcelReceived(String from, boolean quick) { // thanks inhyuk
|
|
OutPacket p = OutPacket.create(SendOpcode.PARCEL);
|
|
p.writeByte(0x19);
|
|
p.writeString(from);
|
|
p.writeBool(quick);
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendDueyParcelNotification(boolean quick) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARCEL);
|
|
p.writeByte(0x1B);
|
|
p.writeBool(quick); // 0 : package received, 1 : quick delivery package
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendDueyMSG(byte operation) {
|
|
return sendDuey(operation, null);
|
|
}
|
|
|
|
public static Packet sendDuey(int operation, List<DueyPackage> packages) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PARCEL);
|
|
p.writeByte(operation);
|
|
if (operation == 8) {
|
|
p.writeByte(0);
|
|
p.writeByte(packages.size());
|
|
for (DueyPackage dp : packages) {
|
|
p.writeInt(dp.getPackageId());
|
|
p.writeFixedString(dp.getSender());
|
|
for (int i = dp.getSender().length(); i < 13; i++) {
|
|
p.writeByte(0);
|
|
}
|
|
|
|
p.writeInt(dp.getMesos());
|
|
p.writeLong(getTime(dp.sentTimeInMilliseconds()));
|
|
|
|
String msg = dp.getMessage();
|
|
if (msg != null) {
|
|
p.writeInt(1);
|
|
p.writeFixedString(msg);
|
|
for (int i = msg.length(); i < 200; i++) {
|
|
p.writeByte(0);
|
|
}
|
|
} else {
|
|
p.writeInt(0);
|
|
p.skip(200);
|
|
}
|
|
|
|
p.writeByte(0);
|
|
if (dp.getItem() != null) {
|
|
p.writeByte(1);
|
|
addItemInfo(p, dp.getItem(), true);
|
|
} else {
|
|
p.writeByte(0);
|
|
}
|
|
}
|
|
p.writeByte(0);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendDojoAnimation(byte firstByte, String animation) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FIELD_EFFECT);
|
|
p.writeByte(firstByte);
|
|
p.writeString(animation);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getDojoInfo(String info) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(10);
|
|
p.writeBytes(new byte[]{(byte) 0xB7, 4});//QUEST ID f5
|
|
p.writeString(info);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getDojoInfoMessage(String message) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(9);
|
|
p.writeString(message);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a "block" packet (ie. the cash shop is unavailable, etc)
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 1: The portal is closed for
|
|
* now.<br> 2: You cannot go to that place.<br> 3: Unable to approach due to
|
|
* the force of the ground.<br> 4: You cannot teleport to or on this
|
|
* map.<br> 5: Unable to approach due to the force of the ground.<br> 6:
|
|
* Only party members can enter this map.<br> 7: The Cash Shop is
|
|
* currently not available. Stay tuned...<br>
|
|
*
|
|
* @param type The type
|
|
* @return The "block" packet.
|
|
*/
|
|
public static Packet blockedMessage(int type) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.BLOCKED_MAP);
|
|
p.writeByte(type);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets a "block" packet (ie. the cash shop is unavailable, etc)
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 1: You cannot move that
|
|
* channel. Please try again later.<br> 2: You cannot go into the cash shop.
|
|
* Please try again later.<br> 3: The Item-Trading Shop is currently
|
|
* unavailable. Please try again later.<br> 4: You cannot go into the trade
|
|
* shop, due to limitation of user count.<br> 5: You do not meet the minimum
|
|
* level requirement to access the Trade Shop.<br>
|
|
*
|
|
* @param type The type
|
|
* @return The "block" packet.
|
|
*/
|
|
public static Packet blockedMessage2(int type) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.BLOCKED_SERVER);
|
|
p.writeByte(type);
|
|
return p;
|
|
}
|
|
|
|
public static Packet updateDojoStats(Character chr, int belt) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(10);
|
|
p.writeBytes(new byte[]{(byte) 0xB7, 4}); //?
|
|
p.writeString("pt=" + chr.getDojoPoints() + ";belt=" + belt + ";tuto=" + (chr.getFinishedDojoTutorial() ? "1" : "0"));
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a "levelup" packet to the guild or family.
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 0: <Family> ? has reached Lv.
|
|
* ?.<br> - The Reps you have received from ? will be reduced in half. 1:
|
|
* <Family> ? has reached Lv. ?.<br> 2: <Guild> ? has reached Lv. ?.<br>
|
|
*
|
|
* @param type The type
|
|
* @return The "levelup" packet.
|
|
*/
|
|
public static Packet levelUpMessage(int type, int level, String charname) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.NOTIFY_LEVELUP);
|
|
p.writeByte(type);
|
|
p.writeInt(level);
|
|
p.writeString(charname);
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a "married" packet to the guild or family.
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 0: <Guild ? is now married.
|
|
* Please congratulate them.<br> 1: <Family ? is now married. Please
|
|
* congratulate them.<br>
|
|
*
|
|
* @param type The type
|
|
* @return The "married" packet.
|
|
*/
|
|
public static Packet marriageMessage(int type, String charname) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.NOTIFY_MARRIAGE);
|
|
p.writeByte(type); // 0: guild, 1: family
|
|
p.writeString("> " + charname); //To fix the stupid packet lol
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a "job advance" packet to the guild or family.
|
|
* <p>
|
|
* Possible values for <code>type</code>:<br> 0: <Guild ? has advanced to
|
|
* a(an) ?.<br> 1: <Family ? has advanced to a(an) ?.<br>
|
|
*
|
|
* @param type The type
|
|
* @return The "job advance" packet.
|
|
*/
|
|
public static Packet jobMessage(int type, int job, String charname) {
|
|
OutPacket p = OutPacket.create(SendOpcode.NOTIFY_JOB_CHANGE);
|
|
p.writeByte(type);
|
|
p.writeInt(job); //Why fking int?
|
|
p.writeString("> " + charname); //To fix the stupid packet lol
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* @param type - (0:Light&Long 1:Heavy&Short)
|
|
* @param delay - seconds
|
|
* @return
|
|
*/
|
|
public static Packet trembleEffect(int type, int delay) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.FIELD_EFFECT);
|
|
p.writeByte(1);
|
|
p.writeByte(type);
|
|
p.writeInt(delay);
|
|
return p;
|
|
}
|
|
|
|
public static Packet getEnergy(String info, int amount) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SESSION_VALUE);
|
|
p.writeString(info);
|
|
p.writeString(Integer.toString(amount));
|
|
return p;
|
|
}
|
|
|
|
public static Packet dojoWarpUp() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DOJO_WARP_UP);
|
|
p.writeByte(0);
|
|
p.writeByte(6);
|
|
return p;
|
|
}
|
|
|
|
public static Packet itemExpired(int itemid) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(2);
|
|
p.writeInt(itemid);
|
|
return p;
|
|
}
|
|
|
|
private static String getRightPaddedStr(String in, char padchar, int length) {
|
|
StringBuilder builder = new StringBuilder(in);
|
|
for (int x = in.length(); x < length; x++) {
|
|
builder.append(padchar);
|
|
}
|
|
return builder.toString();
|
|
}
|
|
|
|
public static Packet MobDamageMobFriendly(MapleMonster mob, int damage, int remainingHp) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.DAMAGE_MONSTER);
|
|
p.writeInt(mob.getObjectId());
|
|
p.writeByte(1); // direction ?
|
|
p.writeInt(damage);
|
|
p.writeInt(remainingHp);
|
|
p.writeInt(mob.getMaxHp());
|
|
return p;
|
|
}
|
|
|
|
public static Packet shopErrorMessage(int error, int type) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.PLAYER_INTERACTION);
|
|
p.writeByte(0x0A);
|
|
p.writeByte(type);
|
|
p.writeByte(error);
|
|
return p;
|
|
}
|
|
|
|
private static void addRingInfo(OutPacket p, Character chr) {
|
|
p.writeShort(chr.getCrushRings().size());
|
|
for (Ring ring : chr.getCrushRings()) {
|
|
p.writeInt(ring.getPartnerChrId());
|
|
p.writeFixedString(getRightPaddedStr(ring.getPartnerName(), '\0', 13));
|
|
p.writeInt(ring.getRingId());
|
|
p.writeInt(0);
|
|
p.writeInt(ring.getPartnerRingId());
|
|
p.writeInt(0);
|
|
}
|
|
p.writeShort(chr.getFriendshipRings().size());
|
|
for (Ring ring : chr.getFriendshipRings()) {
|
|
p.writeInt(ring.getPartnerChrId());
|
|
p.writeFixedString(getRightPaddedStr(ring.getPartnerName(), '\0', 13));
|
|
p.writeInt(ring.getRingId());
|
|
p.writeInt(0);
|
|
p.writeInt(ring.getPartnerRingId());
|
|
p.writeInt(0);
|
|
p.writeInt(ring.getItemId());
|
|
}
|
|
|
|
if (chr.getPartnerId() > 0) {
|
|
Ring marriageRing = chr.getMarriageRing();
|
|
|
|
p.writeShort(1);
|
|
p.writeInt(chr.getRelationshipId());
|
|
p.writeInt(chr.getGender() == 0 ? chr.getId() : chr.getPartnerId());
|
|
p.writeInt(chr.getGender() == 0 ? chr.getPartnerId() : chr.getId());
|
|
p.writeShort((marriageRing != null) ? 3 : 1);
|
|
if (marriageRing != null) {
|
|
p.writeInt(marriageRing.getItemId());
|
|
p.writeInt(marriageRing.getItemId());
|
|
} else {
|
|
p.writeInt(1112803); // Engagement Ring's Outcome (doesn't matter for engagement)
|
|
p.writeInt(1112803); // Engagement Ring's Outcome (doesn't matter for engagement)
|
|
}
|
|
p.writeFixedString(StringUtil.getRightPaddedStr(chr.getGender() == 0 ? chr.getName() : Character.getNameById(chr.getPartnerId()), '\0', 13));
|
|
p.writeFixedString(StringUtil.getRightPaddedStr(chr.getGender() == 0 ? Character.getNameById(chr.getPartnerId()) : chr.getName(), '\0', 13));
|
|
} else {
|
|
p.writeShort(0);
|
|
}
|
|
}
|
|
|
|
public static Packet finishedSort(int inv) {
|
|
OutPacket p = OutPacket.create(SendOpcode.GATHER_ITEM_RESULT);
|
|
p.writeByte(0);
|
|
p.writeByte(inv);
|
|
return p;
|
|
}
|
|
|
|
public static Packet finishedSort2(int inv) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SORT_ITEM_RESULT);
|
|
p.writeByte(0);
|
|
p.writeByte(inv);
|
|
return p;
|
|
}
|
|
|
|
public static Packet bunnyPacket() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHOW_STATUS_INFO);
|
|
p.writeByte(9);
|
|
p.writeFixedString("Protect the Moon Bunny!!!");
|
|
return p;
|
|
}
|
|
|
|
public static Packet hpqMessage(String text) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.BLOW_WEATHER); // not 100% sure
|
|
p.writeByte(0);
|
|
p.writeInt(5120016);
|
|
p.writeFixedString(text);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showEventInstructions() {
|
|
final OutPacket p = OutPacket.create(SendOpcode.GMEVENT_INSTRUCTIONS);
|
|
p.writeByte(0);
|
|
return p;
|
|
}
|
|
|
|
public static Packet leftKnockBack() {
|
|
return OutPacket.create(SendOpcode.LEFT_KNOCK_BACK);
|
|
}
|
|
|
|
public static Packet rollSnowBall(boolean entermap, int state, MapleSnowball ball0, MapleSnowball ball1) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SNOWBALL_STATE);
|
|
if (entermap) {
|
|
p.skip(21);
|
|
} else {
|
|
p.writeByte(state);// 0 = move, 1 = roll, 2 is down disappear, 3 is up disappear
|
|
p.writeInt(ball0.getSnowmanHP() / 75);
|
|
p.writeInt(ball1.getSnowmanHP() / 75);
|
|
p.writeShort(ball0.getPosition());//distance snowball down, 84 03 = max
|
|
p.writeByte(-1);
|
|
p.writeShort(ball1.getPosition());//distance snowball up, 84 03 = max
|
|
p.writeByte(-1);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet hitSnowBall(int what, int damage) {
|
|
OutPacket p = OutPacket.create(SendOpcode.HIT_SNOWBALL);
|
|
p.writeByte(what);
|
|
p.writeInt(damage);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a Snowball Message<br>
|
|
* <p>
|
|
* Possible values for <code>message</code>:<br> 1: ... Team's snowball has
|
|
* passed the stage 1.<br> 2: ... Team's snowball has passed the stage
|
|
* 2.<br> 3: ... Team's snowball has passed the stage 3.<br> 4: ... Team is
|
|
* attacking the snowman, stopping the progress<br> 5: ... Team is moving
|
|
* again<br>
|
|
*
|
|
* @param message
|
|
*/
|
|
public static Packet snowballMessage(int team, int message) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SNOWBALL_MESSAGE);
|
|
p.writeByte(team);// 0 is down, 1 is up
|
|
p.writeInt(message);
|
|
return p;
|
|
}
|
|
|
|
public static Packet coconutScore(int team1, int team2) {
|
|
OutPacket p = OutPacket.create(SendOpcode.COCONUT_SCORE);
|
|
p.writeShort(team1);
|
|
p.writeShort(team2);
|
|
return p;
|
|
}
|
|
|
|
public static Packet hitCoconut(boolean spawn, int id, int type) {
|
|
OutPacket p = OutPacket.create(SendOpcode.COCONUT_HIT);
|
|
if (spawn) {
|
|
p.writeShort(-1);
|
|
p.writeShort(5000);
|
|
p.writeByte(0);
|
|
} else {
|
|
p.writeShort(id);
|
|
p.writeShort(1000);//delay till you can attack again!
|
|
p.writeByte(type); // What action to do for the coconut.
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet customPacket(String packet) {
|
|
OutPacket p = new ByteBufOutPacket();
|
|
p.writeBytes(HexTool.getByteArrayFromHexString(packet));
|
|
return p;
|
|
}
|
|
|
|
public static Packet customPacket(byte[] packet) {
|
|
OutPacket p = new ByteBufOutPacket();
|
|
p.writeBytes(packet);
|
|
return p;
|
|
}
|
|
|
|
public static Packet spawnGuide(boolean spawn) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_GUIDE);
|
|
p.writeBool(spawn);
|
|
return p;
|
|
}
|
|
|
|
public static Packet talkGuide(String talk) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.TALK_GUIDE);
|
|
p.writeByte(0);
|
|
p.writeString(talk);
|
|
p.writeBytes(new byte[]{(byte) 0xC8, 0, 0, 0, (byte) 0xA0, (byte) 0x0F, 0, 0});
|
|
return p;
|
|
}
|
|
|
|
public static Packet guideHint(int hint) {
|
|
OutPacket p = OutPacket.create(SendOpcode.TALK_GUIDE);
|
|
p.writeByte(1);
|
|
p.writeInt(hint);
|
|
p.writeInt(7000);
|
|
return p;
|
|
}
|
|
|
|
public static void addCashItemInformation(OutPacket p, Item item, int accountId) {
|
|
addCashItemInformation(p, item, accountId, null);
|
|
}
|
|
|
|
public static void addCashItemInformation(OutPacket p, Item item, int accountId, String giftMessage) {
|
|
boolean isGift = giftMessage != null;
|
|
boolean isRing = false;
|
|
Equip equip = null;
|
|
if (item.getInventoryType().equals(InventoryType.EQUIP)) {
|
|
equip = (Equip) item;
|
|
isRing = equip.getRingId() > -1;
|
|
}
|
|
p.writeLong(item.getPetId() > -1 ? item.getPetId() : isRing ? equip.getRingId() : item.getCashId());
|
|
if (!isGift) {
|
|
p.writeInt(accountId);
|
|
p.writeInt(0);
|
|
}
|
|
p.writeInt(item.getItemId());
|
|
if (!isGift) {
|
|
p.writeInt(item.getSN());
|
|
p.writeShort(item.getQuantity());
|
|
}
|
|
p.writeFixedString(StringUtil.getRightPaddedStr(item.getGiftFrom(), '\0', 13));
|
|
if (isGift) {
|
|
p.writeFixedString(StringUtil.getRightPaddedStr(giftMessage, '\0', 73));
|
|
return;
|
|
}
|
|
addExpirationTime(p, item.getExpiration());
|
|
p.writeLong(0);
|
|
}
|
|
|
|
public static Packet showWishList(Character mc, boolean update) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
if (update) {
|
|
p.writeByte(0x55);
|
|
} else {
|
|
p.writeByte(0x4F);
|
|
}
|
|
|
|
for (int sn : mc.getCashShop().getWishList()) {
|
|
p.writeInt(sn);
|
|
}
|
|
|
|
for (int i = mc.getCashShop().getWishList().size(); i < 10; i++) {
|
|
p.writeInt(0);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBoughtCashItem(Item item, int accountId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x57);
|
|
addCashItemInformation(p, item, accountId);
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBoughtCashRing(Item ring, String recipient, int accountId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
p.writeByte(0x87);
|
|
addCashItemInformation(p, ring, accountId);
|
|
p.writeString(recipient);
|
|
p.writeInt(ring.getItemId());
|
|
p.writeShort(1); //quantity
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* 00 = Due to an unknown error, failed
|
|
* A3 = Request timed out. Please try again.
|
|
* A4 = Due to an unknown error, failed + warpout
|
|
* A5 = You don't have enough cash.
|
|
* A6 = long as shet msg
|
|
* A7 = You have exceeded the allotted limit of price for gifts.
|
|
* A8 = You cannot send a gift to your own account. Log in on the char and purchase
|
|
* A9 = Please confirm whether the character's name is correct.
|
|
* AA = Gender restriction!
|
|
* AB = gift cannot be sent because recipient inv is full
|
|
* AC = exceeded the number of cash items you can have
|
|
* AD = check and see if the character name is wrong or there is gender restrictions
|
|
* //Skipped a few
|
|
* B0 = Wrong Coupon Code
|
|
* B1 = Disconnect from CS because of 3 wrong coupon codes < lol
|
|
* B2 = Expired Coupon
|
|
* B3 = Coupon has been used already
|
|
* B4 = Nexon internet cafes? lolfk
|
|
* B8 = Due to gender restrictions, the coupon cannot be used.
|
|
* BB = inv full
|
|
* BC = long as shet "(not?) available to purchase by a use at the premium" msg
|
|
* BD = invalid gift recipient
|
|
* BE = invalid receiver name
|
|
* BF = item unavailable to purchase at this hour
|
|
* C0 = not enough items in stock, therefore not available
|
|
* C1 = you have exceeded spending limit of NX
|
|
* C2 = not enough mesos? Lol not even 1 mesos xD
|
|
* C3 = cash shop unavailable during beta phase
|
|
* C4 = check birthday code
|
|
* C7 = only available to users buying cash item, whatever msg too long
|
|
* C8 = already applied for this
|
|
* CD = You have reached the daily purchase limit for the cash shop.
|
|
* D0 = coupon account limit reached
|
|
* D2 = coupon system currently unavailable
|
|
* D3 = item can only be used 15 days after registration
|
|
* D4 = not enough gift tokens
|
|
* D6 = fresh people cannot gift items lul
|
|
* D7 = bad people cannot gift items >:(
|
|
* D8 = cannot gift due to limitations
|
|
* D9 = cannot gift due to amount of gifted times
|
|
* DA = cannot be gifted due to technical difficulties
|
|
* DB = cannot transfer to char below level 20
|
|
* DC = cannot transfer char to same world
|
|
* DD = cannot transfer char to new server world
|
|
* DE = cannot transfer char out of this world
|
|
* DF = cannot transfer char due to no empty char slots
|
|
* E0 = event or free test time ended
|
|
* E6 = item cannot be purchased with MaplePoints
|
|
* E7 = lol sorry for the inconvenience, eh?
|
|
* E8 = cannot purchase by anyone under 7
|
|
*/
|
|
public static Packet showCashShopMessage(byte message) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
p.writeByte(0x5C);
|
|
p.writeByte(message);
|
|
return p;
|
|
}
|
|
|
|
public static Packet showCashInventory(Client c) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x4B);
|
|
p.writeShort(c.getPlayer().getCashShop().getInventory().size());
|
|
|
|
for (Item item : c.getPlayer().getCashShop().getInventory()) {
|
|
addCashItemInformation(p, item, c.getAccID());
|
|
}
|
|
|
|
p.writeShort(c.getPlayer().getStorage().getSlots());
|
|
p.writeShort(c.getCharacterSlots());
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showGifts(List<Pair<Item, String>> gifts) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x4D);
|
|
p.writeShort(gifts.size());
|
|
|
|
for (Pair<Item, String> gift : gifts) {
|
|
addCashItemInformation(p, gift.getLeft(), 0, gift.getRight());
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showGiftSucceed(String to, CashItem item) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x5E); //0x5D, Couldn't be sent
|
|
p.writeString(to);
|
|
p.writeInt(item.getItemId());
|
|
p.writeShort(item.getCount());
|
|
p.writeInt(item.getPrice());
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBoughtInventorySlots(int type, short slots) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x60);
|
|
p.writeByte(type);
|
|
p.writeShort(slots);
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBoughtStorageSlots(short slots) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x62);
|
|
p.writeShort(slots);
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet showBoughtCharacterSlot(short slots) {
|
|
OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x64);
|
|
p.writeShort(slots);
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet takeFromCashInventory(Item item) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x68);
|
|
p.writeShort(item.getPosition());
|
|
addItemInfo(p, item, true);
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet deleteCashItem(Item item) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
p.writeByte(0x6C);
|
|
p.writeLong(item.getCashId());
|
|
return p;
|
|
}
|
|
|
|
public static Packet refundCashItem(Item item, int maplePoints) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
p.writeByte(0x85);
|
|
p.writeLong(item.getCashId());
|
|
p.writeInt(maplePoints);
|
|
return p;
|
|
}
|
|
|
|
public static Packet putIntoCashInventory(Item item, int accountId) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_OPERATION);
|
|
|
|
p.writeByte(0x6A);
|
|
addCashItemInformation(p, item, accountId);
|
|
|
|
return p;
|
|
}
|
|
|
|
public static Packet openCashShop(Client c, boolean mts) throws Exception {
|
|
final OutPacket p = OutPacket.create(mts ? SendOpcode.SET_ITC : SendOpcode.SET_CASH_SHOP);
|
|
|
|
addCharacterInfo(p, c.getPlayer());
|
|
|
|
if (!mts) {
|
|
p.writeByte(1);
|
|
}
|
|
|
|
p.writeString(c.getAccountName());
|
|
if (mts) {
|
|
p.writeBytes(new byte[]{(byte) 0x88, 19, 0, 0,
|
|
7, 0, 0, 0,
|
|
(byte) 0xF4, 1, 0, 0,
|
|
(byte) 0x18, 0, 0, 0,
|
|
(byte) 0xA8, 0, 0, 0,
|
|
(byte) 0x70, (byte) 0xAA, (byte) 0xA7, (byte) 0xC5,
|
|
(byte) 0x4E, (byte) 0xC1, (byte) 0xCA, 1});
|
|
} else {
|
|
p.writeInt(0);
|
|
List<SpecialCashItem> lsci = CashItemFactory.getSpecialCashItems();
|
|
p.writeShort(lsci.size());//Guess what
|
|
for (SpecialCashItem sci : lsci) {
|
|
p.writeInt(sci.getSN());
|
|
p.writeInt(sci.getModifier());
|
|
p.writeByte(sci.getInfo());
|
|
}
|
|
p.skip(121);
|
|
|
|
List<List<Integer>> mostSellers = c.getWorldServer().getMostSellerCashItems();
|
|
for (int i = 1; i <= 8; i++) {
|
|
List<Integer> mostSellersTab = mostSellers.get(i);
|
|
|
|
for (int j = 0; j < 2; j++) {
|
|
for (Integer snid : mostSellersTab) {
|
|
p.writeInt(i);
|
|
p.writeInt(j);
|
|
p.writeInt(snid);
|
|
}
|
|
}
|
|
}
|
|
|
|
p.writeInt(0);
|
|
p.writeShort(0);
|
|
p.writeByte(0);
|
|
p.writeInt(75);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public static Packet sendVegaScroll(int op) {
|
|
OutPacket p = OutPacket.create(SendOpcode.VEGA_SCROLL);
|
|
p.writeByte(op);
|
|
return p;
|
|
}
|
|
|
|
public static Packet resetForcedStats() {
|
|
return OutPacket.create(SendOpcode.FORCED_STAT_RESET);
|
|
}
|
|
|
|
public static Packet showCombo(int count) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SHOW_COMBO);
|
|
p.writeInt(count);
|
|
return p;
|
|
}
|
|
|
|
public static Packet earnTitleMessage(String msg) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SCRIPT_PROGRESS_MESSAGE);
|
|
p.writeString(msg);
|
|
return p;
|
|
}
|
|
|
|
public static Packet CPUpdate(boolean party, int curCP, int totalCP, int team) { // CPQ
|
|
final OutPacket p;
|
|
if (!party) {
|
|
p = OutPacket.create(SendOpcode.MONSTER_CARNIVAL_OBTAINED_CP);
|
|
} else {
|
|
p = OutPacket.create(SendOpcode.MONSTER_CARNIVAL_PARTY_CP);
|
|
p.writeByte(team); // team?
|
|
}
|
|
p.writeShort(curCP);
|
|
p.writeShort(totalCP);
|
|
return p;
|
|
}
|
|
|
|
public static Packet CPQMessage(byte message) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MONSTER_CARNIVAL_MESSAGE);
|
|
p.writeByte(message); // Message
|
|
return p;
|
|
}
|
|
|
|
public static Packet playerSummoned(String name, int tab, int number) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MONSTER_CARNIVAL_SUMMON);
|
|
p.writeByte(tab);
|
|
p.writeByte(number);
|
|
p.writeString(name);
|
|
return p;
|
|
}
|
|
|
|
public static Packet playerDiedMessage(String name, int lostCP, int team) { // CPQ
|
|
OutPacket p = OutPacket.create(SendOpcode.MONSTER_CARNIVAL_DIED);
|
|
p.writeByte(team); // team
|
|
p.writeString(name);
|
|
p.writeByte(lostCP);
|
|
return p;
|
|
}
|
|
|
|
public static Packet startMonsterCarnival(Character chr, int team, int opposition) {
|
|
OutPacket p = OutPacket.create(SendOpcode.MONSTER_CARNIVAL_START);
|
|
p.writeByte(team); // team
|
|
p.writeShort(chr.getCP()); // Obtained CP - Used CP
|
|
p.writeShort(chr.getTotalCP()); // Total Obtained CP
|
|
p.writeShort(chr.getMonsterCarnival().getCP(team)); // Obtained CP - Used CP of the team
|
|
p.writeShort(chr.getMonsterCarnival().getTotalCP(team)); // Total Obtained CP of the team
|
|
p.writeShort(chr.getMonsterCarnival().getCP(opposition)); // Obtained CP - Used CP of the team
|
|
p.writeShort(chr.getMonsterCarnival().getTotalCP(opposition)); // Total Obtained CP of the team
|
|
p.writeShort(0); // Probably useless nexon shit
|
|
p.writeLong(0); // Probably useless nexon shit
|
|
return p;
|
|
}
|
|
|
|
public static Packet sheepRanchInfo(byte wolf, byte sheep) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHEEP_RANCH_INFO);
|
|
p.writeByte(wolf);
|
|
p.writeByte(sheep);
|
|
return p;
|
|
}
|
|
//Know what this is? ?? >=)
|
|
|
|
public static Packet sheepRanchClothes(int id, byte clothes) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.SHEEP_RANCH_CLOTHES);
|
|
p.writeInt(id); //Character id
|
|
p.writeByte(clothes); //0 = sheep, 1 = wolf, 2 = Spectator (wolf without wool)
|
|
return p;
|
|
}
|
|
|
|
public static Packet incubatorResult() {//lol
|
|
OutPacket p = OutPacket.create(SendOpcode.INCUBATOR_RESULT);
|
|
p.skip(6);
|
|
return p;
|
|
}
|
|
|
|
public static Packet pyramidGauge(int gauge) {
|
|
OutPacket p = OutPacket.create(SendOpcode.PYRAMID_GAUGE);
|
|
p.writeInt(gauge);
|
|
return p;
|
|
}
|
|
// f2
|
|
|
|
public static Packet pyramidScore(byte score, int exp) {//Type cannot be higher than 4 (Rank D), otherwise you'll crash
|
|
OutPacket p = OutPacket.create(SendOpcode.PYRAMID_SCORE);
|
|
p.writeByte(score);
|
|
p.writeInt(exp);
|
|
return p;
|
|
}
|
|
|
|
public static Packet spawnDragon(MapleDragon dragon) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SPAWN_DRAGON);
|
|
p.writeInt(dragon.getOwner().getId());//objectid = owner id
|
|
p.writeShort(dragon.getPosition().x);
|
|
p.writeShort(0);
|
|
p.writeShort(dragon.getPosition().y);
|
|
p.writeShort(0);
|
|
p.writeByte(dragon.getStance());
|
|
p.writeByte(0);
|
|
p.writeShort(dragon.getOwner().getJob().getId());
|
|
return p;
|
|
}
|
|
|
|
public static Packet moveDragon(MapleDragon dragon, Point startPos, InPacket movementPacket, long movementDataLength) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.MOVE_DRAGON);
|
|
p.writeInt(dragon.getOwner().getId());
|
|
p.writePos(startPos);
|
|
rebroadcastMovementList(p, movementPacket, movementDataLength);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Sends a request to remove Mir<br>
|
|
*
|
|
* @param charid - Needs the specific Character ID
|
|
* @return The packet
|
|
*/
|
|
public static Packet removeDragon(int chrId) {
|
|
OutPacket p = OutPacket.create(SendOpcode.REMOVE_DRAGON);
|
|
p.writeInt(chrId);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Changes the current background effect to either being rendered or not.
|
|
* Data is still missing, so this is pretty binary at the moment in how it
|
|
* behaves.
|
|
*
|
|
* @param remove whether or not the remove or add the specified layer.
|
|
* @param layer the targeted layer for removal or addition.
|
|
* @param transition the time it takes to transition the effect.
|
|
* @return a packet to change the background effect of a specified layer.
|
|
*/
|
|
public static Packet changeBackgroundEffect(boolean remove, int layer, int transition) {
|
|
OutPacket p = OutPacket.create(SendOpcode.SET_BACK_EFFECT);
|
|
p.writeBool(remove);
|
|
p.writeInt(0); // not sure what this int32 does yet
|
|
p.writeByte(layer);
|
|
p.writeInt(transition);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Makes the NPCs provided set as scriptable, informing the client to search for js scripts for these NPCs even
|
|
* if they already have entries within the wz files.
|
|
*
|
|
* @param scriptableNpcIds Ids of npcs to enable scripts for.
|
|
* @return a packet which makes the npc's provided scriptable.
|
|
*/
|
|
public static Packet setNPCScriptable(Map<Integer, String> scriptableNpcIds) { // thanks to GabrielSin
|
|
OutPacket p = OutPacket.create(SendOpcode.SET_NPC_SCRIPTABLE);
|
|
p.writeByte(scriptableNpcIds.size());
|
|
scriptableNpcIds.forEach((id, name) -> {
|
|
p.writeInt(id);
|
|
// The client needs a name for the npc conversation, which is displayed under etc when the npc has a quest available.
|
|
p.writeString(name);
|
|
p.writeInt(0); // start time
|
|
p.writeInt(Integer.MAX_VALUE); // end time
|
|
});
|
|
return p;
|
|
}
|
|
|
|
private static Packet MassacreResult(byte nRank, int nIncExp) {
|
|
//CField_MassacreResult__OnMassacreResult @ 0x005617C5
|
|
final OutPacket p = OutPacket.create(SendOpcode.PYRAMID_SCORE); //MASSACRERESULT | 0x009E
|
|
p.writeByte(nRank); //(0 - S) (1 - A) (2 - B) (3 - C) (4 - D) ( Else - Crash )
|
|
p.writeInt(nIncExp);
|
|
return p;
|
|
}
|
|
|
|
|
|
private static Packet Tournament__Tournament(byte nState, byte nSubState) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.TOURNAMENT);
|
|
p.writeByte(nState);
|
|
p.writeByte(nSubState);
|
|
return p;
|
|
}
|
|
|
|
private static Packet Tournament__MatchTable(byte nState, byte nSubState) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.TOURNAMENT_MATCH_TABLE); //Prompts CMatchTableDlg Modal
|
|
return p;
|
|
}
|
|
|
|
private static Packet Tournament__SetPrize(byte bSetPrize, byte bHasPrize, int nItemID1, int nItemID2) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.TOURNAMENT_SET_PRIZE);
|
|
|
|
//0 = "You have failed the set the prize. Please check the item number again."
|
|
//1 = "You have successfully set the prize."
|
|
p.writeByte(bSetPrize);
|
|
|
|
p.writeByte(bHasPrize);
|
|
|
|
if (bHasPrize != 0) {
|
|
p.writeInt(nItemID1);
|
|
p.writeInt(nItemID2);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
private static Packet Tournament__UEW(byte nState) {
|
|
final OutPacket p = OutPacket.create(SendOpcode.TOURNAMENT_UEW);
|
|
|
|
//Is this a bitflag o.o ?
|
|
//2 = "You have reached the finals by default."
|
|
//4 = "You have reached the semifinals by default."
|
|
//8 or 16 = "You have reached the round of %n by default." | Encodes nState as %n ?!
|
|
p.writeByte(nState);
|
|
|
|
return p;
|
|
}
|
|
|
|
}
|