SceneScriptManager.java 11 KB
Newer Older
1
2
package emu.grasscutter.scripts;

Akka's avatar
Akka committed
3
import ch.ethz.globis.phtree.PhTree;
4
import emu.grasscutter.Grasscutter;
Akka's avatar
Akka committed
5
6
7
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.data.def.WorldLevelData;
8
import emu.grasscutter.game.entity.EntityGadget;
Akka's avatar
Akka committed
9
10
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
11
import emu.grasscutter.game.world.Scene;
Akka's avatar
Akka committed
12
import emu.grasscutter.net.proto.VisionTypeOuterClass;
Melledy's avatar
Melledy committed
13
import emu.grasscutter.scripts.constants.EventType;
Akka's avatar
Akka committed
14
15
16
import emu.grasscutter.scripts.data.*;
import emu.grasscutter.scripts.service.ScriptMonsterSpawnService;
import emu.grasscutter.scripts.service.ScriptMonsterTideService;
17
18
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
Akka's avatar
Akka committed
19
20
21
import org.luaj.vm2.LuaError;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
22

Akka's avatar
Akka committed
23
import java.util.*;
24

25
26
public class SceneScriptManager {
	private final Scene scene;
Melledy's avatar
Melledy committed
27
	private final Map<String, Integer> variables;
Akka's avatar
Akka committed
28
	private SceneMeta meta;
29
	private boolean isInit;
30
31
32
33
34
35
36
37
	/**
	 * SceneTrigger Set
	 */
	private final Map<String, SceneTrigger> triggers;
	/**
	 * current triggers controlled by RefreshGroup
	 */
	private final Int2ObjectOpenHashMap<Set<SceneTrigger>> currentTriggers;
Melledy's avatar
Melledy committed
38
	private final Int2ObjectOpenHashMap<SceneRegion> regions;
39
40
41
	private Map<Integer,SceneGroup> sceneGroups;
	private ScriptMonsterTideService scriptMonsterTideService;
	private ScriptMonsterSpawnService scriptMonsterSpawnService;
Akka's avatar
Akka committed
42
43
44
45
	/**
	 * blockid - loaded groupSet
	 */
	private Int2ObjectMap<Set<SceneGroup>> loadedGroupSetPerBlock;
46
47
	public SceneScriptManager(Scene scene) {
		this.scene = scene;
48
49
50
		this.triggers = new HashMap<>();
		this.currentTriggers = new Int2ObjectOpenHashMap<>();

Melledy's avatar
Melledy committed
51
		this.regions = new Int2ObjectOpenHashMap<>();
52
		this.variables = new HashMap<>();
53
54
		this.sceneGroups = new HashMap<>();
		this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this);
Akka's avatar
Akka committed
55
		this.loadedGroupSetPerBlock = new Int2ObjectOpenHashMap<>();
56

57
		// TEMPORARY
Akka's avatar
Akka committed
58
		if (this.getScene().getId() < 10 && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) {
59
60
61
62
63
64
65
66
67
68
69
70
			return;
		}
		
		// Create
		this.init();
	}
	
	public Scene getScene() {
		return scene;
	}

	public SceneConfig getConfig() {
Akka's avatar
Akka committed
71
72
73
74
		if(!isInit){
			return null;
		}
		return meta.config;
75
76
	}

Akka's avatar
Akka committed
77
78
	public Map<Integer, SceneBlock> getBlocks() {
		return meta.blocks;
79
80
	}

Melledy's avatar
Melledy committed
81
	public Map<String, Integer> getVariables() {
82
83
84
		return variables;
	}

85
	public Set<SceneTrigger> getTriggersByEvent(int eventId) {
86
		return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>());
87
88
	}
	public void registerTrigger(SceneTrigger trigger) {
89
		this.triggers.put(trigger.name, trigger);
90
91
92
93
		getTriggersByEvent(trigger.event).add(trigger);
	}
	
	public void deregisterTrigger(SceneTrigger trigger) {
94
		this.triggers.remove(trigger.name);
95
96
		getTriggersByEvent(trigger.event).remove(trigger);
	}
