You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-20 15:36:17 +00:00
feat(earnings): integrate native reward sources
This commit is contained in:
@@ -113,3 +113,15 @@ INSERT IGNORE INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
|
|||||||
('earnings.club_job.item_id', '0', 'Items base id granted by club and job earnings claims.'),
|
('earnings.club_job.item_id', '0', 'Items base id granted by club and job earnings claims.'),
|
||||||
('earnings.club_job.item.quantity', '1', 'Furni quantity granted by club and job earnings claims.'),
|
('earnings.club_job.item.quantity', '1', 'Furni quantity granted by club and job earnings claims.'),
|
||||||
('earnings.club_job.hc.days', '0', 'HC days granted by club and job earnings claims.');
|
('earnings.club_job.hc.days', '0', 'HC days granted by club and job earnings claims.');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
|
||||||
|
('earnings.daily_gift.native.enabled', '0', 'Use native hotel subsystem data for daily gift earnings claims when available.'),
|
||||||
|
('earnings.games.native.enabled', '0', 'Use native hotel subsystem data for games earnings claims when available.'),
|
||||||
|
('earnings.achievements.native.enabled', '0', 'Use native hotel subsystem data for achievements earnings claims when available.'),
|
||||||
|
('earnings.marketplace.native.enabled', '1', 'Use marketplace sold item payouts for marketplace earnings claims.'),
|
||||||
|
('earnings.hc_payday.native.enabled', '1', 'Use unclaimed HC payday logs for HC payday earnings claims.'),
|
||||||
|
('earnings.level_progress.native.enabled', '0', 'Use native hotel subsystem data for level progress earnings claims when available.'),
|
||||||
|
('earnings.donations.native.enabled', '0', 'Use native hotel subsystem data for donations earnings claims when available.'),
|
||||||
|
('earnings.bonus_bag.native.enabled', '0', 'Use native hotel subsystem data for bonus bag earnings claims when available.'),
|
||||||
|
('earnings.mystery_boxes.native.enabled', '0', 'Use native hotel subsystem data for mystery boxes earnings claims when available.'),
|
||||||
|
('earnings.club_job.native.enabled', '0', 'Use native hotel subsystem data for club and job earnings claims when available.');
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ public final class Emulator {
|
|||||||
Emulator.config.register(prefix + "item_id", "0");
|
Emulator.config.register(prefix + "item_id", "0");
|
||||||
Emulator.config.register(prefix + "item.quantity", "1");
|
Emulator.config.register(prefix + "item.quantity", "1");
|
||||||
Emulator.config.register(prefix + "hc.days", "0");
|
Emulator.config.register(prefix + "hc.days", "0");
|
||||||
|
Emulator.config.register(prefix + "native.enabled", (category.equals("marketplace") || category.equals("hc_payday")) ? "1" : "0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+201
-17
@@ -1,8 +1,10 @@
|
|||||||
package com.eu.habbo.habbohotel.earnings;
|
package com.eu.habbo.habbohotel.earnings;
|
||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.catalog.marketplace.MarketPlace;
|
||||||
import com.eu.habbo.habbohotel.users.Habbo;
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
import com.eu.habbo.habbohotel.users.HabboBadge;
|
import com.eu.habbo.habbohotel.users.HabboBadge;
|
||||||
|
import com.eu.habbo.habbohotel.users.subscriptions.SubscriptionHabboClub;
|
||||||
import com.eu.habbo.messages.outgoing.users.AddUserBadgeComposer;
|
import com.eu.habbo.messages.outgoing.users.AddUserBadgeComposer;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
@@ -26,16 +28,22 @@ public class EarningsCenterManager {
|
|||||||
private final ConfigSource config;
|
private final ConfigSource config;
|
||||||
private final ClaimRepository claims;
|
private final ClaimRepository claims;
|
||||||
private final RewardApplier rewards;
|
private final RewardApplier rewards;
|
||||||
|
private final NativeIntegration nativeIntegration;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
public EarningsCenterManager() {
|
public EarningsCenterManager() {
|
||||||
this(new EmulatorConfigSource(), new JdbcClaimRepository(), new HabboRewardApplier(), Clock.systemUTC());
|
this(new EmulatorConfigSource(), new JdbcClaimRepository(), new HabboRewardApplier(), new DefaultNativeIntegration(), Clock.systemUTC());
|
||||||
}
|
}
|
||||||
|
|
||||||
public EarningsCenterManager(ConfigSource config, ClaimRepository claims, RewardApplier rewards, Clock clock) {
|
public EarningsCenterManager(ConfigSource config, ClaimRepository claims, RewardApplier rewards, Clock clock) {
|
||||||
|
this(config, claims, rewards, new NoopNativeIntegration(), clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EarningsCenterManager(ConfigSource config, ClaimRepository claims, RewardApplier rewards, NativeIntegration nativeIntegration, Clock clock) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.claims = claims;
|
this.claims = claims;
|
||||||
this.rewards = rewards;
|
this.rewards = rewards;
|
||||||
|
this.nativeIntegration = nativeIntegration;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +53,7 @@ public class EarningsCenterManager {
|
|||||||
List<EarningsEntry> entries = new ArrayList<>();
|
List<EarningsEntry> entries = new ArrayList<>();
|
||||||
|
|
||||||
for (EarningsCategory category : EarningsCategory.values()) {
|
for (EarningsCategory category : EarningsCategory.values()) {
|
||||||
entries.add(buildEntry(userId, category, now));
|
entries.add(buildEntry(habbo, userId, category, now));
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
@@ -73,40 +81,68 @@ public class EarningsCenterManager {
|
|||||||
private EarningsClaimResult claim(Habbo habbo, EarningsCategory category) {
|
private EarningsClaimResult claim(Habbo habbo, EarningsCategory category) {
|
||||||
int userId = getUserId(habbo);
|
int userId = getUserId(habbo);
|
||||||
int now = now();
|
int now = now();
|
||||||
CategoryDefinition definition = loadDefinition(category);
|
CategoryDefinition definition = loadDefinition(habbo, category);
|
||||||
|
|
||||||
if (!definition.enabled()) {
|
if (!definition.enabled()) {
|
||||||
return new EarningsClaimResult(category, EarningsClaimResult.Status.DISABLED, buildEntry(userId, category, now));
|
return new EarningsClaimResult(category, EarningsClaimResult.Status.DISABLED, buildEntry(habbo, userId, category, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.nativeIntegration.handles(category) && nativeEnabled(category)) {
|
||||||
|
return claimNative(habbo, userId, category, now, definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.rewards().isEmpty()) {
|
if (definition.rewards().isEmpty()) {
|
||||||
return new EarningsClaimResult(category, EarningsClaimResult.Status.NO_REWARD, buildEntry(userId, category, now));
|
return new EarningsClaimResult(category, EarningsClaimResult.Status.NO_REWARD, buildEntry(habbo, userId, category, now));
|
||||||
}
|
}
|
||||||
|
|
||||||
String periodKey = periodKey(now, definition.cooldownSeconds());
|
String periodKey = periodKey(now, definition.cooldownSeconds());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.claims.recordClaim(userId, category.getKey(), periodKey, now)) {
|
if (!this.claims.recordClaim(userId, category.getKey(), periodKey, now)) {
|
||||||
return new EarningsClaimResult(category, EarningsClaimResult.Status.ALREADY_CLAIMED, buildEntry(userId, category, now));
|
return new EarningsClaimResult(category, EarningsClaimResult.Status.ALREADY_CLAIMED, buildEntry(habbo, userId, category, now));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rewards.grant(habbo, definition.rewards());
|
this.rewards.grant(habbo, definition.rewards());
|
||||||
return new EarningsClaimResult(category, EarningsClaimResult.Status.SUCCESS, buildEntry(userId, category, now));
|
return new EarningsClaimResult(category, EarningsClaimResult.Status.SUCCESS, buildEntry(habbo, userId, category, now));
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
try {
|
try {
|
||||||
this.claims.removeClaim(userId, category.getKey(), periodKey);
|
this.claims.removeClaim(userId, category.getKey(), periodKey);
|
||||||
} catch (SQLException ignored) {
|
} catch (SQLException ignored) {
|
||||||
}
|
}
|
||||||
return new EarningsClaimResult(category, EarningsClaimResult.Status.ERROR, buildEntry(userId, category, now));
|
return new EarningsClaimResult(category, EarningsClaimResult.Status.ERROR, buildEntry(habbo, userId, category, now));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private EarningsEntry buildEntry(int userId, EarningsCategory category, int now) {
|
private EarningsClaimResult claimNative(Habbo habbo, int userId, EarningsCategory category, int now, CategoryDefinition definition) {
|
||||||
CategoryDefinition definition = loadDefinition(category);
|
try {
|
||||||
|
if (definition.rewards().isEmpty() || !this.nativeIntegration.hasClaim(habbo, category)) {
|
||||||
|
return new EarningsClaimResult(category, EarningsClaimResult.Status.NO_REWARD, buildEntry(habbo, userId, category, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.nativeIntegration.claim(habbo, category)
|
||||||
|
? new EarningsClaimResult(category, EarningsClaimResult.Status.SUCCESS, buildEntry(habbo, userId, category, now))
|
||||||
|
: new EarningsClaimResult(category, EarningsClaimResult.Status.ERROR, buildEntry(habbo, userId, category, now));
|
||||||
|
} catch (SQLException e) {
|
||||||
|
return new EarningsClaimResult(category, EarningsClaimResult.Status.ERROR, buildEntry(habbo, userId, category, now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EarningsEntry buildEntry(Habbo habbo, int userId, EarningsCategory category, int now) {
|
||||||
|
CategoryDefinition definition = loadDefinition(habbo, category);
|
||||||
boolean claimable = false;
|
boolean claimable = false;
|
||||||
int nextClaimAt = 0;
|
int nextClaimAt = 0;
|
||||||
|
|
||||||
if (definition.enabled() && !definition.rewards().isEmpty()) {
|
if (definition.enabled() && !definition.rewards().isEmpty()) {
|
||||||
|
if (this.nativeIntegration.handles(category) && nativeEnabled(category)) {
|
||||||
|
try {
|
||||||
|
claimable = this.nativeIntegration.hasClaim(habbo, category);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
claimable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EarningsEntry(category, true, claimable, 0, definition.rewards());
|
||||||
|
}
|
||||||
|
|
||||||
String periodKey = periodKey(now, definition.cooldownSeconds());
|
String periodKey = periodKey(now, definition.cooldownSeconds());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -121,7 +157,7 @@ public class EarningsCenterManager {
|
|||||||
return new EarningsEntry(category, definition.enabled(), claimable, nextClaimAt, definition.rewards());
|
return new EarningsEntry(category, definition.enabled(), claimable, nextClaimAt, definition.rewards());
|
||||||
}
|
}
|
||||||
|
|
||||||
private CategoryDefinition loadDefinition(EarningsCategory category) {
|
private CategoryDefinition loadDefinition(Habbo habbo, EarningsCategory category) {
|
||||||
String key = CONFIG_PREFIX + category.getKey() + ".";
|
String key = CONFIG_PREFIX + category.getKey() + ".";
|
||||||
boolean enabled = this.config.getBoolean(CONFIG_PREFIX + "enabled", false)
|
boolean enabled = this.config.getBoolean(CONFIG_PREFIX + "enabled", false)
|
||||||
&& this.config.getBoolean(key + "enabled", true);
|
&& this.config.getBoolean(key + "enabled", true);
|
||||||
@@ -129,16 +165,27 @@ public class EarningsCenterManager {
|
|||||||
int pointsType = Math.max(0, this.config.getInt(key + "points.type", DEFAULT_POINTS_TYPE));
|
int pointsType = Math.max(0, this.config.getInt(key + "points.type", DEFAULT_POINTS_TYPE));
|
||||||
List<EarningsReward> rewards = new ArrayList<>();
|
List<EarningsReward> rewards = new ArrayList<>();
|
||||||
|
|
||||||
addReward(rewards, EarningsReward.TYPE_CREDITS, this.config.getInt(key + "credits", 0), 0);
|
if (nativeEnabled(category) && this.nativeIntegration.handles(category)) {
|
||||||
addReward(rewards, EarningsReward.TYPE_PIXELS, this.config.getInt(key + "pixels", 0), 0);
|
try {
|
||||||
addReward(rewards, EarningsReward.TYPE_POINTS, this.config.getInt(key + "points", 0), pointsType);
|
rewards.addAll(this.nativeIntegration.rewards(habbo, category));
|
||||||
addBadgeReward(rewards, this.config.getValue(key + "badge", ""));
|
} catch (SQLException ignored) {
|
||||||
addItemReward(rewards, this.config.getInt(key + "item_id", 0), this.config.getInt(key + "item.quantity", 1));
|
}
|
||||||
addHcReward(rewards, this.config.getInt(key + "hc.days", 0));
|
} else {
|
||||||
|
addReward(rewards, EarningsReward.TYPE_CREDITS, this.config.getInt(key + "credits", 0), 0);
|
||||||
|
addReward(rewards, EarningsReward.TYPE_PIXELS, this.config.getInt(key + "pixels", 0), 0);
|
||||||
|
addReward(rewards, EarningsReward.TYPE_POINTS, this.config.getInt(key + "points", 0), pointsType);
|
||||||
|
addBadgeReward(rewards, this.config.getValue(key + "badge", ""));
|
||||||
|
addItemReward(rewards, this.config.getInt(key + "item_id", 0), this.config.getInt(key + "item.quantity", 1));
|
||||||
|
addHcReward(rewards, this.config.getInt(key + "hc.days", 0));
|
||||||
|
}
|
||||||
|
|
||||||
return new CategoryDefinition(enabled, cooldown, rewards);
|
return new CategoryDefinition(enabled, cooldown, rewards);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean nativeEnabled(EarningsCategory category) {
|
||||||
|
return this.config.getBoolean(CONFIG_PREFIX + category.getKey() + ".native.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
private void addReward(List<EarningsReward> rewards, String type, int amount, int pointsType) {
|
private void addReward(List<EarningsReward> rewards, String type, int amount, int pointsType) {
|
||||||
int clampedAmount = Math.min(Math.max(0, amount), MAX_CONFIGURED_REWARD);
|
int clampedAmount = Math.min(Math.max(0, amount), MAX_CONFIGURED_REWARD);
|
||||||
if (clampedAmount > 0) {
|
if (clampedAmount > 0) {
|
||||||
@@ -213,6 +260,16 @@ public class EarningsCenterManager {
|
|||||||
void grant(Habbo habbo, List<EarningsReward> rewards) throws SQLException;
|
void grant(Habbo habbo, List<EarningsReward> rewards) throws SQLException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface NativeIntegration {
|
||||||
|
boolean handles(EarningsCategory category);
|
||||||
|
|
||||||
|
boolean hasClaim(Habbo habbo, EarningsCategory category) throws SQLException;
|
||||||
|
|
||||||
|
List<EarningsReward> rewards(Habbo habbo, EarningsCategory category) throws SQLException;
|
||||||
|
|
||||||
|
boolean claim(Habbo habbo, EarningsCategory category) throws SQLException;
|
||||||
|
}
|
||||||
|
|
||||||
private static class EmulatorConfigSource implements ConfigSource {
|
private static class EmulatorConfigSource implements ConfigSource {
|
||||||
@Override
|
@Override
|
||||||
public boolean getBoolean(String key, boolean defaultValue) {
|
public boolean getBoolean(String key, boolean defaultValue) {
|
||||||
@@ -344,4 +401,131 @@ public class EarningsCenterManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class NoopNativeIntegration implements NativeIntegration {
|
||||||
|
@Override
|
||||||
|
public boolean handles(EarningsCategory category) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasClaim(Habbo habbo, EarningsCategory category) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<EarningsReward> rewards(Habbo habbo, EarningsCategory category) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean claim(Habbo habbo, EarningsCategory category) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultNativeIntegration implements NativeIntegration {
|
||||||
|
@Override
|
||||||
|
public boolean handles(EarningsCategory category) {
|
||||||
|
return category == EarningsCategory.MARKETPLACE || category == EarningsCategory.HC_PAYDAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasClaim(Habbo habbo, EarningsCategory category) throws SQLException {
|
||||||
|
return !rewards(habbo, category).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<EarningsReward> rewards(Habbo habbo, EarningsCategory category) throws SQLException {
|
||||||
|
if (habbo == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category == EarningsCategory.MARKETPLACE) {
|
||||||
|
int soldPriceTotal = habbo.getInventory().getSoldPriceTotal();
|
||||||
|
if (soldPriceTotal <= 0) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MarketPlace.MARKETPLACE_CURRENCY == 0) {
|
||||||
|
return List.of(new EarningsReward(EarningsReward.TYPE_CREDITS, soldPriceTotal, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of(new EarningsReward(EarningsReward.TYPE_POINTS, soldPriceTotal, MarketPlace.MARKETPLACE_CURRENCY));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category == EarningsCategory.HC_PAYDAY) {
|
||||||
|
return hcPaydayRewards(habbo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean claim(Habbo habbo, EarningsCategory category) throws SQLException {
|
||||||
|
if (habbo == null || habbo.getClient() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category == EarningsCategory.MARKETPLACE) {
|
||||||
|
if (habbo.getInventory().getSoldPriceTotal() <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarketPlace.getCredits(habbo.getClient());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category == EarningsCategory.HC_PAYDAY) {
|
||||||
|
if (hcPaydayRewards(habbo).isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubscriptionHabboClub.processUnclaimed(habbo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EarningsReward> hcPaydayRewards(Habbo habbo) throws SQLException {
|
||||||
|
List<EarningsReward> rewards = new ArrayList<>();
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT currency, SUM(total_payout) AS amount FROM logs_hc_payday WHERE user_id = ? AND claimed = 0 GROUP BY currency")) {
|
||||||
|
statement.setInt(1, habbo.getHabboInfo().getId());
|
||||||
|
|
||||||
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
|
while (set.next()) {
|
||||||
|
EarningsReward reward = currencyReward(set.getString("currency"), set.getInt("amount"));
|
||||||
|
if (reward != null) {
|
||||||
|
rewards.add(reward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewards;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EarningsReward currencyReward(String currency, int amount) {
|
||||||
|
if (amount <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = currency == null ? "" : currency.trim().toLowerCase();
|
||||||
|
return switch (normalized) {
|
||||||
|
case "credits", "credit", "coins", "coin" -> new EarningsReward(EarningsReward.TYPE_CREDITS, amount, 0);
|
||||||
|
case "duckets", "ducket", "pixels", "pixel" -> new EarningsReward(EarningsReward.TYPE_PIXELS, amount, 0);
|
||||||
|
case "diamonds", "diamond" -> new EarningsReward(EarningsReward.TYPE_POINTS, amount, 5);
|
||||||
|
default -> {
|
||||||
|
try {
|
||||||
|
yield new EarningsReward(EarningsReward.TYPE_POINTS, amount, Math.max(0, Integer.parseInt(normalized)));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+79
@@ -121,6 +121,44 @@ class EarningsCenterManagerTest {
|
|||||||
assertEquals(EarningsClaimResult.Status.SUCCESS, retried.getStatus());
|
assertEquals(EarningsClaimResult.Status.SUCCESS, retried.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nativeMarketplaceRowsUseNativeClaimInsteadOfPeriodicClaimLedger() {
|
||||||
|
TestConfig config = enabledConfig().with("earnings.marketplace.native.enabled", "1");
|
||||||
|
TestClaims claims = new TestClaims();
|
||||||
|
TestNativeIntegration nativeIntegration = new TestNativeIntegration(EarningsCategory.MARKETPLACE)
|
||||||
|
.withReward(new EarningsReward(EarningsReward.TYPE_CREDITS, 45, 0));
|
||||||
|
EarningsCenterManager manager = new EarningsCenterManager(config, claims, new TestRewards(), nativeIntegration, FIXED_CLOCK);
|
||||||
|
|
||||||
|
EarningsEntry entry = manager.getEntries(null).stream()
|
||||||
|
.filter(current -> current.getCategory() == EarningsCategory.MARKETPLACE)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
EarningsClaimResult result = manager.claim(null, "marketplace");
|
||||||
|
|
||||||
|
assertTrue(entry.isClaimable());
|
||||||
|
assertEquals(45, entry.getRewards().getFirst().getAmount());
|
||||||
|
assertEquals(EarningsClaimResult.Status.SUCCESS, result.getStatus());
|
||||||
|
assertEquals(1, nativeIntegration.claims);
|
||||||
|
assertTrue(claims.claims.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nativeRowsWithoutAvailableRewardsAreNotClaimable() {
|
||||||
|
TestConfig config = enabledConfig().with("earnings.hc_payday.native.enabled", "1");
|
||||||
|
TestNativeIntegration nativeIntegration = new TestNativeIntegration(EarningsCategory.HC_PAYDAY);
|
||||||
|
EarningsCenterManager manager = new EarningsCenterManager(config, new TestClaims(), new TestRewards(), nativeIntegration, FIXED_CLOCK);
|
||||||
|
|
||||||
|
EarningsEntry entry = manager.getEntries(null).stream()
|
||||||
|
.filter(current -> current.getCategory() == EarningsCategory.HC_PAYDAY)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
EarningsClaimResult result = manager.claim(null, "hc_payday");
|
||||||
|
|
||||||
|
assertFalse(entry.isClaimable());
|
||||||
|
assertEquals(EarningsClaimResult.Status.NO_REWARD, result.getStatus());
|
||||||
|
assertEquals(0, nativeIntegration.claims);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void claimAllGrantsClaimableRowsAndSkipsAlreadyClaimedRows() throws SQLException {
|
void claimAllGrantsClaimableRowsAndSkipsAlreadyClaimedRows() throws SQLException {
|
||||||
TestConfig config = enabledConfig()
|
TestConfig config = enabledConfig()
|
||||||
@@ -199,4 +237,45 @@ class EarningsCenterManagerTest {
|
|||||||
this.granted.addAll(rewards);
|
this.granted.addAll(rewards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TestNativeIntegration implements EarningsCenterManager.NativeIntegration {
|
||||||
|
private final EarningsCategory category;
|
||||||
|
private final List<EarningsReward> rewards = new ArrayList<>();
|
||||||
|
private int claims = 0;
|
||||||
|
|
||||||
|
private TestNativeIntegration(EarningsCategory category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestNativeIntegration withReward(EarningsReward reward) {
|
||||||
|
this.rewards.add(reward);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handles(EarningsCategory category) {
|
||||||
|
return this.category == category;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasClaim(com.eu.habbo.habbohotel.users.Habbo habbo, EarningsCategory category) {
|
||||||
|
return handles(category) && !this.rewards.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<EarningsReward> rewards(com.eu.habbo.habbohotel.users.Habbo habbo, EarningsCategory category) {
|
||||||
|
return handles(category) ? List.copyOf(this.rewards) : List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean claim(com.eu.habbo.habbohotel.users.Habbo habbo, EarningsCategory category) {
|
||||||
|
if (!hasClaim(habbo, category)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.claims++;
|
||||||
|
this.rewards.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ This document is the emulator-side contract for the "Guadagni" UI.
|
|||||||
|
|
||||||
For `points`, `pointsType` carries the currency type. For `badge`, `data` carries the badge code. For `item`, `data` carries the `items_base.id`. Other reward types keep `data` empty.
|
For `points`, `pointsType` carries the currency type. For `badge`, `data` carries the badge code. For `item`, `data` carries the `items_base.id`. Other reward types keep `data` empty.
|
||||||
|
|
||||||
|
`marketplace` and `hc_payday` can be native rows. In native mode the amounts come from existing server state:
|
||||||
|
|
||||||
|
- `marketplace`: sold marketplace offers waiting for payout
|
||||||
|
- `hc_payday`: unclaimed rows in `logs_hc_payday`
|
||||||
|
|
||||||
## Result Status
|
## Result Status
|
||||||
|
|
||||||
- `success`
|
- `success`
|
||||||
|
|||||||
@@ -63,8 +63,10 @@ Add emulator settings with safe defaults:
|
|||||||
- `earnings.<category>.item_id=0`
|
- `earnings.<category>.item_id=0`
|
||||||
- `earnings.<category>.item.quantity=1`
|
- `earnings.<category>.item.quantity=1`
|
||||||
- `earnings.<category>.hc.days=0`
|
- `earnings.<category>.hc.days=0`
|
||||||
|
- `earnings.<category>.native.enabled=0/1`
|
||||||
|
|
||||||
The feature defaults off so existing hotels do not receive surprise economy changes after deploying the jar.
|
The feature defaults off so existing hotels do not receive surprise economy changes after deploying the jar.
|
||||||
|
Marketplace and HC payday default to native integrations once the feature is enabled, because both already have server-side claim ledgers.
|
||||||
|
|
||||||
## Packet Contract
|
## Packet Contract
|
||||||
|
|
||||||
@@ -90,6 +92,8 @@ Composer format is intentionally simple and renderer-friendly: category key, ena
|
|||||||
- Roll back the claim record if a DB-backed reward grant fails.
|
- Roll back the claim record if a DB-backed reward grant fails.
|
||||||
- Use the database unique key to prevent concurrent double claims.
|
- Use the database unique key to prevent concurrent double claims.
|
||||||
- `claim all` processes only claimable rows and returns per-category results.
|
- `claim all` processes only claimable rows and returns per-category results.
|
||||||
|
- Marketplace claims use the existing marketplace sold-offer payout path.
|
||||||
|
- HC payday claims use existing unclaimed `logs_hc_payday` rows.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user