5 Commits

Author SHA1 Message Date
Melledy
005f138599 Implement Boss Blitz star rewards 2025-12-07 16:31:19 -08:00
Melledy
be5a6709fd Improve Boss Blitz UI 2025-12-07 16:02:44 -08:00
Melledy
c6ac09f112 Fix weekly boss entry tickets and rewards 2025-12-07 15:45:19 -08:00
Melledy
7f0bdb1824 Update player delete function to remove achievements and activities
Also cleaned up some stuff
2025-12-07 15:07:48 -08:00
HongchengQ
f8bd7d5db2 fix CN Client cannot modify nickname through /user/set-info 2025-12-07 13:08:00 -08:00
19 changed files with 293 additions and 43 deletions

View File

@@ -38,6 +38,7 @@ public class GameConstants {
public static final int PREM_GEM_ITEM_ID = 3;
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 MAX_ENERGY = 240;
public static final int ENERGY_REGEN_TIME = 360; // Seconds

View File

@@ -136,6 +136,7 @@ public class GameData {
// Score boss
@Getter private static DataTable<ScoreBossControlDef> ScoreBossControlDataTable = new DataTable<>();
@Getter private static DataTable<ScoreBossRewardDef> ScoreBossRewardDataTable = new DataTable<>();
// Activity
@Getter private static DataTable<ActivityDef> ActivityDataTable = new DataTable<>();

View File

@@ -0,0 +1,19 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import lombok.Getter;
@Getter
@ResourceType(name = "ScoreBossReward.json")
public class ScoreBossRewardDef extends BaseDef {
private int StarNeed;
private int RewardItemId1;
private int RewardNum1;
@Override
public int getId() {
return StarNeed;
}
}

View File

@@ -1,7 +1,9 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.GameData;
import emu.nebula.data.ResourceType;
import emu.nebula.data.ResourceType.LoadPriority;
import emu.nebula.game.instance.InstanceData;
import emu.nebula.game.inventory.ItemRewardList;
import emu.nebula.game.inventory.ItemRewardParam;
@@ -10,9 +12,10 @@ import emu.nebula.util.JsonUtils;
import lombok.Getter;
@Getter
@ResourceType(name = "WeekBossLevel.json")
@ResourceType(name = "WeekBossLevel.json", loadPriority = LoadPriority.LOW)
public class WeekBossLevelDef extends BaseDef implements InstanceData {
private int Id;
private int Difficulty;
private int PreLevelId;
private int NeedWorldClass;
private String BaseAwardPreview;
@@ -44,14 +47,33 @@ public class WeekBossLevelDef extends BaseDef implements InstanceData {
for (int[] award : awards) {
int itemId = award[0];
int min = award[1];
int max = award.length >= 4 ? award[2] : min;
int max = award[1];
boolean isFirst = award[award.length - 1] == 1;
// Set reward count based on difficulty
if (min == -1) {
min = 0;
max = 1;
min = this.Difficulty;
max = this.Difficulty;
var item = GameData.getItemDataTable().get(itemId);
if (item != null) {
switch (item.getRarity()) {
case 2:
max = this.Difficulty * 3;
break;
case 3:
min = this.Difficulty * 2;
max = this.Difficulty * 6;
break;
case 4:
min = this.Difficulty * 3;
max = this.Difficulty * 9;
break;
}
}
}
// Create reward param
var reward = new ItemRewardParam(itemId, min, max);
if (isFirst) {

View File

@@ -1,26 +1,25 @@
package emu.nebula.game.activity.type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
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.proto.Public.ActivityQuest;
import emu.nebula.proto.Public.ActivityTowerDefenseLevel;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import lombok.Getter;
@Getter
@Entity
public class TowerDefenseActivity extends GameActivity {
private Map<Integer, Integer> completedStages;
private Map<Integer, Integer> completedQuests;
private Int2IntMap completedStages;
private Int2IntMap completedQuests;
@Deprecated // Morphia only
public TowerDefenseActivity() {
@@ -29,19 +28,16 @@ public class TowerDefenseActivity extends GameActivity {
public TowerDefenseActivity(ActivityManager manager, ActivityDef data) {
super(manager, data);
this.completedStages = new HashMap<Integer, Integer>();
this.completedQuests = new HashMap<Integer, Integer>();
this.completedStages = new Int2IntOpenHashMap();
this.completedQuests = new Int2IntOpenHashMap();
}
public PlayerChangeInfo claimReward(int level) {
// Initialize change info
var change = new PlayerChangeInfo();
// Get rewards
var rewards = GameData.getTowerDefenseLevelDataTable().get(level).getRewards();
// Add rewards
return getPlayer().getInventory().addItems(rewards, change);
return getPlayer().getInventory().addItems(rewards);
}
// public PlayerChangeInfo claimReward(int groupId) {

View File

@@ -137,5 +137,55 @@ public class InstanceManager extends PlayerManager {
// Success
return change.setSuccess(true);
}
public PlayerChangeInfo settleWeekly(InstanceData data, boolean win) {
// Calculate settle data
var settleData = new InstanceSettleData();
settleData.setWin(win);
settleData.setFirst(settleData.isWin() && !getProgress().getWeekBossLog().containsKey(data.getId()));
// Init player change info
var change = new PlayerChangeInfo();
// Handle win
if (settleData.isWin()) {
// Calculate rewards
int entries = this.getPlayer().getInventory().getResourceCount(GameConstants.WEEKLY_ENTRY_ITEM_ID);
if (entries > 0) {
// Generate regular rewards
settleData.setRewards(data.getRewards().generate());
// Add regular rewards
getPlayer().getInventory().addItems(settleData.getRewards(), change);
// Remove weekly entry
getPlayer().getInventory().removeItem(GameConstants.WEEKLY_ENTRY_ITEM_ID, 1, change);
}
// Add first clear rewards even if we dont have the entry ticket
if (settleData.isFirst()) {
// Generate first clear rewards
settleData.setRewards(data.getFirstRewards().generate());
// Add to inventory
getPlayer().getInventory().addItems(settleData.getFirstRewards(), change);
}
// Log
this.getProgress().saveInstanceLog(getProgress().getWeekBossLog(), "weekBossLog", data.getId(), 1);
// Quest triggers
this.getPlayer().trigger(QuestCondition.WeekBoosClearSpecificDifficultyAndTotal, 1);
this.getPlayer().trigger(QuestCondition.BattleTotal, 1);
}
// Set extra data
change.setExtraData(settleData);
// Success
return change.setSuccess(true);
}
}

View File

@@ -627,6 +627,12 @@ public class Player implements GameDatabaseObject {
// Trigger quest/achievement login
this.trigger(QuestCondition.LoginTotal, 1);
// Add weekly boss entry item
int entries = this.getInventory().getResourceCount(GameConstants.WEEKLY_ENTRY_ITEM_ID);
if (entries < 3) {
this.getInventory().addItem(GameConstants.WEEKLY_ENTRY_ITEM_ID, 3 - entries);
}
// Update last epoch day
this.lastEpochDay = Nebula.getGameContext().getEpochDays();
Nebula.getGameDatabase().update(this, this.getUid(), "lastEpochDay", this.lastEpochDay);
@@ -719,6 +725,11 @@ public class Player implements GameDatabaseObject {
// Load complete
this.loaded = true;
}
public void onCreate() {
// Send welcome mail
this.getMailbox().sendWelcomeMail();
}
public void onLogin() {
// See if we need to reset dailies

View File

@@ -10,6 +10,8 @@ import emu.nebula.Nebula;
import emu.nebula.game.GameContext;
import emu.nebula.game.GameContextModule;
import emu.nebula.game.account.Account;
import emu.nebula.game.achievement.AchievementManager;
import emu.nebula.game.activity.ActivityManager;
import emu.nebula.game.agent.AgentManager;
import emu.nebula.game.battlepass.BattlePass;
import emu.nebula.game.character.GameCharacter;
@@ -130,12 +132,13 @@ public class PlayerModule extends GameContextModule {
player.onLoad();
player.save();
// Send welcome mail
player.getMailbox().sendWelcomeMail();
// Handle any player creation events
player.onCreate();
// Put in player cache
this.addToCache(player);
// Complete
return player;
}
@@ -179,6 +182,8 @@ public class PlayerModule extends GameContextModule {
datastore.getCollection(StoryManager.class).deleteOne(idFilter);
datastore.getCollection(QuestManager.class).deleteOne(idFilter);
datastore.getCollection(AgentManager.class).deleteOne(idFilter);
datastore.getCollection(AchievementManager.class).deleteOne(idFilter);
datastore.getCollection(ActivityManager.class).deleteOne(idFilter);
datastore.getCollection(BattlePass.class).deleteOne(idFilter);
datastore.getCollection(ScoreBossRankEntry.class).deleteOne(idFilter);

View File

@@ -132,7 +132,6 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject {
}
}
/**
* Update this quest on the player client
*/

View File

@@ -1,10 +1,17 @@
package emu.nebula.game.scoreboss;
import java.util.Collection;
import java.util.List;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.ScoreBossControlDef;
import emu.nebula.data.resources.ScoreBossRewardDef;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.player.PlayerManager;
import lombok.Getter;
@Getter
@@ -27,7 +34,7 @@ public class ScoreBossManager extends PlayerManager {
return GameData.getScoreBossControlDataTable().get(this.getControlId());
}
public ScoreBossRankEntry getRanking() {
public ScoreBossRankEntry getRankEntry() {
if (this.ranking == null && !this.checkedDatabase) {
this.ranking = Nebula.getGameDatabase().getObjectByUid(ScoreBossRankEntry.class, this.getPlayerUid());
this.checkedDatabase = true;
@@ -57,7 +64,7 @@ public class ScoreBossManager extends PlayerManager {
return true;
}
public boolean settle(int stars, int score) {
public boolean settle(int stars, int score, int skillScore) {
// Get level
var control = getControlData();
if (control == null || !control.getLevelGroup().contains(this.getLevelId())) {
@@ -71,7 +78,7 @@ public class ScoreBossManager extends PlayerManager {
}
// Get ranking from database
this.getRanking();
this.getRankEntry();
// Create ranking if its not in the database
if (this.ranking == null) {
@@ -79,7 +86,7 @@ public class ScoreBossManager extends PlayerManager {
}
// Settle
this.ranking.settle(this.getPlayer(), build, getControlId(), getLevelId(), stars, score);
this.ranking.settle(this.getPlayer(), build, getControlId(), getLevelId(), stars, score, skillScore);
// Save ranking
this.ranking.save();
@@ -91,4 +98,54 @@ public class ScoreBossManager extends PlayerManager {
// Success
return true;
}
public PlayerChangeInfo claimRewards(int star) {
// Get rank entry
this.getRankEntry();
if (this.ranking == null) {
return new PlayerChangeInfo();
}
// Init variables
Collection<ScoreBossRewardDef> claims = null;
// Add to claim list
if (star > 0) {
var data = GameData.getScoreBossRewardDataTable().get(star);
if (data != null) {
claims = List.of(data);
}
} else {
claims = GameData.getScoreBossRewardDataTable().values();
}
// Init rewards
var rewards = new ItemParamMap();
int starCount = ranking.getStars();
boolean shouldSave = false;
// Try and claim
for (var data : claims) {
// Check if we have earned enough stars
if (starCount >= data.getStarNeed() && !ranking.getClaimedRewards().contains(data.getId())) {
// Add rewards
rewards.add(data.getRewardItemId1(), data.getRewardNum1());
// Set in claimed rewards
this.ranking.getClaimedRewards().add(data.getId());
// Set save flag so we update ranking to the database
shouldSave = true;
}
}
// Save to database
if (shouldSave) {
this.ranking.save();
}
// Add rewards
return this.getPlayer().getInventory().addItems(rewards);
}
}

View File

@@ -16,7 +16,8 @@ import emu.nebula.proto.Public.HonorInfo;
import emu.nebula.proto.ScoreBossRank.ScoreBossRankChar;
import emu.nebula.proto.ScoreBossRank.ScoreBossRankData;
import emu.nebula.proto.ScoreBossRank.ScoreBossRankTeam;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Getter;
import lombok.Setter;
@@ -33,6 +34,9 @@ public class ScoreBossRankEntry implements GameDatabaseObject {
private int titleSuffix;
private int[] honor;
private int score;
@SuppressWarnings("unused")
private int stars;
private IntSet claimedRewards;
private int controlId;
private Map<Integer, ScoreBossTeamEntry> teams;
@@ -51,6 +55,25 @@ public class ScoreBossRankEntry implements GameDatabaseObject {
this.teams = new HashMap<>();
}
// TODO replace later
public int getStars() {
int stars = 0;
for (var team : this.getTeams().values()) {
stars += team.getStars();
}
return stars;
}
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();
@@ -60,28 +83,39 @@ public class ScoreBossRankEntry implements GameDatabaseObject {
this.honor = player.getHonor();
}
public void settle(Player player, StarTowerBuild build, int controlId, int level, int stars, int score) {
public void settle(Player player, StarTowerBuild build, int controlId, int level, int stars, int score, int skillScore) {
// Update player data
this.update(player);
// Reset score entry if control id doesn't match
if (this.controlId != controlId) {
this.controlId = controlId;
this.getTeams().clear();
this.reset();
}
// Set team entry
var team = new ScoreBossTeamEntry(player, build, stars, score);
var team = new ScoreBossTeamEntry(player, build, stars, score, skillScore);
// Put in team map
this.getTeams().put(level, team);
// Calculate score
// Calculate score/stars
this.score = 0;
this.stars = 0;
for (var t : this.getTeams().values()) {
this.score += t.getLevelScore();
this.stars += t.getStars();
}
}
private void reset() {
this.score = 0;
this.stars = 0;
this.getClaimedRewards().clear();
this.getTeams().clear();
}
// Proto
public ScoreBossRankData toProto() {
@@ -115,6 +149,7 @@ public class ScoreBossRankEntry implements GameDatabaseObject {
private int buildScore;
private int stars;
private int levelScore;
private int skillScore;
private List<ScoreBossCharEntry> characters;
@Deprecated // Morphia only
@@ -122,11 +157,12 @@ public class ScoreBossRankEntry implements GameDatabaseObject {
}
public ScoreBossTeamEntry(Player player, StarTowerBuild build, int stars, int score) {
public ScoreBossTeamEntry(Player player, StarTowerBuild build, int stars, int score, int skillScore) {
this.buildId = build.getUid();
this.buildScore = build.getScore();
this.stars = stars;
this.levelScore = score;
this.skillScore = skillScore;
this.characters = new ArrayList<>();
for (var charId : build.getCharIds()) {

View File

@@ -162,9 +162,9 @@ public class HttpServer {
getApp().post("/user/detail", new UserLoginHandler());
getApp().post("/user/set", new UserSetDataHandler());
getApp().post("/user/set-info", new UserSetDataHandler()); // CN
getApp().post("/user/login", new UserLoginHandler());
getApp().post("/user/quick-login", new UserLoginHandler());
getApp().post("/user/set-info", new HttpJsonResponse("{\"Code\":200,\"Data\":{},\"Msg\":\"OK\"}"));
getApp().post("/user/send-sms", new HttpJsonResponse("{\"Code\":200,\"Data\":{},\"Msg\":\"OK\"}"));
getApp().post("/yostar/get-auth", new GetAuthHandler());

View File

@@ -4,7 +4,6 @@ import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.ActivityTowerDefenseLevelSettle.ActivityTowerDefenseLevelSettleReq;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.game.activity.type.TowerDefenseActivity;
import emu.nebula.net.GameSession;
@@ -20,7 +19,7 @@ public class HandlerActivityTowerDefenseLevelSettleReq extends NetHandler {
var activity = session.getPlayer().getActivityManager().getActivity(TowerDefenseActivity.class, 102001);
// Claim rewards
var change = activity.claimReward((int)req.getLevelId());
var change = activity.claimReward(req.getLevelId());
// Update completed stages
activity.getCompletedStages().put(req.getLevelId(), req.getStar());

View File

@@ -2,6 +2,7 @@ package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.ScoreBossLevel;
import emu.nebula.proto.ScoreBossInfoOuterClass.ScoreBossInfo;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@@ -15,6 +16,38 @@ public class HandlerScoreBossInfoReq extends NetHandler {
var rsp = ScoreBossInfo.newInstance()
.setControlId(session.getPlayer().getScoreBossManager().getControlId());
// Get rank entry
var rankEntry = session.getPlayer().getScoreBossManager().getRankEntry();
if (rankEntry != null) {
// Set total score
rsp.setScore(rankEntry.getScore());
rsp.setStar(rankEntry.getStars());
// Encode team data to proto
for (var teamEntry : rankEntry.getTeams().entrySet()) {
int id = teamEntry.getKey();
var team = teamEntry.getValue();
var info = ScoreBossLevel.newInstance()
.setBuildId(team.getBuildId())
.setLevelId(id)
.setScore(team.getLevelScore())
.setSkillScore(team.getSkillScore())
.setStar(team.getStars());
for (var charEntry : team.getCharacters()) {
info.addCharIds(charEntry.getId());
}
rsp.addLevels(info);
}
// Add claimed rewards
for (int id : rankEntry.getClaimedRewards()) {
rsp.addStarRewards(id);
}
}
// Encode and send
return session.encodeMsg(NetMsgId.score_boss_info_succeed_ack, rsp);
}

View File

@@ -17,7 +17,7 @@ public class HandlerScoreBossRankReq extends NetHandler {
.setLastRefreshTime(Nebula.getCurrentTime());
// Get self
var self = session.getPlayer().getScoreBossManager().getRanking();
var self = session.getPlayer().getScoreBossManager().getRankEntry();
if (self != null) {
rsp.setSelf(self.toProto());

View File

@@ -16,7 +16,7 @@ public class HandlerScoreBossSettleReq extends NetHandler {
var req = ScoreBossSettleReq.parseFrom(message);
// Settle
boolean success = session.getPlayer().getScoreBossManager().settle(req.getStar(), req.getScore());
boolean success = session.getPlayer().getScoreBossManager().settle(req.getStar(), req.getScore(), req.getSkillScore());
if (success == false) {
return session.encodeMsg(NetMsgId.score_boss_settle_failed_ack);

View File

@@ -0,0 +1,24 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.ScoreBossStarRewardReceive.ScoreBossStarRewardReceiveReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.score_boss_star_reward_receive_req)
public class HandlerScoreBossStarRewardReceiveReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = ScoreBossStarRewardReceiveReq.parseFrom(message);
// Claim rewards
var change = session.getPlayer().getScoreBossManager().claimRewards(req.getStar());
// Encode and send
return session.encodeMsg(NetMsgId.score_boss_star_reward_receive_succeed_ack, change.toProto());
}
}

View File

@@ -7,7 +7,6 @@ import emu.nebula.proto.WeekBossSettle.WeekBossSettleReq;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.game.instance.InstanceSettleData;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.week_boss_settle_req)
@@ -28,12 +27,9 @@ public class HandlerWeekBossSettleReq extends NetHandler {
var req = WeekBossSettleReq.parseFrom(message);
// Settle instance
var changes = player.getInstanceManager().settleInstance(
var changes = player.getInstanceManager().settleWeekly(
data,
QuestCondition.WeekBoosClearSpecificDifficultyAndTotal,
player.getProgress().getWeekBossLog(),
"weekBossLog",
req.getResult() ? 1 : 0
req.getResult()
);
var settleData = (InstanceSettleData) changes.getExtraData();

View File

@@ -31,8 +31,9 @@ public class UserSetDataHandler extends UserLoginHandler {
ctx.result("{\"Code\":100110,\"Data\":{},\"Msg\":\"Error\"}"); // VALID_FAIL
return;
}
if (req.Key.equals("Nickname")) {
// OS uses the former, CN uses the latter
if (req.Key.equals("Nickname") || req.Key.equals("nickname")) {
account.setNickname(req.Value);
account.save();
}