Akka's avatar
Akka committed
97
98
	public void resetTriggers(int eventId) {
		currentTriggers.put(eventId, new HashSet<>());
99
100
101
102
103
104
	}
	public void refreshGroup(SceneGroup group, int suiteIndex){
		var suite = group.getSuiteByIndex(suiteIndex);
		if(suite == null){
			return;
		}
Akka's avatar
Akka committed
105
106
107
108
109
		if(suite.sceneTriggers.size() > 0){
			for(var trigger : suite.sceneTriggers){
				resetTriggers(trigger.event);
				this.currentTriggers.get(trigger.event).add(trigger);
			}
110
111
112
113
		}
		spawnMonstersInGroup(group, suite);
		spawnGadgetsInGroup(group, suite);
	}
Melledy's avatar
Melledy committed
114
115
116
117
118
119
120
121
122
123
124
	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);
	}
Akka's avatar
Akka committed
125
126
127
128
129

	public Int2ObjectMap<Set<SceneGroup>> getLoadedGroupSetPerBlock() {
		return loadedGroupSetPerBlock;
	}

130
131
132
133
134
	// TODO optimize
	public SceneGroup getGroupById(int groupId) {
		for (SceneBlock block : this.getScene().getLoadedBlocks()) {
			for (SceneGroup group : block.groups) {
				if (group.id == groupId) {
Akka's avatar
Akka committed
135
					if(!group.isLoaded()){
Akka's avatar
Akka committed
136
						getScene().onLoadGroup(List.of(group));
Akka's avatar
Akka committed
137
					}
138
139
140
141
142
143
144
145
					return group;
				}
			}
		}
		return null;
	}

	private void init() {
Akka's avatar
Akka committed
146
147
		var meta = ScriptLoader.getSceneMeta(getScene().getId());
		if (meta == null){
148
			return;
149
		}
Akka's avatar
Akka committed
150
151
		this.meta = meta;

152
153
154
155
156
157
158
159
		// TEMP
		this.isInit = true;
	}

	public boolean isInit() {
		return isInit;
	}
	
Akka's avatar
Akka committed
160
161
	public void loadBlockFromScript(SceneBlock block) {
		block.load(scene.getId(), meta.context);
162
163
	}
	
164
	public void loadGroupFromScript(SceneGroup group) {
165
		group.load(getScene().getId());
166

167
168
169
170
		if (group.variables != null) {
			group.variables.forEach(var -> this.getVariables().put(var.name, var.value));
		}

Akka's avatar
Akka committed
171
172
173
174
175
		this.sceneGroups.put(group.id, group);

		if(group.regions != null){
			group.regions.forEach(this::registerRegion);
		}
176
177
	}
	
Melledy's avatar
Melledy committed
178
179
180
181
182
183
184
185
186
187
	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);
188

Melledy's avatar
Melledy committed
189
190
191
192
193
194
195
			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();
			}
		}
196
197
	}
	
198
199
200
201
	public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) {
		spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex));
	}
	
202
	public void spawnGadgetsInGroup(SceneGroup group) {
203
204
205
206
		spawnGadgetsInGroup(group, null);
	}
	
	public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) {
Akka's avatar
Akka committed
207
		var gadgets = group.gadgets.values();
208
209
210
211
		
		if (suite != null) {
			gadgets = suite.sceneGadgets;
		}
Akka's avatar
Akka committed
212
213

		var toCreate = gadgets.stream()
Akka's avatar
Akka committed
214
				.map(g -> createGadget(g.groupId, group.block_id, g))
Akka's avatar
Akka committed
215
216
217
				.filter(Objects::nonNull)
				.toList();
		this.addEntities(toCreate);
Melledy's avatar
Melledy committed
218
	}
