15 Commits

Author SHA1 Message Date
Melledy
aecea6ab03 Implement !build command for creating records 2025-12-01 13:33:34 -08:00
Melledy
e8e7df7d50 Fix incorrect element type for wind/aqua 2025-12-01 13:21:42 -08:00
Melledy
9188d3b53a Improve command arg handling 2025-12-01 13:16:24 -08:00
Melledy
30f565d0d6 Bump version to 1.1.2 2025-11-30 23:49:50 -08:00
Melledy
faa4ea0780 Only show boss blitz leaderboard for the current season 2025-11-30 23:48:24 -08:00
Melledy
354f390c3b Reset score entry if control id doesn't match 2025-11-30 23:42:34 -08:00
Melledy
3171bce3cc Update boss blitz control id 2025-11-30 23:35:04 -08:00
Melledy
56b4d3b66d Fix monolith crash at floor 20 2025-11-30 23:33:05 -08:00
Melledy
585734c2f3 Handle more achievements 2025-11-30 23:16:37 -08:00
Melledy
6f7a92725a Rework how quest/achievement conditions are handled 2025-11-30 17:06:08 -08:00
Melledy
a04f3354f7 Implement trial activities 2025-11-30 15:19:35 -08:00
Melledy
f53bdaba32 Implement achievements properly 2025-11-29 00:29:04 -08:00
Melledy
898e8dd43f Bump version to 1.1.1 2025-11-28 14:33:53 -08:00
Melledy
29db60fd0a Fix commission bonus rewards 2025-11-28 14:29:57 -08:00
Fishia
7577bf87d4 fix(startower): fix level up logic 2025-11-28 07:14:59 -08:00
72 changed files with 2087 additions and 218 deletions

View File

