52 Commits

Author SHA1 Message Date
Melledy
dfb93cae4b Implement some tower achievements 2025-12-06 02:01:57 -08:00
Fishia
5707c1c919 feat(tower_defense): implement first clear rewards 2025-12-06 01:21:28 -08:00
Fishia
51f6db9803 fix(tower_defense): level id is key, not value 2025-12-06 01:21:28 -08:00
Fishia
3df873e385 feat: tower defense activity
Bare minimum work done.
2025-12-06 01:21:28 -08:00
HongchengQ
f44262f427 Fix abnormal player online status on duplicate login
- Prevent incorrect player deletion on duplicate login
2025-12-06 00:54:58 -08:00
Melledy
cf63bc0b7e Implement starting musical notes 2025-12-05 23:24:10 -08:00
Melledy
b7bf1fcdeb Implement potential rerolling 2025-12-05 23:01:37 -08:00
Melledy
198d3aac4f Fix story red dot (untested) 2025-12-05 22:34:47 -08:00
Melledy
810427a028 Fix battle pass weekly exp not resetting 2025-12-05 21:05:21 -08:00
Melledy
70c7c849df Implement !battlepass command
Examples:
`!battlepass premium` = Activates elite grant
`!battlepass lv40` = Unlocks the rewards up to level 40
2025-12-05 21:03:09 -08:00
Melledy
0b7f1ae3a2 Fix battle pass red dot when logging in 2025-12-05 20:32:06 -08:00
Melledy
5182e94db7 Fix item count on star tower shop goods 2025-12-05 19:25:20 -08:00
Melledy
426e5bce63 Add a notification when getting the wrong answer in a npc event 2025-12-05 19:11:39 -08:00
HongchengQ
b9c4a174f8 Improve remote command execution response results
- Ensure that commands return specific messages instead of fixed strings after execution
2025-12-05 13:38:48 -08:00
Melledy
6974631601 Implement bonus potential level monolith talents 2025-12-05 02:12:29 -08:00
Melledy
880f0d1d7d Fix max level of potentials in !build being 3 2025-12-05 01:26:12 -08:00
Melledy
86c607c0b3 Implement some star tower npc events 2025-12-04 23:40:24 -08:00
Melledy
3710f0a697 Implement secondary skills in star tower too 2025-12-04 19:32:23 -08:00
Melledy
c19aa5d0a1 Fix disc secondary skills 2025-12-04 16:25:18 -08:00
Melledy
be84e0f406 Remove max level potentials from potential selectors 2025-12-04 00:01:48 -08:00
Melledy
e5cb842fdd Implement monolith shop discounts and improvements 2025-12-03 23:53:18 -08:00
Melledy
15618414a6 Implement monolith shop discounts 2025-12-03 23:15:25 -08:00
Melledy
71de6184b9 Optimize star tower case handling 2025-12-03 22:52:49 -08:00
Melledy
e887d5eb4c Update data versions 2025-12-03 22:45:42 -08:00
Melledy
211e012c42 Update patchlist handler 2025-12-03 22:44:12 -08:00
Melledy
9c87d74ad7 Add /client-code endpoint 2025-12-03 22:34:29 -08:00
Melledy
357d12779b Implement monolith shop refresh 2025-12-03 22:23:41 -08:00
Melledy
c8a7db75aa Implement bonus max potential level 2025-12-03 21:28:47 -08:00
Melledy
ef8846445c Implement monolith enhancement machines 2025-12-03 19:36:47 -08:00
Melledy
7ef7490c37 Support JP and TW clients 2025-12-03 14:08:38 -08:00
Melledy
2c1e1ae2fb Spawn the shop npc at the end of a monolith run 2025-12-03 12:07:05 -08:00
Melledy
e3d34bfa48 Spawn recovery npc after each battle in monolith 2025-12-03 00:20:51 -08:00
Melledy
65250b07bf Don't send door case after every shop purchase 2025-12-02 23:56:57 -08:00
Melledy
b38f4f0957 Fix duplicate potentials in potential selector 2025-12-02 23:50:57 -08:00
Melledy
e4dc85a50f Fix wrong potentials in star tower potential selector 2025-12-02 23:44:45 -08:00
Melledy
893b23b50d Rework star tower 2025-12-02 23:15:31 -08:00
Melledy
33b1cf55d4 Update data version to 60 2025-12-01 21:43:06 -08:00
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
114 changed files with 5245 additions and 891 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,9 +27,16 @@ For any extra support, questions, or discussions, check out our [Discord](https:
- Boss Blitz
### Not implemented
- Achievements
- Events
### Supported regions
Nebula supports the global PC client by default. If you want to switch regions, you need to change the `region` field in the Nebula config.
Current supported regions (PC): `GLOBAL`, `KR`, `JP`, `TW`
You may need to change the data version when switching regions. The `customDataVersion` field should match the the data version of your client, which is usually the last number of your client's version string (top left of your login screen). Example: 1.0.0.42 = data version 42.
# Running the server and client
### Prerequisites
@@ -49,17 +57,24 @@ For any extra support, questions, or discussions, check out our [Discord](https:
3. Copy and paste the following code into the Fiddlerscript tab of Fiddler Classic. Remember to save the fiddler script after you copy and paste it:
```
import System;
import System.Windows.Forms;
import Fiddler;
import System.Text.RegularExpressions;
class Handlers
{
static var list = [
".yostarplat.com",
".stellasora.global",
".stellasora.kr",
".stellasora.jp",
".stargazer-games.com"
];
static function OnBeforeRequest(oS: Session) {
if (oS.host.EndsWith(".yostarplat.com") || oS.host.EndsWith(".stellasora.global")) {
oS.oRequest.headers.UriScheme = "http";
oS.host = "localhost"; // This can also be replaced with another IP address.
for (var i = 0; i < list.length; i++) {
if (oS.host.EndsWith(list[i])) {
oS.oRequest.headers.UriScheme = "http";
oS.host = "localhost"; // This can also be replaced with another IP address
}
}
}
};
@@ -68,14 +83,6 @@ class Handlers
4. If `autoCreateAccount` is set to true in the config, then you can skip this step. Otherwise, type `/account create [account email]` in the server console to create an account.
5. Login with your account email, the code field is ignored by the server and can be set to anything.
If you are not on the global client, `.stellasora.global` in the fiddlerscript may need to be changed to match the endpoint your client connects to.
### Supported regions
Nebula supports the global client by default. If you want to switch regions, you need to change the `customDataVersion` and `region` fields in the Nebula config. The `customDataVersion` field should match the the data version of your client, which is usually the last number of your client's version string (top left of your login screen). Example: 1.0.0.42 = data version 42.
Current supported regions: `global`, `kr`
### Server commands
Server commands need to be run in the server console OR in the signature edit menu of your profile.

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

@@ -6,8 +6,23 @@ import emu.nebula.game.inventory.ItemParam;
import emu.nebula.util.WeightedList;
public class GameConstants {
private static final int DATA_VERSION = 54;
private static final String VERSION = "1.2.0";
public static final String VERSION = "1.2.0";
public static int DATA_VERSION = 0;
// Set data versions for each region
static {
RegionConfig.getRegion("global")
.setDataVersion(63);
RegionConfig.getRegion("kr")
.setDataVersion(70);
RegionConfig.getRegion("jp")
.setDataVersion(66);
RegionConfig.getRegion("tw")
.setDataVersion(64);
}
public static final ZoneId UTC_ZONE = ZoneId.of("UTC");
@@ -19,7 +34,6 @@ public class GameConstants {
public static final int GEM_ITEM_ID = 2;
public static final int PREM_GEM_ITEM_ID = 3;
public static final int ENERGY_BUY_ITEM_ID = GEM_ITEM_ID;
public static final int STAR_TOWER_GOLD_ITEM_ID = 11;
public static final int EXP_ITEM_ID = 21;
public static final int MAX_ENERGY = 240;
@@ -37,6 +51,14 @@ public class GameConstants {
public static final int MAX_FRIENDSHIPS = 50;
public static final int MAX_PENDING_FRIENDSHIPS = 30;
public static final int TOWER_COIN_ITEM_ID = 11;
public static final int[] TOWER_COMMON_SUB_NOTE_SKILLS = new int[] {
90011, 90012, 90013, 90014, 90015, 90016, 90017
};
public static final int[] TOWER_EVENTS_IDS = new int[] {
101, 102, 104, 105, 106, 107, 108, 114, 115, 116, 126, 127, 128
};
public static int[][] VAMPIRE_SURVIVOR_BONUS_POWER = new int[][] {
new int[] {100, 120},
new int[] {200, 150},
@@ -57,7 +79,14 @@ public class GameConstants {
// Helper functions
public static String getGameVersion() {
return VERSION + "." + getDataVersion() + " (" + Nebula.getConfig().getRegion().toUpperCase() + ")";
// Load data version
var region = RegionConfig.getRegion(Nebula.getConfig().getRegion());
// Set data version from region
GameConstants.DATA_VERSION = region.getDataVersion();
// Init game version string
return VERSION + "." + getDataVersion() + " (" + region.getName().toUpperCase() + ")";
}
public static int getDataVersion() {

View File

@@ -43,9 +43,8 @@ public class Nebula {
@Getter private static PluginManager pluginManager;
public static void main(String[] args) {
// Load config + keys first
// Load config first
Nebula.loadConfig();
AeadHelper.loadKeys();
// Start Server
Nebula.getLogger().info("Starting Nebula " + getJarVersion());
@@ -54,6 +53,9 @@ public class Nebula {
boolean generateHandbook = true;
// Load keys
AeadHelper.loadKeys();
// Load plugin manager
Nebula.pluginManager = new PluginManager();

View File

@@ -0,0 +1,42 @@
package emu.nebula;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class RegionConfig {
private String name;
private int dataVersion;
private String serverMetaKey;
private String serverGarbleKey;
private static Object2ObjectMap<String, RegionConfig> REGIONS = new Object2ObjectOpenHashMap<>();
public RegionConfig(String name) {
this.name = name;
this.serverMetaKey = "";
this.serverGarbleKey = "";
}
public RegionConfig setDataVersion(int i) {
this.dataVersion = i;
return this;
}
public RegionConfig setServerMetaKey(String key) {
this.serverMetaKey = key;
return this;
}
public RegionConfig setServerGarbleKey(String key) {
this.serverGarbleKey = key;
return this;
}
public static RegionConfig getRegion(String name) {
String regionName = name.toLowerCase();
return REGIONS.computeIfAbsent(regionName, r -> new RegionConfig(regionName));
}
}

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,61 @@
package emu.nebula.command.commands;
import emu.nebula.command.Command;
import emu.nebula.command.CommandArgs;
import emu.nebula.command.CommandHandler;
import emu.nebula.net.NetMsgId;
@Command(label = "battlepass", aliases = {"bp"}, permission = "player.battlepass", desc = "/battlepass [free | premium] lv(level). mMdifies your battle pass")
public class BattlePassCommand implements CommandHandler {
@Override
public String execute(CommandArgs args) {
// Get target
var target = args.getTarget();
var battlepass = target.getBattlePassManager().getBattlePass();
boolean changed = false;
// Check if we are changing premium status
int mode = -1;
for (var arg : args.getList()) {
if (arg.equalsIgnoreCase("free")) {
mode = 0;
} else if (arg.equalsIgnoreCase("premium")) {
mode = 2;
}
}
if (mode >= 0 && battlepass.getMode() != mode) {
battlepass.setMode(mode);
changed = true;
}
// Set level
int level = Math.min(args.getLevel(), 50);
if (level >= 0 && battlepass.getLevel() != level) {
battlepass.setLevel(level);
changed = true;
}
// Check if we have made any changes
if (changed) {
// Save battle pass to the database
battlepass.save();
// Send package to notify the client that the battle pass needs updating
target.addNextPackage(
NetMsgId.battle_pass_info_succeed_ack,
battlepass.toProto()
);
// Success message
return "Changed the battle pass successfully.";
}
// Result message
return "No changes were made to the battle pass.";
}
}

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

@@ -52,6 +52,8 @@ public class GameData {
@Getter private static DataTable<DiscPromoteDef> DiscPromoteDataTable = new DataTable<>();
@Getter private static DataTable<DiscPromoteLimitDef> DiscPromoteLimitDataTable = new DataTable<>();
@Getter private static DataTable<SecondarySkillDef> SecondarySkillDataTable = new DataTable<>();
// Items
@Getter private static DataTable<ItemDef> ItemDataTable = new DataTable<>();
@Getter private static DataTable<ProductionDef> ProductionDataTable = new DataTable<>();
@@ -114,9 +116,12 @@ public class GameData {
@Getter private static DataTable<StarTowerGrowthNodeDef> StarTowerGrowthNodeDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerFloorExpDef> StarTowerFloorExpDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerTeamExpDef> StarTowerTeamExpDataTable = new DataTable<>();
@Getter private static DataTable<PotentialDef> PotentialDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerEventDef> StarTowerEventDataTable = new DataTable<>();
@Getter private static DataTable<SubNoteSkillPromoteGroupDef> SubNoteSkillPromoteGroupDataTable = new DataTable<>();
@Getter private static DataTable<PotentialDef> PotentialDataTable = new DataTable<>();
@Getter private static DataTable<CharPotentialDef> CharPotentialDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerBookFateCardBundleDef> StarTowerBookFateCardBundleDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerBookFateCardQuestDef> StarTowerBookFateCardQuestDataTable = new DataTable<>();
@Getter private static DataTable<StarTowerBookFateCardDef> StarTowerBookFateCardDataTable = new DataTable<>();
@@ -131,4 +136,13 @@ public class GameData {
// Score boss
@Getter private static DataTable<ScoreBossControlDef> ScoreBossControlDataTable = new DataTable<>();
// Activity
@Getter private static DataTable<ActivityDef> ActivityDataTable = new DataTable<>();
// Tower defense
@Getter private static DataTable<TowerDefenseLevelDef> TowerDefenseLevelDataTable = 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

@@ -0,0 +1,22 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import lombok.Getter;
@Getter
@ResourceType(name = "CharPotential.json")
public class CharPotentialDef extends BaseDef {
private int Id;
private int[] MasterSpecificPotentialIds;
private int[] AssistSpecificPotentialIds;
private int[] CommonPotentialIds;
private int[] MasterNormalPotentialIds;
private int[] AssistNormalPotentialIds;
@Override
public int getId() {
return Id;
}
}

View File

@@ -18,6 +18,9 @@ public class DiscDef extends BaseDef {
private int TransformItemId;
private int[] MaxStarTransformItem;
private int[] ReadReward;
private int SecondarySkillGroupId1;
private int SecondarySkillGroupId2;
private int SubNoteSkillGroupId;
private transient ElementType elementType;

View File

@@ -0,0 +1,34 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.GameData;
import emu.nebula.data.ResourceType;
import emu.nebula.data.ResourceType.LoadPriority;
import lombok.Getter;
/**
* We don't need a DataTable for this, since we are only using this class to verify event options for the client
*/
@Getter
@ResourceType(name = "EventOptions.json", loadPriority = LoadPriority.LOW)
public class EventOptionsDef extends BaseDef {
private int Id;
@Override
public int getId() {
return Id;
}
@Override
public void onLoad() {
// Get event
var event = GameData.getStarTowerEventDataTable().get(this.Id / 100);
if (event == null) {
return;
}
// Add to avaliable options
event.getOptionIds().add(this.getId());
}
}

View File

@@ -2,6 +2,7 @@ package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import emu.nebula.game.tower.StarTowerGame;
import lombok.Getter;
@Getter
@@ -10,11 +11,44 @@ public class PotentialDef extends BaseDef {
private int Id;
private int CharId;
private int Build;
private int BranchType;
private int MaxLevel;
private int[] BuildScore;
private String BriefDesc;
@Override
public int getId() {
return Id;
}
public int getMaxLevel() {
// Check if regular potential
if (this.BranchType == 3) {
return this.BuildScore.length;
}
// Special potential should always have a max level of 1
return this.MaxLevel;
}
public int getMaxLevel(StarTowerGame game) {
// Check if regular potential
if (this.BranchType == 3) {
return this.MaxLevel + game.getModifiers().getBonusMaxPotentialLevel();
}
// Special potential should always have a max level of 1
return this.MaxLevel;
}
public int getBuildScore(int level) {
int index = level - 1;
if (index >= this.BuildScore.length) {
index = this.BuildScore.length - 1;
}
return this.BuildScore[index];
}
}

View File

@@ -0,0 +1,118 @@
package emu.nebula.data.resources;
import java.util.ArrayList;
import java.util.List;
import emu.nebula.data.BaseDef;
import emu.nebula.data.GameData;
import emu.nebula.data.ResourceType;
import emu.nebula.game.inventory.ItemParamMap;
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;
@Getter
@ResourceType(name = "SecondarySkill.json")
public class SecondarySkillDef extends BaseDef {
private int Id;
private int GroupId;
private int Score;
private String NeedSubNoteSkills;
private transient ItemParamMap reqSubNotes;
@Getter
private static transient Int2ObjectMap<List<SecondarySkillDef>> groups = new Int2ObjectOpenHashMap<>();
@Override
public int getId() {
return Id;
}
public boolean match(ItemParamMap subNotes) {
for (var item : this.reqSubNotes) {
int reqId = item.getIntKey();
int reqCount = item.getIntValue();
int curCount = subNotes.get(reqId);
if (curCount < reqCount) {
return false;
}
}
return true;
}
@Override
public void onLoad() {
// Setup required subnotes
this.reqSubNotes = ItemParamMap.fromJsonString(this.NeedSubNoteSkills);
// Add to group cache
var group = groups.computeIfAbsent(this.GroupId, id -> new ArrayList<>());
group.add(this);
// Clear to save memory
this.NeedSubNoteSkills = null;
}
// Static sub note skill group group
public static List<SecondarySkillDef> getGroup(int id) {
return groups.get(id);
}
public static IntSet calculateSecondarySkills(int[] discIds, ItemParamMap subNotes) {
var secondarySkills = new IntOpenHashSet();
// Get first 3 discs
for (int i = 0; i < 3; i++) {
// Disc id
int discId = discIds[i];
// Get disc data
var data = GameData.getDiscDataTable().get(discId);
if (data == null) continue;
// Add secondary skills
int s1= getSecondarySkill(subNotes, data.getSecondarySkillGroupId1());
if (s1 > 0) {
secondarySkills.add(s1);
}
int s2 = getSecondarySkill(subNotes, data.getSecondarySkillGroupId2());
if (s2 > 0) {
secondarySkills.add(s2);
}
}
return secondarySkills;
}
private static int getSecondarySkill(ItemParamMap subNotes, int groupId) {
// Check group id
if (groupId <= 0) {
return 0;
}
// Get group
var group = SecondarySkillDef.getGroup(groupId);
if (group == null) {
return 0;
}
// Reverse iterator to try and match highest secondary skill first
for (int i = group.size() - 1; i >= 0; i--) {
var data = group.get(i);
if (data.match(subNotes)) {
return data.getId();
}
}
// Failure
return 0;
}
}

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;
@@ -19,8 +21,8 @@ public class StarTowerDef extends BaseDef {
return Id;
}
public int getMaxFloor(int stage) {
int index = stage - 1;
public int getMaxFloor(int stageNum) {
int index = stageNum - 1;
if (index < 0 || index >= this.FloorNum.length) {
return 0;

View File

@@ -0,0 +1,35 @@
package emu.nebula.data.resources;
import emu.nebula.data.BaseDef;
import emu.nebula.data.ResourceType;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter;
@Getter
@ResourceType(name = "StarTowerEvent.json")
public class StarTowerEventDef extends BaseDef {
private int Id;
private int[] RelatedNPCs;
private transient IntList optionIds;
@Override
public int getId() {
return Id;
}
/**
* Returns a deep copy of our option ids
*/
public IntList getClonedOptionIds() {
var list = new IntArrayList();
list.addAll(this.getOptionIds());
return list;
}
@Override
public void onLoad() {
this.optionIds = new IntArrayList();
}
}

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,33 @@
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 = "TowerDefenseLevel.json")
public class TowerDefenseLevelDef extends BaseDef {
private int Id;
private int Condition2;
private int Condition3;
private int Item1;
private int Qty1;
private int Item2;
private int Qty2;
private transient ItemParamMap rewards;
@Override
public int getId() {
return Id;
}
@Override
public void onLoad() {
// Parse rewards
this.rewards = new ItemParamMap();
this.rewards.add(this.Item1, this.Qty1);
this.rewards.add(this.Item2, this.Qty2);
}
}

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,132 @@
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 emu.nebula.game.tower.room.RoomType;
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") || condition.name().endsWith("Times")) {
incrementalAchievementSet.add(condition.getValue());
}
}
incrementalAchievementSet.remove(AchievementCondition.AchievementTotal.getValue());
incrementalAchievementSet.add(AchievementCondition.ItemsAdd.getValue());
incrementalAchievementSet.add(AchievementCondition.ItemsDeplete.getValue());
incrementalAchievementSet.add(AchievementCondition.TowerItemsGet.getValue());
incrementalAchievementSet.add(AchievementCondition.TowerEnterRoom.getValue());
// Fix params
fixParams();
}
private static void fixParams() {
// Star Tower TODO
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
addParams(393, 398, 1, 0);
// Disc count
addParams(382, 387, 1, 0);
// Star Tower team clear
addParams(95, 98, 1, 0); // Aqua team clear
addParams(99, 102, 2, 0); // Fire team clear
addParams(103, 106, 3, 0); // Earth team clear
addParams(107, 110, 4, 0); // Wind team clear
addParams(111, 114, 5, 0); // Light team clear
addParams(115, 118, 6, 0); // Dark team clear
// Star tower items
addParams(139, 144, GameConstants.TOWER_COIN_ITEM_ID, 0);
addParams(145, 149, 90011, 0);
addParams(150, 154, 90012, 0);
addParams(155, 159, 90013, 0);
addParams(160, 164, 90014, 0);
addParams(165, 169, 90015, 0);
addParams(170, 174, 90016, 0);
addParams(175, 179, 90017, 0);
addParams(180, 184, 90018, 0);
addParams(185, 189, 90019, 0);
addParams(190, 194, 90020, 0);
addParams(195, 199, 90021, 0);
addParams(200, 204, 90022, 0);
addParams(205, 209, 90023, 0);
// Star tower rooms
addParams(210, 216, RoomType.BattleRoom.getValue() + 1, 0);
addParams(217, 223, RoomType.EliteBattleRoom.getValue() + 1, 0);
addParams(224, 230, RoomType.BossRoom.getValue() + 1, 0);
addParams(231, 237, RoomType.FinalBossRoom.getValue() + 1, 0);
addParams(238, 244, RoomType.ShopRoom.getValue() + 1, 0);
addParams(245, 251, RoomType.EventRoom.getValue() + 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);
}
private static void addParams(int start, int end, int param1, int param2) {
for (int id = start; id <= end; id++) {
addParam(id, param1, param2);
}
}
}

View File

@@ -0,0 +1,265 @@
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(AchievementCondition condition, int progress) {
this.trigger(condition.getValue(), progress, 0, 0);
}
public synchronized void trigger(AchievementCondition condition, int progress, int param1, int param2) {
this.trigger(condition.getValue(), progress, param1, param2);
}
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,128 @@
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);
case TowerDefense -> new TowerDefenseActivity(this, data);
default -> null;
};
return activity;
}
// Database
@Override
public void save() {
Nebula.getGameDatabase().save(this);
this.queueSave = false;
}
}

View File

@@ -0,0 +1,29 @@
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(102001); // Tower defense activity
//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,108 @@
package emu.nebula.game.activity.type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import dev.morphia.annotations.Entity;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.ActivityDef;
import emu.nebula.game.activity.ActivityManager;
import emu.nebula.game.activity.GameActivity;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.proto.ActivityDetail.ActivityMsg;
import emu.nebula.proto.Public.ActivityQuest;
import emu.nebula.proto.Public.ActivityTowerDefenseLevel;
import lombok.Getter;
@Getter
@Entity
public class TowerDefenseActivity extends GameActivity {
private Map<Integer, Integer> completedStages;
private Map<Integer, Integer> completedQuests;
@Deprecated // Morphia only
public TowerDefenseActivity() {
}
public TowerDefenseActivity(ActivityManager manager, ActivityDef data) {
super(manager, data);
this.completedStages = new HashMap<Integer, Integer>();
this.completedQuests = new HashMap<Integer, Integer>();
}
public PlayerChangeInfo claimReward(int level) {
// Initialize change info
var change = new PlayerChangeInfo();
// Get rewards
var rewards = GameData.getTowerDefenseLevelDataTable().get(level).getRewards();
// Add rewards
return getPlayer().getInventory().addItems(rewards, change);
}
// 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.getMutableTowerDefense();
// Add completed stages
for (int id : this.completedStages.keySet()) {
// Create proto
var level = ActivityTowerDefenseLevel.newInstance();
// Set proto params
level.setId(id);
level.setStar(this.completedStages.get(id));
// Add to final msg proto
proto.addLevels(level);
}
// Add completed quests
for (int id : this.completedStages.keySet()) {
// Create proto
var quest = ActivityQuest.newInstance();
// Set proto params
quest.setActivityId(this.getId());
quest.setId(id);
quest.setStatus(2); // TODO: properly handle event quests
// Add to final msg proto
proto.addQuests(quest);
}
}
}

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;
@@ -31,7 +30,7 @@ public class BattlePass implements GameDatabaseObject {
private int uid;
private transient BattlePassManager manager;
private int battlePassId;
private int battlePassId; // Season id
private int mode;
private int level;
private int exp;
@@ -72,6 +71,13 @@ public class BattlePass implements GameDatabaseObject {
return manager.getPlayer();
}
/**
* Sets the mode directly
*/
public synchronized void setMode(int mode) {
this.mode = mode;
}
public boolean isPremium() {
return this.mode > 0;
}
@@ -80,6 +86,14 @@ public class BattlePass implements GameDatabaseObject {
return GameData.getBattlePassRewardDataTable().get((this.getBattlePassId() << 16) + level);
}
/**
* Sets the level directly, use getMaxExp() instead if adding exp.
*/
public synchronized void setLevel(int level) {
this.level = level;
this.exp = 0;
}
public int getMaxExp() {
var data = GameData.getBattlePassLevelDataTable().get(this.getLevel() + 1);
return data != null ? data.getExp() : 0;
@@ -101,6 +115,32 @@ public class BattlePass implements GameDatabaseObject {
}
}
/**
* Returns true if any rewards or quests are claimable
*/
public synchronized boolean hasNew() {
// Check if any quests are complete but unclaimed
for (var quest : getQuests().values()) {
if (quest.isComplete() && !quest.isClaimed()) {
return true;
}
}
// Check if we have any pending rewards
for (int i = 1; i <= this.getLevel(); i++) {
if (!this.getBasicReward().isSet(i)) {
return true;
}
if (this.isPremium() && !this.getPremiumReward().isSet(i)) {
return true;
}
}
// No claimable things
return false;
}
public synchronized void resetDailyQuests(boolean resetWeekly) {
// Reset daily quests
for (var data : GameData.getBattlePassQuestDataTable()) {
@@ -119,14 +159,19 @@ public class BattlePass implements GameDatabaseObject {
this.syncQuest(quest);
}
// Reset weekly limit for exp
if (resetWeekly) {
this.expWeek = 0;
}
// Persist to database
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

@@ -12,6 +12,10 @@ public class BattlePassManager extends PlayerManager {
public BattlePassManager(Player player) {
super(player);
}
public boolean hasNew() {
return this.getBattlePass().hasNew();
}
// Database

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

@@ -1,6 +1,7 @@
package emu.nebula.game.player;
import java.util.Stack;
import java.util.concurrent.TimeUnit;
import dev.morphia.annotations.AlsoLoad;
import dev.morphia.annotations.Entity;
@@ -12,6 +13,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 +27,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 +108,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;
@@ -180,16 +186,23 @@ public class Player implements GameDatabaseObject {
}
public void setSession(GameSession session) {
if (this.session != null) {
// Sanity check
if (this.session == session) {
return;
}
// Clear player from session
this.session.clearPlayer();
int time = Nebula.getConfig().getServerOptions().sessionTimeout;
long timeout = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(time);
if (this.session == null) {
// Set session
this.session = session;
return;
}
// 1. Sanity check
// 2. Prevent incorrect deletion of players when re-logging into the game
if (this.session == session || this.lastLogin > timeout) {
return;
}
// Clear player from session
this.session.clearPlayer();
// Set session
this.session = session;
}
@@ -204,8 +217,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 +500,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 +518,9 @@ public class Player implements GameDatabaseObject {
WorldClassRewardState.newInstance()
.setFlag(getQuestManager().getLevelRewards().toBigEndianByteArray())
);
// Trigger achievement
this.trigger(AchievementCondition.WorldClassSpecific, this.getLevel());
}
// Calculate changes
@@ -550,7 +573,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 +624,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 +638,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 +703,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 +713,9 @@ public class Player implements GameDatabaseObject {
this.save();
}
// Init activities
this.getActivityManager().init();
// Load complete
this.loaded = true;
}
@@ -680,9 +724,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 +739,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() {
@@ -776,18 +829,20 @@ public class Player implements GameDatabaseObject {
// Set player states
var state = proto.getMutableState()
.setStorySet(true)
.setStorySet(this.getStoryManager().hasNew())
.setFriend(this.getFriendList().hasPendingRequests());
state.getMutableMail()
.setNew(this.getMailbox().hasNewMail());
state.getMutableBattlePass()
.setState(1);
.setState(this.getBattlePassManager().hasNew() ? 1 : 0);
state.getMutableAchievement()
.setNew(this.getAchievementManager().hasNewAchievements());
state.getMutableFriendEnergy();
state.getMutableMallPackage();
state.getMutableAchievement();
state.getMutableTravelerDuelQuest()
.setType(QuestType.TravelerDuel);
state.getMutableTravelerDuelChallengeQuest()
@@ -868,6 +923,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

@@ -38,6 +38,18 @@ public class StoryManager extends PlayerManager implements GameDatabaseObject {
this.save();
}
public boolean hasNew() {
if (this.getCompletedStories().size() < GameData.getStoryDataTable().size()) {
return true;
}
if (this.getCompletedSets().size() < GameData.getStorySetSectionDataTable().size()) {
return true;
}
return false;
}
public PlayerChangeInfo settle(IntList list) {
// Player change info
@@ -63,6 +75,7 @@ public class StoryManager extends PlayerManager implements GameDatabaseObject {
Nebula.getGameDatabase().addToSet(this, this.getPlayerUid(), "completedStories", id);
}
// Complete
return changes;
}
@@ -90,6 +103,7 @@ public class StoryManager extends PlayerManager implements GameDatabaseObject {
// Save to db
Nebula.getGameDatabase().update(this, this.getPlayerUid(), "completedSets." + chapterId, sectionIndex);
// Complete
return changes;
}
}

View File

@@ -1,12 +1,18 @@
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.data.resources.SecondarySkillDef;
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;
@@ -15,6 +21,7 @@ import emu.nebula.proto.PublicStarTower.StarTowerBuildInfo;
import emu.nebula.proto.PublicStarTower.TowerBuildChar;
import emu.nebula.util.Snowflake;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Getter;
@Getter
@@ -37,30 +44,29 @@ public class StarTowerBuild implements GameDatabaseObject {
private ItemParamMap potentials;
private ItemParamMap subNoteSkills;
private IntSet secondarySkills;
@Deprecated
public StarTowerBuild() {
}
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())
.toArray();
// Discs
this.discIds = game.getDiscs().stream()
.filter(d -> d.getId() > 0)
.mapToInt(d -> d.getId())
.toArray();
// Set char/disc ids
this.charIds = game.getCharIds();
this.discIds = game.getDiscIds();
// Add potentials
for (var entry : game.getPotentials()) {
@@ -84,8 +90,23 @@ public class StarTowerBuild implements GameDatabaseObject {
this.getSubNoteSkills().put(entry.getIntKey(), entry.getIntValue());
}
// Set secondary skills
this.secondarySkills = game.getSecondarySkills();
// 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,26 +130,38 @@ public class StarTowerBuild implements GameDatabaseObject {
// Score
private int calculateScore() {
// Init score
int score = 0;
public int calculateScore() {
// Clear score
this.score = 0;
// Potentials
// Add score from potentials
for (var potential : this.getPotentials().int2IntEntrySet()) {
var data = GameData.getPotentialDataTable().get(potential.getIntKey());
if (data == null) continue;
int index = potential.getIntValue() - 1;
score += data.getBuildScore()[index];
this.score += data.getBuildScore(potential.getIntValue());
}
// Sub note skills
// Add score from sub note skills
for (var item : this.getSubNoteSkills()) {
score += item.getIntValue() * 15;
this.score += item.getIntValue() * 15;
}
// Check secondary skills
if (this.getSecondarySkills() == null) {
this.secondarySkills = SecondarySkillDef.calculateSecondarySkills(this.getDiscIds(), this.getSubNoteSkills());
}
// Add score from secondary skills
for (int id : this.getSecondarySkills()) {
var data = GameData.getSecondarySkillDataTable().get(id);
if (data == null) continue;
this.score += data.getScore();
}
// Complete
return score;
return this.score;
}
// Proto
@@ -183,6 +216,11 @@ public class StarTowerBuild implements GameDatabaseObject {
proto.addSubNoteSkills(skill);
}
// Secondary skills
for (int id : this.getSecondarySkills()) {
proto.addActiveSecondaryIds(id);
}
return proto;
}

View File

@@ -1,128 +0,0 @@
package emu.nebula.game.tower;
import java.util.HashMap;
import java.util.Map;
import emu.nebula.proto.PublicStarTower.HawkerGoods;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class StarTowerCase {
private int id;
@Setter(AccessLevel.NONE)
private CaseType type;
// Extra data
private int teamLevel;
private int subNoteSkillNum;
private int floorId;
private int roomType;
private int eventId;
private int npcId;
// Selector
private IntList ids;
// Hawker
private Map<Integer, StarTowerShopGoods> goodsList;
public StarTowerCase(CaseType type) {
this.type = type;
}
public void addId(int id) {
if (this.ids == null) {
this.ids = new IntArrayList();
}
this.ids.add(id);
}
public int selectId(int index) {
if (this.getIds() == null) {
return 0;
}
if (index < 0 || index >= this.getIds().size()) {
return 0;
}
return this.getIds().getInt(index);
}
public void addGoods(StarTowerShopGoods goods) {
if (this.goodsList == null) {
this.goodsList = new HashMap<>();
}
this.getGoodsList().put(getGoodsList().size() + 1, goods);
}
// Proto
public StarTowerRoomCase toProto() {
var proto = StarTowerRoomCase.newInstance()
.setId(this.getId());
switch (this.type) {
case Battle -> {
proto.getMutableBattleCase()
.setSubNoteSkillNum(this.getSubNoteSkillNum());
}
case OpenDoor -> {
proto.getMutableDoorCase()
.setFloor(this.getFloorId())
.setType(this.getRoomType());
}
case SyncHP, RecoveryHP -> {
proto.getMutableSyncHPCase();
}
case SelectSpecialPotential -> {
proto.getMutableSelectSpecialPotentialCase()
.setTeamLevel(this.getTeamLevel())
.addAllIds(this.getIds().toIntArray());
}
case PotentialSelect -> {
proto.getMutableSelectPotentialCase();
}
case NpcEvent -> {
proto.getMutableSelectOptionsEventCase()
.setEvtId(this.getEventId())
.setNPCId(this.getNpcId())
.addAllOptions(this.getIds().toIntArray());
}
case Hawker -> {
var hawker = proto.getMutableHawkerCase();
for (var entry : getGoodsList().entrySet()) {
var sid = entry.getKey();
var goods = entry.getValue();
var info = HawkerGoods.newInstance()
.setIdx(goods.getGoodsId())
.setSid(sid)
.setType(goods.getType())
.setGoodsId(102) // ?
.setPrice(goods.getPrice())
.setTag(1);
hawker.addList(info);
}
}
default -> {
}
}
return proto;
}
}

File diff suppressed because it is too large Load Diff

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;
@@ -204,27 +205,53 @@ public class StarTowerManager extends PlayerManager {
}
// Create game
this.game = new StarTowerGame(this, data, formation, req);
try {
this.game = new StarTowerGame(this, data, formation, req);
} catch (Exception e) {
Nebula.getLogger().error("Could not create star tower game", e);
return null;
}
// 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;
}
@@ -300,9 +327,18 @@ public class StarTowerManager extends PlayerManager {
// Database
public void loadFromDatabase() {
// Init builds
this.builds = new Long2ObjectOpenHashMap<>();
// Load builds with the current player's uid
Nebula.getGameDatabase().getObjects(StarTowerBuild.class, "playerUid", getPlayerUid()).forEach(build -> {
// Fix outdated builds
if (build.getSecondarySkills() == null) {
build.calculateScore();
build.save();
}
// Add build
this.builds.put(build.getUid(), build);
});
}

View File

@@ -0,0 +1,181 @@
package emu.nebula.game.tower;
import emu.nebula.GameConstants;
import lombok.Getter;
/**
* Data class to hold various modifiers for star tower.
*/
@Getter
public class StarTowerModifiers {
private StarTowerGame game;
// Strengthen machines
private boolean enableEndStrengthen;
private boolean enableShopStrengthen;
private boolean freeStrengthen;
private int strengthenDiscount;
// Bonus max potential level
private int bonusMaxPotentialLevel;
// Shop
private int shopGoodsCount;
private int shopRerollCount;
private int shopRerollPrice;
private boolean shopDiscountTier1;
private boolean shopDiscountTier2;
private boolean shopDiscountTier3;
// Bonus potential level proc
private double bonusStrengthenChance = 0;
private double bonusPotentialChance = 0;
private int bonusPotentialLevel = 0;
private int potentialRerollCount;
private int potentialRerollDiscount;
public StarTowerModifiers(StarTowerGame game) {
this.game = game;
// Strengthen machines
this.enableEndStrengthen = game.getDifficulty() >= 2 && this.hasGrowthNode(10601);
this.enableShopStrengthen = game.getDifficulty() >= 4 && this.hasGrowthNode(20301);
this.freeStrengthen = this.hasGrowthNode(10801);
// Strengthen discount (Set Meal Agreement)
if (this.hasGrowthNode(30402)) {
this.strengthenDiscount = 60;
} else if (this.hasGrowthNode(30102)) {
this.strengthenDiscount = 30;
}
// Bonus potential max level (Ocean of Souls)
if (this.hasGrowthNode(30301)) {
this.bonusMaxPotentialLevel = 6;
} else if (this.hasGrowthNode(20601)) {
this.bonusMaxPotentialLevel = 4;
}
// Shop extra goods (Monolith Premium)
if (this.hasGrowthNode(20702)) {
this.shopGoodsCount = 8;
} else if (this.hasGrowthNode(20402)) {
this.shopGoodsCount = 6;
} else if (this.hasGrowthNode(10402)) {
this.shopGoodsCount = 4;
} else {
this.shopGoodsCount = 2;
}
if (this.hasGrowthNode(20902)) {
this.shopRerollCount++;
}
if (this.hasGrowthNode(30601)) {
this.shopRerollCount++;
}
if (this.shopRerollCount > 0) {
this.shopRerollPrice = 100;
}
// Shop discount (Member Discount)
this.shopDiscountTier1 = game.getDifficulty() >= 3 && this.hasGrowthNode(20202);
this.shopDiscountTier2 = game.getDifficulty() >= 4 && this.hasGrowthNode(20502);
this.shopDiscountTier3 = game.getDifficulty() >= 5 && this.hasGrowthNode(20802);
// Bonus potential enhancement level procs (Potential Boost)
if (game.getDifficulty() >= 7 && this.hasGrowthNode(30802)) {
this.bonusStrengthenChance = 0.3;
} else if (game.getDifficulty() >= 6 && this.hasGrowthNode(30502)) {
this.bonusStrengthenChance = 0.2;
} else if (game.getDifficulty() >= 6 && this.hasGrowthNode(30202)) {
this.bonusStrengthenChance = 0.1;
}
// Bonus potential levels (Butterflies Inside)
if (game.getDifficulty() >= 7 && this.hasGrowthNode(30901)) {
this.bonusPotentialChance = 0.3;
this.bonusMaxPotentialLevel = 2;
} else if (game.getDifficulty() >= 7 && this.hasGrowthNode(30801)) {
this.bonusPotentialChance = 0.2;
this.bonusMaxPotentialLevel = 1;
} else if (game.getDifficulty() >= 6 && this.hasGrowthNode(30201)) {
this.bonusPotentialChance = 0.1;
this.bonusMaxPotentialLevel = 1;
} else if (game.getDifficulty() >= 5 && this.hasGrowthNode(20801)) {
this.bonusPotentialChance = 0.05;
this.bonusMaxPotentialLevel = 1;
}
// Potential reroll (Cloud Dice)
if (this.hasGrowthNode(20901)) {
this.potentialRerollCount += 1;
}
// Potential reroll price discount (Destiny of Stars)
if (this.hasGrowthNode(30702)) {
this.potentialRerollDiscount = 60;
} else if (this.hasGrowthNode(30401)) {
this.potentialRerollDiscount = 40;
} else if (this.hasGrowthNode(30101)) {
this.potentialRerollDiscount = 30;
}
}
public boolean hasGrowthNode(int nodeId) {
return this.getGame().getManager().hasGrowthNode(nodeId);
}
public int getStartingCoin() {
int coin = 0;
if (this.hasGrowthNode(10103)) {
coin += 50;
} if (this.hasGrowthNode(10403)) {
coin += 100;
} if (this.hasGrowthNode(10702)) {
coin += 200;
}
return coin;
}
public int getStartingSubNotes() {
int subNotes = 0;
if (this.hasGrowthNode(10102)) {
subNotes += 3;
}
return subNotes;
}
public void addStartingItems() {
// Add starting coin directly
int coin = this.getStartingCoin();
if (coin > 0) {
this.getGame().getRes().add(GameConstants.TOWER_COIN_ITEM_ID, coin);
}
// Add starting subnotes
int subNotes = this.getStartingSubNotes();
for (int i = 0; i < subNotes; i++) {
int id = this.getGame().getRandomSubNoteId();
this.getGame().getItems().add(id, 1);
}
}
public void setFreeStrengthen(boolean b) {
this.freeStrengthen = b;
}
public void consumeShopReroll() {
this.shopRerollCount = Math.max(this.shopRerollCount - 1, 0);
}
}

View File

@@ -0,0 +1,26 @@
package emu.nebula.game.tower;
import emu.nebula.proto.PublicStarTower.PotentialInfo;
import lombok.Getter;
@Getter
public class StarTowerPotentialInfo {
private int id;
private int level;
public StarTowerPotentialInfo(int id, int level) {
this.id = id;
this.level = level;
}
// Proto
public PotentialInfo toProto() {
var proto = PotentialInfo.newInstance()
.setTid(this.getId())
.setLevel(this.getLevel());
return proto;
}
}

View File

@@ -7,12 +7,16 @@ import lombok.Getter;
@Entity(useDiscriminator = false)
public class StarTowerShopGoods {
private int type;
private int goodsId;
private int idx; // This is actually the shop goods id
private int goodsId; // Item id
private int price;
private int discount;
private int charPos;
private boolean sold;
public StarTowerShopGoods(int type, int goodsId, int price) {
public StarTowerShopGoods(int type, int idx, int goodsId, int price) {
this.type = type;
this.idx = idx;
this.goodsId = goodsId;
this.price = price;
}
@@ -21,4 +25,40 @@ public class StarTowerShopGoods {
this.sold = true;
}
public void setCharPos(int charPos) {
this.charPos = charPos;
}
public boolean hasDiscount() {
return this.getDiscount() > 0;
}
public void applyDiscount(double percentage) {
this.discount = (int) Math.ceil(this.price * (1.0 - percentage));
}
public int getPrice() {
return this.price - this.discount;
}
public int getDisplayPrice() {
return this.price;
}
public int getCount() {
if (this.getType() == 2) {
return this.getIdx() == 8 ? 15 : 5;
}
return 1;
}
public int getCharId(StarTowerGame game) {
if (this.getCharPos() == 0) {
return 0;
}
int index = this.getCharPos() - 1;
return game.getCharIds()[index];
}
}

View File

@@ -1,4 +1,4 @@
package emu.nebula.game.tower;
package emu.nebula.game.tower.cases;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;

View File

@@ -0,0 +1,57 @@
package emu.nebula.game.tower.cases;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.StarTowerModifiers;
import emu.nebula.game.tower.room.StarTowerBaseRoom;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import lombok.Getter;
/**
* Base class for star tower cases
*/
@Getter
public abstract class StarTowerBaseCase {
private transient StarTowerGame game;
private int id;
public StarTowerBaseCase() {
}
public StarTowerBaseRoom getRoom() {
return this.getGame().getRoom();
}
public StarTowerModifiers getModifiers() {
return this.getGame().getModifiers();
}
public abstract CaseType getType();
public void register(StarTowerBaseRoom room) {
this.game = room.getGame();
this.id = room.getNextCaseId();
this.onRegister();
}
public void onRegister() {
}
public abstract StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp);
// Proto
public StarTowerRoomCase toProto() {
var proto = StarTowerRoomCase.newInstance()
.setId(this.getId());
this.encodeProto(proto);
return proto;
}
public abstract void encodeProto(StarTowerRoomCase proto);
}

View File

@@ -0,0 +1,119 @@
package emu.nebula.game.tower.cases;
import emu.nebula.GameConstants;
import emu.nebula.data.GameData;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import lombok.Getter;
@Getter
public class StarTowerBattleCase extends StarTowerBaseCase {
private int subNoteSkillNum;
public StarTowerBattleCase() {
this(0);
}
public StarTowerBattleCase(int subNoteSkillNum) {
this.subNoteSkillNum = subNoteSkillNum;
}
@Override
public CaseType getType() {
return CaseType.Battle;
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Parse battle end
var proto = req.getBattleEndReq();
// Init change
var change = new PlayerChangeInfo();
// Handle victory/defeat
if (proto.hasVictory()) {
// Handle leveling up
// Get relevant floor exp data
// fishiatee: THERE'S NO LINQ IN JAVAAAAAAAAAAAAA
var floorExpData = GameData.getStarTowerFloorExpDataTable().stream()
.filter(f -> f.getStarTowerId() == this.getGame().getId())
.findFirst()
.orElseThrow();
int expReward = 0;
// Determine appropriate exp reward
switch (this.getRoom().getType()) {
// Regular battle room
case 0:
expReward = floorExpData.getNormalExp();
break;
// Elite battle room
case 1:
expReward = floorExpData.getEliteExp();
break;
// Non-final boss room
case 2:
expReward = floorExpData.getBossExp();
break;
// Final room
case 3:
expReward = floorExpData.getFinalBossExp();
break;
}
// Level up
this.getGame().addExp(expReward);
this.getGame().addPotentialSelectors(this.getGame().levelUp());
// Add clear time
this.getGame().addBattleTime(proto.getVictory().getTime());
// Handle victory
rsp.getMutableBattleEndResp()
.getMutableVictory()
.setLv(this.getGame().getTeamLevel())
.setBattleTime(this.getGame().getBattleTime());
// Add coin
int coin = this.getRoom().getStage().getInteriorCurrencyQuantity();
this.getGame().addItem(GameConstants.TOWER_COIN_ITEM_ID, coin, change);
// Handle pending potential selectors
var nextCases = this.getGame().handlePendingPotentialSelectors();
for (var towerCase : nextCases) {
this.getGame().addCase(rsp.getMutableCases(), towerCase);
}
// Add sub note skills
this.getGame().addRandomSubNoteSkills(this.getGame().getPendingSubNotes(), change);
// Handle client events for achievements
this.getGame().getPlayer().getAchievementManager().handleClientEvents(proto.getVictory().getEvents());
} else {
// Handle defeat
// TODO
return this.getGame().settle(rsp, false);
}
// Set change
rsp.setChange(change.toProto());
// Return response for the player
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
proto.getMutableBattleCase()
.setSubNoteSkillNum(this.getSubNoteSkillNum());
}
}

View File

@@ -0,0 +1,58 @@
package emu.nebula.game.tower.cases;
import emu.nebula.data.resources.StarTowerStageDef;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import lombok.Getter;
@Getter
public class StarTowerDoorCase extends StarTowerBaseCase {
private int floorNum;
private int roomType;
public StarTowerDoorCase(int floor, StarTowerStageDef data) {
this.floorNum = floor;
if (data != null) {
this.roomType = data.getRoomType();
}
}
@Override
public CaseType getType() {
return CaseType.OpenDoor;
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Get request
var proto = req.getEnterReq();
// Check if we need to settle on the last floor
if (this.getGame().isOnFinalFloor()) {
return this.getGame().settle(rsp, true);
}
// Enter next room
this.getGame().enterNextRoom();
this.getGame().getRoom().setMapInfo(proto);
// Set room proto
rsp.getMutableEnterResp()
.setRoom(this.getRoom().toProto());
// Done
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
proto.getMutableDoorCase()
.setFloor(this.getFloorNum())
.setType(this.getRoomType());
}
}

View File

@@ -0,0 +1,260 @@
package emu.nebula.game.tower.cases;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import emu.nebula.GameConstants;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.tower.StarTowerShopGoods;
import emu.nebula.proto.PublicStarTower.HawkerCaseData;
import emu.nebula.proto.PublicStarTower.HawkerGoods;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import emu.nebula.util.Utils;
import lombok.Getter;
@Getter
public class StarTowerHawkerCase extends StarTowerBaseCase {
private Map<Integer, StarTowerShopGoods> goods;
public StarTowerHawkerCase() {
this.goods = new HashMap<>();
}
@Override
public CaseType getType() {
return CaseType.Hawker;
}
@Override
public void onRegister() {
this.initGoods();
}
public void initGoods() {
// Clear goods
this.getGoods().clear();
// Caclulate amount of potentials/sub notes to sell
int total = getModifiers().getShopGoodsCount();
int minPotentials = Math.max(total / 2, 2);
int maxPotentials = Math.max(total - 1, minPotentials);
int potentials = Utils.randomRange(minPotentials, maxPotentials);
int subNotes = total - potentials;
boolean hasCoins = this.getGame().getResCount(GameConstants.TOWER_COIN_ITEM_ID) >= 500;
// Add goods
for (int i = 0; i < potentials; i++) {
// Create potential selector shop item
var goods = new StarTowerShopGoods(1, 1, 102, 200);
// Add character specific potentials
if (Utils.generateRandomDouble() < .2) {
goods.setCharPos(1);
}
// Add to goods map
this.addGoods(goods);
}
for (int i = 0; i < subNotes; i++) {
// Randomize sub note
int id = Utils.randomElement(this.getGame().getSubNoteDropList());
// Create sub note shop item
StarTowerShopGoods goods = null;
if (hasCoins && Utils.randomChance(.25)) {
goods = new StarTowerShopGoods(2, 8, id, 400);
} else {
goods = new StarTowerShopGoods(2, 3, id, 90);
}
// Add to goods map
this.addGoods(goods);
}
// Apply discounts based on star tower growth nodes
if (getModifiers().isShopDiscountTier1()) {
this.applyDiscount(1.0, 2, 0.8);
}
if (getModifiers().isShopDiscountTier2()) {
this.applyDiscount(0.3, 1, 0.5);
}
if (getModifiers().isShopDiscountTier3()) {
this.applyDiscount(1.0, 1, 0.5);
}
}
private void applyDiscount(double chance, int times, double percentage) {
// Check chance
double random = Utils.generateRandomDouble();
if (random > chance) {
return;
}
// Create goods list
var list = this.getGoods().values().stream()
.filter(g -> !g.hasDiscount())
.collect(Collectors.toList());
// Apply discounts
for (int i = 0; i < times; i++) {
// Sanity check
if (list.isEmpty()) {
break;
}
// Get goods and apply discount
var goods = Utils.randomElement(list, true);
goods.applyDiscount(percentage);
}
}
public void addGoods(StarTowerShopGoods goods) {
this.getGoods().put(getGoods().size() + 1, goods);
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Set nil resp
rsp.getMutableNilResp();
// Get hawker req
var hawker = req.getHawkerReq();
if (hawker.hasReRoll()) {
// Refresh shop items
this.refresh(rsp);
} else if (hawker.hasSid()) {
// Buy shop items
this.buy(hawker.getSid(), rsp);
}
// Success
return rsp;
}
private void refresh(StarTowerInteractResp rsp) {
// Check if we can refresh
if (this.getModifiers().getShopRerollCount() <= 0) {
return;
}
// Make sure we have enough currency
int coin = this.getGame().getResCount(GameConstants.TOWER_COIN_ITEM_ID);
int price = this.getModifiers().getShopRerollPrice();
if (coin < price) {
return;
}
// Create new goods
this.initGoods();
// Consume reroll count
this.getGame().getModifiers().consumeShopReroll();
// Set in proto
rsp.getMutableSelectResp()
.setHawkerCase(this.toHawkerCaseProto());
// Remove coins
var change = this.getGame().addItem(GameConstants.TOWER_COIN_ITEM_ID, -price);
// Set change info
rsp.setChange(change.toProto());
// Achievement
this.getGame().getAchievementManager().trigger(AchievementCondition.TowerSpecificShopReRollTotal, 1);
}
private void buy(int sid, StarTowerInteractResp rsp) {
// Get goods
var goods = this.getGoods().get(sid);
if (goods == null) {
return;
}
// Make sure we have enough currency
int coin = this.getGame().getResCount(GameConstants.TOWER_COIN_ITEM_ID);
int price = goods.getPrice();
if (coin < price || goods.isSold()) {
return;
}
// Mark goods as sold
goods.markAsSold();
// Create change info
var change = new PlayerChangeInfo();
// Add goods
if (goods.getType() == 1) {
// Potential selector
int charId = goods.getCharId(this.getGame());
this.getGame().addCase(rsp.getMutableCases(), this.getGame().createPotentialSelector(charId));
} else {
// Sub notes
this.getGame().addItem(goods.getGoodsId(), goods.getCount(), change);
}
// Remove coins
this.getGame().addItem(GameConstants.TOWER_COIN_ITEM_ID, -price, change);
// Achievement
this.getGame().getAchievementManager().trigger(AchievementCondition.TowerSpecificDifficultyShopBuyTimes, 1);
// Set change info
rsp.setChange(change.toProto());
}
// Proto
private HawkerCaseData toHawkerCaseProto() {
var hawker = HawkerCaseData.newInstance();
if (this.getModifiers().getShopRerollCount() > 0) {
hawker.setCanReRoll(true);
hawker.setReRollTimes(this.getModifiers().getShopRerollCount());
hawker.setReRollPrice(this.getModifiers().getShopRerollPrice());
}
for (var entry : this.getGoods().entrySet()) {
var sid = entry.getKey();
var goods = entry.getValue();
var info = HawkerGoods.newInstance()
.setSid(sid)
.setType(goods.getType())
.setIdx(goods.getIdx())
.setGoodsId(goods.getGoodsId())
.setPrice(goods.getDisplayPrice())
.setTag(1);
if (goods.hasDiscount()) {
info.setDiscount(goods.getPrice());
}
if (goods.getCharPos() > 0) {
info.setCharPos(goods.getCharPos());
}
hawker.addList(info);
}
return hawker;
}
@Override
public void encodeProto(StarTowerRoomCase proto) {
proto.setHawkerCase(this.toHawkerCaseProto());
}
}

View File

@@ -0,0 +1,330 @@
package emu.nebula.game.tower.cases;
import java.util.Collections;
import emu.nebula.GameConstants;
import emu.nebula.data.resources.StarTowerEventDef;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.proto.PublicStarTower.NPCAffinityInfo;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import emu.nebula.util.Utils;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter;
@Getter
public class StarTowerNpcEventCase extends StarTowerBaseCase {
private int npcId;
private int eventId;
private IntList options;
private boolean completed;
public StarTowerNpcEventCase(int npcId, StarTowerEventDef event) {
this.npcId = npcId;
this.eventId = event.getId();
this.options = new IntArrayList();
// Add up to 4 random options
var randomOptions = event.getClonedOptionIds();
int maxOptions = Math.min(randomOptions.size(), 4);
for (int i = 0; i < maxOptions; i++) {
int optionId = Utils.randomElement(randomOptions, true);
this.options.add(optionId);
}
// Fix for question type events to always include the answer
if (this.eventId >= 114 && this.eventId <= 116) {
int answerId = (this.eventId * 100) + 3;
if (!this.getOptions().contains(answerId)) {
this.getOptions().set(0, answerId);
}
}
// Shuffle
Collections.shuffle(this.getOptions());
}
@Override
public CaseType getType() {
return CaseType.NpcEvent;
}
public int getOption(int index) {
if (index < 0 || index >= this.getOptions().size()) {
return 0;
}
return this.getOptions().getInt(index);
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Sanity check to make sure we cant do the event multiple times
if (this.isCompleted()) {
return rsp;
}
// Get option from selection index
int option = this.getOption(req.getSelectReq().getIndex());
// Get select response proto
var selectRsp = rsp.getMutableSelectResp();
var success = selectRsp.getMutableResp();
var change = new PlayerChangeInfo();
// Completed event flag
boolean completed = true;
// Handle option id
switch (option) {
case 10101 -> {
if (this.spendCoin(100, change)) {
this.addPotentialSelector(rsp);
} else {
completed = false;
}
}
case 10102 -> {
if (this.spendCoin(120, change)) {
this.addPotentialSelector(rsp);
} else {
completed = false;
}
}
case 10103 -> {
this.addCoin(30, change);
}
case 10201 -> {
if (this.spendCoin(120, change)) {
this.addPotentialSelector(rsp, this.getRandomSupportCharId());
} else {
completed = false;
}
}
case 10202 -> {
if (this.spendCoin(160, change)) {
this.addPotentialSelector(rsp, this.getMainCharId());
} else {
completed = false;
}
}
case 10203 -> {
if (this.spendCoin(200, change)) {
this.addRarePotentialSelector(rsp);
} else {
completed = false;
}
}
case 10204 -> {
this.addCoin(30, change);
}
case 10302 -> {
// TODO
if (this.spendSubNotes(5, change)) {
this.addCoin(150, change);
} else {
completed = false;
}
}
case 10303 -> {
this.addCoin(30, change);
}
case 10401 -> {
// TODO
completed = false;
}
case 10402 -> {
if (this.spendCoin(200, change)) {
this.addRarePotentialSelector(rsp);
} else {
completed = false;
}
}
case 10403 -> {
this.addCoin(30, change);
}
case 10501 -> {
if (Utils.randomChance(.5)) {
this.addCoin(200, change);
} else {
this.addCoin(-100, change);
}
}
case 10502 -> {
if (Utils.randomChance(.3)) {
this.addCoin(650, change);
} else {
this.addCoin(-200, change);
}
}
case 10503 -> {
this.addCoin(30, change);
}
case 10601 -> {
if (Utils.randomChance(.5)) {
this.addRarePotentialSelector(rsp);
}
}
case 10602 -> {
this.addPotentialSelector(rsp);
}
case 10603 -> {
this.addCoin(30, change);
}
case 10701, 10702, 10703, 10704, 10705, 10706, 10707 -> {
int subNoteId = (option % 100) + 90010;
this.getGame().addItem(subNoteId, 5, change);
}
case 10708 -> {
int subNoteId = this.getGame().getRandomSubNoteId();
this.getGame().addItem(subNoteId, 5, change);
}
case 10801, 10802, 10803, 10804, 10805, 10806, 10807 -> {
if (this.spendCoin(140, change)) {
int subNoteId = (option % 100) + 90010;
this.getGame().addItem(subNoteId, 10, change);
} else {
completed = false;
}
}
case 10808 -> {
if (this.spendCoin(90, change)) {
int subNoteId = this.getGame().getRandomSubNoteId();
this.getGame().addItem(subNoteId, 10, change);
} else {
completed = false;
}
}
case 10809 -> {
this.addCoin(30, change);
}
case 11401, 11402, 11403, 11404, 11405 -> {
if (option == 11403) {
int subNoteId = this.getGame().getRandomSubNoteId();
this.getGame().addItem(subNoteId, 10, change);
} else {
success.setOptionsParamId(100140101);
}
}
case 11501, 11502, 11503, 11504, 11505 -> {
if (option == 11503) {
this.addPotentialSelector(rsp);
} else {
success.setOptionsParamId(100140101);
}
}
case 11601, 11602, 11603, 11604, 11605 -> {
if (option == 11603) {
this.addRarePotentialSelector(rsp);
} else {
success.setOptionsParamId(100140101);
}
}
case 12601 -> {
this.addPotentialSelector(rsp, this.getRandomSupportCharId());
}
case 12602 -> {
// Recover 20% hp
}
case 12701 -> {
this.addPotentialSelector(rsp, this.getRandomSupportCharId());
}
case 12702 -> {
int subNoteId = this.getGame().getRandomSubNoteId();
this.getGame().addItem(subNoteId, 5, change);
}
case 12801 -> {
this.addRarePotentialSelector(rsp, this.getRandomSupportCharId());
}
case 12802 -> {
this.addCoin(30, change);
}
default -> {
// Ignored
}
}
// Set change info
rsp.setChange(change.toProto());
// Set success result
success.setOptionsResult(completed);
this.completed = completed;
// Achievment
if (completed) {
this.getGame().getAchievementManager().trigger(AchievementCondition.TowerEventTimes, 1);
}
// Complete
return rsp;
}
// Helper functions
private boolean spendCoin(int amount, PlayerChangeInfo change) {
int coin = this.getGame().getResCount(GameConstants.TOWER_COIN_ITEM_ID);
if (coin < amount) {
return false;
}
this.addCoin(-amount, change);
return true;
}
private PlayerChangeInfo addCoin(int amount, PlayerChangeInfo change) {
return this.getGame().addItem(GameConstants.TOWER_COIN_ITEM_ID, amount, change);
}
private boolean spendSubNotes(int amount, PlayerChangeInfo change) {
// TODO
return false;
}
private void addPotentialSelector(StarTowerInteractResp rsp) {
this.addPotentialSelector(rsp, 0);
}
private void addPotentialSelector(StarTowerInteractResp rsp, int charId) {
var selectorCase = this.getGame().createPotentialSelector(charId);
this.getRoom().addCase(rsp.getMutableCases(), selectorCase);
}
private void addRarePotentialSelector(StarTowerInteractResp rsp) {
this.addRarePotentialSelector(rsp, 0);
}
private void addRarePotentialSelector(StarTowerInteractResp rsp, int charId) {
var selectorCase = this.getGame().createPotentialSelector(charId, true);
this.getRoom().addCase(rsp.getMutableCases(), selectorCase);
}
private int getMainCharId() {
return this.getGame().getCharIds()[0];
}
private int getRandomSupportCharId() {
return this.getGame().getCharIds()[Utils.randomRange(1, 2)];
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
var info = NPCAffinityInfo.newInstance()
.setNPCId(this.getNpcId())
.setAffinity(0);
proto.getMutableSelectOptionsEventCase()
.setEvtId(this.getEventId())
.setNPCId(this.getNpcId())
.addInfos(info)
.addAllOptions(this.getOptions().toIntArray());
}
}

View File

@@ -0,0 +1,38 @@
package emu.nebula.game.tower.cases;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import lombok.Getter;
@Getter
public class StarTowerNpcRecoveryHPCase extends StarTowerBaseCase {
private int effectId;
public StarTowerNpcRecoveryHPCase() {
this(989970); // Restore Hp/Energy by 50%
}
public StarTowerNpcRecoveryHPCase(int effectId) {
this.effectId = effectId;
}
@Override
public CaseType getType() {
return CaseType.NpcRecoveryHP;
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
// Set case info
proto.getMutableNpcRecoveryHPCase()
.setEffectId(this.getEffectId());
}
}

View File

@@ -0,0 +1,162 @@
package emu.nebula.game.tower.cases;
import java.util.List;
import emu.nebula.GameConstants;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.StarTowerPotentialInfo;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import lombok.Getter;
@Getter
public class StarTowerPotentialCase extends StarTowerBaseCase {
private int teamLevel;
private int charId;
private int reroll;
private int rerollPrice;
private boolean strengthen;
private List<StarTowerPotentialInfo> potentials;
public StarTowerPotentialCase(StarTowerGame game, boolean strengthen, List<StarTowerPotentialInfo> potentials) {
this(game, 0, potentials);
this.strengthen = strengthen;
}
public StarTowerPotentialCase(StarTowerGame game, int charId, List<StarTowerPotentialInfo> potentials) {
this.teamLevel = game.getTeamLevel();
this.charId = charId;
this.reroll = game.getModifiers().getPotentialRerollCount();
this.rerollPrice = 100 - game.getModifiers().getPotentialRerollDiscount();
this.potentials = potentials;
}
@Override
public CaseType getType() {
return CaseType.PotentialSelect;
}
public boolean isRare() {
return false;
}
public void setReroll(int count) {
this.reroll = count;
}
public boolean canReroll() {
return this.reroll > 0;
}
public StarTowerPotentialInfo selectId(int index) {
if (index < 0 || index >= this.getPotentials().size()) {
return null;
}
return this.getPotentials().get(index);
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Get select req
var select = req.getMutableSelectReq();
// Handle select option
if (select.hasReRoll()) {
this.reroll(rsp);
} else {
this.select(select.getIndex(), rsp);
}
return rsp;
}
private void reroll(StarTowerInteractResp rsp) {
// Check if we can reroll
if (!this.canReroll()) {
return;
}
// Check price
int coin = this.getGame().getResCount(GameConstants.TOWER_COIN_ITEM_ID);
int price = this.getRerollPrice();
if (coin < price) {
return;
}
// Subtract rerolls
int newReroll = this.reroll - 1;
// Create reroll case
StarTowerPotentialCase rerollCase = null;
if (this.isStrengthen()) {
rerollCase = this.getGame().createStrengthenSelector();
} else {
rerollCase = this.getGame().createPotentialSelector(this.getCharId(), this.isRare());
}
if (rerollCase == null) {
return;
}
// Clear reroll count
rerollCase.setReroll(newReroll);
// Add reroll case
this.getRoom().addCase(rsp.getMutableCases(), rerollCase);
// Finish subtracting rerolls
this.reroll = newReroll;
// Subtract coins
var change = this.getGame().addItem(GameConstants.TOWER_COIN_ITEM_ID, -price);
rsp.setChange(change.toProto());
// Achievement
this.getGame().getAchievementManager().trigger(AchievementCondition.TowerSpecificPotentialReRollTotal, 1);
}
private void select(int index, StarTowerInteractResp rsp) {
// Get selected potential
var potential = this.selectId(index);
if (potential == null) {
return;
}
// Add potential
var change = this.getGame().addItem(potential.getId(), potential.getLevel());
// Set change
rsp.setChange(change.toProto());
// Handle pending potential selectors
var nextCases = this.getGame().handlePendingPotentialSelectors();
for (var towerCase : nextCases) {
this.getRoom().addCase(rsp.getMutableCases(), towerCase);
}
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
var select = proto.getMutableSelectPotentialCase()
.setTeamLevel(this.getTeamLevel());
for (var potential : this.getPotentials()) {
select.addInfos(potential.toProto());
}
if (this.canReroll()) {
select.setCanReRoll(true);
select.setReRollPrice(this.getRerollPrice());
}
}
}

View File

@@ -0,0 +1,35 @@
package emu.nebula.game.tower.cases;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import lombok.Getter;
@Getter
public class StarTowerRecoveryHPCase extends StarTowerBaseCase {
@Override
public CaseType getType() {
return CaseType.RecoveryHP;
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Set nil resp
rsp.getMutableNilResp();
// Add sync hp case
this.getGame().addCase(rsp.getMutableCases(), new StarTowerSyncHPCase());
// Return
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
// Set field in the proto
proto.getMutableRecoveryHPCase();
}
}

View File

@@ -0,0 +1,43 @@
package emu.nebula.game.tower.cases;
import java.util.List;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.StarTowerPotentialInfo;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import lombok.Getter;
@Getter
public class StarTowerSelectSpecialPotentialCase extends StarTowerPotentialCase {
public StarTowerSelectSpecialPotentialCase(StarTowerGame game, int charId, List<StarTowerPotentialInfo> potentials) {
super(game, charId, potentials);
}
@Override
public CaseType getType() {
return CaseType.SelectSpecialPotential;
}
public boolean isRare() {
return true;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
var select = proto.getMutableSelectSpecialPotentialCase()
.setTeamLevel(this.getTeamLevel());
for (var potential : this.getPotentials()) {
select.addIds(potential.getId());
}
if (this.canReroll()) {
select.setCanReRoll(true);
select.setReRollPrice(this.getRerollPrice());
}
}
}

View File

@@ -0,0 +1,96 @@
package emu.nebula.game.tower.cases;
import emu.nebula.GameConstants;
import emu.nebula.game.achievement.AchievementCondition;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import lombok.Getter;
@Getter
public class StarTowerStrengthenMachineCase extends StarTowerBaseCase {
private boolean free;
private int discount;
private int times;
@Override
public void onRegister() {
// Set strengthen price
this.free = this.getModifiers().isFreeStrengthen();
this.discount = this.getModifiers().getStrengthenDiscount();
}
public int getPrice() {
if (this.free) {
return 0;
}
int price = 120 + (this.times * 60) - this.discount;
return Math.max(price, 0);
}
public void increasePrice() {
if (this.free) {
this.free = false;
this.getModifiers().setFreeStrengthen(false);
} else {
this.times++;
}
}
@Override
public CaseType getType() {
return CaseType.StrengthenMachine;
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Init case
StarTowerBaseCase towerCase = null;
// Check coin
int coin = getGame().getResCount(GameConstants.TOWER_COIN_ITEM_ID);
int price = this.getPrice();
if (coin >= price) {
towerCase = getGame().createStrengthenSelector();
}
if (towerCase != null) {
// Add enhancement selector case
this.getRoom().addCase(rsp.getMutableCases(), towerCase);
// Remove coins
var change = this.getGame().addItem(GameConstants.TOWER_COIN_ITEM_ID, -price);
// Set change info
rsp.setChange(change.toProto());
// Increment price
this.increasePrice();
// Achievement
this.getGame().getAchievementManager().trigger(AchievementCondition.TowerSpecificDifficultyStrengthenMachineTotal, 1);
}
// Set success result
rsp.getMutableStrengthenMachineResp()
.setBuySucceed(towerCase != null);
// Complete
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
// Set field in the proto
proto.getMutableStrengthenMachineCase()
.setFirstFree(this.isFree())
.setDiscount(this.getDiscount())
.setTimes(this.getTimes());
}
}

View File

@@ -0,0 +1,28 @@
package emu.nebula.game.tower.cases;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import lombok.Getter;
@Getter
public class StarTowerSyncHPCase extends StarTowerBaseCase {
@Override
public CaseType getType() {
return CaseType.SyncHP;
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
// Set field in the proto
proto.getMutableSyncHPCase();
}
}

View File

@@ -1,10 +1,10 @@
package emu.nebula.game.tower;
package emu.nebula.game.tower.room;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
public enum StarTowerRoomType {
public enum RoomType {
BattleRoom (0),
EliteBattleRoom (1),
BossRoom (2),
@@ -17,19 +17,19 @@ public enum StarTowerRoomType {
@Getter
private final int value;
private final static Int2ObjectMap<StarTowerRoomType> map = new Int2ObjectOpenHashMap<>();
private final static Int2ObjectMap<RoomType> map = new Int2ObjectOpenHashMap<>();
static {
for (StarTowerRoomType type : StarTowerRoomType.values()) {
for (RoomType type : RoomType.values()) {
map.put(type.getValue(), type);
}
}
private StarTowerRoomType(int value) {
private RoomType(int value) {
this.value = value;
}
public static StarTowerRoomType getByValue(int value) {
public static RoomType getByValue(int value) {
return map.get(value);
}
}

View File

@@ -0,0 +1,158 @@
package emu.nebula.game.tower.room;
import emu.nebula.data.resources.StarTowerStageDef;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.StarTowerModifiers;
import emu.nebula.game.tower.cases.CaseType;
import emu.nebula.game.tower.cases.StarTowerBaseCase;
import emu.nebula.game.tower.cases.StarTowerSyncHPCase;
import emu.nebula.proto.PublicStarTower.InteractEnterReq;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.PublicStarTower.StarTowerRoomData;
import emu.nebula.proto.StarTowerApply.StarTowerApplyReq;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.Getter;
import us.hebi.quickbuf.RepeatedMessage;
@Getter
public class StarTowerBaseRoom {
// Game
private transient StarTowerGame game;
private transient StarTowerStageDef stage;
// Map info
private int mapId;
private int mapTableId;
private String mapParam;
private int paramId;
// Cases
private int lastCaseId = 0;
private Int2ObjectMap<StarTowerBaseCase> cases;
// Misc
private boolean hasDoor;
public StarTowerBaseRoom(StarTowerGame game, StarTowerStageDef stage) {
this.game = game;
this.stage = stage;
this.cases = new Int2ObjectLinkedOpenHashMap<>();
}
public int getType() {
return stage.getRoomType();
}
public boolean hasDoor() {
return this.hasDoor;
}
public StarTowerModifiers getModifiers() {
return this.getGame().getModifiers();
}
public StarTowerBaseCase createExit() {
return this.getGame().createExit();
}
// Map info
public void setMapInfo(StarTowerApplyReq req) {
this.mapId = req.getMapId();
this.mapTableId = req.getMapTableId();
this.mapParam = req.getMapParam();
this.paramId = req.getParamId();
}
public void setMapInfo(InteractEnterReq req) {
this.mapId = req.getMapId();
this.mapTableId = req.getMapTableId();
this.mapParam = req.getMapParam();
this.paramId = req.getParamId();
}
// Cases
public int getNextCaseId() {
return ++this.lastCaseId;
}
public StarTowerBaseCase getCase(int id) {
return this.getCases().get(id);
}
public StarTowerBaseCase addCase(StarTowerBaseCase towerCase) {
return this.addCase(null, towerCase);
}
public StarTowerBaseCase addCase(RepeatedMessage<StarTowerRoomCase> cases, StarTowerBaseCase towerCase) {
// Sanity check
if (towerCase == null) {
return null;
}
// Set game for tower case
towerCase.register(this);
// Add to cases list
this.getCases().put(towerCase.getId(), towerCase);
// Add case to proto
if (cases != null) {
cases.add(towerCase.toProto());
}
// Check if door case
if (towerCase.getType() == CaseType.OpenDoor) {
this.hasDoor = true;
}
// Complete
return towerCase;
}
// Events
public void onEnter() {
// Create sync hp case
this.addCase(new StarTowerSyncHPCase());
// Create door case
this.createExit();
}
// Proto
public emu.nebula.proto.PublicStarTower.StarTowerRoom toProto() {
var proto = emu.nebula.proto.PublicStarTower.StarTowerRoom.newInstance()
.setData(this.getDataProto());
for (var towerCase : this.getCases().values()) {
proto.addCases(towerCase.toProto());
}
return proto;
}
private StarTowerRoomData getDataProto() {
var proto = StarTowerRoomData.newInstance()
.setFloor(this.getGame().getFloorCount())
.setMapId(this.getMapId())
.setRoomType(this.getType())
.setMapTableId(this.getMapTableId());
if (this.getMapParam() != null && !this.getMapParam().isEmpty()) {
proto.setMapParam(this.getMapParam());
}
if (this.getParamId() != 0) {
proto.setParamId(this.getParamId());
}
return proto;
}
}

View File

@@ -0,0 +1,26 @@
package emu.nebula.game.tower.room;
import emu.nebula.data.resources.StarTowerStageDef;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.cases.StarTowerBattleCase;
import emu.nebula.game.tower.cases.StarTowerSyncHPCase;
import emu.nebula.util.Utils;
import lombok.Getter;
@Getter
public class StarTowerBattleRoom extends StarTowerBaseRoom {
public StarTowerBattleRoom(StarTowerGame game, StarTowerStageDef stage) {
super(game, stage);
}
@Override
public void onEnter() {
// Create battle case
this.getGame().setPendingSubNotes(Utils.randomRange(1, 3));
this.addCase(new StarTowerBattleCase(this.getGame().getPendingSubNotes()));
// Create sync hp case
this.addCase(new StarTowerSyncHPCase());
}
}

View File

@@ -0,0 +1,71 @@
package emu.nebula.game.tower.room;
import java.util.Arrays;
import java.util.Objects;
import emu.nebula.GameConstants;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.StarTowerEventDef;
import emu.nebula.data.resources.StarTowerStageDef;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.cases.StarTowerBaseCase;
import emu.nebula.game.tower.cases.StarTowerNpcEventCase;
import emu.nebula.game.tower.cases.StarTowerSyncHPCase;
import emu.nebula.util.Utils;
import lombok.Getter;
@Getter
public class StarTowerEventRoom extends StarTowerBaseRoom {
public StarTowerEventRoom(StarTowerGame game, StarTowerStageDef stage) {
super(game, stage);
}
private StarTowerEventDef getRandomEvent() {
/*
var list = GameData.getStarTowerEventDataTable()
.values()
.stream()
.toList();
*/
var list = Arrays.stream(GameConstants.TOWER_EVENTS_IDS)
.mapToObj(GameData.getStarTowerEventDataTable()::get)
.filter(Objects::nonNull)
.toList();
if (list.isEmpty()) {
return null;
}
return Utils.randomElement(list);
}
public StarTowerBaseCase createNpcEvent() {
// Get random event
var event = this.getRandomEvent();
if (event == null) {
return null;
}
// Get random npc
int npcId = Utils.randomElement(event.getRelatedNPCs());
// Create case with event
return new StarTowerNpcEventCase(npcId, event);
}
@Override
public void onEnter() {
// Create npc
this.addCase(this.createNpcEvent());
// Create sync hp case
this.addCase(new StarTowerSyncHPCase());
// Create door case
this.createExit();
}
}

View File

@@ -0,0 +1,33 @@
package emu.nebula.game.tower.room;
import emu.nebula.data.resources.StarTowerStageDef;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.cases.StarTowerHawkerCase;
import emu.nebula.game.tower.cases.StarTowerStrengthenMachineCase;
import emu.nebula.game.tower.cases.StarTowerSyncHPCase;
import lombok.Getter;
@Getter
public class StarTowerHawkerRoom extends StarTowerBaseRoom {
public StarTowerHawkerRoom(StarTowerGame game, StarTowerStageDef stage) {
super(game, stage);
}
@Override
public void onEnter() {
// Create hawker case (shop)
this.addCase(new StarTowerHawkerCase());
// Create strengthen machine
if (this.getModifiers().isEnableShopStrengthen()) {
this.addCase(new StarTowerStrengthenMachineCase());
}
// Create sync hp case
this.addCase(new StarTowerSyncHPCase());
// Create door case
this.createExit();
}
}

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

@@ -141,6 +141,9 @@ public class HttpServer {
if (this.getType().runGame()) {
this.addGameServerRoutes();
}
// Custom api route(s)
getApp().post("/api/command", new RemoteHandler());
// Exception handler
getApp().exception(Exception.class, (e, c) -> {
@@ -154,8 +157,8 @@ public class HttpServer {
private void addLoginServerRoutes() {
// https://en-sdk-api.yostarplat.com/
getApp().post("/common/config", new CommonConfigHandler(this));
getApp().post("/common/version", new HttpJsonResponse(
"{\"Code\":200,\"Data\":{\"Agreement\":[{\"Version\":\"0.1\",\"Type\":\"user_agreement\",\"Title\":\"用户协议\",\"Content\":\"\",\"Lang\":\"en\"},{\"Version\":\"0.1\",\"Type\":\"privacy_agreement\",\"Title\":\"隐私政策\",\"Content\":\"\",\"Lang\":\"en\"}],\"ErrorCode\":\"4.4\"},\"Msg\":\"OK\"}"));
getApp().post("/common/client-code", new CommonClientCodeHandler());
getApp().post("/common/version", new HttpJsonResponse("{\"Code\":200,\"Data\":{\"Agreement\":[{\"Version\":\"0.1\",\"Type\":\"user_agreement\",\"Title\":\"用户协议\",\"Content\":\"\",\"Lang\":\"en\"},{\"Version\":\"0.1\",\"Type\":\"privacy_agreement\",\"Title\":\"隐私政策\",\"Content\":\"\",\"Lang\":\"en\"}],\"ErrorCode\":\"4.4\"},\"Msg\":\"OK\"}"));
getApp().post("/user/detail", new UserLoginHandler());
getApp().post("/user/set", new UserSetDataHandler());
@@ -163,16 +166,18 @@ public class HttpServer {
getApp().post("/user/quick-login", new UserLoginHandler());
getApp().post("/yostar/get-auth", new GetAuthHandler());
getApp().post("/yostar/send-code", new HttpJsonResponse("{\"Code\":200,\"Data\":{},\"Msg\":\"OK\"}")); // Dummy
// handler
getApp().post("/yostar/send-code", new HttpJsonResponse("{\"Code\":200,\"Data\":{},\"Msg\":\"OK\"}")); // Dummy handler
// https://nova-static.stellasora.global/
getApp().get("/meta/serverlist.html", new MetaServerlistHandler(this));
getApp().get("/meta/win.html", new MetaWinHandler(this));
getApp().post("/api/command", new RemoteHandler());
/*
fishiatee: Maybe this should be handled better.
For example, if raw meta is detected in say ./web/meta, serve that instead.
Otherwise, detect and serve from custom patchlist definition.
*/
//getApp().get("/meta/*.html", new MetaPatchListHandler(this));
}
private void addGameServerRoutes() {

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,17 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.activity_tower_defense_level_apply_req)
public class HandlerActivityTowerDefenseLevelApplyReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Encode and send
return session.encodeMsg(NetMsgId.activity_tower_defense_level_apply_succeed_ack);
}
}

View File

@@ -0,0 +1,35 @@
package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.ActivityTowerDefenseLevelSettle.ActivityTowerDefenseLevelSettleReq;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.game.activity.type.TowerDefenseActivity;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.activity_tower_defense_level_settle_req)
public class HandlerActivityTowerDefenseLevelSettleReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request proto
var req = ActivityTowerDefenseLevelSettleReq.parseFrom(message);
// Get activity
var activity = session.getPlayer().getActivityManager().getActivity(TowerDefenseActivity.class, 102001);
// Claim rewards
var change = activity.claimReward((int)req.getLevelId());
// Update completed stages
activity.getCompletedStages().put(req.getLevelId(), req.getStar());
// Save changes
session.getPlayer().save();
// Encode and send
return session.encodeMsg(NetMsgId.activity_tower_defense_level_settle_succeed_ack, change.toProto());
}
}

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

Some files were not shown because too many files have changed in this diff Show More