219

Melledy's avatar
Melledy committed
220
	public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
221
222
223
224
		var suite = group.getSuiteByIndex(suiteIndex);
		if(suite == null){
			return;
		}
225
226
227
228
229
		spawnMonstersInGroup(group, suite);
	}
	public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) {
		if(suite == null || suite.sceneMonsters.size() <= 0){
			return;
230
		}
Akka's avatar
Akka committed
231
232
233
		this.addEntities(suite.sceneMonsters.stream()
				.map(mob -> createMonster(group.id, group.block_id, mob)).toList());

Melledy's avatar
Melledy committed
234
235
	}
	
Melledy's avatar
Melledy committed
236
	public void spawnMonstersInGroup(SceneGroup group) {
Akka's avatar
Akka committed
237
238
		this.addEntities(group.monsters.values().stream()
				.map(mob -> createMonster(group.id, group.block_id, mob)).toList());
Melledy's avatar
Melledy committed
239
	}
240

241
242
243
	public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) {
		this.scriptMonsterTideService =
				new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId);
244
245

	}
246
247
248
249
250
251
	public void unloadCurrentMonsterTide(){
		if(this.getScriptMonsterTideService() == null){
			return;
		}
		this.getScriptMonsterTideService().unload();
	}
Akka's avatar
Akka committed
252
	public void spawnMonstersByConfigId(SceneGroup group, int configId, int delayTime) {
253
		// TODO delay
Akka's avatar
Akka committed
254
		getScene().addEntity(createMonster(group.id, group.block_id, group.monsters.get(configId)));
Melledy's avatar
Melledy committed
255
	}
256
257
	// Events
	
Melledy's avatar
Melledy committed
258
	public void callEvent(int eventType, ScriptArgs params) {
259
260
261
262
263
		try{
			ScriptLoader.getScriptLib().setSceneScriptManager(this);
			for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
				try{
					ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup);
264

265
266
267
268
269
270
271
272
273
274
275
276
					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();
Akka's avatar
Akka committed
277
				}
278
			}
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
		}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);
301
		}
302
		return ret;
303
	}
304

305
306
	public LuaValue safetyCall(String name, LuaValue func, LuaValue args){
		try{
307
			return func.call(ScriptLoader.getScriptLibLua(), args);
308
		}catch (LuaError error){
Akka's avatar
Akka committed
309
			ScriptLib.logger.error("[LUA] call trigger failed {},{},{}",name,args,error.getMessage());
310
311
312
313
314
315
316
317
318
319
320
321
			return LuaValue.valueOf(-1);
		}
	}

	public ScriptMonsterTideService getScriptMonsterTideService() {
		return scriptMonsterTideService;
	}

	public ScriptMonsterSpawnService getScriptMonsterSpawnService() {
		return scriptMonsterSpawnService;
	}

Akka's avatar
Akka committed
322
	public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) {
Akka's avatar
Akka committed
323
324
325
326
327
328
329
330
331
332
333
		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);
Melledy's avatar
Melledy committed
334
335
		entity.setPointType(g.point_type);
		entity.buildContent();
336
337
338
		
		// Lua event
		this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId()));
Akka's avatar
Akka committed
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375

		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));
376
		
Akka's avatar
Akka committed
377
378
379
380
381
382
		return entity;
	}

	public void addEntity(GameEntity gameEntity){
		getScene().addEntity(gameEntity);
	}
383
	
Akka's avatar
Akka committed
384
385
386
	public void meetEntities(List<? extends GameEntity> gameEntity){
		getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_MEET);
	}
387
	
Akka's avatar
Akka committed
388
389
390
391
392
393
394
	public void addEntities(List<? extends GameEntity> gameEntity){
		getScene().addEntities(gameEntity);
	}

	public PhTree<SceneBlock> getBlocksIndex() {
		return meta.sceneBlockIndex;
	}
395
}