@@ -15,9 +15,10 @@ For any extra support, questions, or discussions, check out our [Discord](https:
- Battle pass
- Gacha
- Friend system (sending energy not implemented)
- Shop (using only in-game currency)
- Shop (only in-game currency supported)
- Commissions
- Heartlink
- Achievements
- Monoliths (completeable but many other features missing)
- Bounty Trials
- Menance Arena
@@ -26,7 +27,6 @@ For any extra support, questions, or discussions, check out our [Discord](https:
- Boss Blitz
### Not implemented
- Achievements
- Events
# Running the server and client

View File

@@ -29,7 +29,7 @@ java {
}
}
version = '1.1.0'
version = '1.1.2'
var shouldGenerateProto = System.getenv("GENERATE_PROTO") == "true"
System.out.println(shouldGenerateProto ? "Generating proto files" : "Skipping proto generation")

View File

@@ -7,8 +7,8 @@ import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.character.GameDisc;
import emu.nebula.game.player.Player;
import emu.nebula.util.Utils;
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import lombok.Getter;
@@ -53,6 +53,9 @@ public class CommandArgs {
} else if (arg.startsWith("lv")) { // Level
this.level = Utils.parseSafeInt(arg.substring(2));
it.remove();
} else if (arg.startsWith("lvl")) { // Level
this.level = Utils.parseSafeInt(arg.substring(3));
it.remove();
} else if (arg.startsWith("a")) { // Advance
this.advance = Utils.parseSafeInt(arg.substring(1));
it.remove();
@@ -76,7 +79,7 @@ public class CommandArgs {
int key = Integer.parseInt(split[0]);
int value = Integer.parseInt(split[1]);
if (this.map == null) this.map = new Int2IntOpenHashMap();
if (this.map == null) this.map = new Int2IntLinkedOpenHashMap();
this.map.put(key, value);
it.remove();

View File

@@ -0,0 +1,179 @@
package emu.nebula.command.commands;
import java.util.ArrayList;
import java.util.List;
import emu.nebula.command.Command;
import emu.nebula.command.CommandArgs;
import emu.nebula.command.CommandHandler;
import emu.nebula.data.GameData;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.character.GameDisc;
import emu.nebula.game.player.Player;
import emu.nebula.game.tower.StarTowerBuild;
import emu.nebula.net.NetMsgId;
import emu.nebula.util.Utils;
import lombok.Getter;
@Command(
label = "build",
aliases = {"b", "record", "r"},
permission = "player.build",
requireTarget = true,
desc = "!build [char ids...] [disc ids...] [potential ids...] [melody ids...]"
)
public class BuildCommand implements CommandHandler {
@Override
public String execute(CommandArgs args) {
// Create record
var target = args.getTarget();
var builder = new StarTowerBuildData(target);
// Parse items
for (String arg : args.getList()) {
int id = Utils.parseSafeInt(arg);
int count = 1;
this.parseItem(builder, id, count);
}
if (args.getMap() != null) {
for (var entry : args.getMap().int2IntEntrySet()) {
int id = entry.getIntKey();
int count = entry.getIntValue();
this.parseItem(builder, id, count);
}
}
// Check if build is valid
if (builder.getCharacters().size() != 3) {
return "Record must have 3 different characters";
}
if (builder.getDiscs().size() < 3 || builder.getDiscs().size() > 6) {
return "Record must have 3-6 different discs";
}
// Create record
var build = builder.toBuild();
// Add to star tower manager
target.getStarTowerManager().getBuilds().put(build.getUid(), build);
// Send package to player
target.addNextPackage(NetMsgId.st_import_build_notify, build.toProto());
// Send result to player
return "Created record for " + target.getName() + " (This command make take time to update on the client)";
}
private void parseItem(StarTowerBuildData builder, int id, int count) {
// Get item data
var itemData = GameData.getItemDataTable().get(id);
if (itemData == null) {
return;
}
// Clamp
count = Math.max(count, 1);
// Parse by item id
switch (itemData.getItemSubType()) {
case Char -> {
var character = builder.getPlayer().getCharacters().getCharacterById(id);
if (character == null || !character.getData().isAvailable()) {
break;
}
builder.addCharacter(character);
}
case Disc -> {
var disc = builder.getPlayer().getCharacters().getDiscById(id);
if (disc == null || !disc.getData().isAvailable()) {
break;
}
builder.addDisc(disc);
}
case Potential, SpecificPotential -> {
var potentialData = GameData.getPotentialDataTable().get(id);
if (potentialData == null) break;
int level = Math.min(count, potentialData.getMaxLevel());
builder.getBuild().getPotentials().add(id, level);
}
case SubNoteSkill -> {
builder.getBuild().getSubNoteSkills().add(id, count);
}
default -> {
// Ignored
}
}
}
@Getter
private static class StarTowerBuildData {
private Player player;
private StarTowerBuild build;
private List<GameCharacter> characters;
private List<GameDisc> discs;
public StarTowerBuildData(Player player) {
this.player = player;
this.build = new StarTowerBuild(player);
this.characters = new ArrayList<>();
this.discs = new ArrayList<>();
}
public void addCharacter(GameCharacter character) {
if (this.characters.contains(character)) {
return;
}
this.characters.add(character);
}
public void addDisc(GameDisc disc) {
if (this.discs.contains(disc)) {
return;
}
this.discs.add(disc);
}
public StarTowerBuild toBuild() {
// Set characters and discs
build.setChars(this.getCharacters());
build.setDiscs(this.getDiscs());
// Clear character potential cache
build.getCharPots().clear();
for (int charId : build.getCharIds()) {
build.getCharPots().put(charId, 0);
}
// Add potentials to character potential cache
var it = build.getPotentials().iterator();
while (it.hasNext()) {
var potential = it.next();
var data = GameData.getPotentialDataTable().get(potential.getIntKey());
if (data == null || !build.getCharPots().containsKey(data.getCharId())) {
it.remove();
continue;
}
build.getCharPots().add(data.getCharId(), 1);
}
// Calculate score
build.calculateScore();
// Return build
return build;
}
}
}

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

@@ -10,6 +10,7 @@ import org.reflections.Reflections;
import emu.nebula.util.JsonUtils;
import emu.nebula.util.Utils;
import emu.nebula.Nebula;
import emu.nebula.game.achievement.AchievementHelper;
public class ResourceLoader {
private static boolean loaded = false;
@@ -22,6 +23,9 @@ public class ResourceLoader {
// Load
loadResources();
// Add hardcoded achievements params
AchievementHelper.init();
// Done
loaded = true;
Nebula.getLogger().info("Resource loading complete");

View File

@@ -2,7 +2,8 @@ package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import emu.nebula.game.achievement.AchievementHelper;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.Getter;
@Getter
@@ -18,9 +19,50 @@ public class AchievementDef extends BaseDef {
private int Tid1;
private int Qty1;
// Custom params
private transient int param1; // -1 == any, 0 = no param, 1+ = param required
private transient int param2; // -1 == any, 0 = no param, 1+ = param required
@Override
public int getId() {
return Id;
}
public void setParams(int param1, int param2) {
this.param1 = param1;
this.param2 = param2;
}
/**
* Checks if this achievement requires params to match
*/
public boolean hasParam1(int param) {
if (this.param1 < 0) {
return false;
} else if (this.param1 == 0) {
return param != 0;
} else {
return true;
}
}
/**
* Checks if this achievement requires params to match
*/
public boolean hasParam2(int param) {
if (this.param2 < 0) {
return false;
} else if (this.param2 == 0) {
return param != 0;
} else {
return true;
}
}
@Override
public void onLoad() {
// Add to cached achievement list
var list = AchievementHelper.getCache().computeIfAbsent(this.CompleteCond, i -> new ObjectArrayList<>());
list.add(this);
}
}

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

@@ -1,10 +1,15 @@
package emu.nebula.data.resources;
import java.util.List;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.inventory.ItemRewardList;
import emu.nebula.game.inventory.ItemRewardParam;
import emu.nebula.util.JsonUtils;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
@@ -35,6 +40,8 @@ public class AgentDef extends BaseDef {
private String BonusPreview4;
private transient Int2ObjectMap<AgentDurationDef> durations;
private transient Int2IntOpenHashMap tags;
private transient Int2IntOpenHashMap extraTags;
@Override
public int getId() {
@@ -45,14 +52,83 @@ public class AgentDef extends BaseDef {
this.durations.put(duration.getTime(), duration);
}
public boolean hasTags(List<GameCharacter> characters) {
// Get character tags
var characterTags = new Int2IntOpenHashMap();
for (var character : characters) {
var data = character.getData().getDes();
for (int tag : data.getTag()) {
characterTags.addTo(tag, 1);
}
}
// Validate that we have the tags
for (var entry : this.tags.int2IntEntrySet()) {
int reqTagId = entry.getIntKey();
int reqTagCount = entry.getIntValue();
// Get amount of tags that we have from our characters
int characterTagCount = characterTags.get(reqTagId);
if (reqTagCount > characterTagCount) {
return false;
}
}
return true;
}
public boolean hasExtraTags(List<GameCharacter> characters) {
// Get character tags
var characterTags = new Int2IntOpenHashMap();
for (var character : characters) {
var data = character.getData().getDes();
for (int tag : data.getTag()) {
characterTags.addTo(tag, 1);
}
}
// Validate that we have the tags
for (var entry : this.extraTags.int2IntEntrySet()) {
int reqTagId = entry.getIntKey();
int reqTagCount = entry.getIntValue();
// Get amount of tags that we have from our characters
int characterTagCount = characterTags.get(reqTagId);
if (reqTagCount > characterTagCount) {
return false;
}
}
return true;
}
@Override
public void onLoad() {
// Cache durations
this.durations = new Int2ObjectOpenHashMap<>();
this.addDuration(new AgentDurationDef(this.Time1, this.RewardPreview1, this.BonusPreview1));
this.addDuration(new AgentDurationDef(this.Time2, this.RewardPreview2, this.BonusPreview2));
this.addDuration(new AgentDurationDef(this.Time3, this.RewardPreview3, this.BonusPreview3));
this.addDuration(new AgentDurationDef(this.Time4, this.RewardPreview4, this.BonusPreview4));
// Cache tags
this.tags = new Int2IntOpenHashMap();
this.extraTags = new Int2IntOpenHashMap();
for (int tag : this.Tags) {
this.tags.addTo(tag, 1);
}
for (int tag : this.ExtraTags) {
this.extraTags.addTo(tag, 1);
}
}
@Getter

View File

@@ -13,6 +13,8 @@ public class PotentialDef extends BaseDef {
private int MaxLevel;
private int[] BuildScore;
private String BriefDesc;
@Override
public int getId() {
return Id;

View File

@@ -10,6 +10,8 @@ import lombok.Getter;
@ResourceType(name = "StarTower.json")
public class StarTowerDef extends BaseDef {
private int Id;
private int GroupId;
private int Difficulty;
private int[] FloorNum;
private transient int maxFloors;

View File

@@ -7,6 +7,7 @@ import lombok.Getter;
@Getter
@ResourceType(name = "StarTowerFloorExp.json")
public class StarTowerFloorExpDef extends BaseDef {
private int Id;
private int StarTowerId;
private int Stage;
private int NormalExp;
@@ -16,6 +17,6 @@ public class StarTowerFloorExpDef extends BaseDef {
@Override
public int getId() {
return StarTowerId;
return Id;
}
}

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();
@@ -164,12 +163,16 @@ public final class DatabaseManager {
return getDatastore().find(cls).stream();
}
public <T> List<T> getSortedObjects(Class<T> cls, String filter, int limit) {
public <T> List<T> getSortedObjects(Class<T> cls, String filter, int value, String sortBy, int limit) {
var options = new FindOptions()
.sort(Sort.descending(filter))
.sort(Sort.descending(sortBy))
.limit(limit);
return getDatastore().find(cls).iterator(options).toList();
return getDatastore()
.find(cls)
.filter(Filters.eq(filter, value))
.iterator(options)
.toList();
}
public <T> void save(T obj) {

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,159 @@
package emu.nebula.game.achievement;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
public enum AchievementCondition {
AchievementSpecific (1),
AchievementTotal (2),
BattleTotal (3),
CharacterAcquire (5),
CharacterAcquireQuantityRarityAndAdvancement (6),
CharacterAdvanceTotal (7),
CharacterSkillUpTotal (8),
CharacterSkillWithSpecificUpTotal (9),
CharacterSpecific (10),
CharacterUpLevel (11),
CharacterUpTotal (12),
CharacterWithSpecificAdvance (13),
CharacterWithSpecificAffinity (14),
CharacterWithSpecificUpLevel (15),
CharactersWithSpecificLevelAndQuantity (16),
CharactersWithSpecificNumberLevelAndAttributes (17),
CharactersWithSpecificPlot (18),
CharactersWithSpecificQuantityAdvancementCountAndAttribute (19),
CharactersWithSpecificQuantityAndRarity (20),
CharactersWithSpecificQuantityRarityAndAdvancement (21),
CharactersWithSpecificQuantityRarityAndLevel (22),
ChatTotal (23),
DailyInstanceClearSpecificDifficultyAndTotal (24),
DailyInstanceClearSpecificTypeAndTotal (25),
DailyInstanceClearTotal (26),
DateSpecific (27),
DiscAcquire (28),
DiscAcquireSpecificQuantityAndRarity (29),
DiscAcquireQuantityLevelAndRarity (30),
DiscAcquireQuantityPhaseAndRarity (31),
DiscAcquireQuantityStarAndRarity (32),
DiscLimitBreakTotal (33),
DiscPromoteTotal (34),
DiscStrengthenTotal (35),
DiscWithSpecificQuantityLevelAndRarity (36),
DiscWithSpecificQuantityPhaseAndRarity (37),
DiscWithSpecificQuantityStarAndRarity (38),
GachaCharacterNotSSRTotal (40),
GachaCharacterTenModeSSRTotal (41),
GachaCharacterTotal (42),
GachaTenModeAcquireQuantityAndRarityItems (43),
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),
SkinAcquire (61),
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),
VampireSurvivorScoreTotal (75),
VampireSurvivorSpecificLevelWithSpecificScore (76),
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),
JointDrillScoreTotal (95),
CharGemInstanceClearTotal (104),
DailyShopReceiveShopTotal (105),
AgentApplyTotal (106),
DiscSpecific (114),
ClientReport (200),
TowerBattleTimes (501),
TowerBossChallengeSpecificHighRewardWithTotal (502),
TowerBuildSpecificCharacter (503),
TowerBuildSpecificScoreWithTotal (504),
TowerClearSpecificCharacterTypeWithTotal (505),
TowerClearSpecificGroupIdAndDifficulty (506),
TowerClearSpecificLevelWithDifficultyAndTotal (507),
TowerClearTotal (508),
TowerEnterRoom (509),
TowerEventTimes (511),
TowerFateTimes (512),
TowerItemsGet (513),
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),
TowerSweepTimes (539),
TowerSweepTotal (540);
@Getter
private final int value;
private final static Int2ObjectMap<AchievementCondition> map = new Int2ObjectOpenHashMap<>();
static {
for (AchievementCondition type : AchievementCondition.values()) {
map.put(type.getValue(), type);
}
}
private AchievementCondition(int value) {
this.value = value;
}
public static AchievementCondition getByValue(int value) {
return map.get(value);
}
}

View File

@@ -0,0 +1,98 @@
package emu.nebula.game.achievement;
import java.util.List;
import emu.nebula.GameConstants;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.AchievementDef;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Getter;
// Because achievements in the data files do not have params, we will hardcode them here
public class AchievementHelper {
// Cache
private static IntSet incrementalAchievementSet = new IntOpenHashSet();
@Getter
private static Int2ObjectMap<List<AchievementDef>> cache = new Int2ObjectOpenHashMap<>();
public static List<AchievementDef> getAchievementsByCondition(int condition) {
return cache.get(condition);
}
//
public static boolean isIncrementalAchievement(int condition) {
return incrementalAchievementSet.contains(condition);
}
// Fix params
public static void init() {
// Cache total achievements
for (var condition : AchievementCondition.values()) {
if (condition.name().endsWith("Total")) {
incrementalAchievementSet.add(condition.getValue());
}
}
incrementalAchievementSet.remove(AchievementCondition.AchievementTotal.getValue());
incrementalAchievementSet.add(AchievementCondition.ItemsAdd.getValue());
incrementalAchievementSet.add(AchievementCondition.ItemsDeplete.getValue());
// Fix params
fixParams();
}
private static void fixParams() {
// Monolith
addParam(78, 0, 2);
addParam(79, 0, 4);
addParam(498, 0, 1);
// Money
addParam(25, GameConstants.GOLD_ITEM_ID, 0);
addParam(26, GameConstants.GOLD_ITEM_ID, 0);
addParam(27, GameConstants.GOLD_ITEM_ID, 0);
addParam(28, GameConstants.GOLD_ITEM_ID, 0);
addParam(29, GameConstants.GOLD_ITEM_ID, 0);
// Ininfite tower
for (int diff = 10, id = 270; diff <= 60; diff += 10) {
addParam(id++, 11000 + diff, 0); // Infinite Arena
addParam(id++, 51000 + diff, 0); // Shake the Floor
addParam(id++, 41000 + diff, 0); // Elegance and Flow
addParam(id++, 71000 + diff, 0); // Upbeat Party
addParam(id++, 31000 + diff, 0); // Thrilling Beat
addParam(id++, 21000 + diff, 0); // Flames and Beats
addParam(id++, 61000 + diff, 0); // Sinister Ritual
}
// Character count
addParam(393, 1, 0);
addParam(394, 1, 0);
addParam(395, 1, 0);
addParam(396, 1, 0);
addParam(397, 1, 0);
addParam(398, 1, 0);
// Disc count
addParam(382, 1, 0);
addParam(383, 1, 0);
addParam(384, 1, 0);
addParam(385, 1, 0);
addParam(386, 1, 0);
addParam(387, 1, 0);
}
private static void addParam(int achievementId, int param1, int param2) {
var data = GameData.getAchievementDataTable().get(achievementId);
if (data == null) return;
data.setParams(param1, param2);
}
}

View File

@@ -0,0 +1,257 @@
package emu.nebula.game.achievement;
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.data.resources.AchievementDef;
import emu.nebula.database.GameDatabaseObject;
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 emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.Achievements;
import emu.nebula.proto.Public.Events;
import lombok.Getter;
import lombok.Setter;
import us.hebi.quickbuf.RepeatedInt;
@Getter
@Entity(value = "achievements", useDiscriminator = false)
public class AchievementManager extends PlayerManager implements GameDatabaseObject {
@Id
private int uid;
// Achievement data
private Map<Integer, GameAchievement> achievements;
@Setter
private transient boolean queueSave;
@Deprecated // Morphia only
public AchievementManager() {
}
public AchievementManager(Player player) {
super(player);
this.uid = player.getUid();
this.achievements = new HashMap<>();
this.save();
}
public synchronized int getCompletedAchievementsCount() {
return (int) this.getAchievements().values().stream()
.filter(GameAchievement::isComplete)
.count();
}
/**
* Returns true if there are any unclaimed achievements
*/
public synchronized boolean hasNewAchievements() {
for (var achievement : this.getAchievements().values()) {
if (achievement.isClaimable()) {
return true;
}
}
return false;
}
public synchronized GameAchievement getAchievement(AchievementDef data) {
// Try and get achievement normally
var achievement = this.getAchievements().get(data.getId());
// Create achievement if it doesnt exist
if (achievement == null) {
achievement = new GameAchievement(data);
this.getAchievements().put(achievement.getId(), achievement);
}
return achievement;
}
public synchronized void handleClientEvents(Events events) {
//
boolean hasCompleted = false;
// Parse events
for (var event : events.getList()) {
// Check id
if (event.getId() != AchievementCondition.ClientReport.getValue()) {
continue;
}
// Sanity check
if (event.getData().length() < 2) {
continue;
}
// Get achievement id and progress
int id = event.getData().get(1);
int progress = event.getData().get(0);
if (progress <= 0) {
continue;
}
// Get achievement data
var data = GameData.getAchievementDataTable().get(id);
if (data == null) continue;
// Make sure achivement can be completed by the client
if (data.getCompleteCond() != AchievementCondition.ClientReport.getValue()) {
continue;
}
// Get achievement
var achievement = this.getAchievement(data);
// Update achievement
boolean changed = achievement.trigger(true, progress, 0, 0);
// Only save/update on client if achievement was changed
if (changed) {
// Sync
this.syncAchievement(achievement);
// Set save flag
this.queueSave = true;
// Check if achievement was completed
if (achievement.isComplete()) {
hasCompleted = true;
}
}
}
// Trigger update
if (hasCompleted) {
this.getPlayer().trigger(AchievementCondition.AchievementTotal, this.getCompletedAchievementsCount());
}
}
public synchronized void trigger(int condition, int progress, int param1, int param2) {
// Sanity check
if (progress <= 0) {
return;
}
// Blacklist
if (condition == AchievementCondition.ClientReport.getValue()) {
return;
}
// Get achievements to trigger
var triggerList = AchievementHelper.getAchievementsByCondition(condition);
if (triggerList == null) {
return;
}
// Check what type of achievement condition this is
boolean isTotal = AchievementHelper.isIncrementalAchievement(condition);
boolean hasCompleted = false;
// Parse achievements
for (var data : triggerList) {
// Get achievement
var achievement = this.getAchievement(data);
// Update achievement
boolean changed = achievement.trigger(isTotal, progress, param1, param2);
// Only save/update on client if achievement was changed
if (changed) {
// Sync
this.syncAchievement(achievement);
// Set save flag
this.queueSave = true;
// Check if achievement was completed
if (achievement.isComplete()) {
hasCompleted = true;
}
}
}
// Trigger update
if (hasCompleted) {
this.getPlayer().trigger(AchievementCondition.AchievementTotal, this.getCompletedAchievementsCount());
}
}
/**
* Update this achievement on the player client
*/
private void syncAchievement(GameAchievement achievement) {
if (!getPlayer().hasSession()) {
return;
}
getPlayer().addNextPackage(
NetMsgId.achievement_change_notify,
achievement.toProto()
);
}
public synchronized PlayerChangeInfo recvRewards(RepeatedInt ids) {
// Sanity check
if (ids.length() <= 0) {
return null;
}
// Init variables
var rewards = new ItemParamMap();
// Claim achievements
for (int id : ids) {
// Get achievement
var achievement = this.getAchievements().get(id);
if (achievement == null) continue;
// Check if we can claim this achievement
if (achievement.isClaimable()) {
// Claim
achievement.setClaimed(true);
// Add rewards
rewards.add(achievement.getData().getTid1(), achievement.getData().getQty1());
// Save
this.queueSave = true;
}
}
// Success
return this.getPlayer().getInventory().addItems(rewards);
}
// Proto
public synchronized Achievements toProto() {
var proto = Achievements.newInstance();
for (var achievement : this.getAchievements().values()) {
proto.addList(achievement.toProto());
}
return proto;
}
// Database
@Override
public void save() {
Nebula.getGameDatabase().save(this);
this.queueSave = false;
}
}

View File

@@ -0,0 +1,122 @@
package emu.nebula.game.achievement;
import dev.morphia.annotations.Entity;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.AchievementDef;
import emu.nebula.proto.Public.Achievement;
import emu.nebula.proto.Public.QuestProgress;
import lombok.Getter;
@Getter
@Entity(useDiscriminator = false)
public class GameAchievement {
private int id;
private int curProgress;
private long completed;
private boolean claimed;
private transient AchievementDef data;
@Deprecated // Morphia only
public GameAchievement() {
}
public GameAchievement(AchievementDef data) {
this.id = data.getId();
this.data = data;
}
public AchievementDef getData() {
if (this.data == null) {
this.data = GameData.getAchievementDataTable().get(this.getId());
}
return this.data;
}
public int getMaxProgress() {
return this.getData().getAimNumShow();
}
public boolean isComplete() {
return this.completed > 0;
}
public boolean isClaimable() {
return !this.isClaimed() && this.isComplete();
}
public void setClaimed(boolean claimed) {
this.claimed = claimed;
}
public int getStatus() {
if (this.isClaimed()) {
return 2;
} else if (this.isComplete()) {
return 1;
}
return 0;
}
/**
* Returns true if achievement was updated
*/
public boolean trigger(boolean isTotal, int progress, int param1, int param2) {
// Sanity check
if (this.isComplete()) {
return false;
}
// Check param conditions
var data = this.getData();
if (data == null) return false;
if (data.hasParam1(param1) && data.getParam1() != param1) {
return false;
}
if (data.hasParam2(param2) && data.getParam2() != param2) {
return false;
}
// Set previous progress
int prevProgress = this.curProgress;
// Update progress
if (isTotal) {
this.curProgress += progress;
} else {
this.curProgress = progress;
}
// Check if completed
if (this.getCurProgress() >= this.getMaxProgress()) {
this.curProgress = this.getMaxProgress();
this.completed = Nebula.getCurrentTime();
return true;
}
// Check if progress was changed
return prevProgress != this.curProgress;
}
// Proto
public Achievement toProto() {
var progress = QuestProgress.newInstance()
.setCur(this.getCurProgress())
.setMax(this.getMaxProgress());
var proto = Achievement.newInstance()
.setId(this.getId())
.setCompleted(this.getCompleted())
.setStatus(this.getStatus())
.addProgress(progress);
return proto;
}
}

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,10 +8,12 @@ import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.nebula.data.GameData;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import lombok.Getter;
import us.hebi.quickbuf.RepeatedInt;
@@ -58,13 +60,23 @@ public class AgentManager extends PlayerManager implements GameDatabaseObject {
}
// Make sure we own the characters
var characters = new ArrayList<GameCharacter>();
for (int charId : charIds) {
if (!getPlayer().getCharacters().hasCharacter(charId)) {
var character = getPlayer().getCharacters().getCharacterById(charId);
// Also check if character fits the commission level requirement
if (character == null || character.getLevel() < data.getLevel()) {
return null;
}
characters.add(character);
}
// TODO verify char tags for rewards
// Check char tags
if (!data.hasTags(characters)) {
return null;
}
// Create agent
var agent = new Agent(data, processTime, charIds.toArray());
@@ -73,7 +85,7 @@ public class AgentManager extends PlayerManager implements GameDatabaseObject {
this.getAgents().put(agent.getId(), agent);
// Quest
this.getPlayer().triggerQuest(QuestCondType.AgentApplyTotal, 1);
this.getPlayer().trigger(QuestCondition.AgentApplyTotal, 1);
// Success
return agent;
@@ -139,12 +151,30 @@ public class AgentManager extends PlayerManager implements GameDatabaseObject {
continue;
}
// Create rewards
var rewards = duration.getRewards().generate();
result.setRewards(rewards);
// Check if we had extra tags
var characters = new ArrayList<GameCharacter>();
// Add to inventory
this.getPlayer().getInventory().addItems(rewards, change);
for (int charId : agent.getCharIds()) {
var character = getPlayer().getCharacters().getCharacterById(charId);
if (character == null) continue;
characters.add(character);
}
// Create rewards
result.setRewards(duration.getRewards().generate());
// Add rewards to inventory
this.getPlayer().getInventory().addItems(result.getRewards(), change);
// Add bonus rewards if we meet the requirements
if (data.hasExtraTags(characters)) {
// Get bonus rewards
result.setBonus(duration.getBonus().generate());
// Add rewards to inventory
this.getPlayer().getInventory().addItems(result.getBonus(), change);
}
}
// Set results in change info
@@ -153,8 +183,9 @@ public class AgentManager extends PlayerManager implements GameDatabaseObject {
// Save to database
this.save();
// Quest
this.getPlayer().triggerQuest(QuestCondType.AgentFinishTotal, list.size());
// Quest + Achievements
getPlayer().trigger(QuestCondition.AgentFinishTotal, list.size());
getPlayer().trigger(AchievementCondition.AgentWithSpecificFinishTotal, list.size());
// Success
return change.setSuccess(true);

View File

@@ -16,7 +16,6 @@ import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.quest.GameQuest;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestType;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.BattlePassInfoOuterClass.BattlePassInfo;
@@ -123,10 +122,10 @@ public class BattlePass implements GameDatabaseObject {
this.save();
}
public synchronized void trigger(QuestCondType condition, int progress, int param) {
public synchronized void trigger(int condition, int progress, int param1, int param2) {
for (var quest : getQuests().values()) {
// Try to trigger quest
boolean result = quest.trigger(condition, progress, param);
boolean result = quest.trigger(condition, progress, param1, param2);
// Skip if quest progress wasn't changed
if (!result) {

View File

@@ -6,6 +6,7 @@ import java.util.Map;
import dev.morphia.annotations.Entity;
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.net.NetMsgId;
import emu.nebula.proto.Public.Contacts;
@@ -125,6 +126,9 @@ public class CharacterContact {
);
}
// Trigger quest/achievement
this.getCharacter().getPlayer().trigger(AchievementCondition.ChatTotal, 1);
// Success
return change.setSuccess(true);
}

View File

@@ -8,8 +8,10 @@ import emu.nebula.data.GameData;
import emu.nebula.data.resources.CharacterDef;
import emu.nebula.data.resources.DiscDef;
import emu.nebula.game.player.PlayerManager;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.HandbookInfo;
import emu.nebula.util.Bitset;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -22,6 +24,10 @@ public class CharacterStorage extends PlayerManager {
private final Int2ObjectMap<GameCharacter> characters;
private final Int2ObjectMap<GameDisc> discs;
// Flags
@Setter private boolean hasAddedChar;
@Setter private boolean hasAddedDisc;
@Setter private boolean updateCharHandbook;
@Setter private boolean updateDiscHandbook;
@@ -67,11 +73,14 @@ public class CharacterStorage extends PlayerManager {
// Save to database
character.save();
// Set flag for player to update character skins in their handbook
this.setUpdateCharHandbook(true);
// Add to characters
this.characters.put(character.getCharId(), character);
// Set flags for player to update character skins in their handbook
this.setUpdateCharHandbook(true);
this.setHasAddedChar(true);
// Return character
return character;
}
@@ -79,18 +88,6 @@ public class CharacterStorage extends PlayerManager {
return this.getCharacters().values();
}
public int getNewPhoneMessageCount() {
int count = 0;
for (var character : this.getCharacterCollection()) {
if (character.getContact().hasNew()) {
count++;
}
}
return count;
}
public HandbookInfo getCharacterHandbook() {
var bitset = new Bitset();
@@ -145,11 +142,14 @@ public class CharacterStorage extends PlayerManager {
// Save to database
disc.save();
// Set flag for player to update discs in their handbook
this.setUpdateDiscHandbook(true);
// Add to discs
this.discs.put(disc.getDiscId(), disc);
// Set flags for player to update discs in their handbook
this.setUpdateDiscHandbook(true);
this.setHasAddedDisc(true);
// Return disc
return disc;
}
@@ -215,6 +215,73 @@ public class CharacterStorage extends PlayerManager {
return change.setExtraData(modifiedDiscs);
}
// Contacts
public int getNewPhoneMessageCount() {
int count = 0;
for (var character : this.getCharacterCollection()) {
if (character.getContact().hasNew()) {
count++;
}
}
return count;
}
//
/**
* Checks if we should add next packages for player
*/
public void checkPlayerState() {
// Check if we need to trigger character achievements
if (this.hasAddedChar) {
this.hasAddedChar = false;
this.getPlayer().trigger(
AchievementCondition.CharactersWithSpecificQuantityAndRarity,
getCharacters().size()
);
this.getPlayer().trigger(
AchievementCondition.CharactersWithSpecificQuantityAndRarity,
(int) getCharacters().values().stream().filter(GameCharacter::isMaster).count(),
1,
0
);
}
// Check if we need to send handbook update to player
if (this.updateCharHandbook) {
this.updateCharHandbook = false;
this.getPlayer().addNextPackage(
NetMsgId.handbook_change_notify,
this.getPlayer().getCharacters().getCharacterHandbook());
}
// Check if we need to trigger disc achievements
if (this.hasAddedChar) {
this.hasAddedChar = false;
this.getPlayer().trigger(
AchievementCondition.DiscAcquireSpecificQuantityAndRarity,
this.getDiscs().size()
);
this.getPlayer().trigger(
AchievementCondition.DiscAcquireSpecificQuantityAndRarity,
(int) getDiscs().values().stream().filter(GameDisc::isMaster).count(),
1,
0
);
}
// Check if we need to send handbook update to player
if (this.updateDiscHandbook) {
this.updateDiscHandbook = false;
this.getPlayer().addNextPackage(
NetMsgId.handbook_change_notify,
this.getPlayer().getCharacters().getDiscHandbook());
}
}
// Database
public void loadFromDatabase() {

View File

@@ -7,10 +7,10 @@ import lombok.Getter;
@Getter
public enum ElementType {
INHERIT (0),
WIND (1, 90020),
AQUA (1, 90018),
FIRE (2, 90019),
EARTH (3, 90021),
AQUA (4, 90018),
WIND (4, 90020),
LIGHT (5, 90022),
DARK (6, 90023),
NONE (7);

View File

@@ -18,10 +18,11 @@ import emu.nebula.data.GameData;
import emu.nebula.data.resources.CharacterDef;
import emu.nebula.data.resources.TalentGroupDef;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Notify.Skin;
import emu.nebula.proto.Notify.SkinChange;
@@ -117,6 +118,10 @@ public class GameCharacter implements GameDatabaseObject {
}
}
public boolean isMaster() {
return this.getData().getGrade() == 1;
}
public void setLevel(int level) {
this.level = level;
}
@@ -186,7 +191,7 @@ public class GameCharacter implements GameDatabaseObject {
// Check if we leveled up
if (this.level > oldLevel) {
// Trigger quest
this.getPlayer().triggerQuest(QuestCondType.CharacterUpTotal, this.level - oldLevel);
this.getPlayer().trigger(QuestCondition.CharacterUpTotal, this.level - oldLevel);
}
// Save to database
@@ -272,6 +277,9 @@ public class GameCharacter implements GameDatabaseObject {
// Save to database
this.save();
// Trigger quest/achievement
this.getPlayer().trigger(AchievementCondition.CharacterAdvanceTotal, 1);
// Success
return changes.setSuccess(true);
}
@@ -459,8 +467,8 @@ public class GameCharacter implements GameDatabaseObject {
// Add affinity exp
this.addAffinityExp(exp);
// Trigger quest
this.getPlayer().triggerQuest(QuestCondType.GiftGiveTotal, count);
// Trigger quest/achievement
this.getPlayer().trigger(QuestCondition.GiftGiveTotal, count);
// Remove items
var change = this.getPlayer().getInventory().removeItems(items);

View File

@@ -11,10 +11,11 @@ import emu.nebula.data.GameData;
import emu.nebula.data.resources.DiscDef;
import emu.nebula.data.resources.SubNoteSkillPromoteGroupDef;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.proto.Public.Disc;
import emu.nebula.proto.PublicStarTower.StarTowerDisc;
@@ -70,6 +71,10 @@ public class GameDisc implements GameDatabaseObject {
}
}
public boolean isMaster() {
return GameData.getItemDataTable().get(this.getDiscId()).getRarity() == 1;
}
public void setLevel(int level) {
this.level = level;
}
@@ -145,7 +150,7 @@ public class GameDisc implements GameDatabaseObject {
// Check if we leveled up
if (this.level > oldLevel) {
// Trigger quest
this.getPlayer().triggerQuest(QuestCondType.DiscStrengthenTotal, this.level - oldLevel);
this.getPlayer().trigger(QuestCondition.DiscStrengthenTotal, this.level - oldLevel);
}
// Save to database
@@ -215,6 +220,9 @@ public class GameDisc implements GameDatabaseObject {
// Save to database
this.save();
// Trigger quest/achievement
this.getPlayer().trigger(AchievementCondition.DiscPromoteTotal, 1);
// Success
return change.setSuccess(true);
}

View File

@@ -4,7 +4,7 @@ import emu.nebula.data.GameData;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import lombok.Getter;
@Getter
@@ -26,8 +26,8 @@ public class DatingManager extends PlayerManager {
// Set landmark + character
this.game = new DatingGame(character, data);
// Trigger quest
this.getPlayer().triggerQuest(QuestCondType.CharactersDatingTotal, 1);
// Trigger quest/achievement
this.getPlayer().trigger(QuestCondition.CharactersDatingTotal, 1);
// Success
return this.game;

View File

@@ -3,6 +3,7 @@ package emu.nebula.game.gacha;
import emu.nebula.data.GameData;
import emu.nebula.game.GameContext;
import emu.nebula.game.GameContextModule;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.inventory.ItemAcquireMap;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.inventory.ItemType;
@@ -164,6 +165,9 @@ public class GachaModule extends GameContextModule {
var log = new GachaHistoryLog(data.getGachaType(), results);
player.getGachaManager().addGachaHistory(log);
// Trigger achievements
player.trigger(AchievementCondition.GachaTotal, amount);
// Complete
return new GachaResult(info, change, results);
}

View File

@@ -2,6 +2,7 @@ package emu.nebula.game.infinitytower;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.InfinityTowerLevelDef;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.player.PlayerManager;
@@ -75,6 +76,9 @@ public class InfinityTowerManager extends PlayerManager {
// Log in player progress
this.getPlayer().getProgress().addInfinityArenaLog(this.getLevelId());
// Trigger achievement
this.getPlayer().trigger(AchievementCondition.InfinityTowerClearSpecificFloor, 10, this.getLevelId(), 0);
// Success
return change.setSuccess(true);
}

View File

@@ -8,7 +8,7 @@ import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.player.PlayerProgress;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import lombok.Getter;
@@ -35,7 +35,7 @@ public class InstanceManager extends PlayerManager {
return this.getPlayer().getProgress();
}
public PlayerChangeInfo settleInstance(InstanceData data, QuestCondType questCondition, Int2IntMap log, String logName, int star) {
public PlayerChangeInfo settleInstance(InstanceData data, QuestCondition questCondition, Int2IntMap log, String logName, int star) {
// Calculate settle data
var settleData = new InstanceSettleData();
@@ -63,8 +63,8 @@ public class InstanceManager extends PlayerManager {
this.getProgress().saveInstanceLog(log, logName, data.getId(), star);
// Quest triggers
this.getPlayer().triggerQuest(questCondition, 1);
this.getPlayer().triggerQuest(QuestCondType.BattleTotal, 1);
this.getPlayer().trigger(questCondition, 1);
this.getPlayer().trigger(QuestCondition.BattleTotal, 1);
}
// Set extra data
@@ -74,7 +74,7 @@ public class InstanceManager extends PlayerManager {
return change.setSuccess(true);
}
public PlayerChangeInfo sweepInstance(InstanceData data, QuestCondType questCondition, Int2IntMap log, int rewardType, int count) {
public PlayerChangeInfo sweepInstance(InstanceData data, QuestCondition questCondition, Int2IntMap log, int rewardType, int count) {
// Sanity check count
if (count <= 0) {
return null;
@@ -131,8 +131,8 @@ public class InstanceManager extends PlayerManager {
change.setExtraData(list);
// Quest triggers
this.getPlayer().triggerQuest(questCondition, count);
this.getPlayer().triggerQuest(QuestCondType.BattleTotal, count);
this.getPlayer().trigger(questCondition, count);
this.getPlayer().trigger(QuestCondition.BattleTotal, count);
// Success
return change.setSuccess(true);

View File

@@ -11,7 +11,7 @@ import emu.nebula.data.resources.MallShopDef;
import emu.nebula.data.resources.ResidentGoodsDef;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Notify.Skin;
import emu.nebula.proto.Public.Honor;
@@ -20,6 +20,7 @@ import emu.nebula.proto.Public.Res;
import emu.nebula.proto.Public.Title;
import emu.nebula.proto.Public.UI32;
import emu.nebula.util.String2IntMap;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -462,11 +463,11 @@ public class Inventory extends PlayerManager implements GameDatabaseObject {
}
}
// Trigger quest
// Trigger quest + achievement
if (amount > 0) {
this.getPlayer().triggerQuest(QuestCondType.ItemsAdd, amount, id);
this.getPlayer().trigger(QuestCondition.ItemsAdd, amount, id);
} else {
this.getPlayer().triggerQuest(QuestCondType.ItemsDeplete, Math.abs(amount), id);
this.getPlayer().trigger(QuestCondition.ItemsDeplete, Math.abs(amount), id);
}
//
@@ -634,6 +635,9 @@ public class Inventory extends PlayerManager implements GameDatabaseObject {
// Add produced items
this.addItem(data.getProductionId(), data.getProductionPerBatch() * num, change);
// Trigger achievement
this.getPlayer().trigger(AchievementCondition.ItemsProductTotal, num);
// Success
return change.setSuccess(true);
}

View File

@@ -12,6 +12,9 @@ import emu.nebula.Nebula;
import emu.nebula.data.GameData;
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;
@@ -23,7 +26,7 @@ import emu.nebula.game.infinitytower.InfinityTowerManager;
import emu.nebula.game.instance.InstanceManager;
import emu.nebula.game.inventory.Inventory;
import emu.nebula.game.mail.Mailbox;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.game.quest.QuestManager;
import emu.nebula.game.scoreboss.ScoreBossManager;
import emu.nebula.game.story.StoryManager;
@@ -104,7 +107,9 @@ public class Player implements GameDatabaseObject {
private transient PlayerProgress progress;
private transient StoryManager storyManager;
private transient QuestManager questManager;
private transient AchievementManager achievementManager;
private transient AgentManager agentManager;
private transient ActivityManager activityManager;
// Extra
private transient Stack<NetMsgPacket> nextPackages;
@@ -204,8 +209,14 @@ public class Player implements GameDatabaseObject {
}
public void setLevel(int level) {
// Set player world class (level)
this.level = level;
// Save to database
Nebula.getGameDatabase().update(this, this.getUid(), "level", this.level);
// Trigger achievement
this.trigger(AchievementCondition.WorldClassSpecific, this.getLevel());
}
public void setExp(int exp) {
@@ -481,16 +492,17 @@ public class Player implements GameDatabaseObject {
// Save to database
Nebula.getGameDatabase().update(
this,
this.getUid(),
"level",
this.getLevel(),
"exp",
this.getExp()
this,
this.getUid(),
"level",
this.getLevel(),
"exp",
this.getExp()
);
// Save level rewards if we changed it
if (oldLevel != this.getLevel()) {
// Update level rewards
this.getQuestManager().saveLevelRewards();
this.addNextPackage(
@@ -498,6 +510,9 @@ public class Player implements GameDatabaseObject {
WorldClassRewardState.newInstance()
.setFlag(getQuestManager().getLevelRewards().toBigEndianByteArray())
);
// Trigger achievement
this.trigger(AchievementCondition.WorldClassSpecific, this.getLevel());
}
// Calculate changes
@@ -550,7 +565,7 @@ public class Player implements GameDatabaseObject {
change = modifyEnergy(-amount, change);
// Trigger quest
this.triggerQuest(QuestCondType.EnergyDeplete, amount);
this.trigger(QuestCondition.EnergyDeplete, amount);
// Complete
return change;
@@ -601,6 +616,9 @@ public class Player implements GameDatabaseObject {
// Reset dailies
this.resetDailies(hasWeekChanged);
// Trigger quest/achievement login
this.trigger(QuestCondition.LoginTotal, 1);
// Update last epoch day
this.lastEpochDay = Nebula.getGameContext().getEpochDays();
Nebula.getGameDatabase().update(this, this.getUid(), "lastEpochDay", this.lastEpochDay);
@@ -612,15 +630,28 @@ public class Player implements GameDatabaseObject {
this.getBattlePassManager().getBattlePass().resetDailyQuests(resetWeekly);
}
// Trigger quests
// Trigger quests + achievements
public void triggerQuest(QuestCondType condition, int progress) {
this.triggerQuest(condition, progress, 0);
public void trigger(int condition, int progress, int param1, int param2) {
this.getQuestManager().trigger(condition, progress, param1, param2);
this.getBattlePassManager().getBattlePass().trigger(condition, progress, param1, param2);
this.getAchievementManager().trigger(condition, progress, param1, param2);
}
public void triggerQuest(QuestCondType condition, int progress, int param) {
this.getQuestManager().trigger(condition, progress, param);
this.getBattlePassManager().getBattlePass().trigger(condition, progress, param);
public void trigger(QuestCondition condition, int progress) {
this.trigger(condition.getValue(), progress, 0, 0);
}
public void trigger(QuestCondition condition, int progress, int param1) {
this.trigger(condition.getValue(), progress, param1, 0);
}
public void trigger(AchievementCondition condition, int progress) {
this.trigger(condition.getValue(), progress, 0, 0);
}
public void trigger(AchievementCondition condition, int progress, int param1, int param2) {
this.trigger(condition.getValue(), progress, param1, param2);
}
// Login
@@ -664,7 +695,9 @@ public class Player implements GameDatabaseObject {
this.gachaManager = this.loadManagerFromDatabase(GachaManager.class);
this.storyManager = this.loadManagerFromDatabase(StoryManager.class);
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) {
@@ -672,6 +705,9 @@ public class Player implements GameDatabaseObject {
this.save();
}
// Init activities
this.getActivityManager().init();
// Load complete
this.loaded = true;
}
@@ -680,9 +716,6 @@ public class Player implements GameDatabaseObject {
// See if we need to reset dailies
this.checkResetDailies();
// Trigger quest login
this.triggerQuest(QuestCondType.LoginTotal, 1);
// Update last login time
this.lastLogin = System.currentTimeMillis();
Nebula.getGameDatabase().update(this, this.getUid(), "lastLogin", this.getLastLogin());
@@ -698,6 +731,18 @@ public class Player implements GameDatabaseObject {
this.getNextPackages().add(new NetMsgPacket(msgId, proto));
}
// Misc
/**
* Called AFTER a response is sent to the client
*/
public void afterResponse() {
// Check if we need save achievements
if (this.getAchievementManager().isQueueSave()) {
this.getAchievementManager().save();
}
}
// Proto
public PlayerInfo toProto() {
@@ -784,10 +829,12 @@ public class Player implements GameDatabaseObject {
state.getMutableBattlePass()
.setState(1);
state.getMutableAchievement()
.setNew(this.getAchievementManager().hasNewAchievements());
state.getMutableFriendEnergy();
state.getMutableMallPackage();
state.getMutableAchievement();
state.getMutableTravelerDuelQuest()
.setType(QuestType.TravelerDuel);
state.getMutableTravelerDuelChallengeQuest()
@@ -868,6 +915,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

@@ -55,19 +55,19 @@ public class GameQuest {
return 0;
}
public boolean trigger(QuestCondType condition, int progress, int param) {
public boolean trigger(int condition, int progress, int param1, int param2) {
// Sanity check
if (this.isComplete()) {
return false;
}
// Skip if not the correct condition
if (this.cond != condition.getValue()) {
if (this.cond != condition) {
return false;
}
// Check quest param
if (this.param != 0 && param != this.param) {
if (this.param != 0 && param1 != this.param) {
return false;
}

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 QuestCondition {
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<QuestCondition> map = new Int2ObjectOpenHashMap<>();
static {
for (QuestCondition type : QuestCondition.values()) {
map.put(type.getValue(), type);
}
}
private QuestCondition(int value) {
this.value = value;
}
public static QuestCondition getByValue(int value) {
return map.get(value);
}
}

View File

@@ -11,19 +11,20 @@ public class QuestHelper {
@Getter
private static final Int2ObjectMap<QuestParams> battlePassQuestParams = new Int2ObjectOpenHashMap<>();
// Put params here
static {
battlePassQuestParams.put(1001, new QuestParams(QuestCondType.LoginTotal, 1));
battlePassQuestParams.put(1002, new QuestParams(QuestCondType.EnergyDeplete, 160));
battlePassQuestParams.put(1003, new QuestParams(QuestCondType.BattleTotal, 6));
battlePassQuestParams.put(1004, new QuestParams(QuestCondType.QuestWithSpecificType, 5, QuestType.Daily));
battlePassQuestParams.put(2001, new QuestParams(QuestCondType.TowerEnterFloor, 1));
battlePassQuestParams.put(2002, new QuestParams(QuestCondType.WeekBoosClearSpecificDifficultyAndTotal, 3));
battlePassQuestParams.put(2003, new QuestParams(QuestCondType.BattleTotal, 20));
battlePassQuestParams.put(2004, new QuestParams(QuestCondType.LoginTotal, 5));
battlePassQuestParams.put(2005, new QuestParams(QuestCondType.AgentFinishTotal, 3));
battlePassQuestParams.put(2006, new QuestParams(QuestCondType.ItemsDeplete, 100000, 1));
battlePassQuestParams.put(2007, new QuestParams(QuestCondType.GiftGiveTotal, 5));
battlePassQuestParams.put(2008, new QuestParams(QuestCondType.EnergyDeplete, 1200));
battlePassQuestParams.put(1001, new QuestParams(QuestCondition.LoginTotal, 1));
battlePassQuestParams.put(1002, new QuestParams(QuestCondition.EnergyDeplete, 160));
battlePassQuestParams.put(1003, new QuestParams(QuestCondition.BattleTotal, 6));
battlePassQuestParams.put(1004, new QuestParams(QuestCondition.QuestWithSpecificType, 5, QuestType.Daily));
battlePassQuestParams.put(2001, new QuestParams(QuestCondition.TowerEnterFloor, 1));
battlePassQuestParams.put(2002, new QuestParams(QuestCondition.WeekBoosClearSpecificDifficultyAndTotal, 3));
battlePassQuestParams.put(2003, new QuestParams(QuestCondition.BattleTotal, 20));
battlePassQuestParams.put(2004, new QuestParams(QuestCondition.LoginTotal, 5));
battlePassQuestParams.put(2005, new QuestParams(QuestCondition.AgentFinishTotal, 3));
battlePassQuestParams.put(2006, new QuestParams(QuestCondition.ItemsDeplete, 100000, 1));
battlePassQuestParams.put(2007, new QuestParams(QuestCondition.GiftGiveTotal, 5));
battlePassQuestParams.put(2008, new QuestParams(QuestCondition.EnergyDeplete, 1200));
}
@Getter
@@ -40,7 +41,7 @@ public class QuestHelper {
this(cond, new int[] {param});
}
public QuestParams(QuestCondType cond, int... params) {
public QuestParams(QuestCondition cond, int... params) {
this(cond.getValue(), params);
}
}

View File

@@ -114,10 +114,10 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject {
this.save();
}
public synchronized void trigger(QuestCondType condition, int progress, int param) {
public synchronized void trigger(int condition, int progress, int param1, int param2) {
for (var quest : getQuests().values()) {
// Try to trigger quest
boolean result = quest.trigger(condition, progress, param);
boolean result = quest.trigger(condition, progress, param1, param2);
// Skip if quest progress wasn't changed
if (!result) {
@@ -194,7 +194,7 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject {
}
// Trigger quest
this.getPlayer().triggerQuest(QuestCondType.QuestWithSpecificType, claimList.size(), QuestType.Daily);
this.getPlayer().trigger(QuestCondition.QuestWithSpecificType, claimList.size(), QuestType.Daily);
// Success
return change.setSuccess(true);
@@ -305,7 +305,7 @@ public class QuestManager extends PlayerManager implements GameDatabaseObject {
Nebula.getGameDatabase().update(this, this.getUid(), "hasDailyReward", this.hasDailyReward);
// Trigger quest
this.getPlayer().triggerQuest(QuestCondType.DailyShopReceiveShopTotal, 1);
this.getPlayer().trigger(QuestCondition.DailyShopReceiveShopTotal, 1);
// Success
return change.setSuccess(true);

View File

@@ -20,7 +20,7 @@ public class ScoreBossManager extends PlayerManager {
}
public int getControlId() {
return 1;
return Nebula.getGameContext().getScoreBossModule().getControlId();
}
public ScoreBossControlDef getControlData() {
@@ -79,7 +79,7 @@ public class ScoreBossManager extends PlayerManager {
}
// Settle
this.ranking.settle(this.getPlayer(), build, this.getLevelId(), stars, score);
this.ranking.settle(this.getPlayer(), build, getControlId(), getLevelId(), stars, score);
// Save ranking
this.ranking.save();

View File

@@ -21,6 +21,11 @@ public class ScoreBossModule extends GameContextModule {
this.ranking = new ArrayList<>();
}
// TODO calculate from bin data
public int getControlId() {
return 2;
}
private long getRefreshTime() {
return Nebula.getConfig().getServerOptions().leaderboardRefreshTime * 1000;
}
@@ -39,7 +44,7 @@ public class ScoreBossModule extends GameContextModule {
this.ranking.clear();
// Get from database
var list = Nebula.getGameDatabase().getSortedObjects(ScoreBossRankEntry.class, "score", 50);
var list = Nebula.getGameDatabase().getSortedObjects(ScoreBossRankEntry.class, "controlId", this.getControlId(), "score", 50);
for (int i = 0; i < list.size(); i++) {
// Get rank entry and set proto

View File

@@ -60,10 +60,16 @@ public class ScoreBossRankEntry implements GameDatabaseObject {
this.honor = player.getHonor();
}
public void settle(Player player, StarTowerBuild build, int level, int stars, int score) {
public void settle(Player player, StarTowerBuild build, int controlId, int level, int stars, int score) {
// 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();
}
// Set team entry
var team = new ScoreBossTeamEntry(player, build, stars, score);
this.getTeams().put(level, team);

View File

@@ -1,12 +1,17 @@
package emu.nebula.game.tower;
import java.util.List;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.character.GameDisc;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.Player;
import emu.nebula.proto.Public.ItemTpl;
import emu.nebula.proto.PublicStarTower.BuildPotential;
import emu.nebula.proto.PublicStarTower.StarTowerBuildBrief;
@@ -42,18 +47,23 @@ public class StarTowerBuild implements GameDatabaseObject {
}
public StarTowerBuild(StarTowerGame game) {
public StarTowerBuild(Player player) {
this.uid = Snowflake.newUid();
this.playerUid = game.getPlayer().getUid();
this.playerUid = player.getUid();
this.name = "";
this.charPots = new ItemParamMap();
this.potentials = new ItemParamMap();
this.subNoteSkills = new ItemParamMap();
}
public StarTowerBuild(StarTowerGame game) {
// Initialize basic variables
this(game.getPlayer());
// Characters
this.charIds = game.getChars().stream()
.filter(d -> d.getId() > 0)
.mapToInt(d -> d.getId())
.filter(c -> c.getId() > 0)
.mapToInt(c -> c.getId())
.toArray();
// Discs
@@ -85,7 +95,19 @@ public class StarTowerBuild implements GameDatabaseObject {
}
// Caclulate record score and cache it
this.score = this.calculateScore();
this.calculateScore();
}
public void setChars(List<GameCharacter> characters) {
this.charIds = characters.stream()
.mapToInt(c -> c.getCharId())
.toArray();
}
public void setDiscs(List<GameDisc> discs) {
this.discIds = discs.stream()
.mapToInt(d -> d.getDiscId())
.toArray();
}
public void setName(String newName) {
@@ -109,9 +131,9 @@ public class StarTowerBuild implements GameDatabaseObject {
// Score
private int calculateScore() {
// Init score
int score = 0;
public int calculateScore() {
// Clear score
this.score = 0;
// Potentials
for (var potential : this.getPotentials().int2IntEntrySet()) {
@@ -119,16 +141,16 @@ public class StarTowerBuild implements GameDatabaseObject {
if (data == null) continue;
int index = potential.getIntValue() - 1;
score += data.getBuildScore()[index];
this.score += data.getBuildScore()[index];
}
// Sub note skills
for (var item : this.getSubNoteSkills()) {
score += item.getIntValue() * 15;
this.score += item.getIntValue() * 15;
}
// Complete
return score;
return this.score;
}
// Proto

View File

@@ -227,35 +227,38 @@ public class StarTowerGame {
return gold;
}
public int levelUp(int exp, int picks) {
if (this.teamExp + exp >= this.nextLevelExp) {
public int levelUp(int picks) {
if (this.teamExp >= this.nextLevelExp && this.nextLevelExp != Integer.MAX_VALUE) {
// Level up
this.teamLevel++;
// Add 1 to pending potential picks
picks++;
// Handle excess exp
if (this.teamExp + exp - this.nextLevelExp > 0) {
int excessExp = this.teamExp + exp - this.nextLevelExp;
return levelUp(excessExp, picks);
}
// Subtract target exp
this.teamExp = this.teamExp - this.nextLevelExp;
// Next level
this.nextLevelExp = GameData.getStarTowerTeamExpDataTable().get(this.teamLevel + 1).getNeedExp();
}
else {
// Update current team exp
this.teamExp += exp;
// Update next level exp
var teamExpData = GameData.getStarTowerTeamExpDataTable().get(this.teamLevel + 1);
if (teamExpData != null) {
this.nextLevelExp = teamExpData.getNeedExp();
} else {
this.nextLevelExp = Integer.MAX_VALUE;
}
// Re-check and continue processing if we still got exp
if (this.teamExp > 0) {
return levelUp(picks);
}
}
// Return picks
return picks;
}
public int levelUp(int exp) {
public int levelUp() {
int potentialPicks = 0;
return this.levelUp(exp, potentialPicks);
return this.levelUp(potentialPicks);
}
// Cases
@@ -480,7 +483,11 @@ public class StarTowerGame {
// Handle leveling up
// Get relevant floor exp data
var floorExpData = GameData.getStarTowerFloorExpDataTable().get(this.getId());
// fishiatee: THERE'S NO LINQ IN JAVAAAAAAAAAAAAA
var floorExpData = GameData.getStarTowerFloorExpDataTable().stream()
.filter(f -> f.getStarTowerId() == this.getId())
.findFirst()
.orElseThrow();
int expReward = 0;
// Determine appropriate exp reward
@@ -504,7 +511,8 @@ public class StarTowerGame {
}
// Level up
this.pendingPotentialCases += this.levelUp(expReward);
this.teamExp += expReward;
this.pendingPotentialCases += this.levelUp();
// Add clear time
this.battleTime += proto.getVictory().getTime();
@@ -548,6 +556,8 @@ public class StarTowerGame {
this.addRandomSubNoteSkills(subNoteSkills, change);
}
// Handle client events for achievements
getPlayer().getAchievementManager().handleClientEvents(proto.getVictory().getEvents());
} else {
// Handle defeat
// TODO
@@ -559,6 +569,7 @@ public class StarTowerGame {
// Set change
rsp.setChange(change.toProto());
// Return response for the player
return rsp;
}
@@ -664,9 +675,9 @@ public class StarTowerGame {
var hawkerCase = new StarTowerCase(CaseType.Hawker);
// TODO
hawkerCase.addGoods(new StarTowerShopGoods(1, 1, 200));
hawkerCase.addGoods(new StarTowerShopGoods(1, 1, 200));
hawkerCase.addGoods(new StarTowerShopGoods(1, 1, 200));
for (int i = 0; i < 8; i++) {
hawkerCase.addGoods(new StarTowerShopGoods(1, 1, 200));
}
this.addCase(room.getMutableCases(), hawkerCase);
}
@@ -738,7 +749,7 @@ public class StarTowerGame {
public StarTowerInteractResp settle(StarTowerInteractResp rsp) {
// End game
this.getManager().endGame();
this.getManager().endGame(true);
// Settle info
var settle = rsp.getMutableSettle()

View File

@@ -3,11 +3,12 @@ package emu.nebula.game.tower;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.StarTowerGrowthNodeDef;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.player.PlayerProgress;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.proto.StarTowerApply.StarTowerApplyReq;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@@ -207,24 +208,45 @@ public class StarTowerManager extends PlayerManager {
this.game = new StarTowerGame(this, data, formation, req);
// Trigger quest
this.getPlayer().triggerQuest(QuestCondType.TowerEnterFloor, 1);
this.getPlayer().trigger(QuestCondition.TowerEnterFloor, 1);
// Success
return change.setExtraData(this.game);
}
public StarTowerGame endGame() {
public StarTowerGame endGame(boolean victory) {
// Cache instance
var game = this.game;
if (game != null) {
// Set last build
this.lastBuild = game.getBuild();
// Clear instance
this.game = null;
if (game == null) {
return null;
}
// Set last build
this.lastBuild = game.getBuild();
// Handle victory events
if (victory) {
// Trigger achievements
this.getPlayer().trigger(AchievementCondition.TowerClearTotal, 1);
this.getPlayer().trigger(
AchievementCondition.TowerClearSpecificGroupIdAndDifficulty,
1,
game.getData().getGroupId(),
game.getData().getDifficulty()
);
this.getPlayer().trigger(
AchievementCondition.TowerClearSpecificLevelWithDifficultyAndTotal,
1,
game.getData().getId(),
game.getData().getDifficulty()
);
}
// Clear game instance
this.game = null;
// Return game
return game;
}

View File

@@ -5,6 +5,7 @@ import java.util.List;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerManager;
import emu.nebula.game.player.PlayerProgress;
@@ -173,6 +174,9 @@ public class VampireSurvivorManager extends PlayerManager {
// Clear game
this.game = null;
// Trigger achievement
getPlayer().trigger(AchievementCondition.VampireWithSpecificClearTotal, 1);
}
private void updateSavedCards() {

View File

@@ -193,18 +193,7 @@ public class GameSession {
}
// Check handbook states
if (this.getPlayer().getCharacters().isUpdateCharHandbook()) {
getPlayer().getCharacters().setUpdateCharHandbook(false);
getPlayer().addNextPackage(
NetMsgId.handbook_change_notify,
this.getPlayer().getCharacters().getCharacterHandbook());
}
if (this.getPlayer().getCharacters().isUpdateDiscHandbook()) {
getPlayer().getCharacters().setUpdateDiscHandbook(false);
getPlayer().addNextPackage(
NetMsgId.handbook_change_notify,
this.getPlayer().getCharacters().getDiscHandbook());
}
this.getPlayer().getCharacters().checkPlayerState();
}
private ProtoMessage<?> addNextPackages(ProtoMessage<?> proto) {
@@ -249,4 +238,15 @@ public class GameSession {
return proto;
}
// Misc network
/**
* Called AFTER a response is sent to the client
*/
public void afterResponse() {
if (this.getPlayer() != null) {
this.getPlayer().afterResponse();
}
}
}

View File

@@ -2,11 +2,7 @@ package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.Achievement;
import emu.nebula.proto.Public.Achievements;
import emu.nebula.proto.Public.QuestProgress;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.achievement_info_req)
@@ -15,21 +11,7 @@ public class HandlerAchievementInfoReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Build response
var rsp = Achievements.newInstance();
for (var data : GameData.getAchievementDataTable()) {
var progress = QuestProgress.newInstance()
.setCur(data.getAimNumShow())
.setMax(data.getAimNumShow());
var info = Achievement.newInstance()
.setId(data.getId())
.setStatus(2)
.setCompleted(session.getPlayer().getCreateTime())
.addProgress(progress);
rsp.addList(info);
}
var rsp = session.getPlayer().getAchievementManager().toProto();
// Encode and send
return session.encodeMsg(NetMsgId.achievement_info_succeed_ack, rsp);

View File

@@ -0,0 +1,28 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.AchievementRewardReceive.AchievementRewardReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.achievement_reward_receive_req)
public class HandlerAchievementRewardReceiveReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = AchievementRewardReq.parseFrom(message);
// Claim rewards
var change = session.getPlayer().getAchievementManager().recvRewards(req.getIds());
if (change == null) {
return session.encodeMsg(NetMsgId.achievement_reward_receive_failed_ack);
}
// Encode and send
return session.encodeMsg(NetMsgId.achievement_reward_receive_succeed_ack, change.toProto());
}
}

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());
}
}

View File

@@ -7,7 +7,7 @@ import emu.nebula.proto.CharGemInstanceSettle.CharGemInstanceSettleResp;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.game.instance.InstanceSettleData;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.char_gem_instance_settle_req)
@@ -30,7 +30,7 @@ public class HandlerCharGemInstanceSettleReq extends NetHandler {
// Settle instance
var changes = player.getInstanceManager().settleInstance(
data,
QuestCondType.CharGemInstanceClearTotal,
QuestCondition.CharGemInstanceClearTotal,
player.getProgress().getCharGemLog(),
"charGemLog",
req.getStar()
@@ -38,6 +38,9 @@ public class HandlerCharGemInstanceSettleReq extends NetHandler {
var settleData = (InstanceSettleData) changes.getExtraData();
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Create response
var rsp = CharGemInstanceSettleResp.newInstance()
.setExp(settleData.getExp())

View File

@@ -11,7 +11,7 @@ import java.util.List;
import emu.nebula.data.GameData;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.char_gem_instance_sweep_req)
@@ -31,7 +31,7 @@ public class HandlerCharGemInstanceSweepReq extends NetHandler {
// Sweep
var change = session.getPlayer().getInstanceManager().sweepInstance(
data,
QuestCondType.CharGemInstanceClearTotal,
QuestCondition.CharGemInstanceClearTotal,
session.getPlayer().getProgress().getCharGemLog(),
0,
req.getTimes()

View File

@@ -4,7 +4,7 @@ import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.Nil;
import emu.nebula.net.HandlerId;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.client_event_report_req)
@@ -13,7 +13,7 @@ public class HandlerClientEventReportReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Interact
session.getPlayer().triggerQuest(QuestCondType.ClientReport, 1, 1005);
session.getPlayer().trigger(QuestCondition.ClientReport, 1, 1005);
// Encode response
return session.encodeMsg(NetMsgId.client_event_report_succeed_ack, Nil.newInstance());

View File

@@ -11,7 +11,7 @@ import java.util.List;
import emu.nebula.data.GameData;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.daily_instance_raid_req)
@@ -36,7 +36,7 @@ public class HandlerDailyInstanceRaidReq extends NetHandler {
// Sweep
var change = session.getPlayer().getInstanceManager().sweepInstance(
data,
QuestCondType.DailyInstanceClearTotal,
QuestCondition.DailyInstanceClearTotal,
session.getPlayer().getProgress().getDailyInstanceLog(),
req.getRewardType(),
req.getTimes()

View File

@@ -7,7 +7,7 @@ import emu.nebula.proto.DailyInstanceSettle.DailyInstanceSettleResp;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.game.instance.InstanceSettleData;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.daily_instance_settle_req)
@@ -30,7 +30,7 @@ public class HandlerDailyInstanceSettleReq extends NetHandler {
// Settle instance
var changes = player.getInstanceManager().settleInstance(
data,
QuestCondType.DailyInstanceClearTotal,
QuestCondition.DailyInstanceClearTotal,
player.getProgress().getDailyInstanceLog(),
"dailyInstanceLog",
req.getStar()
@@ -38,6 +38,9 @@ public class HandlerDailyInstanceSettleReq extends NetHandler {
var settleData = (InstanceSettleData) changes.getExtraData();
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Create response
var rsp = DailyInstanceSettleResp.newInstance()
.setExp(settleData.getExp())

View File

@@ -32,6 +32,9 @@ public class HandlerInfinityTowerSettleReq extends NetHandler {
nextLevel = 0;
}
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Build response
var rsp = InfinityTowerSettleResp.newInstance()
.setNextLevelId(nextLevel)

View File

@@ -7,7 +7,7 @@ import emu.nebula.proto.RegionBossLevelSettle.RegionBossLevelSettleResp;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.game.instance.InstanceSettleData;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.region_boss_level_settle_req)
@@ -30,7 +30,7 @@ public class HandlerRegionBossLevelSettleReq extends NetHandler {
// Settle instance
var changes = player.getInstanceManager().settleInstance(
data,
QuestCondType.RegionBossClearTotal,
QuestCondition.RegionBossClearTotal,
player.getProgress().getRegionBossLog(),
"regionBossLog",
req.getStar()
@@ -38,6 +38,9 @@ public class HandlerRegionBossLevelSettleReq extends NetHandler {
var settleData = (InstanceSettleData) changes.getExtraData();
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Create response
var rsp = RegionBossLevelSettleResp.newInstance()
.setExp(settleData.getExp())

View File

@@ -11,7 +11,7 @@ import java.util.List;
import emu.nebula.data.GameData;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.region_boss_level_sweep_req)
@@ -31,7 +31,7 @@ public class HandlerRegionBossLevelSweepReq extends NetHandler {
// Sweep
var change = session.getPlayer().getInstanceManager().sweepInstance(
data,
QuestCondType.RegionBossClearTotal,
QuestCondition.RegionBossClearTotal,
session.getPlayer().getProgress().getRegionBossLog(),
0,
req.getTimes()

View File

@@ -22,6 +22,9 @@ public class HandlerScoreBossSettleReq extends NetHandler {
return session.encodeMsg(NetMsgId.score_boss_settle_failed_ack);
}
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Build response
var rsp = ScoreBossSettleResp.newInstance();

View File

@@ -7,7 +7,7 @@ import emu.nebula.proto.SkillInstanceSettle.SkillInstanceSettleResp;
import emu.nebula.net.HandlerId;
import emu.nebula.data.GameData;
import emu.nebula.game.instance.InstanceSettleData;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.skill_instance_settle_req)
@@ -30,7 +30,7 @@ public class HandlerSkillInstanceSettleReq extends NetHandler {
// Settle instance
var changes = player.getInstanceManager().settleInstance(
data,
QuestCondType.SkillInstanceClearTotal,
QuestCondition.SkillInstanceClearTotal,
player.getProgress().getSkillInstanceLog(),
"skillInstanceLog",
req.getStar()
@@ -38,6 +38,9 @@ public class HandlerSkillInstanceSettleReq extends NetHandler {
var settleData = (InstanceSettleData) changes.getExtraData();
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Create response
var rsp = SkillInstanceSettleResp.newInstance()
.setExp(settleData.getExp())

View File

@@ -11,7 +11,7 @@ import java.util.List;
import emu.nebula.data.GameData;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.skill_instance_sweep_req)
@@ -31,7 +31,7 @@ public class HandlerSkillInstanceSweepReq extends NetHandler {
// Sweep
var change = session.getPlayer().getInstanceManager().sweepInstance(
data,
QuestCondType.SkillInstanceClearTotal,
QuestCondition.SkillInstanceClearTotal,
session.getPlayer().getProgress().getSkillInstanceLog(),
0,
req.getTimes()

View File

@@ -11,7 +11,7 @@ public class HandlerStarTowerGiveUpReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var game = session.getPlayer().getStarTowerManager().endGame();
var game = session.getPlayer().getStarTowerManager().endGame(false);
if (game == null) {
return session.encodeMsg(NetMsgId.star_tower_give_up_failed_ack);
@@ -19,10 +19,10 @@ public class HandlerStarTowerGiveUpReq extends NetHandler {
// Build response
var rsp = StarTowerGiveUpResp.newInstance()
.setBuild(game.getBuild().toProto())
.setFloor(game.getFloor());
rsp.getMutableChange();
rsp.setBuild(game.getBuild().toProto());
return session.encodeMsg(NetMsgId.star_tower_give_up_succeed_ack, rsp);
}

View File

@@ -24,6 +24,9 @@ public class HandlerVampireSurvivorAreaChangeReq extends NetHandler {
// Calculate score for area
game.settleArea(req.getTime(), req.getKillCount().toArray());
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Encode and send
return session.encodeMsg(NetMsgId.vampire_survivor_area_change_succeed_ack);
}

View File

@@ -32,6 +32,9 @@ public class HandlerVampireSurvivorSettleReq extends NetHandler {
// Settle game
session.getPlayer().getVampireSurvivorManager().settle(victory, score);
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Build response
var rsp = VampireSurvivorSettleResp.newInstance();

View File

@@ -7,7 +7,7 @@ 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.QuestCondType;
import emu.nebula.game.quest.QuestCondition;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.week_boss_settle_req)
@@ -30,7 +30,7 @@ public class HandlerWeekBossSettleReq extends NetHandler {
// Settle instance
var changes = player.getInstanceManager().settleInstance(
data,
QuestCondType.WeekBoosClearSpecificDifficultyAndTotal,
QuestCondition.WeekBoosClearSpecificDifficultyAndTotal,
player.getProgress().getWeekBossLog(),
"weekBossLog",
req.getResult() ? 1 : 0
@@ -38,6 +38,9 @@ public class HandlerWeekBossSettleReq extends NetHandler {
var settleData = (InstanceSettleData) changes.getExtraData();
// Handle client events for achievements
session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents());
// Create response
var rsp = WeekBossLevelSettleResp.newInstance()
.setFirst(settleData.isFirst())

View File

@@ -162,7 +162,13 @@ public class AgentZoneHandler implements Handler {
ctx.status(200);
ctx.result(result);
// Clear header
ctx.res().setHeader("Content-Type", null);
// Handle post response events
if (session != null) {
session.afterResponse();
}
}
}

View File

@@ -41,8 +41,7 @@ public class RemoteHandler implements Handler {
// Check admin key
if (token.equals(adminKey)) {
Nebula.getCommandManager().invoke(null, command);
Nebula.getLogger().warn(
"\u001B[38;2;252;186;3mRemote Server (Using Admin Key) sent command: /" + command + "\u001B[0m");
Nebula.getLogger().warn("\u001B[38;2;252;186;3mRemote Server (Using Admin Key) sent command: /" + command + "\u001B[0m");
ctx.status(200);
ctx.contentType(ContentType.APPLICATION_JSON);
ctx.result("{\"Code\":200,\"Data\":{},\"Msg\":\"Command executed\"}");

View File

@@ -17,12 +17,13 @@ import emu.nebula.data.GameData;
import emu.nebula.data.ResourceType;
import emu.nebula.data.resources.CharacterDef;
import emu.nebula.data.resources.ItemDef;
import emu.nebula.data.resources.PotentialDef;
import emu.nebula.game.inventory.ItemType;
public class Handbook {
public static void generate() {
// Temp vars
Map<String, String> languageKey = null;
List<Integer> list = null;
// Save to file
@@ -41,31 +42,64 @@ public class Handbook {
writer.println(System.lineSeparator());
writer.println("# Characters");
list = GameData.getCharacterDataTable().keySet().intStream().sorted().boxed().toList();
languageKey = loadLanguageKey(CharacterDef.class);
var characterLanguageKey = loadLanguageKey(CharacterDef.class);
for (int id : list) {
CharacterDef data = GameData.getCharacterDataTable().get(id);
writer.print(data.getId());
writer.print(" : ");
writer.print(languageKey.getOrDefault(data.getName(), data.getName()));
writer.print(characterLanguageKey.getOrDefault(data.getName(), data.getName()));
writer.print(" (");
writer.print(data.getElementType().toString());
writer.println(")");
}
// Dump characters
// Dump items
writer.println(System.lineSeparator());
writer.println("# Items");
list = GameData.getItemDataTable().keySet().intStream().sorted().boxed().toList();
languageKey = loadLanguageKey(ItemDef.class);
var itemLanguageKey = loadLanguageKey(ItemDef.class);
for (int id : list) {
ItemDef data = GameData.getItemDataTable().get(id);
writer.print(data.getId());
writer.print(" : ");
writer.print(languageKey.getOrDefault(data.getTitle(), data.getTitle()));
writer.print(itemLanguageKey.getOrDefault(data.getTitle(), data.getTitle()));
writer.print(" [");
writer.print(data.getItemType());
writer.println("]");
writer.print("]");
if (data.getItemType() == ItemType.Disc) {
var discData = GameData.getDiscDataTable().get(id);
if (discData != null) {
writer.print(" (");
writer.print(discData.getElementType().toString());
writer.print(")");
}
}
writer.println("");
}
// Dump potentials
writer.println(System.lineSeparator());
writer.println("# Potentials");
list = GameData.getPotentialDataTable().keySet().intStream().sorted().boxed().toList();
var potentialLanguageKey = loadLanguageKey(PotentialDef.class);
for (int id : list) {
PotentialDef data = GameData.getPotentialDataTable().get(id);
writer.print(data.getId());
writer.print(" : ");
CharacterDef charData = GameData.getCharacterDataTable().get(data.getCharId());
writer.print("[");
writer.print(characterLanguageKey.getOrDefault(charData.getName(), charData.getName()));
writer.print("] ");
ItemDef itemData = GameData.getItemDataTable().get(id);
writer.print(itemLanguageKey.getOrDefault(itemData.getTitle(), itemData.getTitle()));
writer.print(" - ");
writer.print(potentialLanguageKey.getOrDefault(data.getBriefDesc(), data.getBriefDesc()));
writer.println("");
}
} catch (IOException e) {
e.printStackTrace();