From f007b6624f555da84d669ac678bc9559b7ef9d65 Mon Sep 17 00:00:00 2001 From: Melledy <121644117+Melledy@users.noreply.github.com> Date: Sun, 15 Mar 2026 02:33:40 -0700 Subject: [PATCH] Implement Finale Echoing (Permanent) - Sweeps and quests this game mode are not implemented yet --- src/main/java/emu/nebula/GameConstants.java | 1 + src/main/java/emu/nebula/data/GameData.java | 3 + .../nebula/data/resources/ActivityDef.java | 18 ++ .../data/resources/JointDrill2LevelDef.java | 63 ++++ .../java/emu/nebula/game/GameContext.java | 3 + .../nebula/game/activity/ActivityManager.java | 1 + .../nebula/game/activity/ActivityModule.java | 3 + .../nebula/game/activity/GameActivity.java | 14 +- .../activity/type/JointDrillActivity.java | 273 ++++++++++++++++++ .../game/jointdrill/JointDrillBuild.java | 17 ++ .../game/jointdrill/JointDrillModule.java | 55 ++++ .../game/jointdrill/JointDrillRankEntry.java | 190 ++++++++++++ .../game/jointdrill/JointDrillScore.java | 40 +++ .../java/emu/nebula/game/player/Player.java | 6 + .../handlers/HandlerJointDrill2ApplyReq.java | 43 +++ .../HandlerJointDrill2ContinueReq.java | 37 +++ .../HandlerJointDrill2GameOverReq.java | 40 +++ .../handlers/HandlerJointDrill2GiveUpReq.java | 44 +++ .../HandlerJointDrill2RetreatReq.java | 16 + .../handlers/HandlerJointDrill2SettleReq.java | 49 ++++ .../handlers/HandlerJointDrill2SyncReq.java | 16 + .../handlers/HandlerJointDrillRankReq.java | 54 ++++ src/main/resources/versions.json | 10 +- 23 files changed, 989 insertions(+), 7 deletions(-) create mode 100644 src/main/java/emu/nebula/data/resources/JointDrill2LevelDef.java create mode 100644 src/main/java/emu/nebula/game/activity/type/JointDrillActivity.java create mode 100644 src/main/java/emu/nebula/game/jointdrill/JointDrillBuild.java create mode 100644 src/main/java/emu/nebula/game/jointdrill/JointDrillModule.java create mode 100644 src/main/java/emu/nebula/game/jointdrill/JointDrillRankEntry.java create mode 100644 src/main/java/emu/nebula/game/jointdrill/JointDrillScore.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerJointDrill2ApplyReq.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerJointDrill2ContinueReq.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerJointDrill2GameOverReq.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerJointDrill2GiveUpReq.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerJointDrill2RetreatReq.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerJointDrill2SettleReq.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerJointDrill2SyncReq.java create mode 100644 src/main/java/emu/nebula/server/handlers/HandlerJointDrillRankReq.java diff --git a/src/main/java/emu/nebula/GameConstants.java b/src/main/java/emu/nebula/GameConstants.java index 6c9bc38..99a485b 100644 --- a/src/main/java/emu/nebula/GameConstants.java +++ b/src/main/java/emu/nebula/GameConstants.java @@ -22,6 +22,7 @@ public class GameConstants { public static final int ENERGY_BUY_ITEM_ID = GEM_ITEM_ID; public static final int EXP_ITEM_ID = 21; public static final int WEEKLY_ENTRY_ITEM_ID = 28; + public static final int JOINT_DRILL_TICKET_ID = 36; public static final int MAX_ENERGY = 240; public static final int ENERGY_REGEN_TIME = 360; // Seconds diff --git a/src/main/java/emu/nebula/data/GameData.java b/src/main/java/emu/nebula/data/GameData.java index 25559cb..ad65e51 100644 --- a/src/main/java/emu/nebula/data/GameData.java +++ b/src/main/java/emu/nebula/data/GameData.java @@ -166,6 +166,9 @@ public class GameData { @Getter private static DataTable TrialControlDataTable = new DataTable<>(); @Getter private static DataTable TrialGroupDataTable = new DataTable<>(); + // Activity: Joint Drill + @Getter private static DataTable JointDrill2LevelDataTable = new DataTable<>(); + // Activity: Levels @Getter private static DataTable ActivityLevelsLevelDataTable = new DataTable<>(); diff --git a/src/main/java/emu/nebula/data/resources/ActivityDef.java b/src/main/java/emu/nebula/data/resources/ActivityDef.java index 2a84d06..e6ffc25 100644 --- a/src/main/java/emu/nebula/data/resources/ActivityDef.java +++ b/src/main/java/emu/nebula/data/resources/ActivityDef.java @@ -10,8 +10,12 @@ import lombok.Getter; public class ActivityDef extends BaseDef { private int Id; private int ActivityType; + //private String StartTime; + //private String EndTime; private transient emu.nebula.game.activity.ActivityType type; + //private transient long startTimeSec; + //private transient long endTimeSec; @Override public int getId() { @@ -20,6 +24,20 @@ public class ActivityDef extends BaseDef { @Override public void onLoad() { + // Cache activity type this.type = emu.nebula.game.activity.ActivityType.getByValue(this.ActivityType); + + // Parse start/end times + /* + if (this.StartTime != null) { + var start = OffsetDateTime.parse(this.StartTime); + this.startTimeSec = start.toEpochSecond(); + } + + if (this.EndTime != null) { + var end = OffsetDateTime.parse(this.EndTime); + this.endTimeSec = end.toEpochSecond(); + } + */ } } diff --git a/src/main/java/emu/nebula/data/resources/JointDrill2LevelDef.java b/src/main/java/emu/nebula/data/resources/JointDrill2LevelDef.java new file mode 100644 index 0000000..63d332c --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/JointDrill2LevelDef.java @@ -0,0 +1,63 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import emu.nebula.game.inventory.ItemRewardList; +import emu.nebula.game.inventory.ItemRewardParam; +import emu.nebula.util.JsonUtils; +import lombok.Getter; + +@Getter +@ResourceType(name = "JointDrill_2_Level.json") +public class JointDrill2LevelDef extends BaseDef { + private int Id; + private int BattleTime; + + private int TimeScore; + private int ScorePerSec; + private int LevelScore; + private int BaseHpScore; + + private String RewardPreview; + + private transient ItemRewardList firstRewards; + private transient ItemRewardList rewards; + + @Override + public int getId() { + return Id; + } + + @Override + public void onLoad() { + // Init reward lists + this.firstRewards = new ItemRewardList(); + this.rewards = new ItemRewardList(); + + // Parse rewards + var awards = JsonUtils.decodeList(this.RewardPreview, int[].class); + if (awards == null) { + return; + } + + for (int[] award : awards) { + int itemId = award[0]; + int min = award[1]; + int max = min; + boolean isFirst = award[award.length - 1] == 1; + + if (min == -1) { + min = 0; + max = 1; + } + + var reward = new ItemRewardParam(itemId, min, max); + + if (isFirst) { + this.firstRewards.add(reward); + } else { + this.rewards.add(reward); + } + } + } +} diff --git a/src/main/java/emu/nebula/game/GameContext.java b/src/main/java/emu/nebula/game/GameContext.java index 735d2cc..fe0581d 100644 --- a/src/main/java/emu/nebula/game/GameContext.java +++ b/src/main/java/emu/nebula/game/GameContext.java @@ -11,6 +11,7 @@ import emu.nebula.Nebula; import emu.nebula.game.activity.ActivityModule; import emu.nebula.game.ban.BanModule; import emu.nebula.game.gacha.GachaModule; +import emu.nebula.game.jointdrill.JointDrillModule; import emu.nebula.game.player.PlayerModule; import emu.nebula.game.scoreboss.ScoreBossModule; import emu.nebula.game.tutorial.TutorialModule; @@ -31,6 +32,7 @@ public class GameContext implements Runnable { private final TutorialModule tutorialModule; private final ActivityModule activityModule; private final ScoreBossModule scoreBossModule; + private final JointDrillModule jointDrillModule; private final BanModule banModule; // Game loop @@ -50,6 +52,7 @@ public class GameContext implements Runnable { this.tutorialModule = new TutorialModule(this); this.activityModule = new ActivityModule(this); this.scoreBossModule = new ScoreBossModule(this); + this.jointDrillModule = new JointDrillModule(this); this.banModule = new BanModule(this); // Run game loop diff --git a/src/main/java/emu/nebula/game/activity/ActivityManager.java b/src/main/java/emu/nebula/game/activity/ActivityManager.java index d8360b3..1410e62 100644 --- a/src/main/java/emu/nebula/game/activity/ActivityManager.java +++ b/src/main/java/emu/nebula/game/activity/ActivityManager.java @@ -132,6 +132,7 @@ public class ActivityManager extends PlayerManager implements GameDatabaseObject case LoginReward -> new LoginRewardActivity(this, data); case TowerDefense -> new TowerDefenseActivity(this, data); case Trial -> new TrialActivity(this, data); + case JointDrill -> new JointDrillActivity(this, data); case Levels -> new LevelsActivity(this, data); case Task -> new TaskActivity(this, data); case Shop -> new ShopActivity(this, data); diff --git a/src/main/java/emu/nebula/game/activity/ActivityModule.java b/src/main/java/emu/nebula/game/activity/ActivityModule.java index 87fc338..d1ee750 100644 --- a/src/main/java/emu/nebula/game/activity/ActivityModule.java +++ b/src/main/java/emu/nebula/game/activity/ActivityModule.java @@ -44,6 +44,9 @@ public class ActivityModule extends GameContextModule { this.activities.add(1010503); this.activities.add(1010504); + // ===== Joint Drills (Finale Echoing) ===== + this.activities.add(510003); + // ===== Etc Events ===== // Trial activities diff --git a/src/main/java/emu/nebula/game/activity/GameActivity.java b/src/main/java/emu/nebula/game/activity/GameActivity.java index 63d6afa..64fad3f 100644 --- a/src/main/java/emu/nebula/game/activity/GameActivity.java +++ b/src/main/java/emu/nebula/game/activity/GameActivity.java @@ -31,6 +31,16 @@ public abstract class GameActivity { public Player getPlayer() { return this.getManager().getPlayer(); } + + public long getStartTime() { + return 1; + } + + public long getEndTime() { + return Integer.MAX_VALUE; + } + + // Database public void save() { Nebula.getGameDatabase().update( @@ -52,8 +62,8 @@ public abstract class GameActivity { public Activity toProto() { var proto = Activity.newInstance() .setId(this.getId()) - .setStartTime(1) - .setEndTime(Integer.MAX_VALUE); + .setStartTime(this.getStartTime()) + .setEndTime(this.getEndTime()); return proto; } diff --git a/src/main/java/emu/nebula/game/activity/type/JointDrillActivity.java b/src/main/java/emu/nebula/game/activity/type/JointDrillActivity.java new file mode 100644 index 0000000..a15a612 --- /dev/null +++ b/src/main/java/emu/nebula/game/activity/type/JointDrillActivity.java @@ -0,0 +1,273 @@ +package emu.nebula.game.activity.type; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import dev.morphia.annotations.Entity; +import emu.nebula.GameConstants; +import emu.nebula.Nebula; +import emu.nebula.data.GameData; +import emu.nebula.data.resources.ActivityDef; +import emu.nebula.data.resources.JointDrill2LevelDef; +import emu.nebula.game.activity.ActivityManager; +import emu.nebula.game.activity.GameActivity; +import emu.nebula.game.jointdrill.JointDrillBuild; +import emu.nebula.game.jointdrill.JointDrillRankEntry; +import emu.nebula.game.jointdrill.JointDrillScore; +import emu.nebula.game.player.PlayerChangeInfo; +import emu.nebula.game.tower.StarTowerBuild; +import emu.nebula.proto.ActivityDetail.ActivityMsg; +import emu.nebula.proto.PublicJointDrill.JointDrillLevel; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import lombok.Getter; + +@Getter +@Entity +public class JointDrillActivity extends GameActivity { + private Int2IntMap passedLevels; + + // Current level + private transient JointDrill2LevelDef level; + private transient StarTowerBuild build; + private transient boolean simulate; + + private transient List teams; + + // Ranking + private transient boolean checkedDatabase; + private transient JointDrillRankEntry ranking; + + @Deprecated // Morphia only + public JointDrillActivity() { + this.teams = new ArrayList<>(); + } + + public JointDrillActivity(ActivityManager manager, ActivityDef data) { + super(manager, data); + + // Give the player starting amount of tickets + int tickets = this.getPlayer().getInventory().getResourceCount(GameConstants.JOINT_DRILL_TICKET_ID); + if (tickets < 3) { + this.getPlayer().getInventory().addItem(GameConstants.JOINT_DRILL_TICKET_ID, 3 - tickets); + } + } + + public Int2IntMap getPassedLevels() { + if (this.passedLevels == null) { + this.passedLevels = new Int2IntOpenHashMap(); + } + + return this.passedLevels; + } + + public long getStartTime() { + // Force activity to be always open for 6+ days + return Nebula.getCurrentServerTime() - TimeUnit.DAYS.toSeconds(1); + } + + public void reset() { + this.level = null; + this.build = null; + this.teams.clear(); + } + + // Joint drill + + public JointDrillRankEntry getRankEntry() { + if (this.ranking == null && !this.checkedDatabase) { + this.ranking = Nebula.getGameDatabase().getObjectByUid(JointDrillRankEntry.class, this.getPlayer().getUid()); + this.checkedDatabase = true; + } + + return this.ranking; + } + + public synchronized boolean inProgress() { + return this.level != null; + } + + public synchronized PlayerChangeInfo apply(int levelId, long buildId, boolean simulate) { + // Get level and record used + var level = GameData.getJointDrill2LevelDataTable().get(levelId); + var build = this.getPlayer().getStarTowerManager().getBuildById(buildId); + + // Verify that level and record exists + if (level == null || build == null) { + return null; + } + + // Create result + PlayerChangeInfo change = null; + + // Consume ticket IF not simulated + if (!simulate) { + // Make sure player has a ticket + if (!getPlayer().getInventory().hasItem(GameConstants.JOINT_DRILL_TICKET_ID, 1)) { + return null; + } + + change = getPlayer().getInventory().removeItem(GameConstants.JOINT_DRILL_TICKET_ID, 1); + } else { + change = new PlayerChangeInfo(); + } + + // Set level and build + this.level = level; + this.build = build; + this.teams.clear(); + this.simulate = simulate; + + // Failure - No level or record found + return change; + } + + public synchronized JointDrillScore settle(int time, int damage, boolean win) { + // Make sure we are currently in progress + if (!this.inProgress()) { + return null; + } + + // Create score data + var score = new JointDrillScore(); + + // Calculate score + if (win) { + score.calculateScore(this.getLevel(), time); + } else { + // TODO calculate partial score + } + + // Add joint drill team + if (this.getBuild() != null) { + var team = new JointDrillBuild(this.getBuild(), time, damage); + this.getTeams().add(team); + } + + // Only update if we are not simulating a challenge + if (!this.simulate && win) { + // Check if this is our first clear + boolean isFirstClear = this.getPassedLevels().containsKey(this.getLevel().getId()); + + // Get current score + int currentScore = this.getPassedLevels().get(this.getLevel().getId()); + + // Update score + if (score.getTotal() >= currentScore) { + // Update passed levels + this.getPassedLevels().put(this.getLevel().getId(), score.getTotal()); + + // Save activity to database + this.save(); + } + + // Handle ranking + if (this.getTeams().size() > 0) { + // Get ranking from database + this.getRankEntry(); + + // Create ranking if its not in the database + if (this.ranking == null) { + this.ranking = new JointDrillRankEntry(this.getPlayer(), this.getId()); + } + + // Settle ranking and save if we have a higher score + if (score.getTotal() > this.ranking.getScore()) { + this.ranking.settle(this.getPlayer(), this.getTeams(), this.getId(), score.getTotal()); + this.ranking.save(); + } + } + + // Handle rewards + if (isFirstClear) { + score.getRewards().add(this.getLevel().getFirstRewards().generate()); + } + + score.getRewards().add(this.getLevel().getRewards().generate()); + + // Add rewards + this.getPlayer().getInventory().addItems(score.getRewards(), score.getChange()); + } + + // Reset level + this.reset(); + + // Finished + return score; + } + + public synchronized boolean continueDrill(long buildId) { + // Make sure we are currently in progress + if (!this.inProgress()) { + return false; + } + + if (this.getTeams().size() >= 3) { + return false; + } + + // Get build + var build = this.getPlayer().getStarTowerManager().getBuildById(buildId); + + if (build == null) { + return false; + } + + // TODO validate build as unique + + // Set build + this.build = build; + + // Complete + return true; + } + + public synchronized JointDrillScore giveup(int time, int damage) { + // Make sure we are currently in progress + if (!this.inProgress() || this.getBuild() == null) { + return null; + } + + // Create and calculate score + var score = new JointDrillScore(); + + // TODO calculate partial score + + // Log joint drill team + var team = new JointDrillBuild(this.getBuild(), time, damage); + this.getTeams().add(team); + + // Clear build + this.build = null; + + // Complete + return score; + } + + // Proto + + @Override + public void encodeActivityMsg(ActivityMsg msg) { + var proto = msg.getMutableJointDrill(); + + // Mark + proto.getMutableMeta(); + proto.getMutableMode2(); + + // Add passed levels + if (this.passedLevels != null) { + for (var entry : this.passedLevels.int2IntEntrySet()) { + int levelId = entry.getIntKey(); + int score = entry.getIntValue(); + + var info = JointDrillLevel.newInstance() + .setLevelId(levelId) + .setScore(score); + + proto.addPassedLevels(info); + } + } + } + +} diff --git a/src/main/java/emu/nebula/game/jointdrill/JointDrillBuild.java b/src/main/java/emu/nebula/game/jointdrill/JointDrillBuild.java new file mode 100644 index 0000000..d87c221 --- /dev/null +++ b/src/main/java/emu/nebula/game/jointdrill/JointDrillBuild.java @@ -0,0 +1,17 @@ +package emu.nebula.game.jointdrill; + +import emu.nebula.game.tower.StarTowerBuild; +import lombok.Getter; + +@Getter +public class JointDrillBuild { + private StarTowerBuild build; + private int time; + private int damage; + + public JointDrillBuild(StarTowerBuild build, int time, int damage) { + this.build = build; + this.time = time; + this.damage = damage; + } +} diff --git a/src/main/java/emu/nebula/game/jointdrill/JointDrillModule.java b/src/main/java/emu/nebula/game/jointdrill/JointDrillModule.java new file mode 100644 index 0000000..2e03fbf --- /dev/null +++ b/src/main/java/emu/nebula/game/jointdrill/JointDrillModule.java @@ -0,0 +1,55 @@ +package emu.nebula.game.jointdrill; + +import java.util.ArrayList; +import java.util.List; + +import emu.nebula.Nebula; +import emu.nebula.game.GameContext; +import emu.nebula.game.GameContextModule; +import emu.nebula.proto.JointDrillRank.JointDrillRankData; + +@SuppressWarnings("unused") +public class JointDrillModule extends GameContextModule { + private long lastUpdate; + private long nextUpdate; + private List ranking; + + public JointDrillModule(GameContext context) { + super(context); + this.nextUpdate = -1; + this.ranking = new ArrayList<>(); + } + + private long getRefreshTime() { + return Nebula.getConfig().getServerOptions().leaderboardRefreshTime * 1000; + } + + public synchronized List getRanking(int activityId) { + if (System.currentTimeMillis() > this.nextUpdate) { + this.updateRanking(activityId); + } + + return this.ranking; + } + + // Cache ranking so we dont query the database too much + private void updateRanking(int activityId) { + // Clear + this.ranking.clear(); + + // Get from database + var list = Nebula.getGameDatabase().getSortedObjects(JointDrillRankEntry.class, "activityId", activityId, "score", 50); + + for (int i = 0; i < list.size(); i++) { + // Get rank entry and set proto + var entry = list.get(i); + entry.setRank(i + 1); + + // Add to ranking + this.ranking.add(entry.toProto()); + } + + this.nextUpdate = System.currentTimeMillis() + this.getRefreshTime(); + this.lastUpdate = Nebula.getCurrentServerTime(); + } +} diff --git a/src/main/java/emu/nebula/game/jointdrill/JointDrillRankEntry.java b/src/main/java/emu/nebula/game/jointdrill/JointDrillRankEntry.java new file mode 100644 index 0000000..30c3f45 --- /dev/null +++ b/src/main/java/emu/nebula/game/jointdrill/JointDrillRankEntry.java @@ -0,0 +1,190 @@ +package emu.nebula.game.jointdrill; + +import java.util.ArrayList; +import java.util.List; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; + +import emu.nebula.database.GameDatabaseObject; +import emu.nebula.game.player.Player; +import emu.nebula.game.tower.StarTowerBuild; +import emu.nebula.game.character.GameCharacter; +import emu.nebula.proto.JointDrillRank.JointDrillRankChar; +import emu.nebula.proto.JointDrillRank.JointDrillRankData; +import emu.nebula.proto.JointDrillRank.JointDrillRankTeam; +import emu.nebula.proto.Public.HonorInfo; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Entity(value = "joint_drill_rank", useDiscriminator = false) +public class JointDrillRankEntry implements GameDatabaseObject { + @Id + private int playerUid; + + private String name; + private int level; + private int headIcon; + private int titlePrefix; + private int titleSuffix; + private int[] honor; + private int score; + private IntSet claimedRewards; + + private int activityId; + private List teams; + + @Setter + private transient int rank; + + @Deprecated // Morphia only + public JointDrillRankEntry() { + this.rank = 999; + } + + public JointDrillRankEntry(Player player, int activityId) { + this.playerUid = player.getUid(); + this.activityId = activityId; + this.teams = new ArrayList<>(); + } + + public IntSet getClaimedRewards() { + if (this.claimedRewards == null) { + this.claimedRewards = new IntOpenHashSet(); + } + + return this.claimedRewards; + } + + public void update(Player player) { + this.name = player.getName(); + this.level = player.getLevel(); + this.headIcon = player.getHeadIcon(); + this.titlePrefix = player.getTitlePrefix(); + this.titleSuffix = player.getTitleSuffix(); + this.honor = player.getHonor(); + } + + public void settle(Player player, List builds, int activityId, int score) { + // Update player data + this.update(player); + + // Reset score entry if activity id doesn't match + if (this.activityId != activityId) { + this.activityId = activityId; + this.reset(); + } + + // Add teams + for (var build : builds) { + var team = new JointDrillTeamEntry(player, build.getBuild(), build.getTime(), build.getDamage()); + this.getTeams().add(team); + } + + // Calculate score + this.score = score; + } + + private void reset() { + this.score = 0; + this.getClaimedRewards().clear(); + this.getTeams().clear(); + } + + // Proto + + public JointDrillRankData toProto() { + var proto = JointDrillRankData.newInstance() + .setId(this.getPlayerUid()) + .setNickName(this.getName()) + .setWorldClass(this.getLevel()) + .setHeadIcon(this.getHeadIcon()) + .setScore(this.getScore()) + .setTitlePrefix(this.getTitlePrefix()) + .setTitleSuffix(this.getTitleSuffix()) + .setRank(this.getRank()); + + for (int id : this.getHonor()) { + proto.addHonors(HonorInfo.newInstance().setId(id)); + } + + for (var team : this.getTeams()) { + proto.addTeams(team.toProto()); + } + + return proto; + } + + // Extra classes + + @Getter + @Entity(useDiscriminator = false) + public static class JointDrillTeamEntry { + private int buildId; + private int buildScore; + private int damage; + private int time; + private List characters; + + @Deprecated // Morphia only + public JointDrillTeamEntry() { + + } + + public JointDrillTeamEntry(Player player, StarTowerBuild build, int time, int damage) { + this.buildId = build.getUid(); + this.buildScore = build.getScore(); + this.time = time; + this.damage = damage; + this.characters = new ArrayList<>(); + + for (var charId : build.getCharIds()) { + var character = player.getCharacters().getCharacterById(charId); + if (character != null) { + this.getCharacters().add(new JointDrillCharEntry(character)); + } + } + } + + public JointDrillRankTeam toProto() { + var proto = JointDrillRankTeam.newInstance() + .setBuildScore(this.getBuildScore()) + .setDamage(this.getDamage()) + .setTime(this.getTime()); + + for (var c : this.getCharacters()) { + proto.addChars(c.toProto()); + } + + return proto; + } + } + + @Getter + @Entity(useDiscriminator = false) + public static class JointDrillCharEntry { + private int id; + private int level; + + @Deprecated // Morphia only + public JointDrillCharEntry() { + + } + + public JointDrillCharEntry(GameCharacter character) { + this.id = character.getCharId(); + this.level = character.getLevel(); + } + + public JointDrillRankChar toProto() { + var proto = JointDrillRankChar.newInstance() + .setId(this.getId()) + .setLevel(this.getLevel()); + + return proto; + } + } +} diff --git a/src/main/java/emu/nebula/game/jointdrill/JointDrillScore.java b/src/main/java/emu/nebula/game/jointdrill/JointDrillScore.java new file mode 100644 index 0000000..77ec95a --- /dev/null +++ b/src/main/java/emu/nebula/game/jointdrill/JointDrillScore.java @@ -0,0 +1,40 @@ +package emu.nebula.game.jointdrill; + +import emu.nebula.data.resources.JointDrill2LevelDef; +import emu.nebula.game.inventory.ItemParamMap; +import emu.nebula.game.player.PlayerChangeInfo; +import lombok.Getter; + +@Getter +public class JointDrillScore { + // Scores + private int fight; + private int hp; + private int difficulty; + private int total; + + // Items + private PlayerChangeInfo change; + private ItemParamMap rewards; + + public JointDrillScore() { + this.change = new PlayerChangeInfo(); + this.rewards = new ItemParamMap(); + } + + public void calculateScore(JointDrill2LevelDef level, int time) { + // Clamp time + time = Math.max(time, 0); + + // Get time penalty + int timePenalty = (int) (level.getScorePerSec() * (time / 1000D)); + + // Calculate scores + this.difficulty = level.getLevelScore(); + this.hp = level.getBaseHpScore(); + this.fight = Math.max(level.getTimeScore() - timePenalty, 0); + + // Calculate total score + this.total = this.difficulty + this.hp + this.fight; + } +} diff --git a/src/main/java/emu/nebula/game/player/Player.java b/src/main/java/emu/nebula/game/player/Player.java index 689395e..4529af8 100644 --- a/src/main/java/emu/nebula/game/player/Player.java +++ b/src/main/java/emu/nebula/game/player/Player.java @@ -707,6 +707,12 @@ public class Player implements GameDatabaseObject { this.getQuestManager().resetDailyQuests(resetWeekly); this.getBattlePassManager().getBattlePass().resetDailyQuests(resetWeekly); + // Add daily joint drill tickets + int tickets = this.getInventory().getResourceCount(GameConstants.JOINT_DRILL_TICKET_ID); + if (tickets < 3) { + this.getInventory().addItem(GameConstants.JOINT_DRILL_TICKET_ID, 3 - tickets); + } + // Check to reset weeklies if (resetWeekly) { // Add weekly boss entry item diff --git a/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2ApplyReq.java b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2ApplyReq.java new file mode 100644 index 0000000..7b5fa10 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2ApplyReq.java @@ -0,0 +1,43 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.JointDrill2Apply.JointDrill2ApplyReq; +import emu.nebula.proto.JointDrill2Apply.JointDrill2ApplyResp; +import emu.nebula.net.HandlerId; +import emu.nebula.Nebula; +import emu.nebula.game.activity.type.JointDrillActivity; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.joint_drill_2_apply_req) +public class HandlerJointDrill2ApplyReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get joint drill activity + var activity = session.getPlayer().getActivityManager().getFirstActivity(JointDrillActivity.class); + + if (activity == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_apply_failed_ack); + } + + // Parse request + var req = JointDrill2ApplyReq.parseFrom(message); + + // Apply for joint drill stage + var change = activity.apply(req.getLevelId(), req.getBuildId(), req.getSimulate()); + + if (change == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_apply_failed_ack); + } + + // Create response packet + var rsp = JointDrill2ApplyResp.newInstance() + .setStarTime(Nebula.getCurrentServerTime() + activity.getLevel().getBattleTime()) + .setChange(change.toProto()); + + // Encode and send + return session.encodeMsg(NetMsgId.joint_drill_2_apply_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2ContinueReq.java b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2ContinueReq.java new file mode 100644 index 0000000..306a1aa --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2ContinueReq.java @@ -0,0 +1,37 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.JointDrill2Continue.JointDrill2ContinueReq; +import emu.nebula.net.HandlerId; +import emu.nebula.game.activity.type.JointDrillActivity; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.joint_drill_2_continue_req) +public class HandlerJointDrill2ContinueReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get joint drill activity + var activity = session.getPlayer().getActivityManager().getFirstActivity(JointDrillActivity.class); + + if (activity == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_continue_failed_ack); + } + + // Parse request + var req = JointDrill2ContinueReq.parseFrom(message); + + // Continue + var success = activity.continueDrill(req.getBuildId()); + + // Check + if (!success) { + return session.encodeMsg(NetMsgId.joint_drill_2_continue_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.joint_drill_2_continue_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2GameOverReq.java b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2GameOverReq.java new file mode 100644 index 0000000..46584fe --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2GameOverReq.java @@ -0,0 +1,40 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.PublicJointDrill.JointDrillSettle; +import emu.nebula.net.HandlerId; +import emu.nebula.game.activity.type.JointDrillActivity; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.joint_drill_2_game_over_req) +public class HandlerJointDrill2GameOverReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get joint drill activity + var activity = session.getPlayer().getActivityManager().getFirstActivity(JointDrillActivity.class); + + if (activity == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_game_over_failed_ack); + } + + // Give up + var score = activity.settle(0, 0, false); + + if (score == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_game_over_failed_ack); + } + + // Build response + var rsp = JointDrillSettle.newInstance() + .setFightScore(score.getFight()) + .setDifficultyScore(score.getDifficulty()) + .setHpScore(score.getHp()) + .setChange(score.getChange().toProto()); + + // Encode and send + return session.encodeMsg(NetMsgId.joint_drill_2_game_over_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2GiveUpReq.java b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2GiveUpReq.java new file mode 100644 index 0000000..99f5ddb --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2GiveUpReq.java @@ -0,0 +1,44 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.JointDrill2GiveUp.JointDrill2GiveUpReq; +import emu.nebula.proto.PublicJointDrill.JointDrillSettle; +import emu.nebula.net.HandlerId; +import emu.nebula.game.activity.type.JointDrillActivity; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.joint_drill_2_give_up_req) +public class HandlerJointDrill2GiveUpReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get joint drill activity + var activity = session.getPlayer().getActivityManager().getFirstActivity(JointDrillActivity.class); + + if (activity == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_give_up_failed_ack); + } + + // Parse request + var req = JointDrill2GiveUpReq.parseFrom(message); + + // Give up + var score = activity.giveup(req.getTime(), req.getDamage()); + + if (score == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_give_up_failed_ack); + } + + // Build response + var rsp = JointDrillSettle.newInstance() + .setFightScore(score.getFight()) + .setDifficultyScore(score.getDifficulty()) + .setHpScore(score.getHp()) + .setChange(score.getChange().toProto()); + + // Encode and send + return session.encodeMsg(NetMsgId.joint_drill_2_give_up_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2RetreatReq.java b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2RetreatReq.java new file mode 100644 index 0000000..ef19086 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2RetreatReq.java @@ -0,0 +1,16 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.joint_drill_2_retreat_req) +public class HandlerJointDrill2RetreatReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + return session.encodeMsg(NetMsgId.joint_drill_2_retreat_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2SettleReq.java b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2SettleReq.java new file mode 100644 index 0000000..5d0ae78 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2SettleReq.java @@ -0,0 +1,49 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.JointDrill2Settle.JointDrill2SettleReq; +import emu.nebula.proto.PublicJointDrill.JointDrillSettle; +import emu.nebula.net.HandlerId; +import emu.nebula.game.activity.type.JointDrillActivity; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.joint_drill_2_settle_req) +public class HandlerJointDrill2SettleReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get joint drill activity + var activity = session.getPlayer().getActivityManager().getFirstActivity(JointDrillActivity.class); + + if (activity == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_settle_failed_ack); + } + + // Parse request + var req = JointDrill2SettleReq.parseFrom(message); + + // Settle + var score = activity.settle(req.getTime(), req.getDamage(), true); + + if (score == null) { + return session.encodeMsg(NetMsgId.joint_drill_2_settle_failed_ack); + } + + // Handle client events for achievements + session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents()); + + // Build response + var rsp = JointDrillSettle.newInstance() + .setFightScore(score.getFight()) + .setDifficultyScore(score.getDifficulty()) + .setHpScore(score.getHp()) + .setChange(score.getChange().toProto()); + + score.getRewards().toItemTemplateStream().forEach(rsp::addItems); + + // Encode and send + return session.encodeMsg(NetMsgId.joint_drill_2_settle_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2SyncReq.java b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2SyncReq.java new file mode 100644 index 0000000..fb45cb8 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerJointDrill2SyncReq.java @@ -0,0 +1,16 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.joint_drill_2_sync_req) +public class HandlerJointDrill2SyncReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + return session.encodeMsg(NetMsgId.joint_drill_2_sync_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerJointDrillRankReq.java b/src/main/java/emu/nebula/server/handlers/HandlerJointDrillRankReq.java new file mode 100644 index 0000000..40119d5 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerJointDrillRankReq.java @@ -0,0 +1,54 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.JointDrillRank.JointDrillRankInfo; +import emu.nebula.net.HandlerId; +import emu.nebula.Nebula; +import emu.nebula.game.activity.type.JointDrillActivity; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.joint_drill_rank_req) +public class HandlerJointDrillRankReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get joint drill activity + var activity = session.getPlayer().getActivityManager().getFirstActivity(JointDrillActivity.class); + + if (activity == null) { + return session.encodeMsg(NetMsgId.joint_drill_rank_failed_ack); + } + + // Build response + var rsp = JointDrillRankInfo.newInstance() + .setLastRefreshTime(Nebula.getCurrentServerTime()); + + // Get self + var self = activity.getRankEntry(); + + if (self != null) { + rsp.setSelf(self.toProto()); + } + + // Get ranking + var ranking = Nebula.getGameContext().getJointDrillModule().getRanking(activity.getId()); + + for (var entry : ranking) { + // Check self + if (self != null && self.getPlayerUid() == entry.getId()) { + rsp.getMutableSelf().setRank(entry.getRank()); + } + + // Add to ranking + rsp.addRank(entry); + } + + // Set total + rsp.setTotal(ranking.size()); + + // Encode and send + return session.encodeMsg(NetMsgId.joint_drill_rank_succeed_ack, rsp); + } + +} diff --git a/src/main/resources/versions.json b/src/main/resources/versions.json index 9a187db..d4d5e92 100644 --- a/src/main/resources/versions.json +++ b/src/main/resources/versions.json @@ -1,7 +1,7 @@ { - "global": 92, - "kr": 98, - "jp": 95, - "tw": 101, - "cn": 94 + "global": 93, + "kr": 99, + "jp": 96, + "tw": 102, + "cn": 95 } \ No newline at end of file