From d20cffb90557e5c1e548130f9959349bc3583bc8 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sat, 7 May 2022 21:47:13 +0800 Subject: [PATCH 1/3] Monsters tide turn by turn && Ban User Skill && Lua functions --- proto/CanUseSkillNotify.proto | 15 ++ .../game/entity/EntityMonster.java | 1 + .../grasscutter/game/tower/TowerManager.java | 4 +- .../emu/grasscutter/game/world/Scene.java | 8 +- .../scripts/SceneScriptManager.java | 151 +++++++++++------- .../emu/grasscutter/scripts/ScriptLib.java | 123 +++++++++++--- .../grasscutter/scripts/data/SceneGroup.java | 12 +- .../packet/send/PacketCanUseSkillNotify.java | 19 +++ src/main/resources/logback.xml | 2 + 9 files changed, 247 insertions(+), 88 deletions(-) create mode 100644 proto/CanUseSkillNotify.proto create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java diff --git a/proto/CanUseSkillNotify.proto b/proto/CanUseSkillNotify.proto new file mode 100644 index 000000000..60ac6d7f0 --- /dev/null +++ b/proto/CanUseSkillNotify.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message CanUseSkillNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 1019; + } + + bool is_can_use_skill = 1; +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index c9d0c0982..0ae6f356b 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -117,6 +117,7 @@ public class EntityMonster extends GameEntity { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { + getScene().getScriptManager().onMonsterDie(); getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); } if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 51f840663..409549a1f 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -7,6 +7,7 @@ import emu.grasscutter.data.def.TowerLevelData; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; @@ -75,7 +76,8 @@ public class TowerManager { player.getScene().setPrevScenePoint(enterPointId); player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel)); - + // stop using skill + player.getSession().send(new PacketCanUseSkillNotify(false)); } public void notifyCurLevelRecordChange(){ diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 97099c9b9..82ce9139f 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -105,7 +105,13 @@ public class Scene { public GameEntity getEntityById(int id) { return this.entities.get(id); } - + + public GameEntity getEntityByConfigId(int configId) { + return this.entities.values().stream() + .filter(x -> x.getConfigId() == configId) + .findFirst() + .orElse(null); + } /** * @return the autoCloseTime */ diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 5f6a1b7e6..b8ba800a6 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -1,19 +1,14 @@ package emu.grasscutter.scripts; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; -import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.jse.CoerceJavaToLua; @@ -23,12 +18,8 @@ import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.WorldLevelData; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.constants.ScriptGadgetState; -import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.scripts.data.SceneBlock; import emu.grasscutter.scripts.data.SceneConfig; import emu.grasscutter.scripts.data.SceneGadget; @@ -56,7 +47,12 @@ public class SceneScriptManager { private final Int2ObjectOpenHashMap> triggers; private final Int2ObjectOpenHashMap regions; - + private SceneGroup currentGroup; + private AtomicInteger monsterAlive; + private AtomicInteger monsterTideCount; + private int monsterSceneLimit; + private ConcurrentLinkedQueue monsterOrders; + public SceneScriptManager(Scene scene) { this.scene = scene; this.scriptLib = new ScriptLib(this); @@ -222,7 +218,8 @@ public class SceneScriptManager { cs.eval(getBindings()); // Set - group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")); + group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")); group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); @@ -235,7 +232,7 @@ public class SceneScriptManager { // Add monsters to suite TODO optimize Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); - group.monsters.forEach(m -> map.put(m.config_id, m)); + group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m)); group.gadgets.forEach(m -> map.put(m.config_id, m)); for (SceneSuite suite : group.suites) { @@ -323,60 +320,92 @@ public class SceneScriptManager { } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { - spawnMonstersInGroup(group, group.getSuiteByIndex(suiteIndex)); + this.currentGroup = group; + this.monsterSceneLimit = 0; + var suite = group.getSuiteByIndex(suiteIndex); + if(suite == null){ + return; + } + suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); } public void spawnMonstersInGroup(SceneGroup group) { - spawnMonstersInGroup(group, null); + this.currentGroup = group; + this.monsterSceneLimit = 0; + group.monsters.values().forEach(mob -> spawnMonstersInGroup(group, mob)); } - - public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) { - List monsters = group.monsters; - - if (suite != null) { - monsters = suite.sceneMonsters; + public void spawnMonstersInGroup(SceneGroup group,Integer[] ordersConfigId, int tideCount, int sceneLimit) { + this.currentGroup = group; + this.monsterSceneLimit = sceneLimit; + this.monsterTideCount = new AtomicInteger(tideCount); + this.monsterAlive = new AtomicInteger(0); + this.monsterOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + + // add the last turn + group.monsters.keySet().stream() + .filter(i -> !this.monsterOrders.contains(i)) + .forEach(this.monsterOrders::add); + for (int i = 0; i < sceneLimit; i++) { + spawnMonstersInGroup(group, group.monsters.get(this.monsterOrders.poll())); + } + } + public void spawnMonstersInGroup(SceneGroup group, SceneMonster monster) { + if(monster == null){ + return; + } + if(this.monsterSceneLimit > 0){ + this.monsterTideCount.decrementAndGet(); + this.monsterAlive.incrementAndGet(); } - List toAdd = new ArrayList<>(); - - for (SceneMonster monster : monsters) { - MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); - - if (data == null) { - continue; - } - - // Calculate level - int level = monster.level; - - if (getScene().getDungeonData() != null) { - level = getScene().getDungeonData().getShowLevel(); - } else if (getScene().getWorld().getWorldLevel() > 0) { - WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); - - if (worldLevelData != null) { - level = worldLevelData.getMonsterLevel(); - } - } - - // Spawn mob - EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); - entity.getRotation().set(monster.rot); - entity.setGroupId(group.id); - entity.setConfigId(monster.config_id); - - toAdd.add(entity); + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return; } - - if (toAdd.size() > 0) { - getScene().addEntities(toAdd); - - for (GameEntity entity : toAdd) { - callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + + // Calculate level + int level = monster.level; + + if (getScene().getDungeonData() != null) { + level = getScene().getDungeonData().getShowLevel(); + } else if (getScene().getWorld().getWorldLevel() > 0) { + WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); + + if (worldLevelData != null) { + level = worldLevelData.getMonsterLevel(); + } + } + + // Spawn mob + EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); + entity.getRotation().set(monster.rot); + entity.setGroupId(group.id); + entity.setConfigId(monster.config_id); + + getScene().addEntity(entity); + + callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + } + + public void onMonsterDie(){ + if(this.monsterSceneLimit <= 0){ + return; + } + if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { + // maybe not happen + return; + } + if(this.monsterTideCount.get() > 0){ + // add more + spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); + }else if(this.monsterAlive.get() == 0){ + // spawn the last turn of monsters + while(!this.monsterOrders.isEmpty()){ + spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); } } } - // Events public void callEvent(int eventType, ScriptArgs params) { @@ -405,4 +434,8 @@ public class SceneScriptManager { } } } + +// public LuaValue safetyCall(){ +// +// } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 941b00b60..1b9badc11 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -1,28 +1,24 @@ package emu.grasscutter.scripts; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.luaj.vm2.LuaTable; -import org.luaj.vm2.LuaValue; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.SceneRegion; -import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; public class ScriptLib { + public static final Logger logger = LoggerFactory.getLogger(ScriptLib.class); private final SceneScriptManager sceneScriptManager; public ScriptLib(SceneScriptManager sceneScriptManager) { @@ -34,6 +30,8 @@ public class ScriptLib { } public int SetGadgetStateByConfigId(int configId, int gadgetState) { + logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", + configId,gadgetState); Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId).findFirst(); @@ -53,6 +51,8 @@ public class ScriptLib { } public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) { + logger.debug("[LUA] Call SetGroupGadgetStateByConfigId with {},{},{}", + groupId,configId,gadgetState); List list = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getGroupId() == groupId).toList(); @@ -71,6 +71,8 @@ public class ScriptLib { } public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) { + logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}", + groupId,configId,options); Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -90,6 +92,8 @@ public class ScriptLib { } public int DelWorktopOptionByGroupId(int groupId, int configId, int option) { + logger.debug("[LUA] Call DelWorktopOptionByGroupId with {},{},{}",groupId,configId,option); + Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -109,20 +113,24 @@ public class ScriptLib { } // Some fields are guessed - public int AutoMonsterTide(int challengeIndex, int groupId, int[] config_ids, int param4, int param5, int param6) { + public int AutoMonsterTide(int challengeIndex, int groupId, Integer[] ordersConfigId, int tideCount, int sceneLimit, int param6) { + logger.debug("[LUA] Call AutoMonsterTide with {},{},{},{},{},{}", + challengeIndex,groupId,ordersConfigId,tideCount,sceneLimit,param6); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); if (group == null || group.monsters == null) { return 1; } - - // TODO just spawn all from group for now - this.getSceneScriptManager().spawnMonstersInGroup(group); + + this.getSceneScriptManager().spawnMonstersInGroup(group, ordersConfigId, tideCount, sceneLimit); return 0; } public int AddExtraGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call AddExtraGroupSuite with {},{}", + groupId,suite); SceneGroup group = getSceneScriptManager().getGroupById(groupId); if (group == null || group.monsters == null) { @@ -136,8 +144,17 @@ public class ScriptLib { } // param3 (probably time limit for timed dungeons) - public int ActiveChallenge(int challengeId, int challengeIndex, int param3, int groupId, int objectiveKills, int param5) { + public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) { + logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}", + challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + var objective = objectiveKills; + + if(group == null){ + group = getSceneScriptManager().getGroupById(timeLimitOrGroupId); + objective = groupId; + } if (group == null || group.monsters == null) { return 1; @@ -146,7 +163,7 @@ public class ScriptLib { DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); challenge.setChallengeId(challengeId); challenge.setChallengeIndex(challengeIndex); - challenge.setObjective(objectiveKills); + challenge.setObjective(objective); getSceneScriptManager().getScene().setChallenge(challenge); @@ -155,26 +172,37 @@ public class ScriptLib { } public int GetGroupMonsterCountByGroupId(int groupId) { + logger.debug("[LUA] Call GetGroupMonsterCountByGroupId with {}", + groupId); return (int) getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e instanceof EntityMonster && e.getGroupId() == groupId) .count(); } public int GetGroupVariableValue(String var) { + logger.debug("[LUA] Call GetGroupVariableValue with {}", + var); return getSceneScriptManager().getVariables().getOrDefault(var, 0); } public int SetGroupVariableValue(String var, int value) { + logger.debug("[LUA] Call SetGroupVariableValue with {},{}", + var, value); getSceneScriptManager().getVariables().put(var, value); return 0; } public LuaValue ChangeGroupVariableValue(String var, int value) { + logger.debug("[LUA] Call ChangeGroupVariableValue with {},{}", + var, value); + getSceneScriptManager().getVariables().put(var, getSceneScriptManager().getVariables().get(var) + value); return LuaValue.ZERO; } public int RefreshGroup(LuaTable table) { + logger.debug("[LUA] Call RefreshGroup with {}", + table); // Kill and Respawn? int groupId = table.get("group_id").toint(); int suite = table.get("suite").toint(); @@ -192,6 +220,8 @@ public class ScriptLib { } public int GetRegionEntityCount(LuaTable table) { + logger.debug("[LUA] Call GetRegionEntityCount with {}", + table); int regionId = table.get("region_eid").toint(); int entityType = table.get("entity_type").toint(); @@ -205,21 +235,68 @@ public class ScriptLib { } public void PrintContextLog(String msg) { - Grasscutter.getLogger().info("[LUA] " + msg); + logger.info("[LUA] " + msg); } - public int TowerCountTimeStatus(int var1, int var2){ + public int TowerCountTimeStatus(int isDone, int var2){ + logger.debug("[LUA] Call TowerCountTimeStatus with {},{}", + isDone,var2); + // TODO record time return 0; } public int GetGroupMonsterCount(int var1){ - // Maybe... - return GetGroupMonsterCountByGroupId(var1); + logger.debug("[LUA] Call GetGroupMonsterCount with {}", + var1); + + return (int) getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster) + .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ + logger.debug("[LUA] Call SetMonsterBattleByGroup with {},{},{}", + var1,var2,var3); + return 0; } public int CauseDungeonFail(int var1){ + logger.debug("[LUA] Call CauseDungeonFail with {}", + var1); + return 0; } + // 8-1 + public int GetGroupVariableValueByGroup(int var1, String var2, int var3){ + logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}", + var1,var2,var3); + + //TODO + + return getSceneScriptManager().getVariables().getOrDefault(var2, 0); + } + + public int SetIsAllowUseSkill(int canUse, int var2){ + logger.debug("[LUA] Call SetIsAllowUseSkill with {},{}", + canUse,var2); + + getSceneScriptManager().getScene().broadcastPacket(new PacketCanUseSkillNotify(canUse == 1)); + return 0; + } + + public int KillEntityByConfigId(LuaTable table){ + logger.debug("[LUA] Call KillEntityByConfigId with {}", + table); + var configId = table.get("config_id"); + if(configId == LuaValue.NIL){ + return 1; + } + + var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint()); + if(entity == null){ + return 1; + } + getSceneScriptManager().getScene().killEntity(entity, 0); + return 0; + } + } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index a13db7b68..690cd3d0d 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -1,17 +1,21 @@ package emu.grasscutter.scripts.data; -import java.util.List; - import emu.grasscutter.utils.Position; +import java.util.List; +import java.util.Map; + public class SceneGroup { public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference public int id; public int refresh_id; public Position pos; - - public List monsters; + + /** + * ConfigId - Monster + */ + public Map monsters; public List gadgets; public List triggers; public List regions; diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java new file mode 100644 index 000000000..f8fe1314a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CanUseSkillNotifyOuterClass; + +public class PacketCanUseSkillNotify extends BasePacket { + + public PacketCanUseSkillNotify(boolean canUseSkill) { + super(PacketOpcodes.CanUseSkillNotify); + + CanUseSkillNotifyOuterClass.CanUseSkillNotify proto = CanUseSkillNotifyOuterClass.CanUseSkillNotify.newBuilder() + .setIsCanUseSkill(canUseSkill) + .build(); + + this.setData(proto); + } + +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 91d3f133c..1fc6831cb 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -19,4 +19,6 @@ + + \ No newline at end of file From 8a52a041bd3de7eaa213688c787bd2dc4ec8f5e1 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sun, 8 May 2022 17:11:02 +0800 Subject: [PATCH 2/3] Persist Tower Data && Set The Tower Schedule --- build.gradle | 2 +- data/TowerSchedule.json | 5 ++ .../command/commands/UnlockTowerCommand.java | 32 +++++++ .../java/emu/grasscutter/data/GameData.java | 4 + .../data/def/TowerScheduleData.java | 70 ++++++++++++++++ .../dungeons/TowerDungeonSettleListener.java | 16 ++-- .../game/tower/TowerLevelRecord.java | 64 ++++++++++++++ .../grasscutter/game/tower/TowerManager.java | 83 +++++++++++++++---- .../game/tower/TowerScheduleConfig.java | 35 ++++++++ .../game/tower/TowerScheduleManager.java | 75 +++++++++++++++++ .../scripts/SceneScriptManager.java | 29 ++++--- .../emu/grasscutter/scripts/ScriptLib.java | 32 ++++--- .../grasscutter/server/game/GameServer.java | 8 +- .../packet/recv/HandlerTowerAllDataReq.java | 5 +- .../send/PacketDungeonSettleNotify.java | 2 +- .../packet/send/PacketTowerAllDataRsp.java | 57 +++++++++---- .../PacketTowerFloorRecordChangeNotify.java | 6 +- .../send/PacketTowerLevelStarCondNotify.java | 32 +++++++ .../emu/grasscutter/utils/DateHelper.java | 6 +- src/main/resources/languages/en-US.json | 3 + 20 files changed, 500 insertions(+), 66 deletions(-) create mode 100644 data/TowerSchedule.json create mode 100644 src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java create mode 100644 src/main/java/emu/grasscutter/data/def/TowerScheduleData.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java diff --git a/build.gradle b/build.gradle index 8c9257777..2f19e9c99 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ dependencies { 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: 'org.reflections', name: 'reflections', version: '0.10.2' diff --git a/data/TowerSchedule.json b/data/TowerSchedule.json new file mode 100644 index 000000000..b93100645 --- /dev/null +++ b/data/TowerSchedule.json @@ -0,0 +1,5 @@ +{ + "scheduleId" : 1, + "scheduleStartTime" : "2022-05-01T00:00:00+08:00", + "nextScheduleChangeTime" : "2022-05-30T00:00:00+08:00" +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java new file mode 100644 index 000000000..e0fce695c --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java @@ -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 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 floors){ + floors.stream() + .filter(id -> !player.getTowerManager().getRecordMap().containsKey(id)) + .forEach(id -> player.getTowerManager().getRecordMap().put(id, new TowerLevelRecord(id))); + } +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 76a7f1652..fb3991ad7 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -70,6 +70,7 @@ public class GameData { private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerFloorDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerLevelDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap towerScheduleDataMap = new Int2ObjectOpenHashMap<>(); // Cache private static Map> fetters = new HashMap<>(); @@ -320,4 +321,7 @@ public class GameData { public static Int2ObjectMap getTowerLevelDataMap(){ return towerLevelDataMap; } + public static Int2ObjectMap getTowerScheduleDataMap(){ + return towerScheduleDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java b/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java new file mode 100644 index 000000000..017776c06 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java @@ -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 EntranceFloorId; + private List 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 getEntranceFloorId() { + return EntranceFloorId; + } + + public void setEntranceFloorId(List entranceFloorId) { + EntranceFloorId = entranceFloorId; + } + + public List getSchedules() { + return Schedules; + } + + public void setSchedules(List schedules) { + Schedules = schedules; + } + + public int getMonthlyLevelConfigId() { + return MonthlyLevelConfigId; + } + + public void setMonthlyLevelConfigId(int monthlyLevelConfigId) { + MonthlyLevelConfigId = monthlyLevelConfigId; + } + + public static class ScheduleDetail{ + private List FloorList; + + public List getFloorList() { + return FloorList; + } + + public void setFloorList(List floorList) { + FloorList = floorList; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java index 5b1ff7a30..c480d047f 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java @@ -12,12 +12,18 @@ public class TowerDungeonSettleListener implements DungeonSettleListener { scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); var towerManager = scene.getPlayers().get(0).getTowerManager(); - towerManager.notifyCurLevelRecordChangeWhenDone(); - scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(towerManager.getCurrentFloorId())); - scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge(), - true, + towerManager.notifyCurLevelRecordChangeWhenDone(3); + scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify( + towerManager.getCurrentFloorId(), + 3, + towerManager.canEnterScheduleFloor() + )); + + scene.broadcastPacket(new PacketDungeonSettleNotify( + scene.getChallenge(), + towerManager.hasNextFloor(), towerManager.hasNextLevel(), - towerManager.getNextFloorId() + towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId() )); } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java b/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java new file mode 100644 index 000000000..5a65f63ed --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java @@ -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 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 getPassedLevelMap() { + return passedLevelMap; + } + + public void setPassedLevelMap(Map passedLevelMap) { + this.passedLevelMap = passedLevelMap; + } + + public int getFloorStarRewardProgress() { + return floorStarRewardProgress; + } + + public void setFloorStarRewardProgress(int floorStarRewardProgress) { + this.floorStarRewardProgress = floorStarRewardProgress; + } + +} diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 409549a1f..9346ffead 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -9,10 +9,12 @@ import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; - 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.Map; @Entity public class TowerManager { @@ -26,11 +28,19 @@ public class TowerManager { this.player = player; } + /** + * the floor players chose + */ private int currentFloorId; private int currentLevel; @Transient private int currentLevelId; + /** + * floorId - Record + */ + private Map recordMap; + @Transient private int entryScene; @@ -38,7 +48,26 @@ public class TowerManager { return currentFloorId; } + public int getCurrentLevelId(){ + return this.currentLevelId + currentLevel; + } + + /** + * form 1-3 + */ + public int getCurrentLevel(){ + return currentLevel + 1; + } private static final List towerDungeonSettleListener = List.of(new TowerDungeonSettleListener()); + + public Map getRecordMap() { + if(recordMap == null){ + recordMap = new HashMap<>(); + recordMap.put(1001, new TowerLevelRecord(1001)); + } + return recordMap; + } + public void teamSelect(int floor, List> towerTeams) { var floorData = GameData.getTowerFloorDataMap().get(floor); @@ -54,51 +83,73 @@ public class TowerManager { entryScene = player.getSceneId(); } - player.getTeamManager().setupTemporaryTeam(towerTeams); } public void enterLevel(int enterPointId) { - var levelData = GameData.getTowerLevelDataMap().get(currentLevelId + currentLevel); + var levelData = GameData.getTowerLevelDataMap().get(getCurrentLevelId()); - this.currentLevel++; - var id = levelData.getDungeonId(); + var dungeonId = levelData.getDungeonId(); notifyCurLevelRecordChange(); // use team user choose player.getTeamManager().useTemporaryTeam(0); - player.getServer().getDungeonManager().handoffDungeon(player, id, + player.getServer().getDungeonManager().handoffDungeon(player, dungeonId, towerDungeonSettleListener); // make sure user can exit dungeon correctly player.getScene().setPrevScene(entryScene); player.getScene().setPrevScenePoint(enterPointId); - player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel)); + player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, getCurrentLevel())); // stop using skill player.getSession().send(new PacketCanUseSkillNotify(false)); + // notify the cond of stars + player.getSession().send(new PacketTowerLevelStarCondNotify(currentFloorId, getCurrentLevel())); } public void notifyCurLevelRecordChange(){ - player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel)); + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, getCurrentLevel())); } - public void notifyCurLevelRecordChangeWhenDone(){ - player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel + 1)); + public void notifyCurLevelRecordChangeWhenDone(int stars){ + 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(){ return this.currentLevel < 3; } - public int getNextFloorId() { - if(hasNextLevel()){ - return 0; - } - this.currentFloorId++; - return this.currentFloorId; + return player.getServer().getTowerScheduleManager().getNextFloorId(this.currentFloorId); + } + public boolean hasNextFloor(){ + return player.getServer().getTowerScheduleManager().getNextFloorId(this.currentFloorId) > 0; } public void clearEntry() { this.entryScene = 0; } + + public boolean canEnterScheduleFloor(){ + if(!recordMap.containsKey(player.getServer().getTowerScheduleManager().getLastEntranceFloor())){ + return false; + } + return recordMap.get(player.getServer().getTowerScheduleManager().getLastEntranceFloor()) + .getStarCount() >= 6; + } } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java new file mode 100644 index 000000000..35afbc7ba --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java @@ -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; + } +} diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java new file mode 100644 index 000000000..33f5c158d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java @@ -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 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 getBlocks() { return blocks; } @@ -237,16 +241,16 @@ public class SceneScriptManager { for (SceneSuite suite : group.suites) { suite.sceneMonsters = new ArrayList<>(suite.monsters.size()); - for (int id : suite.monsters) { - try { - SceneMonster monster = (SceneMonster) map.get(id); - if (monster != null) { - suite.sceneMonsters.add(monster); + suite.monsters.forEach(id -> { + Object objEntry = map.get(id.intValue()); + if (objEntry instanceof Map.Entry monsterEntry) { + Object monster = monsterEntry.getValue(); + if(monster instanceof SceneMonster sceneMonster){ + suite.sceneMonsters.add(sceneMonster); } - } catch (Exception e) { - continue; } - } + }); + suite.sceneGadgets = new ArrayList<>(suite.gadgets.size()); for (int id : suite.gadgets) { try { @@ -320,13 +324,15 @@ public class SceneScriptManager { } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { - this.currentGroup = group; - this.monsterSceneLimit = 0; var suite = group.getSuiteByIndex(suiteIndex); if(suite == null){ 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) { @@ -401,6 +407,7 @@ public class SceneScriptManager { spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); }else if(this.monsterAlive.get() == 0){ // spawn the last turn of monsters + //callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs()); while(!this.monsterOrders.isEmpty()){ spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 1b9badc11..1c4bbd0f2 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -118,7 +118,7 @@ public class ScriptLib { challengeIndex,groupId,ordersConfigId,tideCount,sceneLimit,param6); SceneGroup group = getSceneScriptManager().getGroupById(groupId); - + if (group == null || group.monsters == null) { return 1; } @@ -136,8 +136,7 @@ public class ScriptLib { if (group == null || group.monsters == null) { return 1; } - - // TODO just spawn all from group for now + this.getSceneScriptManager().spawnMonstersInGroup(group, suite); return 0; @@ -159,7 +158,13 @@ public class ScriptLib { if (group == null || group.monsters == null) { return 1; } - + + if(getSceneScriptManager().getScene().getChallenge() != null && + getSceneScriptManager().getScene().getChallenge().inProgress()) + { + return 0; + } + DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); challenge.setChallengeId(challengeId); challenge.setChallengeIndex(challengeIndex); @@ -249,7 +254,7 @@ public class ScriptLib { var1); return (int) getSceneScriptManager().getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityMonster) + .filter(e -> e instanceof EntityMonster && e.getGroupId() == getSceneScriptManager().getCurrentGroup().id) .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ @@ -266,13 +271,11 @@ public class ScriptLib { return 0; } // 8-1 - public int GetGroupVariableValueByGroup(int var1, String var2, int var3){ - logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}", - var1,var2,var3); + public int GetGroupVariableValueByGroup(String name, int groupId){ + logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{}", + name,groupId); - //TODO - - return getSceneScriptManager().getVariables().getOrDefault(var2, 0); + return getSceneScriptManager().getVariables().getOrDefault(name, 0); } public int SetIsAllowUseSkill(int canUse, int var2){ @@ -299,4 +302,11 @@ public class ScriptLib { return 0; } + public int SetGroupVariableValueByGroup(String key, int value, int groupId){ + logger.debug("[LUA] Call SetGroupVariableValueByGroup with {},{},{}", + key,value,groupId); + + return 0; + } + } diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 7ce8488ef..cb0e4965d 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -15,6 +15,7 @@ import emu.grasscutter.game.managers.InventoryManager; import emu.grasscutter.game.managers.MultiplayerManager; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.shop.ShopManager; +import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.game.world.World; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; @@ -54,6 +55,7 @@ public final class GameServer extends KcpServer { private final DropManager dropManager; private final CombineManger combineManger; + private final TowerScheduleManager towerScheduleManager; public GameServer() { this(new InetSocketAddress( @@ -82,7 +84,7 @@ public final class GameServer extends KcpServer { this.dropManager = new DropManager(this); this.expeditionManager = new ExpeditionManager(this); this.combineManger = new CombineManger(this); - + this.towerScheduleManager = new TowerScheduleManager(this); // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); } @@ -139,6 +141,10 @@ public final class GameServer extends KcpServer { return this.combineManger; } + public TowerScheduleManager getTowerScheduleManager() { + return towerScheduleManager; + } + public TaskMap getTaskMap() { return this.taskMap; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java index 2a9ef2004..38462882f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java @@ -11,7 +11,10 @@ public class HandlerTowerAllDataReq extends PacketHandler { @Override 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() + )); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java index 479029243..56d844d8d 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java @@ -46,7 +46,7 @@ public class PacketDungeonSettleNotify extends BasePacket { .setCount(1000) .build()) ; - if(nextFloorId > 0){ + if(nextFloorId > 0 && canJump){ towerLevelEndNotify.setNextFloorId(nextFloorId); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java index d2d2376e6..654aa4a07 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java @@ -1,37 +1,64 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.def.TowerFloorData; +import emu.grasscutter.game.tower.TowerManager; +import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.TowerAllDataRspOuterClass.TowerAllDataRsp; import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord; 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.IntStream; public class PacketTowerAllDataRsp extends BasePacket { - public PacketTowerAllDataRsp() { + public PacketTowerAllDataRsp(TowerScheduleManager towerScheduleManager, TowerManager towerManager) { super(PacketOpcodes.TowerAllDataRsp); - var list = GameData.getTowerFloorDataMap().values().stream() - .map(TowerFloorData::getFloorId) - .map(id -> TowerFloorRecord.newBuilder().setFloorId(id).build()) - .collect(Collectors.toList()); + var recordList = towerManager.getRecordMap().values().stream() + .map(rec -> TowerFloorRecord.newBuilder() + .setFloorId(rec.getFloorId()) + .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() - .setTowerScheduleId(29) - .addAllTowerFloorRecordList(list) + .setTowerScheduleId(towerScheduleManager.getCurrentTowerScheduleData().getScheduleId()) + .addAllTowerFloorRecordList(recordList) .setCurLevelRecord(TowerCurLevelRecord.newBuilder().setIsEmpty(true)) - .setNextScheduleChangeTime(Integer.MAX_VALUE) - .putFloorOpenTimeMap(1024, 1630486800) - .putFloorOpenTimeMap(1025, 1630486800) - .putFloorOpenTimeMap(1026, 1630486800) - .putFloorOpenTimeMap(1027, 1630486800) - .setScheduleStartTime(1630486800) + .setScheduleStartTime(DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig() + .getScheduleStartTime())) + .setNextScheduleChangeTime(DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig() + .getNextScheduleChangeTime())) + .putAllFloorOpenTimeMap(openTimeMap) + .setIsFinishedEntranceFloor(towerManager.canEnterScheduleFloor()) .build(); this.setData(proto); } + + private List buildFromPassedLevelMap(Map map){ + return map.entrySet().stream() + .map(item -> TowerLevelRecordOuterClass.TowerLevelRecord.newBuilder() + .setLevelId(item.getKey()) + .addAllSatisfiedCondList(IntStream.range(1, item.getValue() + 1).boxed().toList()) + .build()) + .toList(); + + } + } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java index c0ed414a8..5ab091901 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java @@ -8,13 +8,13 @@ import emu.grasscutter.net.proto.TowerLevelRecordOuterClass.TowerLevelRecord; public class PacketTowerFloorRecordChangeNotify extends BasePacket { - public PacketTowerFloorRecordChangeNotify(int floorId) { + public PacketTowerFloorRecordChangeNotify(int floorId, int stars, boolean canEnterScheduleFloor) { super(PacketOpcodes.TowerFloorRecordChangeNotify); TowerFloorRecordChangeNotify proto = TowerFloorRecordChangeNotify.newBuilder() .addTowerFloorRecordList(TowerFloorRecord.newBuilder() .setFloorId(floorId) - .setFloorStarRewardProgress(3) + .setFloorStarRewardProgress(stars) .addPassedLevelRecordList(TowerLevelRecord.newBuilder() .setLevelId(1) .addSatisfiedCondList(1) @@ -22,7 +22,7 @@ public class PacketTowerFloorRecordChangeNotify extends BasePacket { .addSatisfiedCondList(3) .build()) .build()) - .setIsFinishedEntranceFloor(true) + .setIsFinishedEntranceFloor(canEnterScheduleFloor) .build(); this.setData(proto); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java new file mode 100644 index 000000000..c2c301e4e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java @@ -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); + } +} diff --git a/src/main/java/emu/grasscutter/utils/DateHelper.java b/src/main/java/emu/grasscutter/utils/DateHelper.java index 7005d9457..1f1393760 100644 --- a/src/main/java/emu/grasscutter/utils/DateHelper.java +++ b/src/main/java/emu/grasscutter/utils/DateHelper.java @@ -1,7 +1,7 @@ package emu.grasscutter.utils; -import java.util.Date; import java.util.Calendar; +import java.util.Date; public final class DateHelper { public static Date onlyYearMonthDay(Date now) { @@ -13,4 +13,8 @@ public final class DateHelper { calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } + + public static int getUnixTime(Date localDateTime){ + return (int)(localDateTime.getTime() / 1000L); + } } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 48ffb5b0b..65a8634f7 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -280,6 +280,9 @@ "invalid_position": "Invalid position.", "success": "Teleported %s to %s, %s, %s in scene %s" }, + "tower": { + "unlock_done": "Abyss Corridor's Floors are all unlocked now." + }, "weather": { "usage": "Usage: weather [climateId]", "success": "Changed weather to %s with climate %s", From d00465125d831aa0ccc6c9161e730e6fc7dbb2c9 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Mon, 9 May 2022 15:39:49 +0800 Subject: [PATCH 3/3] Support Team Toggle in Tower & Refactor MonsterTide --- proto/TowerMiddleLevelChangeTeamNotify.proto | 14 ++ .../dungeons/TowerDungeonSettleListener.java | 4 + .../game/entity/EntityMonster.java | 4 +- .../grasscutter/game/tower/TowerManager.java | 11 +- .../game/tower/TowerScheduleManager.java | 5 +- .../scripts/SceneScriptManager.java | 182 ++++++++---------- .../emu/grasscutter/scripts/ScriptLib.java | 50 ++++- .../scripts/data/SceneTrigger.java | 14 ++ .../service/ScriptMonsterSpawnService.java | 73 +++++++ .../service/ScriptMonsterTideService.java | 74 +++++++ ...acketTowerMiddleLevelChangeTeamNotify.java | 18 ++ 11 files changed, 334 insertions(+), 115 deletions(-) create mode 100644 proto/TowerMiddleLevelChangeTeamNotify.proto create mode 100644 src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java create mode 100644 src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerMiddleLevelChangeTeamNotify.java diff --git a/proto/TowerMiddleLevelChangeTeamNotify.proto b/proto/TowerMiddleLevelChangeTeamNotify.proto new file mode 100644 index 000000000..35f2685ee --- /dev/null +++ b/proto/TowerMiddleLevelChangeTeamNotify.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message TowerMiddleLevelChangeTeamNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 2417; + } + +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java index c480d047f..3f212ce4a 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java @@ -9,6 +9,10 @@ public class TowerDungeonSettleListener implements DungeonSettleListener { @Override public void onDungeonSettle(Scene scene) { + if(scene.getScriptManager().getVariables().containsKey("stage") + && scene.getScriptManager().getVariables().get("stage") == 1){ + return; + } scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); var towerManager = scene.getPlayers().get(0).getTowerManager(); diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index 0ae6f356b..f8a96a808 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -117,7 +117,9 @@ public class EntityMonster extends GameEntity { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { - getScene().getScriptManager().onMonsterDie(); + if(getScene().getScriptManager().getScriptMonsterSpawnService() != null){ + getScene().getScriptManager().getScriptMonsterSpawnService().onMonsterDead(this); + } getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); } if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 9346ffead..cac848ea6 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -7,10 +7,7 @@ import emu.grasscutter.data.def.TowerLevelData; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; -import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; -import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; -import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; -import emu.grasscutter.server.packet.send.PacketTowerLevelStarCondNotify; +import emu.grasscutter.server.packet.send.*; import java.util.HashMap; import java.util.List; @@ -152,4 +149,10 @@ public class TowerManager { return recordMap.get(player.getServer().getTowerScheduleManager().getLastEntranceFloor()) .getStarCount() >= 6; } + + public void mirrorTeamSetUp(int teamId) { + // use team user choose + player.getTeamManager().useTemporaryTeam(teamId); + player.sendPacket(new PacketTowerMiddleLevelChangeTeamNotify()); + } } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java index 33f5c158d..952acd806 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java @@ -49,6 +49,7 @@ public class TowerScheduleManager { public int getNextFloorId(int floorId){ var entranceFloors = getCurrentTowerScheduleData().getEntranceFloorId(); + var scheduleFloors = getScheduleFloors(); var nextId = 0; // find in entrance floors first for(int i=0;i variables; - private Bindings bindings; private SceneConfig config; private List blocks; private boolean isInit; - - private final Int2ObjectOpenHashMap> triggers; + /** + * SceneTrigger Set + */ + private final Map triggers; + /** + * current triggers controlled by RefreshGroup + */ + private final Int2ObjectOpenHashMap> currentTriggers; private final Int2ObjectOpenHashMap regions; + private Map sceneGroups; private SceneGroup currentGroup; - private AtomicInteger monsterAlive; - private AtomicInteger monsterTideCount; - private int monsterSceneLimit; - private ConcurrentLinkedQueue monsterOrders; + private ScriptMonsterTideService scriptMonsterTideService; + private ScriptMonsterSpawnService scriptMonsterSpawnService; public SceneScriptManager(Scene scene) { this.scene = scene; this.scriptLib = new ScriptLib(this); this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib); - this.triggers = new Int2ObjectOpenHashMap<>(); + this.triggers = new HashMap<>(); + this.currentTriggers = new Int2ObjectOpenHashMap<>(); + this.regions = new Int2ObjectOpenHashMap<>(); this.variables = new HashMap<>(); - + this.sceneGroups = new HashMap<>(); + this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this); + // TEMPORARY if (this.getScene().getId() < 10) { return; @@ -103,17 +108,35 @@ public class SceneScriptManager { } public Set getTriggersByEvent(int eventId) { - return triggers.computeIfAbsent(eventId, e -> new HashSet<>()); + return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>()); } - public void registerTrigger(SceneTrigger trigger) { + this.triggers.put(trigger.name, trigger); getTriggersByEvent(trigger.event).add(trigger); } public void deregisterTrigger(SceneTrigger trigger) { + this.triggers.remove(trigger.name); getTriggersByEvent(trigger.event).remove(trigger); } - + public void resetTriggers(List triggerNames) { + for(var name : triggerNames){ + var instance = triggers.get(name); + this.currentTriggers.get(instance.event).clear(); + this.currentTriggers.get(instance.event).add(instance); + } + } + public void refreshGroup(SceneGroup group, int suiteIndex){ + var suite = group.getSuiteByIndex(suiteIndex); + if(suite == null){ + return; + } + if(suite.triggers.size() > 0){ + resetTriggers(suite.triggers); + } + spawnMonstersInGroup(group, suite); + spawnGadgetsInGroup(group, suite); + } public SceneRegion getRegionById(int id) { return regions.get(id); } @@ -263,6 +286,7 @@ public class SceneScriptManager { } } } + this.sceneGroups.put(group.id, group); } catch (ScriptException e) { Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e); } @@ -322,96 +346,36 @@ public class SceneScriptManager { this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId())); } } - + public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { var suite = group.getSuiteByIndex(suiteIndex); if(suite == null){ return; } - if(suite.sceneMonsters.size() > 0){ - this.currentGroup = group; - this.monsterSceneLimit = 0; - suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); + spawnMonstersInGroup(group, suite); + } + public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) { + if(suite == null || suite.sceneMonsters.size() <= 0){ + return; } + this.currentGroup = group; + suite.sceneMonsters.forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob)); } public void spawnMonstersInGroup(SceneGroup group) { this.currentGroup = group; - this.monsterSceneLimit = 0; - group.monsters.values().forEach(mob -> spawnMonstersInGroup(group, mob)); + group.monsters.values().forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob)); } - public void spawnMonstersInGroup(SceneGroup group,Integer[] ordersConfigId, int tideCount, int sceneLimit) { + + public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) { this.currentGroup = group; - this.monsterSceneLimit = sceneLimit; - this.monsterTideCount = new AtomicInteger(tideCount); - this.monsterAlive = new AtomicInteger(0); - this.monsterOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + this.scriptMonsterTideService = + new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId); - // add the last turn - group.monsters.keySet().stream() - .filter(i -> !this.monsterOrders.contains(i)) - .forEach(this.monsterOrders::add); - for (int i = 0; i < sceneLimit; i++) { - spawnMonstersInGroup(group, group.monsters.get(this.monsterOrders.poll())); - } } - public void spawnMonstersInGroup(SceneGroup group, SceneMonster monster) { - if(monster == null){ - return; - } - if(this.monsterSceneLimit > 0){ - this.monsterTideCount.decrementAndGet(); - this.monsterAlive.incrementAndGet(); - } - - MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); - - if (data == null) { - return; - } - - // Calculate level - int level = monster.level; - - if (getScene().getDungeonData() != null) { - level = getScene().getDungeonData().getShowLevel(); - } else if (getScene().getWorld().getWorldLevel() > 0) { - WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); - - if (worldLevelData != null) { - level = worldLevelData.getMonsterLevel(); - } - } - - // Spawn mob - EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); - entity.getRotation().set(monster.rot); - entity.setGroupId(group.id); - entity.setConfigId(monster.config_id); - - getScene().addEntity(entity); - - callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); - } - - public void onMonsterDie(){ - if(this.monsterSceneLimit <= 0){ - return; - } - if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { - // maybe not happen - return; - } - if(this.monsterTideCount.get() > 0){ - // add more - spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); - }else if(this.monsterAlive.get() == 0){ - // spawn the last turn of monsters - //callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs()); - while(!this.monsterOrders.isEmpty()){ - spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); - } - } + public void spawnMonstersByConfigId(int configId, int delayTime) { + // TODO delay + this.scriptMonsterSpawnService.spawnMonster(this.currentGroup.id, this.currentGroup.monsters.get(configId)); } // Events @@ -432,17 +396,35 @@ public class SceneScriptManager { args = CoerceJavaToLua.coerce(params); } - ret = condition.call(this.getScriptLibLua(), args); + ret = safetyCall(trigger.condition, condition, args); } - if (ret.checkboolean() == true) { + if (ret.isboolean() && ret.checkboolean()) { LuaValue action = (LuaValue) this.getBindings().get(trigger.action); - action.call(this.getScriptLibLua(), LuaValue.NIL); + var arg = new ScriptArgs(); + arg.param2 = 100; + var args = CoerceJavaToLua.coerce(arg); + safetyCall(trigger.action, action, args); } + //TODO some ret may not bool } } -// public LuaValue safetyCall(){ -// -// } + public LuaValue safetyCall(String name, LuaValue func, LuaValue args){ + try{ + return func.call(this.getScriptLibLua(), args); + }catch (LuaError error){ + ScriptLib.logger.error("[LUA] call trigger failed {},{}",name,args,error); + return LuaValue.valueOf(-1); + } + } + + public ScriptMonsterTideService getScriptMonsterTideService() { + return scriptMonsterTideService; + } + + public ScriptMonsterSpawnService getScriptMonsterSpawnService() { + return scriptMonsterSpawnService; + } + } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 1c4bbd0f2..0d686dd5a 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -28,7 +28,17 @@ public class ScriptLib { public SceneScriptManager getSceneScriptManager() { return sceneScriptManager; } - + + private String printTable(LuaTable table){ + StringBuilder sb = new StringBuilder(); + sb.append("{"); + for(var meta : table.keys()){ + sb.append(meta).append(":").append(table.get(meta)).append(","); + } + sb.append("}"); + return sb.toString(); + } + public int SetGadgetStateByConfigId(int configId, int gadgetState) { logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", configId,gadgetState); @@ -123,7 +133,7 @@ public class ScriptLib { return 1; } - this.getSceneScriptManager().spawnMonstersInGroup(group, ordersConfigId, tideCount, sceneLimit); + this.getSceneScriptManager().startMonsterTideInGroup(group, ordersConfigId, tideCount, sceneLimit); return 0; } @@ -204,10 +214,13 @@ public class ScriptLib { getSceneScriptManager().getVariables().put(var, getSceneScriptManager().getVariables().get(var) + value); return LuaValue.ZERO; } - + + /** + * Set the actions and triggers to designated group + */ public int RefreshGroup(LuaTable table) { logger.debug("[LUA] Call RefreshGroup with {}", - table); + printTable(table)); // Kill and Respawn? int groupId = table.get("group_id").toint(); int suite = table.get("suite").toint(); @@ -218,8 +231,7 @@ public class ScriptLib { return 1; } - this.getSceneScriptManager().spawnMonstersInGroup(group, suite); - this.getSceneScriptManager().spawnGadgetsInGroup(group, suite); + getSceneScriptManager().refreshGroup(group, suite); return 0; } @@ -260,7 +272,7 @@ public class ScriptLib { public int SetMonsterBattleByGroup(int var1, int var2, int var3){ logger.debug("[LUA] Call SetMonsterBattleByGroup with {},{},{}", var1,var2,var3); - + // TODO return 0; } @@ -270,7 +282,7 @@ public class ScriptLib { return 0; } - // 8-1 + public int GetGroupVariableValueByGroup(String name, int groupId){ logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{}", name,groupId); @@ -288,7 +300,7 @@ public class ScriptLib { public int KillEntityByConfigId(LuaTable table){ logger.debug("[LUA] Call KillEntityByConfigId with {}", - table); + printTable(table)); var configId = table.get("config_id"); if(configId == LuaValue.NIL){ return 1; @@ -306,6 +318,26 @@ public class ScriptLib { logger.debug("[LUA] Call SetGroupVariableValueByGroup with {},{},{}", key,value,groupId); + getSceneScriptManager().getVariables().put(key, value); + return 0; + } + + public int CreateMonster(LuaTable table){ + logger.debug("[LUA] Call CreateMonster with {}", + printTable(table)); + var configId = table.get("config_id").toint(); + var delayTime = table.get("delay_time").toint(); + + getSceneScriptManager().spawnMonstersByConfigId(configId, delayTime); + return 0; + } + + public int TowerMirrorTeamSetUp(int team, int var1) { + logger.debug("[LUA] Call TowerMirrorTeamSetUp with {},{}", + team,var1); + + getSceneScriptManager().getScene().getPlayers().get(0).getTowerManager().mirrorTeamSetUp(team-1); + return 0; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index a1603b1e6..a627f67c4 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -7,4 +7,18 @@ public class SceneTrigger { public String source; public String condition; public String action; + + @Override + public boolean equals(Object obj) { + if(obj instanceof SceneTrigger sceneTrigger){ + return this.name.equals(sceneTrigger.name); + } + return super.equals(obj); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + } diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java new file mode 100644 index 000000000..dda0d4732 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java @@ -0,0 +1,73 @@ +package emu.grasscutter.scripts.service; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.MonsterData; +import emu.grasscutter.data.def.WorldLevelData; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.scripts.SceneScriptManager; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneMonster; +import emu.grasscutter.scripts.data.ScriptArgs; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class ScriptMonsterSpawnService { + + private final SceneScriptManager sceneScriptManager; + private final List> onMonsterCreatedListener = new ArrayList<>(); + + private final List> onMonsterDeadListener = new ArrayList<>(); + + public ScriptMonsterSpawnService(SceneScriptManager sceneScriptManager){ + this.sceneScriptManager = sceneScriptManager; + } + + public void addMonsterCreatedListener(Consumer consumer){ + onMonsterCreatedListener.add(consumer); + } + public void addMonsterDeadListener(Consumer consumer){ + onMonsterCreatedListener.add(consumer); + } + + public void onMonsterDead(EntityMonster entityMonster){ + onMonsterCreatedListener.stream().forEach(l -> l.accept(entityMonster)); + } + public void spawnMonster(int groupId, SceneMonster monster) { + if(monster == null){ + return; + } + + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return; + } + + // Calculate level + int level = monster.level; + + if (sceneScriptManager.getScene().getDungeonData() != null) { + level = sceneScriptManager.getScene().getDungeonData().getShowLevel(); + } else if (sceneScriptManager.getScene().getWorld().getWorldLevel() > 0) { + WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(sceneScriptManager.getScene().getWorld().getWorldLevel()); + + if (worldLevelData != null) { + level = worldLevelData.getMonsterLevel(); + } + } + + // Spawn mob + EntityMonster entity = new EntityMonster(sceneScriptManager.getScene(), data, monster.pos, level); + entity.getRotation().set(monster.rot); + entity.setGroupId(groupId); + entity.setConfigId(monster.config_id); + + onMonsterCreatedListener.forEach(action -> action.accept(entity)); + + sceneScriptManager.getScene().addEntity(entity); + + sceneScriptManager.callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java new file mode 100644 index 000000000..117297f15 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -0,0 +1,74 @@ +package emu.grasscutter.scripts.service; + +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.scripts.SceneScriptManager; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.SceneMonster; +import emu.grasscutter.scripts.data.ScriptArgs; + +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public class ScriptMonsterTideService { + private final SceneScriptManager sceneScriptManager; + private final SceneGroup currentGroup; + private final AtomicInteger monsterAlive; + private final AtomicInteger monsterTideCount; + private final AtomicInteger monsterKillCount; + private final int monsterSceneLimit; + private final ConcurrentLinkedQueue monsterConfigOrders; + + public ScriptMonsterTideService(SceneScriptManager sceneScriptManager, + SceneGroup group, int tideCount, int monsterSceneLimit, Integer[] ordersConfigId){ + this.sceneScriptManager = sceneScriptManager; + this.currentGroup = group; + this.monsterSceneLimit = monsterSceneLimit; + this.monsterTideCount = new AtomicInteger(tideCount); + this.monsterKillCount = new AtomicInteger(0); + this.monsterAlive = new AtomicInteger(0); + this.monsterConfigOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + + this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterCreatedListener(this::onMonsterCreated); + this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(this::onMonsterDead); + // spawn the first turn + for (int i = 0; i < this.monsterSceneLimit; i++) { + this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(group.id, getNextMonster()); + } + } + + public void onMonsterCreated(EntityMonster entityMonster){ + if(this.monsterSceneLimit > 0){ + this.monsterTideCount.decrementAndGet(); + this.monsterAlive.incrementAndGet(); + } + } + + public SceneMonster getNextMonster(){ + var nextId = this.monsterConfigOrders.poll(); + if(currentGroup.monsters.containsKey(nextId)){ + return currentGroup.monsters.get(nextId); + } + // TODO some monster config_id do not exist in groups, so temporarily set it to the first + return currentGroup.monsters.values().stream().findFirst().orElse(null); + } + + public void onMonsterDead(EntityMonster entityMonster){ + if(this.monsterSceneLimit <= 0){ + return; + } + if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { + // maybe not happen + return; + } + this.monsterKillCount.incrementAndGet(); + if(this.monsterTideCount.get() > 0){ + // add more + this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(this.currentGroup.id, getNextMonster()); + }else if(this.monsterAlive.get() == 0){ + // spawn the last turn of monsters + this.sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(this.monsterKillCount.get())); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerMiddleLevelChangeTeamNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerMiddleLevelChangeTeamNotify.java new file mode 100644 index 000000000..f778c68aa --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerMiddleLevelChangeTeamNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerMiddleLevelChangeTeamNotifyOuterClass; + +public class PacketTowerMiddleLevelChangeTeamNotify extends BasePacket { + + public PacketTowerMiddleLevelChangeTeamNotify() { + super(PacketOpcodes.TowerMiddleLevelChangeTeamNotify); + + TowerMiddleLevelChangeTeamNotifyOuterClass.TowerMiddleLevelChangeTeamNotify proto = + TowerMiddleLevelChangeTeamNotifyOuterClass.TowerMiddleLevelChangeTeamNotify.newBuilder() + .build(); + + this.setData(proto); + } +}