SceneScriptManager.java 12 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
24
25
import javax.script.Bindings;
import javax.script.ScriptException;
import java.util.*;
26

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

Melledy's avatar
Melledy committed
59
		this.regions = new Int2ObjectOpenHashMap<>();
60
		this.variables = new HashMap<>();
61
62
		this.sceneGroups = new HashMap<>();
		this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this);
Akka's avatar
Akka committed
63
		this.loadedGroupSetPerBlock = new Int2ObjectOpenHashMap<>();
64

65
		// TEMPORARY
Akka's avatar
Akka committed
66
		if (this.getScene().getId() < 10 && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) {
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
			return;
		}
		
		// Create
		this.init();
	}
	
	public Scene getScene() {
		return scene;
	}

	public ScriptLib getScriptLib() {
		return scriptLib;
	}
	
	public LuaValue getScriptLibLua() {
		return scriptLibLua;
	}

	public Bindings getBindings() {
		return bindings;
	}

	public SceneConfig getConfig() {
Akka's avatar
Akka committed
91
92
93
94
		if(!isInit){
			return null;
		}
		return meta.config;
95
96
	}

97
98
99
100
	public SceneGroup getCurrentGroup() {
		return currentGroup;
	}

Akka's avatar
Akka committed
101
102
	public Map<Integer, SceneBlock> getBlocks() {
		return meta.blocks;
103
104
	}

Melledy's avatar
Melledy committed
105
	public Map<String, Integer> getVariables() {
106
107
108
		return variables;
	}

109
	public Set<SceneTrigger> getTriggersByEvent(int eventId) {
110
		return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>());
111
112
	}
	public void registerTrigger(SceneTrigger trigger) {
113
		this.triggers.put(trigger.name, trigger);
114
115
116
117
		getTriggersByEvent(trigger.event).add(trigger);
	}
	
	public void deregisterTrigger(SceneTrigger trigger) {
118
		this.triggers.remove(trigger.name);
119
120
		getTriggersByEvent(trigger.event).remove(trigger);
	}
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
	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 refreshGroup(SceneGroup group, int suiteIndex){
		var suite = group.getSuiteByIndex(suiteIndex);
		if(suite == null){
			return;
		}
		if(suite.triggers.size() > 0){
			resetTriggers(suite.triggers);
		}
		spawnMonstersInGroup(group, suite);
		spawnGadgetsInGroup(group, suite);
	}
Melledy's avatar
Melledy committed
139
140
141
142
143
144
145
146
147
148
149
	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
150
151
152
153
154

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

155
156
157
158
159
	// 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
160
161
162
					if(!group.isLoaded()){
						loadGroupFromScript(group);
					}
163
164
165
166
167
168
169
170
171
172
173
174
					return group;
				}
			}
		}
		return null;
	}

	private void init() {
		// Create bindings
		bindings = ScriptLoader.getEngine().createBindings();
		// Set variables
		bindings.put("ScriptLib", getScriptLib());
Melledy's avatar
Melledy committed
175

Akka's avatar
Akka committed
176
177
		var meta = ScriptLoader.getSceneMeta(getScene().getId());
		if (meta == null){
178
			return;
179
		}
Akka's avatar
Akka committed
180
181
		this.meta = meta;

182
183
184
185
186
187
188
189
		// TEMP
		this.isInit = true;
	}

	public boolean isInit() {
		return isInit;
	}
	
Akka's avatar
Akka committed
190
191
	public void loadBlockFromScript(SceneBlock block) {
		block.load(scene.getId(), meta.context);
192
193
	}
	
194
	public void loadGroupFromScript(SceneGroup group) {
Akka's avatar
Akka committed
195
196
		group.load(getScene().getId(), meta.context);

197
		try {
Akka's avatar
Akka committed
198
199
			// build the trigger for this scene
			group.getScript().eval(getBindings());
200
		} catch (ScriptException e) {
Akka's avatar
Akka committed
201
			Grasscutter.getLogger().error("Could not build the trigger for this scene", e);
202
203
		}

Akka's avatar
Akka committed
204
205
206
207
208
209
210
211
212
		group.variables.forEach(var -> this.getVariables().put(var.name, var.value));
		this.sceneGroups.put(group.id, group);

		if(group.triggers != null){
			group.triggers.forEach(this::registerTrigger);
		}
		if(group.regions != null){
			group.regions.forEach(this::registerRegion);
		}
213
214
	}
	
Melledy's avatar
Melledy committed
215
216
217
218
219
220
221
222
223
224
	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);
225

Melledy's avatar
Melledy committed
226
227
228
229
230
231
232
			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();
			}
		}
233
234
	}
	
235
236
237
238
	public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) {
		spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex));
	}
	
239
	public void spawnGadgetsInGroup(SceneGroup group) {
240
241
242
243
		spawnGadgetsInGroup(group, null);
	}
	
	public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) {
Akka's avatar
Akka committed
244
		var gadgets = group.gadgets.values();
245
246
247
248
		
		if (suite != null) {
			gadgets = suite.sceneGadgets;
		}
Akka's avatar
Akka committed
249
250
251
252
253
254

		var toCreate = gadgets.stream()
				.map(g -> createGadgets(g.groupId, group.block_id, g))
				.filter(Objects::nonNull)
				.toList();
		this.addEntities(toCreate);
Melledy's avatar
Melledy committed
255
	}
