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

3
4
5
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
Melledy's avatar
Melledy committed
6
import java.util.stream.Collectors;
7
8
9
10
11
12
13
14
15

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

import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;

import emu.grasscutter.Grasscutter;
Melledy's avatar
Melledy committed
16
17
18
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.data.def.WorldLevelData;
19
20
21
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.world.Scene;
Melledy's avatar
Melledy committed
22
import emu.grasscutter.scripts.constants.EventType;
23
24
25
26
27
28
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
29
import emu.grasscutter.scripts.data.SceneRegion;
30
31
import emu.grasscutter.scripts.data.SceneSuite;
import emu.grasscutter.scripts.data.SceneTrigger;
32
import emu.grasscutter.scripts.data.SceneVar;
Melledy's avatar
Melledy committed
33
import emu.grasscutter.scripts.data.ScriptArgs;
34
35
36
37
38
39
40
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
41
	private final Map<String, Integer> variables;
42
	
43
	private Bindings bindings;
44
45
46
47
	private SceneConfig config;
	private List<SceneBlock> blocks;
	private boolean isInit;
	
Melledy's avatar
Melledy committed
48
49
	private final Int2ObjectOpenHashMap<Set<SceneTrigger>> triggers;
	private final Int2ObjectOpenHashMap<SceneRegion> regions;
50
51
52
53
54
55
	private SceneGroup currentGroup;
	private AtomicInteger monsterAlive;
	private AtomicInteger monsterTideCount;
	private int monsterSceneLimit;
	private ConcurrentLinkedQueue<Integer> monsterOrders;

56
57
58
59
60
	public SceneScriptManager(Scene scene) {
		this.scene = scene;
		this.scriptLib = new ScriptLib(this);
		this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib);
		this.triggers = new Int2ObjectOpenHashMap<>();
Melledy's avatar
Melledy committed
61
		this.regions = new Int2ObjectOpenHashMap<>();
62
		this.variables = new HashMap<>();
63
64
65
66
67
68
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
		
		// 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;
	}

	public List<SceneBlock> getBlocks() {
		return blocks;
	}

Melledy's avatar
Melledy committed
97
	public Map<String, Integer> getVariables() {
98
99
100
		return variables;
	}

101
102
103
104
105
106
107
108
109
110
111
112
	public Set<SceneTrigger> getTriggersByEvent(int eventId) {
		return triggers.computeIfAbsent(eventId, e -> new HashSet<>());
	}
	
	public void registerTrigger(SceneTrigger trigger) {
		getTriggersByEvent(trigger.event).add(trigger);
	}
	
	public void deregisterTrigger(SceneTrigger trigger) {
		getTriggersByEvent(trigger.event).remove(trigger);
	}
	
Melledy's avatar
Melledy committed
113
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);
	}
	
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
	// 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
152

153
154
155
156
157
158
159
160
161
162
163
164
		// 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
165
				SceneBlock block = blocks.get(i);
166
167
				block.id = blockIds.get(i);
				
168
				loadBlockFromScript(block);
169
170
171
172
173
			}
			
			this.blocks = blocks;
		} catch (ScriptException e) {
			Grasscutter.getLogger().error("Error running script", e);
174
			return;
175
		}
176
		
177
178
179
180
181
182
183
184
		// TEMP
		this.isInit = true;
	}

	public boolean isInit() {
		return isInit;
	}
	
185
	private void loadBlockFromScript(SceneBlock block) {
186
187
188
189
190
191
192
193
194
195
196
197
198
		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"));
199
			block.groups.forEach(g -> g.block_id = block.id);
200
201
202
203
204
		} catch (ScriptException e) {
			Grasscutter.getLogger().error("Error loading block " + block.id + " in scene " + getScene().getId(), e);
		}
	}
	
205
206
207
208
	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);
		
209
210
211
212
213
214
215
216
217
218
219
220
		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
221
222
			group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream()
					.collect(Collectors.toMap(x -> x.config_id, y -> y));
223
224
225
			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
226
			group.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions"));
227
			group.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config"));
228
			
Melledy's avatar
Melledy committed
229
			// Add variables to suite
230
			List<SceneVar> variables = ScriptLoader.getSerializer().toList(SceneVar.class, bindings.get("variables"));
Melledy's avatar
Melledy committed
231
232
233
			variables.forEach(var -> this.getVariables().put(var.name, var.value));
			
			// Add monsters to suite TODO optimize
234
			Int2ObjectMap<Object> map = new Int2ObjectOpenHashMap<>();
235
			group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m));
236
			group.gadgets.forEach(m -> map.put(m.config_id, m));
