From c9e62500adb87fd0d16ace5b96fcb5f31dec678d Mon Sep 17 00:00:00 2001 From: Melledy <121644117+Melledy@users.noreply.github.com> Date: Wed, 21 Oct 2026 23:46:25 -0700 Subject: [PATCH] Implement anniversary sign in events --- src/main/java/emu/nebula/data/GameData.java | 1 + .../data/resources/MallActivityDef.java | 25 ++++ .../nebula/game/activity/ActivityManager.java | 2 + .../nebula/game/activity/ActivityModule.java | 6 + .../activity/type/LoginRewardActivity.java | 4 +- .../game/activity/type/MallLoginActivity.java | 119 ++++++++++++++++++ .../game/activity/type/SignInActivity.java | 30 +++++ .../java/emu/nebula/game/player/Player.java | 3 + .../HandlerActivityLoginRewardReceiveReq.java | 6 +- ...dlerActivityMallLoginRewardReceiveReq.java | 36 ++++++ .../handlers/HandlerMallActivityListReq.java | 43 +++++++ 11 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 src/main/java/emu/nebula/data/resources/MallActivityDef.java create mode 100644 src/main/java/emu/nebula/game/activity/type/MallLoginActivity.java create mode 100644 src/main/java/emu/nebula/game/activity/type/SignInActivity.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerActivityMallLoginRewardReceiveReq.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerMallActivityListReq.java diff --git a/src/main/java/emu/nebula/data/GameData.java b/src/main/java/emu/nebula/data/GameData.java index ee2e6f0..737a419 100644 --- a/src/main/java/emu/nebula/data/GameData.java +++ b/src/main/java/emu/nebula/data/GameData.java @@ -57,6 +57,7 @@ public class GameData { @Getter private static DataTable MallPackageDataTable = new DataTable<>(); @Getter private static DataTable MallShopDataTable = new DataTable<>(); @Getter private static DataTable MallGemDataTable = new DataTable<>(); + @Getter private static DataTable MallActivityDataTable = new DataTable<>(); @Getter private static DataTable MonthlyCardRewardDataTable = new DataTable<>(); @Getter private static DataTable ResidentShopDataTable = new DataTable<>(); diff --git a/src/main/java/emu/nebula/data/resources/MallActivityDef.java b/src/main/java/emu/nebula/data/resources/MallActivityDef.java new file mode 100644 index 0000000..17a7cb9 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/MallActivityDef.java @@ -0,0 +1,25 @@ +package emu.nebula.data.resources; + +import com.google.gson.annotations.SerializedName; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; + +import lombok.Getter; + +@Getter +@ResourceType(name = "MallActivity.json") +public class MallActivityDef extends BaseDef { + @SerializedName("Id") + private String IdString; + private String Name; + private int Price; + private String ShowPrice; + private int ActivityId; + + @Override + public int getId() { + return IdString.hashCode(); + } + +} diff --git a/src/main/java/emu/nebula/game/activity/ActivityManager.java b/src/main/java/emu/nebula/game/activity/ActivityManager.java index 862a5a1..72bd2cb 100644 --- a/src/main/java/emu/nebula/game/activity/ActivityManager.java +++ b/src/main/java/emu/nebula/game/activity/ActivityManager.java @@ -140,6 +140,8 @@ public class ActivityManager extends PlayerManager implements GameDatabaseObject case Story -> new StoryActivity(this, data); case PenguinCard -> new PenguinCardActivity(this, data); case Soldier -> new SoldierActivity(this, data); + case MallLogin -> new MallLoginActivity(this, data); + case SignIn -> new SignInActivity(this, data); default -> null; }; diff --git a/src/main/java/emu/nebula/game/activity/ActivityModule.java b/src/main/java/emu/nebula/game/activity/ActivityModule.java index 8a6e63e..c0b8792 100644 --- a/src/main/java/emu/nebula/game/activity/ActivityModule.java +++ b/src/main/java/emu/nebula/game/activity/ActivityModule.java @@ -122,6 +122,12 @@ public class ActivityModule extends GameContextModule { // Trekker versus (broken) //this.activities.add(600001); + + // 1st anniversary + this.activities.add(301051); + this.activities.add(301052); + this.activities.add(301053); + this.activities.add(302001); } } diff --git a/src/main/java/emu/nebula/game/activity/type/LoginRewardActivity.java b/src/main/java/emu/nebula/game/activity/type/LoginRewardActivity.java index 30624d3..912fa7b 100644 --- a/src/main/java/emu/nebula/game/activity/type/LoginRewardActivity.java +++ b/src/main/java/emu/nebula/game/activity/type/LoginRewardActivity.java @@ -29,7 +29,7 @@ public class LoginRewardActivity extends GameActivity { super(manager, data); } - public PlayerChangeInfo claim() { + public synchronized PlayerChangeInfo claim() { // Create change info var change = new PlayerChangeInfo(); @@ -64,7 +64,7 @@ public class LoginRewardActivity extends GameActivity { } @Override - public void onLogin() { + public synchronized void onLogin() { // Check if epoch day has changed if (this.getPlayer().getLastEpochDay() <= this.lastEpochDay || this.isComplete()) { return; diff --git a/src/main/java/emu/nebula/game/activity/type/MallLoginActivity.java b/src/main/java/emu/nebula/game/activity/type/MallLoginActivity.java new file mode 100644 index 0000000..b0127c9 --- /dev/null +++ b/src/main/java/emu/nebula/game/activity/type/MallLoginActivity.java @@ -0,0 +1,119 @@ +package emu.nebula.game.activity.type; + +import dev.morphia.annotations.Entity; + +import emu.nebula.data.GameData; +import emu.nebula.data.resources.ActivityDef; +import emu.nebula.game.activity.ActivityManager; +import emu.nebula.game.activity.GameActivity; +import emu.nebula.game.inventory.ItemParamMap; +import emu.nebula.game.player.PlayerChangeInfo; +import emu.nebula.proto.ActivityDetail.ActivityMsg; +import emu.nebula.util.Utils; + +import lombok.Getter; + +/** + * Mall check-in activity + *

+ * These activities are shown inside of the mall instead of the regular activity list and + * they use their own protocol message (ActivityMallLogin). Next to the amount of unlocked + * check-in days and the amount of claimed rewards, that message also carries an "active" + * (purchased) flag which tells the client if the check-in can be claimed at all. + *

+ * The rewards are the same as the ones used by the login reward activities, they are stored + * in the LoginRewardGroup table using the activity id as a prefix (activityId * 100 + day). + */ +@Getter +@Entity +public class MallLoginActivity extends GameActivity { + // Whether the check-in is active for this player + private boolean active; + // Amount of check-in days that have been unlocked + private int loginDays; + // Amount of check-in rewards that have been claimed + private int received; + // Last day the check-in was advanced on + private long lastEpochDay; + + @Deprecated // Morphia only + public MallLoginActivity() { + + } + + public MallLoginActivity(ActivityManager manager, ActivityDef data) { + super(manager, data); + } + + // Events + + @Override + public synchronized void onLogin() { + long epochDay = Utils.getResetEpochDay(); + + // Check if a new day started since the last login + if (epochDay <= this.lastEpochDay) { + return; + } + + // Unlock the next check-in day + int nextLoginDay = this.loginDays + 1; + var data = GameData.getLoginRewardGroupControlDataTable().get((this.getId() * 100) + nextLoginDay); + + if (data != null) { + this.loginDays = nextLoginDay; + } + + // Update the last day and save to database + this.lastEpochDay = epochDay; + this.save(); + } + + // Rewards + + public synchronized PlayerChangeInfo claim() { + // Create change info + var change = new PlayerChangeInfo(); + + // Sanity check + if (!this.isActive() || this.getReceived() >= this.getLoginDays()) { + return change; + } + + // Get rewards + var rewards = new ItemParamMap(); + + for (int i = this.getReceived() + 1; i <= this.getLoginDays(); i++) { + int rewardId = (this.getId() * 100) + i; + var data = GameData.getLoginRewardGroupControlDataTable().get(rewardId); + + if (data == null) { + continue; + } + + rewards.add(data.getRewards()); + } + + // Give rewards to player + this.getPlayer().getInventory().addItems(rewards, change); + + // Update claimed and save to database + this.received = this.loginDays; + this.save(); + + // Complete + return change; + } + + // Proto + + @Override + public void encodeActivityMsg(ActivityMsg msg) { + msg.getMutableMallLogin() + .setActivityId(this.getId()) + .setActive(this.isActive()) + .setLoginDays(this.getLoginDays()) + .setReceived(this.getReceived()); + } + +} diff --git a/src/main/java/emu/nebula/game/activity/type/SignInActivity.java b/src/main/java/emu/nebula/game/activity/type/SignInActivity.java new file mode 100644 index 0000000..1d7ce47 --- /dev/null +++ b/src/main/java/emu/nebula/game/activity/type/SignInActivity.java @@ -0,0 +1,30 @@ +package emu.nebula.game.activity.type; + +import dev.morphia.annotations.Entity; + +import emu.nebula.data.resources.ActivityDef; +import emu.nebula.game.activity.ActivityManager; + +/** + * Sign-in activity + *

+ * The client does not distinguish sign-in activities from login reward activities, both + * activity types are handled by the same login reward data class, they share the same + * protocol message (ActivityLogin) and they are both claimed with the same request. + *

+ * Because of that this activity only exists to give sign-in activities their own + * activity type, all of the actual logic is inherited from the login reward activity. + */ +@Entity +public class SignInActivity extends LoginRewardActivity { + + @Deprecated // Morphia only + public SignInActivity() { + + } + + public SignInActivity(ActivityManager manager, ActivityDef data) { + super(manager, data); + } + +} diff --git a/src/main/java/emu/nebula/game/player/Player.java b/src/main/java/emu/nebula/game/player/Player.java index 0b3aa03..c0628d7 100644 --- a/src/main/java/emu/nebula/game/player/Player.java +++ b/src/main/java/emu/nebula/game/player/Player.java @@ -863,6 +863,9 @@ public class Player implements GameDatabaseObject { // See if we need to reset dailies this.checkResetDailies(); + // Update the activities (login rewards, sign-ins, etc) + this.getActivityManager().onLogin(); + // Try to update trace hunt control if (this.getTraceHuntManager().updateControl()) { this.getTraceHuntManager().save(); diff --git a/src/main/java/emu/nebula/server/handlers/HandlerActivityLoginRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerActivityLoginRewardReceiveReq.java index 356a32a..38fd592 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerActivityLoginRewardReceiveReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerActivityLoginRewardReceiveReq.java @@ -15,8 +15,12 @@ public class HandlerActivityLoginRewardReceiveReq extends NetHandler { // Parse request var req = UI32.parseFrom(message); + // Get activity id from request + int activityId = req.getValue(); + // Get activity - var activity = session.getPlayer().getActivityManager().getActivity(LoginRewardActivity.class, req.getValue()); + // Note: Sign-in activities are handled by the same request as the login reward activities + var activity = session.getPlayer().getActivityManager().getActivity(LoginRewardActivity.class, activityId); if (activity == null) { return session.encodeMsg(NetMsgId.activity_login_reward_receive_failed_ack); diff --git a/src/main/java/emu/nebula/server/handlers/HandlerActivityMallLoginRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerActivityMallLoginRewardReceiveReq.java new file mode 100644 index 0000000..9dc1ba5 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerActivityMallLoginRewardReceiveReq.java @@ -0,0 +1,36 @@ +package emu.nebula.server.handlers; + +import emu.nebula.game.activity.type.MallLoginActivity; +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.UI32; + +@HandlerId(NetMsgId.activity_malllogin_reward_receive_req) +public class HandlerActivityMallLoginRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = UI32.parseFrom(message); + + // Get activity + var activity = session.getPlayer().getActivityManager().getActivity(MallLoginActivity.class, req.getValue()); + + if (activity == null) { + return session.encodeMsg(NetMsgId.activity_malllogin_reward_receive_failed_ack); + } + + // Claim any rewards + var change = activity.claim(); + + if (change == null) { + return session.encodeMsg(NetMsgId.activity_malllogin_reward_receive_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.activity_malllogin_reward_receive_succeed_ack, change.toProto()); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerMallActivityListReq.java b/src/main/java/emu/nebula/server/handlers/HandlerMallActivityListReq.java new file mode 100644 index 0000000..33dad59 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerMallActivityListReq.java @@ -0,0 +1,43 @@ +package emu.nebula.server.handlers; + +import emu.nebula.data.GameData; +import emu.nebula.game.activity.type.MallLoginActivity; +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.MallActivityListOuterClass.MallActivityInfo; +import emu.nebula.proto.MallActivityListOuterClass.MallActivityList; + +@HandlerId(NetMsgId.mall_activity_list_req) +public class HandlerMallActivityListReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get player + var player = session.getPlayer(); + + // Build response + var rsp = MallActivityList.newInstance(); + + for (var data : GameData.getMallActivityDataTable()) { + // Get the activity that this product unlocks + var activity = player.getActivityManager().getActivity(MallLoginActivity.class, data.getActivityId()); + + if (activity == null) { + continue; + } + + // Create proto info + var info = MallActivityInfo.newInstance() + .setId(data.getIdString()) + .setPurchased(activity.isActive()); + + rsp.addList(info); + } + + // Encode and send + return session.encodeMsg(NetMsgId.mall_activity_list_succeed_ack, rsp); + } + +}