Rework star tower

This commit is contained in:
Melledy
2025-12-02 23:15:31 -08:00
parent 33b1cf55d4
commit 893b23b50d
19 changed files with 880 additions and 537 deletions

View File

@@ -21,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

@@ -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;
}
}

View File

@@ -14,6 +14,14 @@ import emu.nebula.game.formation.Formation;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.tower.cases.StarTowerBaseCase;
import emu.nebula.game.tower.cases.StarTowerDoorCase;
import emu.nebula.game.tower.cases.StarTowerPotentialCase;
import emu.nebula.game.tower.room.RoomType;
import emu.nebula.game.tower.room.StarTowerBaseRoom;
import emu.nebula.game.tower.room.StarTowerBattleRoom;
import emu.nebula.game.tower.room.StarTowerEventRoom;
import emu.nebula.game.tower.room.StarTowerHawkerRoom;
import emu.nebula.proto.PublicStarTower.*;
import emu.nebula.proto.StarTowerApply.StarTowerApplyReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
@@ -21,14 +29,11 @@ import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import emu.nebula.util.Snowflake;
import emu.nebula.util.Utils;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter;
import lombok.SneakyThrows;
import us.hebi.quickbuf.RepeatedMessage;
@Getter
@@ -40,15 +45,13 @@ public class StarTowerGame {
// Tower id
private int id;
// Room
// Tower floor count
private int floorCount;
private int stageNum;
private int stageFloor;
private int floor;
private int mapId;
private int mapTableId;
private String mapParam;
private int paramId;
private int roomType;
// Tower room
private StarTowerBaseRoom room;
// Team
private int formationId;
@@ -58,16 +61,12 @@ public class StarTowerGame {
private int nextLevelExp;
private int charHp;
private int battleTime;
private int battleCount;
private List<StarTowerChar> chars;
private List<StarTowerDisc> discs;
private IntList charIds;
// Case
private int lastCaseId = 0;
private List<StarTowerCase> cases;
private Int2IntMap cachedCases;
private int pendingPotentialCases = 0;
private int pendingSubNotes = 0;
// Bag
private ItemParamMap items;
@@ -91,37 +90,34 @@ public class StarTowerGame {
}
public StarTowerGame(StarTowerManager manager, StarTowerDef data, Formation formation, StarTowerApplyReq req) {
// Set manager and cache resource data
this.manager = manager;
this.data = data;
// Set tower id
this.id = req.getId();
this.mapId = req.getMapId();
this.mapTableId = req.getMapTableId();
this.mapParam = req.getMapParam();
this.paramId = req.getParamId();
// Setup room
this.enterNextRoom();
this.getRoom().setMapInfo(req);
// Setup team
this.formationId = req.getFormationId();
this.buildId = Snowflake.newUid();
this.teamLevel = 1;
this.teamExp = 0;
this.nextLevelExp = GameData.getStarTowerTeamExpDataTable().get(2).getNeedExp();
this.stageNum = 1;
this.stageFloor = 1;
this.floor = 1;
this.charHp = -1;
this.chars = new ArrayList<>();
this.discs = new ArrayList<>();
this.charIds = new IntArrayList();
this.cases = new ArrayList<>();
this.cachedCases = new Int2IntOpenHashMap();
this.items = new ItemParamMap();
this.res = new ItemParamMap();
this.potentials = new ItemParamMap();
this.newInfos = new ItemParamMap();
// Melody skill drop list
this.subNoteDropList = new IntArrayList();
// Init formation
@@ -174,10 +170,6 @@ public class StarTowerGame {
if (money > 0) {
this.getRes().add(GameConstants.STAR_TOWER_GOLD_ITEM_ID, money);
}
// Add cases
this.addCase(new StarTowerCase(CaseType.Battle));
this.addCase(new StarTowerCase(CaseType.SyncHP));
}
public Player getPlayer() {
@@ -196,14 +188,14 @@ public class StarTowerGame {
return Utils.randomElement(this.getCharIds());
}
public StarTowerStageDef getStageData(int stage, int floor) {
var stageId = (this.getId() * 10000) + (stage * 100) + floor;
public StarTowerStageDef getStageData(int stageNum, int stageFloor) {
var stageId = (this.getId() * 10000) + (stageNum * 100) + stageFloor;
return GameData.getStarTowerStageDataTable().get(stageId);
}
public StarTowerStageDef getNextStageData() {
int stage = this.stageNum;
int floor = this.stageFloor + 1;
int stage = this.getStageNum();
int floor = this.getStageFloor() + 1;
if (floor >= this.getData().getMaxFloor(this.getStageNum())) {
floor = 1;
@@ -213,6 +205,49 @@ public class StarTowerGame {
return getStageData(stage, floor);
}
public boolean isOnFinalFloor() {
int nextFloor = this.getFloorCount() + 1;
return nextFloor > this.getData().getMaxFloors();
}
@SneakyThrows
public void enterNextRoom() {
// Increment total floor count
this.floorCount++;
// Calculate stage num/floor
int nextStageFloor = this.stageFloor + 1;
if (this.stageFloor >= this.getData().getMaxFloor(this.stageNum)) {
this.stageNum++;
this.stageFloor = 1;
} else {
this.stageFloor = nextStageFloor;
}
// Get stage data
var stage = this.getStageData(this.stageNum, this.stageFloor);
if (stage == null) {
throw new RuntimeException("No stage data for stage " + this.stageNum + "-" + this.stageFloor + " found");
}
// Create room
int roomType = stage.getRoomType();
if (roomType <= RoomType.FinalBossRoom.getValue()) {
this.room = new StarTowerBattleRoom(this, stage);
} else if (roomType == RoomType.EventRoom.getValue()) {
this.room = new StarTowerEventRoom(this, stage);
} else if (roomType == RoomType.ShopRoom.getValue()) {
this.room = new StarTowerHawkerRoom(this, stage);
} else {
this.room = new StarTowerBaseRoom(this, stage);
}
// Create cases for the room
this.room.onEnter();
}
public int getStartingGold() {
int gold = 0;
@@ -226,6 +261,10 @@ public class StarTowerGame {
return gold;
}
public void addExp(int amount) {
this.teamExp += amount;
}
public int levelUp(int picks) {
if (this.teamExp >= this.nextLevelExp && this.nextLevelExp != Integer.MAX_VALUE) {
@@ -261,50 +300,18 @@ public class StarTowerGame {
return this.levelUp(potentialPicks);
}
public void addBattleTime(int amount) {
this.battleTime += amount;
}
// Cases
public StarTowerCase getCase(CaseType type) {
// Check if we have any cached case for this case type
if (!this.getCachedCases().containsKey(type.getValue())) {
return null;
}
// Get index of cached case
int index = this.getCachedCases().get(type.getValue());
// Sanity check just in case
if (index < 0 || index >= this.getCases().size()) {
return null;
}
return this.getCases().get(index);
public StarTowerBaseCase addCase(StarTowerBaseCase towerCase) {
return this.getRoom().addCase(towerCase);
}
public void cacheCaseIndex(StarTowerCase towerCase) {
this.getCachedCases().put(towerCase.getType().getValue(), this.getCases().size() - 1);
}
public StarTowerCase addCase(StarTowerCase towerCase) {
return this.addCase(null, towerCase);
}
public StarTowerCase addCase(RepeatedMessage<StarTowerRoomCase> cases, StarTowerCase towerCase) {
// Add to cases list
this.getCases().add(towerCase);
// Increment id
towerCase.setId(++this.lastCaseId);
// Set proto
if (cases != null) {
cases.add(towerCase.toProto());
}
// Cache case index
this.cacheCaseIndex(towerCase);
// Complete
return towerCase;
public StarTowerBaseCase addCase(RepeatedMessage<StarTowerRoomCase> cases, StarTowerBaseCase towerCase) {
return this.getRoom().addCase(cases, towerCase);
}
// Items
@@ -388,11 +395,38 @@ public class StarTowerGame {
// Potentials/Sub notes
private StarTowerCase createPotentialSelector(int charId) {
// Add potential selector
var potentialCase = new StarTowerCase(CaseType.SelectSpecialPotential);
potentialCase.setTeamLevel(this.getTeamLevel());
/**
* Adds random potential selector cases for the client
*/
public void addPotentialSelectors(int amount) {
this.pendingPotentialCases += amount;
}
/**
* Creates a potential selector for the client if there are any potential selectors avaliable.
* If there are none, then return null.
*/
public StarTowerBaseCase handlePendingPotentialSelectors() {
if (this.pendingPotentialCases > 0) {
this.pendingPotentialCases--;
return this.createPotentialSelector();
} else {
return null;
}
}
/**
* Creates a potential selector for a random character
*/
public StarTowerBaseCase createPotentialSelector() {
int charId = this.getRandomCharId();
return this.createPotentialSelector(charId);
}
/**
* Creates a potential selector for the specified character
*/
public StarTowerBaseCase createPotentialSelector(int charId) {
// Get random potentials
List<PotentialDef> potentials = new ArrayList<>();
@@ -402,12 +436,21 @@ public class StarTowerGame {
}
}
// Get up to 3 random potentials
// TODO bug: this may pick duplicate potentials
IntList selector = new IntArrayList();
for (int i = 0; i < 3; i++) {
var potentialData = Utils.randomElement(potentials);
potentialCase.addId(potentialData.getId());
selector.add(potentialData.getId());
}
return potentialCase;
// Creator potential selector case
return new StarTowerPotentialCase(this.getTeamLevel(), selector);
}
public void setPendingSubNotes(int amount) {
this.pendingSubNotes = amount;
}
private PlayerChangeInfo addRandomSubNoteSkills(PlayerChangeInfo change) {
@@ -419,7 +462,7 @@ public class StarTowerGame {
return change;
}
private PlayerChangeInfo addRandomSubNoteSkills(int count, PlayerChangeInfo change) {
public PlayerChangeInfo addRandomSubNoteSkills(int count, PlayerChangeInfo change) {
for (int i = 0; i < count; i++) {
this.addRandomSubNoteSkills(change);
}
@@ -427,22 +470,34 @@ public class StarTowerGame {
return change;
}
// Door case
public StarTowerBaseCase createExit() {
return this.createExit(null);
}
public StarTowerBaseCase createExit(RepeatedMessage<StarTowerRoomCase> cases) {
return this.getRoom().addCase(
cases,
new StarTowerDoorCase(this.getFloorCount() + 1, this.getNextStageData())
);
}
// Handlers
public StarTowerInteractResp handleInteract(StarTowerInteractReq req) {
// Build response proto
var rsp = StarTowerInteractResp.newInstance()
.setId(req.getId());
if (req.hasBattleEndReq()) {
rsp = this.onBattleEnd(req, rsp);
} else if (req.hasRecoveryHPReq()) {
rsp = this.onRecoveryHP(req, rsp);
} else if (req.hasSelectReq()) {
rsp = this.onSelect(req, rsp);
} else if (req.hasEnterReq()) {
rsp = this.onEnterReq(req, rsp);
} else if (req.hasHawkerReq()) {
rsp = this.onHawkerReq(req, rsp);
// Get tower case
var towerCase = this.getRoom().getCase(req.getId());
// Handle interaction with tower case
if (towerCase != null) {
rsp = towerCase.interact(req, rsp);
} else {
rsp.getMutableNilResp();
}
// Add any items
@@ -465,291 +520,13 @@ public class StarTowerGame {
// Set these protos
rsp.getMutableChange();
// Return response proto
return rsp;
}
// Interact events
@SneakyThrows
public StarTowerInteractResp onBattleEnd(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.getId())
.findFirst()
.orElseThrow();
int expReward = 0;
// Determine appropriate exp reward
switch (this.getRoomType()) {
// 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.teamExp += expReward;
this.pendingPotentialCases += this.levelUp();
// Add clear time
this.battleTime += proto.getVictory().getTime();
// Handle victory
rsp.getMutableBattleEndResp()
.getMutableVictory()
.setLv(this.getTeamLevel())
.setBattleTime(this.getBattleTime());
// Add money
int money = this.getStageData(this.getStageNum(), this.getStageFloor()).getInteriorCurrencyQuantity();
this.addItem(GameConstants.STAR_TOWER_GOLD_ITEM_ID, money, change);
// Handle pending potential selectors
if (this.pendingPotentialCases > 0) {
// Create potential selector
var potentialCase = this.createPotentialSelector(this.getRandomCharId());
this.addCase(rsp.getMutableCases(), potentialCase);
this.pendingPotentialCases--;
}
else {
// Add door case here
var doorCase = this.addCase(new StarTowerCase(CaseType.OpenDoor));
doorCase.setFloorId(this.getStageFloor() + 1);
var nextStage = this.getNextStageData();
if (nextStage != null) {
doorCase.setRoomType(nextStage.getRoomType());
}
this.addCase(rsp.getMutableCases(), doorCase);
}
// Add sub note skills
var battleCase = this.getCase(CaseType.Battle);
if (battleCase != null) {
int subNoteSkills = battleCase.getSubNoteSkillNum();
this.addRandomSubNoteSkills(subNoteSkills, change);
}
// Handle client events for achievements
getPlayer().getAchievementManager().handleClientEvents(proto.getVictory().getEvents());
} else {
// Handle defeat
// TODO
}
// Increment battle count
this.battleCount++;
// Set change
rsp.setChange(change.toProto());
// Return response for the player
return rsp;
}
public StarTowerInteractResp onSelect(StarTowerInteractReq req, StarTowerInteractResp rsp) {
var index = req.getMutableSelectReq().getIndex();
var selectorCase = this.getCase(CaseType.SelectSpecialPotential);
if (selectorCase == null) {
return rsp;
}
int id = selectorCase.selectId(index);
if (id <= 0) {
return rsp;
}
// Add item
var change = this.addItem(id, 1, null);
// Set change
rsp.setChange(change.toProto());
// Handle pending potential selectors
if (this.pendingPotentialCases > 0) {
// Create potential selector
var potentialCase = this.createPotentialSelector(this.getRandomCharId());
this.addCase(rsp.getMutableCases(), potentialCase);
this.pendingPotentialCases--;
}
else {
// Add door case
var doorCase = this.addCase(new StarTowerCase(CaseType.OpenDoor));
doorCase.setFloorId(this.getStageFloor() + 1);
var nextStage = this.getNextStageData();
if (nextStage != null) {
doorCase.setRoomType(nextStage.getRoomType());
}
this.addCase(rsp.getMutableCases(), doorCase);
}
return rsp;
}
public StarTowerInteractResp onEnterReq(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Get proto
var proto = req.getEnterReq();
// Set
this.mapId = proto.getMapId();
this.mapTableId = proto.getMapTableId();
this.mapParam = proto.getMapParam();
this.paramId = proto.getParamId();
this.floor++;
// Check if we need to settle
if (this.floor > this.getData().getMaxFloors()) {
return this.settle(rsp);
}
// Next floor
int nextFloor = this.stageFloor + 1;
if (this.stageFloor >= this.getData().getMaxFloor(this.getStageNum())) {
this.stageFloor = 1;
this.stageNum++;
} else {
this.stageFloor = nextFloor;
}
// Calculate stage
var stageData = this.getStageData(this.getStageNum(), this.getStageFloor());
if (stageData != null) {
this.roomType = stageData.getRoomType();
} else {
this.roomType = 0;
}
// Clear cases
this.lastCaseId = 0;
this.cases.clear();
this.cachedCases.clear();
// Add cases
var syncHpCase = new StarTowerCase(CaseType.SyncHP);
// Room proto
var room = rsp.getMutableEnterResp().getMutableRoom();
room.setData(this.toRoomDataProto());
// Handle room type TODO
if (this.roomType <= StarTowerRoomType.FinalBossRoom.getValue()) {
var battleCase = new StarTowerCase(CaseType.Battle);
battleCase.setSubNoteSkillNum(Utils.randomRange(1, 3));
this.addCase(room.getMutableCases(), battleCase);
} else if (this.roomType == StarTowerRoomType.EventRoom.getValue()) {
} else if (this.roomType == StarTowerRoomType.ShopRoom.getValue()) {
var hawkerCase = new StarTowerCase(CaseType.Hawker);
// TODO
for (int i = 0; i < 8; i++) {
hawkerCase.addGoods(new StarTowerShopGoods(1, 1, 200));
}
this.addCase(room.getMutableCases(), hawkerCase);
}
// Add cases
this.addCase(room.getMutableCases(), syncHpCase);
// Add door to next floor if current floor is choice/shop domains
if (this.roomType == 6 | this.roomType == 7) {
var doorCase = new StarTowerCase(CaseType.OpenDoor);
doorCase.setFloorId(this.getFloor() + 1);
// Set room type of next room
var nextStage = this.getNextStageData();
if (nextStage != null) {
doorCase.setRoomType(nextStage.getRoomType());
}
// Add case
this.addCase(room.getMutableCases(), doorCase);
}
// Done
return rsp;
}
public StarTowerInteractResp onRecoveryHP(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Add case
this.addCase(rsp.getMutableCases(), new StarTowerCase(CaseType.RecoveryHP));
return rsp;
}
private StarTowerInteractResp onHawkerReq(StarTowerInteractReq req, StarTowerInteractResp rsp) {
// Set this proto
rsp.getMutableNilResp();
// Get hawker case
var shop = this.getCase(CaseType.Hawker);
if (shop == null || shop.getGoodsList() == null) {
return rsp;
}
// Get goods
var goods = shop.getGoodsList().get(req.getHawkerReq().getSid());
// Make sure we have enough currency
if (this.getRes().get(GameConstants.STAR_TOWER_GOLD_ITEM_ID) < goods.getPrice() || goods.isSold()) {
return rsp;
}
// Mark goods as sold
goods.markAsSold();
// Add case
int charId = this.getRandomCharId();
var potentialCase = this.createPotentialSelector(charId);
this.addCase(rsp.getMutableCases(), potentialCase);
// Remove items
var change = this.addItem(GameConstants.STAR_TOWER_GOLD_ITEM_ID, -goods.getPrice(), null);
// Set change info
rsp.setChange(change.toProto());
// Success
return rsp;
}
public StarTowerInteractResp settle(StarTowerInteractResp rsp) {
public StarTowerInteractResp settle(StarTowerInteractResp rsp, boolean isWin) {
// End game
this.getManager().endGame(true);
this.getManager().endGame(isWin);
// Settle info
var settle = rsp.getMutableSettle()
@@ -760,7 +537,9 @@ public class StarTowerGame {
settle.getMutableChange();
// Log victory
this.getManager().getPlayer().getProgress().addStarTowerLog(this.getId());
if (isWin) {
this.getManager().getPlayer().getProgress().addStarTowerLog(this.getId());
}
// Complete
return rsp;
@@ -781,12 +560,8 @@ public class StarTowerGame {
this.getChars().forEach(proto.getMutableMeta()::addChars);
this.getDiscs().forEach(proto.getMutableMeta()::addDiscs);
proto.getMutableRoom().setData(this.toRoomDataProto());
// Cases
for (var starTowerCase : this.getCases()) {
proto.getMutableRoom().addCases(starTowerCase.toProto());
}
// Set room data
proto.setRoom(this.getRoom().toProto());
// Set up bag
var bag = proto.getMutableBag();
@@ -817,23 +592,5 @@ public class StarTowerGame {
return proto;
}
public StarTowerRoomData toRoomDataProto() {
var proto = StarTowerRoomData.newInstance()
.setFloor(this.getStageFloor())
.setMapId(this.getMapId())
.setRoomType(this.getRoomType())
.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

@@ -205,7 +205,12 @@ 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().trigger(QuestCondition.TowerEnterFloor, 1);

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,47 @@
package emu.nebula.game.tower.cases;
import emu.nebula.game.tower.StarTowerGame;
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 void register(StarTowerBaseRoom room) {
this.game = room.getGame();
this.id = room.getNextCaseId();
}
public abstract CaseType getType();
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,121 @@
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 money
int money = this.getRoom().getStage().getInteriorCurrencyQuantity();
this.getGame().addItem(GameConstants.STAR_TOWER_GOLD_ITEM_ID, money, change);
// Handle pending potential selectors
var potentialCase = this.getGame().handlePendingPotentialSelectors();
if (potentialCase != null) {
// Create potential selector
this.getGame().addCase(rsp.getMutableCases(), potentialCase);
} else {
// Add door case here
this.getGame().createExit(rsp.getMutableCases());
}
// 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,82 @@
package emu.nebula.game.tower.cases;
import java.util.HashMap;
import java.util.Map;
import emu.nebula.GameConstants;
import emu.nebula.game.tower.StarTowerShopGoods;
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 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;
}
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 goods
var goods = this.getGoods().get(req.getHawkerReq().getSid());
// Make sure we have enough currency
int gold = this.getGame().getRes().get(GameConstants.STAR_TOWER_GOLD_ITEM_ID);
if (gold < goods.getPrice() || goods.isSold()) {
return rsp;
}
// Mark goods as sold
goods.markAsSold();
// Add case
this.getGame().addCase(rsp.getMutableCases(), this.getGame().createPotentialSelector());
// Remove items
var change = this.getGame().addItem(GameConstants.STAR_TOWER_GOLD_ITEM_ID, -goods.getPrice(), null);
// Set change info
rsp.setChange(change.toProto());
// Success
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
var hawker = proto.getMutableHawkerCase();
for (var entry : this.getGoods().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);
}
}
}

View File

@@ -0,0 +1,41 @@
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 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;
public StarTowerNpcEventCase(int npcId, int eventId) {
this.npcId = npcId;
this.eventId = eventId;
this.options = new IntArrayList();
}
@Override
public CaseType getType() {
return CaseType.NpcEvent;
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
proto.getMutableSelectOptionsEventCase()
.setEvtId(this.getEventId())
.setNPCId(this.getNpcId())
.addAllOptions(this.getOptions().toIntArray());
}
}

View File

@@ -0,0 +1,80 @@
package emu.nebula.game.tower.cases;
import emu.nebula.proto.PublicStarTower.PotentialInfo;
import emu.nebula.proto.PublicStarTower.StarTowerRoomCase;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractReq;
import emu.nebula.proto.StarTowerInteract.StarTowerInteractResp;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter;
@Getter
public class StarTowerPotentialCase extends StarTowerBaseCase {
private int teamLevel;
private IntList potentialIds;
public StarTowerPotentialCase(int teamLevel, IntList potentialIds) {
this.teamLevel = teamLevel;
this.potentialIds = potentialIds;
}
@Override
public CaseType getType() {
return CaseType.PotentialSelect;
}
public int selectId(int index) {
if (this.getPotentialIds() == null) {
return 0;
}
if (index < 0 || index >= this.getPotentialIds().size()) {
return 0;
}
return this.getPotentialIds().getInt(index);
}
@Override
public StarTowerInteractResp interact(StarTowerInteractReq req, StarTowerInteractResp rsp) {
var index = req.getMutableSelectReq().getIndex();
int id = this.selectId(index);
if (id <= 0) {
return rsp;
}
// Add item
var change = this.getGame().addItem(id, 1, null);
// Set change
rsp.setChange(change.toProto());
// Handle pending potential selectors
var potentialCase = this.getGame().handlePendingPotentialSelectors();
if (potentialCase != null) {
// Create potential selector
this.getGame().addCase(rsp.getMutableCases(), potentialCase);
} else {
// Add door case here
this.getGame().createExit(rsp.getMutableCases());
}
return rsp;
}
// Proto
@Override
public void encodeProto(StarTowerRoomCase proto) {
var select = proto.getMutableSelectPotentialCase()
.setTeamLevel(this.getTeamLevel());
for (int id : this.getPotentialIds()) {
var info = PotentialInfo.newInstance()
.setTid(id)
.setLevel(1);
select.addInfos(info);
}
}
}

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,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,132 @@
package emu.nebula.game.tower.room;
import java.util.ArrayList;
import java.util.List;
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.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 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 List<StarTowerBaseCase> cases;
public StarTowerBaseRoom(StarTowerGame game, StarTowerStageDef stage) {
this.game = game;
this.stage = stage;
this.cases = new ArrayList<>();
}
public int getType() {
return stage.getRoomType();
}
// 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().stream()
.filter(c -> c.getId() == id)
.findFirst()
.orElse(null);
}
public StarTowerBaseCase addCase(StarTowerBaseCase towerCase) {
return this.addCase(null, towerCase);
}
public StarTowerBaseCase addCase(RepeatedMessage<StarTowerRoomCase> cases, StarTowerBaseCase towerCase) {
// Add to cases list
this.getCases().add(towerCase);
// Set game for tower case
towerCase.register(this);
// Add case to proto
if (cases != null) {
cases.add(towerCase.toProto());
}
// Complete
return towerCase;
}
// Events
public void onEnter() {
// Create door case
this.getGame().createExit();
// Create sync hp case
this.getGame().addCase(new StarTowerSyncHPCase());
}
// Proto
public emu.nebula.proto.PublicStarTower.StarTowerRoom toProto() {
var proto = emu.nebula.proto.PublicStarTower.StarTowerRoom.newInstance()
.setData(this.getDataProto());
for (var towerCase : this.getCases()) {
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.getGame().addCase(new StarTowerBattleCase(this.getGame().getPendingSubNotes()));
// Create sync hp case
this.getGame().addCase(new StarTowerSyncHPCase());
}
}

View File

@@ -0,0 +1,24 @@
package emu.nebula.game.tower.room;
import emu.nebula.data.resources.StarTowerStageDef;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.cases.StarTowerSyncHPCase;
import lombok.Getter;
@Getter
public class StarTowerEventRoom extends StarTowerBaseRoom {
public StarTowerEventRoom(StarTowerGame game, StarTowerStageDef stage) {
super(game, stage);
}
@Override
public void onEnter() {
// Create door case
this.getGame().createExit();
// Create sync hp case
this.getGame().addCase(new StarTowerSyncHPCase());
}
}

View File

@@ -0,0 +1,35 @@
package emu.nebula.game.tower.room;
import emu.nebula.data.resources.StarTowerStageDef;
import emu.nebula.game.tower.StarTowerGame;
import emu.nebula.game.tower.StarTowerShopGoods;
import emu.nebula.game.tower.cases.StarTowerHawkerCase;
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
var hawker = new StarTowerHawkerCase();
// TODO
for (int i = 0; i < 8; i++) {
hawker.addGoods(new StarTowerShopGoods(1, 1, 200));
}
this.getGame().addCase(hawker);
// Create door case
this.getGame().createExit();
// Create sync hp case
this.getGame().addCase(new StarTowerSyncHPCase());
}
}

View File

@@ -20,7 +20,7 @@ public class HandlerStarTowerGiveUpReq extends NetHandler {
// Build response
var rsp = StarTowerGiveUpResp.newInstance()
.setBuild(game.getBuild().toProto())
.setFloor(game.getFloor());
.setFloor(game.getFloorCount());
rsp.getMutableChange();