SceneScriptManager.java 12.9 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
34
35
36
37
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;

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

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

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

69
70
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
		// 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;
	}

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

102
103
104
105
	public List<SceneBlock> getBlocks() {
		return blocks;
	}

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

110
	public Set<SceneTrigger> getTriggersByEvent(int eventId) {
111
		return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>());
112
113
	}
	public void registerTrigger(SceneTrigger trigger) {
114
		this.triggers.put(trigger.name, trigger);
115
116
117
118
		getTriggersByEvent(trigger.event).add(trigger);
	}
	
	public void deregisterTrigger(SceneTrigger trigger) {
119
		this.triggers.remove(trigger.name);
120
121
		getTriggersByEvent(trigger.event).remove(trigger);
	}
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
	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
140
141
142
143
144
145
146
147
148
149
150
151
	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);
	}
	
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
	// 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(
			Grasscutter.getConfig().SCRIPTS_FOLDER + "Scene/" + getScene().getId() + "/scene" + getScene().getId() + "." + ScriptLoader.getScriptType());
		
		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
179

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

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

			// Set
248
249
			group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream()
					.collect(Collectors.toMap(x -> x.config_id, y -> y));
250
251
252
			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
253
			group.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions"));
254
			group.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config"));
255
			
Melledy's avatar
Melledy committed
256
			// Add variables to suite
257
			List<SceneVar> variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables"));
Melledy's avatar
Melledy committed
258
259
260
			variables.forEach(var -> this.getVariables().put(var.name, var.value));
			
			// Add monsters to suite TODO optimize
261
			Int2ObjectMap<Object> map = new Int2ObjectOpenHashMap<>();
262
			group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m));
263
			group.gadgets.forEach(m -> map.put(m.config_id, m));
Melledy's avatar
Melledy committed
264
265
266
			
			for (SceneSuite suite : group.suites) {
				suite.sceneMonsters = new ArrayList<>(suite.monsters.size());
267
268
269
270
271
272
				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);
273
274
						}
					}
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);
						}
					} catch (Exception e) {
						continue;
Melledy's avatar
Melledy committed
286
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
	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
379
	}
380
381
	// Events
	
Melledy's avatar
Melledy committed
382
383
384
	public void callEvent(int eventType, ScriptArgs params) {
		for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
			LuaValue condition = null;
385
			
Melledy's avatar
Melledy committed
386
387
			if (trigger.condition != null && !trigger.condition.isEmpty()) {
				condition = (LuaValue) this.getBindings().get(trigger.condition);
388
389
			}
			
Melledy's avatar
Melledy committed
390
391
392
393
394
395
396
397
398
			LuaValue ret = LuaValue.TRUE;
			
			if (condition != null) {
				LuaValue args = LuaValue.NIL;
				
				if (params != null) {
					args = CoerceJavaToLua.coerce(params);
				}
				
399
				ret = safetyCall(trigger.condition, condition, args);
400
401
			}
			
402
			if (ret.isboolean() && ret.checkboolean()) {
Melledy's avatar
Melledy committed
403
				LuaValue action = (LuaValue) this.getBindings().get(trigger.action);
404
405
406
407
				var arg = new ScriptArgs();
				arg.param2 = 100;
				var args = CoerceJavaToLua.coerce(arg);
				safetyCall(trigger.action, action, args);
408
			}
409
			//TODO some ret may not bool
410
411
		}
	}
412

413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
	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;
	}

430
}