Implement trial activities

This commit is contained in:
Melledy
2025-11-30 15:19:35 -08:00
parent f53bdaba32
commit a04f3354f7
16 changed files with 612 additions and 10 deletions

View File

@@ -131,4 +131,10 @@ public class GameData {
// Score boss
@Getter private static DataTable<ScoreBossControlDef> ScoreBossControlDataTable = new DataTable<>();
// Activity
@Getter private static DataTable<ActivityDef> ActivityDataTable = new DataTable<>();
@Getter private static DataTable<TrialControlDef> TrialControlDataTable = new DataTable<>();
@Getter private static DataTable<TrialGroupDef> TrialGroupDataTable = new DataTable<>();
}

View File

@@ -0,0 +1,25 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import lombok.Getter;
@Getter
@ResourceType(name = "Activity.json")
public class ActivityDef extends BaseDef {
private int Id;
private int ActivityType;
private transient emu.nebula.game.activity.ActivityType type;
@Override
public int getId() {
return Id;
}
@Override
public void onLoad() {
this.type = emu.nebula.game.activity.ActivityType.getByValue(this.ActivityType);
}
}

View File

@@ -0,0 +1,18 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import lombok.Getter;
@Getter
@ResourceType(name = "TrialControl.json")
public class TrialControlDef extends BaseDef {
private int Id;
private IntOpenHashSet GroupIds;
@Override
public int getId() {
return Id;
}
}

View File

@@ -0,0 +1,41 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import emu.nebula.game.inventory.ItemParamMap;
import lombok.Getter;
@Getter
@ResourceType(name = "TrialGroup.json")
public class TrialGroupDef extends BaseDef {
private int Id;
private int RewardId1;
private int Qty1;
private int RewardId2;
private int Qty2;
private int RewardId3;
private int Qty3;
private transient ItemParamMap rewards;
@Override
public int getId() {
return Id;
}
@Override
public void onLoad() {
this.rewards = new ItemParamMap();
if (this.RewardId1 > 0) {
this.rewards.add(this.RewardId1, this.Qty1);
}
if (this.RewardId2 > 0) {
this.rewards.add(this.RewardId2, this.Qty2);
}
if (this.RewardId3 > 0) {
this.rewards.add(this.RewardId3, this.Qty3);
}
}
}

View File

