From 3e16648f7b691604dd140058a18a0f8961df3a0d Mon Sep 17 00:00:00 2001 From: danthe1st Date: Fri, 26 Jan 2024 11:45:32 +0100 Subject: [PATCH 1/4] configurable limits for XP subtraction --- .../javabot/data/config/guild/HelpConfig.java | 12 +++++++++++- .../javabot/systems/help/HelpExperienceJob.java | 8 ++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/discordjug/javabot/data/config/guild/HelpConfig.java b/src/main/java/net/discordjug/javabot/data/config/guild/HelpConfig.java index f68c36ee7..9fbbf4a96 100644 --- a/src/main/java/net/discordjug/javabot/data/config/guild/HelpConfig.java +++ b/src/main/java/net/discordjug/javabot/data/config/guild/HelpConfig.java @@ -116,7 +116,17 @@ public class HelpConfig extends GuildConfigItem { private double thankExperience = 3; /** - * The amount that should be subtracted from every Help Account each day. + * The minimum total amount of XP that should be subtracted from every Help Account each day. + */ + private int minDailyExperienceSubtraction = 1; + + /** + * The maximum total amount of XP that should be subtracted from every Help Account each day. + */ + private int maxDailyExperienceSubtraction = 50; + + /** + * The percentage of XP that should be subtracted from every Help Account each day. */ private double dailyExperienceSubtraction = 5; diff --git a/src/main/java/net/discordjug/javabot/systems/help/HelpExperienceJob.java b/src/main/java/net/discordjug/javabot/systems/help/HelpExperienceJob.java index 34b5b43a3..43dbbe8d6 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/HelpExperienceJob.java +++ b/src/main/java/net/discordjug/javabot/systems/help/HelpExperienceJob.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import net.discordjug.javabot.data.config.BotConfig; +import net.discordjug.javabot.data.config.guild.HelpConfig; import net.discordjug.javabot.data.h2db.DbHelper; import net.discordjug.javabot.systems.help.dao.HelpAccountRepository; import net.discordjug.javabot.util.ExceptionLogger; @@ -31,9 +32,12 @@ public class HelpExperienceJob { public void execute() { asyncPool.execute(() -> { try { + // just get the config for the first guild the bot is in, as it's not designed to work in multiple guilds anyway + HelpConfig helpConfig = botConfig.get(jda.getGuilds().get(0)).getHelpConfig(); helpAccountRepository.removeExperienceFromAllAccounts( - // just get the config for the first guild the bot is in, as it's not designed to work in multiple guilds anyway - botConfig.get(jda.getGuilds().get(0)).getHelpConfig().getDailyExperienceSubtraction(), 1, 50); + helpConfig.getDailyExperienceSubtraction(), + helpConfig.getMinDailyExperienceSubtraction(), + helpConfig.getMaxDailyExperienceSubtraction()); } catch (DataAccessException e) { ExceptionLogger.capture(e, DbHelper.class.getSimpleName()); } From 8c66fb1dff42ce1ad9fac61fb04028ccd11c5c72 Mon Sep 17 00:00:00 2001 From: danthe1st Date: Fri, 26 Jan 2024 11:46:44 +0100 Subject: [PATCH 2/4] ensure precision in XP subtraction calculation --- .../javabot/systems/help/dao/HelpAccountRepository.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/net/discordjug/javabot/systems/help/dao/HelpAccountRepository.java b/src/main/java/net/discordjug/javabot/systems/help/dao/HelpAccountRepository.java index 2521b7266..518be7244 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/dao/HelpAccountRepository.java +++ b/src/main/java/net/discordjug/javabot/systems/help/dao/HelpAccountRepository.java @@ -2,7 +2,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import net.discordjug.javabot.data.config.BotConfig; import net.discordjug.javabot.systems.help.model.HelpAccount; import org.jetbrains.annotations.NotNull; @@ -26,7 +25,6 @@ @Repository public class HelpAccountRepository { private final JdbcTemplate jdbcTemplate; - private final BotConfig botConfig; /** * Inserts a new {@link HelpAccount}. @@ -105,7 +103,7 @@ public int getTotalAccounts() throws DataAccessException { * @throws DataAccessException If an error occurs. */ public void removeExperienceFromAllAccounts(double change, int min, int max) throws DataAccessException { - long rows = jdbcTemplate.execute("UPDATE help_account SET experience = GREATEST(experience - LEAST(GREATEST(experience * (? / 100), ?), ?), 0)",new CallableStatementCallback() { + long rows = jdbcTemplate.execute("UPDATE help_account SET experience = GREATEST(experience - LEAST(GREATEST((experience * ?) / 100, ?), ?), 0)",new CallableStatementCallback() { @Override public Long doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { From 427c8ad9b5acf3544afe4dfa2ccf40cba1f5b8eb Mon Sep 17 00:00:00 2001 From: danthe1st Date: Fri, 26 Jan 2024 11:46:59 +0100 Subject: [PATCH 3/4] tests for XP subtraction --- .../javabot/data/h2db/DbHelper.java | 8 +- .../help/HelpExperienceSubtractionTest.java | 115 ++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/test/java/net/discordjug/javabot/systems/help/HelpExperienceSubtractionTest.java diff --git a/src/main/java/net/discordjug/javabot/data/h2db/DbHelper.java b/src/main/java/net/discordjug/javabot/data/h2db/DbHelper.java index 858334480..e351dc98d 100644 --- a/src/main/java/net/discordjug/javabot/data/h2db/DbHelper.java +++ b/src/main/java/net/discordjug/javabot/data/h2db/DbHelper.java @@ -99,7 +99,13 @@ private static boolean shouldInitSchema(String jdbcUrl) { return shouldInitSchema; } - private static void initializeSchema(HikariDataSource dataSource) throws IOException, SQLException { + /** + * Initializes the schema of the database by running all SQL statements from the schema.sql script. + * @param dataSource the {@link DataSource} to connect to the DB + * @throws IOException if an error happened while loading the schema.sql + * @throws SQLException if any SQL error happened + */ + public static void initializeSchema(DataSource dataSource) throws IOException, SQLException { try (InputStream is = DbHelper.class.getClassLoader().getResourceAsStream("database/schema.sql")) { if (is == null) throw new IOException("Could not load schema.sql."); List queries = Arrays.stream(new String(is.readAllBytes()).split(";")) diff --git a/src/test/java/net/discordjug/javabot/systems/help/HelpExperienceSubtractionTest.java b/src/test/java/net/discordjug/javabot/systems/help/HelpExperienceSubtractionTest.java new file mode 100644 index 000000000..919d2d6c6 --- /dev/null +++ b/src/test/java/net/discordjug/javabot/systems/help/HelpExperienceSubtractionTest.java @@ -0,0 +1,115 @@ +package net.discordjug.javabot.systems.help; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.sql.SQLException; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.jdbc.core.JdbcTemplate; + +import com.zaxxer.hikari.HikariDataSource; + +import net.discordjug.javabot.data.h2db.DbHelper; +import net.discordjug.javabot.systems.help.dao.HelpAccountRepository; +import net.discordjug.javabot.systems.help.model.HelpAccount; + +/** + * Tests functionality of automated experience subtraction. + */ +public class HelpExperienceSubtractionTest { + + private HikariDataSource dataSource; + private HelpAccountRepository repo; + + @BeforeEach + void setUp() throws IOException, SQLException { + dataSource = DataSourceBuilder.create() + .type(HikariDataSource.class) + .url("jdbc:h2:mem:test") + .username("test") + .password("") + .build(); + + DbHelper.initializeSchema(dataSource); + + JdbcTemplate template = new JdbcTemplate(dataSource); + repo = new HelpAccountRepository(template); + } + + @AfterEach + void cleanUp() { + dataSource.close(); + } + + /** + * If a user has less XP than the minimum experience subtraction, the user should lose all XP. + */ + @Test + void testUserHasLessThanMinimum() { + repo.insert(new HelpAccount(1, 1)); + repo.removeExperienceFromAllAccounts(50, 2, 10); + assertEquals(0, repo.getByUserId(1).get().getExperience()); + } + + /** + * If the XP to subtract is less than the minimum, the minimum XP should be subtracted. + */ + @Test + void testSubtractMinimum() { + repo.insert(new HelpAccount(1, 6));//6XP + //would remove 50% i.e. 3XP + //the minimum is 4XP hence it should subtract 4XP + repo.removeExperienceFromAllAccounts(50, 4, 10); + assertEquals(2, repo.getByUserId(1).get().getExperience()); + } + + @Test + void testSubtractMaximum() { + repo.insert(new HelpAccount(1, 100)); + //tries to subtract 50% which is 50XP + //but maximum is 10XP hence it should subtract 10XP + repo.removeExperienceFromAllAccounts(50, 1, 10); + assertEquals(90, repo.getByUserId(1).get().getExperience()); + } + + @Test + void testFraction() { + repo.insert(new HelpAccount(1, 100)); + //subtract 10% i.e. 10XP + //should be inside [1,50] hence neither minimum nor maximum is active + repo.removeExperienceFromAllAccounts(10, 1, 50); + assertEquals(90, repo.getByUserId(1).get().getExperience()); + //subtract 10% i.e. 9XP + //should be inside [1,50] hence neither minimum nor maximum is active + repo.removeExperienceFromAllAccounts(10, 1, 50); + assertEquals(81, repo.getByUserId(1).get().getExperience()); + } + + @Test + void testMultipleUsers() { + repo.insert(new HelpAccount(79, 79));//below min + repo.insert(new HelpAccount(80, 80));//exactly min + repo.insert(new HelpAccount(100, 100));//within bounds + repo.insert(new HelpAccount(2_000, 2_000));//within bounds + repo.insert(new HelpAccount(3_999, 3_999));//close to upper bound + repo.insert(new HelpAccount(4_000, 4_000));//exactly upper bound + repo.insert(new HelpAccount(4_001, 4_001));//exceeds upper bound + repo.insert(new HelpAccount(10_000, 10_000));//significantly exceeds upper bound + + repo.removeExperienceFromAllAccounts(1.25, 1, 50); + + double delta = 0.0001;//required precision + assertEquals(78, repo.getByUserId(79).get().getExperience(), delta); + assertEquals(79, repo.getByUserId(80).get().getExperience(), delta); + assertEquals(98.75, repo.getByUserId(100).get().getExperience(), delta); + assertEquals(1975, repo.getByUserId(2_000).get().getExperience(), delta); + assertEquals(3949.0125, repo.getByUserId(3_999).get().getExperience(), delta); + assertEquals(3950, repo.getByUserId(4_000).get().getExperience(), delta); + assertEquals(3951, repo.getByUserId(4_001).get().getExperience(), delta); + assertEquals(9_950, repo.getByUserId(10_000).get().getExperience(), delta); + } +} From 50914ce366db9c43289545844b541801626fcace Mon Sep 17 00:00:00 2001 From: danthe1st Date: Fri, 26 Jan 2024 11:56:54 +0100 Subject: [PATCH 4/4] add half-life test --- .../help/HelpExperienceSubtractionTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/java/net/discordjug/javabot/systems/help/HelpExperienceSubtractionTest.java b/src/test/java/net/discordjug/javabot/systems/help/HelpExperienceSubtractionTest.java index 919d2d6c6..6eee5fdbf 100644 --- a/src/test/java/net/discordjug/javabot/systems/help/HelpExperienceSubtractionTest.java +++ b/src/test/java/net/discordjug/javabot/systems/help/HelpExperienceSubtractionTest.java @@ -1,6 +1,8 @@ package net.discordjug.javabot.systems.help; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.sql.SQLException; @@ -89,6 +91,21 @@ void testFraction() { assertEquals(81, repo.getByUserId(1).get().getExperience()); } + @Test + void testXPHalfLife() { + int startXP = 1_000; + int half = startXP/2; + repo.insert(new HelpAccount(1, startXP)); + for (int i = 0; i < 55; i++) { + repo.removeExperienceFromAllAccounts(1.25, 0, 1_000); + double actualXP = repo.getByUserId(1).get().getExperience(); + assertTrue(actualXP > half, "In iteration "+i+", XP have decayed by more than 50%, user has "+actualXP+"XP after iteration"); + } + repo.removeExperienceFromAllAccounts(1.25, 0, 1_000); + double actualXP = repo.getByUserId(1).get().getExperience(); + assertFalse(actualXP > half, "After all iterations, XP have not decayed by more than 50%, user has "+actualXP+"XP at the end"); + } + @Test void testMultipleUsers() { repo.insert(new HelpAccount(79, 79));//below min