diff --git a/build.gradle b/build.gradle index 900671e0e..630646f75 100644 --- a/build.gradle +++ b/build.gradle @@ -86,6 +86,9 @@ dependencies { implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1' + implementation group: 'com.esotericsoftware', name : 'reflectasm', version: '1.11.9' + implementation group: 'com.github.davidmoten', name : 'rtree-multi', version: '0.1' + protobuf files('proto/') compileOnly 'org.projectlombok:lombok:1.18.24' diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 9d81e6f6e..67c7e2945 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -24,8 +24,8 @@ public class GameData { private static final Map openConfigEntries = new HashMap<>(); private static final Map scenePointEntries = new HashMap<>(); private static final Int2ObjectMap mainQuestData = new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap homeworldDefaultSaveData = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap npcBornData = new Int2ObjectOpenHashMap<>(); // ExcelConfigs private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -83,12 +83,14 @@ public class GameData { private static final Int2ObjectMap shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap combineDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap gatherDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerFloorDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerScheduleDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap forgeDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap homeWorldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap furnitureMakeConfigDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap investigationMonsterDataMap = new Int2ObjectOpenHashMap<>(); // Cache private static Map> fetters = new HashMap<>(); @@ -140,9 +142,15 @@ public class GameData { public static Int2ObjectMap getMainQuestDataMap() { return mainQuestData; } + public static Int2ObjectMap getHomeworldDefaultSaveData() { return homeworldDefaultSaveData; } + + public static Int2ObjectMap getSceneNpcBornData() { + return npcBornData; + } + public static Int2ObjectMap getAvatarDataMap() { return avatarDataMap; } @@ -365,9 +373,11 @@ public class GameData { public static Int2ObjectMap getTowerFloorDataMap(){ return towerFloorDataMap; } + public static Int2ObjectMap getTowerLevelDataMap(){ return towerLevelDataMap; } + public static Int2ObjectMap getTowerScheduleDataMap(){ return towerScheduleDataMap; } @@ -379,10 +389,20 @@ public class GameData { public static Int2ObjectMap getForgeDataMap() { return forgeDataMap; } + public static Int2ObjectMap getHomeWorldLevelDataMap() { return homeWorldLevelDataMap; } + public static Int2ObjectMap getFurnitureMakeConfigDataMap() { return furnitureMakeConfigDataMap; } + + public static Int2ObjectMap getGatherDataMap() { + return gatherDataMap; + } + + public static Int2ObjectMap getInvestigationMonsterDataMap() { + return investigationMonsterDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 45987aef1..77c86ab76 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -10,6 +10,7 @@ import java.util.regex.Pattern; import com.google.gson.Gson; import emu.grasscutter.data.binout.*; +import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.utils.Utils; import lombok.SneakyThrows; import org.reflections.Reflections; @@ -64,10 +65,9 @@ public class ResourceLoader { loadQuests(); // Load scene points - must be done AFTER resources are loaded loadScenePoints(); - // Load default home layout loadHomeworldDefaultSaveData(); - + loadNpcBornData(); } public static void loadResources() { @@ -416,6 +416,27 @@ public class ResourceLoader { Grasscutter.getLogger().info("Loaded " + GameData.getHomeworldDefaultSaveData().size() + " HomeworldDefaultSaveDatas."); } + @SneakyThrows + private static void loadNpcBornData(){ + var folder = Files.list(Path.of(RESOURCE("BinOutput/Scene/SceneNpcBorn"))).toList(); + + for(var file : folder){ + if(file.toFile().isDirectory()){ + continue; + } + + var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), SceneNpcBornData.class); + if(data.getBornPosList() == null || data.getBornPosList().size() == 0){ + continue; + } + + data.setIndex(SceneIndexManager.buildIndex(3, data.getBornPosList(), item -> item.getPos().toPoint())); + GameData.getSceneNpcBornData().put(data.getSceneId(), data); + } + + Grasscutter.getLogger().info("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas."); + } + // BinOutput configs public static class AvatarConfig { diff --git a/src/main/java/emu/grasscutter/data/binout/SceneNpcBornData.java b/src/main/java/emu/grasscutter/data/binout/SceneNpcBornData.java new file mode 100644 index 000000000..968ef9a05 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/binout/SceneNpcBornData.java @@ -0,0 +1,29 @@ +package emu.grasscutter.data.custom; + +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import emu.grasscutter.scripts.data.SceneGroup; +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class SceneNpcBornData { + int sceneId; + List bornPosList; + + /** + * Spatial Index For NPC + */ + transient RTree index; + + /** + * npc groups + */ + transient Map groups = new ConcurrentHashMap<>(); +} diff --git a/src/main/java/emu/grasscutter/data/binout/SceneNpcBornEntry.java b/src/main/java/emu/grasscutter/data/binout/SceneNpcBornEntry.java new file mode 100644 index 000000000..1810c94bf --- /dev/null +++ b/src/main/java/emu/grasscutter/data/binout/SceneNpcBornEntry.java @@ -0,0 +1,19 @@ +package emu.grasscutter.data.custom; + +import emu.grasscutter.utils.Position; +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.util.List; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class SceneNpcBornEntry { + int id; + int configId; + Position pos; + Position rot; + int groupId; + List suiteIdList; +} diff --git a/src/main/java/emu/grasscutter/data/excels/GatherData.java b/src/main/java/emu/grasscutter/data/excels/GatherData.java new file mode 100644 index 000000000..c80442378 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/GatherData.java @@ -0,0 +1,49 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +@ResourceType(name = "GatherExcelConfigData.json") +public class GatherData extends GameResource { + private int PointType; + private int Id; + private int GadgetId; + private int ItemId; + private int Cd; // Probably hours + private boolean IsForbidGuest; + private boolean InitDisableInteract; + + @Override + public int getId() { + return this.PointType; + } + + public int getGatherId() { + return Id; + } + + public int getGadgetId() { + return GadgetId; + } + + public int getItemId() { + return ItemId; + } + + public int getCd() { + return Cd; + } + + public boolean isForbidGuest() { + return IsForbidGuest; + } + + public boolean initDisableInteract() { + return InitDisableInteract; + } + + @Override + public void onLoad() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/excels/InvestigationMonsterData.java b/src/main/java/emu/grasscutter/data/excels/InvestigationMonsterData.java new file mode 100644 index 000000000..bdfa720fb --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/InvestigationMonsterData.java @@ -0,0 +1,29 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import lombok.Data; + +import java.util.List; + +@ResourceType(name = "InvestigationMonsterConfigData.json") +@Data +public class InvestigationMonsterData extends GameResource { + private int Id; + private int CityId; + private List MonsterIdList; + private List GroupIdList; + private int RewardPreviewId; + private String MapMarkCreateType; + private String MonsterCategory; + + @Override + public int getId() { + return this.Id; + } + + @Override + public void onLoad() { + super.onLoad(); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java deleted file mode 100644 index ee3d18b3f..000000000 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java +++ /dev/null @@ -1,223 +0,0 @@ -package emu.grasscutter.game.dungeons; - -import emu.grasscutter.data.common.ItemParamData; -import emu.grasscutter.data.excels.DungeonData; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.inventory.ItemType; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.ScriptArgs; -import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; -import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; -import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; -import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; - -import java.util.ArrayList; -import java.util.List; - -public class DungeonChallenge { - private final Scene scene; - private final SceneGroup group; - - private int challengeIndex; - private int challengeId; - private boolean success; - private boolean progress; - /** - * has more challenge - */ - private boolean stage; - private int score; - private int objective = 0; - private IntSet rewardedPlayers; - - public DungeonChallenge(Scene scene, SceneGroup group, int challengeId, int challengeIndex, int objective) { - this.scene = scene; - this.group = group; - this.challengeId = challengeId; - this.challengeIndex = challengeIndex; - this.objective = objective; - this.setRewardedPlayers(new IntOpenHashSet()); - } - - public Scene getScene() { - return scene; - } - - public SceneGroup getGroup() { - return group; - } - - public int getChallengeIndex() { - return challengeIndex; - } - - public void setChallengeIndex(int challengeIndex) { - this.challengeIndex = challengeIndex; - } - - public int getChallengeId() { - return challengeId; - } - - public void setChallengeId(int challengeId) { - this.challengeId = challengeId; - } - - public int getObjective() { - return objective; - } - - public void setObjective(int objective) { - this.objective = objective; - } - - public boolean isSuccess() { - return success; - } - - public void setSuccess(boolean isSuccess) { - this.success = isSuccess; - } - - public boolean inProgress() { - return progress; - } - - public int getScore() { - return score; - } - - public boolean isStage() { - return stage; - } - - public void setStage(boolean stage) { - this.stage = stage; - } - - public int getTimeLimit() { - return 600; - } - - public IntSet getRewardedPlayers() { - return rewardedPlayers; - } - - public void setRewardedPlayers(IntSet rewardedPlayers) { - this.rewardedPlayers = rewardedPlayers; - } - - public void start() { - this.progress = true; - getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); - } - - public void finish() { - this.progress = false; - - getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); - - if (this.isSuccess()) { - // Call success script event - this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, null); - - // Settle - settle(); - } else { - this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_FAIL, null); - } - } - - private void settle() { - getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); - - if(!stage){ - getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0)); - } - } - - public void onMonsterDie(EntityMonster entity) { - score = getScore() + 1; - - getScene().broadcastPacket(new PacketChallengeDataNotify(this, 1, getScore())); - - if (getScore() >= getObjective() && this.progress) { - this.setSuccess(true); - finish(); - } - } - - private List rollRewards() { - List rewards = new ArrayList<>(); - - for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) { - rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); - } - - return rewards; - } - - public void getStatueDrops(Player player, GadgetInteractReq request) { - DungeonData dungeonData = getScene().getDungeonData(); - int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20; - - if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) { - return; - } - - // Already rewarded - if (getRewardedPlayers().contains(player.getUid())) { - return; - } - - // Get rewards. - List rewards = new ArrayList<>(); - - if (request.getIsUseCondenseResin()) { - // Check if condensed resin is usable here. - // For this, we use the following logic for now: - // The normal resin cost of the dungeon has to be 20. - if (resinCost != 20) { - return; - } - - // Make sure the player has condensed resin. - GameItem condensedResin = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(220007); - if (condensedResin == null || condensedResin.getCount() <= 0) { - return; - } - - // Deduct. - player.getInventory().removeItem(condensedResin, 1); - - // Roll rewards, twice (because condensed). - rewards.addAll(this.rollRewards()); - rewards.addAll(this.rollRewards()); - } - else { - // If the player used regular resin, try to deduct. - // Stop if insufficient resin. - boolean success = player.getResinManager().useResin(resinCost); - if (!success) { - return; - } - - // Roll rewards. - rewards.addAll(this.rollRewards()); - } - - // Add rewards to player and send notification. - player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); - player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); - - getRewardedPlayers().add(player.getUid()); - } -} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java new file mode 100644 index 000000000..f2a485f1d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/DungeonChallenge.java @@ -0,0 +1,91 @@ +package emu.grasscutter.game.dungeons.challenge; + +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.data.def.DungeonData; +import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +import java.util.ArrayList; +import java.util.List; + +public class DungeonChallenge extends WorldChallenge { + + /** + * has more challenge + */ + private boolean stage; + private IntSet rewardedPlayers; + + public DungeonChallenge(Scene scene, SceneGroup group, + int challengeId, int challengeIndex, + List paramList, + int timeLimit, int goal, + List challengeTriggers) { + super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers); + this.setRewardedPlayers(new IntOpenHashSet()); + } + + public boolean isStage() { + return stage; + } + + public void setStage(boolean stage) { + this.stage = stage; + } + + public IntSet getRewardedPlayers() { + return rewardedPlayers; + } + + public void setRewardedPlayers(IntSet rewardedPlayers) { + this.rewardedPlayers = rewardedPlayers; + } + + @Override + public void done() { + super.done(); + if (this.isSuccess()) { + // Settle + settle(); + } + } + + private void settle() { + if(!stage){ + getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); + getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, + new ScriptArgs(this.isSuccess() ? 1 : 0)); + } + } + + public void getStatueDrops(Player player) { + DungeonData dungeonData = getScene().getDungeonData(); + if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) { + return; + } + + // Already rewarded + if (getRewardedPlayers().contains(player.getUid())) { + return; + } + + List rewards = new ArrayList<>(); + for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) { + rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); + } + + player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); + player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); + + getRewardedPlayers().add(player.getUid()); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java new file mode 100644 index 000000000..106bd13e8 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java @@ -0,0 +1,132 @@ +package emu.grasscutter.game.dungeons.challenge; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +@Getter +@Setter +public class WorldChallenge { + private final Scene scene; + private final SceneGroup group; + private final int challengeId; + private final int challengeIndex; + private final List paramList; + private final int timeLimit; + private final List challengeTriggers; + private boolean progress; + private boolean success; + private long startedAt; + private int finishedTime; + private final int goal; + private final AtomicInteger score; + + public WorldChallenge(Scene scene, SceneGroup group, + int challengeId, int challengeIndex, List paramList, + int timeLimit, int goal, + List challengeTriggers){ + this.scene = scene; + this.group = group; + this.challengeId = challengeId; + this.challengeIndex = challengeIndex; + this.paramList = paramList; + this.timeLimit = timeLimit; + this.challengeTriggers = challengeTriggers; + this.goal = goal; + this.score = new AtomicInteger(0); + } + public boolean inProgress(){ + return this.progress; + } + public void onCheckTimeOut(){ + if(!inProgress()){ + return; + } + if(timeLimit <= 0){ + return; + } + challengeTriggers.forEach(t -> t.onCheckTimeout(this)); + } + public void start(){ + if(inProgress()){ + Grasscutter.getLogger().info("Could not start a in progress challenge."); + return; + } + this.progress = true; + this.startedAt = System.currentTimeMillis(); + getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); + challengeTriggers.forEach(t -> t.onBegin(this)); + } + + public void done(){ + if(!inProgress()){ + return; + } + finish(true); + this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, + // TODO record the time in PARAM2 and used in action + new ScriptArgs().setParam2(finishedTime)); + + challengeTriggers.forEach(t -> t.onFinish(this)); + } + + public void fail(){ + if(!inProgress()){ + return; + } + finish(false); + this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_FAIL, null); + challengeTriggers.forEach(t -> t.onFinish(this)); + } + + private void finish(boolean success){ + this.progress = false; + this.success = success; + this.finishedTime = (int)((System.currentTimeMillis() - this.startedAt) / 1000L); + getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); + } + + public int increaseScore(){ + return score.incrementAndGet(); + } + public void onMonsterDeath(EntityMonster monster){ + if(!inProgress()){ + return; + } + if(monster.getGroupId() != getGroup().id){ + return; + } + this.challengeTriggers.forEach(t -> t.onMonsterDeath(this, monster)); + } + public void onGadgetDeath(EntityGadget gadget){ + if(!inProgress()){ + return; + } + if(gadget.getGroupId() != getGroup().id){ + return; + } + this.challengeTriggers.forEach(t -> t.onGadgetDeath(this, gadget)); + } + + public void onGadgetDamage(EntityGadget gadget){ + if(!inProgress()){ + return; + } + if(gadget.getGroupId() != getGroup().id){ + return; + } + this.challengeTriggers.forEach(t -> t.onGadgetDamage(this, gadget)); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java new file mode 100644 index 000000000..fd1662de6 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java @@ -0,0 +1,30 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.ArrayList; +import java.util.List; + +public class ChallengeFactory { + + private static final List challengeFactoryHandlers = new ArrayList<>(); + + static { + challengeFactoryHandlers.add(new DungeonChallengeFactoryHandler()); + challengeFactoryHandlers.add(new DungeonGuardChallengeFactoryHandler()); + challengeFactoryHandlers.add(new KillGadgetChallengeFactoryHandler()); + challengeFactoryHandlers.add(new KillMonsterChallengeFactoryHandler()); + } + + public static WorldChallenge getChallenge(int param1, int param2, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group){ + for(var handler : challengeFactoryHandlers){ + if(!handler.isThisType(param1, param2, param3, param4, param5, param6, scene, group)){ + continue; + } + return handler.build(param1, param2, param3, param4, param5, param6, scene, group); + } + return null; + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java new file mode 100644 index 000000000..a91d55a28 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java @@ -0,0 +1,10 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +public interface ChallengeFactoryHandler { + boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group); + WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group); +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonChallengeFactoryHandler.java new file mode 100644 index 000000000..8357c9642 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonChallengeFactoryHandler.java @@ -0,0 +1,33 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.List; + +public class DungeonChallengeFactoryHandler implements ChallengeFactoryHandler{ + @Override + public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + // ActiveChallenge with 1,1000,300,233101003,15,0 + return scene.getSceneType() == SceneType.SCENE_DUNGEON + && param4 == group.id; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + var realGroup = scene.getScriptManager().getGroupById(param4); + return new DungeonChallenge( + scene, realGroup, + challengeId, // Id + challengeIndex, // Index + List.of(param5, param3), + param3, // Limit + param5, // Goal + List.of(new InTimeTrigger(), new KillMonsterTrigger())); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonGuardChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonGuardChallengeFactoryHandler.java new file mode 100644 index 000000000..3ea68f114 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/DungeonGuardChallengeFactoryHandler.java @@ -0,0 +1,34 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.List; + +public class DungeonGuardChallengeFactoryHandler implements ChallengeFactoryHandler{ + @Override + public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + // ActiveChallenge with 1,188,234101003,12,3030,0 + return scene.getSceneType() == SceneType.SCENE_DUNGEON + && param3 == group.id; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + var realGroup = scene.getScriptManager().getGroupById(param3); + return new DungeonChallenge( + scene, realGroup, + challengeId, // Id + challengeIndex, // Index + List.of(param4, 0), + 0, // Limit + param5, // Goal + List.of(new GuardTrigger())); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillGadgetChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillGadgetChallengeFactoryHandler.java new file mode 100644 index 000000000..4e075f2d3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillGadgetChallengeFactoryHandler.java @@ -0,0 +1,34 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.factory.ChallengeFactoryHandler; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillGadgetTrigger; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.List; + +public class KillGadgetChallengeFactoryHandler implements ChallengeFactoryHandler { + @Override + public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + // kill gadgets(explosive barrel) in time + // ActiveChallenge with 56,201,20,2,201,4 + // open chest in time + // ActiveChallenge with 666,202,30,7,202,1 + return challengeId == 201 || challengeId == 202; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + return new WorldChallenge( + scene, group, + challengeId, // Id + challengeIndex, // Index + List.of(param3, param6, 0), + param3, // Limit + param6, // Goal + List.of(new InTimeTrigger(), new KillGadgetTrigger()) + ); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterChallengeFactoryHandler.java new file mode 100644 index 000000000..b2285ee3e --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterChallengeFactoryHandler.java @@ -0,0 +1,31 @@ +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; + +import java.util.List; + +public class KillMonsterChallengeFactoryHandler implements ChallengeFactoryHandler{ + @Override + public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + // ActiveChallenge with 180,180,45,133108061,1,0 + return challengeId == 180; + } + + @Override + public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) { + var realGroup = scene.getScriptManager().getGroupById(param4); + return new WorldChallenge( + scene, realGroup, + challengeId, // Id + challengeIndex, // Index + List.of(param5, param3), + param3, // Limit + param5, // Goal + List.of(new KillMonsterTrigger(), new InTimeTrigger()) + ); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java new file mode 100644 index 000000000..b0b174f8d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java @@ -0,0 +1,14 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; + +public abstract class ChallengeTrigger { + public void onBegin(WorldChallenge challenge){} + public void onFinish(WorldChallenge challenge){} + public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster){} + public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget){} + public void onCheckTimeout(WorldChallenge challenge){} + public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget){} +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java new file mode 100644 index 000000000..4b9bc9e43 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java @@ -0,0 +1,27 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; + +public class GuardTrigger extends KillMonsterTrigger{ + @Override + public void onBegin(WorldChallenge challenge) { + super.onBegin(challenge); + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100)); + } + + @Override + public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) { + var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId()); + var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId()); + int percent = (int) (curHp / maxHp); + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent)); + + if(percent <= 0){ + challenge.fail(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java new file mode 100644 index 000000000..abb51c512 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java @@ -0,0 +1,13 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; + +public class InTimeTrigger extends ChallengeTrigger{ + @Override + public void onCheckTimeout(WorldChallenge challenge) { + var current = System.currentTimeMillis(); + if(current - challenge.getStartedAt() > challenge.getTimeLimit() * 1000L){ + challenge.fail(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillGadgetTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillGadgetTrigger.java new file mode 100644 index 000000000..87a1997d4 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillGadgetTrigger.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; + +public class KillGadgetTrigger extends ChallengeTrigger{ + @Override + public void onBegin(WorldChallenge challenge) { + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, challenge.getScore().get())); + } + + @Override + public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) { + var newScore = challenge.increaseScore(); + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, newScore)); + + if(newScore >= challenge.getGoal()){ + challenge.done(); + } + + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java new file mode 100644 index 000000000..dcde43049 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; + +public class KillMonsterTrigger extends ChallengeTrigger{ + @Override + public void onBegin(WorldChallenge challenge) { + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get())); + } + + @Override + public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { + var newScore = challenge.increaseScore(); + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, newScore)); + + if(newScore >= challenge.getGoal()){ + challenge.done(); + } + + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index 5eb9c8fab..a029ef3a3 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -1,16 +1,13 @@ package emu.grasscutter.game.entity; -import java.util.Arrays; -import java.util.List; - import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.GadgetData; +import emu.grasscutter.game.entity.gadget.*; import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityType; +import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.world.Scene; -import emu.grasscutter.game.world.World; -import emu.grasscutter.net.proto.ClientGadgetInfoOuterClass; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; @@ -23,15 +20,17 @@ import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.VectorOuterClass.Vector; -import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGadget; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; +import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.ToString; +@ToString(callSuper = true) public class EntityGadget extends EntityBaseGadget { private final GadgetData data; private final Position pos; @@ -39,7 +38,9 @@ public class EntityGadget extends EntityBaseGadget { private int gadgetId; private int state; - private IntSet worktopOptions; + private int pointType; + private GadgetContent content; + private SceneGadget metaGadget; public EntityGadget(Scene scene, int gadgetId, Position pos) { super(scene); @@ -50,19 +51,22 @@ public class EntityGadget extends EntityBaseGadget { this.rot = new Position(); } + public EntityGadget(Scene scene, int gadgetId, Position pos, GadgetContent content) { + this(scene, gadgetId, pos); + this.content = content; + } + public GadgetData getGadgetData() { return data; } @Override public Position getPosition() { - // TODO Auto-generated method stub return this.pos; } @Override public Position getRotation() { - // TODO Auto-generated method stub return this.rot; } @@ -81,34 +85,73 @@ public class EntityGadget extends EntityBaseGadget { public void setState(int state) { this.state = state; } + + public void updateState(int state){ + this.setState(state); + this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state)); + getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(state, this.getConfigId())); + } - public IntSet getWorktopOptions() { - return worktopOptions; + public int getPointType() { + return pointType; } - - public void addWorktopOptions(int[] options) { - if (this.worktopOptions == null) { - this.worktopOptions = new IntOpenHashSet(); - } - Arrays.stream(options).forEach(this.worktopOptions::add); + + public void setPointType(int pointType) { + this.pointType = pointType; } - - public void removeWorktopOption(int option) { - if (this.worktopOptions == null) { + + public GadgetContent getContent() { + return content; + } + + @Deprecated // Dont use! + public void setContent(GadgetContent content) { + this.content = this.content == null ? content : this.content; + } + + public SceneGadget getMetaGadget() { + return metaGadget; + } + + public void setMetaGadget(SceneGadget metaGadget) { + this.metaGadget = metaGadget; + } + + // TODO refactor + public void buildContent() { + if (getContent() != null || getGadgetData() == null || getGadgetData().getType() == null) { return; } - this.worktopOptions.remove(option); + + EntityType type = getGadgetData().getType(); + GadgetContent content = switch (type) { + case GatherPoint -> new GadgetGatherPoint(this); + case Worktop -> new GadgetWorktop(this); + case RewardStatue -> new GadgetRewardStatue(this); + case Chest -> new GadgetChest(this); + default -> null; + }; + + this.content = content; } @Override public Int2FloatOpenHashMap getFightProperties() { - // TODO Auto-generated method stub return null; } + + @Override + public void onCreate() { + // Lua event + getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId())); + } @Override public void onDeath(int killerId) { - + if(getScene().getChallenge() != null){ + getScene().getChallenge().onGadgetDeath(this); + } + getScene().getScriptManager().callEvent(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId())); } @Override @@ -143,15 +186,16 @@ public class EntityGadget extends EntityBaseGadget { .setIsEnableInteract(true) .setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); - if (this.getGadgetData().getType() == EntityType.Worktop && this.getWorktopOptions() != null) { - WorktopInfo worktop = WorktopInfo.newBuilder() - .addAllOptionList(this.getWorktopOptions()) - .build(); - gadgetInfo.setWorktop(worktop); + if (this.getContent() != null) { + this.getContent().onBuildProto(gadgetInfo); } entityInfo.setGadget(gadgetInfo); return entityInfo.build(); } + public void die() { + getScene().broadcastPacket(new PacketLifeStateChangeNotify(this, LifeState.LIFE_DEAD)); + this.onDeath(0); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index bd9785497..99ab3ea0e 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -4,13 +4,11 @@ import emu.grasscutter.data.GameData; import emu.grasscutter.data.common.PropGrowCurve; import emu.grasscutter.data.excels.MonsterCurveData; import emu.grasscutter.data.excels.MonsterData; -import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.world.Scene; -import emu.grasscutter.game.world.World; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; @@ -25,6 +23,7 @@ import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo; import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo; import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatMap; @@ -111,6 +110,12 @@ public class EntityMonster extends GameEntity { public void setPoseId(int poseId) { this.poseId = poseId; } + + @Override + public void onCreate() { + // Lua event + getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId())); + } @Override public void damage(float amount, int killerId) { @@ -135,8 +140,8 @@ public class EntityMonster extends GameEntity { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } // first set the challenge data - if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { - getScene().getChallenge().onMonsterDie(this); + if (getScene().getChallenge() != null) { + getScene().getChallenge().onMonsterDeath(this); } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { if(getScene().getScriptManager().getScriptMonsterSpawnService() != null){ @@ -144,7 +149,9 @@ public class EntityMonster extends GameEntity { } // prevent spawn monster after success if(getScene().getChallenge() != null && getScene().getChallenge().inProgress()){ - getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); + getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId())); + }else if(getScene().getChallenge() == null){ + getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId())); } } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityNPC.java b/src/main/java/emu/grasscutter/game/entity/EntityNPC.java new file mode 100644 index 000000000..213951ed3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityNPC.java @@ -0,0 +1,81 @@ +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.*; +import emu.grasscutter.scripts.data.SceneNPC; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; + +public class EntityNPC extends GameEntity{ + + private final Position position; + private final Position rotation; + private final SceneNPC metaNpc; + private final int suiteId; + + public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) { + super(scene); + this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC); + setConfigId(metaNPC.config_id); + setGroupId(metaNPC.group.id); + setBlockId(blockId); + this.suiteId = suiteId; + this.position = metaNPC.pos.clone(); + this.rotation = metaNPC.rot.clone(); + this.metaNpc = metaNPC; + + } + + @Override + public Int2FloatOpenHashMap getFightProperties() { + return null; + } + + @Override + public Position getPosition() { + return position; + } + + @Override + public Position getRotation() { + return rotation; + } + + public int getSuiteId() { + return suiteId; + } + + @Override + public SceneEntityInfoOuterClass.SceneEntityInfo toProto() { + + EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority = EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder() + .setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder()) + .setRendererChangedInfo(EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder()) + .setAiInfo(SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder() + .setIsAiOpen(true) + .setBornPos(getPosition().toProto())) + .setBornPos(getPosition().toProto()) + .build(); + + SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo = SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder() + .setEntityId(getId()) + .setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_NPC) + .setMotionInfo(MotionInfoOuterClass.MotionInfo.newBuilder() + .setPos(getPosition().toProto()) + .setRot(getRotation().toProto()) + .setSpeed(VectorOuterClass.Vector.newBuilder())) + .addAnimatorParaList(AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair.newBuilder()) + .setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder()) + .setEntityAuthorityInfo(authority) + .setLifeState(1); + + + entityInfo.setNpc(SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder() + .setNpcId(metaNpc.npc_id) + .setBlockId(getBlockId()) + .build()); + + return entityInfo.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/GameEntity.java b/src/main/java/emu/grasscutter/game/entity/GameEntity.java index b9ca01aea..b3c7afba1 100644 --- a/src/main/java/emu/grasscutter/game/entity/GameEntity.java +++ b/src/main/java/emu/grasscutter/game/entity/GameEntity.java @@ -107,10 +107,6 @@ public abstract class GameEntity { public void setLastMoveReliableSeq(int lastMoveReliableSeq) { this.lastMoveReliableSeq = lastMoveReliableSeq; } - - public abstract SceneEntityInfo toProto(); - - public abstract void onDeath(int killerId); public void setFightProperty(FightProperty prop, float value) { this.getFightProperties().put(prop.getId(), value); @@ -219,4 +215,21 @@ public abstract class GameEntity { getScene().killEntity(this, killerId); } } + + /** + * Called when this entity is added to the world + */ + public void onCreate() { + + } + + /** + * Called when this entity dies + * @param killerId Entity id of the entity that killed this entity + */ + public void onDeath(int killerId) { + + } + + public abstract SceneEntityInfo toProto(); } diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java new file mode 100644 index 000000000..6e2cdf72b --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetChest.java @@ -0,0 +1,65 @@ +package emu.grasscutter.game.entity.gadget; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo; +import emu.grasscutter.net.proto.InterOpTypeOuterClass; +import emu.grasscutter.net.proto.InteractTypeOuterClass; +import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.scripts.constants.ScriptGadgetState; +import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; + +import static emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType.INTER_OP_START; + +public class GadgetChest extends GadgetContent { + + public GadgetChest(EntityGadget gadget) { + super(gadget); + } + + public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) { + var chestInteractHandlerMap = getGadget().getScene().getWorld().getServer().getWorldDataManager().getChestInteractHandlerMap(); + var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName()); + if(handler == null){ + Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", getGadget().getGadgetData().getJsonName()); + return false; + } + + if(opType == INTER_OP_START && handler.isTwoStep()){ + player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_OPEN_CHEST, INTER_OP_START)); + return false; + }else{ + var success = handler.onInteract(this, player); + if (!success){ + return false; + } + + getGadget().updateState(ScriptGadgetState.ChestOpened); + player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_OPEN_CHEST)); + // let the chest disappear + getGadget().die(); + return true; + } + } + + public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { + if(getGadget().getMetaGadget() == null){ + return; + } + + var bossChest = getGadget().getMetaGadget().boss_chest; + if(bossChest != null){ + var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList(); + + gadgetInfo.setBossChest(BossChestInfo.newBuilder() + .setMonsterConfigId(bossChest.monster_config_id) + .setResin(bossChest.resin) + .addAllQualifyUidList(players) + .addAllRemainUidList(players) + .build()); + } + + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetContent.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetContent.java new file mode 100644 index 000000000..cba539df7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetContent.java @@ -0,0 +1,22 @@ +package emu.grasscutter.game.entity.gadget; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.InterOpTypeOuterClass; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; + +public abstract class GadgetContent { + private final EntityGadget gadget; + + public GadgetContent(EntityGadget gadget) { + this.gadget = gadget; + } + + public EntityGadget getGadget() { + return gadget; + } + + public abstract boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType); + + public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo); +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetGatherPoint.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetGatherPoint.java new file mode 100644 index 000000000..9a6074c0e --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetGatherPoint.java @@ -0,0 +1,45 @@ +package emu.grasscutter.game.entity.gadget; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.GatherData; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo; +import emu.grasscutter.net.proto.InterOpTypeOuterClass; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; + +public class GadgetGatherPoint extends GadgetContent { + private GatherData gatherData; + + public GadgetGatherPoint(EntityGadget gadget) { + super(gadget); + this.gatherData = GameData.getGatherDataMap().get(gadget.getPointType()); + } + + public GatherData getGatherData() { + return gatherData; + } + + public int getItemId() { + return getGatherData().getItemId(); + } + + public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) { + GameItem item = new GameItem(gatherData.getItemId(), 1); + + player.getInventory().addItem(item, ActionReason.Gather); + + return true; + } + + public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { + GatherGadgetInfo gatherGadgetInfo = GatherGadgetInfo.newBuilder() + .setItemId(this.getItemId()) + .setIsForbidGuest(this.getGatherData().isForbidGuest()) + .build(); + + gadgetInfo.setGatherGadget(gatherGadgetInfo); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java new file mode 100644 index 000000000..69a25b0a5 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java @@ -0,0 +1,30 @@ +package emu.grasscutter.game.entity.gadget; + +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.InterOpTypeOuterClass; +import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; + +public class GadgetRewardStatue extends GadgetContent { + + public GadgetRewardStatue(EntityGadget gadget) { + super(gadget); + } + + public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) { + if (player.getScene().getChallenge() != null && player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) { + dungeonChallenge.getStatueDrops(player); + } + + player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_OPEN_STATUE)); + + return false; + } + + public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { + + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetWorktop.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetWorktop.java new file mode 100644 index 000000000..a0d395b1d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetWorktop.java @@ -0,0 +1,53 @@ +package emu.grasscutter.game.entity.gadget; + +import java.util.Arrays; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.InterOpTypeOuterClass; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +public class GadgetWorktop extends GadgetContent { + private IntSet worktopOptions; + + public GadgetWorktop(EntityGadget gadget) { + super(gadget); + } + + public IntSet getWorktopOptions() { + return worktopOptions; + } + + public void addWorktopOptions(int[] options) { + if (this.worktopOptions == null) { + this.worktopOptions = new IntOpenHashSet(); + } + Arrays.stream(options).forEach(this.worktopOptions::add); + } + + public void removeWorktopOption(int option) { + if (this.worktopOptions == null) { + return; + } + this.worktopOptions.remove(option); + } + + public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) { + return false; + } + + public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { + if (this.worktopOptions == null) { + return; + } + + WorktopInfo worktop = WorktopInfo.newBuilder() + .addAllOptionList(this.getWorktopOptions()) + .build(); + + gadgetInfo.setWorktop(worktop); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/chest/BossChestInteractHandler.java b/src/main/java/emu/grasscutter/game/entity/gadget/chest/BossChestInteractHandler.java new file mode 100644 index 000000000..491bfd9bd --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/chest/BossChestInteractHandler.java @@ -0,0 +1,40 @@ +package emu.grasscutter.game.entity.gadget.chest; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.game.entity.gadget.GadgetChest; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; + +import java.util.ArrayList; +import java.util.List; + +public class BossChestInteractHandler implements ChestInteractHandler{ + @Override + public boolean isTwoStep() { + return true; + } + + @Override + public boolean onInteract(GadgetChest chest, Player player) { + var worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataManager(); + var monster = chest.getGadget().getMetaGadget().group.monsters.get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id); + var reward = worldDataManager.getRewardByBossId(monster.monster_id); + + if(reward == null){ + Grasscutter.getLogger().warn("Could not found the reward of boss monster {}", monster.monster_id); + return false; + } + List rewards = new ArrayList<>(); + for (ItemParamData param : reward.getPreviewItems()) { + rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); + } + + player.getInventory().addItems(rewards, ActionReason.OpenWorldBossChest); + player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); + + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/chest/ChestInteractHandler.java b/src/main/java/emu/grasscutter/game/entity/gadget/chest/ChestInteractHandler.java new file mode 100644 index 000000000..4dfea36fb --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/chest/ChestInteractHandler.java @@ -0,0 +1,11 @@ +package emu.grasscutter.game.entity.gadget.chest; + +import emu.grasscutter.game.entity.gadget.GadgetChest; +import emu.grasscutter.game.player.Player; + +public interface ChestInteractHandler { + + boolean isTwoStep(); + + boolean onInteract(GadgetChest chest, Player player); +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/chest/NormalChestInteractHandler.java b/src/main/java/emu/grasscutter/game/entity/gadget/chest/NormalChestInteractHandler.java new file mode 100644 index 000000000..32c50691a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/gadget/chest/NormalChestInteractHandler.java @@ -0,0 +1,42 @@ +package emu.grasscutter.game.entity.gadget.chest; + +import emu.grasscutter.game.entity.gadget.GadgetChest; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.world.ChestReward; + +import java.util.Random; + +public class NormalChestInteractHandler implements ChestInteractHandler { + private final ChestReward chestReward; + + public NormalChestInteractHandler(ChestReward rewardData){ + this.chestReward = rewardData; + } + + @Override + public boolean isTwoStep() { + return false; + } + + @Override + public boolean onInteract(GadgetChest chest, Player player) { + player.earnExp(chestReward.getAdvExp()); + player.getInventory().addItem(201, chestReward.getResin()); + + var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5); + player.getInventory().addItem(202, (int)mora); + + for(int i=0;i objNames; + int advExp; + int resin; + int mora; + int sigil; + List content; + int randomCount; + List randomContent; + +} diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 4ca410024..407edd938 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -1,9 +1,9 @@ package emu.grasscutter.game.world; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.excels.*; -import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.entity.*; import emu.grasscutter.game.player.Player; @@ -13,24 +13,24 @@ import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; +import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.SceneScriptManager; import emu.grasscutter.scripts.data.SceneBlock; +import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.server.packet.send.PacketAvatarSkillInfoNotify; -import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; -import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; -import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify; +import emu.grasscutter.server.packet.send.*; +import emu.grasscutter.utils.Position; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.danilopianini.util.SpatialIndex; import java.util.*; +import java.util.stream.Collectors; public class Scene { private final World world; @@ -49,12 +49,11 @@ public class Scene { private int weather; private SceneScriptManager scriptManager; - private DungeonChallenge challenge; + private WorldChallenge challenge; private List dungeonSettleListeners; private DungeonData dungeonData; private int prevScene; // Id of the previous scene private int prevScenePoint; - public Scene(World world, SceneData sceneData) { this.world = world; this.sceneData = sceneData; @@ -198,11 +197,11 @@ public class Scene { this.dungeonData = dungeonData; } - public DungeonChallenge getChallenge() { + public WorldChallenge getChallenge() { return challenge; } - public void setChallenge(DungeonChallenge challenge) { + public void setChallenge(WorldChallenge challenge) { this.challenge = challenge; } @@ -310,6 +309,7 @@ public class Scene { private void addEntityDirectly(GameEntity entity) { getEntities().put(entity.getId(), entity); + entity.onCreate(); // Call entity create event } public synchronized void addEntity(GameEntity entity) { @@ -320,14 +320,21 @@ public class Scene { public synchronized void addEntityToSingleClient(Player player, GameEntity entity) { this.addEntityDirectly(entity); player.sendPacket(new PacketSceneEntityAppearNotify(entity)); + + } + public void addEntities(Collection entities){ + addEntities(entities, VisionType.VISION_TYPE_BORN); } - public synchronized void addEntities(Collection entities) { + public synchronized void addEntities(Collection entities, VisionType visionType) { + if(entities == null || entities.isEmpty()){ + return; + } for (GameEntity entity : entities) { this.addEntityDirectly(entity); } - this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_TYPE_BORN)); + this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, visionType)); } private GameEntity removeEntityDirectly(GameEntity entity) { @@ -344,14 +351,21 @@ public class Scene { this.broadcastPacket(new PacketSceneEntityDisappearNotify(removed, visionType)); } } - + public synchronized void removeEntities(List entity, VisionType visionType) { + var toRemove = entity.stream() + .map(this::removeEntityDirectly) + .toList(); + if (toRemove.size() > 0) { + this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, visionType)); + } + } public synchronized void replaceEntity(EntityAvatar oldEntity, EntityAvatar newEntity) { this.removeEntityDirectly(oldEntity); this.addEntityDirectly(newEntity); this.broadcastPacket(new PacketSceneEntityDisappearNotify(oldEntity, VisionType.VISION_TYPE_REPLACE)); this.broadcastPacket(new PacketSceneEntityAppearNotify(newEntity, VisionType.VISION_TYPE_REPLACE, oldEntity.getId())); } - + public void showOtherEntities(Player player) { List entities = new LinkedList<>(); GameEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity(); @@ -362,7 +376,7 @@ public class Scene { } entities.add(entity); } - + player.sendPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_TYPE_MEET)); } @@ -412,7 +426,7 @@ public class Scene { // Death event target.onDeath(attackerId); } - + public void onTick() { // disable script for home if (this.getSceneType() == SceneType.SCENE_HOME_WORLD || this.getSceneType() == SceneType.SCENE_HOME_ROOM){ @@ -424,9 +438,12 @@ public class Scene { // TEMPORARY this.checkSpawns(); } - // Triggers - this.getScriptManager().onTick(); + this.scriptManager.checkRegions(); + + if(challenge != null){ + challenge.onCheckTimeOut(); + } } // TODO - Test @@ -502,83 +519,152 @@ public class Scene { this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); } } - + + public List getPlayerActiveBlocks(Player player){ + // consider the borders' entities of blocks, so we check if contains by index + return SceneIndexManager.queryNeighbors(getScriptManager().getBlocksIndex(), + player.getPos().toXZDoubleArray(), Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); + } public void checkBlocks() { Set visible = new HashSet<>(); - for (Player player : this.getPlayers()) { - for (SceneBlock block : getScriptManager().getBlocks()) { - if (!block.contains(player.getPos())) { - continue; - } - - visible.add(block); - } + var blocks = getPlayerActiveBlocks(player); + visible.addAll(blocks); } - + Iterator it = this.getLoadedBlocks().iterator(); while (it.hasNext()) { SceneBlock block = it.next(); - + if (!visible.contains(block)) { it.remove(); - + onUnloadBlock(block); } } - - for (SceneBlock block : visible) { + + for(var block : visible){ if (!this.getLoadedBlocks().contains(block)) { - this.onLoadBlock(block); + this.onLoadBlock(block, this.getPlayers()); this.getLoadedBlocks().add(block); + }else{ + // dynamic load the groups for players in a loaded block + var toLoad = this.getPlayers().stream() + .filter(p -> block.contains(p.getPos())) + .map(p -> playerMeetGroups(p, block)) + .flatMap(Collection::stream) + .toList(); + onLoadGroup(toLoad); + } + for (Player player : this.getPlayers()) { + getScriptManager().meetEntities(loadNpcForPlayer(player, block)); } } + } - - // TODO optimize - public void onLoadBlock(SceneBlock block) { - for (SceneGroup group : block.groups) { - // We load the script files for the groups here - if (!group.isLoaded()) { - this.getScriptManager().loadGroupFromScript(group); - } - - group.triggers.forEach(getScriptManager()::registerTrigger); - group.regions.forEach(getScriptManager()::registerRegion); + public List playerMeetGroups(Player player, SceneBlock block){ + List sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos().toDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); + + List groups = sceneGroups.stream() + .filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group)) + .peek(group -> scriptManager.getLoadedGroupSetPerBlock().get(block.id).add(group)) + .toList(); + + if (groups.size() == 0) { + return List.of(); } - + + return groups; + } + public void onLoadBlock(SceneBlock block, List players) { + this.getScriptManager().loadBlockFromScript(block); + scriptManager.getLoadedGroupSetPerBlock().put(block.id , new HashSet<>()); + + // the groups form here is not added in current scene + var groups = players.stream() + .filter(player -> block.contains(player.getPos())) + .map(p -> playerMeetGroups(p, block)) + .flatMap(Collection::stream) + .toList(); + + onLoadGroup(groups); + Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); + } + + public void onLoadGroup(List groups){ + if(groups == null || groups.isEmpty()){ + return; + } + for (SceneGroup group : groups) { + // We load the script files for the groups here + this.getScriptManager().loadGroupFromScript(group); + } + // Spawn gadgets AFTER triggers are added // TODO - for (SceneGroup group : block.groups) { + var entities = new ArrayList(); + for (SceneGroup group : groups) { if (group.init_config == null) { continue; } - int suite = group.init_config.suite; + // Load garbages + List garbageGadgets = group.getGarbageGadgets(); - if (suite == 0) { + if (garbageGadgets != null) { + entities.addAll(garbageGadgets.stream().map(g -> scriptManager.createGadget(group.id, group.block_id, g)) + .filter(Objects::nonNull) + .toList()); + } + + // Load suites + int suite = group.init_config.suite; + + if (suite == 0 || group.suites == null || group.suites.size() == 0) { continue; } - - do { - this.getScriptManager().spawnGadgetsInGroup(group, suite); - suite++; - } while (suite < group.init_config.end_suite); + + // just load the 'init' suite, avoid spawn the suite added by AddExtraGroupSuite etc. + var suiteData = group.getSuiteByIndex(suite); + suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger); + + entities.addAll(suiteData.sceneGadgets.stream() + .map(g -> scriptManager.createGadget(group.id, group.block_id, g)) + .filter(Objects::nonNull) + .toList()); + entities.addAll(suiteData.sceneMonsters.stream() + .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)) + .filter(Objects::nonNull) + .toList()); + } + + scriptManager.meetEntities(entities); + //scriptManager.callEvent(EventType.EVENT_GROUP_LOAD, null); + //groups.forEach(g -> scriptManager.callEvent(EventType.EVENT_GROUP_LOAD, null)); + Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size()); } public void onUnloadBlock(SceneBlock block) { - List toRemove = this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList(); + List toRemove = this.getEntities().values().stream() + .filter(e -> e.getBlockId() == block.id).toList(); if (toRemove.size() > 0) { - toRemove.stream().forEach(this::removeEntityDirectly); + toRemove.forEach(this::removeEntityDirectly); this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); } - for (SceneGroup group : block.groups) { - group.triggers.forEach(getScriptManager()::deregisterTrigger); - group.regions.forEach(getScriptManager()::deregisterRegion); + for (SceneGroup group : block.groups.values()) { + if(group.triggers != null){ + group.triggers.values().forEach(getScriptManager()::deregisterTrigger); + } + if(group.regions != null){ + group.regions.forEach(getScriptManager()::deregisterRegion); + } } + scriptManager.getLoadedGroupSetPerBlock().remove(block.id); + Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); } // Gadgets @@ -643,4 +729,65 @@ public class Scene { player.getSession().send(packet); } } + + public void addItemEntity(int itemId, int amount, GameEntity bornForm){ + ItemData itemData = GameData.getItemDataMap().get(itemId); + if (itemData == null) { + return; + } + if (itemData.isEquip()) { + float range = (3f + (.1f * amount)); + for (int i = 0; i < amount; i++) { + Position pos = bornForm.getPosition().clone().addX((float) (Math.random() * range) - (range / 2)).addZ((float) (Math.random() * range) - (range / 2)).addZ(.9f); + EntityItem entity = new EntityItem(this, null, itemData, pos, 1); + addEntity(entity); + } + } else { + EntityItem entity = new EntityItem(this, null, itemData, bornForm.getPosition().clone().addZ(.9f), amount); + addEntity(entity); + } + } + public List loadNpcForPlayer(Player player, SceneBlock block){ + if(!block.contains(player.getPos())){ + return List.of(); + } + + var pos = player.getPos(); + var data = GameData.getSceneNpcBornData().get(getId()); + if(data == null){ + return List.of(); + } + + var npcs = SceneIndexManager.queryNeighbors(data.getIndex(), pos.toDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); + var entityNPCS = npcs.stream().map(item -> { + var group = data.getGroups().get(item.getGroupId()); + if(group == null){ + group = SceneGroup.of(item.getGroupId()); + data.getGroups().putIfAbsent(item.getGroupId(), group); + group.load(getId()); + } + + if(group.npc == null){ + return null; + } + var npc = group.npc.get(item.getConfigId()); + if(npc == null){ + return null; + } + + return getScriptManager().createNPC(npc, block.id, item.getSuiteIdList().get(0)); + }) + .filter(Objects::nonNull) + .filter(item -> getEntities().values().stream() + .filter(e -> e instanceof EntityNPC) + .noneMatch(e -> e.getConfigId() == item.getConfigId())) + .toList(); + + if(entityNPCS.size() > 0){ + broadcastPacket(new PacketGroupSuiteNotify(entityNPCS)); + } + + return entityNPCS; + } } diff --git a/src/main/java/emu/grasscutter/game/world/WorldDataManager.java b/src/main/java/emu/grasscutter/game/world/WorldDataManager.java new file mode 100644 index 000000000..9f33f4a84 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/world/WorldDataManager.java @@ -0,0 +1,62 @@ +package emu.grasscutter.game.world; + +import com.google.gson.reflect.TypeToken; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.DataLoader; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.RewardPreviewData; +import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler; +import emu.grasscutter.game.entity.gadget.chest.ChestInteractHandler; +import emu.grasscutter.game.entity.gadget.chest.NormalChestInteractHandler; +import emu.grasscutter.server.game.GameServer; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class WorldDataManager { + private final GameServer gameServer; + private final Map chestInteractHandlerMap; // chestType-Handler + + public WorldDataManager(GameServer gameServer){ + this.gameServer = gameServer; + this.chestInteractHandlerMap = new HashMap<>(); + loadChestConfig(); + } + + public synchronized void loadChestConfig(){ + // set the special chest first + chestInteractHandlerMap.put("SceneObj_Chest_Flora", new BossChestInteractHandler()); + + try(InputStream is = DataLoader.load("ChestReward.json"); InputStreamReader isr = new InputStreamReader(is)) { + List chestReward = Grasscutter.getGsonFactory().fromJson( + isr, + TypeToken.getParameterized(List.class, ChestReward.class).getType()); + + chestReward.forEach(reward -> + reward.getObjNames().forEach( + name -> chestInteractHandlerMap.putIfAbsent(name, new NormalChestInteractHandler(reward)))); + + } catch (Exception e) { + Grasscutter.getLogger().error("Unable to load chest reward config.", e); + } + } + + public Map getChestInteractHandlerMap() { + return chestInteractHandlerMap; + } + + public RewardPreviewData getRewardByBossId(int monsterId){ + var investigationMonsterData = GameData.getInvestigationMonsterDataMap().values().parallelStream() + .filter(imd -> imd.getMonsterIdList() != null && !imd.getMonsterIdList().isEmpty()) + .filter(imd -> imd.getMonsterIdList().get(0) == monsterId) + .findFirst(); + + if(investigationMonsterData.isEmpty()){ + return null; + } + return GameData.getRewardPreviewDataMap().get(investigationMonsterData.get().getRewardPreviewId()); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java new file mode 100644 index 000000000..597661226 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/SceneIndexManager.java @@ -0,0 +1,33 @@ +package emu.grasscutter.scripts; + +import com.github.davidmoten.rtreemulti.Entry; +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import com.github.davidmoten.rtreemulti.geometry.Rectangle; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +public class SceneIndexManager { + + public static RTree buildIndex(int dimensions, Collection elements, Function extractor){ + RTree rtree = RTree.dimensions(dimensions).create(); + return rtree.add(elements.stream().map(e -> Entry.entry(e, extractor.apply(e))).toList()); + } + public static List queryNeighbors(RTree tree, double[] position, int range){ + var result = new ArrayList(); + Rectangle rectangle = Rectangle.create(calRange(position, -range), calRange(position, range)); + var queryResult = tree.search(rectangle); + queryResult.forEach(q -> result.add(q.value())); + return result; + } + private static double[] calRange(double[] position, int range){ + var newPos = position.clone(); + for(int i=0;i variables; - private Bindings bindings; - private SceneConfig config; - private List blocks; + private SceneMeta meta; private boolean isInit; - /** - * 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 ScriptMonsterTideService scriptMonsterTideService; private ScriptMonsterSpawnService scriptMonsterSpawnService; - + /** + * blockid - loaded groupSet + */ + private Int2ObjectMap> loadedGroupSetPerBlock; + public static final ExecutorService eventExecutor; + static { + eventExecutor = new ThreadPoolExecutor(4, 4, + 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), + FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy()); + } public SceneScriptManager(Scene scene) { this.scene = scene; - this.scriptLib = new ScriptLib(this); - this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib); - 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); + this.loadedGroupSetPerBlock = new Int2ObjectOpenHashMap<>(); // TEMPORARY - if (this.getScene().getId() < 10) { + if (this.getScene().getId() < 10 && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) { return; } @@ -81,28 +76,15 @@ public class SceneScriptManager { return scene; } - public ScriptLib getScriptLib() { - return scriptLib; - } - - public LuaValue getScriptLibLua() { - return scriptLibLua; - } - - public Bindings getBindings() { - return bindings; - } - public SceneConfig getConfig() { - return config; + if(!isInit){ + return null; + } + return meta.config; } - public SceneGroup getCurrentGroup() { - return currentGroup; - } - - public List getBlocks() { - return blocks; + public Map getBlocks() { + return meta.blocks; } public Map getVariables() { @@ -112,29 +94,31 @@ public class SceneScriptManager { public Set getTriggersByEvent(int eventId) { return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>()); } + public void registerTrigger(List triggers) { + triggers.forEach(this::registerTrigger); + } public void registerTrigger(SceneTrigger trigger) { - this.triggers.put(trigger.name, trigger); getTriggersByEvent(trigger.event).add(trigger); } - + public void deregisterTrigger(List triggers) { + triggers.forEach(this::deregisterTrigger); + } 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 resetTriggers(int eventId) { + currentTriggers.put(eventId, new HashSet<>()); } public void refreshGroup(SceneGroup group, int suiteIndex){ var suite = group.getSuiteByIndex(suiteIndex); if(suite == null){ return; } - if(suite.triggers.size() > 0){ - resetTriggers(suite.triggers); + if(suite.sceneTriggers.size() > 0){ + for(var trigger : suite.sceneTriggers){ + resetTriggers(trigger.event); + this.currentTriggers.get(trigger.event).add(trigger); + } } spawnMonstersInGroup(group, suite); spawnGadgetsInGroup(group, suite); @@ -150,59 +134,34 @@ public class SceneScriptManager { public void deregisterRegion(SceneRegion region) { regions.remove(region.config_id); } - + + public Int2ObjectMap> getLoadedGroupSetPerBlock() { + return loadedGroupSetPerBlock; + } + // TODO optimize public SceneGroup getGroupById(int groupId) { for (SceneBlock block : this.getScene().getLoadedBlocks()) { - for (SceneGroup group : block.groups) { - if (group.id == groupId) { - return group; - } + var group = block.groups.get(groupId); + if(group == null){ + continue; } + + if(!group.isLoaded()){ + getScene().onLoadGroup(List.of(group)); + } + return group; } return null; } private void init() { - // Get compiled script if cached - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("Scene/" + getScene().getId() + "/scene" + getScene().getId() + "." + ScriptLoader.getScriptType())); - - if (cs == null) { - Grasscutter.getLogger().warn("No script found for scene " + getScene().getId()); + var meta = ScriptLoader.getSceneMeta(getScene().getId()); + if (meta == null){ return; } - - // Create bindings - bindings = ScriptLoader.getEngine().createBindings(); - - // Set variables - bindings.put("ScriptLib", getScriptLib()); + this.meta = meta; - // Eval script - try { - cs.eval(getBindings()); - - this.config = ScriptLoader.getSerializer().toObject(SceneConfig.class, bindings.get("scene_config")); - - // TODO optimize later - // Create blocks - List blockIds = ScriptLoader.getSerializer().toList(Integer.class, bindings.get("blocks")); - List blocks = ScriptLoader.getSerializer().toList(SceneBlock.class, bindings.get("block_rects")); - - for (int i = 0; i < blocks.size(); i++) { - SceneBlock block = blocks.get(i); - block.id = blockIds.get(i); - - loadBlockFromScript(block); - } - - this.blocks = blocks; - } catch (ScriptException e) { - Grasscutter.getLogger().error("Error running script", e); - return; - } - // TEMP this.isInit = true; } @@ -211,90 +170,23 @@ public class SceneScriptManager { return isInit; } - private void loadBlockFromScript(SceneBlock block) { - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_block" + block.id + "." + ScriptLoader.getScriptType())); - - if (cs == null) { - return; - } - - // Eval script - try { - cs.eval(getBindings()); - - // Set groups - block.groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups")); - block.groups.forEach(g -> g.block_id = block.id); - } catch (ScriptException e) { - Grasscutter.getLogger().error("Error loading block " + block.id + " in scene " + getScene().getId(), e); - } + public void loadBlockFromScript(SceneBlock block) { + block.load(scene.getId(), meta.context); } public void loadGroupFromScript(SceneGroup group) { - // Set flag here so if there is no script, we dont call this function over and over again. - group.setLoaded(true); - - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_group" + group.id + "." + ScriptLoader.getScriptType())); - - if (cs == null) { - return; + group.load(getScene().getId()); + + if (group.variables != null) { + group.variables.forEach(var -> this.getVariables().put(var.name, var.value)); } - - // Eval script - try { - cs.eval(getBindings()); - // Set - 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")); - group.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); - group.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config")); - - // Add variables to suite - List variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables")); - variables.forEach(var -> this.getVariables().put(var.name, var.value)); - - // Add monsters to suite TODO optimize - Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); - 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) { - suite.sceneMonsters = new ArrayList<>(suite.monsters.size()); - 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); - } - } - }); + this.sceneGroups.put(group.id, group); - suite.sceneGadgets = new ArrayList<>(suite.gadgets.size()); - for (int id : suite.gadgets) { - try { - SceneGadget gadget = (SceneGadget) map.get(id); - if (gadget != null) { - suite.sceneGadgets.add(gadget); - } - } catch (Exception ignored) { } - } - } - this.sceneGroups.put(group.id, group); - } catch (ScriptException e) { - Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e); + if(group.regions != null){ + group.regions.forEach(this::registerRegion); } } - - public void onTick() { - checkRegions(); - } public void checkRegions() { if (this.regions.size() == 0) { @@ -315,7 +207,17 @@ public class SceneScriptManager { } } } - + + public void addGroupSuite(SceneGroup group, SceneSuite suite){ + spawnMonstersInGroup(group, suite); + spawnGadgetsInGroup(group, suite); + registerTrigger(suite.sceneTriggers); + } + public void removeGroupSuite(SceneGroup group, SceneSuite suite){ + removeMonstersInGroup(group, suite); + removeGadgetsInGroup(group, suite); + deregisterTrigger(suite.sceneTriggers); + } public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) { spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex)); } @@ -325,26 +227,17 @@ public class SceneScriptManager { } public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) { - List gadgets = group.gadgets; + var gadgets = group.gadgets.values(); if (suite != null) { gadgets = suite.sceneGadgets; } - - for (SceneGadget g : gadgets) { - EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos); - - if (entity.getGadgetData() == null) continue; - - entity.setBlockId(group.block_id); - entity.setConfigId(g.config_id); - entity.setGroupId(group.id); - entity.getRotation().set(g.rot); - entity.setState(g.state); - - getScene().addEntity(entity); - this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId())); - } + + var toCreate = gadgets.stream() + .map(g -> createGadget(g.group.id, group.block_id, g)) + .filter(Objects::nonNull) + .toList(); + this.addEntities(toCreate); } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { @@ -358,17 +251,16 @@ public class SceneScriptManager { if(suite == null || suite.sceneMonsters.size() <= 0){ return; } - this.currentGroup = group; - suite.sceneMonsters.forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob)); + this.addEntities(suite.sceneMonsters.stream() + .map(mob -> createMonster(group.id, group.block_id, mob)).toList()); } public void spawnMonstersInGroup(SceneGroup group) { - this.currentGroup = group; - group.monsters.values().forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob)); + this.addEntities(group.monsters.values().stream() + .map(mob -> createMonster(group.id, group.block_id, mob)).toList()); } public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) { - this.currentGroup = group; this.scriptMonsterTideService = new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId); @@ -379,49 +271,71 @@ public class SceneScriptManager { } this.getScriptMonsterTideService().unload(); } - public void spawnMonstersByConfigId(int configId, int delayTime) { + public void spawnMonstersByConfigId(SceneGroup group, int configId, int delayTime) { // TODO delay - this.scriptMonsterSpawnService.spawnMonster(this.currentGroup.id, this.currentGroup.monsters.get(configId)); + getScene().addEntity(createMonster(group.id, group.block_id, group.monsters.get(configId))); } // Events - - public void callEvent(int eventType, ScriptArgs params) { - for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { - LuaValue condition = null; - - if (trigger.condition != null && !trigger.condition.isEmpty()) { - condition = (LuaValue) this.getBindings().get(trigger.condition); - } - - LuaValue ret = LuaValue.TRUE; - - if (condition != null) { - LuaValue args = LuaValue.NIL; - - if (params != null) { - args = CoerceJavaToLua.coerce(params); - } + public void callEvent(int eventType, ScriptArgs params){ + /** + * We use ThreadLocal to trans SceneScriptManager context to ScriptLib, to avoid eval script for every groups' trigger in every scene instances. + * But when callEvent is called in a ScriptLib func, it may cause NPE because the inner call cleans the ThreadLocal so that outer call could not get it. + * e.g. CallEvent -> set -> ScriptLib.xxx -> CallEvent -> set -> remove -> NPE -> (remove) + * So we use thread pool to clean the stack to avoid this new issue. + */ + eventExecutor.submit(() -> this.realCallEvent(eventType, params)); + } - ScriptLib.logger.trace("Call Condition Trigger {}", trigger); - ret = safetyCall(trigger.condition, condition, args); + private void realCallEvent(int eventType, ScriptArgs params) { + try{ + ScriptLoader.getScriptLib().setSceneScriptManager(this); + for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { + try{ + ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup); + + LuaValue ret = callScriptFunc(trigger.condition, trigger.currentGroup, params); + Grasscutter.getLogger().trace("Call Condition Trigger {}", trigger.condition); + + if (ret.isboolean() && ret.checkboolean()) { + // the SetGroupVariableValueByGroup in tower need the param to record the first stage time + callScriptFunc(trigger.action, trigger.currentGroup, params); + Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.action); + } + //TODO some ret may not bool + + }finally { + ScriptLoader.getScriptLib().removeCurrentGroup(); + } } - - if (ret.isboolean() && ret.checkboolean()) { - ScriptLib.logger.trace("Call Action Trigger {}", trigger); - LuaValue action = (LuaValue) this.getBindings().get(trigger.action); - // TODO impl the param of SetGroupVariableValueByGroup - var arg = new ScriptArgs(); - arg.param2 = 100; - var args = CoerceJavaToLua.coerce(arg); - safetyCall(trigger.action, action, args); - } - //TODO some ret may not bool + }finally { + // make sure it is removed + ScriptLoader.getScriptLib().removeSceneScriptManager(); } } + private LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params){ + LuaValue funcLua = null; + if (funcName != null && !funcName.isEmpty()) { + funcLua = (LuaValue) group.getBindings().get(funcName); + } + + LuaValue ret = LuaValue.TRUE; + + if (funcLua != null) { + LuaValue args = LuaValue.NIL; + + if (params != null) { + args = CoerceJavaToLua.coerce(params); + } + + ret = safetyCall(funcName, funcLua, args); + } + return ret; + } + public LuaValue safetyCall(String name, LuaValue func, LuaValue args){ try{ - return func.call(this.getScriptLibLua(), args); + return func.call(ScriptLoader.getScriptLibLua(), args); }catch (LuaError error){ ScriptLib.logger.error("[LUA] call trigger failed {},{}",name,args,error); return LuaValue.valueOf(-1); @@ -436,4 +350,102 @@ public class SceneScriptManager { return scriptMonsterSpawnService; } + public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) { + EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos); + + if (entity.getGadgetData() == null){ + return null; + } + + entity.setBlockId(blockId); + entity.setConfigId(g.config_id); + entity.setGroupId(groupId); + entity.getRotation().set(g.rot); + entity.setState(g.state); + entity.setPointType(g.point_type); + entity.setMetaGadget(g); + entity.buildContent(); + + return entity; + } + public EntityNPC createNPC(SceneNPC npc, int blockId, int suiteId) { + return new EntityNPC(getScene(), npc, blockId, suiteId); + } + public EntityMonster createMonster(int groupId, int blockId, SceneMonster monster) { + if(monster == null){ + return null; + } + + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return null; + } + + // 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(groupId); + entity.setBlockId(blockId); + entity.setConfigId(monster.config_id); + entity.setPoseId(monster.pose_id); + + this.getScriptMonsterSpawnService() + .onMonsterCreatedListener.forEach(action -> action.onNotify(entity)); + + return entity; + } + + public void addEntity(GameEntity gameEntity){ + getScene().addEntity(gameEntity); + } + + public void meetEntities(List gameEntity){ + getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_MEET); + } + + public void addEntities(List gameEntity){ + getScene().addEntities(gameEntity); + } + + public RTree getBlocksIndex() { + return meta.sceneBlockIndex; + } + public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) { + var configSet = suite.sceneMonsters.stream() + .map(m -> m.config_id) + .collect(Collectors.toSet()); + var toRemove = getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster) + .filter(e -> e.getGroupId() == group.id) + .filter(e -> configSet.contains(e.getConfigId())) + .toList(); + + getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_MISS); + } + public void removeGadgetsInGroup(SceneGroup group, SceneSuite suite) { + var configSet = suite.sceneGadgets.stream() + .map(m -> m.config_id) + .collect(Collectors.toSet()); + var toRemove = getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityGadget) + .filter(e -> e.getGroupId() == group.id) + .filter(e -> configSet.contains(e.getConfigId())) + .toList(); + + getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_MISS); + } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index b7fb5939f..631250c90 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -1,32 +1,43 @@ package emu.grasscutter.scripts; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.entity.gadget.GadgetWorktop; +import emu.grasscutter.game.dungeons.challenge.factory.ChallengeFactory; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; -import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify; +import io.netty.util.concurrent.FastThreadLocal; 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) { - this.sceneScriptManager = sceneScriptManager; + private final FastThreadLocal sceneScriptManager; + private final FastThreadLocal currentGroup; + public ScriptLib() { + this.sceneScriptManager = new FastThreadLocal<>(); + this.currentGroup = new FastThreadLocal<>(); + } + + public void setSceneScriptManager(SceneScriptManager sceneScriptManager){ + this.sceneScriptManager.set(sceneScriptManager); + } + + public void removeSceneScriptManager(){ + this.sceneScriptManager.remove(); } public SceneScriptManager getSceneScriptManager() { - return sceneScriptManager; + // normally not null + return Optional.of(sceneScriptManager.get()).get(); } private String printTable(LuaTable table){ @@ -38,7 +49,15 @@ public class ScriptLib { sb.append("}"); return sb.toString(); } - + public void setCurrentGroup(SceneGroup currentGroup){ + this.currentGroup.set(currentGroup); + } + public Optional getCurrentGroup(){ + return Optional.of(this.currentGroup.get()); + } + public void removeCurrentGroup(){ + this.currentGroup.remove(); + } public int SetGadgetStateByConfigId(int configId, int gadgetState) { logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", configId,gadgetState); @@ -48,34 +67,24 @@ public class ScriptLib { if (entity.isEmpty()) { return 1; } - - if (!(entity.get() instanceof EntityGadget)) { - return 1; + + if (entity.get() instanceof EntityGadget entityGadget) { + entityGadget.updateState(gadgetState); + return 0; } - - EntityGadget gadget = (EntityGadget) entity.get(); - gadget.setState(gadgetState); - - getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState)); - return 0; + + return 1; } 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(); - - for (GameEntity entity : list) { - if (!(entity instanceof EntityGadget)) { - continue; - } - - EntityGadget gadget = (EntityGadget) entity; - gadget.setState(gadgetState); - - getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState)); - } + + getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e.getGroupId() == groupId) + .filter(e -> e instanceof EntityGadget) + .map(e -> (EntityGadget)e) + .forEach(e -> e.updateState(gadgetState)); return 0; } @@ -83,42 +92,47 @@ 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(); - if (entity.isEmpty()) { - return 1; - } - - if (!(entity.get() instanceof EntityGadget)) { - return 1; - } - - EntityGadget gadget = (EntityGadget) entity.get(); - gadget.addWorktopOptions(options); + if (entity.isEmpty() || !(entity.get() instanceof EntityGadget gadget)) { + return 1; + } + + if (!(gadget.getContent() instanceof GadgetWorktop worktop)) { + return 1; + } + + worktop.addWorktopOptions(options); getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); + + return 0; + } + + public int SetWorktopOptions(LuaTable table){ + logger.debug("[LUA] Call SetWorktopOptions with {}", printTable(table)); + // TODO return 0; } - 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(); - if (entity.isEmpty()) { + if (entity.isEmpty() || !(entity.get() instanceof EntityGadget gadget)) { + return 1; + } + + if (!(gadget.getContent() instanceof GadgetWorktop worktop)) { return 1; } - if (!(entity.get() instanceof EntityGadget)) { - return 1; - } - - EntityGadget gadget = (EntityGadget) entity.get(); - gadget.removeWorktopOption(option); - + worktop.removeWorktopOption(option); getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); + return 0; } @@ -146,48 +160,102 @@ public class ScriptLib { if (group == null || group.monsters == null) { return 1; } - + var suiteData = group.getSuiteByIndex(suite); + if(suiteData == null){ + return 1; + } // avoid spawn wrong monster if(getSceneScriptManager().getScene().getChallenge() != null) if(!getSceneScriptManager().getScene().getChallenge().inProgress() || getSceneScriptManager().getScene().getChallenge().getGroup().id != groupId){ return 0; } - this.getSceneScriptManager().spawnMonstersInGroup(group, suite); - + this.getSceneScriptManager().addGroupSuite(group, suiteData); + + return 0; + } + public int GoToGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call GoToGroupSuite with {},{}", + groupId,suite); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + if (group == null || group.monsters == null) { + return 1; + } + var suiteData = group.getSuiteByIndex(suite); + if(suiteData == null){ + return 1; + } + + for(var suiteItem : group.suites){ + if(suiteData == suiteItem){ + continue; + } + this.getSceneScriptManager().removeGroupSuite(group, suiteItem); + } + this.getSceneScriptManager().addGroupSuite(group, suiteData); + + return 0; + } + public int RemoveExtraGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call RemoveExtraGroupSuite with {},{}", + groupId,suite); + + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + if (group == null || group.monsters == null) { + return 1; + } + var suiteData = group.getSuiteByIndex(suite); + if(suiteData == null){ + return 1; + } + + this.getSceneScriptManager().removeGroupSuite(group, suiteData); + + return 0; + } + public int KillExtraGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call KillExtraGroupSuite with {},{}", + groupId,suite); + + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + if (group == null || group.monsters == null) { + return 1; + } + var suiteData = group.getSuiteByIndex(suite); + if(suiteData == null){ + return 1; + } + + this.getSceneScriptManager().removeGroupSuite(group, suiteData); + return 0; } - // param3 (probably time limit for timed dungeons) 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; + var challenge = ChallengeFactory.getChallenge( + challengeId, + challengeIndex, + timeLimitOrGroupId, + groupId, + objectiveKills, + param5, + getSceneScriptManager().getScene(), + getCurrentGroup().get() + ); - if(group == null){ - group = getSceneScriptManager().getGroupById(timeLimitOrGroupId); - objective = groupId; - } - - if (group == null || group.monsters == null) { + if(challenge == null){ return 1; } - if(getSceneScriptManager().getScene().getChallenge() != null && - getSceneScriptManager().getScene().getChallenge().inProgress()) - { - return 0; + if(challenge instanceof DungeonChallenge dungeonChallenge){ + // set if tower first stage (6-1) + dungeonChallenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0); } - DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), - group, challengeId, challengeIndex, objective); - // set if tower first stage (6-1) - challenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0); - getSceneScriptManager().getScene().setChallenge(challenge); - challenge.start(); return 0; } @@ -244,7 +312,7 @@ public class ScriptLib { public int GetRegionEntityCount(LuaTable table) { logger.debug("[LUA] Call GetRegionEntityCount with {}", - table); + printTable(table)); int regionId = table.get("region_eid").toint(); int entityType = table.get("entity_type").toint(); @@ -267,12 +335,12 @@ public class ScriptLib { // TODO record time return 0; } - public int GetGroupMonsterCount(int var1){ - logger.debug("[LUA] Call GetGroupMonsterCount with {}", - var1); + public int GetGroupMonsterCount(){ + logger.debug("[LUA] Call GetGroupMonsterCount "); return (int) getSceneScriptManager().getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityMonster && e.getGroupId() == getSceneScriptManager().getCurrentGroup().id) + .filter(e -> e instanceof EntityMonster && + e.getGroupId() == getCurrentGroup().map(sceneGroup -> sceneGroup.id).orElse(-1)) .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ @@ -314,7 +382,7 @@ public class ScriptLib { var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint()); if(entity == null){ - return 1; + return 0; } getSceneScriptManager().getScene().killEntity(entity, 0); return 0; @@ -334,7 +402,11 @@ public class ScriptLib { var configId = table.get("config_id").toint(); var delayTime = table.get("delay_time").toint(); - getSceneScriptManager().spawnMonstersByConfigId(configId, delayTime); + if(getCurrentGroup().isEmpty()){ + return 1; + } + + getSceneScriptManager().spawnMonstersByConfigId(getCurrentGroup().get(), configId, delayTime); return 0; } @@ -353,8 +425,81 @@ public class ScriptLib { printTable(table)); var configId = table.get("config_id").toint(); - //TODO + var group = getCurrentGroup(); + + if (group.isEmpty()) { + return 1; + } + + var gadget = group.get().gadgets.get(configId); + var entity = getSceneScriptManager().createGadget(group.get().id, group.get().block_id, gadget); + + getSceneScriptManager().addEntity(entity); return 0; } + public int CheckRemainGadgetCountByGroupId(LuaTable table){ + logger.debug("[LUA] Call CheckRemainGadgetCountByGroupId with {}", + printTable(table)); + var groupId = table.get("group_id").toint(); + + var count = getSceneScriptManager().getScene().getEntities().values().stream() + .filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == groupId) + .count(); + return (int)count; + } + + public int GetGadgetStateByConfigId(int groupId, int configId){ + logger.debug("[LUA] Call GetGadgetStateByConfigId with {},{}", + groupId, configId); + + if(groupId == 0){ + groupId = getCurrentGroup().get().id; + } + final int realGroupId = groupId; + var gadget = getSceneScriptManager().getScene().getEntities().values().stream() + .filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == realGroupId) + .filter(g -> g.getConfigId() == configId) + .findFirst(); + if(gadget.isEmpty()){ + return 1; + } + return ((EntityGadget)gadget.get()).getState(); + } + + public int MarkPlayerAction(int var1, int var2, int var3, int var4){ + logger.debug("[LUA] Call MarkPlayerAction with {},{},{},{}", + var1, var2,var3,var4); + + return 0; + } + + public int AddQuestProgress(String var1){ + logger.debug("[LUA] Call AddQuestProgress with {}", + var1); + + return 0; + } + + /** + * change the state of gadget + */ + public int ChangeGroupGadget(LuaTable table){ + logger.debug("[LUA] Call ChangeGroupGadget with {}", + printTable(table)); + var configId = table.get("config_id").toint(); + var state = table.get("state").toint(); + + var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId); + if(entity == null){ + return 1; + } + + if (entity instanceof EntityGadget entityGadget) { + entityGadget.updateState(state); + return 0; + } + + return 1; + } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java index 0cbd6a191..c55a76251 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java @@ -1,32 +1,27 @@ package emu.grasscutter.scripts; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import javax.script.Compilable; -import javax.script.CompiledScript; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; - -import org.luaj.vm2.LuaTable; -import org.luaj.vm2.LuaValue; -import org.luaj.vm2.lib.OneArgFunction; -import org.luaj.vm2.lib.jse.CoerceJavaToLua; -import org.luaj.vm2.script.LuajContext; - import emu.grasscutter.Grasscutter; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.ScriptGadgetState; import emu.grasscutter.scripts.constants.ScriptRegionShape; +import emu.grasscutter.scripts.data.SceneMeta; import emu.grasscutter.scripts.serializer.LuaSerializer; import emu.grasscutter.scripts.serializer.Serializer; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.jse.CoerceJavaToLua; +import org.luaj.vm2.script.LuajContext; + +import javax.script.*; +import java.io.File; +import java.io.FileReader; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; public class ScriptLoader { private static ScriptEngineManager sm; @@ -34,9 +29,17 @@ public class ScriptLoader { private static ScriptEngineFactory factory; private static String fileType; private static Serializer serializer; - - private static Map scripts = new HashMap<>(); - + private static ScriptLib scriptLib; + private static LuaValue scriptLibLua; + /** + * suggest GC to remove it if the memory is less + */ + private static Map> scriptsCache = new ConcurrentHashMap<>(); + /** + * sceneId - SceneMeta + */ + private static Map> sceneMetaCache = new ConcurrentHashMap<>(); + public synchronized static void init() throws Exception { if (sm != null) { throw new Exception("Script loader already initialized"); @@ -67,6 +70,10 @@ public class ScriptLoader { ctx.globals.set("EventType", CoerceJavaToLua.coerce(new EventType())); // TODO - make static class to avoid instantiating a new class every scene ctx.globals.set("GadgetState", CoerceJavaToLua.coerce(new ScriptGadgetState())); ctx.globals.set("RegionShape", CoerceJavaToLua.coerce(new ScriptRegionShape())); + + scriptLib = new ScriptLib(); + scriptLibLua = CoerceJavaToLua.coerce(scriptLib); + ctx.globals.set("ScriptLib", scriptLibLua); } public static ScriptEngine getEngine() { @@ -81,25 +88,50 @@ public class ScriptLoader { return serializer; } - public static CompiledScript getScriptByPath(String path) { - CompiledScript sc = scripts.get(path); - - Grasscutter.getLogger().info("Loaded " + path); - - if (sc == null) { - File file = new File(path); - - if (!file.exists()) return null; - - try (FileReader fr = new FileReader(file)) { - sc = ((Compilable) getEngine()).compile(fr); - scripts.put(path, sc); - } catch (Exception e) { - //e.printStackTrace(); - return null; - } - } - - return sc; + public static ScriptLib getScriptLib() { + return scriptLib; } + + public static LuaValue getScriptLibLua() { + return scriptLibLua; + } + + public static Optional tryGet(SoftReference softReference){ + try{ + return Optional.ofNullable(softReference.get()); + }catch (NullPointerException npe){ + return Optional.empty(); + } + } + public static CompiledScript getScriptByPath(String path) { + var sc = tryGet(scriptsCache.get(path)); + if (sc.isPresent()) { + return sc.get(); + } + + Grasscutter.getLogger().info("Loading script " + path); + + File file = new File(path); + + if (!file.exists()) return null; + + try (FileReader fr = new FileReader(file)) { + var script = ((Compilable) getEngine()).compile(fr); + scriptsCache.put(path, new SoftReference<>(script)); + return script; + } catch (Exception e) { + Grasscutter.getLogger().error("Loading script {} failed!", path, e); + return null; + } + + } + + public static SceneMeta getSceneMeta(int sceneId) { + return tryGet(sceneMetaCache.get(sceneId)).orElseGet(() -> { + var instance = SceneMeta.of(sceneId); + sceneMetaCache.put(sceneId, new SoftReference<>(instance)); + return instance; + }); + } + } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptUtils.java b/src/main/java/emu/grasscutter/scripts/ScriptUtils.java new file mode 100644 index 000000000..b79fef9d6 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/ScriptUtils.java @@ -0,0 +1,28 @@ +package emu.grasscutter.scripts; + +import java.util.HashMap; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +import emu.grasscutter.Grasscutter; + +public class ScriptUtils { + + public static HashMap toMap(LuaTable table) { + HashMap map = new HashMap<>(); + LuaValue[] rootKeys = table.keys(); + for (LuaValue k : rootKeys) { + if (table.get(k).istable()) { + map.put(k, toMap(table.get(k).checktable())); + } else { + map.put(k, table.get(k)); + } + } + return map; + } + + public static void print(LuaTable table) { + Grasscutter.getLogger().info(toMap(table).toString()); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/constants/EventType.java b/src/main/java/emu/grasscutter/scripts/constants/EventType.java index 58d8dc3ab..b7236b85c 100644 --- a/src/main/java/emu/grasscutter/scripts/constants/EventType.java +++ b/src/main/java/emu/grasscutter/scripts/constants/EventType.java @@ -2,6 +2,9 @@ package emu.grasscutter.scripts.constants; public class EventType { public static final int EVENT_NONE = 0; + /** + * param1: monster.configId + */ public static final int EVENT_ANY_MONSTER_DIE = 1; public static final int EVENT_ANY_GADGET_DIE = 2; public static final int EVENT_VARIABLE_CHANGE = 3; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index 11a930fdd..3584f3fd5 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -1,17 +1,81 @@ package emu.grasscutter.scripts.data; -import java.util.List; - +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import com.github.davidmoten.rtreemulti.geometry.Rectangle; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.SceneIndexManager; +import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; +import java.util.Map; +import java.util.stream.Collectors; + +import static emu.grasscutter.Configuration.SCRIPT; + +@ToString +@Setter public class SceneBlock { public int id; public Position max; public Position min; - public List groups; - + + public int sceneId; + public Map groups; + public RTree sceneGroupIndex; + + private transient boolean loaded; // Not an actual variable in the scripts either + + public boolean isLoaded() { + return loaded; + } + + public void setLoaded(boolean loaded) { + this.loaded = loaded; + } + public boolean contains(Position pos) { return pos.getX() <= max.getX() && pos.getX() >= min.getX() && pos.getZ() <= max.getZ() && pos.getZ() >= min.getZ(); } -} + + public SceneBlock load(int sceneId, Bindings bindings){ + if(loaded){ + return this; + } + this.sceneId = sceneId; + setLoaded(true); + + CompiledScript cs = ScriptLoader.getScriptByPath( + SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_block" + id + "." + ScriptLoader.getScriptType())); + + if (cs == null) { + return null; + } + + // Eval script + try { + cs.eval(bindings); + + // Set groups + groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups")).stream() + .collect(Collectors.toMap(x -> x.id, y -> y)); + + groups.values().forEach(g -> g.block_id = id); + this.sceneGroupIndex = SceneIndexManager.buildIndex(3, groups.values(), g -> g.pos.toPoint()); + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error loading block " + id + " in scene " + sceneId, e); + } + Grasscutter.getLogger().info("scene {} block {} is loaded successfully.", sceneId, id); + return this; + } + + public Rectangle toRectangle() { + return Rectangle.create(min.toXZDoubleArray(), max.toXZDoubleArray()); + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBossChest.java b/src/main/java/emu/grasscutter/scripts/data/SceneBossChest.java new file mode 100644 index 000000000..f0009eb05 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBossChest.java @@ -0,0 +1,13 @@ +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@Setter +@ToString +public class SceneBossChest { + public int life_time; + public int monster_config_id; + public int resin; + public int take_num; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBusiness.java b/src/main/java/emu/grasscutter/scripts/data/SceneBusiness.java new file mode 100644 index 000000000..f03011e09 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBusiness.java @@ -0,0 +1,10 @@ +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneBusiness { + public int type; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java index 3a1ca60ea..cba2be8bd 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java @@ -1,7 +1,11 @@ package emu.grasscutter.scripts.data; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneConfig { public Position vision_anchor; public Position born_pos; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index e5340ae55..3b9f2bf7c 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -1,12 +1,14 @@ package emu.grasscutter.scripts.data; -import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; -public class SceneGadget { - public int level; - public int config_id; +@ToString +@Setter +public class SceneGadget extends SceneObject{ public int gadget_id; public int state; - public Position pos; - public Position rot; + public int point_type; + public SceneBossChest boss_chest; + public int interact_id; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java b/src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java new file mode 100644 index 000000000..19f98984e --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java @@ -0,0 +1,12 @@ +package emu.grasscutter.scripts.data; + +import java.util.List; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneGarbage { + public List gadgets; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 690cd3d0d..0be30342d 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -1,10 +1,27 @@ package emu.grasscutter.scripts.data; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import static emu.grasscutter.Configuration.SCRIPT; + +@ToString +@Setter public class SceneGroup { public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference @@ -12,27 +29,140 @@ public class SceneGroup { public int refresh_id; public Position pos; - /** - * ConfigId - Monster - */ - public Map monsters; - public List gadgets; - public List triggers; + public Map monsters; // + public Map gadgets; // + public Map triggers; + public Map npc; // public List regions; public List suites; - public SceneInitConfig init_config; + public List variables; - private transient boolean isLoaded; // Not an actual variable in the scripts either + public SceneBusiness business; + public SceneGarbage garbages; + public SceneInitConfig init_config; + + private transient boolean loaded; // Not an actual variable in the scripts either + private transient CompiledScript script; + private transient Bindings bindings; + public static SceneGroup of(int groupId) { + var group = new SceneGroup(); + group.id = groupId; + return group; + } public boolean isLoaded() { - return isLoaded; - } - - public boolean setLoaded(boolean loaded) { return loaded; } + public void setLoaded(boolean loaded) { + this.loaded = loaded; + } + + public int getBusinessType() { + return this.business == null ? 0 : this.business.type; + } + + public List getGarbageGadgets() { + return this.garbages == null ? null : this.garbages.gadgets; + } + + public CompiledScript getScript() { + return script; + } + public SceneSuite getSuiteByIndex(int index) { return suites.get(index - 1); } + + public Bindings getBindings() { + return bindings; + } + + public synchronized SceneGroup load(int sceneId){ + if(loaded){ + return this; + } + // Set flag here so if there is no script, we dont call this function over and over again. + setLoaded(true); + + this.bindings = ScriptLoader.getEngine().createBindings(); + + CompiledScript cs = ScriptLoader.getScriptByPath( + SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_group" + id + "." + ScriptLoader.getScriptType())); + + if (cs == null) { + return this; + } + + this.script = cs; + + // Eval script + try { + cs.eval(bindings); + + // Set + monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); + monsters.values().forEach(m -> m.group = this); + + gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); + gadgets.values().forEach(m -> m.group = this); + + triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")).stream() + .collect(Collectors.toMap(x -> x.name, y -> y)); + triggers.values().forEach(t -> t.currentGroup = this); + + suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); + regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); + init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config")); + + // Garbages TODO fix properly later + Object garbagesValue = bindings.get("garbages"); + if (garbagesValue != null && garbagesValue instanceof LuaValue garbagesTable) { + garbages = new SceneGarbage(); + if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) { + garbages.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable()); + garbages.gadgets.forEach(m -> m.group = this); + } + } + + // Add variables to suite + variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables")); + // NPC in groups + npc = ScriptLoader.getSerializer().toList(SceneNPC.class, bindings.get("npcs")).stream() + .collect(Collectors.toMap(x -> x.npc_id, y -> y)); + npc.values().forEach(n -> n.group = this); + + // Add monsters and gadgets to suite + for (SceneSuite suite : suites) { + suite.sceneMonsters = new ArrayList<>( + suite.monsters.stream() + .filter(monsters::containsKey) + .map(monsters::get) + .toList() + ); + + suite.sceneGadgets = new ArrayList<>( + suite.gadgets.stream() + .filter(gadgets::containsKey) + .map(gadgets::get) + .toList() + ); + + suite.sceneTriggers = new ArrayList<>( + suite.triggers.stream() + .filter(triggers::containsKey) + .map(triggers::get) + .toList() + ); + } + + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error loading group " + id + " in scene " + sceneId, e); + } + + Grasscutter.getLogger().info("group {} in scene {} is loaded successfully.", id, sceneId); + return this; + } } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java index fcdc92c87..a9bc586c9 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java @@ -1,7 +1,10 @@ package emu.grasscutter.scripts.data; -import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneInitConfig { public int suite; public int end_suite; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java new file mode 100644 index 000000000..212c2c85d --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java @@ -0,0 +1,75 @@ +package emu.grasscutter.scripts.data; + +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.SceneIndexManager; +import emu.grasscutter.scripts.ScriptLoader; +import lombok.Setter; +import lombok.ToString; + +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static emu.grasscutter.Configuration.SCRIPT; + +@ToString +@Setter +public class SceneMeta { + + public SceneConfig config; + public Map blocks; + + public Bindings context; + + public RTree sceneBlockIndex; + + public static SceneMeta of(int sceneId) { + return new SceneMeta().load(sceneId); + } + + public SceneMeta load(int sceneId){ + // Get compiled script if cached + CompiledScript cs = ScriptLoader.getScriptByPath( + SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "." + ScriptLoader.getScriptType())); + + if (cs == null) { + Grasscutter.getLogger().warn("No script found for scene " + sceneId); + return null; + } + + // Create bindings + context = ScriptLoader.getEngine().createBindings(); + + // Eval script + try { + cs.eval(context); + + this.config = ScriptLoader.getSerializer().toObject(SceneConfig.class, context.get("scene_config")); + + // TODO optimize later + // Create blocks + List blockIds = ScriptLoader.getSerializer().toList(Integer.class, context.get("blocks")); + List blocks = ScriptLoader.getSerializer().toList(SceneBlock.class, context.get("block_rects")); + + for (int i = 0; i < blocks.size(); i++) { + SceneBlock block = blocks.get(i); + block.id = blockIds.get(i); + + } + + this.blocks = blocks.stream().collect(Collectors.toMap(b -> b.id, b -> b)); + this.sceneBlockIndex = SceneIndexManager.buildIndex(2, blocks, SceneBlock::toRectangle); + + } catch (ScriptException e) { + Grasscutter.getLogger().error("Error running script", e); + return null; + } + Grasscutter.getLogger().info("scene {} metadata is loaded successfully.", sceneId); + return this; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java index 56d2e3d3d..070a87fee 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -1,11 +1,12 @@ package emu.grasscutter.scripts.data; -import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; -public class SceneMonster { - public int level; - public int config_id; +@ToString +@Setter +public class SceneMonster extends SceneObject{ public int monster_id; - public Position pos; - public Position rot; -} + public int pose_id; + public int drop_id; +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneNPC.java b/src/main/java/emu/grasscutter/scripts/data/SceneNPC.java new file mode 100644 index 000000000..97f3f79dd --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneNPC.java @@ -0,0 +1,10 @@ +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneNPC extends SceneObject{ + public int npc_id; +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java new file mode 100644 index 000000000..eb1f42d8c --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java @@ -0,0 +1,20 @@ +package emu.grasscutter.scripts.data; + +import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneObject { + public int level; + public int config_id; + public int area_id; + + public Position pos; + public Position rot; + /** + * not set by lua + */ + public transient SceneGroup group; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java index dac164d0e..edc04be15 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java @@ -5,7 +5,12 @@ import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.utils.Position; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.Data; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneRegion { public int config_id; public int shape; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java index 90433b3d4..7555601ca 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -2,8 +2,11 @@ package emu.grasscutter.scripts.data; import java.util.List; -import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; +@ToString +@Setter public class SceneSuite { public List monsters; public List gadgets; @@ -12,4 +15,5 @@ public class SceneSuite { public transient List sceneMonsters; public transient List sceneGadgets; + public transient List sceneTriggers; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index 301fdb8e0..34e4df75e 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -1,5 +1,8 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; + +@Setter public class SceneTrigger { public String name; public int config_id; @@ -8,6 +11,7 @@ public class SceneTrigger { public String condition; public String action; + public transient SceneGroup currentGroup; @Override public boolean equals(Object obj) { if(obj instanceof SceneTrigger sceneTrigger){ diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java index 9ead6f474..41ebda1b1 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java @@ -1,5 +1,10 @@ package emu.grasscutter.scripts.data; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter public class SceneVar { public String name; public int value; diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index c476b150f..8e668c48a 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -1,14 +1,28 @@ package emu.grasscutter.scripts.serializer; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; + import com.esotericsoftware.reflectasm.ConstructorAccess; +import com.esotericsoftware.reflectasm.MethodAccess; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.ScriptUtils; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.experimental.FieldDefaults; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + public class LuaSerializer implements Serializer { - + + private final static Map, MethodAccess> methodAccessCache = new ConcurrentHashMap<>(); + private final static Map, ConstructorAccess> constructorCache = new ConcurrentHashMap<>(); + private final static Map, Map> fieldMetaCache = new ConcurrentHashMap<>(); + @Override public List toList(Class type, Object obj) { return serializeList(type, (LuaTable) obj); @@ -20,7 +34,11 @@ public class LuaSerializer implements Serializer { } public List serializeList(Class type, LuaTable table) { - List list = new ArrayList(); + List list = new ArrayList<>(); + + if (table == null) { + return list; + } try { LuaValue[] keys = table.keys(); @@ -55,7 +73,7 @@ public class LuaSerializer implements Serializer { return list; } - + public T serialize(Class type, LuaTable table) { T object = null; @@ -70,27 +88,38 @@ public class LuaSerializer implements Serializer { } try { - //noinspection ConfusingArgumentToVarargsMethod - object = type.getDeclaredConstructor().newInstance(); + if (!methodAccessCache.containsKey(type)) { + cacheType(type); + } + var methodAccess = methodAccessCache.get(type); + var fieldMetaMap = fieldMetaCache.get(type); + + object = (T) constructorCache.get(type).newInstance(); + + if (table == null) { + return object; + } LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { - Field field = object.getClass().getDeclaredField(k.checkjstring()); - - field.setAccessible(true); + var keyName = k.checkjstring(); + if(!fieldMetaMap.containsKey(keyName)){ + continue; + } + var fieldMeta = fieldMetaMap.get(keyName); LuaValue keyValue = table.get(k); if (keyValue.istable()) { - field.set(object, serialize(field.getType(), keyValue.checktable())); - } else if (field.getType().equals(float.class)) { - field.setFloat(object, keyValue.tofloat()); - } else if (field.getType().equals(int.class)) { - field.setInt(object, keyValue.toint()); - } else if (field.getType().equals(String.class)) { - field.set(object, keyValue.tojstring()); + methodAccess.invoke(object, fieldMeta.index, serialize(fieldMeta.getType(), keyValue.checktable())); + } else if (fieldMeta.getType().equals(float.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.tofloat()); + } else if (fieldMeta.getType().equals(int.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.toint()); + } else if (fieldMeta.getType().equals(String.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring()); } else { - field.set(object, keyValue); + methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring()); } } catch (Exception ex) { //ex.printStackTrace(); @@ -98,9 +127,64 @@ public class LuaSerializer implements Serializer { } } } catch (Exception e) { + Grasscutter.getLogger().info(ScriptUtils.toMap(table).toString()); e.printStackTrace(); } return object; } + + public Map cacheType(Class type){ + if(fieldMetaCache.containsKey(type)) { + return fieldMetaCache.get(type); + } + if(!constructorCache.containsKey(type)){ + constructorCache.putIfAbsent(type, ConstructorAccess.get(type)); + } + var methodAccess = Optional.ofNullable(methodAccessCache.get(type)).orElse(MethodAccess.get(type)); + methodAccessCache.putIfAbsent(type, methodAccess); + + var fieldMetaMap = new HashMap(); + var methodNameSet = new HashSet<>(Arrays.stream(methodAccess.getMethodNames()).toList()); + + Arrays.stream(type.getDeclaredFields()) + .filter(field -> methodNameSet.contains(getSetterName(field.getName()))) + .forEach(field -> { + var setter = getSetterName(field.getName()); + var index = methodAccess.getIndex(setter); + fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType())); + }); + + Arrays.stream(type.getFields()) + .filter(field -> !fieldMetaMap.containsKey(field.getName())) + .filter(field -> methodNameSet.contains(getSetterName(field.getName()))) + .forEach(field -> { + var setter = getSetterName(field.getName()); + var index = methodAccess.getIndex(setter); + fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType())); + }); + + fieldMetaCache.put(type, fieldMetaMap); + return fieldMetaMap; + } + + public String getSetterName(String fieldName){ + if(fieldName == null || fieldName.length() == 0){ + return null; + } + if(fieldName.length() == 1){ + return "set" + fieldName.toUpperCase(); + } + return "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + } + + @Data + @AllArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE) + static class FieldMeta{ + String name; + String setter; + int index; + Class type; + } } diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java index e8e0d0668..c4ceedc89 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java @@ -16,9 +16,9 @@ import java.util.List; public class ScriptMonsterSpawnService { private final SceneScriptManager sceneScriptManager; - private final List onMonsterCreatedListener = new ArrayList<>(); + public final List onMonsterCreatedListener = new ArrayList<>(); - private final List onMonsterDeadListener = new ArrayList<>(); + public final List onMonsterDeadListener = new ArrayList<>(); public ScriptMonsterSpawnService(SceneScriptManager sceneScriptManager){ this.sceneScriptManager = sceneScriptManager; @@ -39,40 +39,5 @@ public class ScriptMonsterSpawnService { public void onMonsterDead(EntityMonster entityMonster){ onMonsterDeadListener.forEach(l -> l.onNotify(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.onNotify(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 index 57d4735ba..831548716 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -37,7 +37,7 @@ public class ScriptMonsterTideService { this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead); // spawn the first turn for (int i = 0; i < this.monsterSceneLimit; i++) { - this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(group.id, getNextMonster()); + sceneScriptManager.addEntity(this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster())); } } @@ -73,7 +73,7 @@ public class ScriptMonsterTideService { monsterKillCount.incrementAndGet(); if (monsterTideCount.get() > 0) { // add more - sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(currentGroup.id, getNextMonster()); + sceneScriptManager.addEntity(sceneScriptManager.createMonster(currentGroup.id, currentGroup.block_id, getNextMonster())); } // spawn the last turn of monsters // fix the 5-2 diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 3a93150d1..1218bd67b 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -19,6 +19,7 @@ import emu.grasscutter.game.quest.ServerQuestHandler; import emu.grasscutter.game.shop.ShopManager; import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.game.world.World; +import emu.grasscutter.game.world.WorldDataManager; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.server.event.types.ServerEvent; @@ -55,25 +56,15 @@ public final class GameServer extends KcpServer { private final CommandMap commandMap; private final TaskMap taskMap; private final DropManager dropManager; - + private final WorldDataManager worldDataManager; + private final CombineManger combineManger; private final TowerScheduleManager towerScheduleManager; - private static InetSocketAddress getAdapterInetSocketAddress(){ - InetSocketAddress inetSocketAddress; - if(GAME_INFO.bindAddress.equals("")){ - inetSocketAddress=new InetSocketAddress(GAME_INFO.bindPort); - }else{ - inetSocketAddress=new InetSocketAddress( - GAME_INFO.bindAddress, - GAME_INFO.bindPort - ); - } - return inetSocketAddress; - } public GameServer() { this(getAdapterInetSocketAddress()); } + public GameServer(InetSocketAddress address) { ChannelConfig channelConfig = new ChannelConfig(); channelConfig.nodelay(true,40,2,true); @@ -104,7 +95,7 @@ public final class GameServer extends KcpServer { this.expeditionManager = new ExpeditionManager(this); this.combineManger = new CombineManger(this); this.towerScheduleManager = new TowerScheduleManager(this); - + this.worldDataManager = new WorldDataManager(this); // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); } @@ -173,11 +164,27 @@ public final class GameServer extends KcpServer { return towerScheduleManager; } + public WorldDataManager getWorldDataManager() { + return worldDataManager; + } public TaskMap getTaskMap() { return this.taskMap; } + private static InetSocketAddress getAdapterInetSocketAddress(){ + InetSocketAddress inetSocketAddress; + if(GAME_INFO.bindAddress.equals("")){ + inetSocketAddress=new InetSocketAddress(GAME_INFO.bindPort); + }else{ + inetSocketAddress=new InetSocketAddress( + GAME_INFO.bindAddress, + GAME_INFO.bindPort + ); + } + return inetSocketAddress; + } + public void registerPlayer(Player player) { getPlayers().put(player.getUid(), player); } diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index dc7930f0b..fdec9d2ca 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -3,7 +3,6 @@ package emu.grasscutter.server.game; import java.io.File; import java.net.InetSocketAddress; import java.util.Set; - import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.game.Account; @@ -17,8 +16,9 @@ import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import static emu.grasscutter.Configuration.*; + import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public class GameSession implements GameSessionManager.KcpChannel { private final GameServer server; diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java index 9fa116977..1fb7a641b 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java @@ -13,7 +13,7 @@ public class HandlerGadgetInteractReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { GadgetInteractReq req = GadgetInteractReq.parseFrom(payload); - session.getPlayer().interactWith(req.getGadgetEntityId(), req); + session.getPlayer().interactWith(req.getGadgetEntityId(), req.getOpType()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetInvestigationMonsterReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetInvestigationMonsterReq.java new file mode 100644 index 000000000..e3689a094 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetInvestigationMonsterReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetInvestigationMonsterReqOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetInvestigationMonsterRsp; + +@Opcodes(PacketOpcodes.GetInvestigationMonsterReq) +public class HandlerGetInvestigationMonsterReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var req = GetInvestigationMonsterReqOuterClass.GetInvestigationMonsterReq.parseFrom(payload); + + session.send(new PacketGetInvestigationMonsterRsp(req.getCityIdListList())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java index 452ca7118..2a133149d 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChallengeDataNotify.java @@ -1,13 +1,13 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.ChallengeDataNotifyOuterClass.ChallengeDataNotify; public class PacketChallengeDataNotify extends BasePacket { - - public PacketChallengeDataNotify(DungeonChallenge challenge, int index, int value) { + + public PacketChallengeDataNotify(WorldChallenge challenge, int index, int value) { super(PacketOpcodes.ChallengeDataNotify); ChallengeDataNotify proto = ChallengeDataNotify.newBuilder() diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java index 0c52c9d04..c02f41119 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeBeginNotify.java @@ -1,23 +1,22 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.DungeonChallengeBeginNotifyOuterClass.DungeonChallengeBeginNotify; public class PacketDungeonChallengeBeginNotify extends BasePacket { - public PacketDungeonChallengeBeginNotify(DungeonChallenge challenge) { + public PacketDungeonChallengeBeginNotify(WorldChallenge challenge) { super(PacketOpcodes.DungeonChallengeBeginNotify, true); DungeonChallengeBeginNotify proto = DungeonChallengeBeginNotify.newBuilder() .setChallengeId(challenge.getChallengeId()) .setChallengeIndex(challenge.getChallengeIndex()) .setGroupId(challenge.getGroup().id) - .addParamList(challenge.getObjective()) - .addParamList(challenge.getTimeLimit()) + .addAllParamList(challenge.getParamList()) .build(); - + this.setData(proto); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java index 1036b564c..ad1a02b9b 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonChallengeFinishNotify.java @@ -1,13 +1,13 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.DungeonChallengeFinishNotifyOuterClass.DungeonChallengeFinishNotify; public class PacketDungeonChallengeFinishNotify extends BasePacket { - - public PacketDungeonChallengeFinishNotify(DungeonChallenge challenge) { + + public PacketDungeonChallengeFinishNotify(WorldChallenge challenge) { super(PacketOpcodes.DungeonChallengeFinishNotify, true); DungeonChallengeFinishNotify proto = DungeonChallengeFinishNotify.newBuilder() @@ -15,7 +15,7 @@ public class PacketDungeonChallengeFinishNotify extends BasePacket { .setIsSuccess(challenge.isSuccess()) .setChallengeRecordType(2) .build(); - + this.setData(proto); } } 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 ff860289b..bec8b18ab 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java @@ -1,6 +1,6 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.DungeonSettleNotifyOuterClass.DungeonSettleNotify; @@ -9,7 +9,7 @@ import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNoti public class PacketDungeonSettleNotify extends BasePacket { - public PacketDungeonSettleNotify(DungeonChallenge challenge) { + public PacketDungeonSettleNotify(WorldChallenge challenge) { super(PacketOpcodes.DungeonSettleNotify); DungeonSettleNotify proto = DungeonSettleNotify.newBuilder() @@ -22,10 +22,10 @@ public class PacketDungeonSettleNotify extends BasePacket { this.setData(proto); } - public PacketDungeonSettleNotify(DungeonChallenge challenge, - boolean canJump, - boolean hasNextLevel, - int nextFloorId + public PacketDungeonSettleNotify(WorldChallenge challenge, + boolean canJump, + boolean hasNextLevel, + int nextFloorId ) { super(PacketOpcodes.DungeonSettleNotify); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java index c5e5d723e..57e5a8412 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGadgetInteractRsp.java @@ -4,20 +4,27 @@ import emu.grasscutter.game.entity.EntityBaseGadget; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.GadgetInteractRspOuterClass.GadgetInteractRsp; +import emu.grasscutter.net.proto.InterOpTypeOuterClass; import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.RetcodeOuterClass; public class PacketGadgetInteractRsp extends BasePacket { public PacketGadgetInteractRsp(EntityBaseGadget gadget, InteractType interact) { + this(gadget, interact, null); + } + public PacketGadgetInteractRsp(EntityBaseGadget gadget, InteractType interact, InterOpTypeOuterClass.InterOpType opType) { super(PacketOpcodes.GadgetInteractRsp); - GadgetInteractRsp proto = GadgetInteractRsp.newBuilder() + var proto = GadgetInteractRsp.newBuilder() .setGadgetEntityId(gadget.getId()) .setInteractType(interact) - .setGadgetId(gadget.getGadgetId()) - .build(); + .setGadgetId(gadget.getGadgetId()); + + if(opType != null){ + proto.setOpType(opType); + } - this.setData(proto); + this.setData(proto.build()); } public PacketGadgetInteractRsp() { diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetInvestigationMonsterRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetInvestigationMonsterRsp.java new file mode 100644 index 000000000..d6e921711 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetInvestigationMonsterRsp.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetActivityInfoRspOuterClass; + +import java.util.List; + +public class PacketGetInvestigationMonsterRsp extends BasePacket { + + public PacketGetInvestigationMonsterRsp(List cityIdListList) { + super(PacketOpcodes.GetInvestigationMonsterRsp); + + var resp = GetActivityInfoRspOuterClass.GetActivityInfoRsp.newBuilder(); + + + + this.setData(resp.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java new file mode 100644 index 000000000..9abcfc049 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityNPC; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GroupSuiteNotifyOuterClass; + +import java.util.List; + +public class PacketGroupSuiteNotify extends BasePacket { + + /** + * control which npc suite is loaded + */ + public PacketGroupSuiteNotify(List list) { + super(PacketOpcodes.GroupSuiteNotify); + + var proto = GroupSuiteNotifyOuterClass.GroupSuiteNotify.newBuilder(); + + list.forEach(item -> proto.putGroupMap(item.getGroupId(), item.getSuiteId())); + + this.setData(proto); + + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java index 75685a27e..37545d8ea 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java @@ -1,17 +1,23 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.LifeStateChangeNotifyOuterClass.LifeStateChangeNotify; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; -import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff; - -import java.util.ArrayList; public class PacketLifeStateChangeNotify extends BasePacket { + public PacketLifeStateChangeNotify(GameEntity target, LifeState lifeState) { + super(PacketOpcodes.LifeStateChangeNotify); + + LifeStateChangeNotify proto = LifeStateChangeNotify.newBuilder() + .setEntityId(target.getId()) + .setLifeState(lifeState.getValue()) + .build(); + + this.setData(proto); + } public PacketLifeStateChangeNotify(GameEntity attacker, GameEntity target, LifeState lifeState) { super(PacketOpcodes.LifeStateChangeNotify); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java index 5e8bfa58e..39bf4cadc 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneEntityAppearNotify.java @@ -36,7 +36,7 @@ public class PacketSceneEntityAppearNotify extends BasePacket { this(player.getTeamManager().getCurrentAvatarEntity()); } - public PacketSceneEntityAppearNotify(Collection entities, VisionType visionType) { + public PacketSceneEntityAppearNotify(Collection entities, VisionType visionType) { super(PacketOpcodes.SceneEntityAppearNotify, true); SceneEntityAppearNotify.Builder proto = SceneEntityAppearNotify.newBuilder() diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java index 14648a618..ade64cf09 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorktopOptionNotify.java @@ -1,6 +1,7 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.gadget.GadgetWorktop; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.WorktopOptionNotifyOuterClass.WorktopOptionNotify; @@ -13,8 +14,8 @@ public class PacketWorktopOptionNotify extends BasePacket { WorktopOptionNotify.Builder proto = WorktopOptionNotify.newBuilder() .setGadgetEntityId(gadget.getId()); - if (gadget.getWorktopOptions() != null) { - proto.addAllOptionList(gadget.getWorktopOptions()); + if (gadget.getContent() instanceof GadgetWorktop worktop) { + proto.addAllOptionList(worktop.getWorktopOptions()); } this.setData(proto); diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index 67496d765..fb2af55e2 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -137,6 +137,9 @@ public class ConfigContainer { public int bindPort = 22102; /* This is the port used in the default region. */ public int accessPort = 0; + /* Entities within a certain range will be loaded for the player */ + public int loadEntitiesForPlayerRange = 100; + public boolean enableScriptInBigWorld = false; public boolean enableConsole = true; public GameOptions gameOptions = new GameOptions(); public JoinOptions joinOptions = new JoinOptions(); diff --git a/src/main/java/emu/grasscutter/utils/FileUtils.java b/src/main/java/emu/grasscutter/utils/FileUtils.java index ae0be71ca..ff3c23e86 100644 --- a/src/main/java/emu/grasscutter/utils/FileUtils.java +++ b/src/main/java/emu/grasscutter/utils/FileUtils.java @@ -9,6 +9,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -103,7 +104,7 @@ public final class FileUtils { result = Arrays.stream(f.listFiles()).map(File::toPath).toList(); } - + return result; } diff --git a/src/main/java/emu/grasscutter/utils/Position.java b/src/main/java/emu/grasscutter/utils/Position.java index b9b75e25b..cb688a115 100644 --- a/src/main/java/emu/grasscutter/utils/Position.java +++ b/src/main/java/emu/grasscutter/utils/Position.java @@ -3,7 +3,7 @@ package emu.grasscutter.utils; import java.io.Serializable; import com.google.gson.annotations.SerializedName; - +import com.github.davidmoten.rtreemulti.geometry.Point; import dev.morphia.annotations.Entity; import emu.grasscutter.net.proto.VectorOuterClass.Vector; @@ -162,4 +162,20 @@ public class Position implements Serializable { .setZ(this.getZ()) .build(); } + public Point toPoint(){ + return Point.create(x,y,z); + } + + /** + * To XYZ array for Spatial Index + */ + public double[] toDoubleArray(){ + return new double[]{ x, y, z}; + } + /** + * To XZ array for Spatial Index (Blocks) + */ + public double[] toXZDoubleArray(){ + return new double[]{x, z}; + } } diff --git a/src/main/resources/defaults/data/ChestReward.json b/src/main/resources/defaults/data/ChestReward.json new file mode 100644 index 000000000..a9c847b16 --- /dev/null +++ b/src/main/resources/defaults/data/ChestReward.json @@ -0,0 +1,135 @@ +[ + { + "objNames" : [ + "SceneObj_Chest_Default_Lv1", + "SceneObj_Chest_Locked_Lv1", + "SceneObj_Chest_Bramble_Lv1", + "SceneObj_Chest_Frozen_Lv1", + "SceneObj_Chest_Rock_Lv1", + "SceneObj_EssenceChest_Default_Lv1", + "SceneObj_EssenceChest_Locked_Lv1" + ], + "advExp" : 10, + "resin" : 0, + "mora" : 257, + "sigil" : 1, + "content" : [ + { + "itemId" : 104011, + "count": 3 + }, + { + "itemId" : 104001, + "count": 1 + } + ], + "randomCount": 4, + "randomContent": [ + { + "itemId" : 11101, + "count": 1 + }, + { + "itemId" : 11201, + "count": 1 + }, + { + "itemId" : 12101, + "count": 1 + }, + { + "itemId" : 12201, + "count": 1 + }, + { + "itemId" : 13101, + "count": 1 + }, + { + "itemId" : 13201, + "count": 1 + }, + { + "itemId" : 14101, + "count": 1 + }, + { + "itemId" : 14201, + "count": 1 + }, + { + "itemId" : 15101, + "count": 1 + }, + { + "itemId" : 15201, + "count": 1 + } + ] + }, + { + "objNames" : [ + "SceneObj_Chest_Default_Lv2", + "SceneObj_Chest_Locked_Lv2", + "SceneObj_Chest_Bramble_Lv2", + "SceneObj_Chest_Frozen_Lv2" + ], + "advExp" : 20, + "resin" : 2, + "mora" : 756, + "sigil" : 2, + "content" : [ + { + "itemId" : 104012, + "count": 3 + }, + { + "itemId" : 104002, + "count": 1 + } + ], + "randomCount": 4, + "randomContent": [ + { + "itemId" : 11201, + "count": 1 + }, + { + "itemId" : 11301, + "count": 1 + }, + { + "itemId" : 12201, + "count": 1 + }, + { + "itemId" : 12301, + "count": 1 + }, + { + "itemId" : 13201, + "count": 1 + }, + { + "itemId" : 13301, + "count": 1 + }, + { + "itemId" : 14201, + "count": 1 + }, + { + "itemId" : 14301, + "count": 1 + }, + { + "itemId" : 15201, + "count": 1 + }, + { + "itemId" : 15301, + "count": 1 + } + ] + } +] \ No newline at end of file