2022-04-28 22:19:14 -07:00
|
|
|
package emu.grasscutter.scripts;
|
|
|
|
|
2022-05-15 19:19:24 +08:00
|
|
|
import ch.ethz.globis.phtree.PhTree;
|
2022-04-28 22:19:14 -07:00
|
|
|
import emu.grasscutter.Grasscutter;
|
2022-05-15 19:19:24 +08:00
|
|
|
import emu.grasscutter.data.GameData;
|
|
|
|
import emu.grasscutter.data.def.MonsterData;
|
|
|
|
import emu.grasscutter.data.def.WorldLevelData;
|
2022-04-28 22:19:14 -07:00
|
|
|
import emu.grasscutter.game.entity.EntityGadget;
|
2022-05-15 19:19:24 +08:00
|
|
|
import emu.grasscutter.game.entity.EntityMonster;
|
|
|
|
import emu.grasscutter.game.entity.GameEntity;
|
2022-04-28 22:19:14 -07:00
|
|
|
import emu.grasscutter.game.world.Scene;
|
2022-05-15 19:19:24 +08:00
|
|
|
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
2022-04-29 00:00:23 -07:00
|
|
|
import emu.grasscutter.scripts.constants.EventType;
|
2022-05-15 19:19:24 +08:00
|
|
|
import emu.grasscutter.scripts.data.*;
|
|
|
|
import emu.grasscutter.scripts.service.ScriptMonsterSpawnService;
|
|
|
|
import emu.grasscutter.scripts.service.ScriptMonsterTideService;
|
2022-04-28 22:19:14 -07:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
2022-05-15 19:19:24 +08:00
|
|
|
import org.luaj.vm2.LuaError;
|
|
|
|
import org.luaj.vm2.LuaValue;
|
|
|
|
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
|
2022-04-28 22:19:14 -07:00
|
|
|
|
2022-05-15 19:19:24 +08:00
|
|
|
import java.util.*;
|
2022-05-11 00:30:07 -04:00
|
|
|
|
2022-04-28 22:19:14 -07:00
|
|
|
public class SceneScriptManager {
|
|
|
|
private final Scene scene;
|
2022-04-29 03:06:33 -07:00
|
|
|
private final Map<String, Integer> variables;
|
2022-05-15 19:19:24 +08:00
|
|
|
private SceneMeta meta;
|
2022-04-28 22:19:14 -07:00
|
|
|
private boolean isInit;
|
2022-05-09 15:39:49 +08:00
|
|
|
/**
|
|
|
|
* SceneTrigger Set
|
|
|
|
*/
|
|
|
|
private final Map<String, SceneTrigger> triggers;
|
|
|
|
/**
|
|
|
|
* current triggers controlled by RefreshGroup
|
|
|
|
*/
|
|
|
|
private final Int2ObjectOpenHashMap<Set<SceneTrigger>> currentTriggers;
|
2022-04-30 01:08:38 -07:00
|
|
|
private final Int2ObjectOpenHashMap<SceneRegion> regions;
|
2022-05-09 15:39:49 +08:00
|
|
|
private Map<Integer,SceneGroup> sceneGroups;
|
|
|
|
private ScriptMonsterTideService scriptMonsterTideService;
|
|
|
|
private ScriptMonsterSpawnService scriptMonsterSpawnService;
|
2022-05-15 19:19:24 +08:00
|
|
|
/**
|
|
|
|
* blockid - loaded groupSet
|
|
|
|
*/
|
|
|
|
private Int2ObjectMap<Set<SceneGroup>> loadedGroupSetPerBlock;
|
2022-04-28 22:19:14 -07:00
|
|
|
public SceneScriptManager(Scene scene) {
|
|
|
|
this.scene = scene;
|
2022-05-09 15:39:49 +08:00
|
|
|
this.triggers = new HashMap<>();
|
|
|
|
this.currentTriggers = new Int2ObjectOpenHashMap<>();
|
|
|
|
|
2022-04-30 01:08:38 -07:00
|
|
|
this.regions = new Int2ObjectOpenHashMap<>();
|
2022-04-29 02:38:25 -07:00
|
|
|
this.variables = new HashMap<>();
|
2022-05-09 15:39:49 +08:00
|
|
|
this.sceneGroups = new HashMap<>();
|
|
|
|
this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this);
|
2022-05-15 19:19:24 +08:00
|
|
|
this.loadedGroupSetPerBlock = new Int2ObjectOpenHashMap<>();
|
2022-05-09 15:39:49 +08:00
|
|
|
|
2022-04-28 22:19:14 -07:00
|
|
|
// TEMPORARY
|
2022-05-15 19:19:24 +08:00
|
|
|
if (this.getScene().getId() < 10 && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) {
|
2022-04-28 22:19:14 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create
|
|
|
|
this.init();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Scene getScene() {
|
|
|
|
return scene;
|
|
|
|
}
|
|
|
|
|
|
|
|
public SceneConfig getConfig() {
|
2022-05-15 19:19:24 +08:00
|
|
|
if(!isInit){
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return meta.config;
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
|
|
|
|
2022-05-15 19:19:24 +08:00
|
|
|
public Map<Integer, SceneBlock> getBlocks() {
|
|
|
|
return meta.blocks;
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
|
|
|
|
2022-04-29 03:06:33 -07:00
|
|
|
public Map<String, Integer> getVariables() {
|
2022-04-29 02:38:25 -07:00
|
|
|
return variables;
|
|
|
|
}
|
|
|
|
|
2022-04-28 22:19:14 -07:00
|
|
|
public Set<SceneTrigger> getTriggersByEvent(int eventId) {
|
2022-05-09 15:39:49 +08:00
|
|
|
return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>());
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
|
|
|
public void registerTrigger(SceneTrigger trigger) {
|
2022-05-09 15:39:49 +08:00
|
|
|
this.triggers.put(trigger.name, trigger);
|
2022-04-28 22:19:14 -07:00
|
|
|
getTriggersByEvent(trigger.event).add(trigger);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void deregisterTrigger(SceneTrigger trigger) {
|
2022-05-09 15:39:49 +08:00
|
|
|
this.triggers.remove(trigger.name);
|
2022-04-28 22:19:14 -07:00
|
|
|
getTriggersByEvent(trigger.event).remove(trigger);
|
|
|
|
}
|
2022-05-18 15:13:31 +08:00
|
|
|
public void resetTriggers(int eventId) {
|
|
|
|
currentTriggers.put(eventId, new HashSet<>());
|
2022-05-09 15:39:49 +08:00
|
|
|
}
|
|
|
|
public void refreshGroup(SceneGroup group, int suiteIndex){
|
|
|
|
var suite = group.getSuiteByIndex(suiteIndex);
|
|
|
|
if(suite == null){
|
|
|
|
return;
|
|
|
|
}
|
2022-05-18 15:13:31 +08:00
|
|
|
if(suite.sceneTriggers.size() > 0){
|
|
|
|
for(var trigger : suite.sceneTriggers){
|
|
|
|
resetTriggers(trigger.event);
|
|
|
|
this.currentTriggers.get(trigger.event).add(trigger);
|
|
|
|
}
|
2022-05-09 15:39:49 +08:00
|
|
|
}
|
|
|
|
spawnMonstersInGroup(group, suite);
|
|
|
|
spawnGadgetsInGroup(group, suite);
|
|
|
|
}
|
2022-04-30 01:08:38 -07:00
|
|
|
public SceneRegion getRegionById(int id) {
|
|
|
|
return regions.get(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void registerRegion(SceneRegion region) {
|
|
|
|
regions.put(region.config_id, region);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void deregisterRegion(SceneRegion region) {
|
|
|
|
regions.remove(region.config_id);
|
|
|
|
}
|
2022-05-15 19:19:24 +08:00
|
|
|
|
|
|
|
public Int2ObjectMap<Set<SceneGroup>> getLoadedGroupSetPerBlock() {
|
|
|
|
return loadedGroupSetPerBlock;
|
|
|
|
}
|
|
|
|
|
2022-04-28 22:19:14 -07:00
|
|
|
// TODO optimize
|
|
|
|
public SceneGroup getGroupById(int groupId) {
|
|
|
|
for (SceneBlock block : this.getScene().getLoadedBlocks()) {
|
|
|
|
for (SceneGroup group : block.groups) {
|
|
|
|
if (group.id == groupId) {
|
2022-05-15 19:19:24 +08:00
|
|
|
if(!group.isLoaded()){
|
2022-05-18 15:13:31 +08:00
|
|
|
getScene().onLoadGroup(List.of(group));
|
2022-05-15 19:19:24 +08:00
|
|
|
}
|
2022-04-28 22:19:14 -07:00
|
|
|
return group;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void init() {
|
2022-05-15 19:19:24 +08:00
|
|
|
var meta = ScriptLoader.getSceneMeta(getScene().getId());
|
|
|
|
if (meta == null){
|
2022-04-29 01:15:40 -07:00
|
|
|
return;
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
2022-05-15 19:19:24 +08:00
|
|
|
this.meta = meta;
|
|
|
|
|
2022-04-28 22:19:14 -07:00
|
|
|
// TEMP
|
|
|
|
this.isInit = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isInit() {
|
|
|
|
return isInit;
|
|
|
|
}
|
|
|
|
|
2022-05-15 19:19:24 +08:00
|
|
|
public void loadBlockFromScript(SceneBlock block) {
|
|
|
|
block.load(scene.getId(), meta.context);
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
|
|
|
|
2022-04-29 02:07:25 -07:00
|
|
|
public void loadGroupFromScript(SceneGroup group) {
|
2022-05-19 11:15:40 +08:00
|
|
|
group.load(getScene().getId());
|
2022-04-28 22:19:14 -07:00
|
|
|
|
2022-05-19 02:28:25 -07:00
|
|
|
if (group.variables != null) {
|
|
|
|
group.variables.forEach(var -> this.getVariables().put(var.name, var.value));
|
|
|
|
}
|
|
|
|
|
2022-05-15 19:19:24 +08:00
|
|
|
this.sceneGroups.put(group.id, group);
|
|
|
|
|
|
|
|
if(group.regions != null){
|
|
|
|
group.regions.forEach(this::registerRegion);
|
|
|
|
}
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
|
|
|
|
2022-04-30 01:08:38 -07:00
|
|
|
public void checkRegions() {
|
|
|
|
if (this.regions.size() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (SceneRegion region : this.regions.values()) {
|
|
|
|
getScene().getEntities().values()
|
|
|
|
.stream()
|
|
|
|
.filter(e -> e.getEntityType() <= 2 && region.contains(e.getPosition()))
|
|
|
|
.forEach(region::addEntity);
|
2022-04-28 22:19:14 -07:00
|
|
|
|
2022-04-30 01:08:38 -07:00
|
|
|
if (region.hasNewEntities()) {
|
|
|
|
// This is not how it works, source_eid should be region entity id, but we dont have an entity for regions yet
|
|
|
|
callEvent(EventType.EVENT_ENTER_REGION, new ScriptArgs(region.config_id).setSourceEntityId(region.config_id));
|
|
|
|
|
|
|
|
region.resetNewEntities();
|
|
|
|
}
|
|
|
|
}
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
|
|
|
|
2022-05-03 23:13:42 -07:00
|
|
|
public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) {
|
|
|
|
spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex));
|
|
|
|
}
|
|
|
|
|
2022-04-29 02:07:25 -07:00
|
|
|
public void spawnGadgetsInGroup(SceneGroup group) {
|
2022-05-03 23:13:42 -07:00
|
|
|
spawnGadgetsInGroup(group, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) {
|
2022-05-17 11:17:38 +08:00
|
|
|
var gadgets = group.gadgets.values();
|
2022-05-03 23:13:42 -07:00
|
|
|
|
|
|
|
if (suite != null) {
|
|
|
|
gadgets = suite.sceneGadgets;
|
|
|
|
}
|
2022-05-15 19:19:24 +08:00
|
|
|
|
|
|
|
var toCreate = gadgets.stream()
|
2022-05-18 15:13:31 +08:00
|
|
|
.map(g -> createGadget(g.groupId, group.block_id, g))
|
2022-05-15 19:19:24 +08:00
|
|
|
.filter(Objects::nonNull)
|
|
|
|
.toList();
|
|
|
|
this.addEntities(toCreate);
|
2022-04-29 01:03:16 -07:00
|
|
|
}
|
2022-05-09 15:39:49 +08:00
|
|
|
|
2022-04-29 03:06:33 -07:00
|
|
|
public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
|
2022-05-07 21:47:13 +08:00
|
|
|
var suite = group.getSuiteByIndex(suiteIndex);
|
|
|
|
if(suite == null){
|
|
|
|
return;
|
|
|
|
}
|
2022-05-09 15:39:49 +08:00
|
|
|
spawnMonstersInGroup(group, suite);
|
|
|
|
}
|
|
|
|
public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) {
|
|
|
|
if(suite == null || suite.sceneMonsters.size() <= 0){
|
|
|
|
return;
|
2022-05-08 17:11:02 +08:00
|
|
|
}
|
2022-05-15 19:19:24 +08:00
|
|
|
this.addEntities(suite.sceneMonsters.stream()
|
|
|
|
.map(mob -> createMonster(group.id, group.block_id, mob)).toList());
|
|
|
|
|
2022-04-29 03:06:33 -07:00
|
|
|
}
|
|
|
|
|
2022-04-29 01:03:16 -07:00
|
|
|
public void spawnMonstersInGroup(SceneGroup group) {
|
2022-05-15 19:19:24 +08:00
|
|
|
this.addEntities(group.monsters.values().stream()
|
|
|
|
.map(mob -> createMonster(group.id, group.block_id, mob)).toList());
|
2022-04-29 03:06:33 -07:00
|
|
|
}
|
2022-05-07 21:47:13 +08:00
|
|
|
|
2022-05-09 15:39:49 +08:00
|
|
|
public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) {
|
|
|
|
this.scriptMonsterTideService =
|
|
|
|
new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId);
|
2022-05-07 21:47:13 +08:00
|
|
|
|
|
|
|
}
|
2022-05-10 00:05:01 +08:00
|
|
|
public void unloadCurrentMonsterTide(){
|
|
|
|
if(this.getScriptMonsterTideService() == null){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.getScriptMonsterTideService().unload();
|
|
|
|
}
|
2022-05-18 15:13:31 +08:00
|
|
|
public void spawnMonstersByConfigId(SceneGroup group, int configId, int delayTime) {
|
2022-05-09 15:39:49 +08:00
|
|
|
// TODO delay
|
2022-05-18 15:13:31 +08:00
|
|
|
getScene().addEntity(createMonster(group.id, group.block_id, group.monsters.get(configId)));
|
2022-04-29 01:03:16 -07:00
|
|
|
}
|
2022-04-28 22:19:14 -07:00
|
|
|
// Events
|
|
|
|
|
2022-04-29 00:00:23 -07:00
|
|
|
public void callEvent(int eventType, ScriptArgs params) {
|
2022-05-19 11:15:40 +08:00
|
|
|
try{
|
|
|
|
ScriptLoader.getScriptLib().setSceneScriptManager(this);
|
|
|
|
for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
|
|
|
|
try{
|
|
|
|
ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup);
|
2022-05-10 00:05:01 +08:00
|
|
|
|
2022-05-19 11:15:40 +08:00
|
|
|
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();
|
2022-05-15 19:19:24 +08:00
|
|
|
}
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
2022-05-19 11:15:40 +08:00
|
|
|
}finally {
|
|
|
|
// make sure it is removed
|
|
|
|
ScriptLoader.getScriptLib().removeSceneScriptManager();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public 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);
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
2022-05-19 11:15:40 +08:00
|
|
|
return ret;
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|
2022-05-07 21:47:13 +08:00
|
|
|
|
2022-05-09 15:39:49 +08:00
|
|
|
public LuaValue safetyCall(String name, LuaValue func, LuaValue args){
|
|
|
|
try{
|
2022-05-19 11:15:40 +08:00
|
|
|
return func.call(ScriptLoader.getScriptLibLua(), args);
|
2022-05-09 15:39:49 +08:00
|
|
|
}catch (LuaError error){
|
2022-05-18 15:13:31 +08:00
|
|
|
ScriptLib.logger.error("[LUA] call trigger failed {},{},{}",name,args,error.getMessage());
|
2022-05-09 15:39:49 +08:00
|
|
|
return LuaValue.valueOf(-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public ScriptMonsterTideService getScriptMonsterTideService() {
|
|
|
|
return scriptMonsterTideService;
|
|
|
|
}
|
|
|
|
|
|
|
|
public ScriptMonsterSpawnService getScriptMonsterSpawnService() {
|
|
|
|
return scriptMonsterSpawnService;
|
|
|
|
}
|
|
|
|
|
2022-05-18 15:13:31 +08:00
|
|
|
public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) {
|
2022-05-15 19:19:24 +08:00
|
|
|
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);
|
2022-05-18 02:21:34 -07:00
|
|
|
entity.setPointType(g.point_type);
|
|
|
|
entity.buildContent();
|
2022-05-18 05:33:00 -07:00
|
|
|
|
|
|
|
// Lua event
|
|
|
|
this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId()));
|
2022-05-15 19:19:24 +08:00
|
|
|
|
|
|
|
return entity;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
this.getScriptMonsterSpawnService()
|
|
|
|
.onMonsterCreatedListener.forEach(action -> action.onNotify(entity));
|
2022-05-18 05:33:00 -07:00
|
|
|
|
2022-05-15 19:19:24 +08:00
|
|
|
return entity;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addEntity(GameEntity gameEntity){
|
|
|
|
getScene().addEntity(gameEntity);
|
|
|
|
}
|
2022-05-18 05:33:00 -07:00
|
|
|
|
2022-05-15 19:19:24 +08:00
|
|
|
public void meetEntities(List<? extends GameEntity> gameEntity){
|
|
|
|
getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_MEET);
|
|
|
|
}
|
2022-05-18 05:33:00 -07:00
|
|
|
|
2022-05-15 19:19:24 +08:00
|
|
|
public void addEntities(List<? extends GameEntity> gameEntity){
|
|
|
|
getScene().addEntities(gameEntity);
|
|
|
|
}
|
|
|
|
|
|
|
|
public PhTree<SceneBlock> getBlocksIndex() {
|
|
|
|
return meta.sceneBlockIndex;
|
|
|
|
}
|
2022-04-28 22:19:14 -07:00
|
|
|
}
|