SceneScriptManager.java 13.2 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
		
		// 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;
	}

93
94
95
96
	public SceneGroup getCurrentGroup() {
		return currentGroup;
	}

97
98
99
100
	public List<SceneBlock> getBlocks() {
		return blocks;
	}

Melledy's avatar
Melledy committed
101
	public Map<String, Integer> getVariables() {
102
103
104
		return variables;
	}

105
106
107
108
109
110
111
112
113
114
115
116
	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
117
118
119
120
121
122
123
124
125
126
127
128
	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);
	}
	
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
	// 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
156

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

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

254
255
256
257
258
259
260
261
262
				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
263
264
265
					}
				}
			}
266
267
268
269
270
271
		} catch (ScriptException e) {
			Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e);
		}
	}

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

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

367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
		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
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

		// 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
410
			//callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs());
411
412
			while(!this.monsterOrders.isEmpty()){
				spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
Melledy's avatar
Melledy committed
413
414
415
			}
		}
	}
416
417
	// Events
	
Melledy's avatar
Melledy committed
418
419
420
	public void callEvent(int eventType, ScriptArgs params) {
		for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) {
			LuaValue condition = null;
421
			
Melledy's avatar
Melledy committed
422
423
			if (trigger.condition != null && !trigger.condition.isEmpty()) {
				condition = (LuaValue) this.getBindings().get(trigger.condition);
424
425
			}
			
Melledy's avatar
Melledy committed
426
427
428
429
430
431
432
433
434
435
			LuaValue ret = LuaValue.TRUE;
			
			if (condition != null) {
				LuaValue args = LuaValue.NIL;
				
				if (params != null) {
					args = CoerceJavaToLua.coerce(params);
				}
				
				ret = condition.call(this.getScriptLibLua(), args);
436
437
438
			}
			
			if (ret.checkboolean() == true) {
Melledy's avatar
Melledy committed
439
				LuaValue action = (LuaValue) this.getBindings().get(trigger.action);
440
441
442
443
				action.call(this.getScriptLibLua(), LuaValue.NIL);
			}
		}
	}
444
445
446
447

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