mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-25 08:44:45 +00:00
Merge branch 'dev-world-scripts' of https://github.com/Grasscutters/Grasscutter into development
This commit is contained in:
commit
30c7bb9443
@ -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'
|
||||
|
@ -24,8 +24,8 @@ public class GameData {
|
||||
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
|
||||
private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>();
|
||||
private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private static final Int2ObjectMap<HomeworldDefaultSaveData> homeworldDefaultSaveData = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<SceneNpcBornData> npcBornData = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// ExcelConfigs
|
||||
private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>();
|
||||
@ -83,12 +83,14 @@ public class GameData {
|
||||
private static final Int2ObjectMap<ShopGoodsData> shopGoodsDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<CombineData> combineDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<RewardPreviewData> rewardPreviewDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<GatherData> gatherDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<TowerScheduleData> towerScheduleDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<ForgeData> forgeDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<FurnitureMakeConfigData> furnitureMakeConfigDataMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<InvestigationMonsterData> investigationMonsterDataMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// Cache
|
||||
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
|
||||
@ -140,9 +142,15 @@ public class GameData {
|
||||
public static Int2ObjectMap<MainQuestData> getMainQuestDataMap() {
|
||||
return mainQuestData;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<HomeworldDefaultSaveData> getHomeworldDefaultSaveData() {
|
||||
return homeworldDefaultSaveData;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<SceneNpcBornData> getSceneNpcBornData() {
|
||||
return npcBornData;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<AvatarData> getAvatarDataMap() {
|
||||
return avatarDataMap;
|
||||
}
|
||||
@ -365,9 +373,11 @@ public class GameData {
|
||||
public static Int2ObjectMap<TowerFloorData> getTowerFloorDataMap(){
|
||||
return towerFloorDataMap;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<TowerLevelData> getTowerLevelDataMap(){
|
||||
return towerLevelDataMap;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<TowerScheduleData> getTowerScheduleDataMap(){
|
||||
return towerScheduleDataMap;
|
||||
}
|
||||
@ -379,10 +389,20 @@ public class GameData {
|
||||
public static Int2ObjectMap<ForgeData> getForgeDataMap() {
|
||||
return forgeDataMap;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<HomeWorldLevelData> getHomeWorldLevelDataMap() {
|
||||
return homeWorldLevelDataMap;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<FurnitureMakeConfigData> getFurnitureMakeConfigDataMap() {
|
||||
return furnitureMakeConfigDataMap;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<GatherData> getGatherDataMap() {
|
||||
return gatherDataMap;
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<InvestigationMonsterData> getInvestigationMonsterDataMap() {
|
||||
return investigationMonsterDataMap;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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<SceneNpcBornEntry> bornPosList;
|
||||
|
||||
/**
|
||||
* Spatial Index For NPC
|
||||
*/
|
||||
transient RTree<SceneNpcBornEntry, Geometry> index;
|
||||
|
||||
/**
|
||||
* npc groups
|
||||
*/
|
||||
transient Map<Integer, SceneGroup> groups = new ConcurrentHashMap<>();
|
||||
}
|
@ -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<Integer> suiteIdList;
|
||||
}
|
49
src/main/java/emu/grasscutter/data/excels/GatherData.java
Normal file
49
src/main/java/emu/grasscutter/data/excels/GatherData.java
Normal file
@ -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() {
|
||||
|
||||
}
|
||||
}
|
@ -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<Integer> MonsterIdList;
|
||||
private List<Integer> GroupIdList;
|
||||
private int RewardPreviewId;
|
||||
private String MapMarkCreateType;
|
||||
private String MonsterCategory;
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return this.Id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
super.onLoad();
|
||||
}
|
||||
}
|
@ -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<GameItem> rollRewards() {
|
||||
List<GameItem> 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<GameItem> 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());
|
||||
}
|
||||
}
|
@ -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<Integer> paramList,
|
||||
int timeLimit, int goal,
|
||||
List<ChallengeTrigger> 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<GameItem> 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());
|
||||
}
|
||||
}
|
@ -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<Integer> paramList;
|
||||
private final int timeLimit;
|
||||
private final List<ChallengeTrigger> 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<Integer> paramList,
|
||||
int timeLimit, int goal,
|
||||
List<ChallengeTrigger> 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));
|
||||
}
|
||||
}
|
@ -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<ChallengeFactoryHandler> 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
@ -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){}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
81
src/main/java/emu/grasscutter/game/entity/EntityNPC.java
Normal file
81
src/main/java/emu/grasscutter/game/entity/EntityNPC.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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<GameItem> 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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<chestReward.getContent().size();i++){
|
||||
chest.getGadget().getScene().addItemEntity(chestReward.getContent().get(i).getItemId(), chestReward.getContent().get(i).getCount(), chest.getGadget());
|
||||
}
|
||||
|
||||
var random = new Random(System.currentTimeMillis());
|
||||
for(int i=0;i<chestReward.getRandomCount();i++){
|
||||
var index = random.nextInt(chestReward.getRandomContent().size());
|
||||
var item = chestReward.getRandomContent().get(index);
|
||||
chest.getGadget().getScene().addItemEntity(item.getItemId(), item.getCount(), chest.getGadget());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -37,7 +37,6 @@ import emu.grasscutter.game.managers.mapmark.*;
|
||||
import emu.grasscutter.game.managers.stamina.StaminaManager;
|
||||
import emu.grasscutter.game.managers.SotSManager;
|
||||
import emu.grasscutter.game.props.ActionReason;
|
||||
import emu.grasscutter.game.props.EntityType;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.game.quest.QuestManager;
|
||||
@ -988,7 +987,8 @@ public class Player {
|
||||
return this.getMailHandler().replaceMailByIndex(index, message);
|
||||
}
|
||||
|
||||
public void interactWith(int gadgetEntityId, GadgetInteractReq request) {
|
||||
|
||||
public void interactWith(int gadgetEntityId, InterOpTypeOuterClass.InterOpType opType) {
|
||||
GameEntity entity = getScene().getEntityById(gadgetEntityId);
|
||||
if (entity == null) {
|
||||
return;
|
||||
@ -1016,11 +1016,15 @@ public class Player {
|
||||
}
|
||||
}
|
||||
} else if (entity instanceof EntityGadget gadget) {
|
||||
if (gadget.getGadgetData().getType() == EntityType.RewardStatue) {
|
||||
if (scene.getChallenge() != null) {
|
||||
scene.getChallenge().getStatueDrops(this, request);
|
||||
}
|
||||
this.sendPacket(new PacketGadgetInteractRsp(gadget, InteractType.INTERACT_TYPE_OPEN_STATUE));
|
||||
|
||||
if (gadget.getContent() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shouldDelete = gadget.getContent().onInteract(this, opType);
|
||||
|
||||
if (shouldDelete) {
|
||||
entity.getScene().removeEntity(entity);
|
||||
}
|
||||
} else if (entity instanceof EntityMonster monster) {
|
||||
insectCaptureManager.arrestSmallCreature(monster);
|
||||
|
22
src/main/java/emu/grasscutter/game/world/ChestReward.java
Normal file
22
src/main/java/emu/grasscutter/game/world/ChestReward.java
Normal file
@ -0,0 +1,22 @@
|
||||
package emu.grasscutter.game.world;
|
||||
|
||||
import emu.grasscutter.game.inventory.ItemDef;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class ChestReward {
|
||||
List<String> objNames;
|
||||
int advExp;
|
||||
int resin;
|
||||
int mora;
|
||||
int sigil;
|
||||
List<ItemDef> content;
|
||||
int randomCount;
|
||||
List<ItemDef> randomContent;
|
||||
|
||||
}
|
@ -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<DungeonSettleListener> 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<? extends GameEntity> entities){
|
||||
addEntities(entities, VisionType.VISION_TYPE_BORN);
|
||||
}
|
||||
|
||||
public synchronized void addEntities(Collection<GameEntity> entities) {
|
||||
public synchronized void addEntities(Collection<? extends GameEntity> 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<GameEntity> 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<GameEntity> 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<SceneBlock> 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<SceneBlock> 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<SceneBlock> 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<SceneGroup> playerMeetGroups(Player player, SceneBlock block){
|
||||
List<SceneGroup> sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos().toDoubleArray(),
|
||||
Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange);
|
||||
|
||||
List<SceneGroup> 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<Player> 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<SceneGroup> 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<GameEntity>();
|
||||
for (SceneGroup group : groups) {
|
||||
if (group.init_config == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int suite = group.init_config.suite;
|
||||
// Load garbages
|
||||
List<SceneGadget> 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<GameEntity> toRemove = this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList();
|
||||
List<GameEntity> 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<EntityNPC> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<String, ChestInteractHandler> 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> 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<String, ChestInteractHandler> 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());
|
||||
}
|
||||
}
|
33
src/main/java/emu/grasscutter/scripts/SceneIndexManager.java
Normal file
33
src/main/java/emu/grasscutter/scripts/SceneIndexManager.java
Normal file
@ -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 <T> RTree<T, Geometry> buildIndex(int dimensions, Collection<T> elements, Function<T, Geometry> extractor){
|
||||
RTree<T, Geometry> rtree = RTree.dimensions(dimensions).create();
|
||||
return rtree.add(elements.stream().map(e -> Entry.entry(e, extractor.apply(e))).toList());
|
||||
}
|
||||
public static <T> List<T> queryNeighbors(RTree<T, Geometry> tree, double[] position, int range){
|
||||
var result = new ArrayList<T>();
|
||||
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<newPos.length;i++){
|
||||
newPos[i] += range;
|
||||
}
|
||||
return newPos;
|
||||
}
|
||||
}
|
@ -1,75 +1,70 @@
|
||||
package emu.grasscutter.scripts;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.CompiledScript;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import com.github.davidmoten.rtreemulti.RTree;
|
||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.def.MonsterData;
|
||||
import emu.grasscutter.data.def.WorldLevelData;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.game.entity.EntityNPC;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.*;
|
||||
import emu.grasscutter.scripts.service.ScriptMonsterSpawnService;
|
||||
import emu.grasscutter.scripts.service.ScriptMonsterTideService;
|
||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.luaj.vm2.LuaError;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.SceneBlock;
|
||||
import emu.grasscutter.scripts.data.SceneConfig;
|
||||
import emu.grasscutter.scripts.data.SceneGadget;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import emu.grasscutter.scripts.data.SceneInitConfig;
|
||||
import emu.grasscutter.scripts.data.SceneMonster;
|
||||
import emu.grasscutter.scripts.data.SceneRegion;
|
||||
import emu.grasscutter.scripts.data.SceneSuite;
|
||||
import emu.grasscutter.scripts.data.SceneTrigger;
|
||||
import emu.grasscutter.scripts.data.SceneVar;
|
||||
import emu.grasscutter.scripts.data.ScriptArgs;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import static emu.grasscutter.Configuration.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SceneScriptManager {
|
||||
private final Scene scene;
|
||||
private final ScriptLib scriptLib;
|
||||
private final LuaValue scriptLibLua;
|
||||
private final Map<String, Integer> variables;
|
||||
private Bindings bindings;
|
||||
private SceneConfig config;
|
||||
private List<SceneBlock> blocks;
|
||||
private SceneMeta meta;
|
||||
private boolean isInit;
|
||||
/**
|
||||
* SceneTrigger Set
|
||||
*/
|
||||
private final Map<String, SceneTrigger> triggers;
|
||||
/**
|
||||
* current triggers controlled by RefreshGroup
|
||||
*/
|
||||
private final Int2ObjectOpenHashMap<Set<SceneTrigger>> currentTriggers;
|
||||
private final Int2ObjectOpenHashMap<SceneRegion> regions;
|
||||
private Map<Integer,SceneGroup> sceneGroups;
|
||||
private SceneGroup currentGroup;
|
||||
private ScriptMonsterTideService scriptMonsterTideService;
|
||||
private ScriptMonsterSpawnService scriptMonsterSpawnService;
|
||||
|
||||
/**
|
||||
* blockid - loaded groupSet
|
||||
*/
|
||||
private Int2ObjectMap<Set<SceneGroup>> 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<SceneBlock> getBlocks() {
|
||||
return blocks;
|
||||
public Map<Integer, SceneBlock> getBlocks() {
|
||||
return meta.blocks;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getVariables() {
|
||||
@ -112,29 +94,31 @@ public class SceneScriptManager {
|
||||
public Set<SceneTrigger> getTriggersByEvent(int eventId) {
|
||||
return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>());
|
||||
}
|
||||
public void registerTrigger(List<SceneTrigger> 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<SceneTrigger> triggers) {
|
||||
triggers.forEach(this::deregisterTrigger);
|
||||
}
|
||||
public void deregisterTrigger(SceneTrigger trigger) {
|
||||
this.triggers.remove(trigger.name);
|
||||
getTriggersByEvent(trigger.event).remove(trigger);
|
||||
}
|
||||
public void resetTriggers(List<String> 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<Set<SceneGroup>> 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<Integer> blockIds = ScriptLoader.getSerializer().toList(Integer.class, bindings.get("blocks"));
|
||||
List<SceneBlock> 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<SceneVar> 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<Object> 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<SceneGadget> 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<? extends GameEntity> gameEntity){
|
||||
getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_MEET);
|
||||
}
|
||||
|
||||
public void addEntities(List<? extends GameEntity> gameEntity){
|
||||
getScene().addEntities(gameEntity);
|
||||
}
|
||||
|
||||
public RTree<SceneBlock, Geometry> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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> sceneScriptManager;
|
||||
private final FastThreadLocal<SceneGroup> 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<SceneGroup> 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<GameEntity> 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<GameEntity> 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<GameEntity> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<String, CompiledScript> scripts = new HashMap<>();
|
||||
|
||||
private static ScriptLib scriptLib;
|
||||
private static LuaValue scriptLibLua;
|
||||
/**
|
||||
* suggest GC to remove it if the memory is less
|
||||
*/
|
||||
private static Map<String, SoftReference<CompiledScript>> scriptsCache = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* sceneId - SceneMeta
|
||||
*/
|
||||
private static Map<Integer, SoftReference<SceneMeta>> 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 <T> Optional<T> tryGet(SoftReference<T> 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
28
src/main/java/emu/grasscutter/scripts/ScriptUtils.java
Normal file
28
src/main/java/emu/grasscutter/scripts/ScriptUtils.java
Normal file
@ -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<Object, Object> toMap(LuaTable table) {
|
||||
HashMap<Object, Object> 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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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<SceneGroup> groups;
|
||||
|
||||
|
||||
public int sceneId;
|
||||
public Map<Integer,SceneGroup> groups;
|
||||
public RTree<SceneGroup, Geometry> 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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneBusiness {
|
||||
public int type;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
12
src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java
Normal file
12
src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java
Normal file
@ -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<SceneGadget> gadgets;
|
||||
}
|
@ -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<Integer,SceneMonster> monsters;
|
||||
public List<SceneGadget> gadgets;
|
||||
public List<SceneTrigger> triggers;
|
||||
public Map<Integer,SceneMonster> monsters; // <ConfigId, Monster>
|
||||
public Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets>
|
||||
public Map<String, SceneTrigger> triggers;
|
||||
public Map<Integer, SceneNPC> npc; // <NpcId, NPC>
|
||||
public List<SceneRegion> regions;
|
||||
public List<SceneSuite> suites;
|
||||
public SceneInitConfig init_config;
|
||||
public List<SceneVar> 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<SceneGadget> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
75
src/main/java/emu/grasscutter/scripts/data/SceneMeta.java
Normal file
75
src/main/java/emu/grasscutter/scripts/data/SceneMeta.java
Normal file
@ -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<Integer, SceneBlock> blocks;
|
||||
|
||||
public Bindings context;
|
||||
|
||||
public RTree<SceneBlock, Geometry> 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<Integer> blockIds = ScriptLoader.getSerializer().toList(Integer.class, context.get("blocks"));
|
||||
List<SceneBlock> 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
10
src/main/java/emu/grasscutter/scripts/data/SceneNPC.java
Normal file
10
src/main/java/emu/grasscutter/scripts/data/SceneNPC.java
Normal file
@ -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;
|
||||
}
|
20
src/main/java/emu/grasscutter/scripts/data/SceneObject.java
Normal file
20
src/main/java/emu/grasscutter/scripts/data/SceneObject.java
Normal file
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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<Integer> monsters;
|
||||
public List<Integer> gadgets;
|
||||
@ -12,4 +15,5 @@ public class SceneSuite {
|
||||
|
||||
public transient List<SceneMonster> sceneMonsters;
|
||||
public transient List<SceneGadget> sceneGadgets;
|
||||
public transient List<SceneTrigger> sceneTriggers;
|
||||
}
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
|
@ -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<Class<?>, MethodAccess> methodAccessCache = new ConcurrentHashMap<>();
|
||||
private final static Map<Class<?>, ConstructorAccess<?>> constructorCache = new ConcurrentHashMap<>();
|
||||
private final static Map<Class<?>, Map<String, FieldMeta>> fieldMetaCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public <T> List<T> toList(Class<T> type, Object obj) {
|
||||
return serializeList(type, (LuaTable) obj);
|
||||
@ -20,7 +34,11 @@ public class LuaSerializer implements Serializer {
|
||||
}
|
||||
|
||||
public <T> List<T> serializeList(Class<T> type, LuaTable table) {
|
||||
List<T> list = new ArrayList();
|
||||
List<T> 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> T serialize(Class<T> 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 <T> Map<String, FieldMeta> cacheType(Class<T> 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<String, FieldMeta>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,9 @@ import java.util.List;
|
||||
public class ScriptMonsterSpawnService {
|
||||
|
||||
private final SceneScriptManager sceneScriptManager;
|
||||
private final List<ScriptMonsterListener> onMonsterCreatedListener = new ArrayList<>();
|
||||
public final List<ScriptMonsterListener> onMonsterCreatedListener = new ArrayList<>();
|
||||
|
||||
private final List<ScriptMonsterListener> onMonsterDeadListener = new ArrayList<>();
|
||||
public final List<ScriptMonsterListener> 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()));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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<Integer> cityIdListList) {
|
||||
super(PacketOpcodes.GetInvestigationMonsterRsp);
|
||||
|
||||
var resp = GetActivityInfoRspOuterClass.GetActivityInfoRsp.newBuilder();
|
||||
|
||||
|
||||
|
||||
this.setData(resp.build());
|
||||
}
|
||||
}
|
@ -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<EntityNPC> list) {
|
||||
super(PacketOpcodes.GroupSuiteNotify);
|
||||
|
||||
var proto = GroupSuiteNotifyOuterClass.GroupSuiteNotify.newBuilder();
|
||||
|
||||
list.forEach(item -> proto.putGroupMap(item.getGroupId(), item.getSuiteId()));
|
||||
|
||||
this.setData(proto);
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -36,7 +36,7 @@ public class PacketSceneEntityAppearNotify extends BasePacket {
|
||||
this(player.getTeamManager().getCurrentAvatarEntity());
|
||||
}
|
||||
|
||||
public PacketSceneEntityAppearNotify(Collection<GameEntity> entities, VisionType visionType) {
|
||||
public PacketSceneEntityAppearNotify(Collection<? extends GameEntity> entities, VisionType visionType) {
|
||||
super(PacketOpcodes.SceneEntityAppearNotify, true);
|
||||
|
||||
SceneEntityAppearNotify.Builder proto = SceneEntityAppearNotify.newBuilder()
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
135
src/main/resources/defaults/data/ChestReward.json
Normal file
135
src/main/resources/defaults/data/ChestReward.json
Normal file
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue
Block a user