[Refactoring] Use AnimeGameLua for lua parsing and lua script handling

* Also added support to read overwrite script files from an overwrite location
This commit is contained in:
hartie95 2024-01-22 01:15:14 +01:00
parent a48b55ec1d
commit 740e12b05c
146 changed files with 2969 additions and 5867 deletions

View File

@ -96,8 +96,13 @@ dependencies {
implementation group: 'org.quartz-scheduler', name: 'quartz-jobs', version: '2.3.2'
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation("org.anime_game_servers.core:gi:0.1")
implementation("org.anime_game_servers:luaj:3.0.3")
implementation("org.anime_game_servers:JNLua_GC:0.1.0")
implementation("org.anime_game_servers.lua:base:0.1")
implementation("org.anime_game_servers.lua:GIlua:0.1")
implementation("org.anime_game_servers.lua:LuaJEngine:0.1")
implementation("org.anime_game_servers.lua:JNLuaEngine:0.1")
implementation("org.anime_game_servers.multi_proto:gi-jvm:0.1.32")
@ -106,7 +111,6 @@ dependencies {
implementation group: 'io.javalin', name: 'javalin', version: '4.6.4'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.3'
//implementation("pro.streem.pbandk:pbandk-runtime:0.14.2")
protobuf files('proto/')

View File

@ -117,7 +117,6 @@ public final class Grasscutter {
// Load all resources.
Grasscutter.updateDayOfWeek();
ScriptLoader.init();
ResourceLoader.loadAll();
// Generate handbooks.

View File

@ -5,8 +5,6 @@ import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.utils.Position;
import lombok.Setter;
import lombok.val;

View File

@ -86,6 +86,7 @@ public class ConfigContainer {
public String data = "./data/";
public String packets = "./packets/";
public String scripts = "resources:Scripts/";
public String scriptOverwrites = "./overwrites/Scripts/";
public String plugins = "./plugins/";
public String cache = "./server/cache/";

View File

@ -31,7 +31,6 @@ import emu.grasscutter.game.quest.RewindData;
import emu.grasscutter.game.quest.TeleportData;
import emu.grasscutter.game.world.GroupReplacementData;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.scripts.data.DummyPoint;
import emu.grasscutter.utils.Utils;
import emu.grasscutter.data.excels.*;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@ -46,6 +45,8 @@ import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Tolerate;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.DummyPoint;
import org.anime_game_servers.gi_lua.models.scene.SceneGroupReplacement;
import javax.annotation.Nullable;
@ -188,7 +189,7 @@ public class GameData {
@Getter private static final Int2ObjectMap<DropTableExcelConfigData> dropTableExcelConfigDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<MonsterMapping> monsterMappingMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<ActivityCondGroup> activityCondGroupMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<GroupReplacementData> groupReplacements = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<SceneGroupReplacement> groupReplacements = new Int2ObjectOpenHashMap<>();
// Cache
@Getter private static final IntList scenePointIdList = new IntArrayList();

View File

@ -30,15 +30,10 @@ import emu.grasscutter.game.quest.RewindData;
import emu.grasscutter.game.quest.TeleportData;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.GroupReplacementData;
import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.SpawnDataEntry.GridBlockId;
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
import emu.grasscutter.scripts.EntityControllerScriptManager;
import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.scripts.data.DummyPoint;
import emu.grasscutter.scripts.lua_engine.ScriptType;
import emu.grasscutter.scripts.*;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.TsvUtils;
@ -48,13 +43,14 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import lombok.val;
import org.anime_game_servers.gi_lua.models.loader.SceneReplacementScriptLoadParams;
import org.anime_game_servers.gi_lua.models.loader.ShardQuestScriptLoadParams;
import org.anime_game_servers.gi_lua.models.scene.SceneGroupReplacement;
import org.reflections.Reflections;
import org.slf4j.Logger;
import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -755,7 +751,6 @@ public class ResourceLoader {
private static void loadScriptData(){
loadQuestShareConfig();
loadGroupReplacements();
loadDummyPoints();
}
private static void loadQuestShareConfig(){
@ -766,20 +761,18 @@ public class ResourceLoader {
stream.forEach(path -> {
val matcher = pattern.matcher(path.getFileName().toString());
if (!matcher.find()) return;
val questId = Integer.parseInt(matcher.group(1));
val cs = ScriptLoader.getScript("Quest/Share/"+path.getFileName().toString(), ScriptType.DATA_STORAGE);
if (cs == null) return;
try{
cs.evaluate();
val sharedQuestParams = new ShardQuestScriptLoadParams(questId);
if(!ScriptSystem.getScriptLoader().loadData(sharedQuestParams, script -> {
// these are Map<String, class>
val teleportDataMap = cs.getGlobalVariableMap("quest_data", TeleportData.class);
val rewindDataMap = cs.getGlobalVariableMap("rewind_data", RewindData.class);
val teleportDataMap = script.getGlobalVariableMap("quest_data", TeleportData.class);
val rewindDataMap = script.getGlobalVariableMap("rewind_data", RewindData.class);
// convert them to Map<Integer, class> and cache
GameData.getTeleportDataMap().putAll(teleportDataMap.entrySet().stream().collect(Collectors.toMap(entry -> Integer.valueOf(entry.getKey()), Entry::getValue)));
GameData.getRewindDataMap().putAll(rewindDataMap.entrySet().stream().collect(Collectors.toMap(entry -> Integer.valueOf(entry.getKey()), Entry::getValue)));
} catch (Throwable e){
})) {
logger.error("Error while loading Quest Share Config: {}", path.getFileName().toString());
}
});
@ -793,38 +786,6 @@ public class ResourceLoader {
}
}
private static void loadDummyPoints(){
// Load from scene scripts
try(val stream = Files.newDirectoryStream(getResourcePath("Scripts/Scene/"), p -> Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS))) {
stream.forEach(path -> {
int sceneId = Integer.parseInt(path.getFileName().toString());
val targetScript = "Scene/"+sceneId+"/scene"+sceneId+"_dummy_points.lua";
val targetPath = FileUtils.getScriptPath(targetScript);
if(Files.notExists(targetPath)) return;
val cs = ScriptLoader.getScript(targetScript, ScriptType.DATA_STORAGE);
if (cs == null) return;
try{
cs.evaluate();
val teleportDataMap = cs.getGlobalVariableMap("dummy_points",DummyPoint.class);
GameData.getDummyPointMap().put(sceneId, teleportDataMap);
logger.info("Loaded {} dummy points for scene {}.", teleportDataMap.size(), sceneId);
} catch (Throwable e){
logger.error("Error while loading Quest Share Config: {}", path.getFileName().toString());
}
});
} catch (IOException e) {
logger.error("Error loading Quest Share Config: no files found");
return;
}
if (GameData.getDummyPointMap() == null || GameData.getDummyPointMap().isEmpty()) {
logger.error("No scene dummy points loaded!");
} else {
logger.info("Loaded dummy points for {} scenes.", GameData.getDummyPointMap().size());
}
}
private static void loadGadgetMappings() {
try {
val gadgetMap = GameData.getGadgetMappingMap();
@ -931,33 +892,15 @@ public class ResourceLoader {
}
private static void loadGroupReplacements(){
val cs = ScriptLoader.getScript("Scene/groups_replacement.lua", ScriptType.DATA_STORAGE);
if (cs == null) {
logger.error("Error while loading Group Replacements: file not found");
return;
}
try{
cs.evaluate();
val scriptParams = new SceneReplacementScriptLoadParams();
if(!ScriptSystem.getScriptLoader().loadData(scriptParams, script -> {
// these are Map<String, class>
var replacementsMap = cs.getGlobalVariableMap("replacements", GroupReplacementData.class);
var replacementsMap = script.getGlobalVariableMap("replacements", SceneGroupReplacement.class);
// convert them to Map<Integer, class> and cache
GameData.getGroupReplacements().putAll(replacementsMap.entrySet().stream().collect(Collectors.toMap(entry -> Integer.valueOf(entry.getValue().getId()), Entry::getValue)));
} catch (Throwable e){
})) {
logger.error("Error while loading Group Replacements");
}
if (GameData.getGroupReplacements() == null || GameData.getGroupReplacements().isEmpty()) {
logger.error("No Group Replacements loaded!");
return;
} else {
logger.debug("Loaded {} group replacements.", GameData.getGroupReplacements().size());
GameData.getGroupReplacements().forEach((group, groups) -> {
logger.debug("{} -> {}", group, groups.getReplace_groups().stream().map(String::valueOf).collect(Collectors.joining(",")));
});
}
}
// BinOutput configs

View File

@ -2,10 +2,10 @@ package emu.grasscutter.data.binout;
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 org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;
import java.util.Map;

View File

@ -1,8 +1,6 @@
package emu.grasscutter.data.binout;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.utils.Position;
import lombok.Data;
import java.util.List;

View File

@ -5,7 +5,7 @@ import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityCondition;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestState;
import org.anime_game_servers.core.gi.enums.QuestState;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_QUEST_FINISH;

View File

@ -2,8 +2,6 @@ package emu.grasscutter.game.dungeons;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.common.PointData;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.data.excels.DungeonElementChallengeData;
import emu.grasscutter.data.excels.DungeonPassConfigData;
@ -19,8 +17,6 @@ import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketDungeonDataNotify;
import emu.grasscutter.server.packet.send.PacketDungeonReviseLevelNotify;
import emu.grasscutter.server.packet.send.PacketDungeonWayPointNotify;
@ -34,6 +30,8 @@ import lombok.NonNull;
import lombok.Setter;
import lombok.val;
import messages.scene.entity.WeeklyBossResinDiscountInfo;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import org.anime_game_servers.gi_lua.models.constants.EventType;
import javax.annotation.Nullable;
import java.util.*;

View File

@ -9,16 +9,16 @@ import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.ElementReactionType;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneTrigger;
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.RequiredArgsConstructor;
import lombok.Setter;
import lombok.val;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import org.anime_game_servers.gi_lua.models.constants.EventType;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneTrigger;
import java.util.ArrayList;
import java.util.List;
@ -58,7 +58,7 @@ public class WorldChallenge {
* Get the monster group id spawned by the challenge
*/
public int getGroupId(){
return group!=null ? group.getId() : 0;
return group!=null ? group.getGroupInfo().getId() : 0;
}
@ -184,7 +184,7 @@ public class WorldChallenge {
public void onGroupTriggerDeath(SceneTrigger trigger){
if(!inProgress()) return;
if(Optional.ofNullable(trigger.getCurrentGroup()).filter(g -> g.getId() == getGroupId()).isEmpty()) return;
if(trigger.getGroupId() != getGroupId()) return;
Optional.ofNullable(this.challengeTriggers.get(TriggerGroupTriggerTrigger.class))
.ifPresent(t -> t.onGroupTrigger(this, trigger));

View File

@ -8,8 +8,8 @@ import emu.grasscutter.game.dungeons.challenge.ChallengeScoreInfo;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.*;

View File

@ -6,7 +6,7 @@ import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;
import java.util.Map;

View File

@ -6,7 +6,7 @@ import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -7,8 +7,8 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.ElementReactionTrigger;
import emu.grasscutter.game.props.ElementReactionType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;
import java.util.stream.IntStream;

View File

@ -7,7 +7,7 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.FatherTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -7,7 +7,7 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.ElementReactionTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -6,8 +6,8 @@ import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -7,8 +7,8 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.ElementReactionTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -7,8 +7,8 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -8,8 +8,8 @@ import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_GUARD_HP;

View File

@ -7,8 +7,8 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -7,8 +7,8 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -6,7 +6,7 @@ import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.DamageCountTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -6,7 +6,7 @@ import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -7,8 +7,8 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -5,7 +5,7 @@ import emu.grasscutter.game.dungeons.challenge.ChallengeScoreInfo;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -6,7 +6,7 @@ import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.TriggerGroupTriggerTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -7,7 +7,7 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TriggerGroupTriggerTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -8,7 +8,7 @@ import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TriggerGroupTriggerTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import java.util.List;

View File

@ -5,9 +5,9 @@ import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.ElementReactionType;
import emu.grasscutter.scripts.data.SceneTrigger;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
import lombok.Getter;
import org.anime_game_servers.gi_lua.models.scene.group.SceneTrigger;
import java.util.concurrent.atomic.AtomicInteger;

View File

@ -1,7 +1,7 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.scripts.data.SceneTrigger;
import org.anime_game_servers.gi_lua.models.scene.group.SceneTrigger;
public class TriggerGroupTriggerTrigger extends ChallengeTrigger{
private final String TRIGGER_TAG;

View File

@ -12,12 +12,12 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.NonNull;
import lombok.val;
import messages.dungeon.DungeonEntryInfo;
import messages.scene.entity.WeeklyBossResinDiscountInfo;
import org.anime_game_servers.core.gi.enums.QuestState;
import java.util.*;
import java.util.stream.Stream;

View File

@ -1,7 +1,7 @@
package emu.grasscutter.game.dungeons.enums;
import emu.grasscutter.scripts.constants.IntValueEnum;
import lombok.Getter;
import org.anime_game_servers.core.base.interfaces.IntValueEnum;
public enum DungeonPassConditionType implements IntValueEnum {
DUNGEON_COND_NONE(0),

View File

@ -4,12 +4,12 @@ import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.event.entity.EntityDamageEvent;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import static emu.grasscutter.scripts.constants.EventType.EVENT_SPECIFIC_GADGET_HP_CHANGE;
import static org.anime_game_servers.gi_lua.models.constants.EventType.EVENT_SPECIFIC_GADGET_HP_CHANGE;
public abstract class EntityBaseGadget extends GameEntity {
@Getter(onMethod = @__(@Override))

View File

@ -1,8 +1,6 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityData;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.data.binout.config.fields.ConfigAbilityData;
import emu.grasscutter.data.excels.GadgetData;

View File

@ -16,9 +16,6 @@ import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.scripts.EntityControllerScriptManager;
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.PacketPlatformStartRouteNotify;
import emu.grasscutter.server.packet.send.PacketPlatformStopRouteNotify;
@ -34,6 +31,9 @@ import lombok.val;
import messages.gadget.GadgetInteractReq;
import messages.general.ability.AbilitySyncStateInfo;
import messages.scene.entity.*;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import org.anime_game_servers.gi_lua.models.constants.EventType;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGadget;
import javax.annotation.Nullable;
import java.util.ArrayList;
@ -126,8 +126,8 @@ public class EntityGadget extends EntityBaseGadget implements ConfigAbilityDataA
public void setState(int state) {
this.state = state;
//Cache the gadget state
if(metaGadget != null && metaGadget.getGroup() != null) {
var instance = getScene().getScriptManager().getCachedGroupInstanceById(metaGadget.getGroup().getId());
if(metaGadget != null && metaGadget.getGroupId() > 0) {
var instance = getScene().getScriptManager().getCachedGroupInstanceById(metaGadget.getGroupId());
if(instance != null) instance.cacheGadgetState(metaGadget, state);
}
}
@ -207,7 +207,7 @@ public class EntityGadget extends EntityBaseGadget implements ConfigAbilityDataA
SceneGroupInstance groupInstance = getScene().getScriptManager().getCachedGroupInstanceById(this.getGroupId());
if(groupInstance != null && metaGadget != null)
groupInstance.getDeadEntities().add(metaGadget.getConfig_id());
groupInstance.getDeadEntities().add(metaGadget.getConfigId());
val hostBlossom = getScene().getWorld().getHost().getBlossomManager();
val removedChest = hostBlossom.getSpawnedChest().remove(getConfigId());
@ -278,7 +278,7 @@ public class EntityGadget extends EntityBaseGadget implements ConfigAbilityDataA
gadgetInfo.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId());
if (this.metaGadget != null) {
gadgetInfo.setDraftId(this.metaGadget.getDraft_id());
gadgetInfo.setDraftId(this.metaGadget.getDraftId());
}
if(owner != null){

View File

@ -1,7 +1,6 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityData;
import emu.grasscutter.data.binout.config.ConfigEntityMonster;
import emu.grasscutter.data.binout.config.fields.ConfigAbilityData;
import emu.grasscutter.data.common.PropGrowCurve;
@ -11,17 +10,12 @@ import emu.grasscutter.data.excels.MonsterCurveData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.game.ability.AbilityManager;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.entity.interfaces.ConfigAbilityDataAbilityEntity;
import emu.grasscutter.game.entity.interfaces.StringAbilityEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneMonster;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.event.entity.EntityDamageEvent;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
@ -34,12 +28,16 @@ import messages.gadget.GadgetInteractReq;
import messages.general.ability.AbilitySyncStateInfo;
import messages.general.entity.SceneWeaponInfo;
import messages.scene.entity.*;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import org.anime_game_servers.gi_lua.models.constants.EventType;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneMonster;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import static emu.grasscutter.scripts.constants.EventType.EVENT_SPECIFIC_MONSTER_HP_CHANGE;
import static org.anime_game_servers.gi_lua.models.constants.EventType.EVENT_SPECIFIC_MONSTER_HP_CHANGE;
public class EntityMonster extends GameEntity implements StringAbilityEntity {
@Getter(onMethod = @__(@Override))
@ -123,7 +121,7 @@ public class EntityMonster extends GameEntity implements StringAbilityEntity {
ArrayList<String> abilityNames = new ArrayList<>();
val defaultAbilities = GameData.getConfigGlobalCombat().getDefaultAbilities();
//Affix abilities
Optional<SceneGroup> optionalGroup = getScene().getLoadedGroups().stream().filter(g -> g.getId() == getGroupId()).findAny();
Optional<SceneGroup> optionalGroup = getScene().getLoadedGroups().stream().filter(g -> g.getGroupInfo().getId() == getGroupId()).findAny();
List<MonsterAffixData> affixes = getAffixes(optionalGroup.orElse(null));
// first add pre add affix abilities
@ -277,7 +275,7 @@ public class EntityMonster extends GameEntity implements StringAbilityEntity {
SceneGroupInstance groupInstance = scene.getScriptManager().getGroupInstanceById(this.getGroupId());
if(groupInstance != null && metaMonster != null)
groupInstance.getDeadEntities().add(metaMonster.getConfig_id());
groupInstance.getDeadEntities().add(metaMonster.getConfigId());
scene.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_KILL_GROUP_MONSTER, this.getGroupId());
scene.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_KILL_TYPE_MONSTER, this.getMonsterData().getType().getValue());
@ -341,9 +339,9 @@ public class EntityMonster extends GameEntity implements StringAbilityEntity {
monsterInfo.setBlockId(getScene().getId());
monsterInfo.setBornType(MonsterBornType.MONSTER_BORN_DEFAULT);
if(metaMonster!=null && metaMonster.getSpecial_name_id()!=0){
monsterInfo.setTitleId(this.metaMonster.getTitle_id());
monsterInfo.setSpecialNameId(this.metaMonster.getSpecial_name_id());
if(metaMonster!=null && metaMonster.getSpecialNameId()!=0){
monsterInfo.setTitleId(this.metaMonster.getTitleId());
monsterInfo.setSpecialNameId(this.metaMonster.getSpecialNameId());
} else if (monsterData.getDescribeData() != null) {
monsterInfo.setTitleId(monsterData.getDescribeData().getTitleId());
monsterInfo.setSpecialNameId(monsterData.getSpecialNameId());

View File

@ -2,8 +2,6 @@ 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.Int2FloatMap;
import lombok.Getter;
@ -11,6 +9,7 @@ import lombok.val;
import messages.general.Vector;
import messages.general.ability.AbilitySyncStateInfo;
import messages.scene.entity.*;
import org.anime_game_servers.gi_lua.models.scene.group.SceneNPC;
import java.util.List;
@ -25,12 +24,12 @@ public class EntityNPC extends GameEntity {
public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC);
setConfigId(metaNPC.getConfig_id());
setGroupId(metaNPC.getGroup().getId());
setConfigId(metaNPC.getConfigId());
setGroupId(metaNPC.getGroupId());
setBlockId(blockId);
this.suiteId = suiteId;
this.position = metaNPC.getPos().clone();
this.rotation = metaNPC.getRot().clone();
this.position = new Position(metaNPC.getPos());
this.rotation = new Position(metaNPC.getRot());
this.metaNpc = metaNPC;
}

View File

@ -2,11 +2,11 @@ package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
import messages.scene.entity.SceneEntityInfo;
import org.anime_game_servers.gi_lua.models.scene.group.SceneRegion;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ -24,10 +24,10 @@ public class EntityRegion extends GameEntity{
public EntityRegion(Scene scene, SceneRegion region) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION);
setGroupId(region.getGroup().getId());
setBlockId(region.getGroup().block_id);
setConfigId(region.getConfig_id());
this.position = region.getPos().clone();
setGroupId(region.getGroupId());
setBlockId(region.getBlockId());
setConfigId(region.getConfigId());
this.position = new Position(region.getPos());
this.entities = ConcurrentHashMap.newKeySet();
this.newEntities = ConcurrentHashMap.newKeySet();
this.leftEntities = ConcurrentHashMap.newKeySet();
@ -45,7 +45,7 @@ public class EntityRegion extends GameEntity{
@Override
public int getEntityTypeId() {
return metaRegion.getConfig_id();
return metaRegion.getConfigId();
}
public boolean hasNewEntities() {

View File

@ -1,11 +1,9 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.dungeons.DungeonManager;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import lombok.val;
import messages.gadget.GadgetInteractReq;
@ -14,6 +12,8 @@ import messages.gadget.InteractType;
import messages.gadget.ResinCostType;
import messages.scene.entity.BossChestInfo;
import messages.scene.entity.SceneGadgetInfo;
import org.anime_game_servers.gi_lua.models.constants.ScriptGadgetState;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGadget;
import java.util.ArrayList;
import java.util.Map;
@ -60,9 +60,9 @@ public class GadgetChest extends GadgetContent {
val playersUid = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
Optional.ofNullable(getGadget().getMetaGadget())
.map(g -> g.getBoss_chest())
.map(SceneGadget::getBossChest)
.ifPresent(bossChest -> {
val chestProto = new BossChestInfo(bossChest.getMonster_config_id(), bossChest.getResin());
val chestProto = new BossChestInfo(bossChest.getMonsterConfigId(), bossChest.getResin());
// removing instead of creating new list directly below is because
// it also has to consider normal cases

View File

@ -30,16 +30,22 @@ public class BossChestInteractHandler implements ChestInteractHandler{
val worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataSystem();
val chestMetaGadget = chest.getGadget().getMetaGadget();
val monsterCfgId = chestMetaGadget.getBoss_chest().getMonster_config_id();
val groupMonsters = chestMetaGadget.getGroup().getMonsters();
val group = chestMetaGadget.getSceneMeta().getGroup(chestMetaGadget.getGroupId());
if(group == null){
Grasscutter.getLogger().warn("group is null {} unable to get cfg id {}",
chestMetaGadget.getGroupId(), chestMetaGadget.getBossChest().getMonsterConfigId());
return false;
}
val monsterCfgId = chestMetaGadget.getBossChest().getMonsterConfigId();
val groupMonsters = group.getMonsters();
if(groupMonsters == null){
Grasscutter.getLogger().warn("group monsters are null {} unable to get cfg id {}",
chestMetaGadget.getGroup().getId(), monsterCfgId);
chestMetaGadget.getGroupId(), monsterCfgId);
return false;
}
val monster = groupMonsters.get(monsterCfgId);
val reward = worldDataManager.getRewardByBossId(monster.getMonster_id());
val reward = worldDataManager.getRewardByBossId(monster.getMonsterId());
if (reward == null) {
val dungeonManager = player.getScene().getDungeonManager();
@ -47,7 +53,7 @@ public class BossChestInteractHandler implements ChestInteractHandler{
if(dungeonManager != null){
return dungeonManager.getStatueDrops(player, useCondensedResin, chest.getGadget().getGroupId());
}
Grasscutter.getLogger().warn("Could not found the reward of boss monster {}", monster.getMonster_id());
Grasscutter.getLogger().warn("Could not found the reward of boss monster {}", monster.getMonsterId());
return false;
}

View File

@ -1,13 +1,13 @@
package emu.grasscutter.game.entity.gadget.platform;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import messages.general.MathQuaternion;
import messages.scene.entity.PlatformInfo;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGadget;
public abstract class BaseRoute {
@Getter @Setter private boolean isStarted;
@ -24,14 +24,14 @@ public abstract class BaseRoute {
BaseRoute(SceneGadget gadget) {
this.startRot = new Position(gadget.getRot());
this.isStarted = gadget.isStart_route();
this.isActive = gadget.isStart_route();
this.isStarted = gadget.isStartRoute();
this.isActive = gadget.isStartRoute();
}
public static BaseRoute fromSceneGadget(SceneGadget sceneGadget) {
if (sceneGadget.getRoute_id() != 0) {
if (sceneGadget.getRouteId() != 0) {
return new ConfigRoute(sceneGadget);
} else if (sceneGadget.is_use_point_array()) {
} else if (sceneGadget.isUsePointArray()) {
return new PointArrayRoute(sceneGadget);
}
return null;

View File

@ -1,12 +1,12 @@
package emu.grasscutter.game.entity.gadget.platform;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import messages.scene.entity.MovingPlatformType;
import messages.scene.entity.PlatformInfo;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGadget;
public class ConfigRoute extends BaseRoute {
@ -14,7 +14,7 @@ public class ConfigRoute extends BaseRoute {
public ConfigRoute(SceneGadget gadget) {
super(gadget);
this.routeId = gadget.getRoute_id();
this.routeId = gadget.getRouteId();
}
public ConfigRoute(Position startRot, boolean startRoute, boolean isActive, int routeId) {

View File

@ -1,12 +1,12 @@
package emu.grasscutter.game.entity.gadget.platform;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import messages.scene.entity.MovingPlatformType;
import messages.scene.entity.PlatformInfo;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGadget;
/**
* TODO implement point array routes, read from missing resources

View File

@ -6,6 +6,7 @@ import java.util.stream.IntStream;
import java.util.stream.Stream;
import dev.morphia.annotations.Entity;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.*;
import emu.grasscutter.data.excels.BlossomRefreshData.*;
@ -16,8 +17,6 @@ import emu.grasscutter.game.player.BasePlayerDataManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.BlossomBriefInfoOuterClass;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketBlossomBriefInfoNotify;
import emu.grasscutter.server.packet.send.PacketWorldOwnerBlossomScheduleInfoNotify;
import emu.grasscutter.utils.Utils;
@ -25,6 +24,8 @@ import lombok.Getter;
import lombok.NonNull;
import lombok.val;
import messages.gadget.BlossomChestInfo;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import org.anime_game_servers.gi_lua.models.constants.EventType;
@Getter
@Entity
@ -174,6 +175,7 @@ public class BlossomManager extends BasePlayerDataManager {
public void buildNextCamp(int groupId) {
val schedule = this.blossomSchedule.remove(groupId);
if(schedule == null) return;
val world = player.getWorld();
Optional.ofNullable(GameData.getBlossomGroupsDataMap().get(schedule.getCircleCampId()))
.map(BlossomGroupsData::getNextCampId)
@ -181,14 +183,20 @@ public class BlossomManager extends BasePlayerDataManager {
// if next camp overlaps with existing schedule, get further next camp
.map(groupsData -> this.blossomSchedule.containsKey(groupsData.getNewGroupId()) ?
GameData.getBlossomGroupsDataMap().get(groupsData.getNextCampId()) : groupsData).stream()
.map(groupData -> BlossomSchedule.create(schedule, groupData, getWorldLevel()))
.map(groupData -> BlossomSchedule.create(world, schedule, groupData, getWorldLevel()))
.filter(Objects::nonNull)
.peek(newSchedule -> this.blossomSchedule.put(newSchedule.getGroupId(), newSchedule))
.peek(newSchedule -> this.player.getScene().runWhenFinished(() -> this.player.getScene().loadDynamicGroup(newSchedule.getGroupId())))
// .peek(newSchedule -> Grasscutter.getLogger().info("[BlossomManager] New {}", newSchedule))
.map(BlossomSchedule::getDecorateGroupId)
.forEach(decorateGroupId -> this.player.getScene().runWhenFinished(() -> this.player.getScene().loadDynamicGroup(decorateGroupId)));
// .forEach(decorateId -> Grasscutter.getLogger().info("[BlossomManager] New Decorate Group: {}", decorateId));
.forEach(newSchedule -> {
this.blossomSchedule.put(newSchedule.getGroupId(), newSchedule);
this.player.getScene().runWhenFinished(() -> {
this.player.getScene().loadDynamicGroup(newSchedule.getGroupId());
val decorateGroupId = newSchedule.getDecorateGroupId();
if(decorateGroupId != 0) {
Grasscutter.getLogger().debug("[BlossomManager] New Decorate Group: {}", decorateGroupId);
this.player.getScene().loadDynamicGroup(decorateGroupId);
}
});
});
notifyPlayerIcon(); // notify all camps again
}
@ -196,13 +204,20 @@ public class BlossomManager extends BasePlayerDataManager {
* Rebuild all Blossom Camp gadget (not chest gadget) for scene
* */
public void loadBlossomGroup() {
Optional.ofNullable(this.player.getScene()).ifPresent(scene -> this.blossomSchedule.values().stream()
val scene = this.player.getScene();
if (scene == null) return;
this.blossomSchedule.values().stream()
.filter(schedule -> schedule.getSceneId() == scene.getId())
.peek(schedule -> scene.loadDynamicGroup(schedule.getGroupId()))
.forEach(schedule -> {
scene.loadDynamicGroup(schedule.getGroupId());
val decorateGroupId = schedule.getDecorateGroupId();
if(decorateGroupId != 0) {
Grasscutter.getLogger().debug("[BlossomManager] Decorate Group: {}", decorateGroupId);
scene.loadDynamicGroup(decorateGroupId);
}
});
// .peek(schedule -> Grasscutter.getLogger().info("[Blossom Manager] Loading Blossom Group: {}", schedule.getGroupId()))
.map(BlossomSchedule::getDecorateGroupId)
.forEach(scene::loadDynamicGroup));
// .forEach(decorateId -> Grasscutter.getLogger().info("[Blossom Manager] Loading Decorate Group:{}", decorateId)));
}
private int getWorldLevel() {
@ -266,7 +281,7 @@ public class BlossomManager extends BasePlayerDataManager {
.map(sectionList -> IntStream.range(0, data.getRefreshCount()).mapToObj(e ->
Stream.ofNullable(groupList).filter(gl -> !gl.isEmpty()).map(Utils::drawRandomListElement)// draw a new random group
.peek(randomGroup -> appendedGroupId.add(randomGroup.getId())).peek(groupList::remove)
.map(randomGroup -> BlossomSchedule.create(data, randomGroup, getWorldLevel())) // build blossom schedule
.map(randomGroup -> BlossomSchedule.create(player.getWorld(), data, randomGroup, getWorldLevel())) // build blossom schedule
.filter(Objects::nonNull).findFirst().orElse(null))
.filter(Objects::nonNull).toList())
.findFirst().orElse(null);

View File

@ -2,17 +2,22 @@ package emu.grasscutter.game.managers.blossom;
import dev.morphia.annotations.Entity;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.BaseBlossomROSData;
import emu.grasscutter.data.excels.*;
import emu.grasscutter.game.managers.blossom.enums.BlossomRefreshType;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.BlossomBriefInfoOuterClass.BlossomBriefInfo;
import emu.grasscutter.net.proto.BlossomScheduleInfoOuterClass.BlossomScheduleInfo;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.ScriptSystem;
import emu.grasscutter.utils.Position;
import lombok.*;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import org.anime_game_servers.core.gi.models.Vector;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.*;
@ -48,19 +53,30 @@ public class BlossomSchedule implements BaseBlossomROSData {
/**
* Builder function
* */
public static BlossomSchedule create(@NotNull BaseBlossomROSData baseData, @NotNull BlossomGroupsData groupsData, int worldLevel) {
public static BlossomSchedule create(@Nonnull World world, @NotNull BaseBlossomROSData baseData, @NotNull BlossomGroupsData groupsData, int worldLevel) {
final int sceneId = Optional.ofNullable(GameData.getCityDataMap().get(groupsData.getCityId())).map(CityData::getSceneId)
.orElse(3);
val scriptSystem = world.getHost().getServer().getScriptSystem();
val scene = scriptSystem.getSceneMeta(sceneId);
return Optional.ofNullable(SceneGroup.of(groupsData.getNewGroupId()).load(sceneId))
.map(SceneGroup::getGadgets)
.map(Map::values)
.stream().flatMap(Collection::stream)
.filter(gadget -> gadget.getGadget_id() == baseData.getRefreshType().getGadgetId())
val group = scene.getGroup(groupsData.getNewGroupId());
if(group == null) {
Grasscutter.getLogger().warn("[BlossomSchedule] Unable to find group {} in scene {}", groupsData.getNewGroupId(), sceneId);
return null;
}
group.load(ScriptSystem.getScriptLoader());
val gadgets = group.getGadgets();
if(gadgets == null || gadgets.isEmpty()) {
return null;
}
return gadgets.values().stream()
.filter(gadget -> gadget.getGadgetId() == baseData.getRefreshType().getGadgetId())
.map(gadget -> BlossomSchedule.of()
.setSceneId(sceneId)
.setCityId(baseData.getCityId())
.setPosition(gadget.getPos())
.setPosition(new Position(gadget.getPos()))
.setResin(getResinCost(baseData.getRefreshType()))
.setMonsterLevel(getMonsterLevel(worldLevel))
.setRewardId(baseData.getRewardId(worldLevel))

View File

@ -60,7 +60,6 @@ import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent;
import emu.grasscutter.server.game.GameServer;

View File

@ -8,20 +8,20 @@ import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.ParentQuestState;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketOpenStateUpdateNotify;
import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify;
import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify;
import emu.grasscutter.server.packet.send.PacketSetOpenStateRsp;
import lombok.val;
import org.anime_game_servers.core.gi.enums.QuestState;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import java.util.*;
import java.util.stream.Collectors;
import static emu.grasscutter.scripts.constants.EventType.EVENT_UNLOCK_TRANS_POINT;
import static org.anime_game_servers.gi_lua.models.constants.EventType.EVENT_UNLOCK_TRANS_POINT;
// @Entity
public class PlayerProgressManager extends BasePlayerDataManager {

View File

@ -15,6 +15,7 @@ public enum ActivityType {
NONE(0),
NEW_ACTIVITY_TRIAL_AVATAR(4),
NEW_ACTIVITY_PERSONAL_LIINE(8),
NEW_ACTIVITY_ASTER(1100),
NEW_ACTIVITY_SALESMAN_MP(1205),
NEW_ACTIVITY_SUMMER_TIME(1600),
NEW_ACTIVITY_GENERAL_BANNER(2100),

View File

@ -4,11 +4,11 @@ import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import emu.grasscutter.scripts.constants.IntValueEnum;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.anime_game_servers.core.base.interfaces.IntValueEnum;
public enum CampTargetType implements IntValueEnum{
public enum CampTargetType implements IntValueEnum {
None (0),
Alliance (1),
Enemy (2),

View File

@ -4,11 +4,11 @@ import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import emu.grasscutter.scripts.constants.IntValueEnum;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import org.anime_game_servers.core.base.interfaces.IntValueEnum;
public enum ElementType implements IntValueEnum {
None (0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),

View File

@ -4,13 +4,13 @@ import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import emu.grasscutter.scripts.constants.IntValueEnum;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import org.anime_game_servers.core.base.interfaces.IntValueEnum;
public enum EntityType implements IntValueEnum{
public enum EntityType implements IntValueEnum {
None (0),
Avatar (1),
Monster (2),

View File

@ -14,6 +14,7 @@ import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import org.anime_game_servers.core.gi.enums.Element;
public enum FightProperty {
FIGHT_PROP_NONE(0),
@ -231,4 +232,31 @@ public enum FightProperty {
public static boolean isPercentage(FightProperty prop) {
return !flatProps.contains(prop);
}
public static FightProperty getCurrentEnergyProp(Element element){
return switch (element){
case FIRE -> FIGHT_PROP_CUR_FIRE_ENERGY;
case WATER -> FIGHT_PROP_CUR_WATER_ENERGY;
case GRASS -> FIGHT_PROP_CUR_GRASS_ENERGY;
case ELECTRIC -> FIGHT_PROP_CUR_ELEC_ENERGY;
case ICE -> FIGHT_PROP_CUR_ICE_ENERGY;
case WIND -> FIGHT_PROP_CUR_WIND_ENERGY;
case ROCK -> FIGHT_PROP_CUR_ROCK_ENERGY;
default -> FIGHT_PROP_NONE;
};
}
public static FightProperty getMaxEnergyProp(Element element){
return switch (element){
case FIRE -> FIGHT_PROP_MAX_FIRE_ENERGY;
case WATER -> FIGHT_PROP_MAX_WATER_ENERGY;
case GRASS -> FIGHT_PROP_MAX_GRASS_ENERGY;
case ELECTRIC -> FIGHT_PROP_MAX_ELEC_ENERGY;
case ICE -> FIGHT_PROP_MAX_ICE_ENERGY;
case FROZEN -> FIGHT_PROP_MAX_ICE_ENERGY;
case WIND -> FIGHT_PROP_MAX_WIND_ENERGY;
case ROCK -> FIGHT_PROP_MAX_ROCK_ENERGY;
default -> FIGHT_PROP_NONE;
};
}
}

View File

@ -31,6 +31,8 @@ import org.slf4j.Logger;
import javax.annotation.Nullable;
import java.util.*;
import org.anime_game_servers.core.gi.enums.QuestState;
@Entity(value = "quests", useDiscriminator = false)
public class GameMainQuest {
public static final Logger logger = Loggers.getQuestSystem();
@ -264,7 +266,7 @@ public class GameMainQuest {
if (highestActiveQuest == null) {
var firstUnstarted = getChildQuests().values().stream()
.filter(q -> q.getQuestData() != null && q.getState().getValue() != QuestState.FINISHED.getValue())
.filter(q -> q.getQuestData() != null && q.getState().getValue() != QuestState.QUEST_STATE_FINISHED.getValue())
.min(Comparator.comparingInt(a -> a.getQuestData().getOrder()));
if(firstUnstarted.isEmpty()){
// all quests are probably finished, do don't rewind and maybe also set the mainquest to finished?

View File

@ -12,10 +12,8 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.net.proto.ChapterStateOuterClass;
import emu.grasscutter.net.proto.QuestOuterClass.Quest;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.server.packet.send.PacketChapterStateNotify;
import emu.grasscutter.server.packet.send.PacketDelQuestNotify;
@ -24,6 +22,7 @@ import emu.grasscutter.utils.Utils;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import org.anime_game_servers.core.gi.enums.QuestState;
import javax.annotation.Nullable;
import javax.script.Bindings;
@ -82,7 +81,8 @@ public class GameQuest {
}
triggerData.put(newTrigger.getTriggerName(), newTrigger);
triggers.put(newTrigger.getTriggerName(), false);
SceneGroup group = SceneGroup.of(newTrigger.getGroupId()).load(newTrigger.getSceneId());
val scene = getOwner().getWorld().getSceneById(newTrigger.getSceneId());
val group = scene.getScriptManager().getGroupById(newTrigger.getGroupId());
getOwner().getWorld().getSceneById(newTrigger.getSceneId()).loadTriggerFromGroup(group, newTrigger.getTriggerName());
}
}

View File

@ -21,6 +21,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import lombok.val;
import org.anime_game_servers.core.gi.enums.QuestState;
import javax.annotation.Nonnull;

View File

@ -9,7 +9,7 @@ import emu.grasscutter.game.quest.QuestValueCond;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestCond.QUEST_COND_STATE_EQUAL;
import static emu.grasscutter.game.quest.enums.QuestState.QUEST_STATE_NONE;
import static org.anime_game_servers.core.gi.enums.QuestState.QUEST_STATE_NONE;
@QuestValueCond(QUEST_COND_STATE_EQUAL)
public class ConditionStateEqual extends BaseCondition {

View File

@ -9,7 +9,7 @@ import emu.grasscutter.game.quest.QuestValueCond;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestCond.QUEST_COND_STATE_NOT_EQUAL;
import static emu.grasscutter.game.quest.enums.QuestState.QUEST_STATE_NONE;
import static org.anime_game_servers.core.gi.enums.QuestState.QUEST_STATE_NONE;
@QuestValueCond(QUEST_COND_STATE_NOT_EQUAL)
public class ConditionStateNotEqual extends BaseCondition {

View File

@ -7,7 +7,7 @@ import emu.grasscutter.game.quest.QuestValueContent;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_QUEST_STATE_EQUAL;
import static emu.grasscutter.game.quest.enums.QuestState.QUEST_STATE_NONE;
import static org.anime_game_servers.core.gi.enums.QuestState.QUEST_STATE_NONE;
@QuestValueContent(QUEST_CONTENT_QUEST_STATE_EQUAL)
public class ContentQuestStateEqual extends BaseContent {

View File

@ -7,7 +7,7 @@ import emu.grasscutter.game.quest.QuestValueContent;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_QUEST_STATE_NOT_EQUAL;
import static emu.grasscutter.game.quest.enums.QuestState.QUEST_STATE_NONE;
import static org.anime_game_servers.core.gi.enums.QuestState.QUEST_STATE_NONE;
@QuestValueContent(QUEST_CONTENT_QUEST_STATE_NOT_EQUAL)
public class ContentQuestStateNotEqual extends BaseContent {

View File

@ -4,13 +4,13 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestSystem;
import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler;
import emu.grasscutter.data.common.quest.SubQuestData.QuestExecParam;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import lombok.val;
import org.anime_game_servers.core.gi.enums.QuestState;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import org.anime_game_servers.gi_lua.models.constants.EventType;
@QuestValueExec(QuestExec.QUEST_EXEC_NOTIFY_GROUP_LUA)
public class ExecNotifyGroupLua extends QuestExecHandler {

View File

@ -23,22 +23,26 @@ import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.scripts.SceneScriptManager;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneBlock;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneInitConfig;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.KahnsSort;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import kotlin.Pair;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import messages.gadget.SelectWorktopOptionReq;
import messages.scene.EnterType;
import messages.scene.VisionType;
import org.anime_game_servers.gi_lua.models.SceneGroupUserData;
import org.anime_game_servers.gi_lua.models.ScriptArgs;
import org.anime_game_servers.gi_lua.models.constants.EventType;
import org.anime_game_servers.gi_lua.models.scene.block.SceneBlock;
import org.anime_game_servers.gi_lua.models.scene.block.SceneGroupInfo;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneInitConfig;
import org.anime_game_servers.gi_lua.utils.GroupUtils;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@ -425,18 +429,20 @@ public class Scene {
private void checkPlayerRespawn() {
if (this.scriptManager.getConfig() == null) return;
val diePos = this.scriptManager.getConfig().getDie_y();
val diePos = this.scriptManager.getConfig().getDieY();
// Check if we need a respawn
this.players.stream().filter(p -> diePos >= p.getPosition().getY()).forEach(this::respawnPlayer);
this.entities.values().stream().filter(e -> diePos >= e.getPosition().getY()).forEach(this::killEntity);
}
private Position getDefaultLocation(Player player) {
return Optional.ofNullable(this.scriptManager.getConfig().getBorn_pos()).orElse(player.getPosition());
val bornPost = this.scriptManager.getConfig().getBeginPos();
return bornPost != null ? new Position(bornPost) : player.getPosition();
}
private Position getDefaultRot(Player player) {
return Optional.ofNullable(this.scriptManager.getConfig().getBorn_rot()).orElse(player.getRotation());
val bornPost = this.scriptManager.getConfig().getBornRot();
return bornPost != null ? new Position(bornPost) : player.getRotation();
}
private Position getRespawnLocation(Player player) {
@ -586,12 +592,6 @@ public class Scene {
}
}
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.getPosition().toXZDoubleArray(), Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange);
}
private Set<Integer> getPlayerActiveGroups(Player player) {
// consider the borders' entities of blocks, so we check if contains by index
return IntStream.range(0, 4)
@ -624,13 +624,12 @@ public class Scene {
val visible = this.players.stream().map(this::getPlayerActiveGroups)
.flatMap(Collection::stream).collect(Collectors.toSet());
this.loadedGroups.stream().filter(group -> !visible.contains(group.getId()) && !group.isDynamic_load())
.forEach(group -> unloadGroup(this.scriptManager.getBlocks().get(group.block_id), group.getId()));
this.loadedGroups.stream().map(SceneGroup::getGroupInfo).filter(group -> !visible.contains(group.getId()) && !group.isDynamicLoad())
.forEach(group -> unloadGroup(this.scriptManager.getBlocks().get(group.getBlockId()), group.getId()));
val toLoad = visible.stream().filter(g -> this.loadedGroups.stream().noneMatch(gr -> gr.getId() == g))
.filter(g -> !this.replacedGroup.contains(g)).map(g -> Optional.ofNullable(this.scriptManager.getBlocks())
.stream().map(Map::values).flatMap(Collection::stream).peek(this::loadBlock).map(b -> b.getGroups().get(g))
.filter(Objects::nonNull).filter(group -> !group.isDynamic_load()).findFirst().orElse(null))
val toLoad = visible.stream().filter(g -> this.loadedGroups.stream().noneMatch(gr -> gr.getGroupInfo().getId() == g))
.filter(g -> !this.replacedGroup.contains(g)).map(g -> Optional.ofNullable(this.scriptManager.getMeta().getGroups().get(g))
.filter(Objects::nonNull).filter(group -> !group.getGroupInfo().isDynamicLoad()).orElse(null))
.filter(Objects::nonNull).toList();
onLoadGroup(toLoad);
@ -639,12 +638,18 @@ public class Scene {
private Set<SceneGroup> onLoadBlock(SceneBlock block, List<Player> players) {
if (!block.isLoaded()) {
this.scriptManager.loadBlockFromScript(block);
Grasscutter.getLogger().info("Scene {} Block {} loaded.", getId(), block.getId());
}
return this.scriptManager.getLoadedGroupSetPerBlock().computeIfAbsent(block.getId(), f -> new HashSet<>());
}
private Pair<SceneGroupInfo, SceneGroupUserData> getReplaceableGroupsPair(SceneGroup group){
return new Pair<>(group.getGroupInfo(), scriptManager.getCachedGroupInstanceById(group.getGroupInfo().getId()));
}
private Pair<SceneGroupInfo, SceneGroupUserData> getReplaceableGroupsPair(SceneGroupInfo groupInfo){
return new Pair<>(groupInfo, scriptManager.getCachedGroupInstanceById(groupInfo.getId()));
}
/**
* Load specific (dynamic loaded) group
* don't load the group if it was replaced by other groups, TODO should probably log the failed loading reason
@ -652,20 +657,28 @@ public class Scene {
public int loadDynamicGroup(int groupId) {
return this.scriptManager.getGroupInstanceById(groupId) != null || this.replacedGroup.contains(groupId) ? -1 :
Optional.ofNullable(this.scriptManager.getGroupById(groupId))
.map(SceneGroup::getInit_config).map(SceneInitConfig::getSuite).orElse(-1);
.map(SceneGroup::getInitConfig).map(SceneInitConfig::getSuite).orElse(-1);
}
public boolean unregisterDynamicGroup(int groupId){
val group = this.scriptManager.getGroupById(groupId);
if(group == null) return false;
val groupInfo = getReplaceableGroupsPair(group);
val block = this.scriptManager.getBlocks().get(group.block_id);
val block = this.scriptManager.getBlocks().get(group.getGroupInfo().getBlockId());
unloadGroup(block, groupId);
val toRestore = Optional.ofNullable(block.getGroups().get(groupId)).map(g -> g.getReplaceableGroups(block.getGroups().values()))
.stream().flatMap(List::stream).filter(replacement -> this.replacedGroup.remove(replacement.getId())).toList();
val groupInfoList = block.getGroupInfo().values().stream()
.map(this::getReplaceableGroupsPair)
.toList();
val toRestore = Optional.ofNullable(scriptManager.getMeta().getGroups().get(groupId))
.map(g -> GroupUtils.getReplaceableGroups(groupInfo, groupInfoList, GameData.getGroupReplacements()))
.stream().flatMap(List::stream)
.filter(replacement -> this.replacedGroup.remove(replacement.getId()))
.toList();
if (!toRestore.isEmpty()) {
onLoadGroup(toRestore);
onLoadGroupInfo(toRestore);
Grasscutter.getLogger().info("Unregistered group: {}", groupId);
Grasscutter.getLogger().info("Replaced groups: {}", this.replacedGroup);
}
@ -678,66 +691,87 @@ public class Scene {
public void onRegisterGroups() {
// Create the graph
val groupList = new HashSet<Integer>();
val nodes = GameData.getGroupReplacements().values().stream()
.filter(replacement -> this.loadedGroups.stream().filter(group -> group.isDynamic_load())
.anyMatch(group -> group.getId() == replacement.id)) // dynamic groups
// .filter(replacement -> getReplacedGroup().stream().noneMatch(replacement.replace_groups::contains))
.peek(replacement -> Grasscutter.getLogger().info("Graph ordering replacement {}", replacement))
.peek(replacement -> groupList.add(replacement.id))
.peek(replacement -> groupList.addAll(replacement.replace_groups))
.map(replacement -> replacement.replace_groups
.stream().map(id -> new KahnsSort.Node(replacement.id, id)).toList())
val groupIds = this.loadedGroups.stream().map(SceneGroup::getGroupInfo).map(SceneGroupInfo::getId).toList();
val replacements = GameData.getGroupReplacements().values().stream()
.filter(replacement -> this.loadedGroups.stream()
.filter(g-> g.getGroupInfo().isDynamicLoad())
.anyMatch(group -> group.getGroupInfo().getId() == replacement.getId())).toList(); // dynamic groups
//.filter(replacement -> replacedGroup.stream().noneMatch(replacement.getReplace_groups()::contains))
replacements.forEach(replacement -> {
Grasscutter.getLogger().info("Graph ordering: replacement {}", replacement);
groupList.add(replacement.getId());
groupList.addAll(replacement.getReplaceGroups());
});
val nodes = replacements.stream()
.map(replacement -> replacement.getReplaceGroups().stream()
.map(id -> new KahnsSort.Node(replacement.getId(), id))
.toList())
.flatMap(List::stream)
.collect(Collectors.toSet());
val dynamicGroupsOrdered = KahnsSort.doSort(new KahnsSort.Graph(
nodes.stream().toList(), groupList.stream().toList()));
if(dynamicGroupsOrdered == null) {
Grasscutter.getLogger().error("Graph ordering: failed to order dynamic groups");
return;
}
// Now we can start unloading and loading groups :D
Optional.ofNullable(KahnsSort.doSort(new KahnsSort.Graph(
nodes.stream().toList(), groupList.stream().toList()))).stream().flatMap(List::stream)
.map(groupId -> this.loadedGroups.stream().filter(g -> g.getId() == groupId).findFirst()) // isGroupJoinReplacement
.filter(Optional::isPresent).map(Optional::get)
.map(targetGroup -> targetGroup.getReplaceableGroups(this.loadedGroups))
dynamicGroupsOrdered.stream()
.map(groupId -> this.loadedGroups.stream().filter(g -> g.getGroupInfo().getId() == groupId).findFirst()) // isGroupJoinReplacement
.filter(Optional::isPresent)
.map(Optional::get)
.map(targetGroup -> GroupUtils.getReplaceableGroups(getReplaceableGroupsPair(targetGroup), this.loadedGroups.stream().map(this::getReplaceableGroupsPair).toList(), GameData.getGroupReplacements()))
.flatMap(List::stream)
.filter(replacement -> !this.replacedGroup.contains(replacement.getId()))
.peek(replacement -> this.replacedGroup.add(replacement.getId()))
.peek(replacement -> Grasscutter.getLogger().info("Graph ordering: unloaded {}", replacement.getId()))
.peek(replacement -> Grasscutter.getLogger().info("Replaced groups: {}", this.replacedGroup))
.forEach(replacement -> unloadGroup(this.scriptManager.getBlocks().get(replacement.block_id), replacement.getId()));
.forEach(replacement -> {
this.replacedGroup.add(replacement.getId());
Grasscutter.getLogger().info("Graph ordering: unloaded {}", replacement.getId());
Grasscutter.getLogger().info("Replaced groups: {}", this.replacedGroup);
unloadGroup(this.scriptManager.getBlocks().get(replacement.getBlockId()), replacement.getId());
});
}
public void loadTriggerFromGroup(SceneGroup group, String triggerName) {
//Load triggers and regions
this.scriptManager.registerTrigger(group.getTriggers().values().stream().filter(p -> p.getName().contains(triggerName)).toList());
group.getRegions().values().stream().filter(q -> q.getConfig_id() == Integer.parseInt(triggerName.substring(13)))
group.getRegions().values().stream().filter(q -> q.getConfigId() == Integer.parseInt(triggerName.substring(13)))
.map(region -> new EntityRegion(this, region)).forEach(this.scriptManager::registerRegion);
}
public void onLoadGroupInfo(List<org.anime_game_servers.gi_lua.models.scene.block.SceneGroupInfo> groups) {
onLoadGroup(groups.stream().map(info -> scriptManager.getMeta().getGroup(info.getId())).toList());
}
/**
* Load specific group(s), used when this
* 1) group is came into player's sight (or visible)
* 2) group is being registered
* */
public void onLoadGroup(List<SceneGroup> groups) {
public void onLoadGroup(List<org.anime_game_servers.gi_lua.models.scene.group.SceneGroup> groups) {
if (groups == null || groups.isEmpty()) return;
groups.stream().filter(Objects::nonNull).filter(group -> !this.loadedGroups.contains(group))
// We load the script files for the groups here
.peek(this.scriptManager::loadGroupFromScript)
.forEach(group -> onLoadBlock(this.scriptManager.getBlocks().get(group.block_id), getPlayers()).add(group));
.forEach(group -> onLoadBlock(this.scriptManager.getBlocks().get(group.getGroupInfo().getBlockId()), getPlayers()).add(group));
// Spawn gadgets AFTER triggers are added
// TODO
val entities = new ArrayList<GameEntity>();
val entitiesBorn = new ArrayList<GameEntity>();
groups.stream().filter(group -> !this.loadedGroups.contains(group)).filter(group -> group.getInit_config() != null)
.map(group -> Optional.ofNullable(this.scriptManager.getCachedGroupInstanceById(group.getId()))
groups.stream().filter(group -> !this.loadedGroups.contains(group)).filter(group -> group.getInitConfig() != null)
.map(group -> Optional.ofNullable(this.scriptManager.getCachedGroupInstanceById(group.getGroupInfo().getId()))
.stream().peek(cachedInstance -> cachedInstance.setLuaGroup(group))
.findFirst().orElse(this.scriptManager.getGroupInstanceById(group.getId())))
.findFirst().orElse(this.scriptManager.getGroupInstanceById(group.getGroupInfo().getId())))
.peek(gi -> this.loadedGroups.add(gi.getLuaGroup())) // Load suites
.forEach(gi -> this.scriptManager.refreshGroup(gi, 0, false, entitiesBorn)); //This is what the official server does
this.scriptManager.meetEntities(entities);
this.scriptManager.addEntities(entitiesBorn);
groups.forEach(g -> this.scriptManager.callEvent(new ScriptArgs(g.getId(), EventType.EVENT_GROUP_LOAD, g.getId())));
groups.forEach(g -> this.scriptManager.callEvent(new ScriptArgs(g.getGroupInfo().getId(), EventType.EVENT_GROUP_LOAD, g.getGroupInfo().getId())));
Grasscutter.getLogger().info("Scene {} loaded {} group(s)", getId(), groups.size());
}
@ -752,7 +786,7 @@ public class Scene {
e.getBlockId() == block.getId() && e.getGroupId() == groupId).toList(), VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
SceneGroup group = block.getGroups().get(groupId);
SceneGroup group = scriptManager.getMeta().getGroups().get(groupId);
val triggers = group.getTriggers();
if (triggers != null) {
triggers.values().forEach(getScriptManager()::deregisterTrigger);

View File

@ -5,6 +5,9 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.anime_game_servers.gi_lua.models.SceneGroupUserData;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGadget;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity;
@ -12,13 +15,12 @@ import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.Nullable;
@Entity(value = "group_instances", useDiscriminator = false)
public class SceneGroupInstance {
public class SceneGroupInstance implements SceneGroupUserData {
@Id private ObjectId id;
@Indexed private int ownerUid; //This group is owned by the host player
@ -34,10 +36,11 @@ public class SceneGroupInstance {
@Getter private Map<String, Integer> cachedVariables;
@Getter @Setter private int lastTimeRefreshed;
@Nullable @Getter @Setter private Boolean isReplaceable;
public SceneGroupInstance(SceneGroup group, Player owner) {
this.luaGroup = group;
this.groupId = group.getId();
this.groupId = group.getGroupInfo().getId();
this.targetSuiteId = 0;
this.activeSuiteId = 0;
this.lastTimeRefreshed = 0;
@ -58,7 +61,8 @@ public class SceneGroupInstance {
public void setLuaGroup(SceneGroup group) {
this.luaGroup = group;
this.groupId = group.getId();
this.groupId = group.getGroupInfo().getId();
initWithSceneGroup(group.getGroupInfo());
}
public boolean isCached() {
@ -72,15 +76,26 @@ public class SceneGroupInstance {
public void cacheGadgetState(SceneGadget g, int state) {
if(g.isPersistent()) //Only cache when is persistent
cachedGadgetStates.put(g.getConfig_id(), state);
cachedGadgetStates.put(g.getConfigId(), state);
}
public int getCachedGadgetState(SceneGadget g) {
Integer state = cachedGadgetStates.getOrDefault(g.getConfig_id(), null);
Integer state = cachedGadgetStates.getOrDefault(g.getConfigId(), null);
return (state == null) ? g.getState() : state;
}
public void save() {
DatabaseHelper.saveGroupInstance(this);
}
@Nullable
@Override
public Boolean isReplaceable() {
return isReplaceable;
}
@Override
public void setReplaceable(@Nullable Boolean isReplaceable) {
this.isReplaceable = isReplaceable;
}
}

View File

@ -310,8 +310,8 @@ public class World implements Iterable<Player> {
// }
val config = newScene.getScriptManager().getConfig();
if (teleportProperties.getTeleportTo() == null && config != null) {
Optional.ofNullable(config.getBorn_pos()).map(Position::new).ifPresent(teleportProperties::setTeleportTo);
Optional.ofNullable(config.getBorn_rot()).map(Position::new).ifPresent(teleportProperties::setTeleportRot);
Optional.ofNullable(config.getBeginPos()).map(Position::new).ifPresent(teleportProperties::setTeleportTo);
Optional.ofNullable(config.getBornRot()).map(Position::new).ifPresent(teleportProperties::setTeleportRot);
}
// Set player position and rotation

View File

@ -11,12 +11,13 @@ import emu.grasscutter.game.entity.gadget.chest.ChestInteractHandler;
import emu.grasscutter.game.entity.gadget.chest.NormalChestInteractHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.InvestigationMonsterOuterClass;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneMonster;
import emu.grasscutter.scripts.ScriptSystem;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.Position;
import lombok.val;
import org.luaj.vm2.LuaError;
import org.anime_game_servers.gi_lua.models.scene.group.SceneGroup;
import org.anime_game_servers.gi_lua.models.scene.group.SceneMonster;
import java.util.HashMap;
import java.util.List;
@ -68,9 +69,15 @@ public class WorldDataSystem extends BaseGameSystem {
private SceneGroup getInvestigationGroup(int sceneId, int groupId) {
val key = sceneId + "_" + groupId;
val sceneMeta = getServer().getScriptSystem().getSceneMeta(sceneId);
if (!sceneInvestigationGroupMap.containsKey(key)) {
try{
val group = SceneGroup.of(groupId).load(sceneId);
val group = sceneMeta.getGroup(groupId);
if(group == null){
Grasscutter.getLogger().error("Null investigationGroup {} in scene{}:", groupId, sceneId);
return null;
}
group.load(ScriptSystem.getScriptLoader());
sceneInvestigationGroupMap.putIfAbsent(key, group);
return group;
} catch (Exception luaError){
@ -98,6 +105,7 @@ public class WorldDataSystem extends BaseGameSystem {
var groupId = imd.getGroupIdList().get(0);
var monsterId = imd.getMonsterIdList().get(0);
var sceneId = imd.getCityData().getSceneId();
// scene id of the city doesn't match the scene id of the investigation group in some cases ( e.g. investigation data 37 has city 1 (scene 3) but group 155005095 is from scene 5)
var group = getInvestigationGroup(sceneId, groupId);
if (group == null || group.getMonsters() == null) {
@ -105,7 +113,7 @@ public class WorldDataSystem extends BaseGameSystem {
}
val monsterOpt = group.getMonsters().values().stream()
.filter(x -> x.getMonster_id() == monsterId)
.filter(x -> x.getMonsterId() == monsterId)
.findFirst();
if (monsterOpt.isEmpty()) {
return null;
@ -128,13 +136,13 @@ public class WorldDataSystem extends BaseGameSystem {
.setIsAlive(true)
.setNextRefreshTime(Integer.MAX_VALUE)
.setRefreshInterval(Integer.MAX_VALUE)
.setPos(monsterPos.toProtoOld());
.setPos(new Position(monsterPos).toProtoOld());
if ("Boss".equals(imd.getMonsterCategory())) {
var bossChest = group.searchBossChestInGroup();
if (bossChest.isPresent()) {
builder.setResin(bossChest.get().getResin());
builder.setBossChestNum(bossChest.get().getTake_num());
builder.setBossChestNum(bossChest.get().getTakeNum());
}
}
return builder.build();

View File

@ -2,8 +2,8 @@ package emu.grasscutter.scripts;
import emu.grasscutter.Loggers;
import emu.grasscutter.scripts.data.controller.EntityController;
import emu.grasscutter.scripts.lua_engine.ScriptType;
import lombok.val;
import org.anime_game_servers.gi_lua.models.loader.GadgetScriptLoadParams;
import org.slf4j.Logger;
import java.io.IOException;
@ -22,6 +22,7 @@ public class EntityControllerScriptManager {
}
private static void cacheGadgetControllers(){
val scriptLoader = ScriptSystem.getScriptLoader();
try (val stream = Files.newDirectoryStream(getScriptPath("Gadget/"), "*.lua")) {
stream.forEach(path -> {
val fileName = path.getFileName().toString();
@ -30,16 +31,10 @@ public class EntityControllerScriptManager {
return;
val controllerName = fileName.substring(0, fileName.length()-4);
val cs = ScriptLoader.getScript("Gadget/"+fileName, ScriptType.EXECUTABLE);
if (cs == null)
return;
try{
cs.evaluate();
val scriptParams = new GadgetScriptLoadParams(fileName);
scriptLoader.loadData(scriptParams, cs -> {
gadgetController.put(controllerName, new EntityController(cs));
} catch (Throwable e){
logger.error("Error while loading gadget controller: {}", fileName, e);
}
});
});
logger.info("Loaded {} gadget controllers", gadgetController.size());

View File

@ -12,29 +12,35 @@ import emu.grasscutter.data.server.Grid;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.entity.gadget.platform.BaseRoute;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestGroupSuite;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.*;
import emu.grasscutter.scripts.lua_engine.LuaValue;
import emu.grasscutter.scripts.lua_engine.mock_results.BooleanLuaValue;
import emu.grasscutter.scripts.lua_engine.GroupEventLuaContext;
import emu.grasscutter.scripts.lua_engine.service.ScriptMonsterSpawnService;
import emu.grasscutter.scripts.lua_engine.service.ScriptMonsterTideService;
import emu.grasscutter.server.packet.send.PacketGroupSuiteNotify;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.GridPosition;
import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.Position;
import io.netty.util.concurrent.FastThreadLocalThread;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import kotlin.Pair;
import lombok.Getter;
import lombok.val;
import messages.scene.VisionType;
import org.anime_game_servers.gi_lua.models.*;
import org.anime_game_servers.gi_lua.models.constants.EventType;
import org.anime_game_servers.gi_lua.models.scene.SceneConfig;
import org.anime_game_servers.gi_lua.models.scene.SceneMeta;
import org.anime_game_servers.gi_lua.models.scene.block.SceneBlock;
import org.anime_game_servers.gi_lua.models.scene.block.SceneGroupInfo;
import org.anime_game_servers.gi_lua.models.scene.group.*;
import org.anime_game_servers.lua.engine.LuaValue;
import org.anime_game_servers.lua.models.BooleanLuaValue;
import org.slf4j.Logger;
import javax.annotation.Nonnull;
@ -49,14 +55,14 @@ import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static emu.grasscutter.scripts.constants.EventType.*;
import static emu.grasscutter.scripts.data.SceneTrigger.INF_TRIGGERS;
import static org.anime_game_servers.gi_lua.models.constants.EventType.EVENT_TIMER_EVENT;
import static org.anime_game_servers.gi_lua.models.scene.group.SceneTrigger.INF_TRIGGERS;
public class SceneScriptManager {
private static final Logger logger = Loggers.getScriptSystem();
private final Scene scene;
private final Map<String, Integer> variables;
private SceneMeta meta;
@Getter private SceneMeta meta;
private boolean isInit;
/**
* current triggers controlled by RefreshGroup
@ -154,15 +160,17 @@ public class SceneScriptManager {
currentTriggers.put(eventId, ConcurrentHashMap.newKeySet());
}
public void resetTriggersForGroupSuite(SceneGroup group, int suiteIndex) {
logger.debug("reset triggers for group {} suite {}", group.getId(), suiteIndex);
var suite = group.getSuiteByIndex(suiteIndex);
public void resetTriggersForGroupSuite(SceneGroupInstance groupInstance, int suiteIndex) {
val groupId = groupInstance.getGroupId();
val luaGroup = meta.getGroups().get(groupId);
logger.debug("reset triggers for group {} suite {}", groupId, suiteIndex);
var suite = luaGroup.getSuiteByIndex(suiteIndex);
if (suite == null) {
logger.warn("Trying to load null suite Triggers for group {} with suiteindex {}", group.getId(), suiteIndex);
logger.warn("Trying to load null suite Triggers for group {} with suiteindex {}", groupId, suiteIndex);
return;
}
var groupSceneTriggers = triggersByGroupScene.get(group.getId()+"_"+suiteIndex);
var groupSceneTriggers = triggersByGroupScene.get(groupId+"_"+suiteIndex);
if(groupSceneTriggers == null){
groupSceneTriggers = new HashSet<>();
}
@ -182,7 +190,7 @@ public class SceneScriptManager {
.add(trigger);*/
}
}
triggersByGroupScene.put(group.getId()+"_"+suiteIndex, groupSceneTriggers);
triggersByGroupScene.put(groupId+"_"+suiteIndex, groupSceneTriggers);
}
public void refreshGroup(int groupId, int suiteIndex, boolean excludePrevSuite) {
@ -207,7 +215,8 @@ public class SceneScriptManager {
return refreshGroup(groupInstance, suiteIndex, excludePrevSuite, entitiesAdded, false);
}
public int refreshGroup(SceneGroupInstance groupInstance, int suiteIndex, boolean excludePrevSuite, List<GameEntity> entitiesAdded, boolean dontLoad) {
SceneGroup group = groupInstance.getLuaGroup();
val groudId = groupInstance.getGroupId();
SceneGroup group = meta.getGroups().get(groudId);
if(suiteIndex == 0) {
if(excludePrevSuite) {
suiteIndex = group.findInitSuiteIndex(groupInstance.getActiveSuiteId());
@ -218,7 +227,7 @@ public class SceneScriptManager {
var suiteData = group.getSuiteByIndex(suiteIndex);
if (suiteData == null) {
logger.warn("Group {} suite {} not found", group.getId(), suiteIndex);
logger.warn("Group {} suite {} not found", groudId, suiteIndex);
return 0;
}
@ -228,7 +237,7 @@ public class SceneScriptManager {
if(prevSuiteIndex != 0) {
prevSuiteData = group.getSuiteByIndex(prevSuiteIndex);
if (prevSuiteData != null) {
if(prevSuiteData.isBan_refresh() && !suiteData.isBan_refresh()) {
if(prevSuiteData.isBanRefresh() && !suiteData.isBanRefresh()) {
waitForOne = true;
}
}
@ -236,7 +245,7 @@ public class SceneScriptManager {
if(waitForOne && (groupInstance.getTargetSuiteId() == 0 || prevSuiteIndex != groupInstance.getTargetSuiteId())) {
groupInstance.setTargetSuiteId(suiteIndex);
logger.debug("Group {} suite {} wating one more refresh", group.getId(), suiteIndex);
logger.debug("Group {} suite {} wating one more refresh", groudId, suiteIndex);
return 0;
}
@ -252,7 +261,7 @@ public class SceneScriptManager {
//Refesh variables here
group.getVariables().forEach(variable -> {
if(!variable.isNo_refresh())
if(!variable.isNoRefresh())
groupInstance.getCachedVariables().put(variable.getName(), variable.getValue());
});
@ -305,10 +314,10 @@ public class SceneScriptManager {
}
val monstersToSpawn = monsters.values().stream()
.filter(m -> {
var entity = scene.getEntityByConfigId(m.getConfig_id());
return (entity == null || entity.getGroupId()!=group.getId());/*&& !groupInstance.getDeadEntities().contains(entity); */ //TODO: Investigate the usage of deadEntities
var entity = scene.getEntityByConfigId(m.getConfigId(), groupId);
return (entity == null || entity.getGroupId()!=group.getGroupInfo().getId());/*&& !groupInstance.getDeadEntities().contains(entity); */ //TODO: Investigate the usage of deadEntities
})
.map(mob -> createMonster(group.getId(), group.block_id, mob))
.map(mob -> createMonster(group.getGroupInfo().getId(), group.getGroupInfo().getBlockId(), mob))
.toList();//TODO check if it interferes with bigworld or anything else
this.addEntities(monstersToSpawn);
@ -320,7 +329,7 @@ public class SceneScriptManager {
public void registerRegion(EntityRegion region) {
regions.put(region.getId(), region);
logger.debug("Registered region {} from group {}", region.getMetaRegion().getConfig_id(), region.getGroupId());
logger.debug("Registered region {} from group {}", region.getMetaRegion().getConfigId(), region.getGroupId());
}
public void registerRegionInGroupSuite(SceneGroup group, SceneSuite suite) {
suite.getSceneRegions().stream().map(region -> new EntityRegion(this.getScene(), region))
@ -328,7 +337,7 @@ public class SceneScriptManager {
}
public synchronized void deregisterRegion(SceneRegion region) {
var instance = regions.values().stream()
.filter(r -> r.getConfigId() == region.getConfig_id())
.filter(r -> r.getConfigId() == region.getConfigId())
.findFirst();
instance.ifPresent(entityRegion -> regions.remove(entityRegion.getId()));
}
@ -339,25 +348,17 @@ public class SceneScriptManager {
// TODO optimize
public SceneGroup getGroupById(int groupId) {
for (SceneBlock block : getBlocks().values()) {
getScene().loadBlock(block);
if(block.getGroups() == null){
logger.warn("Block {} in scene {} has no groups", block.getId(), getScene().getId());
continue;
}
val luaGroup = meta.getGroups().get(groupId);
var group = block.getGroups().get(groupId);
if (group == null) {
continue;
}
if (!this.sceneGroupsInstances.containsKey(groupId)) {
getScene().onLoadGroup(List.of(group));
getScene().onRegisterGroups();
}
return group;
if(luaGroup == null) {
logger.error("Group {} not found in scene {}", groupId, getScene().getId());
return null;
}
return null;
if (!this.sceneGroupsInstances.containsKey(groupId)) {
getScene().onLoadGroup(List.of(luaGroup));
getScene().onRegisterGroups();
}
return luaGroup;
}
public SceneGroupInstance getGroupInstanceById(int groupId) {
@ -378,14 +379,14 @@ public class SceneScriptManager {
return instance;
}
private static void addEntityGridPosToMap(List<Map<GridPosition, Set<Integer>>> groupPositions, Set<Integer> visionLevels, SceneObject sceneObject, SceneGroup group){
private static void addEntityGridPosToMap(List<Map<GridPosition, Set<Integer>>> groupPositions, Set<Integer> visionLevels, SceneObject sceneObject, SceneGroupInfo group){
visionLevels.add(addEntityGridPosToMap(groupPositions, sceneObject, group));
}
private static int addEntityGridPosToMap(List<Map<GridPosition, Set<Integer>>> groupPositions, SceneObject sceneObject, SceneGroup group){
private static int addEntityGridPosToMap(List<Map<GridPosition, Set<Integer>>> groupPositions, SceneObject sceneObject, SceneGroupInfo group){
val visionLevel = switch (sceneObject.getType()){
case GADGET -> Math.max(getGadgetVisionLevel(((SceneGadget)sceneObject).getGadget_id()), sceneObject.getVision_level());
case GADGET -> Math.max(getGadgetVisionLevel(((SceneGadget)sceneObject).getGadgetId()), sceneObject.getVisionLevel());
case REGION -> 0;
default -> sceneObject.getVision_level();
default -> sceneObject.getVisionLevel();
};
addGridPositionToMap(groupPositions.get(visionLevel), group.getId(), visionLevel, sceneObject.getPos());
return visionLevel;
@ -416,11 +417,13 @@ public class SceneScriptManager {
}
private void init() {
var meta = ScriptLoader.getSceneMeta(getScene().getId());
val scriptSystem = getScene().getWorld().getHost().getServer().getScriptSystem();
var meta = scriptSystem.getSceneMeta(getScene().getId());
if (meta == null) {
return;
}
this.meta = meta;
meta.loadActivity(ScriptSystem.getScriptLoader(), 2001);
// TEMP
this.isInit = true;
@ -448,63 +451,58 @@ public class SceneScriptManager {
for (int i = 0; i < 6; i++) groupPositions.add(new HashMap<>());
var visionOptions = Grasscutter.config.server.game.visionOptions;
meta.getBlocks().values().forEach(block -> {
block.load(sceneId);
if(block.getGroups() == null){
logger.error("block.groups null for block {}", block.getId());
return;
val scriptLoader = ScriptSystem.getScriptLoader();
meta.getGroups().values().stream().filter(g -> !g.getGroupInfo().isDynamicLoad()).forEach(group -> {
val groupInfo = group.getGroupInfo();
val groupId = groupInfo.getId();
group.load(scriptLoader);
//Add all entities here
Set<Integer> vision_levels = new HashSet<>();
val monsters = group.getMonsters();
if (monsters != null) {
monsters.values().forEach(m -> addEntityGridPosToMap(groupPositions, vision_levels, m, groupInfo));
} else {
logger.error("group.monsters null for group {}", groupId);
}
val gadgets = group.getGadgets();
if (gadgets != null) {
gadgets.values().forEach(g -> addEntityGridPosToMap(groupPositions, vision_levels, g, groupInfo));
} else {
logger.error("group.gadgets null for group {}", groupId);
}
logger.debug("Loading block grid " + block.getId());
block.getGroups().values().stream().filter(g -> !g.isDynamic_load()).forEach(group -> {
group.load(this.scene.getId());
//Add all entities here
Set<Integer> vision_levels = new HashSet<>();
val npcs = group.getNpcs();
if (npcs != null) {
npcs.values().forEach(n -> addEntityGridPosToMap(groupPositions, n, groupInfo));
} else {
logger.error("group.npcs null for group {}", groupId);
}
val monsters = group.getMonsters();
if (monsters != null) {
monsters.values().forEach(m -> addEntityGridPosToMap(groupPositions, vision_levels, m, group));
} else {
logger.error("group.monsters null for group {}", group.getId());
}
val gadgets = group.getGadgets();
if (gadgets != null) {
gadgets.values().forEach(g -> addEntityGridPosToMap(groupPositions, vision_levels, g, group));
} else {
logger.error("group.gadgets null for group {}", group.getId());
val regions = group.getRegions();
if (regions != null) {
regions.values().forEach(r -> addEntityGridPosToMap(groupPositions, r, groupInfo));
} else {
logger.error("group.regions null for group {}", groupId);
}
// TODO should we add those to the grid?
val garbages = group.getGarbages();
if (garbages != null && garbages.getGadgets() != null) {
garbages.getGadgets().forEach(g -> addEntityGridPosToMap(groupPositions, g, groupInfo));
}
int max_vision_level = -1;
if (!vision_levels.isEmpty()) {
for (int vision_level : vision_levels) {
if (max_vision_level == -1 || visionOptions[max_vision_level].visionRange < visionOptions[vision_level].visionRange)
max_vision_level = vision_level;
}
}
if (max_vision_level == -1) max_vision_level = 0;
val npcs = group.getNpcs();
if (npcs != null) {
npcs.values().forEach(n -> addEntityGridPosToMap(groupPositions, n, group));
} else {
logger.error("group.npcs null for group {}", group.getId());
}
val regions = group.getRegions();
if (regions != null) {
regions.values().forEach(r -> addEntityGridPosToMap(groupPositions, r, group));
} else {
logger.error("group.regions null for group {}", group.getId());
}
// TODO should we add those to the grid?
val garbages = group.getGarbages();
if (garbages != null && garbages.getGadgets() != null) {
garbages.getGadgets().forEach(g -> addEntityGridPosToMap(groupPositions, g, group));
}
int max_vision_level = -1;
if (!vision_levels.isEmpty()) {
for (int vision_level : vision_levels) {
if (max_vision_level == -1 || visionOptions[max_vision_level].visionRange < visionOptions[vision_level].visionRange)
max_vision_level = vision_level;
}
}
if (max_vision_level == -1) max_vision_level = 0;
addGridPositionToMap(groupPositions.get(max_vision_level), group.getId(), max_vision_level, group.getPos());
});
addGridPositionToMap(groupPositions.get(max_vision_level), groupInfo.getId(), max_vision_level, groupInfo.getPos());
});
var groupGrids = new ArrayList<Grid>();
@ -531,28 +529,26 @@ public class SceneScriptManager {
return isInit;
}
public void loadBlockFromScript(SceneBlock block) {
block.load(scene.getId());
}
public void loadGroupFromScript(SceneGroup group) {
group.load(getScene().getId());
val scriptLoader = ScriptSystem.getScriptLoader();
val groupId = group.getGroupInfo().getId();
group.load(scriptLoader);
this.sceneGroups.put(group.getId(), group);
if(this.getCachedGroupInstanceById(group.getId()) != null) {
this.sceneGroupsInstances.put(group.getId(), this.cachedSceneGroupsInstances.get(group.getId()));
this.cachedSceneGroupsInstances.get(group.getId()).setCached(false);
this.cachedSceneGroupsInstances.get(group.getId()).setLuaGroup(group);
this.sceneGroups.put(groupId, group);
if(this.getCachedGroupInstanceById(groupId) != null) {
this.sceneGroupsInstances.put(groupId, this.cachedSceneGroupsInstances.get(groupId));
this.cachedSceneGroupsInstances.get(groupId).setCached(false);
this.cachedSceneGroupsInstances.get(groupId).setLuaGroup(group);
} else {
var instance = new SceneGroupInstance(group, getScene().getWorld().getHost());
this.sceneGroupsInstances.put(group.getId(), instance);
this.cachedSceneGroupsInstances.put(group.getId(), instance);
this.sceneGroupsInstances.put(groupId, instance);
this.cachedSceneGroupsInstances.put(groupId, instance);
instance.save(); //Save the instance
}
if (group.getVariables() != null) {
group.getVariables().forEach(variable -> {
val variables = this.getVariables(group.getId());
val variables = this.getVariables(groupId);
if(variables != null && !variables.containsKey(variable.getName()))
variables.put(variable.getName(), variable.getValue());
});
@ -560,7 +556,7 @@ public class SceneScriptManager {
}
public void unregisterGroup(SceneGroup group) {
this.sceneGroups.remove(group.getId());
this.sceneGroups.remove(group.getGroupInfo().getId());
this.sceneGroupsInstances.values().removeIf(i -> i.getLuaGroup().equals(group));
this.cachedSceneGroupsInstances.values().stream().filter(i -> Objects.equals(i.getLuaGroup(),group)).forEach(s -> s.setCached(true));
}
@ -602,25 +598,27 @@ public class SceneScriptManager {
}
public List<EntityGadget> getGadgetsInGroupSuite(SceneGroupInstance groupInstance, SceneSuite suite) {
var group = groupInstance.getLuaGroup();
val group = groupInstance.getLuaGroup();
val groupId = group.getGroupInfo().getId();
return suite.getSceneGadgets().stream()
.filter(m -> {
var entity = scene.getEntityByConfigId(m.getConfig_id());
return (entity == null || entity.getGroupId()!=group.getId()) && (!m.isOneoff() || !m.isPersistent() || !groupInstance.getDeadEntities().contains(m.getConfig_id()));
val entity = scene.getEntityByConfigId(m.getConfigId(), groupId);
return (entity == null || entity.getGroupId()!=groupId) && (!m.isOneOff() || !m.isPersistent() || !groupInstance.getDeadEntities().contains(m.getConfigId()));
})
.map(g -> createGadget(group.getId(), group.block_id, g, groupInstance.getCachedGadgetState(g)))
.peek(g -> groupInstance.cacheGadgetState(g.getMetaGadget(), g.getState()))
.map(g -> createGadget(groupId, group.getGroupInfo().getBlockId(), g, groupInstance.getCachedGadgetState(g)))
.filter(Objects::nonNull)
.peek(g -> groupInstance.cacheGadgetState(g.getMetaGadget(), g.getState()))
.toList();
}
public List<EntityMonster> getMonstersInGroupSuite(SceneGroupInstance groupInstance, SceneSuite suite) {
var group = groupInstance.getLuaGroup();
val group = groupInstance.getLuaGroup();
val groupId = group.getGroupInfo().getId();
return suite.getSceneMonsters().stream()
.filter(m -> {
var entity = scene.getEntityByConfigId(m.getConfig_id());
return (entity == null || entity.getGroupId()!=group.getId());/*&& !groupInstance.getDeadEntities().contains(entity); */ //TODO: Investigate the usage of deadEntities
var entity = scene.getEntityByConfigId(m.getConfigId(), groupId);
return (entity == null || entity.getGroupId()!=groupId);/*&& !groupInstance.getDeadEntities().contains(entity); */ //TODO: Investigate the usage of deadEntities
}) //TODO: Add persistent monster cached data
.map(mob -> createMonster(group.getId(), group.block_id, mob))
.map(mob -> createMonster(groupId, group.getGroupInfo().getBlockId(), mob))
.filter(Objects::nonNull)
.toList();
}
@ -684,26 +682,27 @@ public class SceneScriptManager {
}
public void spawnMonstersByConfigId(SceneGroup group, int configId, int delayTime) {
// TODO delay
var entity = scene.getEntityByConfigId(configId);
if(entity!=null && entity.getGroupId() == group.getId()){
logger.debug("entity already exists failed in group {} with config {}", group.getId(), configId);
val groupId = group.getGroupInfo().getId();
var entity = scene.getEntityByConfigId(configId, groupId);
if(entity!=null && entity.getGroupId() == groupId){
logger.debug("entity already exists failed in group {} with config {}", groupId, configId);
return;
}
val groupMonsters = group.getMonsters();
if(groupMonsters == null){
logger.warn("monsters in group {} are null, unable to get configId {}", group.getId(), configId);
logger.warn("monsters in group {} are null, unable to get configId {}", groupId, configId);
return;
}
val monster = groupMonsters.get(configId);
if(monster == null){
logger.warn("configId {} not found in group {}", configId, group.getId());
logger.warn("configId {} not found in group {}", configId, groupId);
return;
}
entity = createMonster(group.getId(), group.block_id, monster);
entity = createMonster(groupId, group.getGroupInfo().getBlockId(), monster);
if(entity!=null){
getScene().addEntity(entity);
} else {
logger.warn("failed to create entity with group {} and config {}", group.getId(), configId);
logger.warn("failed to create entity with group {} and config {}", groupId, configId);
}
}
// Events
@ -724,7 +723,7 @@ public class SceneScriptManager {
try {
int eventType = params.type;
Set<SceneTrigger> relevantTriggers = this.getTriggersByEvent(eventType).stream()
.filter(t -> params.getGroupId() == 0 || t.getCurrentGroup().getId() == params.getGroupId())
.filter(t -> params.getGroupId() == 0 || t.getGroupId() == params.getGroupId())
.filter(t -> (t.getSource().isEmpty() || t.getSource().equals(params.getEventSource())))
.collect(Collectors.toSet());
@ -739,8 +738,9 @@ public class SceneScriptManager {
private boolean handleEventForTrigger(ScriptArgs params, SceneTrigger trigger ){
logger.debug("checking trigger {} for event {}", trigger.getName(), params.type);
try {
if (evaluateTriggerCondition(trigger, params)) {
callTrigger(trigger, params);
val group = trigger.getSceneMeta().getGroup(trigger.getGroupId());
if (evaluateTriggerCondition(trigger, group, params)) {
callTrigger(trigger, group, params);
return true;
} else {
logger.debug("Condition Trigger {} returned false", trigger.getCondition());
@ -760,22 +760,22 @@ public class SceneScriptManager {
* @param params
* @return true if there is no condition, otherwise the result of the condition call as boolean
*/
private boolean evaluateTriggerCondition(SceneTrigger trigger, ScriptArgs params){
private boolean evaluateTriggerCondition(SceneTrigger trigger, SceneGroup group, ScriptArgs params){
logger.trace("Call Condition Trigger {}, [{},{},{}]", trigger.getCondition(), params.param1, params.source_eid, params.target_eid);
val condition = trigger.getCondition();
if(condition == null || condition.isBlank()){
return true;
}
val ret = this.callScriptFunc(trigger.getCondition(), trigger.getCurrentGroup(), params);
val ret = this.callScriptFunc(trigger.getCondition(), group, params);
return ret.isBoolean() && ret.asBoolean();
}
private void callTrigger(SceneTrigger trigger, ScriptArgs params){
private void callTrigger(SceneTrigger trigger, SceneGroup group, ScriptArgs params){
val action = trigger.getAction();
LuaValue callResult = BooleanLuaValue.TRUE;
if(action != null && !action.isBlank()){
// the SetGroupVariableValueByGroup in tower need the param to record the first stage time
callResult = this.callScriptFunc(trigger.getAction(), trigger.getCurrentGroup(), params);
callResult = this.callScriptFunc(trigger.getAction(), group, params);
}
val invocationsCounter = triggerInvocations.get(trigger.getName());
@ -797,7 +797,7 @@ public class SceneScriptManager {
}
if(trigger.getEvent() == EVENT_TIMER_EVENT){
cancelGroupTimerEvent(trigger.getCurrentGroup().getId(), trigger.getSource());
cancelGroupTimerEvent(trigger.getGroupId(), trigger.getSource());
}
// always deregister on error, otherwise only if the count is reached
if(callResult.isBoolean() && !callResult.asBoolean() || callResult.isInteger() && callResult.asInteger()!=0
@ -822,11 +822,11 @@ public class SceneScriptManager {
return BooleanLuaValue.FALSE;
}
val context = script.getGroupEventLuaContext(group, params, this);
val context = new GroupEventLuaContext(script.getEngine(), group, params, this);
try{
return script.callMethod(funcName, context, params);
} catch (RuntimeException | ScriptException | NoSuchMethodException error){
logger.error("[LUA] call trigger failed in group {} with {},{}",group.getId(),funcName,params,error);
logger.error("[LUA] call trigger failed in group {} with {},{}",group.getGroupInfo().getId(),funcName,params,error);
return new BooleanLuaValue(false);
}
}
@ -844,29 +844,29 @@ public class SceneScriptManager {
}
public EntityGadget createGadget(int groupId, int blockId, SceneGadget g, int state) {
if (g.isOneoff()) {
if (g.isOneOff()) {
var hasEntity = getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityGadget)
.filter(e -> e.getGroupId() == g.getGroup().getId())
.filter(e -> e.getConfigId() == g.getConfig_id())
.filter(e -> e.getGroupId() == g.getGroupId())
.filter(e -> e.getConfigId() == g.getConfigId())
.findFirst();
if (hasEntity.isPresent()) {
return null;
}
}
EntityGadget entity = new EntityGadget(getScene(), g.getGadget_id(), g.getPos());
EntityGadget entity = new EntityGadget(getScene(), g.getGadgetId(), new emu.grasscutter.utils.Position(g.getPos()));
if (entity.getGadgetData() == null) {
return null;
}
entity.setBlockId(blockId);
entity.setConfigId(g.getConfig_id());
entity.setConfigId(g.getConfigId());
entity.setGroupId(groupId);
entity.getRotation().set(g.getRot());
entity.setState(state);
entity.setPointType(g.getPoint_type());
entity.setPointType(g.getPointType());
entity.setRouteConfig(BaseRoute.fromSceneGadget(g));
entity.setMetaGadget(g);
entity.buildContent();
@ -881,7 +881,7 @@ public class SceneScriptManager {
return null;
}
MonsterData data = GameData.getMonsterDataMap().get(monster.getMonster_id());
MonsterData data = GameData.getMonsterDataMap().get(monster.getMonsterId());
if (data == null) {
return null;
@ -891,7 +891,7 @@ public class SceneScriptManager {
int level = monster.getLevel();
if (getScene().getDungeonManager() != null) {
level = getScene().getDungeonManager().getLevelForMonster(monster.getConfig_id());
level = getScene().getDungeonManager().getLevelForMonster(monster.getConfigId());
} else if (getScene().getWorld().getWorldLevel() > 0) {
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel());
@ -901,12 +901,12 @@ public class SceneScriptManager {
}
// Spawn mob
EntityMonster entity = new EntityMonster(getScene(), data, monster.getPos(), level);
EntityMonster entity = new EntityMonster(getScene(), data, new emu.grasscutter.utils.Position(monster.getPos()), level);
entity.getRotation().set(monster.getRot());
entity.setGroupId(groupId);
entity.setBlockId(blockId);
entity.setConfigId(monster.getConfig_id());
entity.setPoseId(monster.getPose_id());
entity.setConfigId(monster.getConfigId());
entity.setPoseId(monster.getPoseId());
entity.setMetaMonster(monster);
this.getScriptMonsterSpawnService()
@ -920,7 +920,7 @@ public class SceneScriptManager {
}
public void meetEntities(List<? extends GameEntity> gameEntity) {
getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_TYPE_MEET);
getScene().addEntities(gameEntity, VisionType.VISION_MEET);
}
public void addEntities(List<? extends GameEntity> gameEntity) {
@ -931,16 +931,13 @@ public class SceneScriptManager {
getScene().removeEntities(gameEntity.stream().map(e -> (GameEntity) e).collect(Collectors.toList()), VisionTypeOuterClass.VisionType.VISION_TYPE_REFRESH);
}
public RTree<SceneBlock, Geometry> getBlocksIndex() {
return meta.getSceneBlockIndex();
}
public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) {
var configSet = suite.getSceneMonsters().stream()
.map(SceneObject::getConfig_id)
.map(SceneObject::getConfigId)
.collect(Collectors.toSet());
var toRemove = getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityMonster)
.filter(e -> e.getGroupId() == group.getId())
.filter(e -> e.getGroupId() == group.getGroupInfo().getId())
.filter(e -> configSet.contains(e.getConfigId()))
.toList();
@ -948,11 +945,11 @@ public class SceneScriptManager {
}
public void removeGadgetsInGroup(SceneGroup group, SceneSuite suite) {
var configSet = suite.getSceneGadgets().stream()
.map(SceneObject::getConfig_id)
.map(SceneObject::getConfigId)
.collect(Collectors.toSet());
var toRemove = getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityGadget)
.filter(e -> e.getGroupId() == group.getId())
.filter(e -> e.getGroupId() == group.getGroupInfo().getId())
.filter(e -> configSet.contains(e.getConfigId()))
.toList();
@ -961,11 +958,11 @@ public class SceneScriptManager {
public void killMonstersInGroup(SceneGroup group, SceneSuite suite) {
var configSet = suite.getSceneMonsters().stream()
.map(SceneObject::getConfig_id)
.map(SceneObject::getConfigId)
.collect(Collectors.toSet());
var toRemove = getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityMonster)
.filter(e -> e.getGroupId() == group.getId())
.filter(e -> e.getGroupId() == group.getGroupInfo().getId())
.filter(e -> configSet.contains(e.getConfigId()))
.toList();
@ -973,11 +970,11 @@ public class SceneScriptManager {
}
public void killGadgetsInGroup(SceneGroup group, SceneSuite suite) {
var configSet = suite.getSceneGadgets().stream()
.map(SceneObject::getConfig_id)
.map(SceneObject::getConfigId)
.collect(Collectors.toSet());
var toRemove = getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityGadget)
.filter(e -> e.getGroupId() == group.getId())
.filter(e -> e.getGroupId() == group.getGroupInfo().getId())
.filter(e -> configSet.contains(e.getConfigId()))
.toList();
@ -1035,7 +1032,7 @@ public class SceneScriptManager {
if(monsters == null || monsters.isEmpty()) return true;
return monsters.values().stream().noneMatch(m -> {
val entity = scene.getEntityByConfigId(m.getConfig_id());
val entity = scene.getEntityByConfigId(m.getConfigId(), groupId);
return entity != null && entity.getGroupId() == groupId;
});
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,111 +0,0 @@
package emu.grasscutter.scripts;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Loggers;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeEventMarkType;
import emu.grasscutter.game.dungeons.challenge.enums.FatherChallengeProperty;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.scripts.constants.*;
import emu.grasscutter.scripts.constants.temporary.ExhibitionPlayType;
import emu.grasscutter.scripts.constants.temporary.FlowSuiteOperatePolicy;
import emu.grasscutter.scripts.constants.temporary.GalleryProgressScoreType;
import emu.grasscutter.scripts.constants.temporary.GalleryProgressScoreUIType;
import emu.grasscutter.scripts.data.SceneMeta;
import emu.grasscutter.scripts.lua_engine.LuaEngine;
import emu.grasscutter.scripts.lua_engine.LuaScript;
import emu.grasscutter.scripts.lua_engine.ScriptType;
import emu.grasscutter.scripts.lua_engine.jnlua.JNLuaEngine;
import emu.grasscutter.scripts.lua_engine.luaj.LuaJEngine;
import lombok.Getter;
import org.slf4j.Logger;
import java.lang.ref.SoftReference;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ScriptLoader {
@Getter private static LuaEngine luaEngine;
private static final Logger logger = Loggers.getScriptSystem();
/**
* suggest GC to remove it if the memory is less
*/
private static Map<String, SoftReference<LuaScript>> scriptsCache = new ConcurrentHashMap<>();
/**
* sceneId - SceneMeta
*/
private static Map<Integer, SoftReference<SceneMeta>> sceneMetaCache = new ConcurrentHashMap<>();
public synchronized static void init() throws Exception {
if (luaEngine != null) {
throw new Exception("Script loader already initialized");
}
// Create script engine
if(Grasscutter.getConfig().server.game.useJNLua){
logger.info("Using JNLua");
luaEngine = new JNLuaEngine();
} else {
logger.info("Using LuaJ");
luaEngine = new LuaJEngine();
}
luaEngine.addGlobalEnumByIntValue("EntityType", EntityType.values());
luaEngine.addGlobalEnumByIntValue("QuestState", QuestState.values());
luaEngine.addGlobalEnumByIntValue("ElementType", ElementType.values());
luaEngine.addGlobalEnumByOrdinal("GroupKillPolicy", GroupKillPolicy.values());
luaEngine.addGlobalEnumByOrdinal("SealBattleType", SealBattleType.values());
luaEngine.addGlobalEnumByOrdinal("FatherChallengeProperty", FatherChallengeProperty.values());
luaEngine.addGlobalEnumByOrdinal("ChallengeEventMarkType", ChallengeEventMarkType.values());
luaEngine.addGlobalEnumByOrdinal("VisionLevelType", VisionLevelType.values());
luaEngine.addGlobalEnumByOrdinal("ExhibitionPlayType", ExhibitionPlayType.values());
luaEngine.addGlobalEnumByOrdinal("FlowSuiteOperatePolicy", FlowSuiteOperatePolicy.values());
luaEngine.addGlobalEnumByOrdinal("GalleryProgressScoreUIType", GalleryProgressScoreUIType.values());
luaEngine.addGlobalEnumByOrdinal("GalleryProgressScoreType", GalleryProgressScoreType.values());
luaEngine.addGlobalStaticClass("EventType", EventType.class);
luaEngine.addGlobalStaticClass("GadgetState", ScriptGadgetState.class);
luaEngine.addGlobalStaticClass("RegionShape", ScriptRegionShape.class);
luaEngine.addGlobalStaticClass("ScriptLib", ScriptLib.class);
}
public static <T> Optional<T> tryGet(SoftReference<T> softReference) {
try {
return Optional.ofNullable(softReference.get());
}catch (NullPointerException npe) {
return Optional.empty();
}
}
public static LuaScript getScript(String path, ScriptType scriptType) {
var sc = tryGet(scriptsCache.get(path));
if (sc.isPresent()) {
return sc.get();
}
try {
var script = luaEngine.getScript(path, scriptType);
if(script == null) {
logger.error("Loading script {} failed! - {}", path, "script is null");
return null;
}
scriptsCache.put(path, new SoftReference<>(script));
return script;
} catch (Exception e) {
logger.error("Loading script {} failed! - {}", path, e.getLocalizedMessage());
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;
});
}
}

View File

@ -0,0 +1,129 @@
package emu.grasscutter.scripts;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Loggers;
import emu.grasscutter.utils.FileUtils;
import lombok.Getter;
import lombok.val;
import org.anime_game_servers.gi_lua.models.loader.GIScriptLoader;
import org.anime_game_servers.gi_lua.models.loader.ScriptSource;
import org.anime_game_servers.jnlua_engine.JNLuaEngine;
import org.anime_game_servers.lua.engine.LuaEngine;
import org.anime_game_servers.lua.engine.LuaScript;
import org.anime_game_servers.lua.engine.ScriptConfig;
import org.anime_game_servers.lua.models.ScriptType;
import org.anime_game_servers.luaj_engine.LuaJEngine;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ScriptLoaderLib implements GIScriptLoader {
private static final Logger logger = Loggers.getScriptSystem();
/**
* suggest GC to remove it if the memory is less
*/
private Map<Path, SoftReference<LuaScript>> scriptsCache = new ConcurrentHashMap<>();
@Getter private LuaEngine luaEngine;
private ScriptConfig scriptConfig;
public ScriptLoaderLib() {
this.scriptConfig = fromGcConfig();
// Create script engine
if(Grasscutter.getConfig().server.game.useJNLua){
logger.info("Using JNLua");
luaEngine = new JNLuaEngine(scriptConfig);
} else {
logger.info("Using LuaJ");
luaEngine = new LuaJEngine(scriptConfig);
}
addDefaultsForEngine(luaEngine);
}
private ScriptConfig fromGcConfig(){
val gcConfig = Grasscutter.getConfig();
return new ScriptConfig(this, false);
}
public static <T> Optional<T> tryGet(SoftReference<T> softReference) {
try {
return Optional.ofNullable(softReference.get());
}catch (NullPointerException npe) {
return Optional.empty();
}
}
@Override
public Path getScriptPath(String scriptName) {
var path = FileUtils.getScriptOverwritePath(scriptName);
if(path != null && Files.exists(path)) {
return path;
}
path = FileUtils.getScriptPath(scriptName);
if(path != null && Files.exists(path)) {
return path;
}
return null;
}
@Nullable
@Override
public LuaScript getScript(@NotNull ScriptLoadParams scriptLoadParams) {
val basePath = scriptLoadParams.getBasePath();
val scriptPath = getScriptPath(basePath);
if(scriptPath == null) {
logger.error("Loading script {} failed! - {}", scriptLoadParams.getScriptName(), "scriptPath is null");
return null;
}
var sc = tryGet(scriptsCache.get(scriptPath));
if (sc.isPresent()) {
return sc.get();
}
try {
var script = luaEngine.getScript(scriptPath, scriptLoadParams.getScriptType());
if(script == null) {
logger.error("Loading script {} failed! - {}", scriptPath, "script is null");
return null;
}
scriptsCache.put(scriptPath, new SoftReference<>(script));
return script;
} catch (Exception e) {
logger.error("Loading script {} failed! - {}", scriptPath, e.getLocalizedMessage());
return null;
}
}
@Nullable
@Override
public InputStream openScript(@NotNull ScriptLoadParams scriptLoadParams) {
val basePath = scriptLoadParams.getBasePath();
val scriptPath = getScriptPath(basePath);
if(scriptPath == null) {
logger.error("Loading script {} failed! - {}", scriptLoadParams.getScriptName(), "scriptPath is null");
return null;
}
if (Files.exists(scriptPath)) {
try {
return Files.newInputStream(scriptPath);
} catch (IOException e) {
logger.error("[Lua] exception while reading file {}:", scriptPath, e);
}
} else {
logger.warn("[Lua] trying to load non existent lua file {}", scriptPath);
}
return null;
}
}

View File

@ -0,0 +1,39 @@
package emu.grasscutter.scripts;
import emu.grasscutter.scripts.scriptlib_handlers.ScriptLibControllerHandlerProvider;
import emu.grasscutter.scripts.scriptlib_handlers.ScriptLibGroupHandlerProvider;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import lombok.val;
import org.anime_game_servers.gi_lua.models.scene.SceneMeta;
import org.anime_game_servers.gi_lua.script_lib.ScriptLib;
import org.anime_game_servers.gi_lua.script_lib.ScriptLibHandler;
public class ScriptSystem extends BaseGameSystem {
@Getter private static final ScriptLoaderLib scriptLoader = new ScriptLoaderLib();
Int2ObjectMap<SceneMeta> sceneMetaCache = new Int2ObjectOpenHashMap<>();
@Getter ScriptLibHandler scriptLibHandler = new emu.grasscutter.scripts.ScriptLibHandler();
@Getter
ScriptLibControllerHandlerProvider scriptLibControllerHandlerProvider = new ScriptLibControllerHandlerProvider();
@Getter
ScriptLibGroupHandlerProvider scriptLibGroupHandlerProvider = new ScriptLibGroupHandlerProvider();
public ScriptSystem(GameServer server) {
super(server);
ScriptLib.staticHandler = new StaticScriptLibHandler();
}
public SceneMeta getSceneMeta(int sceneId) {
if(sceneMetaCache.containsKey(sceneId)){
return sceneMetaCache.get(sceneId);
}
val meta = SceneMeta.of(sceneId, scriptLoader);
sceneMetaCache.put(sceneId, meta);
return meta;
}
}

View File

@ -2,10 +2,9 @@ package emu.grasscutter.scripts;
import java.util.HashMap;
import emu.grasscutter.scripts.lua_engine.LuaEngine;
import emu.grasscutter.scripts.lua_engine.LuaTable;
import emu.grasscutter.utils.Position;
import lombok.val;
import org.anime_game_servers.lua.engine.LuaTable;
import org.luaj.vm2.LuaValue;
public class ScriptUtils {
@ -23,21 +22,6 @@ public class ScriptUtils {
return map;
}
public static LuaTable posToLua(Position position, LuaEngine engine){
var result = engine.createTable();
if(position != null){
result.set("x", position.getX());
result.set("y", position.getY());
result.set("z", position.getZ());
} else {
result.set("x", 0);
result.set("y", 0);
result.set("z", 0);
}
return result;
}
public static Position luaToPos(LuaTable position){
val result = new Position();
if(position != null){

View File

@ -0,0 +1,33 @@
package emu.grasscutter.scripts;
import emu.grasscutter.Loggers;
import emu.grasscutter.game.props.EntityIdType;
import lombok.Getter;
import org.anime_game_servers.gi_lua.script_lib.LuaContext;
import org.anime_game_servers.gi_lua.script_lib.handler.ScriptLibStaticHandler;
import org.slf4j.Logger;
public class StaticScriptLibHandler implements ScriptLibStaticHandler {
@Getter
private static final Logger logger = Loggers.getScriptSystem();
@Override
public void PrintLog(String msg) {
logger.debug("[LUA] PrintLog: {}", msg);
}
@Override
public int GetEntityType(int entityId) {
return EntityIdType.fromEntityId(entityId).getType().getValue();
}
@Override
public void PrintContextLog(LuaContext luaContext, String msg) {
if(luaContext instanceof emu.grasscutter.scripts.lua_engine.GroupEventLuaContext){
var group = ((emu.grasscutter.scripts.lua_engine.GroupEventLuaContext) luaContext).getCurrentGroup();
logger.debug("[LUA] PrintContextLog {} {}", group.getGroupInfo().getId(), msg);
return;
} else {
logger.debug("[LUA] PrintContextLog {}", msg);
}
}
}

View File

@ -1,127 +0,0 @@
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;
public static final int EVENT_ENTER_REGION = 4;
public static final int EVENT_LEAVE_REGION = 5;
public static final int EVENT_GADGET_CREATE = 6;
public static final int EVENT_GADGET_STATE_CHANGE = 7;
public static final int EVENT_DUNGEON_SETTLE = 8;
public static final int EVENT_SELECT_OPTION = 9;
public static final int EVENT_CLIENT_EXECUTE = 10;
public static final int EVENT_ANY_MONSTER_LIVE = 11;
public static final int EVENT_SPECIFIC_MONSTER_HP_CHANGE = 12;
public static final int EVENT_CITY_LEVELUP_UNLOCK_DUNGEON_ENTRY = 13;
public static final int EVENT_DUNGEON_BROADCAST_ONTIMER = 14;
public static final int EVENT_TIMER_EVENT = 15;
public static final int EVENT_CHALLENGE_SUCCESS = 16;
public static final int EVENT_CHALLENGE_FAIL = 17;
public static final int EVENT_SEAL_BATTLE_BEGIN = 18;
public static final int EVENT_SEAL_BATTLE_END = 19;
public static final int EVENT_GATHER = 20;
public static final int EVENT_QUEST_FINISH = 21;
public static final int EVENT_MONSTER_BATTLE = 22;
public static final int EVENT_CITY_LEVELUP = 23;
public static final int EVENT_CUTSCENE_END = 24;
public static final int EVENT_AVATAR_NEAR_PLATFORM = 25; // also send by the client
public static final int EVENT_PLATFORM_REACH_POINT = 26;
public static final int EVENT_UNLOCK_TRANS_POINT = 27;
public static final int EVENT_QUEST_START = 28;
public static final int EVENT_GROUP_LOAD = 29;
public static final int EVENT_GROUP_WILL_UNLOAD = 30;
public static final int EVENT_GROUP_WILL_REFRESH = 31;
public static final int EVENT_GROUP_REFRESH = 32;
public static final int EVENT_DUNGEON_REWARD_GET = 33;
public static final int EVENT_SPECIFIC_GADGET_HP_CHANGE = 34;
public static final int EVENT_MONSTER_TIDE_OVER = 35;
public static final int EVENT_MONSTER_TIDE_CREATE = 36;
public static final int EVENT_MONSTER_TIDE_DIE = 37;
public static final int EVENT_SEALAMP_PHASE_CHANGE = 38;
public static final int EVENT_BLOSSOM_PROGRESS_FINISH = 39;
public static final int EVENT_BLOSSOM_CHEST_DIE = 40;
public static final int EVENT_GADGET_PLAY_START = 41;
public static final int EVENT_GADGET_PLAY_START_CD = 42;
public static final int EVENT_GADGET_PLAY_STOP = 43;
public static final int EVENT_GADGET_LUA_NOTIFY = 44;
public static final int EVENT_MP_PLAY_PREPARE = 45;
public static final int EVENT_MP_PLAY_BATTLE = 46;
public static final int EVENT_MP_PLAY_PREPARE_INTERRUPT = 47;
public static final int EVENT_SELECT_DIFFICULTY = 48;
public static final int EVENT_SCENE_MP_PLAY_BATTLE_STATE = 49;
public static final int EVENT_SCENE_MP_PLAY_BATTLE_STAGE_CHANGE = 50;
public static final int EVENT_SCENE_MP_PLAY_BATTLE_RESULT = 51;
public static final int EVENT_SEAL_BATTLE_PROGRESS_DECREASE = 52;
public static final int EVENT_GENERAL_REWARD_DIE = 53;
public static final int EVENT_SCENE_MP_PLAY_BATTLE_INTERRUPT = 54;
public static final int EVENT_MONSTER_DIE_BEFORE_LEAVE_SCENE = 55;
public static final int EVENT_SCENE_MP_PLAY_OPEN = 56;
public static final int EVENT_OFFERING_LEVELUP = 57;
public static final int EVENT_DUNGEON_REVIVE = 58;
public static final int EVENT_SCENE_MP_PLAY_ALL_AVATAR_DIE = 59;
public static final int EVENT_DUNGEON_ALL_AVATAR_DIE = 60;
public static final int EVENT_GENERAL_REWARD_TAKEN = 61;
public static final int EVENT_PLATFORM_REACH_ARRAYPOINT = 62;
public static final int EVENT_SCENE_MULTISTAGE_PLAY_STAGE_END = 63;
public static final int EVENT_SCENE_MULTISTAGE_PLAY_END_STAGE_REQ = 64;
public static final int EVENT_MECHANICUS_PICKED_CARD = 65;
public static final int EVENT_POOL_MONSTER_TIDE_OVER = 66;
public static final int EVENT_POOL_MONSTER_TIDE_CREATE = 67;
public static final int EVENT_POOL_MONSTER_TIDE_DIE = 68;
public static final int EVENT_DUNGEON_AVATAR_SLIP_DIE = 69;
public static final int EVENT_GALLERY_START = 70;
public static final int EVENT_GALLERY_STOP = 71;
public static final int EVENT_TIME_AXIS_PASS = 72;
public static final int EVENT_FLEUR_FAIR_DUNGEON_ALL_PLAYER_ENTER = 73;
public static final int EVENT_GADGETTALK_DONE = 74;
public static final int EVENT_SET_GAME_TIME = 75;
public static final int EVENT_HIDE_AND_SEEK_PLAYER_QUIT = 76;
public static final int EVENT_AVATAR_DIE = 77;
public static final int EVENT_SCENE_MULTISTAGE_PLAY_STAGE_START = 78;
public static final int EVENT_GALLERY_PROGRESS_PASS = 79;
public static final int EVENT_GALLERY_PROGRESS_EMPTY = 80;
public static final int EVENT_GALLERY_PROGRESS_FULL = 81;
public static final int EVENT_HUNTING_FINISH_FINAL = 82;
public static final int EVENT_USE_WIDGET_TOY_FOX_CAMERA = 83;
public static final int EVENT_LUNA_RITE_SACRIFICE = 84;
public static final int EVENT_SUMO_SWITCH_TEAM_EVENT = 85;
public static final int EVENT_FISHING_START = 86;
public static final int EVENT_FISHING_STOP = 87;
public static final int EVENT_FISHING_QTE_FINISH = 88;
public static final int EVENT_FISHING_TIMEOUT_FLEE = 89;
public static final int EVENT_ROGUE_CELL_STATE_CHANGE = 90;
public static final int EVENT_ROGUE_CELL_CONSTRUCT = 91;
public static final int EVENT_ROGUE_CELL_FINISH_SELECT_CARD = 92;
public static final int EVENT_ANY_MONSTER_CAPTURE = 93;
public static final int EVENT_ACTIVITY_INTERACT_GADGET = 94;
public static final int EVENT_CHALLENGE_PAUSE = 95;
public static final int EVENT_LEVEL_TAG_CHANGE = 96;
public static final int EVENT_CUSTOM_DUNGEON_START = 97;
public static final int EVENT_CUSTOM_DUNGEON_RESTART = 98;
public static final int EVENT_CUSTOM_DUNGEON_REACTIVE = 99;
public static final int EVENT_CUSTOM_DUNGEON_OUT_STUCK = 100;
public static final int EVENT_CUSTOM_DUNGEON_EXIT_TRY = 101;
public static final int EVENT_CUSTOM_DUNGEON_OFFICIAL_RESTART = 102;
public static final int EVENT_ANY_MONSTER_CAPTURE_AND_DISAPPEAR = 103;
public static final int EVENT_MICHIAE_INTERACT = 104;
public static final int EVENT_SELECT_UIINTERACT = 105;
public static final int EVENT_LUA_NOTIFY = 106;
public static final int EVENT_PHOTO_FINISH = 107;
public static final int EVENT_IRODORI_MASTER_READY = 108;
public static final int EVENT_ROGUE_START_FIGHT = 109;
public static final int EVENT_ROGUE_CREAGE_FIGHT_GADGET = 110;
public static final int EVENT_ROGUE_CREAGE_REPAIR_GADGET = 111;
public static final int EVENT_ROGUE_OPEN_ACCESS = 112;
public static final int EVENT_GADGET_GIVING_FINISHED = 113;
public static final int EVENT_OBSERVATION_POINT_NOTIFY = 114;
public static final int EVENT_GADGET_GIVING_TAKEBACK = 115;
public static final int EVENT_ECHO_SHELL_INTERACT = 116;
public static final int EVENT_PLATFORM_ARRIVAL = 2701;
public static final int EVENT_PLAYER_BACK_GALLERY_REVIVE_POINT = 2800;
public static final int EVENT_GALLERY_CANNOT_START_AFTER_COUNTDOWN = 2801;
}

View File

@ -1,9 +0,0 @@
package emu.grasscutter.scripts.constants;
public enum GroupKillPolicy {
GROUP_KILL_NONE,
GROUP_KILL_ALL,
GROUP_KILL_MONSTER,
GROUP_KILL_GADGET,
GROUP_KILL_NPC
}

View File

@ -1,5 +0,0 @@
package emu.grasscutter.scripts.constants;
public interface IntValueEnum {
int getValue();
}

View File

@ -1,24 +0,0 @@
package emu.grasscutter.scripts.constants;
public class ScriptGadgetState {
public static final int Default = 0;
public static final int GatherDrop = 1;
public static final int ChestLocked = 101;
public static final int ChestOpened = 102;
public static final int ChestTrap = 103;
public static final int ChestBramble = 104;
public static final int ChestFrozen = 105;
public static final int ChestRock = 106;
public static final int GearStart = 201;
public static final int GearStop = 202;
public static final int GearAction1 = 203;
public static final int GearAction2 = 204;
public static final int CrystalResonate1 = 301;
public static final int CrystalResonate2 = 302;
public static final int CrystalExplode = 303;
public static final int CrystalDrain = 304;
public static final int StatueActive = 401;
public static final int Action01 = 901;
public static final int Action02 = 902;
public static final int Action03 = 903;
}

View File

@ -1,9 +0,0 @@
package emu.grasscutter.scripts.constants;
public class ScriptRegionShape {
public static final int NONE = 0;
public static final int SPHERE = 1;
public static final int CUBIC = 2;
public static final int CYLINDER = 3;
public static final int POLYGON = 4;
}

View File

@ -1,7 +0,0 @@
package emu.grasscutter.scripts.constants;
public enum SealBattleType {
NONE,
ENERGY_CHARGE,
KILL_MONSTER
}

View File

@ -1,12 +0,0 @@
package emu.grasscutter.scripts.constants;
public enum VisionLevelType {
VISION_LEVEL_NORMAL,
VISION_LEVEL_LITTLE_REMOTE,
VISION_LEVEL_REMOTE,
VISION_LEVEL_SUPER,
VISION_LEVEL_NEARBY,
VISION_LEVEL_SUPER_NEARBY,
VISION_LEVEL_SUPER_NUM,
}

View File

@ -1,6 +0,0 @@
package emu.grasscutter.scripts.constants.temporary;
public enum ExhibitionPlayType {
Challenge,
Gallery,
}

View File

@ -1,6 +0,0 @@
package emu.grasscutter.scripts.constants.temporary;
public enum FlowSuiteOperatePolicy {
DEFAULT,
COMPLETE
}

View File

@ -1,6 +0,0 @@
package emu.grasscutter.scripts.constants.temporary;
public enum GalleryProgressScoreType {
GALLERY_PROGRESS_SCORE_NONE,
GALLERY_PROGRESS_SCORE_NO_DEGRADE
}

View File

@ -1,10 +0,0 @@
package emu.grasscutter.scripts.constants.temporary;
public enum GalleryProgressScoreUIType {
GALLERY_PROGRESS_SCORE_UI_TYPE_NONE,
GALLERY_PROGRESS_SCORE_UI_TYPE_BUOYANT_COMBAT,
GALLERY_PROGRESS_SCORE_UI_TYPE_SUMO_STAGE,
GALLERY_PROGRESS_SCORE_UI_TYPE_DIG,
GALLERY_PROGRESS_SCORE_UI_TYPE_CRYSTAL_LINK,
GALLERY_PROGRESS_SCORE_UI_TYPE_TREASURE;
}

View File

@ -1,10 +0,0 @@
package emu.grasscutter.scripts.data;
import emu.grasscutter.utils.Position;
import lombok.Data;
@Data
public class DummyPoint {
private Position pos;
private Position rot;
}

View File

@ -1,9 +0,0 @@
package emu.grasscutter.scripts.data;
import lombok.Data;
@Data
public class Explore {
private String name;
private int exp;
}

View File

@ -1,77 +0,0 @@
package emu.grasscutter.scripts.data;
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.scripts.lua_engine.ScriptType;
import emu.grasscutter.utils.Position;
import lombok.*;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;
import java.util.Map;
import java.util.stream.Collectors;
@ToString
@Getter
public class SceneBlock {
@Setter(AccessLevel.PACKAGE)
private int id;
private Position max;
private Position min;
private int sceneId;
private Map<Integer,SceneGroup> groups;
private RTree<SceneGroup, Geometry> sceneGroupIndex;
private transient boolean loaded; // Not an actual variable in the scripts either
public void setLoaded(boolean loaded) {
this.loaded = loaded;
}
public boolean contains(Position pos) {
int range = Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange;
return pos.getX() <= (this.max.getX() + range) && pos.getX() >= (this.min.getX() - range) &&
pos.getZ() <= (this.max.getZ() + range) && pos.getZ() >= (this.min.getZ() - range);
}
public SceneBlock load(int sceneId) {
if (this.loaded) {
return this;
}
this.sceneId = sceneId;
this.setLoaded(true);
val cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_block" + this.id + ".lua", ScriptType.DATA_STORAGE);
if (cs == null) {
return null;
}
// Eval script
try {
cs.evaluate();
// Set groups
this.groups = cs.getGlobalVariableList("groups", SceneGroup.class).stream()
.collect(Collectors.toMap(x -> x.getId(), y -> y, (a, b) -> a));
this.groups.values().forEach(g -> g.block_id = this.id);
this.sceneGroupIndex = SceneIndexManager.buildIndex(3, this.groups.values(), g -> g.getPos().toPoint());
} catch (ScriptException exception) {
Grasscutter.getLogger().error("An error occurred while loading block " + this.id + " in scene " + sceneId, exception);
}
Grasscutter.getLogger().debug("Successfully loaded block {} in scene {}.", this.id, sceneId);
return this;
}
public Rectangle toRectangle() {
return Rectangle.create(this.min.toXZDoubleArray(), this.max.toXZDoubleArray());
}
}

View File

@ -1,14 +0,0 @@
package emu.grasscutter.scripts.data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@ToString
public class SceneBossChest {
private int life_time;
private int monster_config_id;
private int resin;
private int take_num;
}

View File

@ -1,11 +0,0 @@
package emu.grasscutter.scripts.data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
public class SceneBusiness {
private int type;
}

View File

@ -1,17 +0,0 @@
package emu.grasscutter.scripts.data;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
public class SceneConfig {
private Position vision_anchor;
private Position born_pos;
private Position born_rot;
private Position begin_pos;
private Position size;
private float die_y;
}

View File

@ -1,37 +0,0 @@
package emu.grasscutter.scripts.data;
import emu.grasscutter.game.props.EntityIdType;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
@ToString
@Getter
public class SceneGadget extends SceneObject{
private int gadget_id;
private int state;
private int point_type;
private SceneBossChest boss_chest;
private int chest_drop_id;
private int interact_id;
private boolean isOneoff;
private int draft_id;
private int route_id;
private boolean start_route = true;
private boolean is_use_point_array = false;
private boolean persistent = false;
private boolean showcutscene;
private Explore explore;
private List<Integer> arguments;
public void setIsOneoff(boolean isOneoff) {
this.isOneoff = isOneoff;
}
@Override
public EntityIdType getType() {
return EntityIdType.GADGET;
}
}

View File

@ -1,13 +0,0 @@
package emu.grasscutter.scripts.data;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
public class SceneGarbage {
private List<SceneGadget> gadgets;
}

View File

@ -1,190 +0,0 @@
package emu.grasscutter.scripts.data;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.world.GroupReplacementData;
import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.scripts.lua_engine.LuaScript;
import emu.grasscutter.scripts.lua_engine.ScriptType;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.val;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
@ToString
@Getter
public class SceneGroup {
public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference
@Setter
private int id;
private int refresh_id;
@Nullable
private Position pos;
@Nullable
private Map<Integer, SceneMonster> monsters; // <ConfigId, Monster>
@Nullable
private Map<Integer, SceneNPC> npcs; // <ConfigId, Npc>
@Nullable
private Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets>
@Nullable
private Map<String, SceneTrigger> triggers;
@Nullable
private Map<Integer, SceneRegion> regions;
@Nullable
private List<SceneSuite> suites;
@Nullable
private List<SceneVar> variables;
@Nullable
private SceneBusiness business;
@Nullable
private SceneGarbage garbages;
@Nullable
private SceneInitConfig init_config;
private final boolean dynamic_load = false;
@Nullable
private SceneReplaceable is_replaceable;
private transient boolean loaded; // Not an actual variable in the scripts either
private transient LuaScript script;
public static SceneGroup of(int groupId) {
var group = new SceneGroup();
group.id = groupId;
return group;
}
public int getBusinessType() {
return this.business == null ? 0 : this.business.getType();
}
public List<SceneGadget> getGarbageGadgets() {
return this.garbages == null ? null : this.garbages.getGadgets();
}
public boolean isReplaceable() {
return this.is_replaceable != null ? this.is_replaceable.isValue() : false;
}
public SceneSuite getSuiteByIndex(int index) {
if (index < 1 || index > suites.size()) {
return null;
}
return this.suites.get(index - 1);
}
public synchronized SceneGroup load(int sceneId) {
if (this.loaded) {
return this;
}
// Set flag here so if there is no script, we don't call this function over and over again.
this.loaded = true;
val cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua", ScriptType.EXECUTABLE);
if (cs == null) {
return this;
}
this.script = cs;
// Eval script
try {
cs.evaluate();
// Set
this.monsters = cs.getGlobalVariableList("monsters", SceneMonster.class).stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
this.monsters.values().forEach(m -> m.group = this);
this.npcs = cs.getGlobalVariableList("npcs", SceneNPC.class).stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
this.npcs.values().forEach(m -> m.group = this);
this.gadgets = cs.getGlobalVariableList("gadgets", SceneGadget.class).stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
this.gadgets.values().forEach(m -> m.group = this);
this.triggers = cs.getGlobalVariableList("triggers", SceneTrigger.class).stream()
.collect(Collectors.toMap(SceneTrigger::getName, y -> y, (a, b) -> a));
this.triggers.values().forEach(t -> t.setCurrentGroup(this));
this.suites = cs.getGlobalVariableList("suites", SceneSuite.class);
this.regions = cs.getGlobalVariableList("regions", SceneRegion.class).stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
this.regions.values().forEach(m -> m.group = this);
this.init_config = cs.getGlobalVariable("init_config", SceneInitConfig.class);
// Garbages // TODO: fix properly later
/*Object garbagesValue = this.bindings.get("garbages");
if (garbagesValue instanceof LuaValue garbagesTable) {
this.garbages = new SceneGarbage();
if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) {
this.garbages.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable());
this.garbages.gadgets.forEach(m -> m.group = this);
}
}*/
// Add variables to suite
this.variables = cs.getGlobalVariableList("variables", SceneVar.class);
// Add monsters and gadgets to suite
this.suites.forEach(i -> i.init(this));
} catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while loading group " + this.id + " in scene " + sceneId + ".", e);
}
Grasscutter.getLogger().debug("Successfully loaded group {} in scene {}.", this.id, sceneId);
return this;
}
public int findInitSuiteIndex(int exclude_index) { //TODO: Investigate end index
if (init_config == null) return 1;
if (init_config.getIo_type() == 1) return init_config.getSuite(); //IO TYPE FLOW
if (init_config.isRand_suite()) {
if (suites.size() == 1) {
return init_config.getSuite();
} else {
List<Integer> randSuiteList = new ArrayList<>();
for (int i = 0; i < suites.size(); i++) {
if (i == exclude_index) continue;
var suite = suites.get(i);
for (int j = 0; j < suite.getRand_weight(); j++) randSuiteList.add(Integer.valueOf(i + 1));
}
return randSuiteList.get(new Random().nextInt(randSuiteList.size()));
}
}
return init_config.getSuite();
}
public Optional<SceneBossChest> searchBossChestInGroup() {
return this.gadgets.values().stream().map(g -> g.getBoss_chest()).filter(Objects::nonNull)
.filter(bossChest -> bossChest.getMonster_config_id() > 0)
.findFirst();
}
public List<SceneGroup> getReplaceableGroups(Collection<SceneGroup> loadedGroups) {
return this.is_replaceable == null ? List.of() :
Optional.ofNullable(GameData.getGroupReplacements().get(this.id)).stream()
.map(GroupReplacementData::getReplace_groups)
.flatMap(List::stream)
.map(replacementId -> loadedGroups.stream().filter(g -> g.id == replacementId).findFirst())
.filter(Optional::isPresent).map(Optional::get)
.filter(replacementGroup -> replacementGroup.is_replaceable != null)
.filter(replacementGroup -> (replacementGroup.is_replaceable.isValue()
&& replacementGroup.is_replaceable.getVersion() <= this.is_replaceable.getVersion())
|| replacementGroup.is_replaceable.isNew_bin_only())
.toList();
}
}

View File

@ -1,14 +0,0 @@
package emu.grasscutter.scripts.data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
public class SceneInitConfig {
private int suite;
private int end_suite;
private int io_type ;
private boolean rand_suite;
}

View File

@ -1,73 +0,0 @@
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 emu.grasscutter.scripts.lua_engine.LuaScript;
import emu.grasscutter.scripts.lua_engine.ScriptType;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.val;
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;
@ToString
@Getter
public class SceneMeta {
private SceneConfig config;
private Map<Integer, SceneBlock> blocks;
private LuaScript context;
private 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
val cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + ".lua", ScriptType.DATA_STORAGE);
if (cs == null) {
Grasscutter.getLogger().warn("No script found for scene " + sceneId);
return null;
}
// Eval script
try {
cs.evaluate();
this.config = cs.getGlobalVariable("scene_config", SceneConfig.class);
// TODO optimize later
// Create blocks
List<Integer> blockIds = cs.getGlobalVariableList("blocks", Integer.class);
List<SceneBlock> blocks = cs.getGlobalVariableList("block_rects", SceneBlock.class);
for (int i = 0; i < blocks.size(); i++) {
SceneBlock block = blocks.get(i);
block.setId(blockIds.get(i));
}
this.blocks = blocks.stream().collect(Collectors.toMap(b -> b.getId(), b -> b, (a, b) -> a));
this.sceneBlockIndex = SceneIndexManager.buildIndex(2, blocks, SceneBlock::toRectangle);
} catch (ScriptException exception) {
Grasscutter.getLogger().error("An error occurred while running a script.", exception);
return null;
}
Grasscutter.getLogger().debug("Successfully loaded metadata in scene {}.", sceneId);
return this;
}
}

View File

@ -1,35 +0,0 @@
package emu.grasscutter.scripts.data;
import java.util.List;
import emu.grasscutter.game.props.EntityIdType;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
@ToString
@Getter
public class SceneMonster extends SceneObject{
private int monster_id;
private int pose_id;
private String pose_logic_state;
private int drop_id;
private boolean disableWander;
private int title_id;
private int special_name_id;
private List<Integer> affix;
private boolean isElite;
private int climate_area_id;
private int ai_config_id;
private int kill_score;
private int speed_level;
private long tag;
private boolean is_light_config;
@Override
public EntityIdType getType() {
return EntityIdType.MONSTER;
}
}

View File

@ -1,17 +0,0 @@
package emu.grasscutter.scripts.data;
import emu.grasscutter.game.props.EntityIdType;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
public class SceneNPC extends SceneObject{
private int npc_id;
@Override
public EntityIdType getType() {
return EntityIdType.NPC;
}
}

View File

@ -1,32 +0,0 @@
package emu.grasscutter.scripts.data;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
public abstract class SceneObject {
protected int level;
protected int config_id;
protected int area_id;
protected int vision_level = 0;
protected int mark_flag;
protected String drop_tag;
protected int guest_ban_drop;
protected int oneoff_reset_version;
protected int sight_group_index;
// server_global_value_config, might be group only
protected Position pos;
protected Position rot;
/**
* not set by lua
*/
protected transient SceneGroup group;
public abstract EntityIdType getType();
}

Some files were not shown because too many files have changed in this diff Show More