Implement anniversary sign in events

This commit is contained in:
Melledy committed 2026-10-21 23:46:25 -07:00
1 parent f5bdee5df3
commit c9e62500ad
11 files changed
+272 -3

No files matched your search

@@ -57,6 +57,7 @@ public class GameData {
@Getter private static DataTable<MallPackageDef> MallPackageDataTable = new DataTable<>();
@Getter private static DataTable<MallShopDef> MallShopDataTable = new DataTable<>();
@Getter private static DataTable<MallGemDef> MallGemDataTable = new DataTable<>();
@Getter private static DataTable<MallActivityDef> MallActivityDataTable = new DataTable<>();
@Getter private static DataTable<MonthlyCardRewardDef> MonthlyCardRewardDataTable = new DataTable<>();
@Getter private static DataTable<ResidentShopDef> ResidentShopDataTable = new DataTable<>();
@@ -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();
}
}
@@ -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;
};
@@ -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);
}
}
@@ -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;
@@ -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
* <p>
* 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.
* <p>
* 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());
}
}
@@ -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
* <p>
* 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.
* <p>
* 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);
}
}
@@ -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();
@@ -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);
@@ -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());
}
}
@@ -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);
}
}