Added cash shop name changes and world transfers. (#495)

* Name changes

* World transfers. Fix NPE. Applied Arnuh's suggestions. More logging.
This commit is contained in:
Ubaware
2019-07-31 20:22:48 -03:00
committed by Ronan Lana
parent 85812ba489
commit 153f5de4fa
14 changed files with 733 additions and 20 deletions
-2
View File
@@ -2,8 +2,6 @@
Uncoded features:
NX Format
Name Change
World transfer
MTS (v53)
Family system (v67)
Family and Medal Quests(?)
+22
View File
@@ -16342,6 +16342,17 @@ CREATE TABLE IF NOT EXISTS `mts_items` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `namechanges` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`characterid` int(11) NOT NULL,
`old` varchar(13) NOT NULL,
`new` varchar(13) NOT NULL,
`requestTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`completionTime` timestamp,
PRIMARY KEY (`id`),
INDEX (characterid)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `newyear` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`senderid` int(10) NOT NULL DEFAULT '-1',
@@ -21418,6 +21429,17 @@ CREATE TABLE IF NOT EXISTS `wishlists` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `worldtransfers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`characterid` int(11) NOT NULL,
`from` tinyint(3) NOT NULL,
`to` tinyint(3) NOT NULL,
`requestTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`completionTime` timestamp,
PRIMARY KEY (`id`),
INDEX (characterid)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
ALTER TABLE `dueyitems`
ADD CONSTRAINT `dueyitems_ibfk_1` FOREIGN KEY (`PackageId`) REFERENCES `dueypackages` (`PackageId`) ON DELETE CASCADE;
+374
View File
@@ -328,6 +328,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
private int banishSp = -1;
private long banishTime = 0;
private long lastExpGainTime;
private boolean pendingNameChange; //only used to change name on logout, not to be relied upon elsewhere
private MapleCharacter() {
super.setListener(new AbstractCharacterListener() {
@@ -10364,6 +10365,379 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
jailExpiration = 0;
}
public boolean registerNameChange(String newName) {
try (Connection con = DatabaseConnection.getConnection()) {
//check for pending name change
long currentTimeMillis = System.currentTimeMillis();
try (PreparedStatement ps = con.prepareStatement("SELECT completionTime FROM namechanges WHERE characterid=?")) { //double check, just in case
ps.setInt(1, getId());
ResultSet rs = ps.executeQuery();
while(rs.next()) {
Timestamp completedTimestamp = rs.getTimestamp("completionTime");
if(completedTimestamp == null) return false; //pending
else if(completedTimestamp.getTime() + ServerConstants.NAME_CHANGE_COOLDOWN > currentTimeMillis) return false;
}
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to register name change for character " + getName() + ".");
return false;
}
try (PreparedStatement ps = con.prepareStatement("INSERT INTO namechanges (characterid, old, new) VALUES (?, ?, ?)")){
ps.setInt(1, getId());
ps.setString(2, getName());
ps.setString(3, newName);
ps.executeUpdate();
this.pendingNameChange = true;
return true;
} catch (SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to register name change for character " + getName() + ".");
}
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to get DB connection.");
}
return false;
}
public boolean cancelPendingNameChange() {
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM namechanges WHERE characterid=? AND completionTime IS NULL")) {
ps.setInt(1, getId());
int affectedRows = ps.executeUpdate();
if(affectedRows > 0) pendingNameChange = false;
return affectedRows > 0; //rows affected
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to cancel name change for character " + getName() + ".");
return false;
}
}
public void doPendingNameChange() { //called on logout
if(!pendingNameChange) return;
try (Connection con = DatabaseConnection.getConnection()) {
int nameChangeId = -1;
String newName = null;
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM namechanges WHERE characterid = ? AND completionTime IS NULL")) {
ps.setInt(1, getId());
ResultSet rs = ps.executeQuery();
if(!rs.next()) return;
nameChangeId = rs.getInt("id");
newName = rs.getString("new");
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to retrieve pending name changes for character " + getName() + ".");
}
con.setAutoCommit(false);
boolean success = doNameChange(con, getId(), getName(), newName, nameChangeId);
if(!success) con.rollback();
else FilePrinter.print(FilePrinter.CHANGE_CHARACTER_NAME, "Name change applied : from \"" + getName() + "\" to \"" + newName + "\" at " + Calendar.getInstance().getTime().toString());
con.setAutoCommit(true);
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to get DB connection.");
}
}
public static void doNameChange(int characterId, String oldName, String newName, int nameChangeId) { //Don't do this while player is online
try (Connection con = DatabaseConnection.getConnection()) {
con.setAutoCommit(false);
boolean success = doNameChange(con, characterId, oldName, newName, nameChangeId);
if(!success) con.rollback();
con.setAutoCommit(true);
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to get DB connection.");
}
}
public static boolean doNameChange(Connection con, int characterId, String oldName, String newName, int nameChangeId) {
try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET name = ? WHERE id = ?")) {
ps.setString(1, newName);
ps.setInt(2, characterId);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE rings SET partnername = ? WHERE partnername = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
/*try (PreparedStatement ps = con.prepareStatement("UPDATE playernpcs SET name = ? WHERE name = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE gifts SET `from` = ? WHERE `from` = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE dueypackages SET SenderName = ? WHERE SenderName = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE dueypackages SET SenderName = ? WHERE SenderName = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE inventoryitems SET owner = ? WHERE owner = ?")) { //GMS doesn't do this
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE mts_items SET owner = ? WHERE owner = ?")) { //GMS doesn't do this
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE newyear SET sendername = ? WHERE sendername = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE newyear SET receivername = ? WHERE receivername = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE notes SET `to` = ? WHERE `to` = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE notes SET `from` = ? WHERE `from` = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE nxcode SET retriever = ? WHERE retriever = ?")) {
ps.setString(1, newName);
ps.setString(2, oldName);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}*/
if(nameChangeId != -1) {
try (PreparedStatement ps = con.prepareStatement("UPDATE namechanges SET completionTime = ? WHERE id = ?")) {
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
ps.setInt(2, nameChangeId);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Character ID : " + characterId);
return false;
}
}
return true;
}
public int checkWorldTransferEligibility() {
if(getLevel() < 20) {
return 2;
} else if(getClient().getTempBanCalendar() != null && getClient().getTempBanCalendar().getTimeInMillis() + (30*24*60*60*1000) < Calendar.getInstance().getTimeInMillis()) {
return 3;
} else if(isMarried()) {
return 4;
} else if(getGuildRank() < 2) {
return 5;
} else if(getFamily() != null) {
return 8;
} else {
return 0;
}
}
public static String checkWorldTransferEligibility(Connection con, int characterId, int oldWorld, int newWorld) {
if(!ServerConstants.ALLOW_CASHSHOP_WORLD_TRANSFER) return "World transfers disabled.";
int accountId = -1;
try (PreparedStatement ps = con.prepareStatement("SELECT accountid, level, guildid, guildrank, partnerId, familyId FROM characters WHERE id = ?")) {
ps.setInt(1, characterId);
ResultSet rs = ps.executeQuery();
if(!rs.next()) return "Character does not exist.";
accountId = rs.getInt("accountid");
if(rs.getInt("level") < 20) return "Character is under level 20.";
if(rs.getInt("familyId") != -1) return "Character is in family.";
if(rs.getInt("partnerId") != 0) return "Character is married.";
if(rs.getInt("guildid") != 0 && rs.getInt("guildrank") < 2) return "Character is the leader of a guild.";
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e);
return "SQL Error";
}
try (PreparedStatement ps = con.prepareStatement("SELECT tempban FROM accounts WHERE id = ?")) {
ps.setInt(1, accountId);
ResultSet rs = ps.executeQuery();
if(!rs.next()) return "Account does not exist.";
if(rs.getLong("tempban") != 0 && !rs.getString("tempban").equals("2018-06-20 00:00:00.0")) return "Account has been banned.";
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e);
return "SQL Error";
}
try (PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) AS rowcount FROM characters WHERE accountid = ? AND world = ?")) {
ps.setInt(1, accountId);
ps.setInt(2, newWorld);
ResultSet rs = ps.executeQuery();
if(!rs.next()) return "SQL Error";
if(rs.getInt("rowcount") >= 3) return "Too many characters on destination world.";
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e);
return "SQL Error";
}
return null;
}
public boolean registerWorldTransfer(int newWorld) {
try (Connection con = DatabaseConnection.getConnection()) {
//check for pending world transfer
long currentTimeMillis = System.currentTimeMillis();
try (PreparedStatement ps = con.prepareStatement("SELECT completionTime FROM worldtransfers WHERE characterid=?")) { //double check, just in case
ps.setInt(1, getId());
ResultSet rs = ps.executeQuery();
while(rs.next()) {
Timestamp completedTimestamp = rs.getTimestamp("completionTime");
if(completedTimestamp == null) return false; //pending
else if(completedTimestamp.getTime() + ServerConstants.WORLD_TRANSFER_COOLDOWN > currentTimeMillis) return false;
}
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Failed to register world transfer for character " + getName() + ".");
return false;
}
try (PreparedStatement ps = con.prepareStatement("INSERT INTO worldtransfers (characterid, `from`, `to`) VALUES (?, ?, ?)")){
ps.setInt(1, getId());
ps.setInt(2, getWorld());
ps.setInt(3, newWorld);
ps.executeUpdate();
return true;
} catch (SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Failed to register world transfer for character " + getName() + ".");
}
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Failed to get DB connection.");
}
return false;
}
public boolean cancelPendingWorldTranfer() {
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM worldtransfers WHERE characterid=? AND completionTime IS NULL")) {
ps.setInt(1, getId());
int affectedRows = ps.executeUpdate();
return affectedRows > 0; //rows affected
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Failed to cancel pending world transfer for character " + getName() + ".");
return false;
}
}
public static boolean doWorldTransfer(Connection con, int characterId, int oldWorld, int newWorld, int worldTransferId) {
int mesos = 0;
try (PreparedStatement ps = con.prepareStatement("SELECT meso FROM characters WHERE id = ?")) {
ps.setInt(1, characterId);
ResultSet rs = ps.executeQuery();
if(!rs.next()) {
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, "Character data invalid? (charid " + characterId + ")");
return false;
}
mesos = rs.getInt("meso");
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET world = ?, meso = ?, guildid = ?, guildrank = ? WHERE id = ?")) {
ps.setInt(1, newWorld);
ps.setInt(2, Math.min(mesos, 1000000)); //might want a limit in ServerConstants for this
ps.setInt(3, 0);
ps.setInt(4, 5);
ps.setInt(5, characterId);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Character ID : " + characterId);
return false;
}
try (PreparedStatement ps = con.prepareStatement("DELETE FROM buddies WHERE characterid = ? OR buddyid = ?")) {
ps.setInt(1, characterId);
ps.setInt(2, characterId);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Character ID : " + characterId);
return false;
}
if(worldTransferId != -1) {
try (PreparedStatement ps = con.prepareStatement("UPDATE worldtransfers SET completionTime = ? WHERE id = ?")) {
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
ps.setInt(2, worldTransferId);
ps.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Character ID : " + characterId);
return false;
}
}
return true;
}
public String getLastCommandMessage() {
return this.commandtext;
}
+18 -2
View File
@@ -121,6 +121,7 @@ public class MapleClient {
private final Lock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT, true);
private final Lock encoderLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_ENCODER, true);
private static final Lock loginLocks[] = new Lock[200]; // thanks Masterrulax & try2hack for pointing out a bottleneck issue here
private Calendar tempBanCalendar;
private int votePoints;
private int voteTime = -1;
private int visibleWorlds;
@@ -640,7 +641,7 @@ public class MapleClient {
}
}
public Calendar getTempBanCalendar() {
public Calendar getTempBanCalendarFromDB() {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
@@ -654,10 +655,12 @@ public class MapleClient {
return null;
}
long blubb = rs.getLong("tempban");
if (blubb == 0) { // basically if timestamp in db is 0000-00-00
if (blubb == 0 || rs.getString("tempban").equals("2018-06-20 00:00:00.0")) { // 0000-00-00 or 2018-06-20 (default set in LoginPasswordHandler)
return null;
}
lTempban.setTimeInMillis(rs.getTimestamp("tempban").getTime());
tempBanCalendar = lTempban;
return lTempban;
} catch (SQLException e) {
e.printStackTrace();
@@ -678,6 +681,14 @@ public class MapleClient {
}
return null;//why oh why!?!
}
public Calendar getTempBanCalendar() {
return tempBanCalendar;
}
public boolean hasBeenBanned() {
return tempBanCalendar != null;
}
public static long dottedQuadToLong(String dottedQuad) throws RuntimeException {
String[] quads = dottedQuad.split("\\.");
@@ -1017,6 +1028,7 @@ public class MapleClient {
player.saveCharToDB(true);
player.logOff();
if(ServerConstants.INSTANT_NAME_CHANGE) player.doPendingNameChange();
clear();
} else {
getChannelServer().removePlayer(player);
@@ -1329,6 +1341,10 @@ public class MapleClient {
return (short) Math.max(0, characterSlots - Server.getInstance().getAccountWorldCharacterCount(accId, world));
}
public short getAvailableCharacterWorldSlots(int world) {
return (short) Math.max(0, characterSlots - Server.getInstance().getAccountWorldCharacterCount(accId, world));
}
public short getCharacterSlots() {
return characterSlots;
}
+5
View File
@@ -126,6 +126,8 @@ public class ServerConstants {
//Cash Shop Configuration
public static final boolean USE_JOINT_CASHSHOP_INVENTORY = true;//Enables usage of a same cash shop inventory for explorers, cygnus and legends. Items from exclusive cash shop inventories won't show up on the shared inventory, though.
public static final boolean USE_CLEAR_OUTDATED_COUPONS = true; //Enables deletion of older code coupon registry from the DB, freeing so-long irrelevant data.
public static final boolean ALLOW_CASHSHOP_NAME_CHANGE = true; //Allows players to buy name changes in the cash shop.
public static final boolean ALLOW_CASHSHOP_WORLD_TRANSFER =true;//Allows players to buy world transfers in the cash shop.
//Maker Configuration
public static final boolean USE_MAKER_PERMISSIVE_ATKUP = true; //Allows players to use attack-based strengthening gems on non-weapon items.
@@ -169,6 +171,9 @@ public class ServerConstants {
public static final int TOT_MOB_QUEST_REQUIREMENT = 77; //Overwrites old 999-mobs requirement for the ToT questline with new requirement value, set 0 for default.
public static final int MOB_REACTOR_REFRESH_TIME = 30 * 1000; //Overwrites refresh time for those reactors oriented to inflict damage to bosses (Ice Queen, Riche), set 0 for default.
public static final int PARTY_SEARCH_REENTRY_LIMIT = 10; //Max amount of times a party leader is allowed to persist on the Party Search before entry expiration (thus needing to manually restart the Party Search to be able to search for members).
public static final int NAME_CHANGE_COOLDOWN = 30*24*60*60*1000; //Cooldown for name changes, default (GMS) is 30 days.
public static final int WORLD_TRANSFER_COOLDOWN=NAME_CHANGE_COOLDOWN;//Cooldown for world tranfers, default is same as name change (30 days).
public static final boolean INSTANT_NAME_CHANGE = false; //Whether or not to wait for server restart to apply name changes. Does on reconnect otherwise (requires queries on every login).
//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.
+79 -1
View File
@@ -103,6 +103,7 @@ import server.life.MaplePlayerNPCFactory;
import server.quest.MapleQuest;
import tools.AutoJCE;
import tools.DatabaseConnection;
import tools.FilePrinter;
import tools.Pair;
import org.apache.mina.core.session.IoSession;
@@ -881,7 +882,8 @@ public class Server {
} catch (SQLException sqle) {
sqle.printStackTrace();
}
applyAllNameChanges(); //name changes can be missed by INSTANT_NAME_CHANGE
applyAllWorldTransfers();
MaplePet.clearMissingPetsFromDb();
MapleCashidGenerator.loadExistentCashIdsFromDb();
@@ -1554,6 +1556,82 @@ public class Server {
}
}
private static void applyAllNameChanges() {
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM namechanges WHERE completionTime IS NULL")) {
ResultSet rs = ps.executeQuery();
List<Pair<String, String>> changedNames = new LinkedList<Pair<String, String>>(); //logging only
while(rs.next()) {
con.setAutoCommit(false);
int nameChangeId = rs.getInt("id");
int characterId = rs.getInt("characterId");
String oldName = rs.getString("old");
String newName = rs.getString("new");
boolean success = MapleCharacter.doNameChange(con, characterId, oldName, newName, nameChangeId);
if(!success) con.rollback(); //discard changes
else changedNames.add(new Pair<String, String>(oldName, newName));
con.setAutoCommit(true);
}
//log
for(Pair<String, String> namePair : changedNames) {
FilePrinter.print(FilePrinter.CHANGE_CHARACTER_NAME, "Name change applied : from \"" + namePair.getLeft() + "\" to \"" + namePair.getRight() + "\" at " + Calendar.getInstance().getTime().toString());
}
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to retrieve list of pending name changes.");
}
}
private static void applyAllWorldTransfers() {
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM worldtransfers WHERE completionTime IS NULL")) {
ResultSet rs = ps.executeQuery();
List<Integer> removedTransfers = new LinkedList<Integer>();
while(rs.next()) {
int nameChangeId = rs.getInt("id");
int characterId = rs.getInt("characterId");
int oldWorld = rs.getInt("from");
int newWorld = rs.getInt("to");
String reason = MapleCharacter.checkWorldTransferEligibility(con, characterId, oldWorld, newWorld); //check if character is still eligible
if(reason != null) {
removedTransfers.add(nameChangeId);
FilePrinter.print(FilePrinter.WORLD_TRANSFER, "World transfer cancelled : Character ID " + characterId + " at " + Calendar.getInstance().getTime().toString() + ", Reason : " + reason);
try (PreparedStatement delPs = con.prepareStatement("DELETE FROM worldtransfers WHERE id = ?")) {
delPs.setInt(1, nameChangeId);
delPs.executeUpdate();
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Failed to delete world transfer for character ID " + characterId);
}
}
}
rs.beforeFirst();
List<Pair<Integer, Pair<Integer, Integer>>> worldTransfers = new LinkedList<Pair<Integer, Pair<Integer, Integer>>>(); //logging only <charid, <oldWorld, newWorld>>
while(rs.next()) {
con.setAutoCommit(false);
int nameChangeId = rs.getInt("id");
if(removedTransfers.contains(nameChangeId)) continue;
int characterId = rs.getInt("characterId");
int oldWorld = rs.getInt("from");
int newWorld = rs.getInt("to");
boolean success = MapleCharacter.doWorldTransfer(con, characterId, oldWorld, newWorld, nameChangeId);
if(!success) con.rollback();
else worldTransfers.add(new Pair<Integer, Pair<Integer, Integer>>(characterId, new Pair<Integer, Integer>(oldWorld, newWorld)));
con.setAutoCommit(true);
}
//log
for(Pair<Integer, Pair<Integer, Integer>> worldTransferPair : worldTransfers) {
int charId = worldTransferPair.getLeft();
int oldWorld = worldTransferPair.getRight().getLeft();
int newWorld = worldTransferPair.getRight().getRight();
FilePrinter.print(FilePrinter.WORLD_TRANSFER, "World transfer applied : Character ID " + charId + " from World " + oldWorld + " to World " + newWorld + " at " + Calendar.getInstance().getTime().toString());
}
} catch(SQLException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Failed to retrieve list of pending world transfers.");
}
}
public void loadAccountCharacters(MapleClient c) {
Integer accId = c.getAccID();
if (!isFirstAccountLogin(accId)) {
@@ -34,6 +34,7 @@ import java.util.Calendar;
import java.util.List;
import java.util.Map;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import server.CashShop;
import server.CashShop.CashItem;
import server.CashShop.CashItemFactory;
@@ -343,7 +344,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
slea.readByte();
MapleCharacter partner = c.getChannelServer().getPlayerStorage().getCharacterByName(sentTo);
if (partner == null) {
chr.dropMessage(5, "The partner you specified cannot be found. Please make sure your partner is online and in the same channel.");
c.announce(MaplePacketCreator.showCashShopMessage((byte)0xBE));
} else {
// Need to check to make sure its actually an equip and the right SN...
if(itemRing.toItem() instanceof Equip) {
@@ -351,7 +352,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
Pair<Integer, Integer> rings = MapleRing.createRing(itemRing.getItemId(), chr, partner);
eqp.setRingId(rings.getLeft());
cs.addToInventory(eqp);
c.announce(MaplePacketCreator.showBoughtCashItem(eqp, c.getAccID()));
c.announce(MaplePacketCreator.showBoughtCashRing(eqp, partner.getName(), c.getAccID()));
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight());
cs.gainCash(payment, -itemRing.getPrice());
chr.addFriendshipRing(MapleRing.loadFromDb(rings.getLeft()));
@@ -368,6 +369,65 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
c.announce(MaplePacketCreator.showCash(c.getPlayer()));
} else if (action == 0x2E) { //name change
CashItem cItem = CashItemFactory.getItem(slea.readInt());
if (cItem == null || !canBuy(chr, cItem, cs.getCash(4))) {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
c.enableCSActions();
return;
}
if(cItem.getSN() == 50600000 && ServerConstants.ALLOW_CASHSHOP_NAME_CHANGE) {
slea.readMapleAsciiString(); //old name
String newName = slea.readMapleAsciiString();
if(!MapleCharacter.canCreateChar(newName) || chr.getLevel() < 10) { //(longest ban duration isn't tracked currently)
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
c.enableCSActions();
return;
} else if(c.getTempBanCalendar() != null && c.getTempBanCalendar().getTimeInMillis() + (30*24*60*60*1000) > Calendar.getInstance().getTimeInMillis()) {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
c.enableCSActions();
return;
}
if(chr.registerNameChange(newName)) { //success
Item item = cItem.toItem();
c.announce(MaplePacketCreator.showNameChangeSuccess(item, c.getAccID()));
cs.addToInventory(item);
cs.gainCash(4, cItem, chr.getWorld());
} else {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
}
}
c.enableCSActions();
} else if(action == 0x31) { //world transfer
CashItem cItem = CashItemFactory.getItem(slea.readInt());
if (cItem == null || !canBuy(chr, cItem, cs.getCash(4))) {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
c.enableCSActions();
return;
}
if(cItem.getSN() == 50600001 && ServerConstants.ALLOW_CASHSHOP_WORLD_TRANSFER) {
int newWorldSelection = slea.readInt();
int worldTransferError = chr.checkWorldTransferEligibility();
if(worldTransferError != 0 || newWorldSelection >= Server.getInstance().getWorldsSize() || Server.getInstance().getWorldsSize() <= 1) {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
return;
} else if(newWorldSelection == c.getWorld()) {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0xDC));
return;
} else if(c.getAvailableCharacterWorldSlots(newWorldSelection) < 1 || Server.getInstance().getAccountWorldCharacterCount(c.getAccID(), newWorldSelection) >= 3) {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0xDF));
return;
} else if(chr.registerWorldTransfer(newWorldSelection)) {
Item item = cItem.toItem();
c.announce(MaplePacketCreator.showWorldTransferSuccess(item, c.getAccID()));
cs.addToInventory(item);
cs.gainCash(4, cItem, chr.getWorld());
} else {
c.announce(MaplePacketCreator.showCashShopMessage((byte)0));
}
}
c.enableCSActions();
} else {
System.out.println("Unhandled action: " + action + "\n" + slea);
}
@@ -20,14 +20,25 @@
package net.server.channel.handlers;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.sql.Connection;
import client.MapleCharacter;
import client.MapleClient;
import constants.ServerConstants;
import net.AbstractMaplePacketHandler;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
/**
*
* @author Ronan
* @author Ubaware
*/
public final class TransferNameHandler extends AbstractMaplePacketHandler {
@@ -40,7 +51,37 @@ public final class TransferNameHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.enableActions());
return;
}
c.announce(MaplePacketCreator.sendNameTransferRules(4));
if(!ServerConstants.ALLOW_CASHSHOP_NAME_CHANGE) {
c.announce(MaplePacketCreator.sendNameTransferRules(4));
return;
}
MapleCharacter chr = c.getPlayer();
if(chr.getLevel() < 10) {
c.announce(MaplePacketCreator.sendNameTransferRules(4));
return;
} else if(c.getTempBanCalendar() != null && c.getTempBanCalendar().getTimeInMillis() + (30*24*60*60*1000) < Calendar.getInstance().getTimeInMillis()) {
c.announce(MaplePacketCreator.sendNameTransferRules(2));
return;
}
//sql queries
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT completionTime FROM namechanges WHERE characterid=?")) { //double check, just in case
ps.setInt(1, chr.getId());
ResultSet rs = ps.executeQuery();
while(rs.next()) {
Timestamp completedTimestamp = rs.getTimestamp("completionTime");
if(completedTimestamp == null) { //has pending name request
c.announce(MaplePacketCreator.sendNameTransferRules(1));
return;
} else if(completedTimestamp.getTime() + ServerConstants.NAME_CHANGE_COOLDOWN > System.currentTimeMillis()) {
c.announce(MaplePacketCreator.sendNameTransferRules(3));
return;
};
}
} catch(SQLException e) {
e.printStackTrace();
return;
}
c.announce(MaplePacketCreator.sendNameTransferRules(0));
}
}
@@ -35,6 +35,6 @@ public final class TransferNameResultHandler extends AbstractMaplePacketHandler
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
String name = slea.readMapleAsciiString();
c.announce(MaplePacketCreator.sendNameTransferCheck(MapleCharacter.canCreateChar(name)));
c.announce(MaplePacketCreator.sendNameTransferCheck(name, MapleCharacter.canCreateChar(name)));
}
}
@@ -20,14 +20,25 @@
package net.server.channel.handlers;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import client.MapleCharacter;
import client.MapleClient;
import constants.ServerConstants;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
/**
*
* @author Ronan
* @author Ubaware
*/
public final class TransferWorldHandler extends AbstractMaplePacketHandler {
@@ -40,7 +51,34 @@ public final class TransferWorldHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.enableActions());
return;
}
c.announce(MaplePacketCreator.sendWorldTransferRules(9));
MapleCharacter chr = c.getPlayer();
if(!ServerConstants.ALLOW_CASHSHOP_WORLD_TRANSFER || Server.getInstance().getWorldsSize() <= 1) {
c.announce(MaplePacketCreator.sendWorldTransferRules(9, c));
return;
}
int worldTransferError = chr.checkWorldTransferEligibility();
if(worldTransferError != 0) {
c.announce(MaplePacketCreator.sendWorldTransferRules(worldTransferError, c));
return;
}
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT completionTime FROM worldtransfers WHERE characterid=?")) {
ps.setInt(1, chr.getId());
ResultSet rs = ps.executeQuery();
while(rs.next()) {
Timestamp completedTimestamp = rs.getTimestamp("completionTime");
if(completedTimestamp == null) { //has pending world transfer
c.announce(MaplePacketCreator.sendWorldTransferRules(6, c));
return;
} else if(completedTimestamp.getTime() + ServerConstants.WORLD_TRANSFER_COOLDOWN > System.currentTimeMillis()) {
c.announce(MaplePacketCreator.sendWorldTransferRules(7, c));
return;
};
}
} catch(SQLException e) {
e.printStackTrace();
return;
}
c.announce(MaplePacketCreator.sendWorldTransferRules(0, c));
}
}
@@ -423,6 +423,24 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler {
}
}, 1000 * 10);
remove(c, position, itemId);
} else if (itemType == 540) {
slea.readByte();
slea.readInt();
if(itemId == 5400000) { //name change
if(player.cancelPendingNameChange()) {
player.dropMessage(1, "Successfully canceled pending name change.");
} else {
player.dropMessage(1, "You do not have a pending name change.");
}
} else if(itemId == 5401000) { //world transfer
if(player.cancelPendingWorldTranfer()) {
player.dropMessage(1, "Successfully canceled pending world transfer.");
} else {
player.dropMessage(1, "You do not have a pending world transfer.");
}
}
remove(c, position, itemId);
c.announce(MaplePacketCreator.enableActions());
} else if (itemType == 543) {
if(itemId == 5432000 && !c.gainCharacterSlot()) {
player.dropMessage(1, "You have already used up all 12 extra character slots.");
@@ -135,7 +135,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
c.announce(MaplePacketCreator.getLoginFailed(3));
return;
}
Calendar tempban = c.getTempBanCalendar();
Calendar tempban = c.getTempBanCalendarFromDB();
if (tempban != null) {
if (tempban.getTimeInMillis() > Calendar.getInstance().getTimeInMillis()) {
c.announce(MaplePacketCreator.getTempBan(tempban.getTimeInMillis(), c.getGReason()));
+2
View File
@@ -59,6 +59,8 @@ public class FilePrinter {
QUEST_UNCODED = "game/quests/UncodedQuests.txt",
AUTOSAVING_CHARACTER = "players/SaveCharAuto.txt",
SAVING_CHARACTER = "players/SaveChar.txt",
CHANGE_CHARACTER_NAME = "players/NameChange.txt",
WORLD_TRANSFER = "players/WorldTransfer.txt",
USED_COMMANDS = "commands/UsedCommands.txt",
DEADLOCK_ERROR = "deadlocks/Deadlocks.txt",
DEADLOCK_STACK = "deadlocks/Path.txt",
+68 -7
View File
@@ -81,6 +81,7 @@ import net.server.guild.MapleGuildSummary;
import net.server.world.MapleParty;
import net.server.world.MaplePartyCharacter;
import net.server.world.PartyOperation;
import net.server.world.World;
import server.CashShop.CashItem;
import server.CashShop.CashItemFactory;
import server.CashShop.SpecialCashItem;
@@ -6091,18 +6092,33 @@ public class MaplePacketCreator {
8: must quit family,
9: unknown error
*/
public static byte[] sendWorldTransferRules(int error) {
public static byte[] sendWorldTransferRules(int error, MapleClient c) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CASHSHOP_CHECK_TRANSFER_WORLD_POSSIBLE_RESULT.getValue());
mplew.writeInt(0);
mplew.write(0);
mplew.writeInt(0); //ignored
mplew.write(error);
mplew.writeInt(0);
mplew.writeBool(error == 0); //0 = ?, otherwise list servers
if(error == 0) {
List<World> worlds = Server.getInstance().getWorlds();
mplew.writeInt(worlds.size());
for(World world : worlds) {
mplew.writeMapleAsciiString(GameConstants.WORLD_NAMES[world.getId()]);
}
}
return mplew.getPacket();
}
/* 1: name change already submitted
public static byte[] showWorldTransferSuccess(Item item, int accountId) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CASHSHOP_OPERATION.getValue());
mplew.write(0xA0);
addCashItemInformation(mplew, item, accountId);
return mplew.getPacket();
}
/* 0: no error, send rules
1: name change already submitted
2: name change within a month
3: recently banned
4: unknown error
@@ -6117,15 +6133,28 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
/* 0: Name available
* >0: Name is in use
* <0: Unknown error
*/
public static byte[] sendNameTransferCheck(boolean canUseName) {
public static byte[] sendNameTransferCheck(String availableName, boolean canUseName) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CASHSHOP_CHECK_NAME_CHANGE.getValue());
mplew.writeShort(0);
//Send provided name back to client to add to temporary cache of checked & accepted names
mplew.writeMapleAsciiString(availableName);
mplew.writeBool(!canUseName);
return mplew.getPacket();
}
public static byte[] showNameChangeSuccess(Item item, int accountId) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CASHSHOP_OPERATION.getValue());
mplew.write(0x9E);
addCashItemInformation(mplew, item, accountId);
return mplew.getPacket();
}
public static byte[] showMTSCash(MapleCharacter p) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.MTS_OPERATION2.getValue());
@@ -7708,8 +7737,20 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
public static byte[] showBoughtCashRing(Item ring, String recipient, int accountId) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CASHSHOP_OPERATION.getValue());
mplew.write(0x87);
addCashItemInformation(mplew, ring, accountId);
mplew.writeMapleAsciiString(recipient);
mplew.writeInt(ring.getItemId());
mplew.writeShort(1); //quantity
return mplew.getPacket();
}
/*
* 00 = Due to an unknown error, failed
* A3 = Request timed out. Please try again.
* A4 = Due to an unknown error, failed + warpout
* A5 = You don't have enough cash.
* A6 = long as shet msg
@@ -7726,6 +7767,7 @@ public class MaplePacketCreator {
* B2 = Expired Coupon
* B3 = Coupon has been used already
* B4 = Nexon internet cafes? lolfk
* B8 = Due to gender restrictions, the coupon cannot be used.
* BB = inv full
* BC = long as shet "(not?) available to purchase by a use at the premium" msg
* BD = invalid gift recipient
@@ -7738,6 +7780,8 @@ public class MaplePacketCreator {
* C4 = check birthday code
* C7 = only available to users buying cash item, whatever msg too long
* C8 = already applied for this
* CD = You have reached the daily purchase limit for the cash shop.
* D0 = coupon account limit reached
* D2 = coupon system currently unavailable
* D3 = item can only be used 15 days after registration
* D4 = not enough gift tokens
@@ -7851,6 +7895,23 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
public static byte[] deleteCashItem(Item item) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CASHSHOP_OPERATION.getValue());
mplew.write(0x6C);
mplew.writeLong(item.getCashId());
return mplew.getPacket();
}
public static byte[] refundCashItem(Item item, int maplePoints) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CASHSHOP_OPERATION.getValue());
mplew.write(0x85);
mplew.writeLong(item.getCashId());
mplew.writeInt(maplePoints);
return mplew.getPacket();
}
public static byte[] putIntoCashInventory(Item item, int accountId) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();