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