mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-01-10 21:42:52 +08:00
Persist Tower Data && Set The Tower Schedule
This commit is contained in:
parent
39e8f810d2
commit
4b6842f006
@ -70,7 +70,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation group: 'io.netty', name: 'netty-all', version: '4.1.71.Final'
|
implementation group: 'io.netty', name: 'netty-all', version: '4.1.71.Final'
|
||||||
|
|
||||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.8'
|
implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0'
|
||||||
implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.18.2'
|
implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.18.2'
|
||||||
|
|
||||||
implementation group: 'org.reflections', name: 'reflections', version: '0.10.2'
|
implementation group: 'org.reflections', name: 'reflections', version: '0.10.2'
|
||||||
|
5
data/TowerSchedule.json
Normal file
5
data/TowerSchedule.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"scheduleId" : 1,
|
||||||
|
"scheduleStartTime" : "2022-05-01T00:00:00+08:00",
|
||||||
|
"nextScheduleChangeTime" : "2022-05-30T00:00:00+08:00"
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package emu.grasscutter.command.commands;
|
||||||
|
|
||||||
|
import emu.grasscutter.command.Command;
|
||||||
|
import emu.grasscutter.command.CommandHandler;
|
||||||
|
import emu.grasscutter.game.player.Player;
|
||||||
|
import emu.grasscutter.game.tower.TowerLevelRecord;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static emu.grasscutter.utils.Language.translate;
|
||||||
|
|
||||||
|
@Command(label = "unlocktower", usage = "unlocktower", aliases = {"ut"},
|
||||||
|
description = "Unlock all levels of tower", permission = "player.tower")
|
||||||
|
public class UnlockTowerCommand implements CommandHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
||||||
|
unlockFloor(sender, sender.getServer().getTowerScheduleManager()
|
||||||
|
.getCurrentTowerScheduleData().getEntranceFloorId());
|
||||||
|
|
||||||
|
unlockFloor(sender, sender.getServer().getTowerScheduleManager()
|
||||||
|
.getScheduleFloors());
|
||||||
|
|
||||||
|
CommandHandler.sendMessage(sender, translate("commands.tower.unlock_done"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockFloor(Player player, List<Integer> floors){
|
||||||
|
floors.stream()
|
||||||
|
.filter(id -> !player.getTowerManager().getRecordMap().containsKey(id))
|
||||||
|
.forEach(id -> player.getTowerManager().getRecordMap().put(id, new TowerLevelRecord(id)));
|
||||||
|
}
|
||||||
|
}
|
@ -70,6 +70,7 @@ public class GameData {
|
|||||||
private static final Int2ObjectMap<RewardPreviewData> rewardPreviewDataMap = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<RewardPreviewData> rewardPreviewDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
private static final Int2ObjectMap<TowerScheduleData> towerScheduleDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
|
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
|
||||||
@ -320,4 +321,7 @@ public class GameData {
|
|||||||
public static Int2ObjectMap<TowerLevelData> getTowerLevelDataMap(){
|
public static Int2ObjectMap<TowerLevelData> getTowerLevelDataMap(){
|
||||||
return towerLevelDataMap;
|
return towerLevelDataMap;
|
||||||
}
|
}
|
||||||
|
public static Int2ObjectMap<TowerScheduleData> getTowerScheduleDataMap(){
|
||||||
|
return towerScheduleDataMap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package emu.grasscutter.data.def;
|
||||||
|
|
||||||
|
import emu.grasscutter.data.GameResource;
|
||||||
|
import emu.grasscutter.data.ResourceType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ResourceType(name = "TowerScheduleExcelConfigData.json")
|
||||||
|
public class TowerScheduleData extends GameResource {
|
||||||
|
private int ScheduleId;
|
||||||
|
private List<Integer> EntranceFloorId;
|
||||||
|
private List<ScheduleDetail> Schedules;
|
||||||
|
private int MonthlyLevelConfigId;
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return ScheduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad() {
|
||||||
|
super.onLoad();
|
||||||
|
this.Schedules = this.Schedules.stream()
|
||||||
|
.filter(item -> item.getFloorList().size() > 0)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScheduleId() {
|
||||||
|
return ScheduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleId(int scheduleId) {
|
||||||
|
ScheduleId = scheduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getEntranceFloorId() {
|
||||||
|
return EntranceFloorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntranceFloorId(List<Integer> entranceFloorId) {
|
||||||
|
EntranceFloorId = entranceFloorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ScheduleDetail> getSchedules() {
|
||||||
|
return Schedules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchedules(List<ScheduleDetail> schedules) {
|
||||||
|
Schedules = schedules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMonthlyLevelConfigId() {
|
||||||
|
return MonthlyLevelConfigId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMonthlyLevelConfigId(int monthlyLevelConfigId) {
|
||||||
|
MonthlyLevelConfigId = monthlyLevelConfigId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ScheduleDetail{
|
||||||
|
private List<Integer> FloorList;
|
||||||
|
|
||||||
|
public List<Integer> getFloorList() {
|
||||||
|
return FloorList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFloorList(List<Integer> floorList) {
|
||||||
|
FloorList = floorList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,12 +12,18 @@ public class TowerDungeonSettleListener implements DungeonSettleListener {
|
|||||||
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
|
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
|
||||||
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
||||||
|
|
||||||
towerManager.notifyCurLevelRecordChangeWhenDone();
|
towerManager.notifyCurLevelRecordChangeWhenDone(3);
|
||||||
scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(towerManager.getCurrentFloorId()));
|
scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(
|
||||||
scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge(),
|
towerManager.getCurrentFloorId(),
|
||||||
true,
|
3,
|
||||||
|
towerManager.canEnterScheduleFloor()
|
||||||
|
));
|
||||||
|
|
||||||
|
scene.broadcastPacket(new PacketDungeonSettleNotify(
|
||||||
|
scene.getChallenge(),
|
||||||
|
towerManager.hasNextFloor(),
|
||||||
towerManager.hasNextLevel(),
|
towerManager.hasNextLevel(),
|
||||||
towerManager.getNextFloorId()
|
towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId()
|
||||||
));
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package emu.grasscutter.game.tower;
|
||||||
|
|
||||||
|
import dev.morphia.annotations.Entity;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class TowerLevelRecord {
|
||||||
|
/**
|
||||||
|
* floorId in config
|
||||||
|
*/
|
||||||
|
private int floorId;
|
||||||
|
/**
|
||||||
|
* LevelId - Stars
|
||||||
|
*/
|
||||||
|
private Map<Integer, Integer> passedLevelMap;
|
||||||
|
|
||||||
|
private int floorStarRewardProgress;
|
||||||
|
|
||||||
|
public TowerLevelRecord setLevelStars(int levelId, int stars){
|
||||||
|
passedLevelMap.put(levelId, stars);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStarCount() {
|
||||||
|
return passedLevelMap.values().stream().mapToInt(Integer::intValue).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TowerLevelRecord(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public TowerLevelRecord(int floorId){
|
||||||
|
this.floorId = floorId;
|
||||||
|
this.passedLevelMap = new HashMap<>();
|
||||||
|
this.floorStarRewardProgress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFloorId() {
|
||||||
|
return floorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFloorId(int floorId) {
|
||||||
|
this.floorId = floorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, Integer> getPassedLevelMap() {
|
||||||
|
return passedLevelMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassedLevelMap(Map<Integer, Integer> passedLevelMap) {
|
||||||
|
this.passedLevelMap = passedLevelMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFloorStarRewardProgress() {
|
||||||
|
return floorStarRewardProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFloorStarRewardProgress(int floorStarRewardProgress) {
|
||||||
|
this.floorStarRewardProgress = floorStarRewardProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,10 +9,12 @@ import emu.grasscutter.game.dungeons.TowerDungeonSettleListener;
|
|||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify;
|
import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify;
|
import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify;
|
||||||
|
|
||||||
import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp;
|
import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketTowerLevelStarCondNotify;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
public class TowerManager {
|
public class TowerManager {
|
||||||
@ -26,11 +28,19 @@ public class TowerManager {
|
|||||||
this.player = player;
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the floor players chose
|
||||||
|
*/
|
||||||
private int currentFloorId;
|
private int currentFloorId;
|
||||||
private int currentLevel;
|
private int currentLevel;
|
||||||
@Transient
|
@Transient
|
||||||
private int currentLevelId;
|
private int currentLevelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* floorId - Record
|
||||||
|
*/
|
||||||
|
private Map<Integer, TowerLevelRecord> recordMap;
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private int entryScene;
|
private int entryScene;
|
||||||
|
|
||||||
@ -38,7 +48,26 @@ public class TowerManager {
|
|||||||
return currentFloorId;
|
return currentFloorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCurrentLevelId(){
|
||||||
|
return this.currentLevelId + currentLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* form 1-3
|
||||||
|
*/
|
||||||
|
public int getCurrentLevel(){
|
||||||
|
return currentLevel + 1;
|
||||||
|
}
|
||||||
private static final List<DungeonSettleListener> towerDungeonSettleListener = List.of(new TowerDungeonSettleListener());
|
private static final List<DungeonSettleListener> towerDungeonSettleListener = List.of(new TowerDungeonSettleListener());
|
||||||
|
|
||||||
|
public Map<Integer, TowerLevelRecord> getRecordMap() {
|
||||||
|
if(recordMap == null){
|
||||||
|
recordMap = new HashMap<>();
|
||||||
|
recordMap.put(1001, new TowerLevelRecord(1001));
|
||||||
|
}
|
||||||
|
return recordMap;
|
||||||
|
}
|
||||||
|
|
||||||
public void teamSelect(int floor, List<List<Long>> towerTeams) {
|
public void teamSelect(int floor, List<List<Long>> towerTeams) {
|
||||||
var floorData = GameData.getTowerFloorDataMap().get(floor);
|
var floorData = GameData.getTowerFloorDataMap().get(floor);
|
||||||
|
|
||||||
@ -54,51 +83,73 @@ public class TowerManager {
|
|||||||
entryScene = player.getSceneId();
|
entryScene = player.getSceneId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
player.getTeamManager().setupTemporaryTeam(towerTeams);
|
player.getTeamManager().setupTemporaryTeam(towerTeams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void enterLevel(int enterPointId) {
|
public void enterLevel(int enterPointId) {
|
||||||
var levelData = GameData.getTowerLevelDataMap().get(currentLevelId + currentLevel);
|
var levelData = GameData.getTowerLevelDataMap().get(getCurrentLevelId());
|
||||||
|
|
||||||
this.currentLevel++;
|
var dungeonId = levelData.getDungeonId();
|
||||||
var id = levelData.getDungeonId();
|
|
||||||
|
|
||||||
notifyCurLevelRecordChange();
|
notifyCurLevelRecordChange();
|
||||||
// use team user choose
|
// use team user choose
|
||||||
player.getTeamManager().useTemporaryTeam(0);
|
player.getTeamManager().useTemporaryTeam(0);
|
||||||
player.getServer().getDungeonManager().handoffDungeon(player, id,
|
player.getServer().getDungeonManager().handoffDungeon(player, dungeonId,
|
||||||
towerDungeonSettleListener);
|
towerDungeonSettleListener);
|
||||||
|
|
||||||
// make sure user can exit dungeon correctly
|
// make sure user can exit dungeon correctly
|
||||||
player.getScene().setPrevScene(entryScene);
|
player.getScene().setPrevScene(entryScene);
|
||||||
player.getScene().setPrevScenePoint(enterPointId);
|
player.getScene().setPrevScenePoint(enterPointId);
|
||||||
|
|
||||||
player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel));
|
player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, getCurrentLevel()));
|
||||||
// stop using skill
|
// stop using skill
|
||||||
player.getSession().send(new PacketCanUseSkillNotify(false));
|
player.getSession().send(new PacketCanUseSkillNotify(false));
|
||||||
|
// notify the cond of stars
|
||||||
|
player.getSession().send(new PacketTowerLevelStarCondNotify(currentFloorId, getCurrentLevel()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyCurLevelRecordChange(){
|
public void notifyCurLevelRecordChange(){
|
||||||
player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel));
|
player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, getCurrentLevel()));
|
||||||
}
|
}
|
||||||
public void notifyCurLevelRecordChangeWhenDone(){
|
public void notifyCurLevelRecordChangeWhenDone(int stars){
|
||||||
player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel + 1));
|
if(!recordMap.containsKey(currentFloorId)){
|
||||||
|
recordMap.put(currentFloorId,
|
||||||
|
new TowerLevelRecord(currentFloorId).setLevelStars(getCurrentLevelId(),stars));
|
||||||
|
}else{
|
||||||
|
recordMap.put(currentFloorId,
|
||||||
|
recordMap.get(currentFloorId).setLevelStars(getCurrentLevelId(),stars));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentLevel++;
|
||||||
|
|
||||||
|
if(!hasNextLevel()){
|
||||||
|
// set up the next floor
|
||||||
|
recordMap.put(getNextFloorId(), new TowerLevelRecord(getNextFloorId()));
|
||||||
|
player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(getNextFloorId(), 1));
|
||||||
|
}else{
|
||||||
|
player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, getCurrentLevel()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public boolean hasNextLevel(){
|
public boolean hasNextLevel(){
|
||||||
return this.currentLevel < 3;
|
return this.currentLevel < 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNextFloorId() {
|
public int getNextFloorId() {
|
||||||
if(hasNextLevel()){
|
return player.getServer().getTowerScheduleManager().getNextFloorId(this.currentFloorId);
|
||||||
return 0;
|
}
|
||||||
}
|
public boolean hasNextFloor(){
|
||||||
this.currentFloorId++;
|
return player.getServer().getTowerScheduleManager().getNextFloorId(this.currentFloorId) > 0;
|
||||||
return this.currentFloorId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearEntry() {
|
public void clearEntry() {
|
||||||
this.entryScene = 0;
|
this.entryScene = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean canEnterScheduleFloor(){
|
||||||
|
if(!recordMap.containsKey(player.getServer().getTowerScheduleManager().getLastEntranceFloor())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return recordMap.get(player.getServer().getTowerScheduleManager().getLastEntranceFloor())
|
||||||
|
.getStarCount() >= 6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package emu.grasscutter.game.tower;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class TowerScheduleConfig {
|
||||||
|
private int scheduleId;
|
||||||
|
|
||||||
|
private Date scheduleStartTime;
|
||||||
|
private Date nextScheduleChangeTime;
|
||||||
|
|
||||||
|
|
||||||
|
public int getScheduleId() {
|
||||||
|
return scheduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleId(int scheduleId) {
|
||||||
|
this.scheduleId = scheduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getScheduleStartTime() {
|
||||||
|
return scheduleStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleStartTime(Date scheduleStartTime) {
|
||||||
|
this.scheduleStartTime = scheduleStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getNextScheduleChangeTime() {
|
||||||
|
return nextScheduleChangeTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextScheduleChangeTime(Date nextScheduleChangeTime) {
|
||||||
|
this.nextScheduleChangeTime = nextScheduleChangeTime;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package emu.grasscutter.game.tower;
|
||||||
|
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
|
import emu.grasscutter.data.GameData;
|
||||||
|
import emu.grasscutter.data.def.TowerScheduleData;
|
||||||
|
import emu.grasscutter.server.game.GameServer;
|
||||||
|
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TowerScheduleManager {
|
||||||
|
private final GameServer gameServer;
|
||||||
|
|
||||||
|
public GameServer getGameServer() {
|
||||||
|
return gameServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TowerScheduleManager(GameServer gameServer) {
|
||||||
|
this.gameServer = gameServer;
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TowerScheduleConfig towerScheduleConfig;
|
||||||
|
|
||||||
|
public synchronized void load(){
|
||||||
|
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "TowerSchedule.json")) {
|
||||||
|
towerScheduleConfig = Grasscutter.getGsonFactory().fromJson(fileReader, TowerScheduleConfig.class);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Grasscutter.getLogger().error("Unable to load tower schedule config.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TowerScheduleConfig getTowerScheduleConfig() {
|
||||||
|
return towerScheduleConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TowerScheduleData getCurrentTowerScheduleData(){
|
||||||
|
var data = GameData.getTowerScheduleDataMap().get(towerScheduleConfig.getScheduleId());
|
||||||
|
if(data == null){
|
||||||
|
Grasscutter.getLogger().error("Could not get current tower schedule data by config:{}", towerScheduleConfig);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getScheduleFloors() {
|
||||||
|
return getCurrentTowerScheduleData().getSchedules().get(0).getFloorList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNextFloorId(int floorId){
|
||||||
|
var entranceFloors = getCurrentTowerScheduleData().getEntranceFloorId();
|
||||||
|
var nextId = 0;
|
||||||
|
// find in entrance floors first
|
||||||
|
for(int i=0;i<entranceFloors.size()-1;i++){
|
||||||
|
if(floorId == entranceFloors.get(i)){
|
||||||
|
nextId = entranceFloors.get(i+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(nextId != 0){
|
||||||
|
return nextId;
|
||||||
|
}
|
||||||
|
var scheduleFloors = getScheduleFloors();
|
||||||
|
// find in schedule floors
|
||||||
|
for(int i=0;i<scheduleFloors.size()-1;i++){
|
||||||
|
if(floorId == scheduleFloors.get(i)){
|
||||||
|
nextId = scheduleFloors.get(i+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLastEntranceFloor() {
|
||||||
|
return getCurrentTowerScheduleData().getEntranceFloorId().get(getCurrentTowerScheduleData().getEntranceFloorId().size()-1);
|
||||||
|
}
|
||||||
|
}
|
@ -90,6 +90,10 @@ public class SceneScriptManager {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SceneGroup getCurrentGroup() {
|
||||||
|
return currentGroup;
|
||||||
|
}
|
||||||
|
|
||||||
public List<SceneBlock> getBlocks() {
|
public List<SceneBlock> getBlocks() {
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
@ -237,16 +241,16 @@ public class SceneScriptManager {
|
|||||||
|
|
||||||
for (SceneSuite suite : group.suites) {
|
for (SceneSuite suite : group.suites) {
|
||||||
suite.sceneMonsters = new ArrayList<>(suite.monsters.size());
|
suite.sceneMonsters = new ArrayList<>(suite.monsters.size());
|
||||||
for (int id : suite.monsters) {
|
suite.monsters.forEach(id -> {
|
||||||
try {
|
Object objEntry = map.get(id.intValue());
|
||||||
SceneMonster monster = (SceneMonster) map.get(id);
|
if (objEntry instanceof Map.Entry<?,?> monsterEntry) {
|
||||||
if (monster != null) {
|
Object monster = monsterEntry.getValue();
|
||||||
suite.sceneMonsters.add(monster);
|
if(monster instanceof SceneMonster sceneMonster){
|
||||||
|
suite.sceneMonsters.add(sceneMonster);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
suite.sceneGadgets = new ArrayList<>(suite.gadgets.size());
|
suite.sceneGadgets = new ArrayList<>(suite.gadgets.size());
|
||||||
for (int id : suite.gadgets) {
|
for (int id : suite.gadgets) {
|
||||||
try {
|
try {
|
||||||
@ -320,13 +324,15 @@ public class SceneScriptManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
|
public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
|
||||||
this.currentGroup = group;
|
|
||||||
this.monsterSceneLimit = 0;
|
|
||||||
var suite = group.getSuiteByIndex(suiteIndex);
|
var suite = group.getSuiteByIndex(suiteIndex);
|
||||||
if(suite == null){
|
if(suite == null){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob));
|
if(suite.sceneMonsters.size() > 0){
|
||||||
|
this.currentGroup = group;
|
||||||
|
this.monsterSceneLimit = 0;
|
||||||
|
suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void spawnMonstersInGroup(SceneGroup group) {
|
public void spawnMonstersInGroup(SceneGroup group) {
|
||||||
@ -401,6 +407,7 @@ public class SceneScriptManager {
|
|||||||
spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
|
spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
|
||||||
}else if(this.monsterAlive.get() == 0){
|
}else if(this.monsterAlive.get() == 0){
|
||||||
// spawn the last turn of monsters
|
// spawn the last turn of monsters
|
||||||
|
//callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs());
|
||||||
while(!this.monsterOrders.isEmpty()){
|
while(!this.monsterOrders.isEmpty()){
|
||||||
spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
|
spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,6 @@ public class ScriptLib {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO just spawn all from group for now
|
|
||||||
this.getSceneScriptManager().spawnMonstersInGroup(group, suite);
|
this.getSceneScriptManager().spawnMonstersInGroup(group, suite);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -160,6 +159,12 @@ public class ScriptLib {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(getSceneScriptManager().getScene().getChallenge() != null &&
|
||||||
|
getSceneScriptManager().getScene().getChallenge().inProgress())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group);
|
DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group);
|
||||||
challenge.setChallengeId(challengeId);
|
challenge.setChallengeId(challengeId);
|
||||||
challenge.setChallengeIndex(challengeIndex);
|
challenge.setChallengeIndex(challengeIndex);
|
||||||
@ -249,7 +254,7 @@ public class ScriptLib {
|
|||||||
var1);
|
var1);
|
||||||
|
|
||||||
return (int) getSceneScriptManager().getScene().getEntities().values().stream()
|
return (int) getSceneScriptManager().getScene().getEntities().values().stream()
|
||||||
.filter(e -> e instanceof EntityMonster)
|
.filter(e -> e instanceof EntityMonster && e.getGroupId() == getSceneScriptManager().getCurrentGroup().id)
|
||||||
.count();
|
.count();
|
||||||
}
|
}
|
||||||
public int SetMonsterBattleByGroup(int var1, int var2, int var3){
|
public int SetMonsterBattleByGroup(int var1, int var2, int var3){
|
||||||
@ -266,13 +271,11 @@ public class ScriptLib {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// 8-1
|
// 8-1
|
||||||
public int GetGroupVariableValueByGroup(int var1, String var2, int var3){
|
public int GetGroupVariableValueByGroup(String name, int groupId){
|
||||||
logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}",
|
logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{}",
|
||||||
var1,var2,var3);
|
name,groupId);
|
||||||
|
|
||||||
//TODO
|
return getSceneScriptManager().getVariables().getOrDefault(name, 0);
|
||||||
|
|
||||||
return getSceneScriptManager().getVariables().getOrDefault(var2, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int SetIsAllowUseSkill(int canUse, int var2){
|
public int SetIsAllowUseSkill(int canUse, int var2){
|
||||||
@ -299,4 +302,11 @@ public class ScriptLib {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int SetGroupVariableValueByGroup(String key, int value, int groupId){
|
||||||
|
logger.debug("[LUA] Call SetGroupVariableValueByGroup with {},{},{}",
|
||||||
|
key,value,groupId);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import emu.grasscutter.game.managers.InventoryManager;
|
|||||||
import emu.grasscutter.game.managers.MultiplayerManager;
|
import emu.grasscutter.game.managers.MultiplayerManager;
|
||||||
import emu.grasscutter.game.player.Player;
|
import emu.grasscutter.game.player.Player;
|
||||||
import emu.grasscutter.game.shop.ShopManager;
|
import emu.grasscutter.game.shop.ShopManager;
|
||||||
|
import emu.grasscutter.game.tower.TowerScheduleManager;
|
||||||
import emu.grasscutter.game.world.World;
|
import emu.grasscutter.game.world.World;
|
||||||
import emu.grasscutter.net.packet.PacketHandler;
|
import emu.grasscutter.net.packet.PacketHandler;
|
||||||
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
|
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
|
||||||
@ -54,6 +55,7 @@ public final class GameServer extends KcpServer {
|
|||||||
private final DropManager dropManager;
|
private final DropManager dropManager;
|
||||||
|
|
||||||
private final CombineManger combineManger;
|
private final CombineManger combineManger;
|
||||||
|
private final TowerScheduleManager towerScheduleManager;
|
||||||
|
|
||||||
public GameServer() {
|
public GameServer() {
|
||||||
this(new InetSocketAddress(
|
this(new InetSocketAddress(
|
||||||
@ -82,7 +84,7 @@ public final class GameServer extends KcpServer {
|
|||||||
this.dropManager = new DropManager(this);
|
this.dropManager = new DropManager(this);
|
||||||
this.expeditionManager = new ExpeditionManager(this);
|
this.expeditionManager = new ExpeditionManager(this);
|
||||||
this.combineManger = new CombineManger(this);
|
this.combineManger = new CombineManger(this);
|
||||||
|
this.towerScheduleManager = new TowerScheduleManager(this);
|
||||||
// Hook into shutdown event.
|
// Hook into shutdown event.
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
|
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
|
||||||
}
|
}
|
||||||
@ -139,6 +141,10 @@ public final class GameServer extends KcpServer {
|
|||||||
return this.combineManger;
|
return this.combineManger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TowerScheduleManager getTowerScheduleManager() {
|
||||||
|
return towerScheduleManager;
|
||||||
|
}
|
||||||
|
|
||||||
public TaskMap getTaskMap() {
|
public TaskMap getTaskMap() {
|
||||||
return this.taskMap;
|
return this.taskMap;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,10 @@ public class HandlerTowerAllDataReq extends PacketHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||||
session.send(new PacketTowerAllDataRsp());
|
session.send(new PacketTowerAllDataRsp(
|
||||||
|
session.getServer().getTowerScheduleManager(),
|
||||||
|
session.getPlayer().getTowerManager()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ public class PacketDungeonSettleNotify extends BasePacket {
|
|||||||
.setCount(1000)
|
.setCount(1000)
|
||||||
.build())
|
.build())
|
||||||
;
|
;
|
||||||
if(nextFloorId > 0){
|
if(nextFloorId > 0 && canJump){
|
||||||
towerLevelEndNotify.setNextFloorId(nextFloorId);
|
towerLevelEndNotify.setNextFloorId(nextFloorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +1,64 @@
|
|||||||
package emu.grasscutter.server.packet.send;
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
import emu.grasscutter.data.GameData;
|
import emu.grasscutter.game.tower.TowerManager;
|
||||||
import emu.grasscutter.data.def.TowerFloorData;
|
import emu.grasscutter.game.tower.TowerScheduleManager;
|
||||||
import emu.grasscutter.net.packet.BasePacket;
|
import emu.grasscutter.net.packet.BasePacket;
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
import emu.grasscutter.net.proto.TowerAllDataRspOuterClass.TowerAllDataRsp;
|
import emu.grasscutter.net.proto.TowerAllDataRspOuterClass.TowerAllDataRsp;
|
||||||
import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord;
|
import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord;
|
||||||
import emu.grasscutter.net.proto.TowerFloorRecordOuterClass.TowerFloorRecord;
|
import emu.grasscutter.net.proto.TowerFloorRecordOuterClass.TowerFloorRecord;
|
||||||
|
import emu.grasscutter.net.proto.TowerLevelRecordOuterClass;
|
||||||
|
import emu.grasscutter.utils.DateHelper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class PacketTowerAllDataRsp extends BasePacket {
|
public class PacketTowerAllDataRsp extends BasePacket {
|
||||||
|
|
||||||
public PacketTowerAllDataRsp() {
|
public PacketTowerAllDataRsp(TowerScheduleManager towerScheduleManager, TowerManager towerManager) {
|
||||||
super(PacketOpcodes.TowerAllDataRsp);
|
super(PacketOpcodes.TowerAllDataRsp);
|
||||||
|
|
||||||
var list = GameData.getTowerFloorDataMap().values().stream()
|
var recordList = towerManager.getRecordMap().values().stream()
|
||||||
.map(TowerFloorData::getFloorId)
|
.map(rec -> TowerFloorRecord.newBuilder()
|
||||||
.map(id -> TowerFloorRecord.newBuilder().setFloorId(id).build())
|
.setFloorId(rec.getFloorId())
|
||||||
.collect(Collectors.toList());
|
.setFloorStarRewardProgress(rec.getFloorStarRewardProgress())
|
||||||
|
.putAllPassedLevelMap(rec.getPassedLevelMap())
|
||||||
|
.addAllPassedLevelRecordList(buildFromPassedLevelMap(rec.getPassedLevelMap()))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
var openTimeMap = towerScheduleManager.getScheduleFloors().stream()
|
||||||
|
.collect(Collectors.toMap(x -> x,
|
||||||
|
y -> DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig()
|
||||||
|
.getScheduleStartTime()))
|
||||||
|
);
|
||||||
|
|
||||||
TowerAllDataRsp proto = TowerAllDataRsp.newBuilder()
|
TowerAllDataRsp proto = TowerAllDataRsp.newBuilder()
|
||||||
.setTowerScheduleId(29)
|
.setTowerScheduleId(towerScheduleManager.getCurrentTowerScheduleData().getScheduleId())
|
||||||
.addAllTowerFloorRecordList(list)
|
.addAllTowerFloorRecordList(recordList)
|
||||||
.setCurLevelRecord(TowerCurLevelRecord.newBuilder().setIsEmpty(true))
|
.setCurLevelRecord(TowerCurLevelRecord.newBuilder().setIsEmpty(true))
|
||||||
.setNextScheduleChangeTime(Integer.MAX_VALUE)
|
.setScheduleStartTime(DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig()
|
||||||
.putFloorOpenTimeMap(1024, 1630486800)
|
.getScheduleStartTime()))
|
||||||
.putFloorOpenTimeMap(1025, 1630486800)
|
.setNextScheduleChangeTime(DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig()
|
||||||
.putFloorOpenTimeMap(1026, 1630486800)
|
.getNextScheduleChangeTime()))
|
||||||
.putFloorOpenTimeMap(1027, 1630486800)
|
.putAllFloorOpenTimeMap(openTimeMap)
|
||||||
.setScheduleStartTime(1630486800)
|
.setIsFinishedEntranceFloor(towerManager.canEnterScheduleFloor())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
this.setData(proto);
|
this.setData(proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<TowerLevelRecordOuterClass.TowerLevelRecord> buildFromPassedLevelMap(Map<Integer, Integer> map){
|
||||||
|
return map.entrySet().stream()
|
||||||
|
.map(item -> TowerLevelRecordOuterClass.TowerLevelRecord.newBuilder()
|
||||||
|
.setLevelId(item.getKey())
|
||||||
|
.addAllSatisfiedCondList(IntStream.range(1, item.getValue() + 1).boxed().toList())
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,13 @@ import emu.grasscutter.net.proto.TowerLevelRecordOuterClass.TowerLevelRecord;
|
|||||||
|
|
||||||
public class PacketTowerFloorRecordChangeNotify extends BasePacket {
|
public class PacketTowerFloorRecordChangeNotify extends BasePacket {
|
||||||
|
|
||||||
public PacketTowerFloorRecordChangeNotify(int floorId) {
|
public PacketTowerFloorRecordChangeNotify(int floorId, int stars, boolean canEnterScheduleFloor) {
|
||||||
super(PacketOpcodes.TowerFloorRecordChangeNotify);
|
super(PacketOpcodes.TowerFloorRecordChangeNotify);
|
||||||
|
|
||||||
TowerFloorRecordChangeNotify proto = TowerFloorRecordChangeNotify.newBuilder()
|
TowerFloorRecordChangeNotify proto = TowerFloorRecordChangeNotify.newBuilder()
|
||||||
.addTowerFloorRecordList(TowerFloorRecord.newBuilder()
|
.addTowerFloorRecordList(TowerFloorRecord.newBuilder()
|
||||||
.setFloorId(floorId)
|
.setFloorId(floorId)
|
||||||
.setFloorStarRewardProgress(3)
|
.setFloorStarRewardProgress(stars)
|
||||||
.addPassedLevelRecordList(TowerLevelRecord.newBuilder()
|
.addPassedLevelRecordList(TowerLevelRecord.newBuilder()
|
||||||
.setLevelId(1)
|
.setLevelId(1)
|
||||||
.addSatisfiedCondList(1)
|
.addSatisfiedCondList(1)
|
||||||
@ -22,7 +22,7 @@ public class PacketTowerFloorRecordChangeNotify extends BasePacket {
|
|||||||
.addSatisfiedCondList(3)
|
.addSatisfiedCondList(3)
|
||||||
.build())
|
.build())
|
||||||
.build())
|
.build())
|
||||||
.setIsFinishedEntranceFloor(true)
|
.setIsFinishedEntranceFloor(canEnterScheduleFloor)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
this.setData(proto);
|
this.setData(proto);
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
|
import emu.grasscutter.net.packet.BasePacket;
|
||||||
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
|
import emu.grasscutter.net.proto.TowerLevelStarCondDataOuterClass.TowerLevelStarCondData;
|
||||||
|
import emu.grasscutter.net.proto.TowerLevelStarCondNotifyOuterClass.TowerLevelStarCondNotify;
|
||||||
|
|
||||||
|
public class PacketTowerLevelStarCondNotify extends BasePacket {
|
||||||
|
|
||||||
|
public PacketTowerLevelStarCondNotify(int floorId, int levelIndex) {
|
||||||
|
super(PacketOpcodes.TowerLevelStarCondNotify);
|
||||||
|
|
||||||
|
TowerLevelStarCondNotify proto = TowerLevelStarCondNotify.newBuilder()
|
||||||
|
.setFloorId(floorId)
|
||||||
|
.setLevelIndex(levelIndex)
|
||||||
|
.addCondDataList(TowerLevelStarCondData.newBuilder()
|
||||||
|
.setCondValue(1)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.addCondDataList(TowerLevelStarCondData.newBuilder()
|
||||||
|
.setCondValue(2)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.addCondDataList(TowerLevelStarCondData.newBuilder()
|
||||||
|
.setCondValue(3)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.setData(proto);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package emu.grasscutter.utils;
|
package emu.grasscutter.utils;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
public final class DateHelper {
|
public final class DateHelper {
|
||||||
public static Date onlyYearMonthDay(Date now) {
|
public static Date onlyYearMonthDay(Date now) {
|
||||||
@ -13,4 +13,8 @@ public final class DateHelper {
|
|||||||
calendar.set(Calendar.MILLISECOND, 0);
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
return calendar.getTime();
|
return calendar.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getUnixTime(Date localDateTime){
|
||||||
|
return (int)(localDateTime.getTime() / 1000L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,6 +280,9 @@
|
|||||||
"invalid_position": "Invalid position.",
|
"invalid_position": "Invalid position.",
|
||||||
"success": "Teleported %s to %s, %s, %s in scene %s"
|
"success": "Teleported %s to %s, %s, %s in scene %s"
|
||||||
},
|
},
|
||||||
|
"tower": {
|
||||||
|
"unlock_done": "Abyss Corridor's Floors are all unlocked now."
|
||||||
|
},
|
||||||
"weather": {
|
"weather": {
|
||||||
"usage": "Usage: weather <weatherId> [climateId]",
|
"usage": "Usage: weather <weatherId> [climateId]",
|
||||||
"success": "Changed weather to %s with climate %s",
|
"success": "Changed weather to %s with climate %s",
|
||||||
|
Loading…
Reference in New Issue
Block a user