Implemented Kites, PlayerNPCs and C. Shop Surprise & Tweaked login

Added code support for Kites.
Reviewed concurrent access issues with pet autopot.
Addressed PlayerStorage issue where characters would not be properly deregistered from channel PlayerStorage in certain situations.
Implemented harp quest (questid 3314) because of reasons.
Added SFX to signalize KC/NLC subway departing/approaching.
Changed traveling time values to work similarly to GMS.
Properly developed the PlayerNPC feature in the source.
Added autodeployable PlayerNPC system and Hall of Fame.
Solved a glitch with NLC mayor's quiz questline that would allow a player to restart the quiz as many times one would see fit.
Added a custom server flag that allows overwriting the ToT 999 mobs to a new value (technically it doesn't overwrite, rather sets the player at quest start with 999 - n credited mobs).
Fixed permanent pets expiring after a while.
Added code support for Cash Shop Surprise item.
Reviewed login handler system as a whole, protecting many exposed flaws.
Solved a bug with ULTRA_THREE_SNAILS sometimes taking wrong etc shell from inventory.
This commit is contained in:
ronancpl
2018-05-19 14:28:06 -03:00
parent 80b1776ad3
commit fca7b2adaa
5747 changed files with 225411 additions and 12868 deletions
+7 -65
View File
@@ -94,7 +94,7 @@ import server.maps.MapleMiniGame;
import server.maps.MaplePlayerShop;
import server.maps.MaplePlayerShopItem;
import server.maps.MapleSummon;
import server.maps.PlayerNPCs;
import server.life.MaplePlayerNPC;
import server.maps.SavedLocation;
import server.maps.SavedLocationType;
import server.partyquest.MonsterCarnival;
@@ -313,7 +313,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
private int banishMap = -1;
private int banishSp = -1;
private long banishTime = 0;
private MapleCharacter() {
useCS = false;
@@ -5111,6 +5111,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
maxmp = Math.min(30000, maxmp);
if (level == 200) {
exp.set(0);
if(ServerConstants.PLAYERNPC_AUTODEPLOY) MaplePlayerNPC.spawnPlayerNPC(GameConstants.getHallOfFameMapid(job), this);
}
recalcLocalStats();
hp = localmaxhp;
@@ -5226,11 +5227,11 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
} else if (level == 80) {
yellowMessage("You think you are powerful enough? Try facing horntail!");
} else if (level == 85) {
yellowMessage("Did you know? The majority of people who hit level 85 in Solaxia don't live to be 85 years old?");
yellowMessage("Did you know? The majority of people who hit level 85 in HeavenMS don't live to be 85 years old?");
} else if (level == 90) {
yellowMessage("Hey do you like the amusement park? I heard Spooky World is the best theme park around. I heard they sell cute teddy-bears.");
} else if (level == 95) {
yellowMessage("100% of people who hit level 95 in Solaxia don't live to be 95 years old.");
yellowMessage("100% of people who hit level 95 in HeavenMS don't live to be 95 years old.");
} else if (level == 100) {
yellowMessage("Mid-journey so far... You just reached level 100! Now THAT's such a feat, however to manage the 200 you will need even more passion and determination than ever! Good hunting!");
} else if (level == 105) {
@@ -5923,65 +5924,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
maplemount = new MapleMount(this, id, skillid);
}
public void playerNPC(MapleCharacter v, int scriptId) {
int npcId;
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT id FROM playernpcs WHERE ScriptId = ?");
ps.setInt(1, scriptId);
ResultSet rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps = con.prepareStatement("INSERT INTO playernpcs (name, hair, face, skin, x, cy, map, ScriptId, Foothold, rx0, rx1) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
ps.setString(1, v.getName());
ps.setInt(2, v.getHair());
ps.setInt(3, v.getFace());
ps.setInt(4, v.getSkinColor().getId());
ps.setInt(5, getPosition().x);
ps.setInt(6, getPosition().y);
ps.setInt(7, getMapId());
ps.setInt(8, scriptId);
ps.setInt(9, getMap().getFootholds().findBelow(getPosition()).getId());
ps.setInt(10, getPosition().x + 50);
ps.setInt(11, getPosition().x - 50);
ps.executeUpdate();
rs = ps.getGeneratedKeys();
rs.next();
npcId = rs.getInt(1);
ps.close();
ps = con.prepareStatement("INSERT INTO playernpcs_equip (NpcId, equipid, equippos) VALUES (?, ?, ?)");
ps.setInt(1, npcId);
for (Item equip : getInventory(MapleInventoryType.EQUIPPED)) {
int position = Math.abs(equip.getPosition());
if ((position < 12 && position > 0) || (position > 100 && position < 112)) {
ps.setInt(2, equip.getItemId());
ps.setInt(3, equip.getPosition());
ps.addBatch();
}
}
ps.executeBatch();
ps.close();
rs.close();
ps = con.prepareStatement("SELECT * FROM playernpcs WHERE ScriptId = ?");
ps.setInt(1, scriptId);
rs = ps.executeQuery();
rs.next();
PlayerNPCs pn = new PlayerNPCs(rs);
for (Channel channel : Server.getInstance().getChannelsFromWorld(world)) {
MapleMap m = channel.getMapFactory().getMap(getMapId());
m.broadcastMessage(MaplePacketCreator.spawnPlayerNPC(pn));
m.broadcastMessage(MaplePacketCreator.getPlayerNPC(pn));
m.addMapObject(pn);
}
}
ps.close();
rs.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
private void playerDead() {
cancelAllBuffs(false);
dispelDebuffs();
@@ -6840,7 +6782,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
public void sendPolice(int greason, String reason, int duration) {
announce(MaplePacketCreator.sendPolice(String.format("You have been blocked by the#b %s Police for %s.#k", "Solaxia", reason)));
announce(MaplePacketCreator.sendPolice(String.format("You have been blocked by the#b %s Police for %s.#k", "HeavenMS", reason)));
this.isbanned = true;
TimerManager.getInstance().schedule(new Runnable() {
@Override
@@ -7966,7 +7908,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
public void autoban(String reason) {
this.ban(reason);
announce(MaplePacketCreator.sendPolice(String.format("You have been blocked by the#b %s Police for HACK reason.#k", "Solaxia")));
announce(MaplePacketCreator.sendPolice(String.format("You have been blocked by the#b %s Police for HACK reason.#k", "HeavenMS")));
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
+7 -7
View File
@@ -112,7 +112,7 @@ public class MapleClient {
private long lastNpcClick;
private long sessionId;
public MapleClient(MapleAESOFB send, MapleAESOFB receive, IoSession session) {
public MapleClient(MapleAESOFB send, MapleAESOFB receive, IoSession session) {
this.send = send;
this.receive = receive;
this.session = session;
@@ -914,10 +914,9 @@ public class MapleClient {
} catch (final Exception e) {
FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, e);
} finally {
getChannelServer().removePlayer(player);
if (!this.serverTransition) {
worlda.removePlayer(player);
//getChannelServer().removePlayer(player); already being done
player.saveCooldowns();
player.saveToDB(true);
@@ -925,10 +924,11 @@ public class MapleClient {
player.empty(false);
}
player.logOff();
}
else {
player.saveCooldowns();
player.saveToDB();
} else {
getChannelServer().removePlayer(player);
player.saveCooldowns();
player.saveToDB();
}
player = null;
}
+1 -1
View File
@@ -64,7 +64,7 @@ public enum MapleJob {
final int jobid;
final static int maxId = 22; // maxId = (EVAN / 100);
private MapleJob(int id) {
jobid = id;
}
+1 -1
View File
@@ -142,7 +142,7 @@ public class MapleQuestStatus {
}
public void setProgress(int id, String pr) {
progress.put(id, pr);
progress.put(id, pr);
}
public boolean madeProgress() {
+23 -11
View File
@@ -68,6 +68,7 @@ import server.life.MapleLifeFactory;
import server.life.MapleMonster;
import server.life.MapleMonsterInformationProvider;
import server.life.MapleNPC;
import server.life.MaplePlayerNPC;
import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.life.MonsterDropEntry;
@@ -350,6 +351,22 @@ public class Commands {
MapleCharacter player = c.getPlayer();
switch(sub[0]) {
case "wow":
MaplePlayerNPC.multicastSpawnPlayerNPC(player.getMapId(), player.getWorld());
break;
case "out":
MaplePlayerNPC.removeAllPlayerNPC();
break;
case "er":
MaplePlayerNPC.spawnPlayerNPC(player.getMapId(), player);
break;
case "re":
MaplePlayerNPC.removePlayerNPC(player);
break;
case "help":
case "commands":
case "playercommands":
@@ -369,7 +386,7 @@ public class Commands {
case "time":
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone(ServerConstants.TIMEZONE));
player.yellowMessage("Solaxia Server Time: " + dateFormat.format(new Date()));
player.yellowMessage("HeavenMS Server Time: " + dateFormat.format(new Date()));
break;
case "credits":
@@ -2069,10 +2086,6 @@ public class Commands {
}
break;
case "energy":
System.out.println(c.getPlayer().getDojoEnergy());
break;
case "maxenergy":
c.getPlayer().setDojoEnergy(10000);
c.announce(MaplePacketCreator.getEnergy("energy", 10000));
@@ -2599,16 +2612,15 @@ public class Commands {
player.getMap().spawnMonsterOnGroundBelow(monster, player.getPosition());
break;
/*
case "playernpc":
if (sub.length < 3){
player.yellowMessage("Syntax: !playernpc <playername> <npcid>");
player.yellowMessage("Syntax: !playernpc <playername>");
break;
}
player.playerNPC(c.getChannelServer().getPlayerStorage().getCharacterByName(sub[1]), Integer.parseInt(sub[2]));
MaplePlayerNPC.spawnPlayerNPC(player.getMapId(), player.getPosition(), c.getChannelServer().getPlayerStorage().getCharacterByName(sub[1]));
break;
*/
default:
return false;
@@ -2930,7 +2942,7 @@ public class Commands {
else return true;
}
public static boolean executeSolaxiaPlayerCommand(MapleClient c, String[] sub, char heading) {
public static boolean executeHeavenMSPlayerCommand(MapleClient c, String[] sub, char heading) {
Channel cserv = c.getChannelServer();
Server srv = Server.getInstance();
+2 -1
View File
@@ -26,6 +26,7 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import server.MapleItemInformationProvider;
public class Item implements Comparable<Item> {
@@ -153,7 +154,7 @@ public class Item implements Comparable<Item> {
}
public void setExpiration(long expire) {
this.expiration = expire;
this.expiration = !ItemConstants.isPermanentItem(id) ? expire : ItemConstants.isPet(id) ? Long.MAX_VALUE : -1;
}
public int getSN() {
+8
View File
@@ -550,4 +550,12 @@ public class MapleInventory implements Iterable<Item> {
lock.unlock();
}
}
public void lockInventory() {
lock.lock();
}
public void unlockInventory() {
lock.unlock();
}
}
+143
View File
@@ -1,5 +1,7 @@
package constants;
import java.util.HashMap;
import java.util.Map;
import client.MapleJob;
import constants.skills.Aran;
import server.maps.MapleMap;
@@ -11,6 +13,9 @@ import server.quest.MapleQuest;
* @author Ronan
*/
public class GameConstants {
public static String[] WORLD_NAMES = {"Scania", "Bera", "Broa", "Windia", "Khaini", "Bellocan", "Mardia", "Kradia", "Yellonde", "Demethos", "Galicia", "El Nido", "Zenith", "Arcenia", "Kastia", "Judis", "Plana", "Kalluna", "Stius", "Croa", "Medere"};
final static Map<Integer, String> jobNames = new HashMap<>();
public static final int[] OWL_DATA = new int[]{1082002, 2070005, 2070006, 1022047, 1102041, 2044705, 2340000, 2040017, 1092030, 2040804};
// Ronan's rates upgrade system
@@ -18,6 +23,16 @@ public class GameConstants {
private static final int[] MESO_RATE_GAIN = {1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105};
private static final int[] EXP_RATE_GAIN = {1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610}; //fibonacci :3
public static boolean availableDeveloperRoom = false;
public static void setAvailableDeveloperRoom() {
availableDeveloperRoom = true;
}
public static boolean canEnterDeveloperRoom() {
return availableDeveloperRoom;
}
public static int getPlayerBonusDropRate(int slot) {
return(DROP_RATE_GAIN[slot]);
}
@@ -63,6 +78,130 @@ public class GameConstants {
330000, 340000, 350000, 360000, 370000, 380000, 390000, 400000, 410000, 420000, 430000, 440000, 450000, 460000, 470000, 480000, 490000, 500000, 510000, 520000,
530000, 550000, 570000, 590000, 610000, 630000, 650000, 670000, 690000, 710000, 730000, 750000, 770000, 790000, 810000, 830000, 850000, 870000, 890000, 910000};
public static String getJobName(int jobid) {
String name = jobNames.get(jobid);
if(name == null) {
MapleJob job = MapleJob.getById(jobid);
if(job != null) {
name = job.name().toLowerCase();
name = name.replaceAll("[*0-9]", "");
name = name.substring(0, 1).toUpperCase() + name.substring(1);
} else {
name = "";
}
jobNames.put(jobid, name);
}
return name;
}
public static boolean isHallOfFameMap(int mapid) {
switch(mapid) {
case 102000004: // warrior
case 101000004: // magician
case 100000204: // bowman
case 103000008: // thief
case 120000105: // pirate
case 130000100: // cygnus
case 130000101: // other cygnus
case 130000110: // cygnus 2nd floor
case 130000120: // cygnus 3rd floor (beginners)
case 140010110: // aran
return true;
default:
return false;
}
}
public static byte getHallOfFameBranch(MapleJob job, int mapid) {
if(!isHallOfFameMap(mapid)) {
return (byte) (26 + 4 * (mapid / 100000000)); // custom, 400 pnpcs available per continent
}
if(job.isA(MapleJob.WARRIOR)) {
return 10;
} else if(job.isA(MapleJob.MAGICIAN)) {
return 11;
} else if(job.isA(MapleJob.BOWMAN)) {
return 12;
} else if(job.isA(MapleJob.THIEF)) {
return 13;
} else if(job.isA(MapleJob.PIRATE)) {
return 14;
} else if(job.isA(MapleJob.DAWNWARRIOR1)) {
return 15;
} else if(job.isA(MapleJob.BLAZEWIZARD1)) {
return 16;
} else if(job.isA(MapleJob.WINDARCHER1)) {
return 17;
} else if(job.isA(MapleJob.NIGHTWALKER1)) {
return 18;
} else if(job.isA(MapleJob.THUNDERBREAKER1)) {
return 19;
} else if(job.isA(MapleJob.ARAN1)) {
return 20;
} else if(job.isA(MapleJob.EVAN1)) {
return 21;
} else if(job.isA(MapleJob.BEGINNER)) {
return 22;
} else if(job.isA(MapleJob.NOBLESSE)) {
return 23;
} else if(job.isA(MapleJob.LEGEND)) {
return 24;
} else {
return 25;
}
}
public static int getOverallJobRankByScriptId(int scriptId) {
int branch = (scriptId / 100) % 100;
if(branch < 26) {
return (scriptId % 100) + 1;
} else {
return ((scriptId - 2600) % 400) + 1;
}
}
public static boolean canPnpcBranchUseScriptId(byte branch, int scriptId) {
scriptId /= 100;
scriptId %= 100;
if(branch < 26) {
return branch == scriptId;
} else {
return scriptId >= branch && scriptId < branch + 4;
}
}
public static int getHallOfFameMapid(MapleJob job) {
int jobid = job.getId();
if(isCygnus(jobid)) {
return 130000100;
} else if(isAran(jobid)) {
return 140010110;
} else {
if(job.isA(MapleJob.WARRIOR)) {
return 102000004;
} else if(job.isA(MapleJob.MAGICIAN)) {
return 101000004;
} else if(job.isA(MapleJob.BOWMAN)) {
return 100000204;
} else if(job.isA(MapleJob.THIEF)) {
return 103000008;
} else if(job.isA(MapleJob.PIRATE)) {
return 120000105;
} else {
return 130000110; // beginner explorers are allotted with the Cygnus, available map lul
}
}
}
public static int getJobBranch(MapleJob job) {
int jobid = job.getId();
@@ -125,6 +264,10 @@ public class GameConstants {
return Aran.HIDDEN_FULL_DOUBLE == skill || Aran.HIDDEN_FULL_TRIPLE == skill || Aran.HIDDEN_OVER_DOUBLE == skill || Aran.HIDDEN_OVER_TRIPLE == skill;
}
public static boolean isCygnus(final int job) {
return job / 1000 == 1;
}
public static boolean isAran(final int job) {
return job == 2000 || (job >= 2100 && job <= 2112);
}
+14
View File
@@ -22,6 +22,8 @@
package constants;
import client.inventory.MapleInventoryType;
import java.util.HashSet;
import java.util.Set;
import java.util.HashMap;
import java.util.Map;
@@ -42,7 +44,15 @@ public final class ItemConstants {
public final static int ACCOUNT_SHARING = 0x100;
public final static boolean EXPIRING_ITEMS = true;
public final static Set<Integer> permanentItemids = new HashSet<>();
static {
int[] pi = {5000060, 5000100, 5000101, 5000102}; // i ain't going to open one gigantic itemid cache just for 4 perma itemids, no way!
for(int i : pi) {
permanentItemids.add(i);
}
}
public static int getFlagByInt(int type) {
if (type == 128) {
return PET_COME;
@@ -97,6 +107,10 @@ public final class ItemConstants {
return ServerConstants.USE_ERASE_PET_ON_EXPIRATION || itemId == 5000054;
}
public static boolean isPermanentItem(int itemId) {
return permanentItemids.contains(itemId);
}
public static boolean isNewYearCardEtc(int itemId) {
return itemId / 10000 == 430;
}
+16 -6
View File
@@ -13,9 +13,8 @@ public class ServerConstants {
public static String DB_PASS = "";
public static final boolean DB_CONNECTION_POOL = true; //Installs a connection pool to hub DB connections. Set false to default.
//World And Version
//Server Version
public static short VERSION = 83;
public static String[] WORLD_NAMES = {"Scania", "Bera", "Broa", "Windia", "Khaini", "Bellocan", "Mardia", "Kradia", "Yellonde", "Demethos", "Galicia", "El Nido", "Zenith", "Arcenia", "Kastia", "Judis", "Plana", "Kalluna", "Stius", "Croa", "Medere"};
//Login Configuration
public static final int CHANNEL_LOAD = 100; //Max players per channel (limit actually used to calculate the World server capacity).
@@ -93,17 +92,19 @@ public class ServerConstants {
public static final boolean BLOCK_GENERATE_CASH_ITEM = false; //Prevents creation of cash items with the item/drop command.
//Miscellaneous Configuration
public static final byte MIN_UNDERLEVEL_TO_EXP_GAIN = 5; //Characters are unable to get EXP from a mob if their level are under this threshold, only if "USE_ENFORCE_MOB_LEVEL_RANGE" is enabled. For bosses, this attribute is doubled.
public static final byte MAX_MONITORED_BUFFSTATS = 5; //Limits accounting for "dormant" buff effects, that should take place when stronger stat buffs expires.
public static String TIMEZONE = "-GMT3";
public static boolean USE_UNITPRICE_WITH_COMMA = true; //Set this accordingly with the layout of the unitPrices on Item.wz XML's, whether it's using commas or dots to represent fractions.
public static final byte MIN_UNDERLEVEL_TO_EXP_GAIN = 5; //Characters are unable to get EXP from a mob if their level are under this threshold, only if "USE_ENFORCE_MOB_LEVEL_RANGE" is enabled. For bosses, this attribute is doubled.
public static final byte MAX_MONITORED_BUFFSTATS = 5; //Limits accounting for "dormant" buff effects, that should take place when stronger stat buffs expires.
public static final int MAX_AP = 32767; //Max AP allotted on the auto-assigner.
public static final int MAX_EVENT_LEVELS = 8; //Event has different levels of rewarding system.
public static final long BLOCK_NPC_RACE_CONDT = (long)(0.5 * 1000); //Time the player client must wait before reopening a conversation with an NPC.
public static final long PET_LOOT_UPON_ATTACK = (long)(0.7 * 1000); //Time the pet must wait before trying to pick items up.
public static boolean USE_UNITPRICE_WITH_COMMA = true; //Set this accordingly with the layout of the unitPrices on Item.wz XML's, whether it's using commas or dots to represent fractions.
public static String TIMEZONE = "-GMT3";
public static final int TOT_MOB_QUEST_REQUIREMENT = 0; //Overwrites old 999-mobs requirement for the ToT questline with new requirement value, set 0 for default.
//Dangling Items/Locks Configuration
public static final int ITEM_EXPIRE_TIME = 3 * 60 * 1000; //Time before items start disappearing. Recommended to be set up to 3 minutes.
public static final int KITE_EXPIRE_TIME = 60 * 60 * 1000; //Time before new year (cash item) kite disappears.
public static final int ITEM_MONITOR_TIME = 5 * 60 * 1000; //Interval between item monitoring tasks on maps, which checks for dangling (null) item objects on the map item history.
public static final int LOCK_MONITOR_TIME = 30 * 1000; //Waiting time for a lock to be released. If it reach timed out, a critical server deadlock has made present.
public static final int ITEM_EXPIRE_CHECK = 10 * 1000; //Interval between item expiring tasks on maps, which checks and makes disappear expired items.
@@ -145,6 +146,15 @@ public class ServerConstants {
public static final byte CHAIR_EXTRA_HEAL_HP = 70; //Each chair extra heal proc increasing HP.
public static final byte CHAIR_EXTRA_HEAL_MP = 42; //Each chair extra heal proc increasing MP.
//Player NPC Configuration
public static final int PLAYERNPC_INITIAL_X = 262; //Map frame width for putting PlayerNPCs.
public static final int PLAYERNPC_INITIAL_Y = 262; //Map frame height for putting PlayerNPCs.
public static final int PLAYERNPC_AREA_X = 320; //Initial width gap between PlayerNPCs.
public static final int PLAYERNPC_AREA_Y = 160; //Initial height gap between PlayerNPCs.
public static final int PLAYERNPC_AREA_STEPS = 4; //Max number of times gap is shortened to comport PlayerNPCs
public static final boolean PLAYERNPC_ORGANIZE_AREA = true; //Automatically rearranges PlayerNPCs on the map if there is no space set the new NPC. Current distance gap between NPCs is decreased to solve this issue.
public static final boolean PLAYERNPC_AUTODEPLOY = true; //Makes PlayerNPC automatically deployed on the Hall of Fame at the instant one reaches max level. If false, eligible players must talk to 1st job instructor to deploy a NPC.
//Pet Auto-Pot Configuration
public static final boolean USE_EQUIPS_ON_AUTOPOT = true; //Player MaxHP and MaxMP check values on autopot handler will be updated by the HP/MP bonuses on equipped items.
public static final double PET_AUTOHP_RATIO = 0.99; //Will automatically consume potions until given ratio of the MaxHP/MaxMP is reached.
+1
View File
@@ -227,6 +227,7 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.USE_SOLOMON_ITEM, new UseSolomonHandler());
registerHandler(RecvOpcode.USE_GACHA_EXP, new UseGachaExpHandler());
registerHandler(RecvOpcode.NEW_YEAR_CARD_REQUEST, new NewYearCardHandler());
registerHandler(RecvOpcode.CASHSHOP_SURPRISE, new CashShopSurpriseHandler());
registerHandler(RecvOpcode.USE_ITEM_REWARD, new ItemRewardHandler());
registerHandler(RecvOpcode.USE_REMOTE, new RemoteGachaponHandler());
registerHandler(RecvOpcode.ACCEPT_FAMILY, new AcceptFamilyHandler());
+1
View File
@@ -147,6 +147,7 @@ public enum RecvOpcode {
USE_SOLOMON_ITEM(0x9D),
USE_GACHA_EXP(0x9E),
NEW_YEAR_CARD_REQUEST(0x9F),
CASHSHOP_SURPRISE(0xA1),
CLICK_GUIDE(0xA2),
ARAN_COMBO_COUNTER(0xA3),
MOVE_PET(0xA7),
+12 -2
View File
@@ -268,8 +268,9 @@ public enum SendOpcode {
UPDATE_HIRED_MERCHANT(0x10B),
DROP_ITEM_FROM_MAPOBJECT(0x10C),
REMOVE_ITEM_FROM_MAP(0x10D),
KITE_MESSAGE(0x10E),
KITE(0x10F),
CANNOT_SPAWN_KITE(0x10E),
SPAWN_KITE(0x10F),
REMOVE_KITE(0x110),
SPAWN_MIST(0x111),
REMOVE_MIST(0x112),
SPAWN_DOOR(0x113),
@@ -326,6 +327,15 @@ public enum SendOpcode {
QUERY_CASH_RESULT(0x144),
CASHSHOP_OPERATION(0x145),
//check whether the character's name is ok.(0x146),
//this name can be used, press ok if you wish.(0x147),
//rule for character name change (needed lots of bytes)(0x148),
//rule for character transfer(0x14A),
//cannot receive gacha stamps(0x14B),
CASHSHOP_GACHAPON_STAMP_RESULT(0x14C),
CASHSHOP_CASH_ITEM_GACHAPON_RESULT(0x14D),
CASHSHOP_CASH_GACHAPON_OPEN_RESULT(0x14E),
KEYMAP(0x14F),
AUTO_HP_POT(0x150),
AUTO_MP_POT(0x151),
+79 -12
View File
@@ -27,11 +27,13 @@ import net.server.worker.RankingWorker;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.Security;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.HashMap;
@@ -43,7 +45,6 @@ import java.util.Properties;
import java.util.Set;
import tools.locks.MonitoredReentrantLock;
import java.util.concurrent.locks.Lock;
import net.MapleServerHandler;
import net.mina.MapleCodecFactory;
import net.server.channel.Channel;
@@ -60,23 +61,23 @@ import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import server.CashShop.CashItemFactory;
import server.TimerManager;
import tools.DatabaseConnection;
import tools.FilePrinter;
import tools.Pair;
import client.MapleClient;
import client.MapleCharacter;
import client.SkillFactory;
import client.newyear.NewYearCardRecord;
import constants.ItemConstants;
import constants.GameConstants;
import constants.ServerConstants;
import java.security.Security;
import java.util.Calendar;
import net.server.audit.ThreadTracker;
import server.CashShop.CashItemFactory;
import server.TimerManager;
import server.life.MaplePlayerNPCFactory;
import server.quest.MapleQuest;
import tools.locks.MonitoredLockType;
import tools.AutoJCE;
import tools.DatabaseConnection;
import tools.FilePrinter;
import tools.Pair;
import tools.locks.MonitoredLockType;
public class Server {
private static final Set<Integer> activeFly = new HashSet<>();
@@ -89,6 +90,7 @@ public class Server {
private final Properties subnetInfo = new Properties();
private static Server instance = null;
private final Map<Integer, Set<Integer>> accountChars = new HashMap<>();
private final Map<Integer, Integer> worldChars = new HashMap<>();
private final Map<String, Integer> transitioningChars = new HashMap<>();
private List<Pair<Integer, String>> worldRecommendedList = new LinkedList<>();
private final Map<Integer, MapleGuild> guilds = new HashMap<>(100);
@@ -132,6 +134,25 @@ public class Server {
return newyears.remove(cardid);
}
private void loadPlayerNpcMapStepFromDb() {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM playernpcs_field");
ResultSet rs = ps.executeQuery();
while(rs.next()) {
int world = rs.getInt("world"), map = rs.getInt("map"), step = rs.getInt("step");
worlds.get(world).setPlayerNpcMapStep(map, step, true);
}
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/*
public void removeChannel(int worldid, int channel) { //lol don't!
channels.remove(channel);
@@ -365,7 +386,7 @@ public class Server {
if(ServerConstants.USE_THREAD_TRACKER) ThreadTracker.getInstance().registerThreadTrackerTask();
try {
Integer worldCount = Math.min(ServerConstants.WORLD_NAMES.length, Integer.parseInt(p.getProperty("worlds")));
Integer worldCount = Math.min(GameConstants.WORLD_NAMES.length, Integer.parseInt(p.getProperty("worlds")));
for (int i = 0; i < worldCount; i++) {
System.out.println("Starting world " + i);
@@ -389,6 +410,9 @@ public class Server {
world.setServerMessage(p.getProperty("servermessage" + i));
System.out.println("Finished loading world " + i + "\r\n");
}
MaplePlayerNPCFactory.loadFactoryMetadata();
loadPlayerNpcMapStepFromDb();
} catch (Exception e) {
e.printStackTrace();//For those who get errors
System.out.println("Error in moople.ini, start CreateINI.bat to re-make the file.");
@@ -579,7 +603,7 @@ public class Server {
if(mc != null) {
mc.setMGC(g.getMGC(mc.getId()));
if(g.getMGC(mc.getId()) == null) System.out.println("null for " + mc.getName() + " when loading " + id);
if(g.getMGC(mc.getId()) == null) System.out.println("null for " + mc.getName() + " when loading guild " + id);
g.getMGC(mc.getId()).setCharacter(mc);
g.setOnline(mc.getId(), true, mc.getClient().getChannel());
}
@@ -824,7 +848,7 @@ public class Server {
}
}
public void createCharacterid(Integer accountid, Integer chrid) {
public void createCharacterid(Integer accountid, Integer chrid, Integer world) {
lgnLock.lock();
try {
Set<Integer> accChars = accountChars.get(accountid);
@@ -834,6 +858,7 @@ public class Server {
}
accChars.add(chrid);
worldChars.put(chrid, world);
} finally {
lgnLock.unlock();
}
@@ -846,6 +871,48 @@ public class Server {
if(accChars != null) {
accChars.remove(chrid);
}
worldChars.remove(chrid);
} finally {
lgnLock.unlock();
}
}
private static int getCharacterWorldFromDB(int chrid) {
int world = -1;
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT world FROM characters WHERE id = ?")) {
ps.setInt(1, chrid);
try (ResultSet rs = ps.executeQuery()) {
if(rs.next()) {
world = rs.getInt("world");
}
}
}
con.close();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
return world;
}
public int getCharacterWorld(Integer chrid) {
lgnLock.lock();
try {
Integer worldid = worldChars.get(chrid);
if(worldid == null) {
worldid = getCharacterWorldFromDB(chrid);
worldChars.put(chrid, worldid);
}
return worldid;
} finally {
lgnLock.unlock();
}
+2 -2
View File
@@ -191,8 +191,8 @@ public final class Channel {
return players;
}
public void removePlayer(MapleCharacter chr) {
players.removePlayer(chr.getId());
public boolean removePlayer(MapleCharacter chr) {
return players.removePlayer(chr.getId()) != null;
}
public int getChannelCapacity() {
@@ -445,29 +445,37 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
AutobanFactory.FIX_DAMAGE.autoban(player, String.valueOf(totDamageToOneMonster) + " damage");
}
if(ServerConstants.USE_ULTRA_THREE_SNAILS) {
AbstractPlayerInteraction api = player.getClient().getAbstractPlayerInteraction();
int shellId;
switch(totDamageToOneMonster) {
case 10:
shellId = 4000019;
break;
case 25:
shellId = 4000000;
break;
default:
shellId = 4000016;
}
if(api.haveItem(shellId, 1)) {
api.gainItem(shellId, (short)-1, false);
totDamageToOneMonster *= player.getLevel();
}
else {
player.dropMessage(5, "You ran out of shells to activate the hidden power of Three Snails.");
int threeSnailsId = player.getJobType() * 10000000 + 1000;
if(attack.skill == threeSnailsId) {
if(ServerConstants.USE_ULTRA_THREE_SNAILS) {
int skillLv = player.getSkillLevel(threeSnailsId);
if(skillLv > 0) {
AbstractPlayerInteraction api = player.getClient().getAbstractPlayerInteraction();
int shellId;
switch(skillLv) {
case 1:
shellId = 4000019;
break;
case 2:
shellId = 4000000;
break;
default:
shellId = 4000016;
}
if(api.haveItem(shellId, 1)) {
api.gainItem(shellId, (short) -1, false);
totDamageToOneMonster *= player.getLevel();
} else {
player.dropMessage(5, "You have ran out of shells to activate the hidden power of Three Snails.");
}
} else {
totDamageToOneMonster = 0;
}
}
}
}
@@ -73,7 +73,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
if (action == 0x03) { // Item
Item item = cItem.toItem();
cs.addToInventory(item);
cs.addToInventory(item);
c.announce(MaplePacketCreator.showBoughtCashItem(item, c.getAccID()));
} else { // Package
List<Item> cashPackage = CashItemFactory.getPackage(cItem.getItemId());
@@ -0,0 +1,48 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
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 net.server.channel.handlers;
import client.MapleClient;
import client.inventory.Item;
import net.AbstractMaplePacketHandler;
import server.CashShop;
import tools.data.input.SeekableLittleEndianAccessor;
import tools.MaplePacketCreator;
/**
*
* @author RonanLana
*/
public class CashShopSurpriseHandler extends AbstractMaplePacketHandler {
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
CashShop cs = c.getPlayer().getCashShop();
if(cs.isOpened()) {
Item cssItem = cs.openCashShopSurprise();
if(cssItem != null) {
c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xA4));
} else {
c.announce(MaplePacketCreator.showCashShopMessage((byte) 0x00));
}
}
}
}
@@ -59,7 +59,7 @@ public final class GeneralChatHandler extends net.AbstractMaplePacketHandler {
String[] sp = s.split(" ");
sp[0] = sp[0].toLowerCase().substring(1);
if(Commands.executeSolaxiaPlayerCommand(c, sp, heading)) {
if(Commands.executeHeavenMSPlayerCommand(c, sp, heading)) {
String command = "";
for (String used : sp) {
command += used + " ";
@@ -28,7 +28,7 @@ import net.AbstractMaplePacketHandler;
import scripting.npc.NPCScriptManager;
import server.life.MapleNPC;
import server.maps.MapleMapObject;
import server.maps.PlayerNPCs;
import server.life.MaplePlayerNPC;
import tools.FilePrinter;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
@@ -78,8 +78,14 @@ public final class NPCTalkHandler extends AbstractMaplePacketHandler {
}
}
}
} else if (obj instanceof PlayerNPCs) {
NPCScriptManager.getInstance().start(c, ((PlayerNPCs) obj).getId(), null);
} else if (obj instanceof MaplePlayerNPC) {
MaplePlayerNPC pnpc = (MaplePlayerNPC) obj;
if(pnpc.getScriptId() < 9977777) {
NPCScriptManager.getInstance().start(c, pnpc.getScriptId(), "rank_user", null);
} else {
NPCScriptManager.getInstance().start(c, pnpc.getScriptId(), null);
}
}
}
}
@@ -25,6 +25,7 @@ import client.MapleClient;
import client.MapleCharacter;
import client.inventory.Equip;
import client.inventory.Item;
import client.inventory.MapleInventory;
import client.inventory.MapleInventoryType;
import net.AbstractMaplePacketHandler;
import server.MapleInventoryManipulator;
@@ -69,67 +70,74 @@ public final class PetAutoPotHandler extends AbstractMaplePacketHandler {
itemId = slea.readInt();
MapleCharacter chr = c.getPlayer();
toUse = chr.getInventory(MapleInventoryType.USE).getItem(slot);
MapleInventory useInv = chr.getInventory(MapleInventoryType.USE);
if(toUse != null) {
if (toUse.getItemId() != itemId) {
c.announce(MaplePacketCreator.enableActions());
return;
}
toUseList = null;
// from now on, toUse becomes the "cursor" for the current pot being used
if(toUse.getQuantity() <= 0) {
if(!cursorOnNextAvailablePot(chr)) {
useInv.lockInventory();
try {
toUse = useInv.getItem(slot);
if(toUse != null) {
if (toUse.getItemId() != itemId) {
c.announce(MaplePacketCreator.enableActions());
return;
}
}
MapleStatEffect stat = MapleItemInformationProvider.getInstance().getItemEffect(toUse.getItemId());
hasHpGain = stat.getHp() > 0 || stat.getHpRate() > 0.0;
hasMpGain = stat.getMp() > 0 || stat.getMpRate() > 0.0;
// contabilize the HP and MP gains from equipments on one's effective MaxHP/MaxMP
Pair<Short, Short> maxHpMp = calcEffectivePool(chr);
maxHp = maxHpMp.left;
maxMp = maxHpMp.right;
incHp = stat.getHp();
if(incHp <= 0 && hasHpGain) incHp = (short)(maxHp * stat.getHpRate());
incMp = stat.getMp();
if(incMp <= 0 && hasMpGain) incMp = (short)(maxMp * stat.getMpRate());
curHp = chr.getHp();
curMp = chr.getMp();
//System.out.println("\n-------------------\n");
while(true) {
do {
MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.USE, slot, (short) 1, false);
stat.applyTo(chr);
curHp += incHp;
curMp += incMp;
//System.out.println();
//System.out.println("hp: " + hasHpGain + " hpgain " + incHp + " player hp " + curHp + " maxhp " + maxHp);
//System.out.println("mp: " + hasMpGain + " mpgain " + incMp + " player mp " + curMp + " maxmp " + maxMp);
//System.out.println("redo? " + (shouldReusePot() && toUse.getQuantity() > 0));
} while(shouldReusePot() && toUse.getQuantity() > 0);
if(toUse.getQuantity() == 0 && shouldReusePot()) {
// depleted out the current slot, fetch for more
toUseList = null;
// from now on, toUse becomes the "cursor" for the current pot being used
if(toUse.getQuantity() <= 0) {
if(!cursorOnNextAvailablePot(chr)) {
break; // no more pots available
c.announce(MaplePacketCreator.enableActions());
return;
}
}
MapleStatEffect stat = MapleItemInformationProvider.getInstance().getItemEffect(toUse.getItemId());
hasHpGain = stat.getHp() > 0 || stat.getHpRate() > 0.0;
hasMpGain = stat.getMp() > 0 || stat.getMpRate() > 0.0;
// contabilize the HP and MP gains from equipments on one's effective MaxHP/MaxMP
Pair<Short, Short> maxHpMp = calcEffectivePool(chr);
maxHp = maxHpMp.left;
maxMp = maxHpMp.right;
incHp = stat.getHp();
if(incHp <= 0 && hasHpGain) incHp = (short)(maxHp * stat.getHpRate());
incMp = stat.getMp();
if(incMp <= 0 && hasMpGain) incMp = (short)(maxMp * stat.getMpRate());
curHp = chr.getHp();
curMp = chr.getMp();
//System.out.println("\n-------------------\n");
while(true) {
do {
MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.USE, slot, (short) 1, false);
stat.applyTo(chr);
curHp += incHp;
curMp += incMp;
//System.out.println();
//System.out.println("hp: " + hasHpGain + " hpgain " + incHp + " player hp " + curHp + " maxhp " + maxHp);
//System.out.println("mp: " + hasMpGain + " mpgain " + incMp + " player mp " + curMp + " maxmp " + maxMp);
//System.out.println("redo? " + (shouldReusePot() && toUse.getQuantity() > 0));
} while(shouldReusePot() && toUse.getQuantity() > 0);
if(toUse.getQuantity() == 0 && shouldReusePot()) {
// depleted out the current slot, fetch for more
if(!cursorOnNextAvailablePot(chr)) {
break; // no more pots available
}
} else {
break; // gracefully finished it's job, quit the loop
}
} else {
break; // gracefully finished it's job, quit the loop
}
}
} finally {
useInv.unlockInventory();
}
}
@@ -33,6 +33,7 @@ import client.inventory.Item;
import client.inventory.MapleInventoryType;
import client.inventory.MaplePet;
import client.inventory.ModifyInventory;
import constants.GameConstants;
import constants.ItemConstants;
import constants.ServerConstants;
@@ -51,6 +52,7 @@ import server.MapleShopFactory;
import server.TimerManager;
import server.maps.AbstractMapleMapObject;
import server.maps.MaplePlayerShopItem;
import server.maps.MapleKite;
import server.maps.MapleMap;
import server.maps.MapleTVEffect;
import tools.MaplePacketCreator;
@@ -82,7 +84,49 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.enableActions());
return;
}
if (itemType == 505) { // AP/SP reset
if (itemType == 504) { // vip teleport rock
String error1 = "Either the player could not be found or you were trying to teleport to an illegal location.";
boolean vip = slea.readByte() == 1;
remove(c, itemId);
if (!vip) {
int mapId = slea.readInt();
if (c.getChannelServer().getMapFactory().getMap(mapId).getForcedReturnId() == 999999999) {
player.changeMap(c.getChannelServer().getMapFactory().getMap(mapId));
} else {
MapleInventoryManipulator.addById(c, itemId, (short) 1);
c.getPlayer().dropMessage(1, error1);
c.announce(MaplePacketCreator.enableActions());
}
} else {
String name = slea.readMapleAsciiString();
MapleCharacter victim = c.getChannelServer().getPlayerStorage().getCharacterByName(name);
boolean success = false;
if (victim != null) {
MapleMap target = victim.getMap();
if (c.getChannelServer().getMapFactory().getMap(victim.getMapId()).getForcedReturnId() == 999999999 || victim.getMapId() < 100000000) {
if (victim.gmLevel() <= player.gmLevel()) {
if (itemId == 5041000 || victim.getMapId() / player.getMapId() == 1) { //viprock & same continent
player.changeMap(target, target.findClosestPlayerSpawnpoint(victim.getPosition()));
success = true;
} else {
player.dropMessage(1, "You cannot teleport between continents with this teleport rock.");
}
} else {
player.dropMessage(1, error1);
}
} else {
player.dropMessage(1, "You cannot teleport to this map.");
}
} else {
player.dropMessage(1, "Player could not be found in this channel.");
}
if (!success) {
MapleInventoryManipulator.addById(c, itemId, (short) 1);
c.announce(MaplePacketCreator.enableActions());
}
}
} else if (itemType == 505) { // AP/SP reset
if(!player.isAlive()) {
c.announce(MaplePacketCreator.enableActions());
return;
@@ -368,9 +412,15 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler {
break;
}
remove(c, itemId);
} else if (itemType == 508) { //graduation banner
slea.readMapleAsciiString(); // message, separated by 0A for lines
c.announce(MaplePacketCreator.enableActions());
} else if (itemType == 508) { // graduation banner, thanks to tmskdl12
MapleKite kite = new MapleKite(c.getPlayer(), slea.readMapleAsciiString(), itemId);
if (!GameConstants.isFreeMarketRoom(c.getPlayer().getMapId())) {
c.getPlayer().getMap().spawnKite(kite);
remove(c, itemId);
} else {
c.announce(MaplePacketCreator.sendCannotSpawnKite());
}
} else if (itemType == 509) {
String sendTo = slea.readMapleAsciiString();
String msg = slea.readMapleAsciiString();
@@ -408,47 +458,6 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler {
player.getMap().broadcastMessage(player, MaplePacketCreator.changePetName(player, newName, 1), true);
c.announce(MaplePacketCreator.enableActions());
remove(c, itemId);
} else if (itemType == 504) { // vip teleport rock
String error1 = "Either the player could not be found or you were trying to teleport to an illegal location.";
boolean vip = slea.readByte() == 1;
remove(c, itemId);
if (!vip) {
int mapId = slea.readInt();
if (c.getChannelServer().getMapFactory().getMap(mapId).getForcedReturnId() == 999999999) {
player.changeMap(c.getChannelServer().getMapFactory().getMap(mapId));
} else {
MapleInventoryManipulator.addById(c, itemId, (short) 1);
c.getPlayer().dropMessage(1, error1);
c.announce(MaplePacketCreator.enableActions());
}
} else {
String name = slea.readMapleAsciiString();
MapleCharacter victim = c.getChannelServer().getPlayerStorage().getCharacterByName(name);
boolean success = false;
if (victim != null) {
MapleMap target = victim.getMap();
if (c.getChannelServer().getMapFactory().getMap(victim.getMapId()).getForcedReturnId() == 999999999 || victim.getMapId() < 100000000) {
if (victim.gmLevel() <= player.gmLevel()) {
if (itemId == 5041000 || victim.getMapId() / player.getMapId() == 1) { //viprock & same continent
player.changeMap(target, target.findClosestPlayerSpawnpoint(victim.getPosition()));
success = true;
} else {
player.dropMessage(1, "You cannot teleport between continents with this teleport rock.");
}
} else {
player.dropMessage(1, error1);
}
} else {
player.dropMessage(1, "You cannot teleport to this map.");
}
} else {
player.dropMessage(1, "Player could not be found in this channel.");
}
if (!success) {
MapleInventoryManipulator.addById(c, itemId, (short) 1);
c.announce(MaplePacketCreator.enableActions());
}
}
} else if (itemType == 520) {
player.gainMeso(ii.getMeso(itemId), true, false, true);
remove(c, itemId);
@@ -35,11 +35,11 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
c.updateMacs(macs);
c.updateHWID(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
c.getSession().close(true);
return;
@@ -51,6 +51,7 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
return;
}
c.setWorld(server.getCharacterWorld(charId));
if(c.getWorldServer().isWorldCapacityFull()) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
@@ -16,11 +16,11 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
String pic = slea.readMapleAsciiString();
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
c.updateMacs(macs);
c.updateHWID(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
c.getSession().close(true);
return;
@@ -33,6 +33,7 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
}
if (c.checkPic(pic)) {
c.setWorld(server.getCharacterWorld(charId));
if(c.getWorldServer().isWorldCapacityFull()) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
@@ -136,7 +136,7 @@ public final class CreateCharHandler extends AbstractMaplePacketHandler {
}
c.announce(MaplePacketCreator.addNewCharEntry(newchar));
Server.getInstance().createCharacterid(newchar.getAccountID(), newchar.getId());
Server.getInstance().createCharacterid(newchar.getAccountID(), newchar.getId(), newchar.getWorld());
Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.sendYellowTip("[NEW CHAR]: " + c.getAccountName() + " has created a new character with IGN " + name));
}
}
@@ -23,6 +23,7 @@ package net.server.handlers.login;
import client.MapleClient;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
@@ -35,23 +36,39 @@ public final class PickCharHandler extends AbstractMaplePacketHandler {
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
int charId = slea.readInt();
int world = slea.readInt();//Wuuu? ):
c.setWorld(world);
slea.readInt(); // please don't let the client choose which world they should login
Server server = Server.getInstance();
if(!server.haveCharacterid(c.getAccID(), charId)) {
c.getSession().close(true);
return;
}
c.setWorld(server.getCharacterWorld(charId));
if(c.getWorldServer().isWorldCapacityFull()) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
String macs = slea.readMapleAsciiString();
c.updateMacs(macs);
if (c.hasBannedMac()) {
c.getSession().close(true);
return;
}
try {
c.setChannel(Randomizer.nextInt(Server.getInstance().getWorld(world).getChannels().size()));
c.setChannel(Randomizer.nextInt(c.getWorldServer().getChannels().size()));
} catch (Exception e) {
e.printStackTrace();
c.setChannel(1);
}
Server.getInstance().unregisterLoginState(c);
server.unregisterLoginState(c);
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":");
server.setCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), charId);
String[] socket = server.getIP(c.getWorld(), c.getChannel()).split(":");
try {
c.announce(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
} catch (UnknownHostException e) {
@@ -8,6 +8,7 @@ import net.server.Server;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
import java.net.InetSocketAddress;
public final class RegisterPicHandler extends AbstractMaplePacketHandler {
@@ -15,23 +16,37 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler {
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
slea.readByte();
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
c.updateMacs(macs);
c.updateHWID(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
c.getSession().close(true);
return;
}
Server server = Server.getInstance();
if(!server.haveCharacterid(c.getAccID(), charId)) {
c.getSession().close(true);
return;
}
String pic = slea.readMapleAsciiString();
if (c.getPic() == null || c.getPic().equals("")) {
c.setPic(pic);
Server.getInstance().unregisterLoginState(c);
c.setWorld(server.getCharacterWorld(charId));
if(c.getWorldServer().isWorldCapacityFull()) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
server.unregisterLoginState(c);
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":");
server.setCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), charId);
String[] socket = server.getIP(c.getWorld(), c.getChannel()).split(":");
try {
c.announce(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
} catch (UnknownHostException e) {
@@ -22,7 +22,7 @@
package net.server.handlers.login;
import client.MapleClient;
import constants.ServerConstants;
import constants.GameConstants;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.world.World;
@@ -35,7 +35,7 @@ public final class ServerlistRequestHandler extends AbstractMaplePacketHandler {
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
Server server = Server.getInstance();
for (World world : server.getWorlds()) {
c.announce(MaplePacketCreator.getServerList(world.getId(), ServerConstants.WORLD_NAMES[world.getId()], world.getFlag(), world.getEventMessage(), world.getChannels()));
c.announce(MaplePacketCreator.getServerList(world.getId(), GameConstants.WORLD_NAMES[world.getId()], world.getFlag(), world.getEventMessage(), world.getChannels()));
}
c.announce(MaplePacketCreator.getEndOfServerList());
c.announce(MaplePacketCreator.selectWorld(0));//too lazy to make a check lol
@@ -9,6 +9,7 @@ import tools.MaplePacketCreator;
import tools.Randomizer;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
import java.net.InetSocketAddress;
public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandler {
@@ -17,22 +18,36 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandle
String pic = slea.readMapleAsciiString();
int charId = slea.readInt();
int world = slea.readInt();//world
c.setWorld(world);
int channel = Randomizer.rand(0, Server.getInstance().getWorld(world).getChannels().size());
slea.readInt(); // please don't let the client choose which world they should login
Server server = Server.getInstance();
if(!server.haveCharacterid(c.getAccID(), charId)) {
c.getSession().close(true);
return;
}
c.setWorld(server.getCharacterWorld(charId));
int channel = Randomizer.rand(0, c.getWorldServer().getChannels().size());
c.setChannel(channel);
String macs = slea.readMapleAsciiString();
c.updateMacs(macs);
if (c.hasBannedMac()) {
c.getSession().close(true);
return;
}
if (c.checkPic(pic)) {
Server.getInstance().unregisterLoginState(c);
if(c.getWorldServer().isWorldCapacityFull()) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
server.unregisterLoginState(c);
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
server.setCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), charId);
String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":");
String[] socket = server.getIP(c.getWorld(), c.getChannel()).split(":");
try {
c.announce(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
} catch (UnknownHostException e) {
@@ -2,6 +2,7 @@ package net.server.handlers.login;
import client.MapleClient;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
@@ -16,21 +17,39 @@ public final class ViewAllPicRegisterHandler extends AbstractMaplePacketHandler
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
slea.readByte();
int charId = slea.readInt();
c.setWorld(slea.readInt()); //world
int channel = Randomizer.rand(0, Server.getInstance().getWorld(c.getWorld()).getChannels().size());
slea.readInt(); // please don't let the client choose which world they should login
Server server = Server.getInstance();
if(!server.haveCharacterid(c.getAccID(), charId)) {
c.getSession().close(true);
return;
}
c.setWorld(server.getCharacterWorld(charId));
if(c.getWorldServer().isWorldCapacityFull()) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
int channel = Randomizer.rand(0, server.getWorld(c.getWorld()).getChannels().size());
c.setChannel(channel);
String mac = slea.readMapleAsciiString();
c.updateMacs(mac);
if (c.hasBannedMac()) {
c.getSession().close(true);
return;
}
slea.readMapleAsciiString();
String pic = slea.readMapleAsciiString();
c.setPic(pic);
Server.getInstance().unregisterLoginState(c);
server.unregisterLoginState(c);
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
String[] socket = Server.getInstance().getIP(c.getWorld(), channel).split(":");
server.setCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), charId);
String[] socket = server.getIP(c.getWorld(), channel).split(":");
try {
c.announce(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
} catch (UnknownHostException e) {
@@ -0,0 +1,37 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
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 net.server.worker;
import net.server.world.World;
/**
* @author Ronan
*/
public class TimedMapObjectWorker extends BaseWorker implements Runnable {
@Override
public void run() {
wserv.runTimedMapObjectSchedule();
}
public TimedMapObjectWorker(World world) {
super(world);
}
}
+98 -1
View File
@@ -58,6 +58,7 @@ import server.maps.AbstractMapleMapObject;
import net.server.worker.CharacterAutosaverWorker;
import net.server.worker.MountTirednessWorker;
import net.server.worker.PetFullnessWorker;
import net.server.worker.TimedMapObjectWorker;
import net.server.PlayerStorage;
import net.server.Server;
import net.server.channel.Channel;
@@ -80,6 +81,7 @@ public class World {
private int id, flag, exprate, droprate, mesorate, questrate;
private String eventmsg;
private List<Channel> channels = new ArrayList<>();
private Map<Integer, Byte> pnpcStep = new HashMap<>();
private Map<Integer, MapleMessenger> messengers = new HashMap<>();
private AtomicInteger runningMessengerId = new AtomicInteger();
private Map<Integer, MapleFamily> families = new LinkedHashMap<>();
@@ -112,6 +114,10 @@ public class World {
private Map<Integer, Pair<MapleHiredMerchant, Byte>> activeMerchants = new LinkedHashMap<>();
private long merchantUpdate;
private Map<Runnable, Long> registeredTimedMapObjects = new LinkedHashMap<>();
private ScheduledFuture<?> timedMapObjectsSchedule;
private Lock timedMapObjectLock = new MonitoredReentrantLock(MonitoredLockType.MAP_OBJS, true);
private ScheduledFuture<?> charactersSchedule;
public World(int world, int flag, String eventmsg, int exprate, int droprate, int mesorate, int questrate) {
@@ -130,6 +136,7 @@ public class World {
petsSchedule = TimerManager.getInstance().register(new PetFullnessWorker(this), 60 * 1000, 60 * 1000);
mountsSchedule = TimerManager.getInstance().register(new MountTirednessWorker(this), 60 * 1000, 60 * 1000);
timedMapObjectsSchedule = TimerManager.getInstance().register(new TimedMapObjectWorker(this), 60 * 1000, 60 * 1000);
charactersSchedule = TimerManager.getInstance().register(new CharacterAutosaverWorker(this), 60 * 60 * 1000, 60 * 60 * 1000);
}
@@ -228,7 +235,18 @@ public class World {
}
public void removePlayer(MapleCharacter chr) {
channels.get(chr.getClient().getChannel() - 1).removePlayer(chr);
if(!channels.get(chr.getClient().getChannel() - 1).removePlayer(chr)) {
if(!chr.getClient().getChannelServer().removePlayer(chr)) {
// oy the player is not where it should be, find this mf
for(Channel ch : channels) {
if(ch.removePlayer(chr)) {
break;
}
}
}
}
players.removePlayer(chr.getId());
}
@@ -1035,6 +1053,80 @@ public class World {
}
}
public void registerTimedMapObject(Runnable r, long duration) {
timedMapObjectLock.lock();
try {
long expirationTime = System.currentTimeMillis() + duration;
registeredTimedMapObjects.put(r, expirationTime);
} finally {
timedMapObjectLock.unlock();
}
}
public void runTimedMapObjectSchedule() {
List<Runnable> toRemove = new LinkedList<>();
timedMapObjectLock.lock();
try {
long timeNow = System.currentTimeMillis();
for(Entry<Runnable, Long> rtmo : registeredTimedMapObjects.entrySet()) {
if(rtmo.getValue() <= timeNow) {
toRemove.add(rtmo.getKey());
}
}
for(Runnable r : toRemove) {
registeredTimedMapObjects.remove(r);
}
} finally {
timedMapObjectLock.unlock();
}
for(Runnable r : toRemove) {
r.run();
}
}
public void setPlayerNpcMapStep(int mapid, int step) {
setPlayerNpcMapStep(mapid, step, false);
}
public void setPlayerNpcMapStep(int mapid, int step, boolean silent) {
if(!silent) {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps;
if(pnpcStep.containsKey(mapid)) {
ps = con.prepareStatement("UPDATE playernpcs_field SET step = ? WHERE world = ? AND map = ?");
} else {
ps = con.prepareStatement("INSERT INTO playernpcs_field (step, world, map) VALUES (?, ?, ?)");
}
ps.setInt(1, step);
ps.setInt(2, id);
ps.setInt(3, mapid);
ps.executeUpdate();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
pnpcStep.put(mapid, (byte) step);
}
public int getPlayerNpcMapStep(int mapid) {
try {
return pnpcStep.get(mapid);
} catch (NullPointerException npe) {
return 0;
}
}
public void setServerMessage(String msg) {
for (Channel ch : channels) {
ch.setServerMessage(msg);
@@ -1092,6 +1184,11 @@ public class World {
mountsSchedule = null;
}
if(timedMapObjectsSchedule != null) {
timedMapObjectsSchedule.cancel(false);
timedMapObjectsSchedule = null;
}
players.disconnectAll();
}
}
@@ -24,6 +24,7 @@ package scripting.npc;
import java.io.File;
import java.sql.SQLException;
import constants.ServerConstants;
import net.server.Server;
import net.server.guild.MapleAlliance;
import net.server.guild.MapleGuild;
@@ -38,6 +39,7 @@ import server.MapleShopFactory;
import server.events.gm.MapleEvent;
import server.gachapon.MapleGachapon;
import server.gachapon.MapleGachapon.MapleGachaponItem;
import server.life.MaplePlayerNPC;
import server.maps.MapleMap;
import server.maps.MapleMapFactory;
import server.partyquest.Pyramid;
@@ -56,6 +58,10 @@ import client.inventory.Item;
import client.inventory.ItemFactory;
import client.inventory.MaplePet;
import constants.ItemConstants;
import java.awt.Point;
import java.util.Arrays;
import server.maps.MapleMapObject;
import server.maps.MapleMapObjectType;
/**
*
@@ -308,6 +314,23 @@ public class NPCConversationManager extends AbstractPlayerInteraction {
public void displayGuildRanks() {
MapleGuild.displayGuildRanks(getClient(), npc);
}
public boolean canSpawnPlayerNpc(int mapid) {
MapleCharacter chr = getPlayer();
return !ServerConstants.PLAYERNPC_AUTODEPLOY && chr.getLevel() >= chr.getMaxClassLevel() && MaplePlayerNPC.canSpawnPlayerNpc(chr.getName(), mapid);
}
public MaplePlayerNPC getPlayerNPCByScriptid(int scriptId) {
for(MapleMapObject pnpcObj : getPlayer().getMap().getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER_NPC))) {
MaplePlayerNPC pn = (MaplePlayerNPC) pnpcObj;
if(pn.getScriptId() == scriptId) {
return pn;
}
}
return null;
}
@Override
public MapleParty getParty() {
+65 -3
View File
@@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import tools.locks.MonitoredReentrantLock;
@@ -92,10 +93,10 @@ public class CashShop {
Item item;
int petid = -1;
if (ItemConstants.isPet(itemId))
if (ItemConstants.isPet(itemId)) {
petid = MaplePet.createPet(itemId);
}
if (ItemConstants.getInventoryType(itemId).equals(MapleInventoryType.EQUIP)) {
item = MapleItemInformationProvider.getInstance().getEquipById(itemId);
} else {
@@ -119,10 +120,12 @@ public class CashShop {
item.setExpiration(System.currentTimeMillis() + (1000 * 60 * 60 * 24 * period));
}
}
item.setSN(sn);
return item;
}
}
public static class SpecialCashItem {
private int sn, modifier;
private byte info; //?
@@ -151,6 +154,7 @@ public class CashShop {
private static final Map<Integer, CashItem> items = new HashMap<>();
private static final Map<Integer, List<Integer>> packages = new HashMap<>();
private static final List<SpecialCashItem> specialcashitems = new ArrayList<>();
private static final List<Integer> randomitemids = new ArrayList<>();
static {
MapleDataProvider etc = MapleDataProviderFactory.getDataProvider(new File("wz/Etc.wz"));
@@ -174,6 +178,13 @@ public class CashShop {
packages.put(Integer.parseInt(cashPackage.getName()), cPackage);
}
for(Entry<Integer, CashItem> e : items.entrySet()) {
if(e.getValue().isOnSale()) {
randomitemids.add(e.getKey());
}
}
PreparedStatement ps = null;
ResultSet rs = null;
Connection con = null;
@@ -197,6 +208,13 @@ public class CashShop {
}
}
public static CashItem getRandomCashItem() {
if(randomitemids.isEmpty()) return null;
int rnd = (int)(Math.random() * randomitemids.size());
return items.get(randomitemids.get(rnd));
}
public static CashItem getItem(int sn) {
return items.get(sn);
}
@@ -508,4 +526,48 @@ public class CashShop {
ps.close();
}
private Item getCashShopItemByItemid(int itemid) {
lock.lock();
try {
for(Item it : inventory) {
if(it.getItemId() == itemid) {
return it;
}
}
} finally {
lock.unlock();
}
return null;
}
public synchronized Item openCashShopSurprise() {
Item css = getCashShopItemByItemid(5222000);
if(css != null) {
CashItem cItem = CashItemFactory.getRandomCashItem();
if(cItem != null) {
if(css.getQuantity() > 1) {
/* if(NOT ENOUGH SPACE) { looks like we're not dealing with cash inventory limit whatsoever, k then
return null;
} */
css.setQuantity((short) (css.getQuantity() - 1));
} else {
removeFromInventory(css);
}
Item item = cItem.toItem();
addToInventory(item);
return item;
} else {
return null;
}
} else {
return null;
}
}
}
@@ -118,7 +118,6 @@ public class MapleItemInformationProvider {
protected Map<Integer, Pair<String, Integer>> statUpgradeMakerCache = new HashMap<>();
protected Map<Integer, MakerItemFactory.MakerItemCreateEntry> makerItemCache = new HashMap<>();
private MapleItemInformationProvider() {
loadCardIdData();
itemData = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/Item.wz"));
+644
View File
@@ -0,0 +1,644 @@
/*
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 server.life;
import java.awt.Point;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import client.MapleCharacter;
import client.MapleClient;
import client.inventory.Item;
import client.inventory.MapleInventoryType;
import constants.GameConstants;
import net.server.Server;
import net.server.channel.Channel;
import net.server.world.World;
import server.maps.AbstractMapleMapObject;
import server.maps.MapleMap;
import server.maps.MapleMapObject;
import server.maps.MapleMapObjectType;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
import tools.Pair;
/**
*
* @author XoticStory
* @author Ronan
*/
public class MaplePlayerNPC extends AbstractMapleMapObject {
private static final Map<Byte, AtomicInteger> runningPlayerNpcScriptIds = new HashMap<>();
private static final AtomicInteger runningOverallRank = new AtomicInteger();
private static final List<AtomicInteger> runningWorldRank = new ArrayList<>();
private static final Map<Pair<Integer, Integer>, AtomicInteger> runningWorldJobRank = new HashMap<>();
private Map<Short, Integer> equips = new HashMap<>();
private int scriptId, face, hair, gender, job;
private byte skin;
private String name = "";
private int dir, FH, RX0, RX1, CY;
private int worldRank, overallRank, worldJobRank, overallJobRank;
static {
getRunningMetadata();
}
public MaplePlayerNPC(String name, int scriptId, int face, int hair, int gender, byte skin, Map<Short, Integer> equips, int dir, int FH, int RX0, int RX1, int CX, int CY, int oid) {
this.equips = equips;
this.scriptId = scriptId;
this.face = face;
this.hair = hair;
this.gender = gender;
this.skin = skin;
this.name = name;
this.dir = dir;
this.FH = FH;
this.RX0 = RX0;
this.RX1 = RX1;
this.CY = CY;
this.job = 7777; // supposed to be developer
setPosition(new Point(CX, CY));
setObjectId(oid);
}
public MaplePlayerNPC(ResultSet rs) {
try {
CY = rs.getInt("cy");
name = rs.getString("name");
hair = rs.getInt("hair");
face = rs.getInt("face");
skin = rs.getByte("skin");
gender = rs.getInt("gender");
dir = rs.getInt("dir");
FH = rs.getInt("fh");
RX0 = rs.getInt("rx0");
RX1 = rs.getInt("rx1");
scriptId = rs.getInt("scriptid");
worldRank = rs.getInt("worldrank");
overallRank = rs.getInt("overallrank");
worldJobRank = rs.getInt("worldjobrank");
overallJobRank = GameConstants.getOverallJobRankByScriptId(scriptId);
job = rs.getInt("job");
setPosition(new Point(rs.getInt("x"), CY));
setObjectId(rs.getInt("id"));
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT equippos, equipid FROM playernpcs_equip WHERE npcid = ?");
ps.setInt(1, rs.getInt("id"));
ResultSet rs2 = ps.executeQuery();
while (rs2.next()) {
equips.put(rs2.getShort("equippos"), rs2.getInt("equipid"));
}
rs2.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Map<Short, Integer> getEquips() {
return equips;
}
public int getScriptId() {
return scriptId;
}
public int getJob() {
return job;
}
public int getDirection() {
return dir;
}
public int getFH() {
return FH;
}
public int getRX0() {
return RX0;
}
public int getRX1() {
return RX1;
}
public int getCY() {
return CY;
}
public byte getSkin() {
return skin;
}
public String getName() {
return name;
}
public int getFace() {
return face;
}
public int getHair() {
return hair;
}
public int getGender() {
return gender;
}
public int getWorldRank() {
return worldRank;
}
public int getOverallRank() {
return overallRank;
}
public int getWorldJobRank() {
return worldJobRank;
}
public int getOverallJobRank() {
return overallJobRank;
}
@Override
public MapleMapObjectType getType() {
return MapleMapObjectType.PLAYER_NPC;
}
@Override
public void sendSpawnData(MapleClient client) {
client.announce(MaplePacketCreator.spawnPlayerNPC(this));
client.announce(MaplePacketCreator.getPlayerNPC(this));
}
@Override
public void sendDestroyData(MapleClient client) {
client.announce(MaplePacketCreator.removeNPCController(this.getObjectId()));
client.announce(MaplePacketCreator.removePlayerNPC(this.getObjectId()));
}
private static void getRunningMetadata() {
try {
Connection con = DatabaseConnection.getConnection();
getRunningPlayerNpcScriptIds(con);
getRunningOverallRanks(con);
getRunningWorldRanks(con);
getRunningWorldJobRanks(con);
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void getRunningPlayerNpcScriptIds(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement("SELECT floor(scriptid / 100), max(scriptid) FROM playernpcs GROUP BY floor(scriptid / 100) ORDER BY floor(scriptid / 100)");
ResultSet rs = ps.executeQuery();
while(rs.next()) {
byte branch = (byte) (rs.getInt(1) % 100);
int maxid = rs.getInt(2);
if(branch < 26) {
runningPlayerNpcScriptIds.put(branch, new AtomicInteger(maxid));
} else {
runningPlayerNpcScriptIds.put((byte)(branch - ((branch - 26) % 4)), new AtomicInteger(maxid));
}
}
rs.close();
ps.close();
}
private static void getRunningOverallRanks(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement("SELECT max(overallrank) FROM playernpcs");
ResultSet rs = ps.executeQuery();
if(rs.next()) {
runningOverallRank.set(rs.getInt(1) + 1);
} else {
runningOverallRank.set(1);
}
rs.close();
ps.close();
}
private static void getRunningWorldRanks(Connection con) throws SQLException {
int numWorlds = Server.getInstance().getWorlds().size();
for(int i = 0; i < numWorlds; i++) {
runningWorldRank.add(new AtomicInteger(1));
}
PreparedStatement ps = con.prepareStatement("SELECT world, max(worldrank) FROM playernpcs GROUP BY world ORDER BY world");
ResultSet rs = ps.executeQuery();
while(rs.next()) {
int wid = rs.getInt(1);
if(wid < numWorlds) {
runningWorldRank.get(wid).set(rs.getInt(2) + 1);
}
}
rs.close();
ps.close();
}
private static void getRunningWorldJobRanks(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement("SELECT world, job, max(worldjobrank) FROM playernpcs GROUP BY world, job ORDER BY world, job");
ResultSet rs = ps.executeQuery();
while(rs.next()) {
runningWorldJobRank.put(new Pair<>(rs.getInt(1), rs.getInt(2)), new AtomicInteger(rs.getInt(3) + 1));
}
rs.close();
ps.close();
}
private static int getAndIncrementRunningWorldJobRanks(int world, int job) {
AtomicInteger wjr = runningWorldJobRank.get(new Pair<>(world, job));
if(wjr == null) {
wjr = new AtomicInteger(1);
runningWorldJobRank.put(new Pair<>(world, job), wjr);
}
return wjr.getAndIncrement();
}
public static boolean canSpawnPlayerNpc(String name, int mapid) {
boolean ret = true;
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT name FROM playernpcs WHERE name LIKE ? AND map = ?");
ps.setString(1, name);
ps.setInt(2, mapid);
ResultSet rs = ps.executeQuery();
if(rs.next()) {
ret = false;
}
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
return ret;
}
public void updatePlayerNPCPosition(MapleMap map, Point newPos) {
setPosition(newPos);
RX0 = newPos.x + 50;
RX1 = newPos.x - 50;
CY = newPos.y;
FH = map.getFootholds().findBelow(newPos).getId();
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE playernpcs SET x = ?, cy = ?, fh = ?, rx0 = ?, rx1 = ? WHERE id = ?");
ps.setInt(1, newPos.x);
ps.setInt(2, CY);
ps.setInt(3, FH);
ps.setInt(4, RX0);
ps.setInt(5, RX1);
ps.setInt(6, getObjectId());
ps.executeUpdate();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static MaplePlayerNPC createPlayerNPCInternal(MapleMap map, Point pos, MapleCharacter chr) {
if(pos == null) {
pos = map.getNextPlayerNpcPosition();
if(pos == null) {
return null;
}
}
int mapId = map.getId();
int worldId = chr.getWorld();
int jobId = (chr.getJob().getId() / 100) * 100;
/*
if(!canSpawnPlayerNpc(chr.getName(), mapId)) {
return null;
}
*/
byte branch = GameConstants.getHallOfFameBranch(chr.getJob(), mapId);
int scriptId;
try {
scriptId = runningPlayerNpcScriptIds.get(branch).getAndIncrement();
if(!GameConstants.canPnpcBranchUseScriptId(branch, scriptId)) {
return null;
}
} catch (NullPointerException npe) {
scriptId = 9900000 + (branch * 100);
runningPlayerNpcScriptIds.put(branch, new AtomicInteger(scriptId + 1));
}
MaplePlayerNPC ret;
int npcId;
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM playernpcs WHERE scriptid = ?");
ps.setInt(1, scriptId);
ResultSet rs = ps.executeQuery();
if (!rs.next()) { // creates new playernpc if scriptid doesn't exist
rs.close();
ps.close();
ps = con.prepareStatement("INSERT INTO playernpcs (name, hair, face, skin, gender, x, cy, world, map, scriptid, dir, fh, rx0, rx1, worldrank, overallrank, worldjobrank, job) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
ps.setString(1, chr.getName());
ps.setInt(2, chr.getHair());
ps.setInt(3, chr.getFace());
ps.setInt(4, chr.getSkinColor().getId());
ps.setInt(5, chr.getGender());
ps.setInt(6, pos.x);
ps.setInt(7, pos.y);
ps.setInt(8, worldId);
ps.setInt(9, mapId);
ps.setInt(10, scriptId);
ps.setInt(11, 1); // default direction
ps.setInt(12, map.getFootholds().findBelow(pos).getId());
ps.setInt(13, pos.x + 50);
ps.setInt(14, pos.x - 50);
ps.setInt(15, runningWorldRank.get(worldId).getAndIncrement());
ps.setInt(16, runningOverallRank.getAndIncrement());
ps.setInt(17, getAndIncrementRunningWorldJobRanks(worldId, jobId));
ps.setInt(18, jobId);
ps.executeUpdate();
rs = ps.getGeneratedKeys();
rs.next();
npcId = rs.getInt(1);
rs.close();
ps.close();
ps = con.prepareStatement("INSERT INTO playernpcs_equip (npcid, equipid, equippos) VALUES (?, ?, ?)");
ps.setInt(1, npcId);
for (Item equip : chr.getInventory(MapleInventoryType.EQUIPPED)) {
int position = Math.abs(equip.getPosition());
if ((position < 12 && position > 0) || (position > 100 && position < 112)) {
ps.setInt(2, equip.getItemId());
ps.setInt(3, equip.getPosition());
ps.addBatch();
}
}
ps.executeBatch();
ps.close();
ps = con.prepareStatement("SELECT * FROM playernpcs WHERE id = ?");
ps.setInt(1, npcId);
rs = ps.executeQuery();
rs.next();
ret = new MaplePlayerNPC(rs);
} else {
ret = null;
}
rs.close();
ps.close();
con.close();
return ret;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
private static List<Integer> removePlayerNPCInternal(MapleMap map, MapleCharacter chr) {
Set<Integer> updateMapids = new HashSet<>();
List<Integer> mapids = new LinkedList<>();
mapids.add(chr.getWorld());
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT id, map FROM playernpcs WHERE name LIKE ?" + (map != null ? " AND map = ?" : ""));
ps.setString(1, chr.getName());
if(map != null) ps.setInt(2, map.getId());
ResultSet rs = ps.executeQuery();
while(rs.next()) {
updateMapids.add(rs.getInt("map"));
int npcId = rs.getInt("id");
PreparedStatement ps2 = con.prepareStatement("DELETE FROM playernpcs WHERE id = ?");
ps2.setInt(1, npcId);
ps2.executeUpdate();
ps2.close();
ps2 = con.prepareStatement("DELETE FROM playernpcs_equip WHERE npcid = ?");
ps2.setInt(1, npcId);
ps2.executeUpdate();
ps2.close();
}
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
for(Integer i : updateMapids) {
mapids.add(i);
}
return mapids;
}
private static synchronized Pair<MaplePlayerNPC, List<Integer>> processPlayerNPCInternal(MapleMap map, Point pos, MapleCharacter chr, boolean create) {
if(create) {
return new Pair<>(createPlayerNPCInternal(map, pos, chr), null);
} else {
return new Pair<>(null, removePlayerNPCInternal(map, chr));
}
}
public static boolean spawnPlayerNPC(int mapid, MapleCharacter chr) {
return spawnPlayerNPC(mapid, null, chr);
}
public static boolean spawnPlayerNPC(int mapid, Point pos, MapleCharacter chr) {
MaplePlayerNPC pn = processPlayerNPCInternal(chr.getClient().getChannelServer().getMapFactory().getMap(mapid), pos, chr, true).getLeft();
if(pn != null) {
for (Channel channel : Server.getInstance().getChannelsFromWorld(chr.getWorld())) {
MapleMap m = channel.getMapFactory().getMap(mapid);
m.addPlayerNPCMapObject(pn);
m.broadcastMessage(MaplePacketCreator.spawnPlayerNPC(pn));
m.broadcastMessage(MaplePacketCreator.getPlayerNPC(pn));
}
return true;
} else {
return false;
}
}
private static MaplePlayerNPC getPlayerNPCFromWorldMap(String name, int world, int map) {
World wserv = Server.getInstance().getWorld(world);
for(MapleMapObject pnpcObj : wserv.getChannel(1).getMapFactory().getMap(map).getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER_NPC))) {
MaplePlayerNPC pn = (MaplePlayerNPC) pnpcObj;
if(name.contentEquals(pn.getName()) && pn.getScriptId() < 9977777) {
return pn;
}
}
return null;
}
public static void removePlayerNPC(MapleCharacter chr) {
List<Integer> updateMapids = processPlayerNPCInternal(null, null, chr, false).getRight();
int worldid = updateMapids.remove(0);
for (Integer mapid : updateMapids) {
MaplePlayerNPC pn = getPlayerNPCFromWorldMap(chr.getName(), worldid, mapid);
if(pn != null) {
for (Channel channel : Server.getInstance().getChannelsFromWorld(worldid)) {
MapleMap m = channel.getMapFactory().getMap(mapid);
m.removeMapObject(pn);
m.broadcastMessage(MaplePacketCreator.removeNPCController(pn.getObjectId()));
m.broadcastMessage(MaplePacketCreator.removePlayerNPC(pn.getObjectId()));
}
}
}
}
private static List<Integer> loadCharacteridsFromDB(int world) {
List<Integer> list = new LinkedList<>();
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT id FROM characters WHERE world = ?");
ps.setInt(1, world);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
list.add(rs.getInt(1));
}
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public static void multicastSpawnPlayerNPC(int mapid, int world) {
MapleClient mockClient = new MapleClient(null, null, null);
for(Integer cid : loadCharacteridsFromDB(world)) {
try {
MapleCharacter mc = MapleCharacter.loadCharFromDB(cid, mockClient, false);
spawnPlayerNPC(mapid, mc);
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
}
public static void removeAllPlayerNPC() {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT DISTINCT world, map FROM playernpcs");
ResultSet rs = ps.executeQuery();
while(rs.next()) {
int world = rs.getInt("world"), map = rs.getInt("map");
World w = Server.getInstance().getWorld(world);
for (Channel channel : w.getChannels()) {
MapleMap m = channel.getMapFactory().getMap(map);
for(MapleMapObject pnpcObj : m.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER_NPC))) {
MaplePlayerNPC pn = (MaplePlayerNPC) pnpcObj;
m.removeMapObject(pn);
m.broadcastMessage(MaplePacketCreator.removeNPCController(pn.getObjectId()));
m.broadcastMessage(MaplePacketCreator.removePlayerNPC(pn.getObjectId()));
}
}
}
rs.close();
ps.close();
ps = con.prepareStatement("DELETE FROM playernpcs");
ps.executeUpdate();
ps.close();
ps = con.prepareStatement("DELETE FROM playernpcs_equip");
ps.executeUpdate();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
+134
View File
@@ -0,0 +1,134 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
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 server.life;
import constants.GameConstants;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.LinkedList;
import provider.MapleData;
import provider.MapleDataProvider;
import provider.MapleDataProviderFactory;
import provider.MapleDataTool;
/**
*
* @author RonanLana
*/
public class MaplePlayerNPCFactory {
private static final Map<Integer, List<MaplePlayerNPC>> dnpcMaps = new HashMap<>();
private static Integer runningDeveloperOid = 2147483000; // 647 slots, long enough
public static void loadDeveloperRoomMetadata(MapleDataProvider npc) {
MapleData thisData = npc.getData("9977777.img");
if(thisData != null) {
MapleDataProvider map = MapleDataProviderFactory.getDataProvider(new File("wz/Map.wz"));
thisData = map.getData("Map/Map7/777777777.img");
if(thisData != null) {
MapleDataProvider sound = MapleDataProviderFactory.getDataProvider(new File("wz/Sound.wz"));
thisData = sound.getData("Field.img");
if(thisData != null) {
MapleData md = thisData.getChildByPath("anthem/brazil");
if(md != null) {
GameConstants.setAvailableDeveloperRoom();
}
}
}
}
}
public static void loadFactoryMetadata() {
MapleDataProvider npc = MapleDataProviderFactory.getDataProvider(new File("wz/Npc.wz"));
loadDeveloperRoomMetadata(npc);
MapleDataProvider etc = MapleDataProviderFactory.getDataProvider(new File("wz/Etc.wz"));
MapleData dnpcData = etc.getData("DeveloperNpc.img");
if(dnpcData != null) {
for (MapleData data : dnpcData.getChildren()) {
int scriptId = Integer.parseInt(data.getName());
String name = MapleDataTool.getString("name", data, "");
int face = MapleDataTool.getIntConvert("face", data, 20000);
int hair = MapleDataTool.getIntConvert("hair", data, 30000);
int gender = MapleDataTool.getIntConvert("gender", data, 0);
byte skin = (byte) MapleDataTool.getIntConvert("skin", data, 0);
int dir = MapleDataTool.getIntConvert("dir", data, 0);
int mapid = MapleDataTool.getIntConvert("map", data, 0);
int FH = MapleDataTool.getIntConvert("fh", data, 0);
int RX0 = MapleDataTool.getIntConvert("rx0", data, 0);
int RX1 = MapleDataTool.getIntConvert("rx1", data, 0);
int CX = MapleDataTool.getIntConvert("cx", data, 0);
int CY = MapleDataTool.getIntConvert("cy", data, 0);
Map<Short, Integer> equips = new HashMap<>();
for (MapleData edata : data.getChildByPath("equips").getChildren()) {
short equippos = (short) MapleDataTool.getIntConvert("pos", edata);
int equipid = MapleDataTool.getIntConvert("itemid", edata);
equips.put(equippos, equipid);
}
List<MaplePlayerNPC> dnpcSet = dnpcMaps.get(mapid);
if(dnpcSet == null) {
dnpcSet = new LinkedList<>();
dnpcMaps.put(mapid, dnpcSet);
}
dnpcSet.add(new MaplePlayerNPC(name, scriptId, face, hair, gender, skin, equips, dir, FH, RX0, RX1, CX, CY, runningDeveloperOid));
runningDeveloperOid++;
}
} else {
MapleData thisData = npc.getData("9977777.img");
if(thisData != null) {
byte[] encData = {0x52,0x6F,0x6E,0x61,0x6E};
String name = new String(encData);
int face = 20104, hair = 30215, gender = 0, skin = 0, dir = 0, mapid = 777777777;
int FH = 4, RX0 = -143, RX1 = -243, CX = -193, CY = 117, scriptId = 9977777;
Map<Short, Integer> equips = new HashMap<>();
equips.put((short) -1, 1002067);
equips.put((short) -11, 1402046);
equips.put((short) -8, 1082140);
equips.put((short) -6, 1060091);
equips.put((short) -7, 1072154);
equips.put((short) -5, 1040103);
List<MaplePlayerNPC> dnpcSet = dnpcMaps.get(mapid);
if(dnpcSet == null) {
dnpcSet = new LinkedList<>();
dnpcMaps.put(mapid, dnpcSet);
}
dnpcSet.add(new MaplePlayerNPC(name, scriptId, face, hair, gender, (byte) skin, equips, dir, FH, RX0, RX1, CX, CY, runningDeveloperOid));
runningDeveloperOid++;
}
}
}
public static List<MaplePlayerNPC> getDeveloperNpcsFromMapid(int mapid) {
return dnpcMaps.get(mapid);
}
}
+61
View File
@@ -0,0 +1,61 @@
package server.maps;
import java.awt.Point;
import client.MapleCharacter;
import client.MapleClient;
import constants.ServerConstants;
import tools.MaplePacketCreator;
public class MapleKite extends AbstractMapleMapObject {
private Point pos;
private MapleCharacter owner;
private String text;
private int ft;
private int itemid;
public MapleKite(MapleCharacter owner, String text, int itemid) {
this.owner = owner;
this.pos = owner.getPosition();
this.ft = owner.getFh();
this.text = text;
this.itemid = itemid;
}
@Override
public MapleMapObjectType getType() {
return MapleMapObjectType.KITE;
}
@Override
public Point getPosition() {
return pos.getLocation();
}
public MapleCharacter getOwner() {
return owner;
}
@Override
public void setPosition(Point position) {
throw new UnsupportedOperationException();
}
@Override
public void sendDestroyData(MapleClient client) {
client.getSession().write(makeDestroyData());
}
@Override
public void sendSpawnData(MapleClient client) {
client.getSession().write(makeSpawnData());
}
public final byte[] makeSpawnData() {
return MaplePacketCreator.spawnKite(getObjectId(), itemid, owner.getName(), text, pos, ft);
}
public final byte[] makeDestroyData() {
return MaplePacketCreator.removeKite(getObjectId(), 0);
}
}
+228 -10
View File
@@ -59,6 +59,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.lang.ref.WeakReference;
import java.util.Comparator;
import net.server.Server;
import net.server.channel.Channel;
import scripting.map.MapScriptManager;
@@ -83,6 +84,7 @@ import server.partyquest.MonsterCarnival;
import server.partyquest.MonsterCarnivalParty;
//import server.partyquest.Pyramid;
import scripting.event.EventInstanceManager;
import server.life.MaplePlayerNPC;
import server.life.MonsterListener;
import tools.FilePrinter;
import tools.MaplePacketCreator;
@@ -343,6 +345,15 @@ public class MapleMap {
return -1;
}
public void addPlayerNPCMapObject(MaplePlayerNPC pnpcobject) {
objectWLock.lock();
try {
this.mapobjects.put(pnpcobject.getObjectId(), pnpcobject);
} finally {
objectWLock.unlock();
}
}
public void addMapObject(MapleMapObject mapobject) {
objectWLock.lock();
try {
@@ -353,7 +364,7 @@ public class MapleMap {
objectWLock.unlock();
}
}
private void spawnAndAddRangedMapObject(MapleMapObject mapobject, DelayedPacketCreation packetbakery) {
spawnAndAddRangedMapObject(mapobject, packetbakery, null);
}
@@ -403,7 +414,7 @@ public class MapleMap {
Integer curOid;
do {
if ((curOid = runningOid.incrementAndGet()) < 0) {
if ((curOid = runningOid.incrementAndGet()) < 0) { // clashes with playernpc on curOid >= 2147000000, developernpc uses >= 2147483000
runningOid.set(curOid = 1000000001);
}
} while (mapobjects.containsKey(curOid));
@@ -1995,6 +2006,21 @@ public class MapleMap {
}, duration);
}
public void spawnKite(final MapleKite kite) {
addMapObject(kite);
broadcastMessage(kite.makeSpawnData());
Runnable expireKite = new Runnable() {
@Override
public void run() {
removeMapObject(kite);
broadcastMessage(kite.makeDestroyData());
}
};
Server.getInstance().getWorld(world).registerTimedMapObject(expireKite, ServerConstants.KITE_EXPIRE_TIME);
}
public final void spawnItemDrop(final MapleMapObject dropper, final MapleCharacter owner, final Item item, Point pos, final boolean ffaDrop, final boolean playerDrop) {
spawnItemDrop(dropper, owner, item, pos, (byte)(ffaDrop ? 2 : 0), playerDrop);
}
@@ -2278,7 +2304,7 @@ public class MapleMap {
}
}, 60 * 1000);
} else if (mapid == 200090030) { // To Ereve (SkyFerry)
chr.getClient().announce(MaplePacketCreator.getClock(60));
chr.getClient().announce(MaplePacketCreator.getClock(2 * 60));
TimerManager.getInstance().schedule(new Runnable() {
@Override
@@ -2287,9 +2313,9 @@ public class MapleMap {
chr.changeMap(130000210, 0);
}
}
}, 60 * 1000);
}, 2 * 60 * 1000);
} else if (mapid == 200090031) { // To Victoria Island (SkyFerry)
chr.getClient().announce(MaplePacketCreator.getClock(60));
chr.getClient().announce(MaplePacketCreator.getClock(2 * 60));
TimerManager.getInstance().schedule(new Runnable() {
@Override
@@ -2298,9 +2324,9 @@ public class MapleMap {
chr.changeMap(101000400, 0);
}
}
}, 60 * 1000);
}, 2 * 60 * 1000);
} else if (mapid == 200090021) { // To Orbis (SkyFerry)
chr.getClient().announce(MaplePacketCreator.getClock(60));
chr.getClient().announce(MaplePacketCreator.getClock(8 * 60));
TimerManager.getInstance().schedule(new Runnable() {
@Override
@@ -2309,9 +2335,9 @@ public class MapleMap {
chr.changeMap(200000161, 0);
}
}
}, 60 * 1000);
}, 8 * 60 * 1000);
} else if (mapid == 200090020) { // To Ereve From Orbis (SkyFerry)
chr.getClient().announce(MaplePacketCreator.getClock(60));
chr.getClient().announce(MaplePacketCreator.getClock(8 * 60));
TimerManager.getInstance().schedule(new Runnable() {
@Override
@@ -2320,7 +2346,7 @@ public class MapleMap {
chr.changeMap(130000210, 0);
}
}
}, 60 * 1000);
}, 8 * 60 * 1000);
} else if (mapid == 103040400) {
if (chr.getEventInstance() != null) {
chr.getEventInstance().movePlayer(chr);
@@ -2681,6 +2707,7 @@ public class MapleMap {
case PLAYER_NPC:
case DRAGON:
case MIST:
case KITE:
return true;
default:
return false;
@@ -3762,4 +3789,195 @@ public class MapleMap {
spawnMonsterOnGroundBelow(m, targetPoint);
}
}
private static boolean isPlayerNpcNearby(List<Point> otherPos, Point searchPos, int xLimit, int yLimit) {
int xLimit2 = xLimit / 2, yLimit2 = yLimit / 2;
Rectangle searchRect = new Rectangle(searchPos.x - xLimit2, searchPos.y - yLimit2, xLimit, yLimit);
for(Point pos : otherPos) {
Rectangle otherRect = new Rectangle(pos.x - xLimit2, pos.y - yLimit2, xLimit, yLimit);
if(otherRect.intersects(searchRect)) {
return true;
}
}
return false;
}
private static int calcDx(int newStep) {
return ServerConstants.PLAYERNPC_AREA_X / (newStep + 1);
}
private static int calcDy(int newStep) {
return (ServerConstants.PLAYERNPC_AREA_Y / 2) + (ServerConstants.PLAYERNPC_AREA_Y / (1 << (newStep + 1)));
}
private List<Point> rearrangePlayerNpcPositions(int newStep, int pnpcsSize) {
int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y;
int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height;
int cx = calcDx(newStep), cy = calcDy(newStep);
List<Point> otherPlayerNpcs = new LinkedList<>();
while(py < outy) {
px = leftPx;
while(px < outx) {
Point searchPos = calcPointBelow(new Point(px, py));
if(searchPos != null) {
if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) {
otherPlayerNpcs.add(searchPos);
if(otherPlayerNpcs.size() == pnpcsSize) {
return otherPlayerNpcs;
}
}
}
px += cx;
}
py += cy;
}
return null;
}
private Point rearrangePlayerNpcs(int newStep, List<MaplePlayerNPC> pnpcs) {
int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y;
int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height;
int cx = calcDx(newStep), cy = calcDy(newStep);
List<Point> otherPlayerNpcs = new LinkedList<>();
int i = 0;
while(py < outy) {
px = leftPx;
while(px < outx) {
Point searchPos = calcPointBelow(new Point(px, py));
if(searchPos != null) {
if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) {
if(i == pnpcs.size()) {
return searchPos;
}
MaplePlayerNPC pn = pnpcs.get(i);
i++;
pn.updatePlayerNPCPosition(this, searchPos);
otherPlayerNpcs.add(searchPos);
}
}
px += cx;
}
py += cy;
}
return null; // this area should not be reached under any scenario
}
private Point reorganizePlayerNpcs(int newStep, List<MapleMapObject> mmoList) {
if(!mmoList.isEmpty()) {
List<MaplePlayerNPC> playerNpcs = new ArrayList<>(mmoList.size());
for(MapleMapObject mmo : mmoList) {
playerNpcs.add((MaplePlayerNPC) mmo);
}
Collections.sort(playerNpcs, new Comparator<MaplePlayerNPC>() {
@Override
public int compare(MaplePlayerNPC p1, MaplePlayerNPC p2) {
return p1.getScriptId() - p2.getScriptId(); // scriptid as playernpc history
}
});
for(Channel ch : Server.getInstance().getChannelsFromWorld(world)) {
MapleMap map = ch.getMapFactory().getMap(mapid);
for(MaplePlayerNPC pn : playerNpcs) {
map.removeMapObject(pn);
map.broadcastMessage(MaplePacketCreator.removeNPCController(pn.getObjectId()));
map.broadcastMessage(MaplePacketCreator.removePlayerNPC(pn.getObjectId()));
}
}
Point ret = rearrangePlayerNpcs(newStep, playerNpcs);
for(Channel ch : Server.getInstance().getChannelsFromWorld(world)) {
MapleMap map = ch.getMapFactory().getMap(mapid);
for(MaplePlayerNPC pn : playerNpcs) {
map.addPlayerNPCMapObject(pn);
map.broadcastMessage(MaplePacketCreator.spawnPlayerNPC(pn));
map.broadcastMessage(MaplePacketCreator.getPlayerNPC(pn));
}
}
return ret;
}
return null;
}
public Point getNextPlayerNpcPosition() {
return getNextPlayerNpcPosition(Server.getInstance().getWorld(world).getPlayerNpcMapStep(mapid));
}
private Point getNextPlayerNpcPosition(int initStep) { // automated playernpc position thanks to Ronan
List<MapleMapObject> mmoList = getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER_NPC));
List<Point> otherPlayerNpcs = new LinkedList<>();
for(MapleMapObject mmo : mmoList) {
otherPlayerNpcs.add(mmo.getPosition());
}
int cx = calcDx(initStep), cy = calcDy(initStep);
int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height;
boolean reorganize = false;
int i = initStep;
while(i < ServerConstants.PLAYERNPC_AREA_STEPS) {
int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y;
while(py < outy) {
px = leftPx;
while(px < outx) {
Point searchPos = calcPointBelow(new Point(px, py));
if(searchPos != null) {
if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) {
if(i > initStep) {
Server.getInstance().getWorld(world).setPlayerNpcMapStep(mapid, i);
}
if(reorganize && ServerConstants.PLAYERNPC_ORGANIZE_AREA) {
return reorganizePlayerNpcs(i, mmoList);
}
return searchPos;
}
}
px += cx;
}
py += cy;
}
reorganize = true;
i++;
cx = calcDx(i);
cy = calcDy(i);
if(ServerConstants.PLAYERNPC_ORGANIZE_AREA) {
otherPlayerNpcs = rearrangePlayerNpcPositions(i, mmoList.size());
}
}
if(i > initStep) {
Server.getInstance().getWorld(world).setPlayerNpcMapStep(mapid, ServerConstants.PLAYERNPC_AREA_STEPS - 1);
}
return null;
}
}
+23 -15
View File
@@ -27,12 +27,12 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import tools.locks.MonitoredReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
@@ -44,6 +44,8 @@ import server.PortalFactory;
import server.life.AbstractLoadedMapleLife;
import server.life.MapleLifeFactory;
import server.life.MapleMonster;
import server.life.MaplePlayerNPC;
import server.life.MaplePlayerNPCFactory;
import scripting.event.EventInstanceManager;
import tools.DatabaseConnection;
import tools.StringUtil;
@@ -198,22 +200,30 @@ public class MapleMapFactory {
map.addMapleArea(new Rectangle(x1, y1, (x2 - x1), (y2 - y1)));
}
}
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM playernpcs WHERE map = ?")) {
ps.setInt(1, omapid);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
map.addMapObject(new PlayerNPCs(rs));
if(event == null) {
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM playernpcs WHERE map = ? AND world = ?")) {
ps.setInt(1, omapid);
ps.setInt(2, world);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
map.addPlayerNPCMapObject(new MaplePlayerNPC(rs));
}
}
}
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
List<MaplePlayerNPC> dnpcs = MaplePlayerNPCFactory.getDeveloperNpcsFromMapid(mapid);
if(dnpcs != null) {
for(MaplePlayerNPC dnpc : dnpcs) {
map.addPlayerNPCMapObject(dnpc);
}
}
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
for (MapleData life : mapData.getChildByPath("life")) {
String id = MapleDataTool.getString(life.getChildByPath("id"));
String type = MapleDataTool.getString(life.getChildByPath("type"));
@@ -319,8 +329,6 @@ public class MapleMapFactory {
private AbstractLoadedMapleLife loadLife(MapleData life, String id, String type) {
AbstractLoadedMapleLife myLife = MapleLifeFactory.getLife(Integer.parseInt(id), type);
if(life == null) System.out.println("lf null");
if(myLife == null) System.out.println("mlf null");
myLife.setCy(MapleDataTool.getInt(life.getChildByPath("cy")));
MapleData dF = life.getChildByPath("f");
if (dF != null) {
+1 -1
View File
@@ -22,5 +22,5 @@
package server.maps;
public enum MapleMapObjectType {
NPC, MONSTER, ITEM, PLAYER, DOOR, SUMMON, SHOP, MINI_GAME, MIST, REACTOR, HIRED_MERCHANT, PLAYER_NPC, DRAGON;
NPC, MONSTER, ITEM, PLAYER, DOOR, SUMMON, SHOP, MINI_GAME, MIST, REACTOR, HIRED_MERCHANT, PLAYER_NPC, DRAGON, KITE;
}
-129
View File
@@ -1,129 +0,0 @@
/*
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 server.maps;
import java.awt.Point;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import client.MapleClient;
import java.sql.Connection;
import java.sql.SQLException;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
/**
*
* @author XoticStory
*/
public class PlayerNPCs extends AbstractMapleMapObject {
private Map<Short, Integer> equips = new HashMap<Short, Integer>();
private int npcId, face, hair;
private byte skin;
private String name = "";
private int FH, RX0, RX1, CY;
public PlayerNPCs(ResultSet rs) {
try {
CY = rs.getInt("cy");
name = rs.getString("name");
hair = rs.getInt("hair");
face = rs.getInt("face");
skin = rs.getByte("skin");
FH = rs.getInt("Foothold");
RX0 = rs.getInt("rx0");
RX1 = rs.getInt("rx1");
npcId = rs.getInt("ScriptId");
setPosition(new Point(rs.getInt("x"), CY));
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT equippos, equipid FROM playernpcs_equip WHERE NpcId = ?");
ps.setInt(1, rs.getInt("id"));
ResultSet rs2 = ps.executeQuery();
while (rs2.next()) {
equips.put(rs2.getShort("equippos"), rs2.getInt("equipid"));
}
rs2.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Map<Short, Integer> getEquips() {
return equips;
}
public int getId() {
return npcId;
}
public int getFH() {
return FH;
}
public int getRX0() {
return RX0;
}
public int getRX1() {
return RX1;
}
public int getCY() {
return CY;
}
public byte getSkin() {
return skin;
}
public String getName() {
return name;
}
public int getFace() {
return face;
}
public int getHair() {
return hair;
}
@Override
public void sendDestroyData(MapleClient client) {
return;
}
@Override
public MapleMapObjectType getType() {
return MapleMapObjectType.PLAYER_NPC;
}
@Override
public void sendSpawnData(MapleClient client) {
client.announce(MaplePacketCreator.spawnPlayerNPC(this));
client.announce(MaplePacketCreator.getPlayerNPC(this));
}
}
+9 -8
View File
@@ -22,15 +22,16 @@
package server.maps;
public enum SavedLocationType {
FREE_MARKET,
EVENT,
WORLDTOUR,
FLORINA,
INTRO,
SUNDAY_MARKET,
MIRROR,
FREE_MARKET,
WORLDTOUR,
FLORINA,
INTRO,
SUNDAY_MARKET,
MIRROR,
EVENT,
BOSSPQ,
HAPPYVILLE;
HAPPYVILLE,
DEVELOPER;
public static SavedLocationType fromString(String Str) {
return valueOf(Str);
+15
View File
@@ -30,6 +30,7 @@ import java.util.Map;
import client.MapleCharacter;
import client.MapleQuestStatus;
import client.MapleQuestStatus.Status;
import constants.ServerConstants;
import java.util.EnumMap;
import java.util.Set;
import provider.MapleData;
@@ -39,6 +40,7 @@ import provider.MapleDataTool;
import server.quest.actions.*;
import server.quest.requirements.*;
import tools.MaplePacketCreator;
import tools.StringUtil;
/**
*
@@ -323,6 +325,19 @@ public class MapleQuest {
}
c.updateQuest(newStatus);
if(id / 100 == 35 && ServerConstants.TOT_MOB_QUEST_REQUIREMENT > 0) {
int setProg = 999 - Math.min(999, ServerConstants.TOT_MOB_QUEST_REQUIREMENT);
for(Integer pid : newStatus.getProgress().keySet()) {
if(pid >= 8200000 && pid <= 8200012) {
String pr = StringUtil.getLeftPaddedStr(Integer.toString(setProg), '0', 3);
newStatus.setProgress(pid, pr);
c.announce(MaplePacketCreator.updateQuest(newStatus, false));
}
}
}
return true;
}
+139 -48
View File
@@ -72,7 +72,7 @@ import server.maps.MaplePlayerShop;
import server.maps.MaplePlayerShopItem;
import server.maps.MapleReactor;
import server.maps.MapleSummon;
import server.maps.PlayerNPCs;
import server.life.MaplePlayerNPC;
import server.movement.LifeMovementFragment;
import server.partyquest.MonsterCarnivalParty;
import tools.data.output.LittleEndianWriter;
@@ -1074,7 +1074,7 @@ public class MaplePacketCreator {
mplew.writePos(pos);
return mplew.getPacket();
}
/**
* Gets a packet to remove a door.
*
@@ -1135,6 +1135,32 @@ public class MaplePacketCreator {
mplew.write(animated ? 4 : 1); // ?
return mplew.getPacket();
}
public static byte[] spawnKite(int oid, int itemid, String name, String msg, Point pos, int ft) {
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.SPAWN_KITE.getValue());
mplew.writeInt(oid);
mplew.writeInt(itemid);
mplew.writeMapleAsciiString(msg);
mplew.writeMapleAsciiString(name);
mplew.writeShort(pos.x);
mplew.writeShort(ft);
return mplew.getPacket();
}
public static byte[] removeKite(int objectid, int animationType) { // thanks to Arnah
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.REMOVE_KITE.getValue());
mplew.write(animationType); // 0 is 10/10, 1 just vanishes
mplew.writeInt(objectid);
return mplew.getPacket();
}
public static byte[] sendCannotSpawnKite() {
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CANNOT_SPAWN_KITE.getValue());
return mplew.getPacket();
}
/**
* Gets the response to a relog request.
@@ -4754,47 +4780,6 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
public static byte[] getPlayerNPC(PlayerNPCs npc) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.IMITATED_NPC_DATA.getValue());
mplew.write(0x01);
mplew.writeInt(npc.getId());
mplew.writeMapleAsciiString(npc.getName());
mplew.write(0); // direction
mplew.write(npc.getSkin());
mplew.writeInt(npc.getFace());
mplew.write(0);
mplew.writeInt(npc.getHair());
Map<Short, Integer> equip = npc.getEquips();
Map<Short, Integer> myEquip = new LinkedHashMap<>();
for (short position : equip.keySet()) {
short pos = (short) (position * -1);
if (pos > 100) {
pos -= 100;
myEquip.put(pos, equip.get(position));
} else {
if (myEquip.get(pos) == null) {
myEquip.put(pos, equip.get(position));
}
}
}
for (Entry<Short, Integer> entry : myEquip.entrySet()) {
mplew.write(entry.getKey());
mplew.writeInt(entry.getValue());
}
mplew.writeShort(-1);
Integer cWeapon = equip.get((byte) -111);
if (cWeapon != null) {
mplew.writeInt(cWeapon);
} else {
mplew.writeInt(0);
}
for (int i = 0; i < 12; i++) {
mplew.write(0);
}
return mplew.getPacket();
}
public static byte[] updateAriantPQRanking(String name, int score, boolean empty) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.ARIANT_SCORE.getValue());
@@ -4871,7 +4856,7 @@ public class MaplePacketCreator {
mplew.write(0);
return mplew.getPacket();
}
public static byte[] getMiniGame(MapleClient c, MapleMiniGame minigame, boolean owner, int piece) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue());
@@ -5533,22 +5518,82 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
public static byte[] spawnPlayerNPC(PlayerNPCs npc) {
public static byte[] spawnPlayerNPC(MaplePlayerNPC npc) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.SPAWN_NPC_REQUEST_CONTROLLER.getValue());
mplew.write(1);
mplew.writeInt(npc.getObjectId());
mplew.writeInt(npc.getId());
mplew.writeInt(npc.getScriptId());
mplew.writeShort(npc.getPosition().x);
mplew.writeShort(npc.getCY());
mplew.write(1);
mplew.write(npc.getDirection());
mplew.writeShort(npc.getFH());
mplew.writeShort(npc.getRX0());
mplew.writeShort(npc.getRX1());
mplew.write(1);
return mplew.getPacket();
}
public static byte[] getPlayerNPC(MaplePlayerNPC npc) { // thanks to Arnah
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.IMITATED_NPC_DATA.getValue());
mplew.write(0x01);
mplew.writeInt(npc.getScriptId());
mplew.writeMapleAsciiString(npc.getName());
mplew.write(npc.getGender());
mplew.write(npc.getSkin());
mplew.writeInt(npc.getFace());
mplew.write(0);
mplew.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 == -128) && pos != 111){ // 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()){
mplew.write(entry.getKey());
mplew.writeInt(entry.getValue());
}
mplew.write(0xFF);
for(Entry<Short, Integer> entry : maskedEquip.entrySet()){
mplew.write(entry.getKey());
mplew.writeInt(entry.getValue());
}
mplew.write(0xFF);
Integer cWeapon = equip.get((byte) -111);
if(cWeapon != null){
mplew.writeInt(cWeapon);
}else{
mplew.writeInt(0);
}
for(int i = 0; i < 3; i++){
mplew.writeInt(0);
}
return mplew.getPacket();
}
public static byte[] removePlayerNPC(int oid) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.IMITATED_NPC_DATA.getValue());
mplew.write(0x00);
mplew.writeInt(oid);
for(int i = 0; i < ServerConstants.DEBUG_VALUES[0]; i++) mplew.writeInt(0);
return mplew.getPacket();
}
public static byte[] sendYellowTip(String tip) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.SET_WEEK_EVENT_MESSAGE.getValue());
@@ -7164,15 +7209,44 @@ public class MaplePacketCreator {
* 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
*
* 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
* 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 be purchased by anyone under 7
*/
public static byte[] showCashShopMessage(byte message) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(4);
@@ -7183,6 +7257,23 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
public static byte[] showCashShopSurprise(int itemid) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(ServerConstants.DEBUG_VALUES[0]);
for(int i = 0; i < ServerConstants.DEBUG_VALUES[1]; i++) {
mplew.write(0);
}
mplew.writeInt(itemid);
for(int i = 0; i < ServerConstants.DEBUG_VALUES[2]; i++) {
mplew.write(0);
}
return mplew.getPacket();
}
public static byte[] showCashInventory(MapleClient c) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();