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

3
import java.util.*;
Melledy's avatar
Melledy committed
4
import java.util.stream.Collectors;
5
6
7
8
9

import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;

10
11
12
import emu.grasscutter.scripts.service.ScriptMonsterSpawnService;
import emu.grasscutter.scripts.service.ScriptMonsterTideService;
import org.luaj.vm2.LuaError;
13
14
15
16
17
18
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;
Melledy's avatar
Melledy committed
19
import emu.grasscutter.scripts.constants.EventType;
20
21
22
23
24
25
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;
Melledy's avatar
Melledy committed
26
import emu.grasscutter.scripts.data.SceneRegion;
27
28
import emu.grasscutter.scripts.data.SceneSuite;
import emu.grasscutter.scripts.data.SceneTrigger;
29
import emu.grasscutter.scripts.data.SceneVar;
Melledy's avatar
Melledy committed
30
import emu.grasscutter.scripts.data.ScriptArgs;
31
32
33
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;

34
35
import static emu.grasscutter.Configuration.*;

36
37
38
39
public class SceneScriptManager {
	private final Scene scene;
	private final ScriptLib scriptLib;
	private final LuaValue scriptLibLua;
Melledy's avatar
Melledy committed
40
	private final Map<String, Integer> variables;
41
	private Bindings bindings;
42
43
44
	private SceneConfig config;
	private List<SceneBlock> blocks;
	private boolean isInit;
45
46
47
48
49
50
51
52
	/**
	 * SceneTrigger Set
	 */
	private final Map<String, SceneTrigger> triggers;
	/**
	 * current triggers controlled by RefreshGroup
	 */
	private final Int2ObjectOpenHashMap<Set<SceneTrigger>> currentTriggers;
Melledy's avatar
Melledy committed
53
	private final Int2ObjectOpenHashMap<SceneRegion> regions;
54
	private Map<Integer,SceneGroup> sceneGroups;
55
	private SceneGroup currentGroup;
56
57
	private ScriptMonsterTideService scriptMonsterTideService;
	private ScriptMonsterSpawnService scriptMonsterSpawnService;
58

59
60
61
62
	public SceneScriptManager(Scene scene) {
		this.scene = scene;
		this.scriptLib = new ScriptLib(this);
		this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib);
63
64
65
		this.triggers = new HashMap<>();
		this.currentTriggers = new Int2ObjectOpenHashMap<>();

Melledy's avatar
Melledy committed
66
		this.regions = new Int2ObjectOpenHashMap<>();
67
		this.variables = new HashMap<>();
68
69
70
		this.sceneGroups = new HashMap<>();
		this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this);

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
		// TEMPORARY
		if (this.getScene().getId() < 10) {
			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() {
		return config;
	}

100
101
102
103
	public SceneGroup getCurrentGroup() {
		return currentGroup;
	}

104
105
106
107
	public List<SceneBlock> getBlocks() {
		return blocks;
	}

Melledy's avatar
Melledy committed
108
	public Map<String, Integer> getVariables() {
109
110
111
		return variables;
	}

112
	public Set<SceneTrigger> getTriggersByEvent(int eventId) {
113
		return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>());
114
115
	}
	public void registerTrigger(SceneTrigger trigger) {
116
		this.triggers.put(trigger.name, trigger);
117
118
119
120
		getTriggersByEvent(trigger.event).add(trigger);
	}
	
	public void deregisterTrigger(SceneTrigger trigger) {
121
		this.triggers.remove(trigger.name);
122
123
		getTriggersByEvent(trigger.event).remove(trigger);
	}
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
	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
