8 Commits

Author SHA1 Message Date
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
12 changed files with 362 additions and 44 deletions

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

@@ -30,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;
@@ -71,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;
}
@@ -79,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;
@@ -100,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()) {
@@ -118,6 +159,11 @@ public class BattlePass implements GameDatabaseObject {
this.syncQuest(quest);
}
// Reset weekly limit for exp
if (resetWeekly) {
this.expWeek = 0;
}
// Persist to database
this.save();
}

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

@@ -821,14 +821,14 @@ 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());

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

@@ -21,6 +21,7 @@ import emu.nebula.game.tower.cases.StarTowerDoorCase;
import emu.nebula.game.tower.cases.StarTowerHawkerCase;
import emu.nebula.game.tower.cases.StarTowerNpcRecoveryHPCase;
import emu.nebula.game.tower.cases.StarTowerPotentialCase;
import emu.nebula.game.tower.cases.StarTowerSelectSpecialPotentialCase;
import emu.nebula.game.tower.cases.StarTowerStrengthenMachineCase;
import emu.nebula.game.tower.room.RoomType;
import emu.nebula.game.tower.room.StarTowerBaseRoom;
@@ -178,11 +179,8 @@ public class StarTowerGame {
this.subNoteDropList.add(id);
}
// Add starting coin directly
int coin = this.getModifiers().getStartingCoin();
if (coin > 0) {
this.getRes().add(GameConstants.TOWER_COIN_ITEM_ID, coin);
}
// Add starting items
this.getModifiers().addStartingItems();
}
public Player getPlayer() {
@@ -489,18 +487,18 @@ public class StarTowerGame {
/**
* Creates a potential selector for a random character
*/
public StarTowerBaseCase createPotentialSelector() {
public StarTowerPotentialCase createPotentialSelector() {
return this.createPotentialSelector(0);
}
public StarTowerBaseCase createPotentialSelector(int charId) {
public StarTowerPotentialCase createPotentialSelector(int charId) {
return this.createPotentialSelector(charId, false);
}
/**
* Creates a potential selector for the specified character
*/
public StarTowerBaseCase createPotentialSelector(int charId, boolean rareOnly) {
public StarTowerPotentialCase createPotentialSelector(int charId, boolean rareOnly) {
// Check character id
if (charId <= 0) {
charId = this.getRandomCharId();
@@ -595,10 +593,14 @@ public class StarTowerGame {
}
// Creator potential selector case
return new StarTowerPotentialCase(this.getTeamLevel(), selector);
if (rareOnly) {
return new StarTowerSelectSpecialPotentialCase(this, charId, selector);
} else {
return new StarTowerPotentialCase(this, charId, selector);
}
}
public StarTowerBaseCase createStrengthenSelector() {
public StarTowerPotentialCase createStrengthenSelector() {
// Random potentials list
var potentials = new IntArrayList();
@@ -649,7 +651,7 @@ public class StarTowerGame {
}
// Creator potential selector case
return new StarTowerPotentialCase(this.getTeamLevel(), selector);
return new StarTowerPotentialCase(this, true, selector);
}
public void setPendingSubNotes(int amount) {

View File

@@ -1,5 +1,6 @@
package emu.nebula.game.tower;
import emu.nebula.GameConstants;
import lombok.Getter;
/**
@@ -29,11 +30,14 @@ public class StarTowerModifiers {
private boolean shopDiscountTier2;
private boolean shopDiscountTier3;
// Bonus potential levels
// 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;
@@ -43,11 +47,11 @@ public class StarTowerModifiers {
this.freeStrengthen = this.hasGrowthNode(10801);
// Strengthen discount (Set Meal Agreement)
if (this.hasGrowthNode(30402)) {
this.strengthenDiscount += 60;
}
if (this.hasGrowthNode(30102)) {
this.strengthenDiscount += 30;
this.strengthenDiscount = 60;
} else if (this.hasGrowthNode(30102)) {
this.strengthenDiscount = 30;
}
// Bonus potential max level (Ocean of Souls)
@@ -57,7 +61,7 @@ public class StarTowerModifiers {
this.bonusMaxPotentialLevel = 4;
}
// Shop (Monolith Premium)
// Shop extra goods (Monolith Premium)
if (this.hasGrowthNode(20702)) {
this.shopGoodsCount = 8;
} else if (this.hasGrowthNode(20402)) {
@@ -84,7 +88,7 @@ public class StarTowerModifiers {
this.shopDiscountTier2 = game.getDifficulty() >= 4 && this.hasGrowthNode(20502);
this.shopDiscountTier3 = game.getDifficulty() >= 5 && this.hasGrowthNode(20802);
// Bonus potential levels (Potential Boost)
// 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)) {
@@ -107,6 +111,20 @@ public class StarTowerModifiers {
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) {
@@ -114,17 +132,43 @@ public class StarTowerModifiers {
}
public int getStartingCoin() {
int gold = 0;
int coin = 0;
if (this.hasGrowthNode(10103)) {
gold += 50;
coin += 50;
} if (this.hasGrowthNode(10403)) {
gold += 100;
coin += 100;
} if (this.hasGrowthNode(10702)) {
gold += 200;
coin += 200;
}
return gold;
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) {

View File

@@ -7,28 +7,24 @@ 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 count;
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;
this.count = 1;
}
public void markAsSold() {
this.sold = true;
}
public void setCount(int count) {
this.count = count;
}
public void setCharPos(int charPos) {
this.charPos = charPos;
}
@@ -49,6 +45,14 @@ public class StarTowerShopGoods {
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;

View File

@@ -44,12 +44,14 @@ public class StarTowerHawkerCase extends StarTowerBaseCase {
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, 102, 200);
var goods = new StarTowerShopGoods(1, 1, 102, 200);
// Add character specific potentials
if (Utils.generateRandomDouble() < .2) {
@@ -62,11 +64,15 @@ public class StarTowerHawkerCase extends StarTowerBaseCase {
for (int i = 0; i < subNotes; i++) {
// Randomize sub note
int id = Utils.randomElement(this.getGame().getSubNoteDropList());
int count = Utils.randomRange(3, 10);
// Create sub note shop item
var goods = new StarTowerShopGoods(2, id, 15 * count);
goods.setCount(count);
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);
@@ -219,9 +225,9 @@ public class StarTowerHawkerCase extends StarTowerBaseCase {
var goods = entry.getValue();
var info = HawkerGoods.newInstance()
.setIdx(1)
.setSid(sid)
.setType(goods.getType())
.setIdx(goods.getIdx())
.setGoodsId(goods.getGoodsId())
.setPrice(goods.getDisplayPrice())
.setTag(1);

View File

@@ -205,16 +205,22 @@ public class StarTowerNpcEventCase extends StarTowerBaseCase {
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 -> {

View File

@@ -2,6 +2,8 @@ package emu.nebula.game.tower.cases;
import java.util.List;
import emu.nebula.GameConstants;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.StarTowerPotentialInfo;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
@@ -12,10 +14,22 @@ 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(int teamLevel, List<StarTowerPotentialInfo> potentials) {
this.teamLevel = teamLevel;
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;
}
@@ -24,6 +38,18 @@ public class StarTowerPotentialCase extends StarTowerBaseCase {
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;
@@ -34,9 +60,66 @@ public class StarTowerPotentialCase extends StarTowerBaseCase {
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Get selected potential
var index = req.getMutableSelectReq().getIndex();
// Check
var select = req.getMutableSelectReq();
if (select.hasReRoll()) {
return this.reroll(rsp);
} else {
return this.select(select.getIndex(), rsp);
}
}
private StarTowerInteractResp reroll(StarTowerInteractResp rsp) {
// Check if we can reroll
if (!this.canReroll()) {
return rsp;
}
// Check price
int coin = this.getGame().getResCount(GameConstants.TOWER_COIN_ITEM_ID);
int price = this.getRerollPrice();
if (coin < price) {
return rsp;
}
// 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 rsp;
}
// 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());
// Complete
return rsp;
}
private StarTowerInteractResp select(int index, StarTowerInteractResp rsp) {
// Get selected potential
var potential = this.selectId(index);
if (potential == null) {
return rsp;
@@ -52,7 +135,7 @@ public class StarTowerPotentialCase extends StarTowerBaseCase {
var nextCases = this.getGame().handlePendingPotentialSelectors();
for (var towerCase : nextCases) {
this.getGame().addCase(rsp.getMutableCases(), towerCase);
this.getRoom().addCase(rsp.getMutableCases(), towerCase);
}
// Complete
@@ -69,5 +152,10 @@ public class StarTowerPotentialCase extends StarTowerBaseCase {
for (var potential : this.getPotentials()) {
select.addInfos(potential.toProto());
}
if (this.canReroll()) {
select.setCanReRoll(true);
select.setReRollPrice(this.getRerollPrice());
}
}
}

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