From 2d7d113458e56d11d56b697966763fe8339af234 Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 26 Dec 2022 13:21:35 +0100 Subject: [PATCH 01/17] Initial jdbi setup --- pom.xml | 7 +++++++ src/main/java/tools/DatabaseConnection.java | 20 +++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1a333333a6..c5ac497114 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ 1.0 5.0.1 8.0.30 + 3.35.0 5.9.0 4.7.0 @@ -59,6 +60,12 @@ mysql-connector-java ${mysql-connector-java.version} + + org.jdbi + jdbi3-core + ${jdbi-version} + + diff --git a/src/main/java/tools/DatabaseConnection.java b/src/main/java/tools/DatabaseConnection.java index 337c07b14e..a5607477a6 100644 --- a/src/main/java/tools/DatabaseConnection.java +++ b/src/main/java/tools/DatabaseConnection.java @@ -3,9 +3,12 @@ package tools; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import config.YamlConfig; +import org.jdbi.v3.core.Handle; +import org.jdbi.v3.core.Jdbi; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.time.Duration; @@ -21,15 +24,24 @@ import static java.util.concurrent.TimeUnit.SECONDS; public class DatabaseConnection { private static final Logger log = LoggerFactory.getLogger(DatabaseConnection.class); private static HikariDataSource dataSource; + private static Jdbi jdbi; public static Connection getConnection() throws SQLException { if (dataSource == null) { - throw new IllegalStateException("Unable to get connection from uninitialized connection pool"); + throw new IllegalStateException("Unable to get connection - connection pool is uninitialized"); } return dataSource.getConnection(); } + public static Handle getHandle() { + if (jdbi == null) { + throw new IllegalStateException("Unable to get handle - connection pool is uninitialized"); + } + + return jdbi.open(); + } + private static String getDbUrl() { // Environment variables override what's defined in the config file // This feature is used for the Docker support @@ -73,6 +85,7 @@ public class DatabaseConnection { Instant initStart = Instant.now(); try { dataSource = new HikariDataSource(config); + initializeJdbi(dataSource); long initDuration = Duration.between(initStart, Instant.now()).toMillis(); log.info("Connection pool initialized in {} ms", initDuration); return true; @@ -84,4 +97,9 @@ public class DatabaseConnection { // Timed out - failed to initialize return false; } + + private static void initializeJdbi(DataSource dataSource) { + // TODO: configure row mappers + jdbi = Jdbi.create(dataSource); + } } From 188eb74a70561f343f1999a2d3561d3dcfe1742e Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 26 Dec 2022 14:54:35 +0100 Subject: [PATCH 02/17] Add Note model object --- src/main/java/model/Note.java | 21 +++++++++++++++++++++ src/test/java/model/NoteTest.java | 28 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/main/java/model/Note.java create mode 100644 src/test/java/model/NoteTest.java diff --git a/src/main/java/model/Note.java b/src/main/java/model/Note.java new file mode 100644 index 0000000000..677f096c5c --- /dev/null +++ b/src/main/java/model/Note.java @@ -0,0 +1,21 @@ +package model; + +import java.util.Objects; + +public record Note(int id, String message, String from, String to, long timestamp, int fame) { + private static final int PLACEHOLDER_ID = -1; + + public Note { + Objects.requireNonNull(message); + Objects.requireNonNull(from); + Objects.requireNonNull(to); + } + + public Note createNormal(String message, String from, String to, long timestamp) { + return new Note(PLACEHOLDER_ID, message, from, to, timestamp, 0); + } + + public Note createGift(String message, String from, String to, long timestamp) { + return new Note(PLACEHOLDER_ID, message, from, to, timestamp, 1); + } +} diff --git a/src/test/java/model/NoteTest.java b/src/test/java/model/NoteTest.java new file mode 100644 index 0000000000..1779e3f258 --- /dev/null +++ b/src/test/java/model/NoteTest.java @@ -0,0 +1,28 @@ +package model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class NoteTest { + + @Test + void requireNonNullMessage() { + assertThrows(NullPointerException.class, () -> new Note(1, null, "from", "to", System.currentTimeMillis(), 0)); + } + + @Test + void requireNonNullFrom() { + assertThrows(NullPointerException.class, () -> new Note(2, "message", null, "to", System.currentTimeMillis(), 0)); + } + + @Test + void requireNonNullTo() { + assertThrows(NullPointerException.class, () -> new Note(3, "message", "from", null, System.currentTimeMillis(), 0)); + } + + @Test + void createNew() { + assertDoesNotThrow(() -> new Note(4, "message", "from", "to", System.currentTimeMillis(), 5)); + } +} \ No newline at end of file From 605f2e212e2a9889bda04dbe61f593b321b85810 Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 26 Dec 2022 15:55:28 +0100 Subject: [PATCH 03/17] Add NoteDao, used by NoteActionHandler to delete note when read --- src/main/java/database/DaoException.java | 10 +++ src/main/java/database/NoteDao.java | 88 +++++++++++++++++++ src/main/java/database/NoteRowMapper.java | 22 +++++ src/main/java/model/Note.java | 4 +- .../channel/handlers/NoteActionHandler.java | 44 +++++----- src/main/java/tools/DatabaseConnection.java | 5 +- 6 files changed, 147 insertions(+), 26 deletions(-) create mode 100644 src/main/java/database/DaoException.java create mode 100644 src/main/java/database/NoteDao.java create mode 100644 src/main/java/database/NoteRowMapper.java diff --git a/src/main/java/database/DaoException.java b/src/main/java/database/DaoException.java new file mode 100644 index 0000000000..3190d233ce --- /dev/null +++ b/src/main/java/database/DaoException.java @@ -0,0 +1,10 @@ +package database; + +import org.jdbi.v3.core.JdbiException; + +public class DaoException extends JdbiException { + + public DaoException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/database/NoteDao.java b/src/main/java/database/NoteDao.java new file mode 100644 index 0000000000..d945505cdf --- /dev/null +++ b/src/main/java/database/NoteDao.java @@ -0,0 +1,88 @@ +package database; + +import model.Note; +import org.jdbi.v3.core.Handle; +import org.jdbi.v3.core.JdbiException; +import tools.DatabaseConnection; + +import java.util.List; +import java.util.Optional; + +public class NoteDao { + + public static void save(Note note) { + try (Handle handle = DatabaseConnection.getHandle()) { + handle.createScript(""" + INSERT INTO notes (`message`, `from`, `to`, `timestamp`, `fame`, `deleted`) + VALUES (?, ?, ?, ?, ?, ?)""") + .bind(0, note.message()) + .bind(1, note.from()) + .bind(2, note.to()) + .bind(3, note.timestamp()) + .bind(4, note.fame()) + .bind(5, 0) + .execute(); + } catch (JdbiException e) { + throw new DaoException("Failed to save note: %s".formatted(note.toString()), e); + } + } + + public static List findAllByTo(String to) { + try (Handle handle = DatabaseConnection.getHandle()) { + return handle.createQuery(""" + SELECT * + FROM notes + WHERE `deleted` = 0 + AND `to` = ?""") + .bind(0, to) + .mapTo(Note.class) + .list(); + } catch (JdbiException e) { + throw new DaoException("Failed to find notes with \"to\": %s".formatted(to), e); + } + } + + public static Optional delete(int id) { + try (Handle handle = DatabaseConnection.getHandle()) { + Optional note = findById(handle, id); + if (note.isEmpty()) { + return Optional.empty(); + } + deleteById(handle, id); + + return note; + } catch (JdbiException e) { + throw new DaoException("Failed to delete note with id: %d".formatted(id), e); + } + } + + private static Optional findById(Handle handle, int id) { + final Optional note; + try { + note = handle.createQuery(""" + SELECT * + FROM notes + WHERE `deleted` = 0 + AND `id` = ?""") + .bind(0, id) + .mapTo(Note.class) + .findOne(); + } catch (JdbiException e) { + throw new DaoException("Failed find note with id %s".formatted(id), e); + } + return note; + } + + private static void deleteById(Handle handle, int id) { + try { + handle.createUpdate(""" + UPDATE notes + SET `deleted` = 1 + WHERE `id` = ?""") + .bind(0, id) + .execute(); + } catch (JdbiException e) { + throw new DaoException("Failed to delete note with id %d".formatted(id), e); + } + } +} diff --git a/src/main/java/database/NoteRowMapper.java b/src/main/java/database/NoteRowMapper.java new file mode 100644 index 0000000000..b035741859 --- /dev/null +++ b/src/main/java/database/NoteRowMapper.java @@ -0,0 +1,22 @@ +package database; + +import model.Note; +import org.jdbi.v3.core.mapper.RowMapper; +import org.jdbi.v3.core.statement.StatementContext; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class NoteRowMapper implements RowMapper { + + @Override + public Note map(ResultSet rs, StatementContext ctx) throws SQLException { + int id = rs.getInt("id"); + String message = rs.getString("message"); + String from = rs.getString("from"); + String to = rs.getString("to"); + long timestamp = rs.getLong("timestamp"); + int fame = rs.getInt("fame"); + return new Note(id, message, from, to, timestamp, fame); + } +} diff --git a/src/main/java/model/Note.java b/src/main/java/model/Note.java index 677f096c5c..9b85643d8d 100644 --- a/src/main/java/model/Note.java +++ b/src/main/java/model/Note.java @@ -11,11 +11,11 @@ public record Note(int id, String message, String from, String to, long timestam Objects.requireNonNull(to); } - public Note createNormal(String message, String from, String to, long timestamp) { + public static Note createNormal(String message, String from, String to, long timestamp) { return new Note(PLACEHOLDER_ID, message, from, to, timestamp, 0); } - public Note createGift(String message, String from, String to, long timestamp) { + public static Note createGift(String message, String from, String to, long timestamp) { return new Note(PLACEHOLDER_ID, message, from, to, timestamp, 1); } } diff --git a/src/main/java/net/server/channel/handlers/NoteActionHandler.java b/src/main/java/net/server/channel/handlers/NoteActionHandler.java index 1918820e7e..7c963691d6 100644 --- a/src/main/java/net/server/channel/handlers/NoteActionHandler.java +++ b/src/main/java/net/server/channel/handlers/NoteActionHandler.java @@ -22,19 +22,23 @@ package net.server.channel.handlers; import client.Client; +import database.DaoException; +import database.NoteDao; +import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; -import tools.DatabaseConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tools.PacketCreator; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Optional; public final class NoteActionHandler extends AbstractPacketHandler { + private static final Logger log = LoggerFactory.getLogger(NoteActionHandler.class); + @Override - public final void handlePacket(InPacket p, Client c) { + public void handlePacket(InPacket p, Client c) { int action = p.readByte(); if (action == 0 && c.getPlayer().getCashShop().getAvailableNotes() > 0) { String charname = p.readString(); @@ -58,24 +62,20 @@ public final class NoteActionHandler extends AbstractPacketHandler { int id = p.readInt(); p.readByte(); //Fame, but we read it from the database :) - try (Connection con = DatabaseConnection.getConnection()) { - try (PreparedStatement ps = con.prepareStatement("SELECT `fame` FROM notes WHERE id=? AND deleted=0")) { - ps.setInt(1, id); - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - fame += rs.getInt("fame"); - } - - } - } - - try (PreparedStatement ps = con.prepareStatement("UPDATE notes SET `deleted` = 1 WHERE id = ?")) { - ps.setInt(1, id); - ps.executeUpdate(); - } - } catch (SQLException e) { - e.printStackTrace(); + final Optional note; + try { + note = NoteDao.delete(id); + } catch (DaoException e) { + log.error("Failed to delete note {}", id, e); + continue; } + + if (note.isEmpty()) { + log.warn("Note with id {} not able to be deleted. Already deleted?", id); + continue; + } + + fame += note.get().fame(); } if (fame > 0) { c.getPlayer().gainFame(fame); diff --git a/src/main/java/tools/DatabaseConnection.java b/src/main/java/tools/DatabaseConnection.java index a5607477a6..433f90ae19 100644 --- a/src/main/java/tools/DatabaseConnection.java +++ b/src/main/java/tools/DatabaseConnection.java @@ -3,6 +3,7 @@ package tools; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import config.YamlConfig; +import database.NoteRowMapper; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import org.slf4j.Logger; @@ -99,7 +100,7 @@ public class DatabaseConnection { } private static void initializeJdbi(DataSource dataSource) { - // TODO: configure row mappers - jdbi = Jdbi.create(dataSource); + jdbi = Jdbi.create(dataSource) + .registerRowMapper(new NoteRowMapper()); } } From 1f4ce98998a02dd9ead411b7a99d7c676b73e118 Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 26 Dec 2022 16:48:00 +0100 Subject: [PATCH 04/17] Show notes using NoteDao --- src/main/java/client/Character.java | 22 +++++++++++----------- src/main/java/tools/PacketCreator.java | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 660b02db29..3bae79a54d 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -41,6 +41,9 @@ import constants.id.MapId; import constants.id.MobId; import constants.inventory.ItemConstants; import constants.skills.*; +import database.DaoException; +import database.NoteDao; +import model.Note; import net.packet.Packet; import net.server.PlayerBuffValueHolder; import net.server.PlayerCoolDownValueHolder; @@ -9641,18 +9644,15 @@ public class Character extends AbstractCharacterObject { } public void showNote() { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT * FROM notes WHERE `to` = ? AND `deleted` = 0", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)) { - ps.setString(1, this.getName()); - try (ResultSet rs = ps.executeQuery()) { - rs.last(); - int count = rs.getRow(); - rs.first(); - sendPacket(PacketCreator.showNotes(rs, count)); - } - } catch (SQLException e) { - e.printStackTrace(); + final List notes; + try { + notes = NoteDao.findAllByTo(name); + } catch (DaoException e) { + log.error("Failed to find notes for chr name {}", name, e); + return; } + + sendPacket(PacketCreator.showNotes(notes)); } public void silentGiveBuffs(List> buffs) { diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index c5e352bfe8..2fb79103db 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -40,6 +40,7 @@ import constants.inventory.ItemConstants; import constants.skills.Buccaneer; import constants.skills.Corsair; import constants.skills.ThunderBreaker; +import model.Note; import net.encryption.InitializationVector; import net.opcodes.SendOpcode; import net.packet.ByteBufOutPacket; @@ -5422,18 +5423,17 @@ public class PacketCreator { return p; } - public static Packet showNotes(ResultSet notes, int count) throws SQLException { + public static Packet showNotes(List notes) { final OutPacket p = OutPacket.create(SendOpcode.MEMO_RESULT); p.writeByte(3); - p.writeByte(count); - for (int i = 0; i < count; i++) { - p.writeInt(notes.getInt("id")); - p.writeString(notes.getString("from") + " ");//Stupid nexon forgot space lol - p.writeString(notes.getString("message")); - p.writeLong(getTime(notes.getLong("timestamp"))); - p.writeByte(notes.getByte("fame"));//FAME :D - notes.next(); - } + p.writeByte(notes.size()); + notes.forEach(note -> { + p.writeInt(note.id()); + p.writeString(note.from() + " ");//Stupid nexon forgot space lol + p.writeString(note.message()); + p.writeLong(getTime(note.timestamp())); + p.writeByte(note.fame());//FAME :D + }); return p; } From 4d480660b5048b97979d0322fad61a8ee98698e7 Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 26 Dec 2022 17:03:40 +0100 Subject: [PATCH 05/17] Rework sending notes to chr, get rid of the first method Sending notes should not be the handled by Character --- src/main/java/client/Character.java | 6 +---- .../processor/npc/FredrickProcessor.java | 14 ++++++++++- src/main/java/net/server/guild/Guild.java | 24 +++++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 3bae79a54d..a3c229cb3b 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -8796,14 +8796,10 @@ public class Character extends AbstractCharacterObject { } public void sendNote(String to, String msg, byte fame) throws SQLException { - sendNote(to, this.getName(), msg, fame); - } - - public static void sendNote(String to, String from, String msg, byte fame) throws SQLException { try (Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("INSERT INTO notes (`to`, `from`, `message`, `timestamp`, `fame`) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, to); - ps.setString(2, from); + ps.setString(2, this.getName()); ps.setString(3, msg); ps.setLong(4, Server.getInstance().getCurrentTime()); ps.setByte(5, fame); diff --git a/src/main/java/client/processor/npc/FredrickProcessor.java b/src/main/java/client/processor/npc/FredrickProcessor.java index 36efe492de..e6beb8b649 100644 --- a/src/main/java/client/processor/npc/FredrickProcessor.java +++ b/src/main/java/client/processor/npc/FredrickProcessor.java @@ -30,6 +30,9 @@ import client.inventory.InventoryType; import client.inventory.Item; import client.inventory.ItemFactory; import client.inventory.manipulator.InventoryManipulator; +import database.DaoException; +import database.NoteDao; +import model.Note; import net.server.Server; import net.server.world.World; import org.slf4j.Logger; @@ -241,7 +244,7 @@ public class FredrickProcessor { ps.addBatch(); String msg = fredrickReminderMessage(cid.getRight() - 1); - Character.sendNote(cid.getLeft().getRight(), "FREDRICK", msg, (byte) 0); + saveFredrickReminderNote(msg, cid.getLeft().getRight()); } ps.executeBatch(); @@ -252,6 +255,15 @@ public class FredrickProcessor { } } + private static void saveFredrickReminderNote(String message, String to) { + Note reminderNote = Note.createNormal(message, "FREDRICK", to, Server.getInstance().getCurrentTime()); + try { + NoteDao.save(reminderNote); + } catch (DaoException e) { + log.error("Failed to save Fredrick reminder note", e); + } + } + private static boolean deleteFredrickItems(int cid) { try (Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("DELETE FROM `inventoryitems` WHERE `type` = ? AND `characterid` = ?")) { diff --git a/src/main/java/net/server/guild/Guild.java b/src/main/java/net/server/guild/Guild.java index 2f8ed78034..daf145e00e 100644 --- a/src/main/java/net/server/guild/Guild.java +++ b/src/main/java/net/server/guild/Guild.java @@ -24,6 +24,9 @@ package net.server.guild; import client.Character; import client.Client; import config.YamlConfig; +import database.DaoException; +import database.NoteDao; +import model.Note; import net.packet.Packet; import net.server.PlayerStorage; import net.server.Server; @@ -512,16 +515,7 @@ public class Guild { if (mgc.isOnline()) { Server.getInstance().getWorld(mgc.getWorld()).setGuildAndRank(cid, 0, 5); } else { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("INSERT INTO notes (`to`, `from`, `message`, `timestamp`) VALUES (?, ?, ?, ?)")) { - ps.setString(1, mgc.getName()); - ps.setString(2, initiator.getName()); - ps.setString(3, "You have been expelled from the guild."); - ps.setLong(4, System.currentTimeMillis()); - ps.executeUpdate(); - } catch (SQLException e) { - log.error("expelMember - Guild", e); - } + sendExpelledNote(initiator.getName(), mgc.getName()); Server.getInstance().getWorld(mgc.getWorld()).setOfflineGuildStatus((short) 0, (byte) 5, cid); } } catch (Exception re) { @@ -537,6 +531,16 @@ public class Guild { } } + private void sendExpelledNote(String expelledBy, String expelledChr) { + Note expelledNote = Note.createNormal("You have been expelled from the guild.", expelledBy, expelledChr, + System.currentTimeMillis()); + try { + NoteDao.save(expelledNote); + } catch (DaoException e) { + log.error("Failed to save guild expel note - {} expelled from {} by {}", expelledChr, name, expelledBy, e); + } + } + public void changeRank(int cid, int newRank) { membersLock.lock(); try { From 6be1fabc557a66970564eb27d742da1334a51f00 Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 26 Dec 2022 17:22:23 +0100 Subject: [PATCH 06/17] Send wedding invitation note directly via dao --- .../channel/handlers/RingActionHandler.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/server/channel/handlers/RingActionHandler.java b/src/main/java/net/server/channel/handlers/RingActionHandler.java index 73a31b842f..40de1106e6 100644 --- a/src/main/java/net/server/channel/handlers/RingActionHandler.java +++ b/src/main/java/net/server/channel/handlers/RingActionHandler.java @@ -30,8 +30,12 @@ import client.inventory.Item; import client.inventory.manipulator.InventoryManipulator; import client.processor.npc.DueyProcessor; import constants.id.ItemId; +import database.DaoException; +import database.NoteDao; +import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; +import net.server.Server; import net.server.channel.Channel; import net.server.world.World; import org.slf4j.Logger; @@ -396,7 +400,8 @@ public final class RingActionHandler extends AbstractPacketHandler { return; } - String groom = c.getPlayer().getName(), bride = Character.getNameById(c.getPlayer().getPartnerId()); + String groom = c.getPlayer().getName(); + String bride = Character.getNameById(c.getPlayer().getPartnerId()); int guest = Character.getIdByName(name); if (groom == null || bride == null || groom.equals("") || bride.equals("") || guest <= 0) { c.getPlayer().dropMessage(5, "Unable to find " + name + "!"); @@ -417,14 +422,16 @@ public final class RingActionHandler extends AbstractPacketHandler { if (resStatus > 0) { long expiration = cserv.getWeddingTicketExpireTime(resStatus + 1); + String baseMessage = "You've been invited to %s and %s's Wedding!".formatted(groom, bride); Character guestChr = c.getWorldServer().getPlayerStorage().getCharacterById(guest); if (guestChr != null && InventoryManipulator.checkSpace(guestChr.getClient(), newItemId, 1, "") && InventoryManipulator.addById(guestChr.getClient(), newItemId, (short) 1, expiration)) { - guestChr.dropMessage(6, "[Wedding] You've been invited to " + groom + " and " + bride + "'s Wedding!"); + guestChr.dropMessage(6, "[Wedding] %s".formatted(baseMessage)); } else { + String dueyMessage = baseMessage + " Receive your invitation from Duey!"; if (guestChr != null && guestChr.isLoggedinWorld()) { - guestChr.dropMessage(6, "[Wedding] You've been invited to " + groom + " and " + bride + "'s Wedding! Receive your invitation from Duey!"); + guestChr.dropMessage(6, "[Wedding] %s".formatted(dueyMessage)); } else { - c.getPlayer().sendNote(name, "You've been invited to " + groom + " and " + bride + "'s Wedding! Receive your invitation from Duey!", (byte) 0); + sendWeddingInvitationNote(dueyMessage, groom, name); } Item weddingTicket = new Item(newItemId, (short) 0, (short) 1); @@ -442,7 +449,7 @@ public final class RingActionHandler extends AbstractPacketHandler { c.getPlayer().dropMessage(5, "Invitation was not sent to '" + name + "'. Either the time for your marriage reservation already came or it was not found."); } - } catch (SQLException ex) { + } catch (Exception ex) { ex.printStackTrace(); return; } @@ -518,4 +525,13 @@ public final class RingActionHandler extends AbstractPacketHandler { c.sendPacket(PacketCreator.enableActions()); } + + private void sendWeddingInvitationNote(String message, String from, String to) { + Note invitationNote = Note.createNormal(message, from, to, Server.getInstance().getCurrentTime()); + try { + NoteDao.save(invitationNote); + } catch (DaoException e) { + log.error("Failed to save wedding invitation note: {}", invitationNote, e); + } + } } From c82881e6f2bd51da0a270e84ffc20b02ddbd0d56 Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 26 Dec 2022 18:01:23 +0100 Subject: [PATCH 07/17] Get rid of Character#sendNote, refactor usages of it --- src/main/java/client/Character.java | 12 ------ .../handlers/CashOperationHandler.java | 42 ++++++++++++------- .../channel/handlers/NoteActionHandler.java | 33 ++++++++++----- .../channel/handlers/UseCashItemHandler.java | 10 +++-- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index a3c229cb3b..11689acc4a 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -8795,18 +8795,6 @@ public class Character extends AbstractCharacterObject { return skillMacros; } - public void sendNote(String to, String msg, byte fame) throws SQLException { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("INSERT INTO notes (`to`, `from`, `message`, `timestamp`, `fame`) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) { - ps.setString(1, to); - ps.setString(2, this.getName()); - ps.setString(3, msg); - ps.setLong(4, Server.getInstance().getCurrentTime()); - ps.setByte(5, fame); - ps.executeUpdate(); - } - } - public static void setAriantRoomLeader(int room, String charname) { ariantroomleader[room] = charname; } diff --git a/src/main/java/net/server/channel/handlers/CashOperationHandler.java b/src/main/java/net/server/channel/handlers/CashOperationHandler.java index 7589c8af8f..36a8268104 100644 --- a/src/main/java/net/server/channel/handlers/CashOperationHandler.java +++ b/src/main/java/net/server/channel/handlers/CashOperationHandler.java @@ -32,6 +32,9 @@ import client.inventory.manipulator.InventoryManipulator; import config.YamlConfig; import constants.id.ItemId; import constants.inventory.ItemConstants; +import database.DaoException; +import database.NoteDao; +import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; import net.server.Server; @@ -128,11 +131,9 @@ public final class CashOperationHandler extends AbstractPacketHandler { cs.gift(Integer.parseInt(recipient.get("id")), chr.getName(), message, cItem.getSN()); c.sendPacket(PacketCreator.showGiftSucceed(recipient.get("name"), cItem)); c.sendPacket(PacketCreator.showCash(chr)); - try { - chr.sendNote(recipient.get("name"), chr.getName() + " has sent you a gift! Go check out the Cash Shop.", (byte) 0); //fame or not - } catch (SQLException ex) { - ex.printStackTrace(); - } + + sendGiftNotificationNote(chr.getName(), recipient.get("name")); + Character receiver = c.getChannelServer().getPlayerStorage().getCharacterByName(recipient.get("name")); if (receiver != null) { receiver.showNote(); @@ -330,11 +331,7 @@ public final class CashOperationHandler extends AbstractPacketHandler { cs.gainCash(toCharge, itemRing, chr.getWorld()); cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight()); chr.addCrushRing(Ring.loadFromDb(rings.getLeft())); - try { - chr.sendNote(partner.getName(), text, (byte) 1); - } catch (SQLException ex) { - ex.printStackTrace(); - } + sendGiftNote(text, chr.getName(), partner.getName()); partner.showNote(); } } @@ -393,11 +390,7 @@ public final class CashOperationHandler extends AbstractPacketHandler { cs.gainCash(payment, -itemRing.getPrice()); cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight()); chr.addFriendshipRing(Ring.loadFromDb(rings.getLeft())); - try { - chr.sendNote(partner.getName(), text, (byte) 1); - } catch (SQLException ex) { - ex.printStackTrace(); - } + sendGiftNote(text, chr.getName(), partner.getName()); partner.showNote(); } } @@ -494,4 +487,23 @@ public final class CashOperationHandler extends AbstractPacketHandler { return false; } } + + private void sendGiftNote(String message, String from, String to) { + Note giftNote = Note.createGift(message, from, to, Server.getInstance().getCurrentTime()); + sendGiftNote(giftNote); + } + + private void sendGiftNotificationNote(String from, String to) { + String message = from + " has sent you a gift! Go check out the Cash Shop."; + Note giftNotificationNote = Note.createNormal(message, from, to, Server.getInstance().getCurrentTime()); + sendGiftNote(giftNotificationNote); + } + + private void sendGiftNote(Note giftNote) { + try { + NoteDao.save(giftNote); + } catch (DaoException e) { + log.error("Failed to send gift note {}", giftNote, e); + } + } } diff --git a/src/main/java/net/server/channel/handlers/NoteActionHandler.java b/src/main/java/net/server/channel/handlers/NoteActionHandler.java index 7c963691d6..7a99f23199 100644 --- a/src/main/java/net/server/channel/handlers/NoteActionHandler.java +++ b/src/main/java/net/server/channel/handlers/NoteActionHandler.java @@ -27,6 +27,7 @@ import database.NoteDao; import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; +import net.server.Server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tools.PacketCreator; @@ -40,20 +41,18 @@ public final class NoteActionHandler extends AbstractPacketHandler { @Override public void handlePacket(InPacket p, Client c) { int action = p.readByte(); - if (action == 0 && c.getPlayer().getCashShop().getAvailableNotes() > 0) { + if (action == 0 && c.getPlayer().getCashShop().getAvailableNotes() > 0) { // Reply to gift in cash shop String charname = p.readString(); String message = p.readString(); - try { - if (c.getPlayer().getCashShop().isOpened()) { - c.sendPacket(PacketCreator.showCashInventory(c)); - } - - c.getPlayer().sendNote(charname, message, (byte) 1); - c.getPlayer().getCashShop().decreaseNotes(); - } catch (SQLException e) { - e.printStackTrace(); + if (c.getPlayer().getCashShop().isOpened()) { + c.sendPacket(PacketCreator.showCashInventory(c)); } - } else if (action == 1) { + + boolean sendNoteSuccess = sendGiftReplyNote(message, c.getPlayer().getName(), charname); + if (sendNoteSuccess) { + c.getPlayer().getCashShop().decreaseNotes(); + } + } else if (action == 1) { // Discard notes in game int num = p.readByte(); p.readByte(); p.readByte(); @@ -82,4 +81,16 @@ public final class NoteActionHandler extends AbstractPacketHandler { } } } + + private boolean sendGiftReplyNote(String message, String from, String to) { + Note giftReplyNote = Note.createGift(message, from, to, Server.getInstance().getCurrentTime()); + try { + NoteDao.save(giftReplyNote); + } catch (DaoException e) { + log.error("Failed to send gift reply note {}", giftReplyNote, e); + return false; + } + + return true; + } } diff --git a/src/main/java/net/server/channel/handlers/UseCashItemHandler.java b/src/main/java/net/server/channel/handlers/UseCashItemHandler.java index 258a75ff43..fc661e8cd0 100644 --- a/src/main/java/net/server/channel/handlers/UseCashItemHandler.java +++ b/src/main/java/net/server/channel/handlers/UseCashItemHandler.java @@ -36,6 +36,9 @@ import constants.game.GameConstants; import constants.id.ItemId; import constants.id.MapId; import constants.inventory.ItemConstants; +import database.DaoException; +import database.NoteDao; +import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; import net.server.Server; @@ -358,10 +361,11 @@ public final class UseCashItemHandler extends AbstractPacketHandler { } else if (itemType == 509) { String sendTo = p.readString(); String msg = p.readString(); + Note note = Note.createNormal(msg, player.getName(), sendTo, Server.getInstance().getCurrentTime()); try { - player.sendNote(sendTo, msg, (byte) 0); - } catch (SQLException e) { - e.printStackTrace(); + NoteDao.save(note); + } catch (DaoException e) { + log.error("Failed to save note {}", note, e); } remove(c, position, itemId); } else if (itemType == 510) { From 7e3be4c45d27e374b110a868532ec3e16f73955a Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 26 Dec 2022 18:06:51 +0100 Subject: [PATCH 08/17] Update README - explain hide --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 870e18d3a7..e1f5ce03bf 100644 --- a/README.md +++ b/README.md @@ -211,12 +211,17 @@ To launch the server, you may either: --- ### Getting into the game -If you ran the admin sql script, there already exists an account in your database with an admin character on it. You don't need to change its GM level. Log in using these credentials: +If you ran the admin sql script, there already exists an account in the database with an admin character on it (GM level 6). + +Log in using these credentials: * Username: "admin" * Password: "admin" * Pin: "0000" * Pic: "000000" +Admin characters have "hide" mode enabled by default. This means your character will be translucent on your screen, and completely invisible to others. +It will also prevent you from controlling mobs (making them stand still). To toggle this mode on and off, type "@hide" in the in-game chat. + By default, the server source is set to allow AUTO-REGISTERING. This means that, by simply typing in a "Login ID" and a "Password", you're able to create a new account. After creating a character, experiment typing in all-chat "@commands". From 5f1f5b7dcdc50726166ce84fe3f9a2d93dabfeaf Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 10:24:47 +0100 Subject: [PATCH 09/17] Fix saving note --- src/main/java/database/{ => note}/NoteDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/database/{ => note}/NoteDao.java (98%) diff --git a/src/main/java/database/NoteDao.java b/src/main/java/database/note/NoteDao.java similarity index 98% rename from src/main/java/database/NoteDao.java rename to src/main/java/database/note/NoteDao.java index d945505cdf..c7dd61e337 100644 --- a/src/main/java/database/NoteDao.java +++ b/src/main/java/database/note/NoteDao.java @@ -12,7 +12,7 @@ public class NoteDao { public static void save(Note note) { try (Handle handle = DatabaseConnection.getHandle()) { - handle.createScript(""" + handle.createUpdate(""" INSERT INTO notes (`message`, `from`, `to`, `timestamp`, `fame`, `deleted`) VALUES (?, ?, ?, ?, ?, ?)""") .bind(0, note.message()) From 389b3ad2a45dcee3b218f45e196d01e30f3f02e2 Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 10:34:55 +0100 Subject: [PATCH 10/17] Add NoteService to handle note operations NoteService should be the only class with access to NoteDao; nowhere else should NoteDao be accessed directly. Channel dependencies are static in PacketProcessor, for now. Ideally they would be injected in the constructor, but since the constructor is private and I don't want to open up that can of worms, I'll leave it like this. At the very least, now we have a way of injecting services into the handlers. This will make further restructuring way easier. --- src/main/java/client/Character.java | 15 --- .../processor/npc/FredrickProcessor.java | 2 +- src/main/java/database/note/NoteDao.java | 9 +- .../database/{ => note}/NoteRowMapper.java | 2 +- src/main/java/net/ChannelDependencies.java | 12 +++ src/main/java/net/PacketProcessor.java | 21 ++++- src/main/java/net/server/Server.java | 16 ++++ .../handlers/CashOperationHandler.java | 43 +++------ .../channel/handlers/NoteActionHandler.java | 40 +++----- .../handlers/PlayerLoggedinHandler.java | 10 +- .../channel/handlers/RingActionHandler.java | 22 ++--- .../channel/handlers/UseCashItemHandler.java | 20 ++-- src/main/java/net/server/guild/Guild.java | 2 +- src/main/java/service/NoteService.java | 93 +++++++++++++++++++ src/main/java/tools/DatabaseConnection.java | 2 +- 15 files changed, 199 insertions(+), 110 deletions(-) rename src/main/java/database/{ => note}/NoteRowMapper.java (96%) create mode 100644 src/main/java/net/ChannelDependencies.java create mode 100644 src/main/java/service/NoteService.java diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 11689acc4a..201160e582 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -41,9 +41,6 @@ import constants.id.MapId; import constants.id.MobId; import constants.inventory.ItemConstants; import constants.skills.*; -import database.DaoException; -import database.NoteDao; -import model.Note; import net.packet.Packet; import net.server.PlayerBuffValueHolder; import net.server.PlayerCoolDownValueHolder; @@ -9627,18 +9624,6 @@ public class Character extends AbstractCharacterObject { client.announceHint(msg, length); } - public void showNote() { - final List notes; - try { - notes = NoteDao.findAllByTo(name); - } catch (DaoException e) { - log.error("Failed to find notes for chr name {}", name, e); - return; - } - - sendPacket(PacketCreator.showNotes(notes)); - } - public void silentGiveBuffs(List> buffs) { for (Pair mbsv : buffs) { PlayerBuffValueHolder mbsvh = mbsv.getRight(); diff --git a/src/main/java/client/processor/npc/FredrickProcessor.java b/src/main/java/client/processor/npc/FredrickProcessor.java index e6beb8b649..4480ea4609 100644 --- a/src/main/java/client/processor/npc/FredrickProcessor.java +++ b/src/main/java/client/processor/npc/FredrickProcessor.java @@ -31,7 +31,7 @@ import client.inventory.Item; import client.inventory.ItemFactory; import client.inventory.manipulator.InventoryManipulator; import database.DaoException; -import database.NoteDao; +import database.note.NoteDao; import model.Note; import net.server.Server; import net.server.world.World; diff --git a/src/main/java/database/note/NoteDao.java b/src/main/java/database/note/NoteDao.java index c7dd61e337..34451e0963 100644 --- a/src/main/java/database/note/NoteDao.java +++ b/src/main/java/database/note/NoteDao.java @@ -1,5 +1,6 @@ -package database; +package database.note; +import database.DaoException; import model.Note; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.JdbiException; @@ -27,7 +28,7 @@ public class NoteDao { } } - public static List findAllByTo(String to) { + public List findAllByTo(String to) { try (Handle handle = DatabaseConnection.getHandle()) { return handle.createQuery(""" SELECT * @@ -38,11 +39,11 @@ public class NoteDao { .mapTo(Note.class) .list(); } catch (JdbiException e) { - throw new DaoException("Failed to find notes with \"to\": %s".formatted(to), e); + throw new DaoException("Failed to find notes sent to: %s".formatted(to), e); } } - public static Optional delete(int id) { + public Optional delete(int id) { try (Handle handle = DatabaseConnection.getHandle()) { Optional note = findById(handle, id); if (note.isEmpty()) { diff --git a/src/main/java/database/NoteRowMapper.java b/src/main/java/database/note/NoteRowMapper.java similarity index 96% rename from src/main/java/database/NoteRowMapper.java rename to src/main/java/database/note/NoteRowMapper.java index b035741859..fd239a5632 100644 --- a/src/main/java/database/NoteRowMapper.java +++ b/src/main/java/database/note/NoteRowMapper.java @@ -1,4 +1,4 @@ -package database; +package database.note; import model.Note; import org.jdbi.v3.core.mapper.RowMapper; diff --git a/src/main/java/net/ChannelDependencies.java b/src/main/java/net/ChannelDependencies.java new file mode 100644 index 0000000000..eb07b2bfba --- /dev/null +++ b/src/main/java/net/ChannelDependencies.java @@ -0,0 +1,12 @@ +package net; + +import service.NoteService; + +import java.util.Objects; + +public record ChannelDependencies(NoteService noteService) { + + public ChannelDependencies { + Objects.requireNonNull(noteService); + } +} diff --git a/src/main/java/net/PacketProcessor.java b/src/main/java/net/PacketProcessor.java index 4c5e2615cc..4f47b2c55d 100644 --- a/src/main/java/net/PacketProcessor.java +++ b/src/main/java/net/PacketProcessor.java @@ -37,6 +37,9 @@ import java.util.Map; public final class PacketProcessor { private static final Logger log = LoggerFactory.getLogger(PacketProcessor.class); private static final Map instances = new LinkedHashMap<>(); + + private static ChannelDependencies channelDependencies; + private PacketHandler[] handlers; private PacketProcessor() { @@ -49,11 +52,19 @@ public final class PacketProcessor { handlers = new PacketHandler[maxRecvOp + 1]; } + public static void registerGameHandlerDependencies(ChannelDependencies channelDependencies) { + PacketProcessor.channelDependencies = channelDependencies; + } + public static PacketProcessor getLoginServerProcessor() { return getProcessor(LoginServer.WORLD_ID, LoginServer.CHANNEL_ID); } public static PacketProcessor getChannelServerProcessor(int world, int channel) { + if (channelDependencies == null) { + throw new IllegalStateException("Unable to get channel server processor - dependencies are not registered"); + } + return getProcessor(world, channel); } @@ -141,7 +152,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.ITEM_SORT, new InventoryMergeHandler()); registerHandler(RecvOpcode.ITEM_MOVE, new ItemMoveHandler()); registerHandler(RecvOpcode.MESO_DROP, new MesoDropHandler()); - registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler()); + registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler(channelDependencies.noteService())); registerHandler(RecvOpcode.CHANGE_MAP, new ChangeMapHandler()); registerHandler(RecvOpcode.MOVE_LIFE, new MoveLifeHandler()); registerHandler(RecvOpcode.CLOSE_RANGE_ATTACK, new CloseRangeDamageHandler()); @@ -149,7 +160,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.MAGIC_ATTACK, new MagicDamageHandler()); registerHandler(RecvOpcode.TAKE_DAMAGE, new TakeDamageHandler()); registerHandler(RecvOpcode.MOVE_PLAYER, new MovePlayerHandler()); - registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler()); + registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler(channelDependencies.noteService())); registerHandler(RecvOpcode.USE_ITEM, new UseItemHandler()); registerHandler(RecvOpcode.USE_RETURN_SCROLL, new UseItemHandler()); registerHandler(RecvOpcode.USE_UPGRADE_SCROLL, new ScrollHandler()); @@ -191,7 +202,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.MESSENGER, new MessengerHandler()); registerHandler(RecvOpcode.NPC_ACTION, new NPCAnimationHandler()); registerHandler(RecvOpcode.CHECK_CASH, new TouchingCashShopHandler()); - registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler()); + registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler(channelDependencies.noteService())); registerHandler(RecvOpcode.COUPON_CODE, new CouponCodeHandler()); registerHandler(RecvOpcode.SPAWN_PET, new SpawnPetHandler()); registerHandler(RecvOpcode.MOVE_PET, new MovePetHandler()); @@ -204,11 +215,11 @@ public final class PacketProcessor { registerHandler(RecvOpcode.CANCEL_DEBUFF, new CancelDebuffHandler()); registerHandler(RecvOpcode.USE_SKILL_BOOK, new SkillBookHandler()); registerHandler(RecvOpcode.SKILL_MACRO, new SkillMacroHandler()); - registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler()); + registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler(channelDependencies.noteService())); registerHandler(RecvOpcode.CLOSE_CHALKBOARD, new CloseChalkboardHandler()); registerHandler(RecvOpcode.USE_MOUNT_FOOD, new UseMountFoodHandler()); registerHandler(RecvOpcode.MTS_OPERATION, new MTSHandler()); - registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler()); + registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler(channelDependencies.noteService())); registerHandler(RecvOpcode.SPOUSE_CHAT, new SpouseChatHandler()); registerHandler(RecvOpcode.PET_AUTO_POT, new PetAutoPotHandler()); registerHandler(RecvOpcode.PET_EXCLUDE_ITEMS, new PetExcludeItemsHandler()); diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index f47f4770af..f341b94eee 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -35,6 +35,9 @@ import constants.game.GameConstants; import constants.inventory.ItemConstants; import constants.net.OpcodeConstants; import constants.net.ServerConstants; +import database.note.NoteDao; +import net.ChannelDependencies; +import net.PacketProcessor; import net.netty.LoginServer; import net.packet.Packet; import net.server.channel.Channel; @@ -55,6 +58,7 @@ import server.TimerManager; import server.expeditions.ExpeditionBossLog; import server.life.PlayerNPCFactory; import server.quest.Quest; +import service.NoteService; import tools.DatabaseConnection; import tools.Pair; @@ -838,6 +842,8 @@ public class Server { throw new IllegalStateException("Failed to initiate a connection to the database"); } + registerChannelDependencies(); + final ExecutorService initExecutor = Executors.newFixedThreadPool(10); // Run slow operations asynchronously to make startup faster final List> futures = new ArrayList<>(); @@ -914,6 +920,16 @@ public class Server { } } + private void registerChannelDependencies() { + NoteDao noteDao = new NoteDao(); + + ChannelDependencies channelDependencies = new ChannelDependencies( + new NoteService(noteDao) + ); + + PacketProcessor.registerGameHandlerDependencies(channelDependencies); + } + private LoginServer initLoginServer(int port) { LoginServer loginServer = new LoginServer(port); loginServer.start(); diff --git a/src/main/java/net/server/channel/handlers/CashOperationHandler.java b/src/main/java/net/server/channel/handlers/CashOperationHandler.java index 36a8268104..077c654a5c 100644 --- a/src/main/java/net/server/channel/handlers/CashOperationHandler.java +++ b/src/main/java/net/server/channel/handlers/CashOperationHandler.java @@ -32,9 +32,6 @@ import client.inventory.manipulator.InventoryManipulator; import config.YamlConfig; import constants.id.ItemId; import constants.inventory.ItemConstants; -import database.DaoException; -import database.NoteDao; -import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; import net.server.Server; @@ -44,10 +41,10 @@ import server.CashShop; import server.CashShop.CashItem; import server.CashShop.CashItemFactory; import server.ItemInformationProvider; +import service.NoteService; import tools.PacketCreator; import tools.Pair; -import java.sql.SQLException; import java.util.Calendar; import java.util.List; import java.util.Map; @@ -57,6 +54,12 @@ import static java.util.concurrent.TimeUnit.DAYS; public final class CashOperationHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(CashOperationHandler.class); + private final NoteService noteService; + + public CashOperationHandler(NoteService noteService) { + this.noteService = noteService; + } + @Override public void handlePacket(InPacket p, Client c) { Character chr = c.getPlayer(); @@ -132,11 +135,12 @@ public final class CashOperationHandler extends AbstractPacketHandler { c.sendPacket(PacketCreator.showGiftSucceed(recipient.get("name"), cItem)); c.sendPacket(PacketCreator.showCash(chr)); - sendGiftNotificationNote(chr.getName(), recipient.get("name")); + String noteMessage = chr.getName() + " has sent you a gift! Go check out the Cash Shop."; + noteService.sendNormal(noteMessage, chr.getName(), recipient.get("name")); Character receiver = c.getChannelServer().getPlayerStorage().getCharacterByName(recipient.get("name")); if (receiver != null) { - receiver.showNote(); + noteService.show(receiver); } } else if (action == 0x05) { // Modify wish list cs.clearWishList(); @@ -331,8 +335,8 @@ public final class CashOperationHandler extends AbstractPacketHandler { cs.gainCash(toCharge, itemRing, chr.getWorld()); cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight()); chr.addCrushRing(Ring.loadFromDb(rings.getLeft())); - sendGiftNote(text, chr.getName(), partner.getName()); - partner.showNote(); + noteService.sendWithFame(text, chr.getName(), partner.getName()); + noteService.show(partner); } } } else { @@ -390,8 +394,8 @@ public final class CashOperationHandler extends AbstractPacketHandler { cs.gainCash(payment, -itemRing.getPrice()); cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight()); chr.addFriendshipRing(Ring.loadFromDb(rings.getLeft())); - sendGiftNote(text, chr.getName(), partner.getName()); - partner.showNote(); + noteService.sendWithFame(text, chr.getName(), partner.getName()); + noteService.show(partner); } } } else { @@ -487,23 +491,4 @@ public final class CashOperationHandler extends AbstractPacketHandler { return false; } } - - private void sendGiftNote(String message, String from, String to) { - Note giftNote = Note.createGift(message, from, to, Server.getInstance().getCurrentTime()); - sendGiftNote(giftNote); - } - - private void sendGiftNotificationNote(String from, String to) { - String message = from + " has sent you a gift! Go check out the Cash Shop."; - Note giftNotificationNote = Note.createNormal(message, from, to, Server.getInstance().getCurrentTime()); - sendGiftNote(giftNotificationNote); - } - - private void sendGiftNote(Note giftNote) { - try { - NoteDao.save(giftNote); - } catch (DaoException e) { - log.error("Failed to send gift note {}", giftNote, e); - } - } } diff --git a/src/main/java/net/server/channel/handlers/NoteActionHandler.java b/src/main/java/net/server/channel/handlers/NoteActionHandler.java index 7a99f23199..ed919b8c5b 100644 --- a/src/main/java/net/server/channel/handlers/NoteActionHandler.java +++ b/src/main/java/net/server/channel/handlers/NoteActionHandler.java @@ -22,22 +22,25 @@ package net.server.channel.handlers; import client.Client; -import database.DaoException; -import database.NoteDao; import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; -import net.server.Server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import service.NoteService; import tools.PacketCreator; -import java.sql.SQLException; import java.util.Optional; public final class NoteActionHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(NoteActionHandler.class); + private final NoteService noteService; + + public NoteActionHandler(NoteService noteService) { + this.noteService = noteService; + } + @Override public void handlePacket(InPacket p, Client c) { int action = p.readByte(); @@ -48,7 +51,7 @@ public final class NoteActionHandler extends AbstractPacketHandler { c.sendPacket(PacketCreator.showCashInventory(c)); } - boolean sendNoteSuccess = sendGiftReplyNote(message, c.getPlayer().getName(), charname); + boolean sendNoteSuccess = noteService.sendWithFame(message, c.getPlayer().getName(), charname); if (sendNoteSuccess) { c.getPlayer().getCashShop().decreaseNotes(); } @@ -61,36 +64,17 @@ public final class NoteActionHandler extends AbstractPacketHandler { int id = p.readInt(); p.readByte(); //Fame, but we read it from the database :) - final Optional note; - try { - note = NoteDao.delete(id); - } catch (DaoException e) { - log.error("Failed to delete note {}", id, e); + Optional discardedNote = noteService.discard(id); + if (discardedNote.isEmpty()) { + log.warn("Note with id {} not able to be discarded. Already discarded?", id); continue; } - if (note.isEmpty()) { - log.warn("Note with id {} not able to be deleted. Already deleted?", id); - continue; - } - - fame += note.get().fame(); + fame += discardedNote.get().fame(); } if (fame > 0) { c.getPlayer().gainFame(fame); } } } - - private boolean sendGiftReplyNote(String message, String from, String to) { - Note giftReplyNote = Note.createGift(message, from, to, Server.getInstance().getCurrentTime()); - try { - NoteDao.save(giftReplyNote); - } catch (DaoException e) { - log.error("Failed to send gift reply note {}", giftReplyNote, e); - return false; - } - - return true; - } } diff --git a/src/main/java/net/server/channel/handlers/PlayerLoggedinHandler.java b/src/main/java/net/server/channel/handlers/PlayerLoggedinHandler.java index 61ccc18149..72a904006c 100644 --- a/src/main/java/net/server/channel/handlers/PlayerLoggedinHandler.java +++ b/src/main/java/net/server/channel/handlers/PlayerLoggedinHandler.java @@ -46,6 +46,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scripting.event.EventInstanceManager; import server.life.MobSkill; +import service.NoteService; import tools.DatabaseConnection; import tools.PacketCreator; import tools.Pair; @@ -63,6 +64,12 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(PlayerLoggedinHandler.class); private static final Set attemptingLoginAccounts = new HashSet<>(); + private final NoteService noteService; + + public PlayerLoggedinHandler(NoteService noteService) { + this.noteService = noteService; + } + private boolean tryAcquireAccount(int accId) { synchronized (attemptingLoginAccounts) { if (attemptingLoginAccounts.contains(accId)) { @@ -302,7 +309,8 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler { } } - player.showNote(); + noteService.show(player); + if (player.getParty() != null) { PartyCharacter pchar = player.getMPC(); diff --git a/src/main/java/net/server/channel/handlers/RingActionHandler.java b/src/main/java/net/server/channel/handlers/RingActionHandler.java index 40de1106e6..d8da534cd0 100644 --- a/src/main/java/net/server/channel/handlers/RingActionHandler.java +++ b/src/main/java/net/server/channel/handlers/RingActionHandler.java @@ -30,18 +30,15 @@ import client.inventory.Item; import client.inventory.manipulator.InventoryManipulator; import client.processor.npc.DueyProcessor; import constants.id.ItemId; -import database.DaoException; -import database.NoteDao; -import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; -import net.server.Server; import net.server.channel.Channel; import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scripting.event.EventInstanceManager; import server.ItemInformationProvider; +import service.NoteService; import tools.DatabaseConnection; import tools.PacketCreator; import tools.Pair; @@ -60,6 +57,12 @@ import java.sql.SQLException; public final class RingActionHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(RingActionHandler.class); + private final NoteService noteService; + + public RingActionHandler(NoteService noteService) { + this.noteService = noteService; + } + private static int getEngagementBoxId(int useItemId) { return switch (useItemId) { case ItemId.ENGAGEMENT_BOX_MOONSTONE -> ItemId.EMPTY_ENGAGEMENT_BOX_MOONSTONE; @@ -431,7 +434,7 @@ public final class RingActionHandler extends AbstractPacketHandler { if (guestChr != null && guestChr.isLoggedinWorld()) { guestChr.dropMessage(6, "[Wedding] %s".formatted(dueyMessage)); } else { - sendWeddingInvitationNote(dueyMessage, groom, name); + noteService.sendNormal(dueyMessage, groom, name); } Item weddingTicket = new Item(newItemId, (short) 0, (short) 1); @@ -525,13 +528,4 @@ public final class RingActionHandler extends AbstractPacketHandler { c.sendPacket(PacketCreator.enableActions()); } - - private void sendWeddingInvitationNote(String message, String from, String to) { - Note invitationNote = Note.createNormal(message, from, to, Server.getInstance().getCurrentTime()); - try { - NoteDao.save(invitationNote); - } catch (DaoException e) { - log.error("Failed to save wedding invitation note: {}", invitationNote, e); - } - } } diff --git a/src/main/java/net/server/channel/handlers/UseCashItemHandler.java b/src/main/java/net/server/channel/handlers/UseCashItemHandler.java index fc661e8cd0..18d09740bf 100644 --- a/src/main/java/net/server/channel/handlers/UseCashItemHandler.java +++ b/src/main/java/net/server/channel/handlers/UseCashItemHandler.java @@ -36,9 +36,6 @@ import constants.game.GameConstants; import constants.id.ItemId; import constants.id.MapId; import constants.inventory.ItemConstants; -import database.DaoException; -import database.NoteDao; -import model.Note; import net.AbstractPacketHandler; import net.packet.InPacket; import net.server.Server; @@ -49,10 +46,10 @@ import server.Shop; import server.ShopFactory; import server.TimerManager; import server.maps.*; +import service.NoteService; import tools.PacketCreator; import tools.Pair; -import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -63,6 +60,12 @@ import static java.util.concurrent.TimeUnit.SECONDS; public final class UseCashItemHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(UseCashItemHandler.class); + private final NoteService noteService; + + public UseCashItemHandler(NoteService noteService) { + this.noteService = noteService; + } + @Override public void handlePacket(InPacket p, Client c) { final Character player = c.getPlayer(); @@ -361,13 +364,10 @@ public final class UseCashItemHandler extends AbstractPacketHandler { } else if (itemType == 509) { String sendTo = p.readString(); String msg = p.readString(); - Note note = Note.createNormal(msg, player.getName(), sendTo, Server.getInstance().getCurrentTime()); - try { - NoteDao.save(note); - } catch (DaoException e) { - log.error("Failed to save note {}", note, e); + boolean sendSuccess = noteService.sendNormal(msg, player.getName(), sendTo); + if (sendSuccess) { + remove(c, position, itemId); } - remove(c, position, itemId); } else if (itemType == 510) { player.getMap().broadcastMessage(PacketCreator.musicChange("Jukebox/Congratulation")); remove(c, position, itemId); diff --git a/src/main/java/net/server/guild/Guild.java b/src/main/java/net/server/guild/Guild.java index daf145e00e..b9ea426923 100644 --- a/src/main/java/net/server/guild/Guild.java +++ b/src/main/java/net/server/guild/Guild.java @@ -25,7 +25,7 @@ import client.Character; import client.Client; import config.YamlConfig; import database.DaoException; -import database.NoteDao; +import database.note.NoteDao; import model.Note; import net.packet.Packet; import net.server.PlayerStorage; diff --git a/src/main/java/service/NoteService.java b/src/main/java/service/NoteService.java new file mode 100644 index 0000000000..20d2e1e4db --- /dev/null +++ b/src/main/java/service/NoteService.java @@ -0,0 +1,93 @@ +package service; + +import client.Character; +import database.DaoException; +import database.note.NoteDao; +import model.Note; +import net.packet.Packet; +import net.server.Server; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.PacketCreator; + +import java.util.List; +import java.util.Optional; + +public class NoteService { + private static final Logger log = LoggerFactory.getLogger(NoteService.class); + + private final NoteDao noteDao; + + public NoteService(NoteDao noteDao) { + this.noteDao = noteDao; + } + + /** + * Send normal note from one character to another + * + * @return Send success + */ + public boolean sendNormal(String message, String senderName, String receiverName) { + Note normalNote = Note.createNormal(message, senderName, receiverName, Server.getInstance().getCurrentTime()); + return send(normalNote); + } + + /** + * Send note which will increase the receiver's fame by one. + * + * @return Send success + */ + public boolean sendWithFame(String message, String senderName, String receiverName) { + Note noteWithFame = Note.createGift(message, senderName, receiverName, Server.getInstance().getCurrentTime()); + return send(noteWithFame); + } + + private boolean send(Note note) { + try { + NoteDao.save(note); + return true; + } catch (DaoException e) { + log.error("Failed to send note {}", note, e); + return false; + } + } + + /** + * Show unread notes + * + * @param chr Note recipient + */ + public void show(Character chr) { + if (chr == null) { + throw new IllegalArgumentException("Unable to show notes - chr is null"); + } + + final List notes; + try { + notes = noteDao.findAllByTo(chr.getName()); + } catch (DaoException e) { + log.error("Failed to find notes sent to chr name {}", chr.getName(), e); + return; + } + + Packet showNotesPacket = PacketCreator.showNotes(notes); + chr.sendPacket(showNotesPacket); + } + + /** + * Discard a read note + * + * @param id Id of note to discard + * @return Discarded note. Empty optional if failed to discard. + */ + public Optional discard(int id) { + try { + return noteDao.delete(id); + } catch (DaoException e) { + log.error("Failed to discard note with id {}", id, e); + return Optional.empty(); + } + } + + +} diff --git a/src/main/java/tools/DatabaseConnection.java b/src/main/java/tools/DatabaseConnection.java index 433f90ae19..d7b51f0366 100644 --- a/src/main/java/tools/DatabaseConnection.java +++ b/src/main/java/tools/DatabaseConnection.java @@ -3,7 +3,7 @@ package tools; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import config.YamlConfig; -import database.NoteRowMapper; +import database.note.NoteRowMapper; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import org.slf4j.Logger; From af14da987eeeabc56e4d1826a0ff96698d16f4d2 Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 10:52:49 +0100 Subject: [PATCH 11/17] Replace FredrickProcessor dependence on NoteDao --- src/main/java/client/Character.java | 14 --------- .../processor/npc/FredrickProcessor.java | 30 +++++++------------ src/main/java/database/note/NoteDao.java | 4 +-- src/main/java/net/ChannelDependencies.java | 4 ++- src/main/java/net/PacketProcessor.java | 18 +++++------ src/main/java/net/server/Server.java | 21 ++++++------- .../channel/handlers/FredrickHandler.java | 7 ++++- .../net/server/task/DueyFredrickTask.java | 7 ++++- 8 files changed, 47 insertions(+), 58 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 201160e582..f21f402091 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -9203,20 +9203,6 @@ public class Character extends AbstractCharacterObject { } } - public void changeName(String name) { - FredrickProcessor.removeFredrickReminders(this.getId()); - - this.name = name; - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("UPDATE `characters` SET `name` = ? WHERE `id` = ?")) { - ps.setString(1, name); - ps.setInt(2, id); - ps.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - public int getDoorSlot() { if (doorSlot != -1) { return doorSlot; diff --git a/src/main/java/client/processor/npc/FredrickProcessor.java b/src/main/java/client/processor/npc/FredrickProcessor.java index 4480ea4609..5e0bd24015 100644 --- a/src/main/java/client/processor/npc/FredrickProcessor.java +++ b/src/main/java/client/processor/npc/FredrickProcessor.java @@ -30,21 +30,18 @@ import client.inventory.InventoryType; import client.inventory.Item; import client.inventory.ItemFactory; import client.inventory.manipulator.InventoryManipulator; -import database.DaoException; -import database.note.NoteDao; -import model.Note; import net.server.Server; import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import server.ItemInformationProvider; import server.maps.HiredMerchant; +import service.NoteService; import tools.DatabaseConnection; import tools.PacketCreator; import tools.Pair; import java.sql.*; -import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -57,6 +54,12 @@ public class FredrickProcessor { private static final Logger log = LoggerFactory.getLogger(FredrickProcessor.class); private static final int[] dailyReminders = new int[]{2, 5, 10, 15, 30, 60, 90, Integer.MAX_VALUE}; + private final NoteService noteService; + + public FredrickProcessor(NoteService noteService) { + this.noteService = noteService; + } + private static byte canRetrieveFromFredrick(Character chr, List> items) { if (!Inventory.checkSpotsAndOwnership(chr, items)) { List itemids = new LinkedList<>(); @@ -130,10 +133,6 @@ public class FredrickProcessor { } } - public static void removeFredrickReminders(int cid) { - removeFredrickReminders(Collections.singletonList(new Pair<>(cid, 0))); - } - private static void removeFredrickReminders(List> expiredCids) { List expiredCnames = new LinkedList<>(); for (Pair id : expiredCids) { @@ -156,7 +155,7 @@ public class FredrickProcessor { } } - public static void runFredrickSchedule() { + public void runFredrickSchedule() { try (Connection con = DatabaseConnection.getConnection()) { List> expiredCids = new LinkedList<>(); List, Integer>> notifCids = new LinkedList<>(); @@ -244,7 +243,7 @@ public class FredrickProcessor { ps.addBatch(); String msg = fredrickReminderMessage(cid.getRight() - 1); - saveFredrickReminderNote(msg, cid.getLeft().getRight()); + noteService.sendNormal(msg, "FREDRICK", cid.getLeft().getRight()); } ps.executeBatch(); @@ -255,15 +254,6 @@ public class FredrickProcessor { } } - private static void saveFredrickReminderNote(String message, String to) { - Note reminderNote = Note.createNormal(message, "FREDRICK", to, Server.getInstance().getCurrentTime()); - try { - NoteDao.save(reminderNote); - } catch (DaoException e) { - log.error("Failed to save Fredrick reminder note", e); - } - } - private static boolean deleteFredrickItems(int cid) { try (Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("DELETE FROM `inventoryitems` WHERE `type` = ? AND `characterid` = ?")) { @@ -278,7 +268,7 @@ public class FredrickProcessor { } } - public static void fredrickRetrieveItems(Client c) { // thanks Gustav for pointing out the dupe on Fredrick handling + public void fredrickRetrieveItems(Client c) { // thanks Gustav for pointing out the dupe on Fredrick handling if (c.tryacquireClient()) { try { Character chr = c.getPlayer(); diff --git a/src/main/java/database/note/NoteDao.java b/src/main/java/database/note/NoteDao.java index 34451e0963..d88de2f446 100644 --- a/src/main/java/database/note/NoteDao.java +++ b/src/main/java/database/note/NoteDao.java @@ -57,7 +57,7 @@ public class NoteDao { } } - private static Optional findById(Handle handle, int id) { + private Optional findById(Handle handle, int id) { final Optional note; try { note = handle.createQuery(""" @@ -74,7 +74,7 @@ public class NoteDao { return note; } - private static void deleteById(Handle handle, int id) { + private void deleteById(Handle handle, int id) { try { handle.createUpdate(""" UPDATE notes diff --git a/src/main/java/net/ChannelDependencies.java b/src/main/java/net/ChannelDependencies.java index eb07b2bfba..a6660f494d 100644 --- a/src/main/java/net/ChannelDependencies.java +++ b/src/main/java/net/ChannelDependencies.java @@ -1,12 +1,14 @@ package net; +import client.processor.npc.FredrickProcessor; import service.NoteService; import java.util.Objects; -public record ChannelDependencies(NoteService noteService) { +public record ChannelDependencies(NoteService noteService, FredrickProcessor fredrickProcessor) { public ChannelDependencies { Objects.requireNonNull(noteService); + Objects.requireNonNull(fredrickProcessor); } } diff --git a/src/main/java/net/PacketProcessor.java b/src/main/java/net/PacketProcessor.java index 4f47b2c55d..391143cb1a 100644 --- a/src/main/java/net/PacketProcessor.java +++ b/src/main/java/net/PacketProcessor.java @@ -38,7 +38,7 @@ public final class PacketProcessor { private static final Logger log = LoggerFactory.getLogger(PacketProcessor.class); private static final Map instances = new LinkedHashMap<>(); - private static ChannelDependencies channelDependencies; + private static ChannelDependencies channelDeps; private PacketHandler[] handlers; @@ -53,7 +53,7 @@ public final class PacketProcessor { } public static void registerGameHandlerDependencies(ChannelDependencies channelDependencies) { - PacketProcessor.channelDependencies = channelDependencies; + PacketProcessor.channelDeps = channelDependencies; } public static PacketProcessor getLoginServerProcessor() { @@ -61,7 +61,7 @@ public final class PacketProcessor { } public static PacketProcessor getChannelServerProcessor(int world, int channel) { - if (channelDependencies == null) { + if (channelDeps == null) { throw new IllegalStateException("Unable to get channel server processor - dependencies are not registered"); } @@ -152,7 +152,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.ITEM_SORT, new InventoryMergeHandler()); registerHandler(RecvOpcode.ITEM_MOVE, new ItemMoveHandler()); registerHandler(RecvOpcode.MESO_DROP, new MesoDropHandler()); - registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler(channelDependencies.noteService())); + registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler(channelDeps.noteService())); registerHandler(RecvOpcode.CHANGE_MAP, new ChangeMapHandler()); registerHandler(RecvOpcode.MOVE_LIFE, new MoveLifeHandler()); registerHandler(RecvOpcode.CLOSE_RANGE_ATTACK, new CloseRangeDamageHandler()); @@ -160,7 +160,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.MAGIC_ATTACK, new MagicDamageHandler()); registerHandler(RecvOpcode.TAKE_DAMAGE, new TakeDamageHandler()); registerHandler(RecvOpcode.MOVE_PLAYER, new MovePlayerHandler()); - registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler(channelDependencies.noteService())); + registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler(channelDeps.noteService())); registerHandler(RecvOpcode.USE_ITEM, new UseItemHandler()); registerHandler(RecvOpcode.USE_RETURN_SCROLL, new UseItemHandler()); registerHandler(RecvOpcode.USE_UPGRADE_SCROLL, new ScrollHandler()); @@ -202,7 +202,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.MESSENGER, new MessengerHandler()); registerHandler(RecvOpcode.NPC_ACTION, new NPCAnimationHandler()); registerHandler(RecvOpcode.CHECK_CASH, new TouchingCashShopHandler()); - registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler(channelDependencies.noteService())); + registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler(channelDeps.noteService())); registerHandler(RecvOpcode.COUPON_CODE, new CouponCodeHandler()); registerHandler(RecvOpcode.SPAWN_PET, new SpawnPetHandler()); registerHandler(RecvOpcode.MOVE_PET, new MovePetHandler()); @@ -215,11 +215,11 @@ public final class PacketProcessor { registerHandler(RecvOpcode.CANCEL_DEBUFF, new CancelDebuffHandler()); registerHandler(RecvOpcode.USE_SKILL_BOOK, new SkillBookHandler()); registerHandler(RecvOpcode.SKILL_MACRO, new SkillMacroHandler()); - registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler(channelDependencies.noteService())); + registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler(channelDeps.noteService())); registerHandler(RecvOpcode.CLOSE_CHALKBOARD, new CloseChalkboardHandler()); registerHandler(RecvOpcode.USE_MOUNT_FOOD, new UseMountFoodHandler()); registerHandler(RecvOpcode.MTS_OPERATION, new MTSHandler()); - registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler(channelDependencies.noteService())); + registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler(channelDeps.noteService())); registerHandler(RecvOpcode.SPOUSE_CHAT, new SpouseChatHandler()); registerHandler(RecvOpcode.PET_AUTO_POT, new PetAutoPotHandler()); registerHandler(RecvOpcode.PET_EXCLUDE_ITEMS, new PetExcludeItemsHandler()); @@ -273,7 +273,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.COCONUT, new CoconutHandler()); registerHandler(RecvOpcode.ARAN_COMBO_COUNTER, new AranComboHandler()); registerHandler(RecvOpcode.CLICK_GUIDE, new ClickGuideHandler()); - registerHandler(RecvOpcode.FREDRICK_ACTION, new FredrickHandler()); + registerHandler(RecvOpcode.FREDRICK_ACTION, new FredrickHandler(channelDeps.fredrickProcessor())); registerHandler(RecvOpcode.MONSTER_CARNIVAL, new MonsterCarnivalHandler()); registerHandler(RecvOpcode.REMOTE_STORE, new RemoteStoreHandler()); registerHandler(RecvOpcode.WEDDING_ACTION, new WeddingHandler()); diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index f341b94eee..2b1da16df8 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -30,6 +30,7 @@ import client.inventory.Item; import client.inventory.ItemFactory; import client.inventory.manipulator.CashIdGenerator; import client.newyear.NewYearCardRecord; +import client.processor.npc.FredrickProcessor; import config.YamlConfig; import constants.game.GameConstants; import constants.inventory.ItemConstants; @@ -842,7 +843,7 @@ public class Server { throw new IllegalStateException("Failed to initiate a connection to the database"); } - registerChannelDependencies(); + ChannelDependencies channelDependencies = registerChannelDependencies(); final ExecutorService initExecutor = Executors.newFixedThreadPool(10); // Run slow operations asynchronously to make startup faster @@ -872,7 +873,7 @@ public class Server { } ThreadManager.getInstance().start(); - initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan + initializeTimelyTasks(channelDependencies); // aggregated method for timely tasks thanks to lxconan try { int worldCount = Math.min(GameConstants.WORLD_NAMES.length, YamlConfig.config.server.WORLDS); @@ -920,14 +921,14 @@ public class Server { } } - private void registerChannelDependencies() { - NoteDao noteDao = new NoteDao(); - - ChannelDependencies channelDependencies = new ChannelDependencies( - new NoteService(noteDao) - ); + private ChannelDependencies registerChannelDependencies() { + NoteService noteService = new NoteService(new NoteDao()); + FredrickProcessor fredrickProcessor = new FredrickProcessor(noteService); + ChannelDependencies channelDependencies = new ChannelDependencies(noteService, fredrickProcessor); PacketProcessor.registerGameHandlerDependencies(channelDependencies); + + return channelDependencies; } private LoginServer initLoginServer(int port) { @@ -948,7 +949,7 @@ public class Server { } } - private void initializeTimelyTasks() { + private void initializeTimelyTasks(ChannelDependencies channelDependencies) { TimerManager tMan = TimerManager.getInstance(); tMan.start(); tMan.register(tMan.purge(), YamlConfig.config.server.PURGING_INTERVAL);//Purging ftw... @@ -962,7 +963,7 @@ public class Server { tMan.register(new LoginCoordinatorTask(), HOURS.toMillis(1), timeLeft); tMan.register(new EventRecallCoordinatorTask(), HOURS.toMillis(1), timeLeft); tMan.register(new LoginStorageTask(), MINUTES.toMillis(2), MINUTES.toMillis(2)); - tMan.register(new DueyFredrickTask(), HOURS.toMillis(1), timeLeft); + tMan.register(new DueyFredrickTask(channelDependencies.fredrickProcessor()), HOURS.toMillis(1), timeLeft); tMan.register(new InvitationTask(), SECONDS.toMillis(30), SECONDS.toMillis(30)); tMan.register(new RespawnTask(), YamlConfig.config.server.RESPAWN_INTERVAL, YamlConfig.config.server.RESPAWN_INTERVAL); diff --git a/src/main/java/net/server/channel/handlers/FredrickHandler.java b/src/main/java/net/server/channel/handlers/FredrickHandler.java index 11dd5517ca..efe65a2126 100644 --- a/src/main/java/net/server/channel/handlers/FredrickHandler.java +++ b/src/main/java/net/server/channel/handlers/FredrickHandler.java @@ -31,6 +31,11 @@ import net.packet.InPacket; * @author kevintjuh93 */ public class FredrickHandler extends AbstractPacketHandler { + private final FredrickProcessor fredrickProcessor; + + public FredrickHandler(FredrickProcessor fredrickProcessor) { + this.fredrickProcessor = fredrickProcessor; + } @Override public void handlePacket(InPacket p, Client c) { @@ -42,7 +47,7 @@ public class FredrickHandler extends AbstractPacketHandler { //c.sendPacket(PacketCreator.getFredrick((byte) 0x24)); break; case 0x1A: - FredrickProcessor.fredrickRetrieveItems(c); + fredrickProcessor.fredrickRetrieveItems(c); break; case 0x1C: //Exit break; diff --git a/src/main/java/net/server/task/DueyFredrickTask.java b/src/main/java/net/server/task/DueyFredrickTask.java index 60b95ff560..4b403db73e 100644 --- a/src/main/java/net/server/task/DueyFredrickTask.java +++ b/src/main/java/net/server/task/DueyFredrickTask.java @@ -26,10 +26,15 @@ import client.processor.npc.FredrickProcessor; * @author Ronan */ public class DueyFredrickTask implements Runnable { + private final FredrickProcessor fredrickProcessor; + + public DueyFredrickTask(FredrickProcessor fredrickProcessor) { + this.fredrickProcessor = fredrickProcessor; + } @Override public void run() { - FredrickProcessor.runFredrickSchedule(); + fredrickProcessor.runFredrickSchedule(); DueyProcessor.runDueyExpireSchedule(); } } From 387437cada298cc960be3c0c51d284fc25efeb16 Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 11:05:00 +0100 Subject: [PATCH 12/17] Workaround for Guild dependence on NoteDao --- src/main/java/database/note/NoteDao.java | 2 +- src/main/java/net/server/Server.java | 5 +++-- src/main/java/net/server/guild/Guild.java | 18 +++--------------- src/main/java/service/NoteService.java | 2 +- 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/java/database/note/NoteDao.java b/src/main/java/database/note/NoteDao.java index d88de2f446..d429aa249f 100644 --- a/src/main/java/database/note/NoteDao.java +++ b/src/main/java/database/note/NoteDao.java @@ -11,7 +11,7 @@ import java.util.Optional; public class NoteDao { - public static void save(Note note) { + public void save(Note note) { try (Handle handle = DatabaseConnection.getHandle()) { handle.createUpdate(""" INSERT INTO notes (`message`, `from`, `to`, `timestamp`, `fame`, `deleted`) diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index 2b1da16df8..5b854bebcd 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -96,6 +96,7 @@ public class Server { private static final Set activeFly = new HashSet<>(); private static final Map couponRates = new HashMap<>(30); private static final List activeCoupons = new LinkedList<>(); + private static ChannelDependencies channelDependencies; private LoginServer loginServer; private final List> channels = new LinkedList<>(); @@ -843,7 +844,7 @@ public class Server { throw new IllegalStateException("Failed to initiate a connection to the database"); } - ChannelDependencies channelDependencies = registerChannelDependencies(); + channelDependencies = registerChannelDependencies(); final ExecutorService initExecutor = Executors.newFixedThreadPool(10); // Run slow operations asynchronously to make startup faster @@ -1181,7 +1182,7 @@ public class Server { public void expelMember(GuildCharacter initiator, String name, int cid) { Guild g = guilds.get(initiator.getGuildId()); if (g != null) { - g.expelMember(initiator, name, cid); + g.expelMember(initiator, name, cid, channelDependencies.noteService()); } } diff --git a/src/main/java/net/server/guild/Guild.java b/src/main/java/net/server/guild/Guild.java index b9ea426923..a68f00e89d 100644 --- a/src/main/java/net/server/guild/Guild.java +++ b/src/main/java/net/server/guild/Guild.java @@ -24,9 +24,6 @@ package net.server.guild; import client.Character; import client.Client; import config.YamlConfig; -import database.DaoException; -import database.note.NoteDao; -import model.Note; import net.packet.Packet; import net.server.PlayerStorage; import net.server.Server; @@ -37,6 +34,7 @@ import net.server.coordinator.world.InviteCoordinator.InviteResult; import net.server.coordinator.world.InviteCoordinator.InviteType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import service.NoteService; import tools.DatabaseConnection; import tools.PacketCreator; @@ -500,7 +498,7 @@ public class Guild { } } - public void expelMember(GuildCharacter initiator, String name, int cid) { + public void expelMember(GuildCharacter initiator, String name, int cid, NoteService noteService) { membersLock.lock(); try { java.util.Iterator itr = members.iterator(); @@ -515,7 +513,7 @@ public class Guild { if (mgc.isOnline()) { Server.getInstance().getWorld(mgc.getWorld()).setGuildAndRank(cid, 0, 5); } else { - sendExpelledNote(initiator.getName(), mgc.getName()); + noteService.sendNormal("You have been expelled from the guild.", initiator.getName(), mgc.getName()); Server.getInstance().getWorld(mgc.getWorld()).setOfflineGuildStatus((short) 0, (byte) 5, cid); } } catch (Exception re) { @@ -531,16 +529,6 @@ public class Guild { } } - private void sendExpelledNote(String expelledBy, String expelledChr) { - Note expelledNote = Note.createNormal("You have been expelled from the guild.", expelledBy, expelledChr, - System.currentTimeMillis()); - try { - NoteDao.save(expelledNote); - } catch (DaoException e) { - log.error("Failed to save guild expel note - {} expelled from {} by {}", expelledChr, name, expelledBy, e); - } - } - public void changeRank(int cid, int newRank) { membersLock.lock(); try { diff --git a/src/main/java/service/NoteService.java b/src/main/java/service/NoteService.java index 20d2e1e4db..c7ee334403 100644 --- a/src/main/java/service/NoteService.java +++ b/src/main/java/service/NoteService.java @@ -44,7 +44,7 @@ public class NoteService { private boolean send(Note note) { try { - NoteDao.save(note); + noteDao.save(note); return true; } catch (DaoException e) { log.error("Failed to send note {}", note, e); From cee82a08bae454aff29ed808281b372be93807cb Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 11:59:14 +0100 Subject: [PATCH 13/17] Remove ItemInformationProvider field in Character This field prevented creation of Character mock in tests --- src/main/java/client/Character.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index f21f402091..1ba4b60603 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -97,7 +97,6 @@ import static java.util.concurrent.TimeUnit.*; public class Character extends AbstractCharacterObject { private static final Logger log = LoggerFactory.getLogger(Character.class); - private static final ItemInformationProvider ii = ItemInformationProvider.getInstance(); private static final String LEVEL_200 = "[Congrats] %s has reached Level %d! Congratulate %s on such an amazing achievement!"; private static final String[] BLOCKED_NAMES = {"admin", "owner", "moderator", "intern", "donor", "administrator", "FREDRICK", "help", "helper", "alert", "notice", "maplestory", "fuck", "wizet", "fucking", "negro", "fuk", "fuc", "penis", "pussy", "asshole", "gay", "nigger", "homo", "suck", "cum", "shit", "shitty", "condom", "security", "official", "rape", "nigga", "sex", "tit", "boner", "orgy", "clit", "asshole", "fatass", "bitch", "support", "gamemaster", "cock", "gaay", "gm", @@ -717,7 +716,7 @@ public class Character extends AbstractCharacterObject { int maxbasedamage; Item weapon_item = getInventory(InventoryType.EQUIPPED).getItem((short) -11); if (weapon_item != null) { - maxbasedamage = calculateMaxBaseDamage(watk, ii.getWeaponType(weapon_item.getItemId())); + maxbasedamage = calculateMaxBaseDamage(watk, ItemInformationProvider.getInstance().getWeaponType(weapon_item.getItemId())); } else { if (job.isA(Job.PIRATE) || job.isA(Job.THUNDERBREAKER1)) { double weapMulti = 3; @@ -817,7 +816,7 @@ public class Character extends AbstractCharacterObject { String medal = ""; final Item medalItem = getInventory(InventoryType.EQUIPPED).getItem((short) -49); if (medalItem != null) { - medal = "<" + ii.getName(medalItem.getItemId()) + "> "; + medal = "<" + ItemInformationProvider.getInstance().getName(medalItem.getItemId()) + "> "; } return medal; } @@ -1873,6 +1872,7 @@ public class Character extends AbstractCharacterObject { public boolean applyConsumeOnPickup(final int itemId) { if (itemId / 1000000 == 2) { + ItemInformationProvider ii = ItemInformationProvider.getInstance(); if (ii.isConsumeOnPickup(itemId)) { if (ItemConstants.isPartyItem(itemId)) { List partyMembers = this.getPartyMembersOnSameMap(); @@ -1943,6 +1943,7 @@ public class Character extends AbstractCharacterObject { Item mItem = mapitem.getItem(); boolean hasSpaceInventory = true; + ItemInformationProvider ii = ItemInformationProvider.getInstance(); if (ItemId.isNxCard(mapitem.getItemId()) || mapitem.getMeso() > 0 || ii.isConsumeOnPickup(mapitem.getItemId()) || (hasSpaceInventory = InventoryManipulator.checkSpace(client, mapitem.getItemId(), mItem.getQuantity(), mItem.getOwner()))) { int mapId = this.getMapId(); @@ -2061,6 +2062,7 @@ public class Character extends AbstractCharacterObject { } public boolean canHoldUniques(List itemids) { + ItemInformationProvider ii = ItemInformationProvider.getInstance(); for (Integer itemid : itemids) { if (ii.isPickupRestricted(itemid) && this.haveItem(itemid)) { return false; @@ -3740,6 +3742,7 @@ public class Character extends AbstractCharacterObject { } public void cancelEffect(int itemId) { + ItemInformationProvider ii = ItemInformationProvider.getInstance(); cancelEffect(ii.getItemEffect(itemId), false, -1); } @@ -6793,6 +6796,7 @@ public class Character extends AbstractCharacterObject { return; } + ItemInformationProvider ii = ItemInformationProvider.getInstance(); StatEffect mse = ii.getItemEffect(couponid); mse.applyTo(this); } @@ -7825,6 +7829,7 @@ public class Character extends AbstractCharacterObject { if (job.isA(Job.THIEF) || job.isA(Job.BOWMAN) || job.isA(Job.PIRATE) || job.isA(Job.NIGHTWALKER1) || job.isA(Job.WINDARCHER1)) { Item weapon_item = getInventory(InventoryType.EQUIPPED).getItem((short) -11); if (weapon_item != null) { + ItemInformationProvider ii = ItemInformationProvider.getInstance(); WeaponType weapon = ii.getWeaponType(weapon_item.getItemId()); boolean bow = weapon == WeaponType.BOW; boolean crossbow = weapon == WeaponType.CROSSBOW; @@ -9301,6 +9306,7 @@ public class Character extends AbstractCharacterObject { return (-1); } + ItemInformationProvider ii = ItemInformationProvider.getInstance(); return (sellAllItemsFromPosition(ii, type, it.getPosition())); } finally { inv.unlockInventory(); @@ -9383,6 +9389,7 @@ public class Character extends AbstractCharacterObject { private List getUpgradeableEquipped() { List list = new LinkedList<>(); + ItemInformationProvider ii = ItemInformationProvider.getInstance(); for (Item item : getInventory(InventoryType.EQUIPPED)) { if (ii.isUpgradeable(item.getItemId())) { list.add((Equip) item); @@ -9495,6 +9502,7 @@ public class Character extends AbstractCharacterObject { private void standaloneMerge(Map statups, Client c, InventoryType type, short slot, Item item) { short quantity; + ItemInformationProvider ii = ItemInformationProvider.getInstance(); if (item == null || (quantity = item.getQuantity()) < 1 || ii.isCash(item.getItemId()) || !ii.isUpgradeable(item.getItemId()) || hasMergeFlag(item)) { return; } @@ -9589,7 +9597,7 @@ public class Character extends AbstractCharacterObject { String medal = ""; Item medalItem = mapOwner.getInventory(InventoryType.EQUIPPED).getItem((short) -49); if (medalItem != null) { - medal = "<" + ii.getName(medalItem.getItemId()) + "> "; + medal = "<" + ItemInformationProvider.getInstance().getName(medalItem.getItemId()) + "> "; } List strLines = new LinkedList<>(); @@ -10312,6 +10320,7 @@ public class Character extends AbstractCharacterObject { } Collection eqpList = new LinkedHashSet<>(); + ItemInformationProvider ii = ItemInformationProvider.getInstance(); for (Item it : fullList) { if (!ii.isCash(it.getItemId())) { eqpList.add(it); @@ -10327,6 +10336,7 @@ public class Character extends AbstractCharacterObject { expGain = Integer.MAX_VALUE; } + ItemInformationProvider ii = ItemInformationProvider.getInstance(); for (Item item : getUpgradeableEquipList()) { Equip nEquip = (Equip) item; String itemName = ii.getName(nEquip.getItemId()); @@ -10342,6 +10352,7 @@ public class Character extends AbstractCharacterObject { public void showAllEquipFeatures() { String showMsg = ""; + ItemInformationProvider ii = ItemInformationProvider.getInstance(); for (Item item : getInventory(InventoryType.EQUIPPED).list()) { Equip nEquip = (Equip) item; String itemName = ii.getName(nEquip.getItemId()); From 2a460de91136fbe0b0227b94b4c831ce03249132 Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 12:05:51 +0100 Subject: [PATCH 14/17] Disable logging in tests --- src/test/resources/log4j2-test.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/test/resources/log4j2-test.xml diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..854243aba2 --- /dev/null +++ b/src/test/resources/log4j2-test.xml @@ -0,0 +1,20 @@ + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{2} - %msg%n + + + + + + ${standard-pattern} + + + + + + + + + + \ No newline at end of file From 65111ae20920c7a66ba0735816e654da9e9cdc67 Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 12:10:33 +0100 Subject: [PATCH 15/17] Create packet class for PacketCreator::showNotes --- .../java/net/packet/out/ShowNotesPacket.java | 30 ++++++++++++++ .../channel/handlers/NoteActionHandler.java | 2 +- src/main/java/service/NoteService.java | 41 ++++++++++++------- src/main/java/tools/PacketCreator.java | 16 -------- 4 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 src/main/java/net/packet/out/ShowNotesPacket.java diff --git a/src/main/java/net/packet/out/ShowNotesPacket.java b/src/main/java/net/packet/out/ShowNotesPacket.java new file mode 100644 index 0000000000..f2b05160ac --- /dev/null +++ b/src/main/java/net/packet/out/ShowNotesPacket.java @@ -0,0 +1,30 @@ +package net.packet.out; + +import model.Note; +import net.opcodes.SendOpcode; +import net.packet.ByteBufOutPacket; + +import java.util.List; +import java.util.Objects; + +import static tools.PacketCreator.getTime; + +public final class ShowNotesPacket extends ByteBufOutPacket { + + public ShowNotesPacket(List notes) { + super(SendOpcode.MEMO_RESULT); + Objects.requireNonNull(notes); + + writeByte(3); + writeByte(notes.size()); + notes.forEach(this::writeNote); + } + + private void writeNote(Note note) { + writeInt(note.id()); + writeString(note.from() + " "); //Stupid nexon forgot space lol + writeString(note.message()); + writeLong(getTime(note.timestamp())); + writeByte(note.fame()); + } +} diff --git a/src/main/java/net/server/channel/handlers/NoteActionHandler.java b/src/main/java/net/server/channel/handlers/NoteActionHandler.java index ed919b8c5b..2be3a48466 100644 --- a/src/main/java/net/server/channel/handlers/NoteActionHandler.java +++ b/src/main/java/net/server/channel/handlers/NoteActionHandler.java @@ -64,7 +64,7 @@ public final class NoteActionHandler extends AbstractPacketHandler { int id = p.readInt(); p.readByte(); //Fame, but we read it from the database :) - Optional discardedNote = noteService.discard(id); + Optional discardedNote = noteService.delete(id); if (discardedNote.isEmpty()) { log.warn("Note with id {} not able to be discarded. Already discarded?", id); continue; diff --git a/src/main/java/service/NoteService.java b/src/main/java/service/NoteService.java index c7ee334403..2221083e2f 100644 --- a/src/main/java/service/NoteService.java +++ b/src/main/java/service/NoteService.java @@ -4,12 +4,12 @@ import client.Character; import database.DaoException; import database.note.NoteDao; import model.Note; -import net.packet.Packet; +import net.packet.out.ShowNotesPacket; import net.server.Server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import tools.PacketCreator; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -62,32 +62,43 @@ public class NoteService { throw new IllegalArgumentException("Unable to show notes - chr is null"); } - final List notes; - try { - notes = noteDao.findAllByTo(chr.getName()); - } catch (DaoException e) { - log.error("Failed to find notes sent to chr name {}", chr.getName(), e); + List notes = getNotes(chr.getName()); + if (notes.isEmpty()) { return; } - Packet showNotesPacket = PacketCreator.showNotes(notes); - chr.sendPacket(showNotesPacket); + chr.sendPacket(new ShowNotesPacket(notes)); + } + + private List getNotes(String to) { + final List notes; + try { + notes = noteDao.findAllByTo(to); + } catch (DaoException e) { + log.error("Failed to find notes sent to chr name {}", to, e); + return Collections.emptyList(); + } + + if (notes == null || notes.isEmpty()) { + return Collections.emptyList(); + } + + return notes; } /** - * Discard a read note + * Delete a read note * - * @param id Id of note to discard + * @param noteId Id of note to discard * @return Discarded note. Empty optional if failed to discard. */ - public Optional discard(int id) { + public Optional delete(int noteId) { try { - return noteDao.delete(id); + return noteDao.delete(noteId); } catch (DaoException e) { - log.error("Failed to discard note with id {}", id, e); + log.error("Failed to discard note with id {}", noteId, e); return Optional.empty(); } } - } diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index 2fb79103db..7fe6a0bb83 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -40,7 +40,6 @@ import constants.inventory.ItemConstants; import constants.skills.Buccaneer; import constants.skills.Corsair; import constants.skills.ThunderBreaker; -import model.Note; import net.encryption.InitializationVector; import net.opcodes.SendOpcode; import net.packet.ByteBufOutPacket; @@ -72,7 +71,6 @@ import server.movement.LifeMovementFragment; import java.awt.*; import java.net.InetAddress; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.*; @@ -5423,20 +5421,6 @@ public class PacketCreator { return p; } - public static Packet showNotes(List notes) { - final OutPacket p = OutPacket.create(SendOpcode.MEMO_RESULT); - p.writeByte(3); - p.writeByte(notes.size()); - notes.forEach(note -> { - p.writeInt(note.id()); - p.writeString(note.from() + " ");//Stupid nexon forgot space lol - p.writeString(note.message()); - p.writeLong(getTime(note.timestamp())); - p.writeByte(note.fame());//FAME :D - }); - return p; - } - public static Packet useChalkboard(Character chr, boolean close) { OutPacket p = OutPacket.create(SendOpcode.CHALKBOARD); p.writeInt(chr.getId()); From 4731c0c60d7a72fc4620d1521d53f1718d21e22e Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 12:18:58 +0100 Subject: [PATCH 16/17] Add tests for NoteService --- src/test/java/service/NoteServiceTest.java | 142 +++++++++++++++++++++ src/test/java/testutil/AnyValues.java | 14 ++ src/test/java/testutil/Mocks.java | 19 +++ 3 files changed, 175 insertions(+) create mode 100644 src/test/java/service/NoteServiceTest.java create mode 100644 src/test/java/testutil/AnyValues.java create mode 100644 src/test/java/testutil/Mocks.java diff --git a/src/test/java/service/NoteServiceTest.java b/src/test/java/service/NoteServiceTest.java new file mode 100644 index 0000000000..9ce7c3cad9 --- /dev/null +++ b/src/test/java/service/NoteServiceTest.java @@ -0,0 +1,142 @@ +package service; + +import database.note.NoteDao; +import model.Note; +import net.packet.out.ShowNotesPacket; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import testutil.Mocks; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static testutil.AnyValues.daoException; +import static testutil.AnyValues.string; + +class NoteServiceTest { + + @Mock + private NoteDao noteDao; + + private NoteService noteService; + + @BeforeEach + void reset() { + MockitoAnnotations.openMocks(this); + this.noteService = new NoteService(noteDao); + } + + @Test + void sendNormalSuccess() { + String message = "message"; + String from = "from"; + String to = "to"; + + boolean success = noteService.sendNormal(message, from, to); + + assertTrue(success); + var noteCaptor = ArgumentCaptor.forClass(Note.class); + verify(noteDao).save(noteCaptor.capture()); + var note = noteCaptor.getValue(); + assertEquals(message, note.message()); + assertEquals(from, note.from()); + assertEquals(to, note.to()); + assertEquals(0, note.fame()); + } + + @Test + void sendWithFameSuccess() { + String message = "fameMessage"; + String from = "fameFrom"; + String to = "fameTo"; + + boolean success = noteService.sendWithFame(message, from, to); + + assertTrue(success); + var noteCaptor = ArgumentCaptor.forClass(Note.class); + verify(noteDao).save(noteCaptor.capture()); + var note = noteCaptor.getValue(); + assertEquals(message, note.message()); + assertEquals(from, note.from()); + assertEquals(to, note.to()); + assertEquals(1, note.fame()); + } + + @Test + void sendFailure() { + doThrow(daoException()).when(noteDao).save(any()); + + boolean success = noteService.sendNormal(string(), string(), string()); + + assertFalse(success); + verify(noteDao).save(any()); + } + + @Test + void showRejectsNull() { + assertThrows(IllegalArgumentException.class, () -> noteService.show(null)); + } + + @Test + void showOneNote() { + String chrName = "showMeNotes"; + var chr = Mocks.chr(chrName); + when(noteDao.findAllByTo(chrName)).thenReturn(List.of(anyNote())); + + noteService.show(chr); + + verify(chr).sendPacket(any(ShowNotesPacket.class)); + } + + private Note anyNote() { + return new Note(1, "message", "from", "to", 100200300400L, 0); + } + + @Test + void showZeroNotes_shouldNotSendPacket() { + var chr = Mocks.chr("mockChr"); + when(noteDao.findAllByTo(any())).thenReturn(Collections.emptyList()); + + noteService.show(chr); + + verify(chr, never()).sendPacket(any()); + } + + @Test + void showNotesFailure_shouldNotSendPacket() { + var chr = Mocks.chr("mockChr"); + when(noteDao.findAllByTo(any())).thenThrow(daoException()); + + noteService.show(chr); + + verify(chr, never()).sendPacket(any()); + } + + @Test + void deleteNoteSuccess() { + int noteId = 1056; + var note = anyNote(); + when(noteDao.delete(noteId)).thenReturn(Optional.of(note)); + + Optional deletedNote = noteDao.delete(noteId); + + assertTrue(deletedNote.isPresent()); + assertEquals(note, deletedNote.get()); + } + + @Test + void deleteNoteFailure() { + when(noteDao.delete(anyInt())).thenThrow(daoException()); + + Optional deletedNote = noteService.delete(4382); + + assertTrue(deletedNote.isEmpty()); + } +} \ No newline at end of file diff --git a/src/test/java/testutil/AnyValues.java b/src/test/java/testutil/AnyValues.java new file mode 100644 index 0000000000..ccf9c0abbf --- /dev/null +++ b/src/test/java/testutil/AnyValues.java @@ -0,0 +1,14 @@ +package testutil; + +import database.DaoException; + +public class AnyValues { + + public static String string() { + return "string"; + } + + public static DaoException daoException() { + return new DaoException(string(), new RuntimeException()); + } +} diff --git a/src/test/java/testutil/Mocks.java b/src/test/java/testutil/Mocks.java new file mode 100644 index 0000000000..5ea5613c04 --- /dev/null +++ b/src/test/java/testutil/Mocks.java @@ -0,0 +1,19 @@ +package testutil; + +import client.Character; +import org.mockito.Mockito; + +import static org.mockito.Mockito.when; + +public class Mocks { + + public static Character chr() { + return Mockito.mock(Character.class); + } + + public static Character chr(String name) { + var chr = chr(); + when(chr.getName()).thenReturn(name); + return chr; + } +} From 37a9a4121f674b63b5b3d898f959417a029696c2 Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 27 Dec 2022 12:31:36 +0100 Subject: [PATCH 17/17] Show confirmation after note is sent --- .../java/net/packet/out/SendNoteSuccessPacket.java | 13 +++++++++++++ .../server/channel/handlers/UseCashItemHandler.java | 2 ++ src/main/java/service/NoteService.java | 6 ++++++ src/main/java/tools/PacketCreator.java | 6 ------ 4 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/packet/out/SendNoteSuccessPacket.java diff --git a/src/main/java/net/packet/out/SendNoteSuccessPacket.java b/src/main/java/net/packet/out/SendNoteSuccessPacket.java new file mode 100644 index 0000000000..a171ea158f --- /dev/null +++ b/src/main/java/net/packet/out/SendNoteSuccessPacket.java @@ -0,0 +1,13 @@ +package net.packet.out; + +import net.opcodes.SendOpcode; +import net.packet.ByteBufOutPacket; + +public final class SendNoteSuccessPacket extends ByteBufOutPacket { + + public SendNoteSuccessPacket() { + super(SendOpcode.MEMO_RESULT); + + writeByte(4); + } +} diff --git a/src/main/java/net/server/channel/handlers/UseCashItemHandler.java b/src/main/java/net/server/channel/handlers/UseCashItemHandler.java index 18d09740bf..1040376772 100644 --- a/src/main/java/net/server/channel/handlers/UseCashItemHandler.java +++ b/src/main/java/net/server/channel/handlers/UseCashItemHandler.java @@ -38,6 +38,7 @@ import constants.id.MapId; import constants.inventory.ItemConstants; import net.AbstractPacketHandler; import net.packet.InPacket; +import net.packet.out.SendNoteSuccessPacket; import net.server.Server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -367,6 +368,7 @@ public final class UseCashItemHandler extends AbstractPacketHandler { boolean sendSuccess = noteService.sendNormal(msg, player.getName(), sendTo); if (sendSuccess) { remove(c, position, itemId); + c.sendPacket(new SendNoteSuccessPacket()); } } else if (itemType == 510) { player.getMap().broadcastMessage(PacketCreator.musicChange("Jukebox/Congratulation")); diff --git a/src/main/java/service/NoteService.java b/src/main/java/service/NoteService.java index 2221083e2f..5cfd58cc54 100644 --- a/src/main/java/service/NoteService.java +++ b/src/main/java/service/NoteService.java @@ -43,6 +43,12 @@ public class NoteService { } private boolean send(Note note) { + // TODO: handle the following cases (originally listed at PacketCreator#noteError) + /* + * 0 = Player online, use whisper + * 1 = Check player's name + * 2 = Receiver inbox full + */ try { noteDao.save(note); return true; diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index 7fe6a0bb83..b7dfe1e1b8 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -5403,12 +5403,6 @@ public class PacketCreator { return p; } - public static Packet noteSendMsg() { - OutPacket p = OutPacket.create(SendOpcode.MEMO_RESULT); - p.writeByte(4); - return p; - } - /* * 0 = Player online, use whisper * 1 = Check player's name