142
143
144
145
146
147
148
149
150
151
152
153
	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);
	}
	
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
	// TODO optimize
	public SceneGroup getGroupById(int groupId) {
		for (SceneBlock block : this.getScene().getLoadedBlocks()) {
			for (SceneGroup group : block.groups) {
				if (group.id == groupId) {
					return group;
				}
			}
		}
		return null;
	}

	private void init() {
		// Get compiled script if cached
		CompiledScript cs = ScriptLoader.getScriptByPath(
169
			SCRIPT("Scene/" + getScene().getId() + "/scene" + getScene().getId() + "." + ScriptLoader.getScriptType()));
170
171
172
173
174
175
176
177
178
179
180
		
		if (cs == null) {
			Grasscutter.getLogger().warn("No script found for scene " + getScene().getId());
			return;
		}
		
		// Create bindings
		bindings = ScriptLoader.getEngine().createBindings();
		
		// Set variables
		bindings.put("ScriptLib", getScriptLib());
Melledy's avatar
Melledy committed
181

182
183
184
185
186
187
188
189
190
191
192
193
		// 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++) {
wulf's avatar
wulf committed
194
				SceneBlock block = blocks.get(i);
195
196
				block.id = blockIds.get(i);
				
197
				loadBlockFromScript(block);
198
199
200
201
202
			}
			
			this.blocks = blocks;
		} catch (ScriptException e) {
			Grasscutter.getLogger().error("Error running script", e);
203
			return;
204
		}
205
		
206
207
208
209
210
211
212
213
		// TEMP
		this.isInit = true;
	}

	public boolean isInit() {
		return isInit;
	}
	
214
	private void loadBlockFromScript(SceneBlock block) {
215
		CompiledScript cs = ScriptLoader.getScriptByPath(
216
			SCRIPT("Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_block" + block.id + "." + ScriptLoader.getScriptType()));
217
218
219
220
221
222
223
224
225
226
227
	
		if (cs == null) {
			return;
		}
		
		// Eval script
		try {
			cs.eval(getBindings());
			
			// Set groups
			block.groups = ScriptLoader.getSerializer().toList(SceneGroup.class, bindings.get("groups"));
228
			block.groups.forEach(g -> g.block_id = block.id);
229
230
231
232
233
		} catch (ScriptException e) {
			Grasscutter.getLogger().error("Error loading block " + block.id + " in scene " + getScene().getId(), e);
		}
	}
	
234
235
236
237
	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);
		
238
		CompiledScript cs = ScriptLoader.getScriptByPath(
239
			SCRIPTS_FOLDER + "Scene/" + getScene().getId() + "/scene" + getScene().getId() + "_group" + group.id + "." + ScriptLoader.getScriptType());
240
241
242
243
244
245
246
247
248
249
	
		if (cs == null) {
			return;
		}
		
		// Eval script
		try {
			cs.eval(getBindings());

			// Set
250
251
			group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream()
					.collect(Collectors.toMap(x -> x.config_id, y -> y));
252
253
254
			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"));
Melledy's avatar
Melledy committed
255
			group.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions"));
256
			group.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config"));
257
			
Melledy's avatar
Melledy committed
258
			// Add variables to suite
259
			List<SceneVar> variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables"));
Melledy's avatar
Melledy committed
260
261
262
			variables.forEach(var -> this.getVariables().put(var.name, var.value));
			
			// Add monsters to suite TODO optimize
263
			Int2ObjectMap<Object> map = new Int2ObjectOpenHashMap<>();
264
			group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m));
265
			group.gadgets.forEach(m -> map.put(m.config_id, m));
Melledy's avatar
Melledy committed
266
267
268
			
			for (SceneSuite suite : group.suites) {
				suite.sceneMonsters = new ArrayList<>(suite.monsters.size());
269
270
271
272
273
274
				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);
275
276
						}
					}
277
278
				});

279
280
281
282
283
284
285
				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);
						}
286
					} catch (Exception ignored) { }
Melledy's avatar
Melledy committed
287
288
				}
			}
289
			this.sceneGroups.put(group.id, group);
290
291
292
293
294
295
		} catch (ScriptException e) {
			Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e);
		}
	}

	public void onTick() {
Melledy's avatar
Melledy committed
296
		checkRegions();
297
298
	}
	
