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
+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);