mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-16 17:05:20 +01:00
Merge branch 'dev-world-scripts' of https://github.com/Grasscutters/Grasscutter into development
This commit is contained in:
33
src/main/java/emu/grasscutter/scripts/SceneIndexManager.java
Normal file
33
src/main/java/emu/grasscutter/scripts/SceneIndexManager.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package emu.grasscutter.scripts;
|
||||
|
||||
import com.github.davidmoten.rtreemulti.Entry;
|
||||
import com.github.davidmoten.rtreemulti.RTree;
|
||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
||||
import com.github.davidmoten.rtreemulti.geometry.Rectangle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SceneIndexManager {
|
||||
|
||||
public static <T> RTree<T, Geometry> buildIndex(int dimensions, Collection<T> elements, Function<T, Geometry> extractor){
|
||||
RTree<T, Geometry> rtree = RTree.dimensions(dimensions).create();
|
||||
return rtree.add(elements.stream().map(e -> Entry.entry(e, extractor.apply(e))).toList());
|
||||
}
|
||||
public static <T> List<T> queryNeighbors(RTree<T, Geometry> tree, double[] position, int range){
|
||||
var result = new ArrayList<T>();
|
||||
Rectangle rectangle = Rectangle.create(calRange(position, -range), calRange(position, range));
|
||||
var queryResult = tree.search(rectangle);
|
||||
queryResult.forEach(q -> result.add(q.value()));
|
||||
return result;
|
||||
}
|
||||
private static double[] calRange(double[] position, int range){
|
||||
var newPos = position.clone();
|
||||
for(int i=0;i<newPos.length;i++){
|
||||
newPos[i] += range;
|
||||
}
|
||||
return newPos;
|
||||
}
|
||||
}
|
||||
@@ -1,75 +1,70 @@
|
||||
package emu.grasscutter.scripts;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.CompiledScript;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import com.github.davidmoten.rtreemulti.RTree;
|
||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.def.MonsterData;
|
||||
import emu.grasscutter.data.def.WorldLevelData;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.game.entity.EntityNPC;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.*;
|
||||
import emu.grasscutter.scripts.service.ScriptMonsterSpawnService;
|
||||
import emu.grasscutter.scripts.service.ScriptMonsterTideService;
|
||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.luaj.vm2.LuaError;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.SceneBlock;
|
||||
import emu.grasscutter.scripts.data.SceneConfig;
|
||||
import emu.grasscutter.scripts.data.SceneGadget;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import emu.grasscutter.scripts.data.SceneInitConfig;
|
||||
import emu.grasscutter.scripts.data.SceneMonster;
|
||||
import emu.grasscutter.scripts.data.SceneRegion;
|
||||
import emu.grasscutter.scripts.data.SceneSuite;
|
||||
import emu.grasscutter.scripts.data.SceneTrigger;
|
||||
import emu.grasscutter.scripts.data.SceneVar;
|
||||
import emu.grasscutter.scripts.data.ScriptArgs;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import static emu.grasscutter.Configuration.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SceneScriptManager {
|
||||
private final Scene scene;
|
||||
private final ScriptLib scriptLib;
|
||||
private final LuaValue scriptLibLua;
|
||||
private final Map<String, Integer> variables;
|
||||
private Bindings bindings;
|
||||
private SceneConfig config;
|
||||
private List<SceneBlock> blocks;
|
||||
private SceneMeta meta;
|
||||
private boolean isInit;
|
||||
/**
|
||||
* SceneTrigger Set
|
||||
*/
|
||||
private final Map<String, SceneTrigger> triggers;
|
||||
/**
|
||||
* current triggers controlled by RefreshGroup
|
||||
*/
|
||||
private final Int2ObjectOpenHashMap<Set<SceneTrigger>> currentTriggers;
|
||||
private final Int2ObjectOpenHashMap<SceneRegion> regions;
|
||||
private Map<Integer,SceneGroup> sceneGroups;
|
||||
private SceneGroup currentGroup;
|
||||
private ScriptMonsterTideService scriptMonsterTideService;
|
||||
private ScriptMonsterSpawnService scriptMonsterSpawnService;
|
||||
|
||||
/**
|
||||
* blockid - loaded groupSet
|
||||
*/
|
||||
private Int2ObjectMap<Set<SceneGroup>> loadedGroupSetPerBlock;
|
||||
public static final ExecutorService eventExecutor;
|
||||
static {
|
||||
eventExecutor = new ThreadPoolExecutor(4, 4,
|
||||
60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000),
|
||||
FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy());
|
||||
}
|
||||
public SceneScriptManager(Scene scene) {
|
||||
this.scene = scene;
|
||||
this.scriptLib = new ScriptLib(this);
|
||||
this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib);
|
||||
this.triggers = new HashMap<>();
|
||||
this.currentTriggers = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
this.regions = new Int2ObjectOpenHashMap<>();
|
||||
this.variables = new HashMap<>();
|
||||
this.sceneGroups = new HashMap<>();
|
||||
this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this);
|
||||
this.loadedGroupSetPerBlock = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// TEMPORARY
|
||||
if (this.getScene().getId() < 10) {
|
||||
if (this.getScene().getId() < 10 && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,28 +76,15 @@ public class SceneScriptManager {
|
||||
return scene;
|
||||
}
|
||||
|
||||
public ScriptLib getScriptLib() {
|
||||
return scriptLib;
|
||||
}
|
||||
|
||||
public LuaValue getScriptLibLua() {
|
||||
return scriptLibLua;
|
||||
}
|
||||
|
||||
public Bindings getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public SceneConfig getConfig() {
|
||||
return config;
|
||||
if(!isInit){
|
||||
return null;
|
||||
}
|
||||
return meta.config;
|
||||
}
|
||||
|
||||
public SceneGroup getCurrentGroup() {
|
||||
return currentGroup;
|
||||
}
|
||||
|
||||
public List<SceneBlock> getBlocks() {
|
||||
return blocks;
|
||||
public Map<Integer, SceneBlock> getBlocks() {
|
||||
return meta.blocks;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getVariables() {
|
||||
@@ -112,29 +94,31 @@ public class SceneScriptManager {
|
||||
public Set<SceneTrigger> getTriggersByEvent(int eventId) {
|
||||
return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>());
|
||||
}
|
||||
public void registerTrigger(List<SceneTrigger> triggers) {
|
||||
triggers.forEach(this::registerTrigger);
|
||||
}
|
||||
public void registerTrigger(SceneTrigger trigger) {
|
||||
this.triggers.put(trigger.name, trigger);
|
||||
getTriggersByEvent(trigger.event).add(trigger);
|
||||
}
|
||||
|
||||
public void deregisterTrigger(List<SceneTrigger> triggers) {
|
||||
triggers.forEach(this::deregisterTrigger);
|
||||
}
|
||||
public void deregisterTrigger(SceneTrigger trigger) {
|
||||
this.triggers.remove(trigger.name);
|
||||
getTriggersByEvent(trigger.event).remove(trigger);
|
||||
}
|
||||
public void resetTriggers(List<String> triggerNames) {
|
||||
for(var name : triggerNames){
|
||||
var instance = triggers.get(name);
|
||||
this.currentTriggers.get(instance.event).clear();
|
||||
this.currentTriggers.get(instance.event).add(instance);
|
||||
}
|
||||
public void resetTriggers(int eventId) {
|
||||
currentTriggers.put(eventId, new HashSet<>());
|
||||
}
|
||||
public void refreshGroup(SceneGroup group, int suiteIndex){
|
||||
var suite = group.getSuiteByIndex(suiteIndex);
|
||||
if(suite == null){
|
||||
return;
|
||||
}
|
||||
if(suite.triggers.size() > 0){
|
||||
resetTriggers(suite.triggers);
|
||||
if(suite.sceneTriggers.size() > 0){
|
||||
for(var trigger : suite.sceneTriggers){
|
||||
resetTriggers(trigger.event);
|
||||
this.currentTriggers.get(trigger.event).add(trigger);
|
||||
}
|
||||
}
|
||||
spawnMonstersInGroup(group, suite);
|
||||
spawnGadgetsInGroup(group, suite);
|
||||
@@ -150,59 +134,34 @@ public class SceneScriptManager {
|
||||
public void deregisterRegion(SceneRegion region) {
|
||||
regions.remove(region.config_id);
|
||||
}
|
||||
|
||||
|
||||
public Int2ObjectMap<Set<SceneGroup>> getLoadedGroupSetPerBlock() {
|
||||
return loadedGroupSetPerBlock;
|
||||
}
|
||||
|
||||
// TODO optimize
|
||||
public SceneGroup getGroupById(int groupId) {
|
||||
for (SceneBlock block : this.getScene().getLoadedBlocks()) {
|
||||
for (SceneGroup group : block.groups) {
|
||||
if (group.id == groupId) {
|
||||
return group;
|
||||
}
|
||||
var group = block.groups.get(groupId);
|
||||
if(group == null){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!group.isLoaded()){
|
||||
getScene().onLoadGroup(List.of(group));
|
||||
}
|
||||
return group;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
// Get compiled script if cached
|
||||
CompiledScript cs = ScriptLoader.getScriptByPath(
|
||||
SCRIPT("Scene/" + getScene().getId() + "/scene" + getScene().getId() + "." + ScriptLoader.getScriptType()));
|
||||
|
||||
if (cs == null) {
|
||||
Grasscutter.getLogger().warn("No script found for scene " + getScene().getId());
|
||||
var meta = ScriptLoader.getSceneMeta(getScene().getId());
|
||||
if (meta == null){
|
||||
return;
|
||||
}
|
||||
|
||||
// Create bindings
|
||||
bindings = ScriptLoader.getEngine().createBindings();
|
||||
|
||||
// Set variables
|
||||
bindings.put("ScriptLib", getScriptLib());
|
||||
this.meta = meta;
|
||||
|
||||
// Eval script
|
||||
try {
|
||||
cs.eval(getBindings());
|
||||
|
||||
this.config = ScriptLoader.getSerializer().toObject(SceneConfig.class, bindings.get("scene_config"));
|
||||
|
||||
// TODO optimize later
|
||||
// Create blocks
|
||||
List<Integer> blockIds = ScriptLoader.getSerializer().toList(Integer.class, bindings.get("blocks"));
|
||||
List<SceneBlock> blocks = ScriptLoader.getSerializer().toList(SceneBlock.class, bindings.get("block_rects"));
|
||||
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
SceneBlock block = blocks.get(i);
|
||||
block.id = blockIds.get(i);
|
||||
|
||||
loadBlockFromScript(block);
|
||||
}
|
||||
|
||||
this.blocks = blocks;
|
||||
} catch (ScriptException e) {
|
||||
Grasscutter.getLogger().error("Error running script", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// TEMP
|
||||
this.isInit = true;
|
||||
}
|
||||
@@ -211,90 +170,23 @@ public class SceneScriptManager {
|
||||
return isInit;
|
||||
}
|
||||
|
||||
private void loadBlockFromScript(SceneBlock block) {
|
||||
CompiledScript cs = ScriptLoader.getScriptByPath(
|
||||
SCRIPT("Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_block" + block.id + "." + ScriptLoader.getScriptType()));
|
||||
|
||||
if (cs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Eval script
|
||||
try {
|
||||
cs.eval(getBindings());
|
||||
|
||||
// Set groups
|
||||
block.groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups"));
|
||||
block.groups.forEach(g -> g.block_id = block.id);
|
||||
} catch (ScriptException e) {
|
||||
Grasscutter.getLogger().error("Error loading block " + block.id + " in scene " + getScene().getId(), e);
|
||||
}
|
||||
public void loadBlockFromScript(SceneBlock block) {
|
||||
block.load(scene.getId(), meta.context);
|
||||
}
|
||||
|
||||
public void loadGroupFromScript(SceneGroup group) {
|
||||
// Set flag here so if there is no script, we dont call this function over and over again.
|
||||
group.setLoaded(true);
|
||||
|
||||
CompiledScript cs = ScriptLoader.getScriptByPath(
|
||||
SCRIPT("Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_group" + group.id + "." + ScriptLoader.getScriptType()));
|
||||
|
||||
if (cs == null) {
|
||||
return;
|
||||
group.load(getScene().getId());
|
||||
|
||||
if (group.variables != null) {
|
||||
group.variables.forEach(var -> this.getVariables().put(var.name, var.value));
|
||||
}
|
||||
|
||||
// Eval script
|
||||
try {
|
||||
cs.eval(getBindings());
|
||||
|
||||
// Set
|
||||
group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream()
|
||||
.collect(Collectors.toMap(x -> x.config_id, y -> y));
|
||||
group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets"));
|
||||
group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers"));
|
||||
group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites"));
|
||||
group.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions"));
|
||||
group.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config"));
|
||||
|
||||
// Add variables to suite
|
||||
List<SceneVar> variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables"));
|
||||
variables.forEach(var -> this.getVariables().put(var.name, var.value));
|
||||
|
||||
// Add monsters to suite TODO optimize
|
||||
Int2ObjectMap<Object> map = new Int2ObjectOpenHashMap<>();
|
||||
group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m));
|
||||
group.gadgets.forEach(m -> map.put(m.config_id, m));
|
||||
|
||||
for (SceneSuite suite : group.suites) {
|
||||
suite.sceneMonsters = new ArrayList<>(suite.monsters.size());
|
||||
suite.monsters.forEach(id -> {
|
||||
Object objEntry = map.get(id.intValue());
|
||||
if (objEntry instanceof Map.Entry<?,?> monsterEntry) {
|
||||
Object monster = monsterEntry.getValue();
|
||||
if(monster instanceof SceneMonster sceneMonster){
|
||||
suite.sceneMonsters.add(sceneMonster);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.sceneGroups.put(group.id, group);
|
||||
|
||||
suite.sceneGadgets = new ArrayList<>(suite.gadgets.size());
|
||||
for (int id : suite.gadgets) {
|
||||
try {
|
||||
SceneGadget gadget = (SceneGadget) map.get(id);
|
||||
if (gadget != null) {
|
||||
suite.sceneGadgets.add(gadget);
|
||||
}
|
||||
} catch (Exception ignored) { }
|
||||
}
|
||||
}
|
||||
this.sceneGroups.put(group.id, group);
|
||||
} catch (ScriptException e) {
|
||||
Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e);
|
||||
if(group.regions != null){
|
||||
group.regions.forEach(this::registerRegion);
|
||||
}
|
||||
}
|
||||
|
||||
public void onTick() {
|
||||
checkRegions();
|
||||
}
|
||||
|
||||
public void checkRegions() {
|
||||
if (this.regions.size() == 0) {
|
||||
@@ -315,7 +207,17 @@ public class SceneScriptManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addGroupSuite(SceneGroup group, SceneSuite suite){
|
||||
spawnMonstersInGroup(group, suite);
|
||||
spawnGadgetsInGroup(group, suite);
|
||||
registerTrigger(suite.sceneTriggers);
|
||||
}
|
||||
public void removeGroupSuite(SceneGroup group, SceneSuite suite){
|
||||
removeMonstersInGroup(group, suite);
|
||||
removeGadgetsInGroup(group, suite);
|
||||
deregisterTrigger(suite.sceneTriggers);
|
||||
}
|
||||
public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) {
|
||||
spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex));
|
||||
}
|
||||
@@ -325,26 +227,17 @@ public class SceneScriptManager {
|
||||
}
|
||||
|
||||
public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) {
|
||||
List<SceneGadget> gadgets = group.gadgets;
|
||||
var gadgets = group.gadgets.values();
|
||||
|
||||
if (suite != null) {
|
||||
gadgets = suite.sceneGadgets;
|
||||
}
|
||||
|
||||
for (SceneGadget g : gadgets) {
|
||||
EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos);
|
||||
|
||||
if (entity.getGadgetData() == null) continue;
|
||||
|
||||
entity.setBlockId(group.block_id);
|
||||
entity.setConfigId(g.config_id);
|
||||
entity.setGroupId(group.id);
|
||||
entity.getRotation().set(g.rot);
|
||||
entity.setState(g.state);
|
||||
|
||||
getScene().addEntity(entity);
|
||||
this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId()));
|
||||
}
|
||||
|
||||
var toCreate = gadgets.stream()
|
||||
.map(g -> createGadget(g.group.id, group.block_id, g))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
this.addEntities(toCreate);
|
||||
}
|
||||
|
||||
public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
|
||||
@@ -358,17 +251,16 @@ public class SceneScriptManager {
|
||||
if(suite == null || suite.sceneMonsters.size() <= 0){
|
||||
return;
|
||||
}
|
||||
this.currentGroup = group;
|
||||
suite.sceneMonsters.forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob));
|
||||
this.addEntities(suite.sceneMonsters.stream()
|
||||
.map(mob -> createMonster(group.id, group.block_id, mob)).toList());
|
||||
}
|
||||
|
||||
public void spawnMonstersInGroup(SceneGroup group) {
|
||||
this.currentGroup = group;
|
||||
group.monsters.values().forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob));
|
||||
this.addEntities(group.monsters.values().stream()
|
||||
.map(mob -> createMonster(group.id, group.block_id, mob)).toList());
|
||||
}
|
||||
|
||||
public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) {
|
||||
this.currentGroup = group;
|
||||
this.scriptMonsterTideService =
|
||||
new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId);
|
||||
|
||||
@@ -379,49 +271,71 @@ public class SceneScriptManager {
|
||||
}
|
||||
this.getScriptMonsterTideService().unload();
|
||||
}
|
||||
public void spawnMonstersByConfigId(int configId, int delayTime) {
|
||||
public void spawnMonstersByConfigId(SceneGroup group, int configId, int delayTime) {
|
||||
// TODO delay
|
||||
this.scriptMonsterSpawnService.spawnMonster(this.currentGroup.id, this.currentGroup.monsters.get(configId));
|
||||
getScene().addEntity(createMonster(group.id, group.block_id, group.monsters.get(configId)));
|
||||
}
|
||||
// Events
|
||||
|
||||
public void callEvent(int eventType, ScriptArgs params) {
|
||||
for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
|
||||
LuaValue condition = null;
|
||||
|
||||
if (trigger.condition != null && !trigger.condition.isEmpty()) {
|
||||
condition = (LuaValue) this.getBindings().get(trigger.condition);
|
||||
}
|
||||
|
||||
LuaValue ret = LuaValue.TRUE;
|
||||
|
||||
if (condition != null) {
|
||||
LuaValue args = LuaValue.NIL;
|
||||
|
||||
if (params != null) {
|
||||
args = CoerceJavaToLua.coerce(params);
|
||||
}
|
||||
public void callEvent(int eventType, ScriptArgs params){
|
||||
/**
|
||||
* We use ThreadLocal to trans SceneScriptManager context to ScriptLib, to avoid eval script for every groups' trigger in every scene instances.
|
||||
* But when callEvent is called in a ScriptLib func, it may cause NPE because the inner call cleans the ThreadLocal so that outer call could not get it.
|
||||
* e.g. CallEvent -> set -> ScriptLib.xxx -> CallEvent -> set -> remove -> NPE -> (remove)
|
||||
* So we use thread pool to clean the stack to avoid this new issue.
|
||||
*/
|
||||
eventExecutor.submit(() -> this.realCallEvent(eventType, params));
|
||||
}
|
||||
|
||||
ScriptLib.logger.trace("Call Condition Trigger {}", trigger);
|
||||
ret = safetyCall(trigger.condition, condition, args);
|
||||
private void realCallEvent(int eventType, ScriptArgs params) {
|
||||
try{
|
||||
ScriptLoader.getScriptLib().setSceneScriptManager(this);
|
||||
for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
|
||||
try{
|
||||
ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup);
|
||||
|
||||
LuaValue ret = callScriptFunc(trigger.condition, trigger.currentGroup, params);
|
||||
Grasscutter.getLogger().trace("Call Condition Trigger {}", trigger.condition);
|
||||
|
||||
if (ret.isboolean() && ret.checkboolean()) {
|
||||
// the SetGroupVariableValueByGroup in tower need the param to record the first stage time
|
||||
callScriptFunc(trigger.action, trigger.currentGroup, params);
|
||||
Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.action);
|
||||
}
|
||||
//TODO some ret may not bool
|
||||
|
||||
}finally {
|
||||
ScriptLoader.getScriptLib().removeCurrentGroup();
|
||||
}
|
||||
}
|
||||
|
||||
if (ret.isboolean() && ret.checkboolean()) {
|
||||
ScriptLib.logger.trace("Call Action Trigger {}", trigger);
|
||||
LuaValue action = (LuaValue) this.getBindings().get(trigger.action);
|
||||
// TODO impl the param of SetGroupVariableValueByGroup
|
||||
var arg = new ScriptArgs();
|
||||
arg.param2 = 100;
|
||||
var args = CoerceJavaToLua.coerce(arg);
|
||||
safetyCall(trigger.action, action, args);
|
||||
}
|
||||
//TODO some ret may not bool
|
||||
}finally {
|
||||
// make sure it is removed
|
||||
ScriptLoader.getScriptLib().removeSceneScriptManager();
|
||||
}
|
||||
}
|
||||
|
||||
private LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params){
|
||||
LuaValue funcLua = null;
|
||||
if (funcName != null && !funcName.isEmpty()) {
|
||||
funcLua = (LuaValue) group.getBindings().get(funcName);
|
||||
}
|
||||
|
||||
LuaValue ret = LuaValue.TRUE;
|
||||
|
||||
if (funcLua != null) {
|
||||
LuaValue args = LuaValue.NIL;
|
||||
|
||||
if (params != null) {
|
||||
args = CoerceJavaToLua.coerce(params);
|
||||
}
|
||||
|
||||
ret = safetyCall(funcName, funcLua, args);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public LuaValue safetyCall(String name, LuaValue func, LuaValue args){
|
||||
try{
|
||||
return func.call(this.getScriptLibLua(), args);
|
||||
return func.call(ScriptLoader.getScriptLibLua(), args);
|
||||
}catch (LuaError error){
|
||||
ScriptLib.logger.error("[LUA] call trigger failed {},{}",name,args,error);
|
||||
return LuaValue.valueOf(-1);
|
||||
@@ -436,4 +350,102 @@ public class SceneScriptManager {
|
||||
return scriptMonsterSpawnService;
|
||||
}
|
||||
|
||||
public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) {
|
||||
EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos);
|
||||
|
||||
if (entity.getGadgetData() == null){
|
||||
return null;
|
||||
}
|
||||
|
||||
entity.setBlockId(blockId);
|
||||
entity.setConfigId(g.config_id);
|
||||
entity.setGroupId(groupId);
|
||||
entity.getRotation().set(g.rot);
|
||||
entity.setState(g.state);
|
||||
entity.setPointType(g.point_type);
|
||||
entity.setMetaGadget(g);
|
||||
entity.buildContent();
|
||||
|
||||
return entity;
|
||||
}
|
||||
public EntityNPC createNPC(SceneNPC npc, int blockId, int suiteId) {
|
||||
return new EntityNPC(getScene(), npc, blockId, suiteId);
|
||||
}
|
||||
public EntityMonster createMonster(int groupId, int blockId, SceneMonster monster) {
|
||||
if(monster == null){
|
||||
return null;
|
||||
}
|
||||
|
||||
MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id);
|
||||
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate level
|
||||
int level = monster.level;
|
||||
|
||||
if (getScene().getDungeonData() != null) {
|
||||
level = getScene().getDungeonData().getShowLevel();
|
||||
} else if (getScene().getWorld().getWorldLevel() > 0) {
|
||||
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel());
|
||||
|
||||
if (worldLevelData != null) {
|
||||
level = worldLevelData.getMonsterLevel();
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn mob
|
||||
EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level);
|
||||
entity.getRotation().set(monster.rot);
|
||||
entity.setGroupId(groupId);
|
||||
entity.setBlockId(blockId);
|
||||
entity.setConfigId(monster.config_id);
|
||||
entity.setPoseId(monster.pose_id);
|
||||
|
||||
this.getScriptMonsterSpawnService()
|
||||
.onMonsterCreatedListener.forEach(action -> action.onNotify(entity));
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void addEntity(GameEntity gameEntity){
|
||||
getScene().addEntity(gameEntity);
|
||||
}
|
||||
|
||||
public void meetEntities(List<? extends GameEntity> gameEntity){
|
||||
getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_MEET);
|
||||
}
|
||||
|
||||
public void addEntities(List<? extends GameEntity> gameEntity){
|
||||
getScene().addEntities(gameEntity);
|
||||
}
|
||||
|
||||
public RTree<SceneBlock, Geometry> getBlocksIndex() {
|
||||
return meta.sceneBlockIndex;
|
||||
}
|
||||
public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) {
|
||||
var configSet = suite.sceneMonsters.stream()
|
||||
.map(m -> m.config_id)
|
||||
.collect(Collectors.toSet());
|
||||
var toRemove = getScene().getEntities().values().stream()
|
||||
.filter(e -> e instanceof EntityMonster)
|
||||
.filter(e -> e.getGroupId() == group.id)
|
||||
.filter(e -> configSet.contains(e.getConfigId()))
|
||||
.toList();
|
||||
|
||||
getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_MISS);
|
||||
}
|
||||
public void removeGadgetsInGroup(SceneGroup group, SceneSuite suite) {
|
||||
var configSet = suite.sceneGadgets.stream()
|
||||
.map(m -> m.config_id)
|
||||
.collect(Collectors.toSet());
|
||||
var toRemove = getScene().getEntities().values().stream()
|
||||
.filter(e -> e instanceof EntityGadget)
|
||||
.filter(e -> e.getGroupId() == group.id)
|
||||
.filter(e -> configSet.contains(e.getConfigId()))
|
||||
.toList();
|
||||
|
||||
getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_MISS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,43 @@
|
||||
package emu.grasscutter.scripts;
|
||||
|
||||
import emu.grasscutter.game.dungeons.DungeonChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
|
||||
import emu.grasscutter.game.dungeons.challenge.factory.ChallengeFactory;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import emu.grasscutter.scripts.data.SceneRegion;
|
||||
import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify;
|
||||
import io.netty.util.concurrent.FastThreadLocal;
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ScriptLib {
|
||||
public static final Logger logger = LoggerFactory.getLogger(ScriptLib.class);
|
||||
private final SceneScriptManager sceneScriptManager;
|
||||
|
||||
public ScriptLib(SceneScriptManager sceneScriptManager) {
|
||||
this.sceneScriptManager = sceneScriptManager;
|
||||
private final FastThreadLocal<SceneScriptManager> sceneScriptManager;
|
||||
private final FastThreadLocal<SceneGroup> currentGroup;
|
||||
public ScriptLib() {
|
||||
this.sceneScriptManager = new FastThreadLocal<>();
|
||||
this.currentGroup = new FastThreadLocal<>();
|
||||
}
|
||||
|
||||
public void setSceneScriptManager(SceneScriptManager sceneScriptManager){
|
||||
this.sceneScriptManager.set(sceneScriptManager);
|
||||
}
|
||||
|
||||
public void removeSceneScriptManager(){
|
||||
this.sceneScriptManager.remove();
|
||||
}
|
||||
|
||||
public SceneScriptManager getSceneScriptManager() {
|
||||
return sceneScriptManager;
|
||||
// normally not null
|
||||
return Optional.of(sceneScriptManager.get()).get();
|
||||
}
|
||||
|
||||
private String printTable(LuaTable table){
|
||||
@@ -38,7 +49,15 @@ public class ScriptLib {
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void setCurrentGroup(SceneGroup currentGroup){
|
||||
this.currentGroup.set(currentGroup);
|
||||
}
|
||||
public Optional<SceneGroup> getCurrentGroup(){
|
||||
return Optional.of(this.currentGroup.get());
|
||||
}
|
||||
public void removeCurrentGroup(){
|
||||
this.currentGroup.remove();
|
||||
}
|
||||
public int SetGadgetStateByConfigId(int configId, int gadgetState) {
|
||||
logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}",
|
||||
configId,gadgetState);
|
||||
@@ -48,34 +67,24 @@ public class ScriptLib {
|
||||
if (entity.isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!(entity.get() instanceof EntityGadget)) {
|
||||
return 1;
|
||||
|
||||
if (entity.get() instanceof EntityGadget entityGadget) {
|
||||
entityGadget.updateState(gadgetState);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EntityGadget gadget = (EntityGadget) entity.get();
|
||||
gadget.setState(gadgetState);
|
||||
|
||||
getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState));
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) {
|
||||
logger.debug("[LUA] Call SetGroupGadgetStateByConfigId with {},{},{}",
|
||||
groupId,configId,gadgetState);
|
||||
List<GameEntity> list = getSceneScriptManager().getScene().getEntities().values().stream()
|
||||
.filter(e -> e.getGroupId() == groupId).toList();
|
||||
|
||||
for (GameEntity entity : list) {
|
||||
if (!(entity instanceof EntityGadget)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EntityGadget gadget = (EntityGadget) entity;
|
||||
gadget.setState(gadgetState);
|
||||
|
||||
getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState));
|
||||
}
|
||||
|
||||
getSceneScriptManager().getScene().getEntities().values().stream()
|
||||
.filter(e -> e.getGroupId() == groupId)
|
||||
.filter(e -> e instanceof EntityGadget)
|
||||
.map(e -> (EntityGadget)e)
|
||||
.forEach(e -> e.updateState(gadgetState));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -83,42 +92,47 @@ public class ScriptLib {
|
||||
public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) {
|
||||
logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}",
|
||||
groupId,configId,options);
|
||||
|
||||
Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream()
|
||||
.filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst();
|
||||
|
||||
if (entity.isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!(entity.get() instanceof EntityGadget)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
EntityGadget gadget = (EntityGadget) entity.get();
|
||||
gadget.addWorktopOptions(options);
|
||||
|
||||
if (entity.isEmpty() || !(entity.get() instanceof EntityGadget gadget)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!(gadget.getContent() instanceof GadgetWorktop worktop)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
worktop.addWorktopOptions(options);
|
||||
getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int SetWorktopOptions(LuaTable table){
|
||||
logger.debug("[LUA] Call SetWorktopOptions with {}", printTable(table));
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int DelWorktopOptionByGroupId(int groupId, int configId, int option) {
|
||||
logger.debug("[LUA] Call DelWorktopOptionByGroupId with {},{},{}",groupId,configId,option);
|
||||
|
||||
Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream()
|
||||
.filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst();
|
||||
|
||||
if (entity.isEmpty()) {
|
||||
if (entity.isEmpty() || !(entity.get() instanceof EntityGadget gadget)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!(gadget.getContent() instanceof GadgetWorktop worktop)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!(entity.get() instanceof EntityGadget)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
EntityGadget gadget = (EntityGadget) entity.get();
|
||||
gadget.removeWorktopOption(option);
|
||||
|
||||
worktop.removeWorktopOption(option);
|
||||
getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -146,48 +160,102 @@ public class ScriptLib {
|
||||
if (group == null || group.monsters == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var suiteData = group.getSuiteByIndex(suite);
|
||||
if(suiteData == null){
|
||||
return 1;
|
||||
}
|
||||
// avoid spawn wrong monster
|
||||
if(getSceneScriptManager().getScene().getChallenge() != null)
|
||||
if(!getSceneScriptManager().getScene().getChallenge().inProgress() ||
|
||||
getSceneScriptManager().getScene().getChallenge().getGroup().id != groupId){
|
||||
return 0;
|
||||
}
|
||||
this.getSceneScriptManager().spawnMonstersInGroup(group, suite);
|
||||
|
||||
this.getSceneScriptManager().addGroupSuite(group, suiteData);
|
||||
|
||||
return 0;
|
||||
}
|
||||
public int GoToGroupSuite(int groupId, int suite) {
|
||||
logger.debug("[LUA] Call GoToGroupSuite with {},{}",
|
||||
groupId,suite);
|
||||
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
|
||||
if (group == null || group.monsters == null) {
|
||||
return 1;
|
||||
}
|
||||
var suiteData = group.getSuiteByIndex(suite);
|
||||
if(suiteData == null){
|
||||
return 1;
|
||||
}
|
||||
|
||||
for(var suiteItem : group.suites){
|
||||
if(suiteData == suiteItem){
|
||||
continue;
|
||||
}
|
||||
this.getSceneScriptManager().removeGroupSuite(group, suiteItem);
|
||||
}
|
||||
this.getSceneScriptManager().addGroupSuite(group, suiteData);
|
||||
|
||||
return 0;
|
||||
}
|
||||
public int RemoveExtraGroupSuite(int groupId, int suite) {
|
||||
logger.debug("[LUA] Call RemoveExtraGroupSuite with {},{}",
|
||||
groupId,suite);
|
||||
|
||||
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
|
||||
if (group == null || group.monsters == null) {
|
||||
return 1;
|
||||
}
|
||||
var suiteData = group.getSuiteByIndex(suite);
|
||||
if(suiteData == null){
|
||||
return 1;
|
||||
}
|
||||
|
||||
this.getSceneScriptManager().removeGroupSuite(group, suiteData);
|
||||
|
||||
return 0;
|
||||
}
|
||||
public int KillExtraGroupSuite(int groupId, int suite) {
|
||||
logger.debug("[LUA] Call KillExtraGroupSuite with {},{}",
|
||||
groupId,suite);
|
||||
|
||||
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
|
||||
if (group == null || group.monsters == null) {
|
||||
return 1;
|
||||
}
|
||||
var suiteData = group.getSuiteByIndex(suite);
|
||||
if(suiteData == null){
|
||||
return 1;
|
||||
}
|
||||
|
||||
this.getSceneScriptManager().removeGroupSuite(group, suiteData);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// param3 (probably time limit for timed dungeons)
|
||||
public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) {
|
||||
logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}",
|
||||
challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5);
|
||||
|
||||
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
|
||||
var objective = objectiveKills;
|
||||
var challenge = ChallengeFactory.getChallenge(
|
||||
challengeId,
|
||||
challengeIndex,
|
||||
timeLimitOrGroupId,
|
||||
groupId,
|
||||
objectiveKills,
|
||||
param5,
|
||||
getSceneScriptManager().getScene(),
|
||||
getCurrentGroup().get()
|
||||
);
|
||||
|
||||
if(group == null){
|
||||
group = getSceneScriptManager().getGroupById(timeLimitOrGroupId);
|
||||
objective = groupId;
|
||||
}
|
||||
|
||||
if (group == null || group.monsters == null) {
|
||||
if(challenge == null){
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(getSceneScriptManager().getScene().getChallenge() != null &&
|
||||
getSceneScriptManager().getScene().getChallenge().inProgress())
|
||||
{
|
||||
return 0;
|
||||
if(challenge instanceof DungeonChallenge dungeonChallenge){
|
||||
// set if tower first stage (6-1)
|
||||
dungeonChallenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0);
|
||||
}
|
||||
|
||||
DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(),
|
||||
group, challengeId, challengeIndex, objective);
|
||||
// set if tower first stage (6-1)
|
||||
challenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0);
|
||||
|
||||
getSceneScriptManager().getScene().setChallenge(challenge);
|
||||
|
||||
challenge.start();
|
||||
return 0;
|
||||
}
|
||||
@@ -244,7 +312,7 @@ public class ScriptLib {
|
||||
|
||||
public int GetRegionEntityCount(LuaTable table) {
|
||||
logger.debug("[LUA] Call GetRegionEntityCount with {}",
|
||||
table);
|
||||
printTable(table));
|
||||
int regionId = table.get("region_eid").toint();
|
||||
int entityType = table.get("entity_type").toint();
|
||||
|
||||
@@ -267,12 +335,12 @@ public class ScriptLib {
|
||||
// TODO record time
|
||||
return 0;
|
||||
}
|
||||
public int GetGroupMonsterCount(int var1){
|
||||
logger.debug("[LUA] Call GetGroupMonsterCount with {}",
|
||||
var1);
|
||||
public int GetGroupMonsterCount(){
|
||||
logger.debug("[LUA] Call GetGroupMonsterCount ");
|
||||
|
||||
return (int) getSceneScriptManager().getScene().getEntities().values().stream()
|
||||
.filter(e -> e instanceof EntityMonster && e.getGroupId() == getSceneScriptManager().getCurrentGroup().id)
|
||||
.filter(e -> e instanceof EntityMonster &&
|
||||
e.getGroupId() == getCurrentGroup().map(sceneGroup -> sceneGroup.id).orElse(-1))
|
||||
.count();
|
||||
}
|
||||
public int SetMonsterBattleByGroup(int var1, int var2, int var3){
|
||||
@@ -314,7 +382,7 @@ public class ScriptLib {
|
||||
|
||||
var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint());
|
||||
if(entity == null){
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
getSceneScriptManager().getScene().killEntity(entity, 0);
|
||||
return 0;
|
||||
@@ -334,7 +402,11 @@ public class ScriptLib {
|
||||
var configId = table.get("config_id").toint();
|
||||
var delayTime = table.get("delay_time").toint();
|
||||
|
||||
getSceneScriptManager().spawnMonstersByConfigId(configId, delayTime);
|
||||
if(getCurrentGroup().isEmpty()){
|
||||
return 1;
|
||||
}
|
||||
|
||||
getSceneScriptManager().spawnMonstersByConfigId(getCurrentGroup().get(), configId, delayTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -353,8 +425,81 @@ public class ScriptLib {
|
||||
printTable(table));
|
||||
var configId = table.get("config_id").toint();
|
||||
|
||||
//TODO
|
||||
var group = getCurrentGroup();
|
||||
|
||||
if (group.isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var gadget = group.get().gadgets.get(configId);
|
||||
var entity = getSceneScriptManager().createGadget(group.get().id, group.get().block_id, gadget);
|
||||
|
||||
getSceneScriptManager().addEntity(entity);
|
||||
|
||||
return 0;
|
||||
}
|
||||
public int CheckRemainGadgetCountByGroupId(LuaTable table){
|
||||
logger.debug("[LUA] Call CheckRemainGadgetCountByGroupId with {}",
|
||||
printTable(table));
|
||||
var groupId = table.get("group_id").toint();
|
||||
|
||||
var count = getSceneScriptManager().getScene().getEntities().values().stream()
|
||||
.filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == groupId)
|
||||
.count();
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
public int GetGadgetStateByConfigId(int groupId, int configId){
|
||||
logger.debug("[LUA] Call GetGadgetStateByConfigId with {},{}",
|
||||
groupId, configId);
|
||||
|
||||
if(groupId == 0){
|
||||
groupId = getCurrentGroup().get().id;
|
||||
}
|
||||
final int realGroupId = groupId;
|
||||
var gadget = getSceneScriptManager().getScene().getEntities().values().stream()
|
||||
.filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == realGroupId)
|
||||
.filter(g -> g.getConfigId() == configId)
|
||||
.findFirst();
|
||||
if(gadget.isEmpty()){
|
||||
return 1;
|
||||
}
|
||||
return ((EntityGadget)gadget.get()).getState();
|
||||
}
|
||||
|
||||
public int MarkPlayerAction(int var1, int var2, int var3, int var4){
|
||||
logger.debug("[LUA] Call MarkPlayerAction with {},{},{},{}",
|
||||
var1, var2,var3,var4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int AddQuestProgress(String var1){
|
||||
logger.debug("[LUA] Call AddQuestProgress with {}",
|
||||
var1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* change the state of gadget
|
||||
*/
|
||||
public int ChangeGroupGadget(LuaTable table){
|
||||
logger.debug("[LUA] Call ChangeGroupGadget with {}",
|
||||
printTable(table));
|
||||
var configId = table.get("config_id").toint();
|
||||
var state = table.get("state").toint();
|
||||
|
||||
var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId);
|
||||
if(entity == null){
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (entity instanceof EntityGadget entityGadget) {
|
||||
entityGadget.updateState(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
package emu.grasscutter.scripts;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.script.Compilable;
|
||||
import javax.script.CompiledScript;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineFactory;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.OneArgFunction;
|
||||
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
|
||||
import org.luaj.vm2.script.LuajContext;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.props.EntityType;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.constants.ScriptGadgetState;
|
||||
import emu.grasscutter.scripts.constants.ScriptRegionShape;
|
||||
import emu.grasscutter.scripts.data.SceneMeta;
|
||||
import emu.grasscutter.scripts.serializer.LuaSerializer;
|
||||
import emu.grasscutter.scripts.serializer.Serializer;
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.OneArgFunction;
|
||||
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
|
||||
import org.luaj.vm2.script.LuajContext;
|
||||
|
||||
import javax.script.*;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ScriptLoader {
|
||||
private static ScriptEngineManager sm;
|
||||
@@ -34,9 +29,17 @@ public class ScriptLoader {
|
||||
private static ScriptEngineFactory factory;
|
||||
private static String fileType;
|
||||
private static Serializer serializer;
|
||||
|
||||
private static Map<String, CompiledScript> scripts = new HashMap<>();
|
||||
|
||||
private static ScriptLib scriptLib;
|
||||
private static LuaValue scriptLibLua;
|
||||
/**
|
||||
* suggest GC to remove it if the memory is less
|
||||
*/
|
||||
private static Map<String, SoftReference<CompiledScript>> scriptsCache = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* sceneId - SceneMeta
|
||||
*/
|
||||
private static Map<Integer, SoftReference<SceneMeta>> sceneMetaCache = new ConcurrentHashMap<>();
|
||||
|
||||
public synchronized static void init() throws Exception {
|
||||
if (sm != null) {
|
||||
throw new Exception("Script loader already initialized");
|
||||
@@ -67,6 +70,10 @@ public class ScriptLoader {
|
||||
ctx.globals.set("EventType", CoerceJavaToLua.coerce(new EventType())); // TODO - make static class to avoid instantiating a new class every scene
|
||||
ctx.globals.set("GadgetState", CoerceJavaToLua.coerce(new ScriptGadgetState()));
|
||||
ctx.globals.set("RegionShape", CoerceJavaToLua.coerce(new ScriptRegionShape()));
|
||||
|
||||
scriptLib = new ScriptLib();
|
||||
scriptLibLua = CoerceJavaToLua.coerce(scriptLib);
|
||||
ctx.globals.set("ScriptLib", scriptLibLua);
|
||||
}
|
||||
|
||||
public static ScriptEngine getEngine() {
|
||||
@@ -81,25 +88,50 @@ public class ScriptLoader {
|
||||
return serializer;
|
||||
}
|
||||
|
||||
public static CompiledScript getScriptByPath(String path) {
|
||||
CompiledScript sc = scripts.get(path);
|
||||
|
||||
Grasscutter.getLogger().info("Loaded " + path);
|
||||
|
||||
if (sc == null) {
|
||||
File file = new File(path);
|
||||
|
||||
if (!file.exists()) return null;
|
||||
|
||||
try (FileReader fr = new FileReader(file)) {
|
||||
sc = ((Compilable) getEngine()).compile(fr);
|
||||
scripts.put(path, sc);
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return sc;
|
||||
public static ScriptLib getScriptLib() {
|
||||
return scriptLib;
|
||||
}
|
||||
|
||||
public static LuaValue getScriptLibLua() {
|
||||
return scriptLibLua;
|
||||
}
|
||||
|
||||
public static <T> Optional<T> tryGet(SoftReference<T> softReference){
|
||||
try{
|
||||
return Optional.ofNullable(softReference.get());
|
||||
}catch (NullPointerException npe){
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
public static CompiledScript getScriptByPath(String path) {
|
||||
var sc = tryGet(scriptsCache.get(path));
|
||||
if (sc.isPresent()) {
|
||||
return sc.get();
|
||||
}
|
||||
|
||||
Grasscutter.getLogger().info("Loading script " + path);
|
||||
|
||||
File file = new File(path);
|
||||
|
||||
if (!file.exists()) return null;
|
||||
|
||||
try (FileReader fr = new FileReader(file)) {
|
||||
var script = ((Compilable) getEngine()).compile(fr);
|
||||
scriptsCache.put(path, new SoftReference<>(script));
|
||||
return script;
|
||||
} catch (Exception e) {
|
||||
Grasscutter.getLogger().error("Loading script {} failed!", path, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static SceneMeta getSceneMeta(int sceneId) {
|
||||
return tryGet(sceneMetaCache.get(sceneId)).orElseGet(() -> {
|
||||
var instance = SceneMeta.of(sceneId);
|
||||
sceneMetaCache.put(sceneId, new SoftReference<>(instance));
|
||||
return instance;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
28
src/main/java/emu/grasscutter/scripts/ScriptUtils.java
Normal file
28
src/main/java/emu/grasscutter/scripts/ScriptUtils.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package emu.grasscutter.scripts;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
public class ScriptUtils {
|
||||
|
||||
public static HashMap<Object, Object> toMap(LuaTable table) {
|
||||
HashMap<Object, Object> map = new HashMap<>();
|
||||
LuaValue[] rootKeys = table.keys();
|
||||
for (LuaValue k : rootKeys) {
|
||||
if (table.get(k).istable()) {
|
||||
map.put(k, toMap(table.get(k).checktable()));
|
||||
} else {
|
||||
map.put(k, table.get(k));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void print(LuaTable table) {
|
||||
Grasscutter.getLogger().info(toMap(table).toString());
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@ package emu.grasscutter.scripts.constants;
|
||||
|
||||
public class EventType {
|
||||
public static final int EVENT_NONE = 0;
|
||||
/**
|
||||
* param1: monster.configId
|
||||
*/
|
||||
public static final int EVENT_ANY_MONSTER_DIE = 1;
|
||||
public static final int EVENT_ANY_GADGET_DIE = 2;
|
||||
public static final int EVENT_VARIABLE_CHANGE = 3;
|
||||
|
||||
@@ -1,17 +1,81 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.github.davidmoten.rtreemulti.RTree;
|
||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
||||
import com.github.davidmoten.rtreemulti.geometry.Rectangle;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.scripts.SceneIndexManager;
|
||||
import emu.grasscutter.scripts.ScriptLoader;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.CompiledScript;
|
||||
import javax.script.ScriptException;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static emu.grasscutter.Configuration.SCRIPT;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneBlock {
|
||||
public int id;
|
||||
public Position max;
|
||||
public Position min;
|
||||
public List<SceneGroup> groups;
|
||||
|
||||
|
||||
public int sceneId;
|
||||
public Map<Integer,SceneGroup> groups;
|
||||
public RTree<SceneGroup, Geometry> sceneGroupIndex;
|
||||
|
||||
private transient boolean loaded; // Not an actual variable in the scripts either
|
||||
|
||||
public boolean isLoaded() {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public void setLoaded(boolean loaded) {
|
||||
this.loaded = loaded;
|
||||
}
|
||||
|
||||
public boolean contains(Position pos) {
|
||||
return pos.getX() <= max.getX() && pos.getX() >= min.getX() &&
|
||||
pos.getZ() <= max.getZ() && pos.getZ() >= min.getZ();
|
||||
}
|
||||
}
|
||||
|
||||
public SceneBlock load(int sceneId, Bindings bindings){
|
||||
if(loaded){
|
||||
return this;
|
||||
}
|
||||
this.sceneId = sceneId;
|
||||
setLoaded(true);
|
||||
|
||||
CompiledScript cs = ScriptLoader.getScriptByPath(
|
||||
SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_block" + id + "." + ScriptLoader.getScriptType()));
|
||||
|
||||
if (cs == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Eval script
|
||||
try {
|
||||
cs.eval(bindings);
|
||||
|
||||
// Set groups
|
||||
groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups")).stream()
|
||||
.collect(Collectors.toMap(x -> x.id, y -> y));
|
||||
|
||||
groups.values().forEach(g -> g.block_id = id);
|
||||
this.sceneGroupIndex = SceneIndexManager.buildIndex(3, groups.values(), g -> g.pos.toPoint());
|
||||
} catch (ScriptException e) {
|
||||
Grasscutter.getLogger().error("Error loading block " + id + " in scene " + sceneId, e);
|
||||
}
|
||||
Grasscutter.getLogger().info("scene {} block {} is loaded successfully.", sceneId, id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Rectangle toRectangle() {
|
||||
return Rectangle.create(min.toXZDoubleArray(), max.toXZDoubleArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Setter
|
||||
@ToString
|
||||
public class SceneBossChest {
|
||||
public int life_time;
|
||||
public int monster_config_id;
|
||||
public int resin;
|
||||
public int take_num;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneBusiness {
|
||||
public int type;
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneConfig {
|
||||
public Position vision_anchor;
|
||||
public Position born_pos;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
public class SceneGadget {
|
||||
public int level;
|
||||
public int config_id;
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneGadget extends SceneObject{
|
||||
public int gadget_id;
|
||||
public int state;
|
||||
public Position pos;
|
||||
public Position rot;
|
||||
public int point_type;
|
||||
public SceneBossChest boss_chest;
|
||||
public int interact_id;
|
||||
}
|
||||
|
||||
12
src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java
Normal file
12
src/main/java/emu/grasscutter/scripts/data/SceneGarbage.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneGarbage {
|
||||
public List<SceneGadget> gadgets;
|
||||
}
|
||||
@@ -1,10 +1,27 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.scripts.ScriptLoader;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.CompiledScript;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static emu.grasscutter.Configuration.SCRIPT;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneGroup {
|
||||
public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference
|
||||
|
||||
@@ -12,27 +29,140 @@ public class SceneGroup {
|
||||
public int refresh_id;
|
||||
public Position pos;
|
||||
|
||||
/**
|
||||
* ConfigId - Monster
|
||||
*/
|
||||
public Map<Integer,SceneMonster> monsters;
|
||||
public List<SceneGadget> gadgets;
|
||||
public List<SceneTrigger> triggers;
|
||||
public Map<Integer,SceneMonster> monsters; // <ConfigId, Monster>
|
||||
public Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets>
|
||||
public Map<String, SceneTrigger> triggers;
|
||||
public Map<Integer, SceneNPC> npc; // <NpcId, NPC>
|
||||
public List<SceneRegion> regions;
|
||||
public List<SceneSuite> suites;
|
||||
public SceneInitConfig init_config;
|
||||
public List<SceneVar> variables;
|
||||
|
||||
private transient boolean isLoaded; // Not an actual variable in the scripts either
|
||||
public SceneBusiness business;
|
||||
public SceneGarbage garbages;
|
||||
public SceneInitConfig init_config;
|
||||
|
||||
private transient boolean loaded; // Not an actual variable in the scripts either
|
||||
private transient CompiledScript script;
|
||||
private transient Bindings bindings;
|
||||
public static SceneGroup of(int groupId) {
|
||||
var group = new SceneGroup();
|
||||
group.id = groupId;
|
||||
return group;
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
public boolean setLoaded(boolean loaded) {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public void setLoaded(boolean loaded) {
|
||||
this.loaded = loaded;
|
||||
}
|
||||
|
||||
public int getBusinessType() {
|
||||
return this.business == null ? 0 : this.business.type;
|
||||
}
|
||||
|
||||
public List<SceneGadget> getGarbageGadgets() {
|
||||
return this.garbages == null ? null : this.garbages.gadgets;
|
||||
}
|
||||
|
||||
public CompiledScript getScript() {
|
||||
return script;
|
||||
}
|
||||
|
||||
public SceneSuite getSuiteByIndex(int index) {
|
||||
return suites.get(index - 1);
|
||||
}
|
||||
|
||||
public Bindings getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public synchronized SceneGroup load(int sceneId){
|
||||
if(loaded){
|
||||
return this;
|
||||
}
|
||||
// Set flag here so if there is no script, we dont call this function over and over again.
|
||||
setLoaded(true);
|
||||
|
||||
this.bindings = ScriptLoader.getEngine().createBindings();
|
||||
|
||||
CompiledScript cs = ScriptLoader.getScriptByPath(
|
||||
SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_group" + id + "." + ScriptLoader.getScriptType()));
|
||||
|
||||
if (cs == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.script = cs;
|
||||
|
||||
// Eval script
|
||||
try {
|
||||
cs.eval(bindings);
|
||||
|
||||
// Set
|
||||
monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream()
|
||||
.collect(Collectors.toMap(x -> x.config_id, y -> y));
|
||||
monsters.values().forEach(m -> m.group = this);
|
||||
|
||||
gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")).stream()
|
||||
.collect(Collectors.toMap(x -> x.config_id, y -> y));
|
||||
gadgets.values().forEach(m -> m.group = this);
|
||||
|
||||
triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")).stream()
|
||||
.collect(Collectors.toMap(x -> x.name, y -> y));
|
||||
triggers.values().forEach(t -> t.currentGroup = this);
|
||||
|
||||
suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites"));
|
||||
regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions"));
|
||||
init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config"));
|
||||
|
||||
// Garbages TODO fix properly later
|
||||
Object garbagesValue = bindings.get("garbages");
|
||||
if (garbagesValue != null && garbagesValue instanceof LuaValue garbagesTable) {
|
||||
garbages = new SceneGarbage();
|
||||
if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) {
|
||||
garbages.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable());
|
||||
garbages.gadgets.forEach(m -> m.group = this);
|
||||
}
|
||||
}
|
||||
|
||||
// Add variables to suite
|
||||
variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables"));
|
||||
// NPC in groups
|
||||
npc = ScriptLoader.getSerializer().toList(SceneNPC.class, bindings.get("npcs")).stream()
|
||||
.collect(Collectors.toMap(x -> x.npc_id, y -> y));
|
||||
npc.values().forEach(n -> n.group = this);
|
||||
|
||||
// Add monsters and gadgets to suite
|
||||
for (SceneSuite suite : suites) {
|
||||
suite.sceneMonsters = new ArrayList<>(
|
||||
suite.monsters.stream()
|
||||
.filter(monsters::containsKey)
|
||||
.map(monsters::get)
|
||||
.toList()
|
||||
);
|
||||
|
||||
suite.sceneGadgets = new ArrayList<>(
|
||||
suite.gadgets.stream()
|
||||
.filter(gadgets::containsKey)
|
||||
.map(gadgets::get)
|
||||
.toList()
|
||||
);
|
||||
|
||||
suite.sceneTriggers = new ArrayList<>(
|
||||
suite.triggers.stream()
|
||||
.filter(triggers::containsKey)
|
||||
.map(triggers::get)
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
} catch (ScriptException e) {
|
||||
Grasscutter.getLogger().error("Error loading group " + id + " in scene " + sceneId, e);
|
||||
}
|
||||
|
||||
Grasscutter.getLogger().info("group {} in scene {} is loaded successfully.", id, sceneId);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneInitConfig {
|
||||
public int suite;
|
||||
public int end_suite;
|
||||
|
||||
75
src/main/java/emu/grasscutter/scripts/data/SceneMeta.java
Normal file
75
src/main/java/emu/grasscutter/scripts/data/SceneMeta.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import com.github.davidmoten.rtreemulti.RTree;
|
||||
import com.github.davidmoten.rtreemulti.geometry.Geometry;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.scripts.SceneIndexManager;
|
||||
import emu.grasscutter.scripts.ScriptLoader;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.CompiledScript;
|
||||
import javax.script.ScriptException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static emu.grasscutter.Configuration.SCRIPT;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneMeta {
|
||||
|
||||
public SceneConfig config;
|
||||
public Map<Integer, SceneBlock> blocks;
|
||||
|
||||
public Bindings context;
|
||||
|
||||
public RTree<SceneBlock, Geometry> sceneBlockIndex;
|
||||
|
||||
public static SceneMeta of(int sceneId) {
|
||||
return new SceneMeta().load(sceneId);
|
||||
}
|
||||
|
||||
public SceneMeta load(int sceneId){
|
||||
// Get compiled script if cached
|
||||
CompiledScript cs = ScriptLoader.getScriptByPath(
|
||||
SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "." + ScriptLoader.getScriptType()));
|
||||
|
||||
if (cs == null) {
|
||||
Grasscutter.getLogger().warn("No script found for scene " + sceneId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create bindings
|
||||
context = ScriptLoader.getEngine().createBindings();
|
||||
|
||||
// Eval script
|
||||
try {
|
||||
cs.eval(context);
|
||||
|
||||
this.config = ScriptLoader.getSerializer().toObject(SceneConfig.class, context.get("scene_config"));
|
||||
|
||||
// TODO optimize later
|
||||
// Create blocks
|
||||
List<Integer> blockIds = ScriptLoader.getSerializer().toList(Integer.class, context.get("blocks"));
|
||||
List<SceneBlock> blocks = ScriptLoader.getSerializer().toList(SceneBlock.class, context.get("block_rects"));
|
||||
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
SceneBlock block = blocks.get(i);
|
||||
block.id = blockIds.get(i);
|
||||
|
||||
}
|
||||
|
||||
this.blocks = blocks.stream().collect(Collectors.toMap(b -> b.id, b -> b));
|
||||
this.sceneBlockIndex = SceneIndexManager.buildIndex(2, blocks, SceneBlock::toRectangle);
|
||||
|
||||
} catch (ScriptException e) {
|
||||
Grasscutter.getLogger().error("Error running script", e);
|
||||
return null;
|
||||
}
|
||||
Grasscutter.getLogger().info("scene {} metadata is loaded successfully.", sceneId);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
public class SceneMonster {
|
||||
public int level;
|
||||
public int config_id;
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneMonster extends SceneObject{
|
||||
public int monster_id;
|
||||
public Position pos;
|
||||
public Position rot;
|
||||
}
|
||||
public int pose_id;
|
||||
public int drop_id;
|
||||
}
|
||||
10
src/main/java/emu/grasscutter/scripts/data/SceneNPC.java
Normal file
10
src/main/java/emu/grasscutter/scripts/data/SceneNPC.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneNPC extends SceneObject{
|
||||
public int npc_id;
|
||||
}
|
||||
20
src/main/java/emu/grasscutter/scripts/data/SceneObject.java
Normal file
20
src/main/java/emu/grasscutter/scripts/data/SceneObject.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneObject {
|
||||
public int level;
|
||||
public int config_id;
|
||||
public int area_id;
|
||||
|
||||
public Position pos;
|
||||
public Position rot;
|
||||
/**
|
||||
* not set by lua
|
||||
*/
|
||||
public transient SceneGroup group;
|
||||
}
|
||||
@@ -5,7 +5,12 @@ import emu.grasscutter.scripts.constants.ScriptRegionShape;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import lombok.Data;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneRegion {
|
||||
public int config_id;
|
||||
public int shape;
|
||||
|
||||
@@ -2,8 +2,11 @@ package emu.grasscutter.scripts.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import emu.grasscutter.utils.Position;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneSuite {
|
||||
public List<Integer> monsters;
|
||||
public List<Integer> gadgets;
|
||||
@@ -12,4 +15,5 @@ public class SceneSuite {
|
||||
|
||||
public transient List<SceneMonster> sceneMonsters;
|
||||
public transient List<SceneGadget> sceneGadgets;
|
||||
public transient List<SceneTrigger> sceneTriggers;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
@Setter
|
||||
public class SceneTrigger {
|
||||
public String name;
|
||||
public int config_id;
|
||||
@@ -8,6 +11,7 @@ public class SceneTrigger {
|
||||
public String condition;
|
||||
public String action;
|
||||
|
||||
public transient SceneGroup currentGroup;
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if(obj instanceof SceneTrigger sceneTrigger){
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package emu.grasscutter.scripts.data;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Setter
|
||||
public class SceneVar {
|
||||
public String name;
|
||||
public int value;
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
package emu.grasscutter.scripts.serializer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.esotericsoftware.reflectasm.ConstructorAccess;
|
||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.scripts.ScriptUtils;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class LuaSerializer implements Serializer {
|
||||
|
||||
|
||||
private final static Map<Class<?>, MethodAccess> methodAccessCache = new ConcurrentHashMap<>();
|
||||
private final static Map<Class<?>, ConstructorAccess<?>> constructorCache = new ConcurrentHashMap<>();
|
||||
private final static Map<Class<?>, Map<String, FieldMeta>> fieldMetaCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public <T> List<T> toList(Class<T> type, Object obj) {
|
||||
return serializeList(type, (LuaTable) obj);
|
||||
@@ -20,7 +34,11 @@ public class LuaSerializer implements Serializer {
|
||||
}
|
||||
|
||||
public <T> List<T> serializeList(Class<T> type, LuaTable table) {
|
||||
List<T> list = new ArrayList();
|
||||
List<T> list = new ArrayList<>();
|
||||
|
||||
if (table == null) {
|
||||
return list;
|
||||
}
|
||||
|
||||
try {
|
||||
LuaValue[] keys = table.keys();
|
||||
@@ -55,7 +73,7 @@ public class LuaSerializer implements Serializer {
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
public <T> T serialize(Class<T> type, LuaTable table) {
|
||||
T object = null;
|
||||
|
||||
@@ -70,27 +88,38 @@ public class LuaSerializer implements Serializer {
|
||||
}
|
||||
|
||||
try {
|
||||
//noinspection ConfusingArgumentToVarargsMethod
|
||||
object = type.getDeclaredConstructor().newInstance();
|
||||
if (!methodAccessCache.containsKey(type)) {
|
||||
cacheType(type);
|
||||
}
|
||||
var methodAccess = methodAccessCache.get(type);
|
||||
var fieldMetaMap = fieldMetaCache.get(type);
|
||||
|
||||
object = (T) constructorCache.get(type).newInstance();
|
||||
|
||||
if (table == null) {
|
||||
return object;
|
||||
}
|
||||
|
||||
LuaValue[] keys = table.keys();
|
||||
for (LuaValue k : keys) {
|
||||
try {
|
||||
Field field = object.getClass().getDeclaredField(k.checkjstring());
|
||||
|
||||
field.setAccessible(true);
|
||||
var keyName = k.checkjstring();
|
||||
if(!fieldMetaMap.containsKey(keyName)){
|
||||
continue;
|
||||
}
|
||||
var fieldMeta = fieldMetaMap.get(keyName);
|
||||
LuaValue keyValue = table.get(k);
|
||||
|
||||
if (keyValue.istable()) {
|
||||
field.set(object, serialize(field.getType(), keyValue.checktable()));
|
||||
} else if (field.getType().equals(float.class)) {
|
||||
field.setFloat(object, keyValue.tofloat());
|
||||
} else if (field.getType().equals(int.class)) {
|
||||
field.setInt(object, keyValue.toint());
|
||||
} else if (field.getType().equals(String.class)) {
|
||||
field.set(object, keyValue.tojstring());
|
||||
methodAccess.invoke(object, fieldMeta.index, serialize(fieldMeta.getType(), keyValue.checktable()));
|
||||
} else if (fieldMeta.getType().equals(float.class)) {
|
||||
methodAccess.invoke(object, fieldMeta.index, keyValue.tofloat());
|
||||
} else if (fieldMeta.getType().equals(int.class)) {
|
||||
methodAccess.invoke(object, fieldMeta.index, keyValue.toint());
|
||||
} else if (fieldMeta.getType().equals(String.class)) {
|
||||
methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring());
|
||||
} else {
|
||||
field.set(object, keyValue);
|
||||
methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
//ex.printStackTrace();
|
||||
@@ -98,9 +127,64 @@ public class LuaSerializer implements Serializer {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Grasscutter.getLogger().info(ScriptUtils.toMap(table).toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
public <T> Map<String, FieldMeta> cacheType(Class<T> type){
|
||||
if(fieldMetaCache.containsKey(type)) {
|
||||
return fieldMetaCache.get(type);
|
||||
}
|
||||
if(!constructorCache.containsKey(type)){
|
||||
constructorCache.putIfAbsent(type, ConstructorAccess.get(type));
|
||||
}
|
||||
var methodAccess = Optional.ofNullable(methodAccessCache.get(type)).orElse(MethodAccess.get(type));
|
||||
methodAccessCache.putIfAbsent(type, methodAccess);
|
||||
|
||||
var fieldMetaMap = new HashMap<String, FieldMeta>();
|
||||
var methodNameSet = new HashSet<>(Arrays.stream(methodAccess.getMethodNames()).toList());
|
||||
|
||||
Arrays.stream(type.getDeclaredFields())
|
||||
.filter(field -> methodNameSet.contains(getSetterName(field.getName())))
|
||||
.forEach(field -> {
|
||||
var setter = getSetterName(field.getName());
|
||||
var index = methodAccess.getIndex(setter);
|
||||
fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType()));
|
||||
});
|
||||
|
||||
Arrays.stream(type.getFields())
|
||||
.filter(field -> !fieldMetaMap.containsKey(field.getName()))
|
||||
.filter(field -> methodNameSet.contains(getSetterName(field.getName())))
|
||||
.forEach(field -> {
|
||||
var setter = getSetterName(field.getName());
|
||||
var index = methodAccess.getIndex(setter);
|
||||
fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType()));
|
||||
});
|
||||
|
||||
fieldMetaCache.put(type, fieldMetaMap);
|
||||
return fieldMetaMap;
|
||||
}
|
||||
|
||||
public String getSetterName(String fieldName){
|
||||
if(fieldName == null || fieldName.length() == 0){
|
||||
return null;
|
||||
}
|
||||
if(fieldName.length() == 1){
|
||||
return "set" + fieldName.toUpperCase();
|
||||
}
|
||||
return "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
static class FieldMeta{
|
||||
String name;
|
||||
String setter;
|
||||
int index;
|
||||
Class<?> type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ import java.util.List;
|
||||
public class ScriptMonsterSpawnService {
|
||||
|
||||
private final SceneScriptManager sceneScriptManager;
|
||||
private final List<ScriptMonsterListener> onMonsterCreatedListener = new ArrayList<>();
|
||||
public final List<ScriptMonsterListener> onMonsterCreatedListener = new ArrayList<>();
|
||||
|
||||
private final List<ScriptMonsterListener> onMonsterDeadListener = new ArrayList<>();
|
||||
public final List<ScriptMonsterListener> onMonsterDeadListener = new ArrayList<>();
|
||||
|
||||
public ScriptMonsterSpawnService(SceneScriptManager sceneScriptManager){
|
||||
this.sceneScriptManager = sceneScriptManager;
|
||||
@@ -39,40 +39,5 @@ public class ScriptMonsterSpawnService {
|
||||
public void onMonsterDead(EntityMonster entityMonster){
|
||||
onMonsterDeadListener.forEach(l -> l.onNotify(entityMonster));
|
||||
}
|
||||
public void spawnMonster(int groupId, SceneMonster monster) {
|
||||
if(monster == null){
|
||||
return;
|
||||
}
|
||||
|
||||
MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id);
|
||||
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate level
|
||||
int level = monster.level;
|
||||
|
||||
if (sceneScriptManager.getScene().getDungeonData() != null) {
|
||||
level = sceneScriptManager.getScene().getDungeonData().getShowLevel();
|
||||
} else if (sceneScriptManager.getScene().getWorld().getWorldLevel() > 0) {
|
||||
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(sceneScriptManager.getScene().getWorld().getWorldLevel());
|
||||
|
||||
if (worldLevelData != null) {
|
||||
level = worldLevelData.getMonsterLevel();
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn mob
|
||||
EntityMonster entity = new EntityMonster(sceneScriptManager.getScene(), data, monster.pos, level);
|
||||
entity.getRotation().set(monster.rot);
|
||||
entity.setGroupId(groupId);
|
||||
entity.setConfigId(monster.config_id);
|
||||
|
||||
onMonsterCreatedListener.forEach(action -> action.onNotify(entity));
|
||||
|
||||
sceneScriptManager.getScene().addEntity(entity);
|
||||
|
||||
sceneScriptManager.callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class ScriptMonsterTideService {
|
||||
this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead);
|
||||
// spawn the first turn
|
||||
for (int i = 0; i < this.monsterSceneLimit; i++) {
|
||||
this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(group.id, getNextMonster());
|
||||
sceneScriptManager.addEntity(this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class ScriptMonsterTideService {
|
||||
monsterKillCount.incrementAndGet();
|
||||
if (monsterTideCount.get() > 0) {
|
||||
// add more
|
||||
sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(currentGroup.id, getNextMonster());
|
||||
sceneScriptManager.addEntity(sceneScriptManager.createMonster(currentGroup.id, currentGroup.block_id, getNextMonster()));
|
||||
}
|
||||
// spawn the last turn of monsters
|
||||
// fix the 5-2
|
||||
|
||||
Reference in New Issue
Block a user