Melledy's avatar
Melledy committed
299
300
301
302
303
304
305
306
307
308
	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);
309

Melledy's avatar
Melledy committed
310
311
312
313
314
315
316
			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();
			}
		}
317
318
	}
	
319
320
321
322
	public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) {
		spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex));
	}
	
323
	public void spawnGadgetsInGroup(SceneGroup group) {
324
325
326
327
328
329
330
331
332
333
334
		spawnGadgetsInGroup(group, null);
	}
	
	public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) {
		List<SceneGadget> gadgets = group.gadgets;
		
		if (suite != null) {
			gadgets = suite.sceneGadgets;
		}
		
		for (SceneGadget g : gadgets) {
Melledy's avatar
Melledy committed
335
336
337
338
			EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos);
			
			if (entity.getGadgetData() == null) continue;
			
339
			entity.setBlockId(group.block_id);
Melledy's avatar
Melledy committed
340
341
342
343
344
345
346
347
348
			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()));
		}
	}
349

Melledy's avatar
Melledy committed
350
	public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
351
352
353
354
		var suite = group.getSuiteByIndex(suiteIndex);
		if(suite == null){
			return;
		}
355
356
357
358
359
		spawnMonstersInGroup(group, suite);
	}
	public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) {
		if(suite == null || suite.sceneMonsters.size() <= 0){
			return;
360
		}
361
362
		this.currentGroup = group;
		suite.sceneMonsters.forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob));
Melledy's avatar
Melledy committed
363
364
	}
	
Melledy's avatar
Melledy committed
365
	public void spawnMonstersInGroup(SceneGroup group) {
366
		this.currentGroup = group;
367
		group.monsters.values().forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob));
Melledy's avatar
Melledy committed
368
	}
369

370
371
372
373
	public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) {
		this.currentGroup = group;
		this.scriptMonsterTideService =
				new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId);
374
375

	}
376
377
378
379
380
381
	public void unloadCurrentMonsterTide(){
		if(this.getScriptMonsterTideService() == null){
			return;
		}
		this.getScriptMonsterTideService().unload();
	}
382
383
384
	public void spawnMonstersByConfigId(int configId, int delayTime) {
		// TODO delay
		this.scriptMonsterSpawnService.spawnMonster(this.currentGroup.id, this.currentGroup.monsters.get(configId));
Melledy's avatar
Melledy committed
385
	}
386
387
	// Events
	
Melledy's avatar
Melledy committed
388
389
390
	public void callEvent(int eventType, ScriptArgs params) {
		for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
			LuaValue condition = null;
391
			
Melledy's avatar
Melledy committed
392
393
			if (trigger.condition != null && !trigger.condition.isEmpty()) {
				condition = (LuaValue) this.getBindings().get(trigger.condition);
394
395
			}
			
Melledy's avatar
Melledy committed
396
397
398
399
400
401
402
403
			LuaValue ret = LuaValue.TRUE;
			
			if (condition != null) {
				LuaValue args = LuaValue.NIL;
				
				if (params != null) {
					args = CoerceJavaToLua.coerce(params);
				}
404
405

				ScriptLib.logger.trace("Call Condition Trigger {}", trigger);
406
				ret = safetyCall(trigger.condition, condition, args);
407
408
			}
			
409
			if (ret.isboolean() && ret.checkboolean()) {
410
				ScriptLib.logger.trace("Call Action Trigger {}", trigger);
Melledy's avatar
Melledy committed
411
				LuaValue action = (LuaValue) this.getBindings().get(trigger.action);
412
				// TODO impl the param of SetGroupVariableValueByGroup
413
414
415
416
				var arg = new ScriptArgs();
				arg.param2 = 100;
				var args = CoerceJavaToLua.coerce(arg);
				safetyCall(trigger.action, action, args);
417
			}
418
			//TODO some ret may not bool
419
420
		}
	}
421

422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
	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;
	}

439
}