256

Melledy's avatar
Melledy committed
257
	public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
258
259
260
261
		var suite = group.getSuiteByIndex(suiteIndex);
		if(suite == null){
			return;
		}
262
263
264
265
266
		spawnMonstersInGroup(group, suite);
	}
	public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) {
		if(suite == null || suite.sceneMonsters.size() <= 0){
			return;
267
		}
268
		this.currentGroup = group;
Akka's avatar
Akka committed
269
270
271
		this.addEntities(suite.sceneMonsters.stream()
				.map(mob -> createMonster(group.id, group.block_id, mob)).toList());

Melledy's avatar
Melledy committed
272
273
	}
	
Melledy's avatar
Melledy committed
274
	public void spawnMonstersInGroup(SceneGroup group) {
275
		this.currentGroup = group;
Akka's avatar
Akka committed
276
277
		this.addEntities(group.monsters.values().stream()
				.map(mob -> createMonster(group.id, group.block_id, mob)).toList());
Melledy's avatar
Melledy committed
278
	}
279

280
281
282
283
	public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) {
		this.currentGroup = group;
		this.scriptMonsterTideService =
				new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId);
284
285

	}
286
287
288
289
290
291
	public void unloadCurrentMonsterTide(){
		if(this.getScriptMonsterTideService() == null){
			return;
		}
		this.getScriptMonsterTideService().unload();
	}
292
293
	public void spawnMonstersByConfigId(int configId, int delayTime) {
		// TODO delay
Akka's avatar
Akka committed
294
295
		getScene().addEntity(
				createMonster(this.currentGroup.id, this.currentGroup.block_id, this.currentGroup.monsters.get(configId)));
Melledy's avatar
Melledy committed
296
	}
297
298
	// Events
	
Melledy's avatar
Melledy committed
299
300
301
	public void callEvent(int eventType, ScriptArgs params) {
		for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
			LuaValue condition = null;
302
			
Melledy's avatar
Melledy committed
303
304
			if (trigger.condition != null && !trigger.condition.isEmpty()) {
				condition = (LuaValue) this.getBindings().get(trigger.condition);
305
306
			}
			
Melledy's avatar
Melledy committed
307
308
309
310
311
312
313
314
			LuaValue ret = LuaValue.TRUE;
			
			if (condition != null) {
				LuaValue args = LuaValue.NIL;
				
				if (params != null) {
					args = CoerceJavaToLua.coerce(params);
				}
315
316

				ScriptLib.logger.trace("Call Condition Trigger {}", trigger);
317
				ret = safetyCall(trigger.condition, condition, args);
318
319
			}
			
320
			if (ret.isboolean() && ret.checkboolean()) {
Akka's avatar
Akka committed
321
322
323
				if(trigger.action == null || trigger.action.isEmpty()){
					return;
				}
324
				ScriptLib.logger.trace("Call Action Trigger {}", trigger);
Melledy's avatar
Melledy committed
325
				LuaValue action = (LuaValue) this.getBindings().get(trigger.action);
326
				// TODO impl the param of SetGroupVariableValueByGroup
327
328
329
330
				var arg = new ScriptArgs();
				arg.param2 = 100;
				var args = CoerceJavaToLua.coerce(arg);
				safetyCall(trigger.action, action, args);
331
			}
332
			//TODO some ret may not bool
333
334
		}
	}
335

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
	public LuaValue safetyCall(String name, LuaValue func, LuaValue args){
		try{
			return func.call(this.getScriptLibLua(), args);
		}catch (LuaError error){
			ScriptLib.logger.error("[LUA] call trigger failed {},{}",name,args,error);
			return LuaValue.valueOf(-1);
		}
	}

	public ScriptMonsterTideService getScriptMonsterTideService() {
		return scriptMonsterTideService;
	}

	public ScriptMonsterSpawnService getScriptMonsterSpawnService() {
		return scriptMonsterSpawnService;
	}

Akka's avatar
Akka committed
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
	public EntityGadget createGadgets(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);

		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));

		return entity;
	}

	public void addEntity(GameEntity gameEntity){
		getScene().addEntity(gameEntity);
		callCreateEvent(gameEntity);
	}
	public void meetEntities(List<? extends GameEntity> gameEntity){
		getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_MEET);
		gameEntity.forEach(this::callCreateEvent);
	}
	public void addEntities(List<? extends GameEntity> gameEntity){
		getScene().addEntities(gameEntity);
		gameEntity.forEach(this::callCreateEvent);
	}
	public void callCreateEvent(GameEntity gameEntity){
		if(!isInit){
			return;
		}
		if(gameEntity instanceof EntityMonster entityMonster){
			callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entityMonster.getConfigId()));
		}
		if(gameEntity instanceof EntityGadget entityGadget){
			this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entityGadget.getConfigId()));
		}
	}

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