diff --git a/src/main/java/database/JdbiConfig.java b/src/main/java/database/JdbiConfig.java index 19d5b0c772..7caa49e434 100644 --- a/src/main/java/database/JdbiConfig.java +++ b/src/main/java/database/JdbiConfig.java @@ -1,5 +1,6 @@ package database; +import database.drop.MonsterDropRowMapper; import database.maker.MakerIngredientRowMapper; import database.maker.MakerReagentRowMapper; import database.maker.MakerRecipeRowMapper; @@ -16,6 +17,7 @@ public final class JdbiConfig { .registerRowMapper(new NoteRowMapper()) .registerRowMapper(new MakerReagentRowMapper()) .registerRowMapper(new MakerRecipeRowMapper()) - .registerRowMapper(new MakerIngredientRowMapper()); + .registerRowMapper(new MakerIngredientRowMapper()) + .registerRowMapper(new MonsterDropRowMapper()); } } diff --git a/src/main/java/database/drop/DropDao.java b/src/main/java/database/drop/DropDao.java new file mode 100644 index 0000000000..8e3cc0a02e --- /dev/null +++ b/src/main/java/database/drop/DropDao.java @@ -0,0 +1,29 @@ +package database.drop; + +import database.DaoException; +import database.PgDatabaseConnection; +import org.jdbi.v3.core.Handle; +import org.jdbi.v3.core.JdbiException; + +import java.util.List; + +public class DropDao { + private final PgDatabaseConnection connection; + + public DropDao(PgDatabaseConnection connection) { + this.connection = connection; + } + + public List getMonsterDrops(int monsterId) { + try (Handle handle = connection.getHandle()) { + return handle.createQuery(""" + SELECT * + FROM monster_drop + WHERE monster_id = ?;""") + .mapTo(MonsterDrop.class) + .list(); + } catch (JdbiException e) { + throw new DaoException("Failed to get monster drops for id %d".formatted(monsterId), e); + } + } +} diff --git a/src/main/java/database/drop/DropProvider.java b/src/main/java/database/drop/DropProvider.java new file mode 100644 index 0000000000..c272b13ad6 --- /dev/null +++ b/src/main/java/database/drop/DropProvider.java @@ -0,0 +1,32 @@ +package database.drop; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import server.life.MonsterDropEntry; + +import java.util.List; + +public class DropProvider { + private final DropDao dropDao; + private final Cache> monsterDropCache = Caffeine.newBuilder().build(); + + public DropProvider(DropDao dropDao) { + if (dropDao == null) { + throw new IllegalArgumentException("DropDao must not be null"); + } + this.dropDao = dropDao; + } + + public List getMonsterDropEntries(int monsterId) { + return monsterDropCache.get(monsterId, dropDao::getMonsterDrops).stream() + .map(this::mapToDropEntry) + .toList(); + } + + // TODO: Temporary. MonsterDropEntry should be removed. + private MonsterDropEntry mapToDropEntry(MonsterDrop monsterDrop) { + short questId = monsterDrop.questId() == null ? 0 : monsterDrop.questId().shortValue(); + return new MonsterDropEntry(monsterDrop.itemId(), monsterDrop.chance(), monsterDrop.minQuantity(), + monsterDrop.maxQuantity(), questId); + } +} diff --git a/src/main/java/database/drop/MonsterDrop.java b/src/main/java/database/drop/MonsterDrop.java new file mode 100644 index 0000000000..0591713d8a --- /dev/null +++ b/src/main/java/database/drop/MonsterDrop.java @@ -0,0 +1,4 @@ +package database.drop; + +public record MonsterDrop(int monsterId, int itemId, int minQuantity, int maxQuantity, Integer questId, int chance) { +} diff --git a/src/main/java/database/drop/MonsterDropRowMapper.java b/src/main/java/database/drop/MonsterDropRowMapper.java new file mode 100644 index 0000000000..668022ce3c --- /dev/null +++ b/src/main/java/database/drop/MonsterDropRowMapper.java @@ -0,0 +1,21 @@ +package database.drop; + +import org.jdbi.v3.core.mapper.RowMapper; +import org.jdbi.v3.core.statement.StatementContext; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class MonsterDropRowMapper implements RowMapper { + + @Override + public MonsterDrop map(ResultSet rs, StatementContext ctx) throws SQLException { + final int monsterId = rs.getInt("monster_id"); + final int itemId = rs.getInt("item_id"); + final int minQuantity = rs.getInt("min_quantity"); + final int maxQuantity = rs.getInt("max_quantity"); + final Integer questId = rs.getObject("quest_id", Integer.class); + final int chance = rs.getInt("chance"); + return new MonsterDrop(monsterId, itemId, minQuantity, maxQuantity, questId, chance); + } +} diff --git a/src/test/java/database/drop/DropProviderTest.java b/src/test/java/database/drop/DropProviderTest.java new file mode 100644 index 0000000000..0b6a9c2e2c --- /dev/null +++ b/src/test/java/database/drop/DropProviderTest.java @@ -0,0 +1,79 @@ +package database.drop; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import server.life.MonsterDropEntry; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +class DropProviderTest { + + @Mock + private DropDao dropDao; + + private DropProvider dropProvider; + + @BeforeEach + void reset() { + MockitoAnnotations.openMocks(this); + this.dropProvider = new DropProvider(dropDao); + } + + @Test + void getMonsterDropEntries_noDrops() { + when(dropDao.getMonsterDrops(anyInt())).thenReturn(Collections.emptyList()); + + List dropEntries = dropProvider.getMonsterDropEntries(489340); + + assertTrue(dropEntries.isEmpty()); + } + + @Test + void getMonsterDropEntries() { + MonsterDrop snailShellDrop = snailShellDrop(); + when(dropDao.getMonsterDrops(anyInt())).thenReturn(List.of(snailShellDrop)); + + List dropEntries = dropProvider.getMonsterDropEntries(100100); + + assertEquals(1, dropEntries.size()); + MonsterDropEntry dropEntry = dropEntries.get(0); + assertEquals(snailShellDrop.itemId(), dropEntry.itemId); + assertEquals(snailShellDrop.minQuantity(), dropEntry.Minimum); + assertEquals(snailShellDrop.maxQuantity(), dropEntry.Maximum); + assertEquals(snailShellDrop.chance(), dropEntry.chance); + assertEquals(0, dropEntry.questid); + } + + @Test + void getCachedMonsterDropEntries() { + when(dropDao.getMonsterDrops(anyInt())).thenReturn(List.of(snailShellDrop())); + int monsterId = 100100; + + List dropEntries1 = dropProvider.getMonsterDropEntries(monsterId); + List dropEntries2 = dropProvider.getMonsterDropEntries(monsterId); + + assertEquals(1, dropEntries1.size()); + assertEquals(1, dropEntries2.size()); + MonsterDropEntry dropEntry1 = dropEntries1.get(0); + MonsterDropEntry dropEntry2 = dropEntries2.get(0); + assertEquals(dropEntry1.itemId, dropEntry2.itemId); + assertEquals(dropEntry1.Minimum, dropEntry2.Minimum); + assertEquals(dropEntry1.Maximum, dropEntry2.Maximum); + assertEquals(dropEntry1.questid, dropEntry2.questid); + assertEquals(dropEntry1.chance, dropEntry2.chance); + verify(dropDao, times(1)).getMonsterDrops(anyInt()); + } + + private MonsterDrop snailShellDrop() { + return new MonsterDrop(100100, 4000019, 1, 2, null, 600_000); + } + +}