package com.rpsg.rpg.object.game;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.ScriptableObject;
import com.rpsg.rpg.controller.MapScriptController;
import com.rpsg.rpg.core.Game;
import com.rpsg.rpg.core.Log;
import com.rpsg.rpg.object.map.CollideType;
import com.rpsg.rpg.object.map.NPC;
/**
* GDX-RPG 脚本<br>
* 本脚本类仅为地图精灵使用,该类本身为一个线程,且内置了JS脚本,该线程将在地图精灵被碰撞而启动,脚本执行完毕后而结束。<br>
* 所有线程都被{@link MapScriptController}所创建、管理。<br>
* 该类的内容将根据游戏持续增加。
*
*/
public class MapScript extends Thread{
NPC npc;
Scriptable js;
CollideType collideType;
ScriptContext context;
boolean executed = false;
//当前异步执行器,如果是null状态则表示没有什么异步执行器在执行,详见act方法说明
public ScriptExecutor currentExecutor = null;
public MapScript(NPC npc, CollideType collideType, Scriptable js) {
this.npc = npc;
this.js = js;
this.collideType = collideType;
}
/**线程启动*/
public void run() {
setName("============GDX-RPG MapScript[" + npc + " & " + collideType + "]============");
//执行JS脚本,将上下文(ScriptContext)作为该脚本的prototype
try {
if(js.executable()) {
Context ctx = Game.getJSContext();
ScriptableObject scope = ctx.initStandardObjects();
//设置js prototype为ScriptContext
scope.setPrototype(((NativeJavaObject)Context.javaToJS(context = new ScriptContext(this), scope)));
//执行js脚本
ctx.evaluateString(scope, "(function(){\n" + js.get(ctx, scope) + "\n}())", npc + " & " + collideType + " ", 1, null);
Context.exit();
}
} catch (Exception e) {
Log.e("无法执行脚本", e);
e.printStackTrace();
}finally {
//执行完毕了,可以被ScriptController移除了
executed = true;
}
}
/**
* 本方法无论何时,都是从游戏主线程中的{@link com.rpsg.rpg.controller.MapScriptController#act() 调用}的。<br>
* <br>
* 在GDX-RPG地图脚本系统中,脚本首先分为两种类型:<br>
* 第一种同步执行的脚本,他和任何正常的调用相同,即调用一个方法,等待方法执行完毕,然后继续执行JS。<br>
* 第二种即为异步执行的脚本,比如让玩家在地图上向前走三步,显然需要等待走完才继续执行脚本。<br>
* <br>
* 这件事实现起来可以简单可以复杂,简单的话我们可以弄出回调,让脚本传入回调,当玩家走完之后,执行回调。<br>
* 但这样的话,脚本将变得没法看下去(一个笑话,一个黑客黑入一家银行,把他们的服务器nodeJS代码最后一页搞到了,结果黑客发现最后一页代码全都是“}}}}}}”233333)<br>
* 所以我们为了保证脚本本身的简洁性,我们需要在程序层面来做到这件事情。<br>
* <br>
* 当一个需要异步执行的方法被执行时候,我们创建一个{@link ScriptExecutor},他拥有{@link ScriptExecutor#create() create()}和{@link ScriptExecutor#act() act()}两个核心方法。<br>
* 顾名思义,create()将在第一次被创建时候执行一次,接下来就每帧都调用它的act()方法。<br>
* 在这期间,当前脚本线程将自我{@link Thread#sleep(long) 睡眠}以达到暂停脚本运行的效果,直到{@link ScriptExecutor#isExecuted()}返回true则代表执行完毕,然后线程取消睡眠,继续执行接下来的js脚本。
* */
public void act() {
//create 异步脚本
if(currentExecutor != null && currentExecutor.needsCreate()){
currentExecutor.superCreate();
//act 异步脚本
}else if(currentExecutor != null && !currentExecutor.isExecuted()){
currentExecutor.act();
//销毁 异步脚本
}else if(currentExecutor != null && currentExecutor.isExecuted()){
synchronized (this) {
this.notifyAll();
}
}
}
/**
* 该方法无论如何都是在JS线程里调用的,用来执行一个异步方法
*/
public Object set(ScriptExecutor executor) {
//设置当前执行器
currentExecutor = executor;
try {
//冻结线程
synchronized (this) {
wait();
}
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//获取返回对象(如果有)
Object obj = currentExecutor.returnObject();
currentExecutor = null;
return obj;
}
public boolean executed() {
return executed;
}
public boolean equals(NPC npc, CollideType collideType) {
return npc == this.npc && collideType == this.collideType;
}
}