@@ -26,7 +26,6 @@ import de.bwaldvogel.mongo.backend.h2.H2Backend;
import de.bwaldvogel.mongo.backend.memory.MemoryBackend;
import dev.morphia.*;
import dev.morphia.annotations.Entity;
import dev.morphia.mapping.Mapper;
import dev.morphia.mapping.MapperOptions;
import dev.morphia.query.FindOptions;
import dev.morphia.query.Sort;
@@ -84,7 +83,7 @@ public final class DatabaseManager {
.stream()
.filter(cls -> {
Entity e = cls.getAnnotation(Entity.class);
return e != null && !e.value().equals(Mapper.IGNORED_FIELDNAME);
return e != null;
})
.toList();

View File

@@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit;
import emu.nebula.GameConstants;
import emu.nebula.Nebula;
import emu.nebula.game.activity.ActivityModule;
import emu.nebula.game.gacha.GachaModule;
import emu.nebula.game.player.PlayerModule;
import emu.nebula.game.scoreboss.ScoreBossModule;
@@ -27,6 +28,7 @@ public class GameContext implements Runnable {
private final PlayerModule playerModule;
private final GachaModule gachaModule;
private final TutorialModule tutorialModule;
private final ActivityModule activityModule;
private final ScoreBossModule scoreBossModule;
// Game loop
@@ -43,6 +45,7 @@ public class GameContext implements Runnable {
this.playerModule = new PlayerModule(this);
this.gachaModule = new GachaModule(this);
this.tutorialModule = new TutorialModule(this);
this.activityModule = new ActivityModule(this);
this.scoreBossModule = new ScoreBossModule(this);
// Run game loop

View File

@@ -0,0 +1,127 @@
package emu.nebula.game.activity;
import java.util.HashMap;
import java.util.Map;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.activity.type.*;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerManager;
import lombok.Getter;
import lombok.Setter;
@Getter
@Entity(value = "activity", useDiscriminator = false)
public class ActivityManager extends PlayerManager implements GameDatabaseObject {
@Id
private int uid;
// Achievement data
private Map<Integer, GameActivity> activities;
@Setter
private transient boolean queueSave;
@Deprecated // Morphia only
public ActivityManager() {
}
public ActivityManager(Player player) {
super(player);
this.uid = player.getUid();
this.activities = new HashMap<>();
}
public <T extends GameActivity> T getActivity(Class<T> activityClass, int id) {
// Get activity first
var activity = this.getActivities().get(id);
if (activity == null) return null;
// Check activity type
if (activityClass.isInstance(activity)) {
return activityClass.cast(activity);
}
// Failure
return null;
}
/**
* This needs to be called after being loaded from the database
*/
public synchronized void init() {
// Check if any activities
var it = this.getActivities().entrySet().iterator();
while (it.hasNext()) {
var entry = it.next();
var activity = entry.getValue();
// Validate the activity to make sure it exists
var data = GameData.getActivityDataTable().get(activity.getId());
if (data == null) {
it.remove();
this.queueSave = true;
}
// Set data
activity.setData(data);
activity.setManager(this);
}
// Load activities
for (var id : Nebula.getGameContext().getActivityModule().getActivities()) {
// Check if we already have this activity
if (this.getActivities().containsKey(id)) {
continue;
}
// Create activity
var activity = this.createActivity(id);
if (activity == null) {
continue;
}
// Add activity
this.getActivities().put(id, activity);
// Set save flag
this.queueSave = true;
}
// Save if any activities were changed
if (this.queueSave) {
this.save();
}
}
private GameActivity createActivity(int id) {
// Get activity data first
var data = GameData.getActivityDataTable().get(id);
if (data == null) {
return null;
}
GameActivity activity = switch (data.getType()) {
case Trial -> new TrialActivity(this, data);
default -> null;
};
return activity;
}
// Database
@Override
public void save() {
Nebula.getGameDatabase().save(this);
this.queueSave = false;
}
}

View File

@@ -0,0 +1,27 @@
package emu.nebula.game.activity;
import emu.nebula.game.GameContext;
import emu.nebula.game.GameContextModule;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter;
@Getter
public class ActivityModule extends GameContextModule {
private IntList activities;
public ActivityModule(GameContext context) {
super(context);
this.activities = new IntArrayList();
// Hardcode these activities for now
this.activities.add(700102);
this.activities.add(700103);
this.activities.add(700104);
this.activities.add(700107);
//this.activities.add(101002);
//this.activities.add(101003);
}
}

View File

@@ -0,0 +1,40 @@
package emu.nebula.game.activity;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
public enum ActivityType {
None (0),
PeriodicQuest (1),
LoginReward (2),
Mining (3),
Cookie (4),
TowerDefense (5),
Trial (6),
JointDrill (7),
CG (8),
Levels (9),
Avg (10),
Task (11),
Shop (12),
Advertise (13);
@Getter
private final int value;
private final static Int2ObjectMap<ActivityType> map = new Int2ObjectOpenHashMap<>();
static {
for (ActivityType type : ActivityType.values()) {
map.put(type.getValue(), type);
}
}
private ActivityType(int value) {
this.value = value;
}
public static ActivityType getByValue(int value) {
return map.get(value);
}
}

View File

@@ -0,0 +1,65 @@
package emu.nebula.game.activity;
import dev.morphia.annotations.Entity;
import emu.nebula.Nebula;
import emu.nebula.data.resources.ActivityDef;
import emu.nebula.game.player.Player;
import emu.nebula.proto.ActivityDetail.ActivityMsg;
import emu.nebula.proto.Public.Activity;
import lombok.Getter;
import lombok.Setter;
@Getter
@Entity(useDiscriminator = true)
public abstract class GameActivity {
private int id;
@Setter private transient ActivityManager manager;
@Setter private transient ActivityDef data;
@Deprecated // Morhpia only
public GameActivity() {
}
public GameActivity(ActivityManager manager, ActivityDef data) {
this.id = data.getId();
this.manager = manager;
this.data = data;
}
public Player getPlayer() {
return this.getManager().getPlayer();
}
public void save() {
Nebula.getGameDatabase().update(
this.getManager(),
this.getManager().getPlayerUid(),
"activities." + this.getId(),
this
);
}
// Proto
public Activity toProto() {
var proto = Activity.newInstance()
.setId(this.getId())
.setStartTime(1)
.setEndTime(Integer.MAX_VALUE);
return proto;
}
public ActivityMsg toMsgProto() {
var proto = ActivityMsg.newInstance()
.setId(this.getId());
this.encodeActivityMsg(proto);
return proto;
}
public abstract void encodeActivityMsg(ActivityMsg msg);
}

View File

@@ -0,0 +1,67 @@
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.player.PlayerChangeInfo;
import emu.nebula.proto.ActivityDetail.ActivityMsg;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter;
@Getter
@Entity
public class TrialActivity extends GameActivity {
private IntList completed;
@Deprecated // Morphia only
public TrialActivity() {
}
public TrialActivity(ActivityManager manager, ActivityDef data) {
super(manager, data);
this.completed = new IntArrayList();
}
public PlayerChangeInfo claimReward(int groupId) {
// Create change info
var change = new PlayerChangeInfo();
// Make sure we haven't completed this group yet
if (this.getCompleted().contains(groupId)) {
return change;
}
// Get trial control
var control = GameData.getTrialControlDataTable().get(this.getId());
if (control == null) return change;
// Get group
var group = GameData.getTrialGroupDataTable().get(groupId);
if (group == null) return change;
// Set as completed
this.getCompleted().add(groupId);
// Save to database
this.save();
// Add rewards
return getPlayer().getInventory().addItems(group.getRewards(), change);
}
// Proto
@Override
public void encodeActivityMsg(ActivityMsg msg) {
var proto = msg.getMutableTrial();
for (int id : this.getCompleted()) {
proto.addCompletedGroupIds(id);
}
}
}

View File

@@ -8,7 +8,6 @@ import emu.nebula.Nebula;
import emu.nebula.data.resources.ChatDef;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.Contacts;
import emu.nebula.proto.Public.UI32;

View File

@@ -14,6 +14,7 @@ import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.account.Account;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.achievement.AchievementManager;
import emu.nebula.game.activity.ActivityManager;
import emu.nebula.game.agent.AgentManager;
import emu.nebula.game.battlepass.BattlePassManager;
import emu.nebula.game.character.CharacterStorage;
@@ -108,6 +109,7 @@ public class Player implements GameDatabaseObject {
private transient QuestManager questManager;
private transient AchievementManager achievementManager;
private transient AgentManager agentManager;
private transient ActivityManager activityManager;
// Extra
private transient Stack<NetMsgPacket> nextPackages;
@@ -691,6 +693,7 @@ public class Player implements GameDatabaseObject {
this.questManager = this.loadManagerFromDatabase(QuestManager.class);
this.achievementManager = this.loadManagerFromDatabase(AchievementManager.class);
this.agentManager = this.loadManagerFromDatabase(AgentManager.class);
this.activityManager = this.loadManagerFromDatabase(ActivityManager.class);
// Database fixes
if (this.showChars == null) {
@@ -698,6 +701,9 @@ public class Player implements GameDatabaseObject {
this.save();
}
// Init activities
this.getActivityManager().init();
// Load complete
this.loaded = true;
}
@@ -905,6 +911,11 @@ public class Player implements GameDatabaseObject {
agentProto.addInfos(agent.toProto());
}
// Activities
for (var activity : getActivityManager().getActivities().values()) {
proto.addActivities(activity.toProto());
}
// Complete
return proto;
}

View File

@@ -0,0 +1,144 @@
package emu.nebula.game.quest;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
public enum QuestCondType {
BattleTotal (3),
BattlesTotalWithPartner (4),
CharacterAcquireQuantityRarityAndAdvancement (6),
CharacterAdvanceTotal (7),
CharacterSkillUpTotal (8),
CharacterSkillWithSpecificUpTotal (9),
CharacterUpTotal (12),
CharacterWithSpecificAdvance (13),
CharacterWithSpecificUpLevel (15),
CharactersWithSpecificNumberLevelAndAttributes (17),
CharactersWithSpecificQuantityAdvancementCountAndAttribute (19),
CharactersWithSpecificQuantityRarityAndLevel (22),
ChatTotal (23),
DailyInstanceClearSpecificDifficultyAndTotal (24),
DailyInstanceClearSpecificTypeAndTotal (25),
DailyInstanceClearTotal (26),
DiscAcquireQuantityLevelAndRarity (30),
DiscAcquireQuantityPhaseAndRarity (31),
DiscAcquireQuantityStarAndRarity (32),
DiscLimitBreakTotal (33),
DiscPromoteTotal (34),
DiscStrengthenTotal (35),
DiscWithSpecificQuantityLevelAndRarity (36),
DiscWithSpecificQuantityPhaseAndRarity (37),
DiscWithSpecificQuantityStarAndRarity (38),
EnergyDeplete (39),
GachaTotal (44),
GiftGiveTotal (45),
InfinityTowerClearSpecificFloor (46),
InfinityTowerClearTotal (47),
ItemsAdd (48),
ItemsDeplete (49),
ItemsProductTotal (50),
LoginTotal (51),
QuestTravelerDuelChallengeTotal (52),
QuestTourGuideSpecific (53),
QuestTravelerDuelSpecific (54),
QuestWithSpecificType (55),
RegionBossClearSpecificFullStarWithBossIdAndDifficulty (56),
RegionBossClearSpecificLevelWithDifficultyAndTotal (57),
RegionBossClearSpecificTotal (58),
RegionBossClearTotal (59),
SkillsWithSpecificQuantityAndLevel (60),
StageClearSpecificStars (62),
StoryClear (63),
TravelerDuelChallengeSpecificBoosLevelWithDifficultyAndTotal (64),
TravelerDuelClearBossTotal (65),
TravelerDuelClearSpecificBossIdAndDifficulty (66),
TravelerDuelChallengeClearSpecificBossLevelAndAffix (67),
TravelerDuelClearSpecificBossLevelWithDifficultyAndTotal (68),
TravelerDuelClearSpecificBossTotal (69),
TravelerDuelChallengeRankUploadTotal (70),
WorldClassSpecific (71),
RegionBossClearSpecificTypeWithTotal (72),
CharactersWithSpecificDatingCount (73),
CharactersDatingTotal (74),
VampireSurvivorPassedSpecificLevel (77),
CharacterParticipateTowerNumber (78),
CharacterAllSkillReachSpecificLevel (79),
TravelerDuelPlayTotal (80),
VampireClearTotal (81),
VampireWithSpecificClearTotal (82),
AgentFinishTotal (83),
AgentWithSpecificFinishTotal (84),
ActivityMiningEnterLayer (86),
ActivityMiningDestroyGrid (87),
BossRushTotalStars (88),
InfinityTowerClearSpecificDifficultyAndTotal (89),
SkillInstanceClearTotal (90),
VampireSurvivorSpecificPassedLevel (91),
WeekBoosClearSpecificDifficultyAndTotal (92),
NpcAffinityWithSpecificLevel (93),
CharacterPassedWithSpecificTowerAndCount (94),
ActivityCookieLevelAccPackage (96),
ActivityCookieLevelScore (97),
ActivityCookieTypeAccPackage (98),
ActivityCookieTypeAccPackCookie (99),
ActivityCookieTypeAccRhythm (100),
ActivityCookieTypeChallenge (101),
CharGemInstanceClearTotal (104),
DailyShopReceiveShopTotal (105),
AgentApplyTotal (106),
ActivityScore (107),
ActivityTypeAvgReadWithSpecificIdAndLevelId (108),
ActivityTypeLevelPassedWithSpecificIdAndLevelId (109),
ActivityTypeLevel3StarPassedWithSpecificIdAndLevelId (110),
ActivityTypeLevelStarWithSpecificIdAndLevelTypeTotal (111),
ActivityTypeLevelPassedWithSpecificIdAndLevelIdAndSpecificPositionAndCharElem (112),
ActivityTypeLevelPassedSpecificIdTotal (113),
ClientReport (200),
TowerBuildSpecificScoreWithTotal (504),
TowerClearSpecificLevelWithDifficultyAndTotal (507),
TowerEnterTotal (510),
TowerSpecificDifficultyShopBuyTimes (514),
TowerGrowthSpecificNote (515),
TowerClearSpecificLevelWithDifficultyAndTotalHistory (516),
TowerBookWithSpecificEvent (517),
TowerBookWithSpecificFateCard (518),
TowerBookWithSpecificPotential (520),
TowerBuildSpecificDifficultyAndScoreWithTotal (521),
TowerSpecificDifficultyStrengthenMachineTotal (522),
TowerSpecificDifficultyKillBossTotal (524),
TowerBookSpecificCharWithPotentialTotal (525),
TowerBuildSpecificCharSpecificScoreWithTotal (526),
TowerGrowthWithSpecificNote (527),
TowerSpecificFateCardReRollTotal (528),
TowerSpecificPotentialReRollTotal (529),
TowerSpecificShopReRollTotal (530),
TowerSpecificNoteActivateTotal (531),
TowerSpecificNoteLevelTotal (532),
TowerSpecificPotentialBonusTotal (533),
TowerSpecificPotentialLuckyTotal (534),
TowerSpecificShopBuyDiscountTotal (535),
TowerSpecificSecondarySkillActivateTotal (536),
TowerSpecificGetExtraNoteLvTotal (537),
TowerEnterFloor (538),
TowerSweepTimes (539),
TowerSweepTotal (540);
@Getter
private final int value;
private final static Int2ObjectMap<QuestCondType> map = new Int2ObjectOpenHashMap<>();
static {
for (QuestCondType type : QuestCondType.values()) {
map.put(type.getValue(), type);
}
}
private QuestCondType(int value) {
this.value = value;
}
public static QuestCondType getByValue(int value) {
return map.get(value);
}
}

View File

@@ -2,9 +2,7 @@ package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.ActivityDetail.ActivityMsg;
import emu.nebula.proto.ActivityDetail.ActivityResp;
import emu.nebula.proto.Public.ActivityTrial;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@@ -13,14 +11,14 @@ public class HandlerActivityDetailReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Build response
var rsp = ActivityResp.newInstance();
var activity = ActivityMsg.newInstance()
.setId(700101)
.setTrial(ActivityTrial.newInstance());
rsp.addList(activity);
for (var activity : session.getPlayer().getActivityManager().getActivities().values()) {
rsp.addList(activity.toMsgProto());
}
// Encode and send
return session.encodeMsg(NetMsgId.activity_detail_succeed_ack, rsp);
}

View File

@@ -0,0 +1,32 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.ActivityTrialRewardReceive.ActivityTrialRewardReceiveReq;
import emu.nebula.net.HandlerId;
import emu.nebula.game.activity.type.TrialActivity;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.activity_trial_reward_receive_req)
public class HandlerActivityTrialRewardReceiveReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = ActivityTrialRewardReceiveReq.parseFrom(message);
// Get activity
var activity = session.getPlayer().getActivityManager().getActivity(TrialActivity.class, req.getActivityId());
if (activity == null) {
return session.encodeMsg(NetMsgId.activity_trial_reward_receive_failed_ack);
}
// Recieve reward
var change = activity.claimReward(req.getGroupId());
// Encode and send
return session.encodeMsg(NetMsgId.activity_trial_reward_receive_succeed_ack, change.toProto());
}
}