Melledy's avatar
Melledy committed
237
238
239
240
			
			for (SceneSuite suite : group.suites) {
				suite.sceneMonsters = new ArrayList<>(suite.monsters.size());
				for (int id : suite.monsters) {
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
					try {
						SceneMonster monster = (SceneMonster) map.get(id);
						if (monster != null) {
							suite.sceneMonsters.add(monster);
						}
					} catch (Exception e) {
						continue;
					}
				}
				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
259
260
261
					}
				}
			}
262
263
264
265
266
267
		} catch (ScriptException e) {
			Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e);
		}
	}

	public void onTick() {
Melledy's avatar
Melledy committed
268
		checkRegions();
269
270
	}
	
Melledy's avatar
Melledy committed
271
272
273
274
275
276
277
278
279
280
	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);
281

Melledy's avatar
Melledy committed
282
283
284
285
286
287
288
			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();
			}
		}
289
290
	}
	
291
292
293
294
	public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) {
		spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex));
	}
	
295
	public void spawnGadgetsInGroup(SceneGroup group) {
296
297
298
299
300
301
302
303
304
305
306
		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
307
308
309
310
			EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos);
			
			if (entity.getGadgetData() == null) continue;
			
311
			entity.setBlockId(group.block_id);
Melledy's avatar
Melledy committed
312
313
314
315
316
317
318
319
320
321
			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()));
		}
	}
	
Melledy's avatar
Melledy committed
322
	public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
323
324
325
326
327
328
329
		this.currentGroup = group;
		this.monsterSceneLimit = 0;
		var suite = group.getSuiteByIndex(suiteIndex);
		if(suite == null){
			return;
		}
		suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob));
Melledy's avatar
Melledy committed
330
331
	}
	
Melledy's avatar
Melledy committed
332
	public void spawnMonstersInGroup(SceneGroup group) {
333
334
335
		this.currentGroup = group;
		this.monsterSceneLimit = 0;
		group.monsters.values().forEach(mob -> spawnMonstersInGroup(group, mob));
Melledy's avatar
Melledy committed
336
	}
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
	public void spawnMonstersInGroup(SceneGroup group,Integer[] ordersConfigId, int tideCount, int sceneLimit) {
		this.currentGroup = group;
		this.monsterSceneLimit = sceneLimit;
		this.monsterTideCount = new AtomicInteger(tideCount);
		this.monsterAlive = new AtomicInteger(0);
		this.monsterOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId));

		// add the last turn
		group.monsters.keySet().stream()
				.filter(i -> !this.monsterOrders.contains(i))
				.forEach(this.monsterOrders::add);
		for (int i = 0; i < sceneLimit; i++) {
			spawnMonstersInGroup(group, group.monsters.get(this.monsterOrders.poll()));
		}
	}
	public void spawnMonstersInGroup(SceneGroup group, SceneMonster monster) {
		if(monster == null){
			return;
		}
		if(this.monsterSceneLimit > 0){
			this.monsterTideCount.decrementAndGet();
			this.monsterAlive.incrementAndGet();
Melledy's avatar
Melledy committed
359
360
		}

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
		MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id);

		if (data == null) {
			return;
		}

		// 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();
Melledy's avatar
Melledy committed
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

		// Spawn mob
		EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level);
		entity.getRotation().set(monster.rot);
		entity.setGroupId(group.id);
		entity.setConfigId(monster.config_id);

		getScene().addEntity(entity);

		callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId()));
	}

	public void onMonsterDie(){
		if(this.monsterSceneLimit <= 0){
			return;
		}
		if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) {
			// maybe not happen
			return;
		}
		if(this.monsterTideCount.get() > 0){
			// add more
			spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
		}else if(this.monsterAlive.get() == 0){
			// spawn the last turn of monsters
			while(!this.monsterOrders.isEmpty()){
				spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
Melledy's avatar
Melledy committed
406
407
408
			}
		}
	}
409
410
	// Events
	
Melledy's avatar
Melledy committed
411
412
413
	public void callEvent(int eventType, ScriptArgs params) {
		for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
			LuaValue condition = null;
414
			
Melledy's avatar
Melledy committed
415
416
			if (trigger.condition != null && !trigger.condition.isEmpty()) {
				condition = (LuaValue) this.getBindings().get(trigger.condition);
417
418
			}
			
Melledy's avatar
Melledy committed
419
420
421
422
423
424
425
426
427
428
			LuaValue ret = LuaValue.TRUE;
			
			if (condition != null) {
				LuaValue args = LuaValue.NIL;
				
				if (params != null) {
					args = CoerceJavaToLua.coerce(params);
				}
				
				ret = condition.call(this.getScriptLibLua(), args);
429
430
431
			}
			
			if (ret.checkboolean() == true) {
Melledy's avatar
Melledy committed
432
				LuaValue action = (LuaValue) this.getBindings().get(trigger.action);
433
434
435
436
				action.call(this.getScriptLibLua(), LuaValue.NIL);
			}
		}
	}
437
438
439
440

//	public LuaValue safetyCall(){
//
//	}
441
}