void CCobEngine::Tick(int deltaTime) { SCOPED_TIMER("Scripts"); GCurrentTime += deltaTime; #if COB_DEBUG > 0 logOutput.Print("----"); #endif // Advance all running threads for (std::list<CCobThread *>::iterator i = running.begin(); i != running.end(); ++i) { //logOutput.Print("Now 1running %d: %s", GCurrentTime, (*i)->GetName().c_str()); #ifdef _CONSOLE printf("----\n"); #endif TickThread(deltaTime, *i); } // A thread can never go from running->running, so clear the list // note: if preemption was to be added, this would no longer hold // however, ta scripts can not run preemptively anyway since there // isn't any synchronization methods available running.clear(); // The threads that just ran may have added new threads that should run next tick for (std::list<CCobThread *>::iterator i = wantToRun.begin(); i != wantToRun.end(); ++i) { running.push_front(*i); } wantToRun.clear(); //Check on the sleeping threads if (!sleeping.empty()) { CCobThread *cur = sleeping.top(); while ((cur != NULL) && (cur->GetWakeTime() < GCurrentTime)) { // Start with removing the executing thread from the queue sleeping.pop(); //Run forward again. This can quite possibly readd the thread to the sleeping array again //But it will not interfere since it is guaranteed to sleep > 0 ms //logOutput.Print("Now 2running %d: %s", GCurrentTime, cur->GetName().c_str()); #ifdef _CONSOLE printf("+++\n"); #endif if (cur->state == CCobThread::Sleep) { cur->state = CCobThread::Run; TickThread(deltaTime, cur); } else if (cur->state == CCobThread::Dead) { delete cur; } else { logOutput.Print("CobError: Sleeping thread strange state %d", cur->state); } if (!sleeping.empty()) cur = sleeping.top(); else cur = NULL; } } }
//Returns 0 if the call terminated. If the caller provides a callback and the thread does not terminate, //it will continue to run. Otherwise it will be killed. Returns 1 in this case. int CCobInstance::RealCall(int functionId, vector<int> &args, CBCobThreadFinish cb, void *p1, void *p2) { CCobThread *t = new CCobThread(script, this); t->Start(functionId, args, false); #if COB_DEBUG > 0 if (COB_DEBUG_FILTER) logOutput.Print("Calling %s:%s", script.name.c_str(), script.scriptNames[functionId].c_str()); #endif int res = t->Tick(30); t->CommitAnims(30); //Make sure this is run even if the call terminates instantly if (cb) t->SetCallback(cb, p1, p2); if (res == -1) { //Retrieve parameter values from stack for (unsigned int i = 0; i < args.size(); ++i) { args[i] = t->GetStackVal(i); } delete t; return 0; } else { //It has already added itself to the correct scheduler (global for sleep, or local for anim) return 1; } }
/** * @brief Calls a cob script function * @param functionId int cob script function id * @param args vector<int> function arguments * @param cb CBCobThreadFinish Callback function * @param p1 void* callback argument #1 * @param p2 void* callback argument #2 * @return 0 if the call terminated. If the caller provides a callback and the thread does not terminate, * it will continue to run. Otherwise it will be killed. Returns 1 in this case. */ int CCobInstance::RealCall(int functionId, vector<int> &args, CBCobThreadFinish cb, void *p1, void *p2) { if (functionId < 0 || size_t(functionId) >= script.scriptNames.size()) { if (cb) { // in case the function doesnt exist the callback should still be called (*cb)(0, p1, p2); } return -1; } CCobThread *t = new CCobThread(script, this); t->Start(functionId, args, false); #if COB_DEBUG > 0 if (COB_DEBUG_FILTER) logOutput.Print("Calling %s:%s", script.name.c_str(), script.scriptNames[functionId].c_str()); #endif int res = t->Tick(30); t->CommitAnims(30); // Make sure this is run even if the call terminates instantly if (cb) t->SetCallback(cb, p1, p2); if (res == -1) { unsigned int i = 0, argc = t->CheckStack(args.size()); // Retrieve parameter values from stack for (; i < argc; ++i) args[i] = t->GetStackVal(i); // Set erroneous parameters to 0 for (; i < args.size(); ++i) args[i] = 0; delete t; return 0; } else { // It has already added itself to the correct scheduler (global for sleep, or local for anim) return 1; } }
void CCobEngine::Tick(int deltaTime) { START_TIME_PROFILE GCurrentTime += deltaTime; #if COB_DEBUG > 0 logOutput.Print("----"); #endif //Advance all running threads for (list<CCobThread *>::iterator i = running.begin(); i != running.end(); ++i) { //logOutput.Print("Now 1running %d: %s", GCurrentTime, (*i)->GetName().c_str()); #ifdef _CONSOLE printf("----\n"); #endif int res = (*i)->Tick(deltaTime); (*i)->CommitAnims(deltaTime); if (res == -1) { delete *i; } } //A thread can never go from running->running, so clear the list //note: if preemption was to be added, this would no longer hold //however, ta scripts can not run preemptively anyway since there isn't any synchronization methods available running.clear(); //The threads that just ran may have added new threads that should run next tick for (list<CCobThread *>::iterator i = wantToRun.begin(); i != wantToRun.end(); ++i) { running.push_front(*i); } wantToRun.clear(); //Check on the sleeping threads if (sleeping.size() > 0) { CCobThread *cur = sleeping.top(); while ((cur != NULL) && (cur->GetWakeTime() < GCurrentTime)) { // Start with removing the executing thread from the queue sleeping.pop(); //Run forward again. This can quite possibly readd the thread to the sleeping array again //But it will not interfere since it is guaranteed to sleep > 0 ms //logOutput.Print("Now 2running %d: %s", GCurrentTime, cur->GetName().c_str()); #ifdef _CONSOLE printf("+++\n"); #endif if (cur->state == CCobThread::Sleep) { cur->state = CCobThread::Run; int res = cur->Tick(deltaTime); cur->CommitAnims(deltaTime); if (res == -1) delete cur; } else if (cur->state == CCobThread::Dead) { delete cur; } else { logOutput.Print("CobError: Sleeping thread strange state %d", cur->state); } if (sleeping.size() > 0) cur = sleeping.top(); else cur = NULL; } } //Tick all instances that have registered themselves as animating list<CCobInstance *>::iterator it = animating.begin(); list<CCobInstance *>::iterator curit; while (it != animating.end()) { curit = it++; if ((*curit)->Tick(deltaTime) == -1) animating.erase(curit); } END_TIME_PROFILE("Scripts"); }
//Returns -1 if this thread is dead and needs to be killed int CCobThread::Tick(int deltaTime) { if (state == Sleep) { logOutput.Print("CobError: sleeping thread ticked!"); } if (state == Dead || !owner) { return -1; } state = Run; GCobEngine.SetCurThread(this); int r1, r2, r3, r4, r5, r6; vector<int> args; CCobThread *thread; execTrace.clear(); delayedAnims.clear(); //list<int>::iterator ei; vector<int>::iterator ei; #if COB_DEBUG > 0 if (COB_DEBUG_FILTER) logOutput.Print("Executing in %s (from %s)", script.scriptNames[callStack.back().functionId].c_str(), GetName().c_str()); #endif while (state == Run) { //int opcode = *(int *)&script.code[PC]; //Disabling exec trace gives about a 50% speedup on vm-intensive code //execTrace.push_back(PC); int opcode = GET_LONG_PC(); #if COB_DEBUG > 1 if (COB_DEBUG_FILTER) logOutput.Print("PC: %x opcode: %x (%s)", PC - 1, opcode, GetOpcodeName(opcode).c_str()); #endif switch(opcode) { case PUSH_CONSTANT: r1 = GET_LONG_PC(); stack.push_back(r1); break; case SLEEP: r1 = POP(); wakeTime = GCurrentTime + r1; state = Sleep; GCobEngine.AddThread(this); GCobEngine.SetCurThread(NULL); #if COB_DEBUG > 0 if (COB_DEBUG_FILTER) logOutput.Print("%s sleeping for %d ms", script.scriptNames[callStack.back().functionId].c_str(), r1); #endif return 0; case SPIN: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r3 = POP(); //speed r4 = POP(); //accel owner->Spin(r1, r2, r3, r4); break; case STOP_SPIN: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r3 = POP(); //decel //logOutput.Print("Stop spin of %s around %d", script.pieceNames[r1].c_str(), r2); owner->StopSpin(r1, r2, r3); break; case RETURN: retCode = POP(); if (callStack.back().returnAddr == -1) { #if COB_DEBUG > 0 if (COB_DEBUG_FILTER) logOutput.Print("%s returned %d", script.scriptNames[callStack.back().functionId].c_str(), retCode); #endif state = Dead; GCobEngine.SetCurThread(NULL); //callStack.pop_back(); //Leave values intact on stack in case caller wants to check them return -1; } PC = callStack.back().returnAddr; while (stack.size() > callStack.back().stackTop) { stack.pop_back(); } callStack.pop_back(); #if COB_DEBUG > 0 if (COB_DEBUG_FILTER) logOutput.Print("Returning to %s", script.scriptNames[callStack.back().functionId].c_str()); #endif break; case SHADE: r1 = GET_LONG_PC(); break; case DONT_SHADE: r1 = GET_LONG_PC(); break; case CACHE: r1 = GET_LONG_PC(); break; case DONT_CACHE: r1 = GET_LONG_PC(); break; case CALL: { r1 = GET_LONG_PC(); PC--; const string& name = script.scriptNames[r1]; if (name.find("lua_") == 0) { script.code[PC - 1] = LUA_CALL; LuaCall(); break; } script.code[PC - 1] = REAL_CALL; // fall through // } case REAL_CALL: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); if (script.scriptLengths[r1] == 0) { //logOutput.Print("Preventing call to zero-len script %s", script.scriptNames[r1].c_str()); break; } struct callInfo ci; ci.functionId = r1; ci.returnAddr = PC; ci.stackTop = stack.size() - r2; callStack.push_back(ci); paramCount = r2; PC = script.scriptOffsets[r1]; #if COB_DEBUG > 0 if (COB_DEBUG_FILTER) logOutput.Print("Calling %s", script.scriptNames[r1].c_str()); #endif break; case LUA_CALL: LuaCall(); break; case POP_STATIC: r1 = GET_LONG_PC(); r2 = POP(); owner->staticVars[r1] = r2; //logOutput.Print("Pop static var %d val %d", r1, r2); break; case POP_STACK: POP(); break; case START: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); if (script.scriptLengths[r1] == 0) { //logOutput.Print("Preventing start of zero-len script %s", script.scriptNames[r1].c_str()); break; } args.clear(); for (r3 = 0; r3 < r2; ++r3) { r4 = POP(); args.push_back(r4); } thread = SAFE_NEW CCobThread(script, owner); thread->Start(r1, args, true); //Seems that threads should inherit signal mask from creator thread->signalMask = signalMask; #if COB_DEBUG > 0 if (COB_DEBUG_FILTER) logOutput.Print("Starting %s %d", script.scriptNames[r1].c_str(), signalMask); #endif break; case CREATE_LOCAL_VAR: if (paramCount == 0) { stack.push_back(0); } else { paramCount--; } break; case GET_UNIT_VALUE: r1 = POP(); if ((r1 >= LUA0) && (r1 <= LUA9)) { stack.push_back(luaArgs[r1 - LUA0]); break; } ForceCommitAllAnims(); // getunitval could possibly read piece locations r1 = owner->GetUnitVal(r1, 0, 0, 0, 0); stack.push_back(r1); break; case JUMP_NOT_EQUAL: r1 = GET_LONG_PC(); r2 = POP(); if (r2 == 0) { PC = r1; } break; case JUMP: r1 = GET_LONG_PC(); //this seem to be an error in the docs.. //r2 = script.scriptOffsets[callStack.back().functionId] + r1; PC = r1; break; case POP_LOCAL_VAR: r1 = GET_LONG_PC(); r2 = POP(); stack[callStack.back().stackTop + r1] = r2; break; case PUSH_LOCAL_VAR: r1 = GET_LONG_PC(); r2 = stack[callStack.back().stackTop + r1]; stack.push_back(r2); break; case SET_LESS_OR_EQUAL: r2 = POP(); r1 = POP(); if (r1 <= r2) stack.push_back(1); else stack.push_back(0); break; case BITWISE_AND: r1 = POP(); r2 = POP(); stack.push_back(r1 & r2); break; case BITWISE_OR: //seems to want stack contents or'd, result places on stack r1 = POP(); r2 = POP(); stack.push_back(r1 | r2); break; case BITWISE_XOR: r1 = POP(); r2 = POP(); stack.push_back(r1 ^ r2); break; case BITWISE_NOT: r1 = POP(); stack.push_back(~r1); break; case EXPLODE: r1 = GET_LONG_PC(); r2 = POP(); owner->Explode(r1, r2); break; case PLAY_SOUND: r1 = GET_LONG_PC(); r2 = POP(); owner->PlayUnitSound(r1, r2); break; case PUSH_STATIC: r1 = GET_LONG_PC(); stack.push_back(owner->staticVars[r1]); //logOutput.Print("Push static %d val %d", r1, owner->staticVars[r1]); break; case SET_NOT_EQUAL: r1 = POP(); r2 = POP(); if (r1 != r2) stack.push_back(1); else stack.push_back(0); break; case SET_EQUAL: r1 = POP(); r2 = POP(); if (r1 == r2) stack.push_back(1); else stack.push_back(0); break; case SET_LESS: r2 = POP(); r1 = POP(); if (r1 < r2) stack.push_back(1); else stack.push_back(0); break; case SET_GREATER: r2 = POP(); r1 = POP(); if (r1 > r2) stack.push_back(1); else stack.push_back(0); break; case SET_GREATER_OR_EQUAL: r2 = POP(); r1 = POP(); if (r1 >= r2) stack.push_back(1); else stack.push_back(0); break; case RAND: r2 = POP(); r1 = POP(); r3 = gs->randInt() % (r2 - r1 + 1) + r1; stack.push_back(r3); break; case EMIT_SFX: r1 = POP(); r2 = GET_LONG_PC(); owner->EmitSfx(r1, r2); break; case MUL: r1 = POP(); r2 = POP(); stack.push_back(r1 * r2); break; case SIGNAL: r1 = POP(); owner->Signal(r1); break; case SET_SIGNAL_MASK: r1 = POP(); signalMask = r1; break; case TURN: r2 = POP(); r1 = POP(); r3 = GET_LONG_PC(); r4 = GET_LONG_PC(); //logOutput.Print("Turning piece %s axis %d to %d speed %d", script.pieceNames[r3].c_str(), r4, r2, r1); ForceCommitAnim(1, r3, r4); owner->Turn(r3, r4, r1, r2); break; case GET: r5 = POP(); r4 = POP(); r3 = POP(); r2 = POP(); r1 = POP(); if ((r1 >= LUA0) && (r1 <= LUA9)) { stack.push_back(luaArgs[r1 - LUA0]); break; } ForceCommitAllAnims(); r6 = owner->GetUnitVal(r1, r2, r3, r4, r5); stack.push_back(r6); break; case ADD: r2 = POP(); r1 = POP(); stack.push_back(r1 + r2); break; case SUB: r2 = POP(); r1 = POP(); r3 = r1 - r2; stack.push_back(r3); break; case DIV: r2 = POP(); r1 = POP(); if (r2 != 0) r3 = r1 / r2; else { r3 = 1000; //infinity! logOutput.Print("CobError: division by zero"); } stack.push_back(r3); break; case MOD: r2 = POP(); r1 = POP(); if (r2 != 0) stack.push_back(r1 % r2); else { stack.push_back(0); logOutput.Print("CobError: modulo division by zero"); } break; case MOVE: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r4 = POP(); r3 = POP(); ForceCommitAnim(2, r1, r2); owner->Move(r1, r2, r3, r4); break; case MOVE_NOW:{ r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r3 = POP(); if (owner->smoothAnim) { DelayedAnim a; a.type = 2; a.piece = r1; a.axis = r2; a.dest = r3; delayedAnims.push_back(a); //logOutput.Print("Delayed move %s %d %d", owner->pieces[r1].name.c_str(), r2, r3); } else { owner->MoveNow(r1, r2, r3); } break;} case TURN_NOW:{ r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r3 = POP(); if (owner->smoothAnim) { DelayedAnim a; a.type = 1; a.piece = r1; a.axis = r2; a.dest = r3; delayedAnims.push_back(a); } else { owner->TurnNow(r1, r2, r3); } break;} case WAIT_TURN: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); //logOutput.Print("Waiting for turn on piece %s around axis %d", script.pieceNames[r1].c_str(), r2); if (owner->AddTurnListener(r1, r2, this)) { state = WaitTurn; GCobEngine.SetCurThread(NULL); return 0; } else break; case WAIT_MOVE: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); //logOutput.Print("Waiting for move on piece %s on axis %d", script.pieceNames[r1].c_str(), r2); if (owner->AddMoveListener(r1, r2, this)) { state = WaitMove; GCobEngine.SetCurThread(NULL); return 0; } break; case SET: r2 = POP(); r1 = POP(); //logOutput.Print("Setting unit value %d to %d", r1, r2); if ((r1 >= LUA0) && (r1 <= LUA9)) { luaArgs[r1 - LUA0] = r2; break; } owner->SetUnitVal(r1, r2); break; case ATTACH: r3 = POP(); r2 = POP(); r1 = POP(); owner->AttachUnit(r2, r1); break; case DROP: r1 = POP(); owner->DropUnit(r1); break; case LOGICAL_NOT: //Like bitwise, but only on values 1 and 0. r1 = POP(); if (r1 == 0) stack.push_back(1); else stack.push_back(0); break; case LOGICAL_AND: r1 = POP(); r2 = POP(); if (r1 && r2) stack.push_back(1); else stack.push_back(0); break; case LOGICAL_OR: r1 = POP(); r2 = POP(); if (r1 || r2) stack.push_back(1); else stack.push_back(0); break; case LOGICAL_XOR: r1 = POP(); r2 = POP(); if (!!r1 ^ !!r2) stack.push_back(1); else stack.push_back(0); break; case HIDE: r1 = GET_LONG_PC(); owner->SetVisibility(r1, false); //logOutput.Print("Hiding %d", r1); break; case SHOW:{ r1 = GET_LONG_PC(); int i; for (i = 0; i < COB_MaxWeapons; ++i) if (callStack.back().functionId == script.scriptIndex[COBFN_FirePrimary + i]) break; // If true, we are in a Fire-script and should show a special flare effect if (i < COB_MaxWeapons) { owner->ShowFlare(r1); } else { owner->SetVisibility(r1, true); } //logOutput.Print("Showing %d", r1); break;} default: logOutput.Print("CobError: Unknown opcode %x (in %s:%s at %x)", opcode, script.name.c_str(), script.scriptNames[callStack.back().functionId].c_str(), PC - 1); logOutput.Print("Exec trace:"); ei = execTrace.begin(); while (ei != execTrace.end()) { logOutput.Print("PC: %3x opcode: %s", *ei, GetOpcodeName(script.code[*ei]).c_str()); ei++; } state = Dead; GCobEngine.SetCurThread(NULL); return -1; break; } } GCobEngine.SetCurThread(NULL); return 0; }
bool CCobThread::Tick() { if (state == Sleep) { LOG_L(L_ERROR, "sleeping thread ticked!"); } if (state == Dead || owner == nullptr) { return false; } state = Run; int r1, r2, r3, r4, r5, r6; vector<int> args; vector<int>::iterator ei; //execTrace.clear(); //LOG_L(L_DEBUG, "Executing in %s (from %s)", script.scriptNames[callStack.back().functionId].c_str(), GetName().c_str()); while (state == Run) { //int opcode = *(int*)&script.code[PC]; // Disabling exec trace gives about a 50% speedup on vm-intensive code //execTrace.push_back(PC); int opcode = GET_LONG_PC(); //LOG_L(L_DEBUG, "PC: %x opcode: %x (%s)", PC - 1, opcode, GetOpcodeName(opcode).c_str()); switch(opcode) { case PUSH_CONSTANT: r1 = GET_LONG_PC(); stack.push_back(r1); break; case SLEEP: r1 = POP(); wakeTime = cobEngine->GetCurrentTime() + r1; state = Sleep; cobEngine->AddThread(this); //LOG_L(L_DEBUG, "%s sleeping for %d ms", script.scriptNames[callStack.back().functionId].c_str(), r1); return true; case SPIN: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r3 = POP(); // speed r4 = POP(); // accel owner->Spin(r1, r2, r3, r4); break; case STOP_SPIN: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r3 = POP(); // decel //LOG_L(L_DEBUG, "Stop spin of %s around %d", script.pieceNames[r1].c_str(), r2); owner->StopSpin(r1, r2, r3); break; case RETURN: retCode = POP(); if (callStack.back().returnAddr == -1) { //LOG_L(L_DEBUG, "%s returned %d", script.scriptNames[callStack.back().functionId].c_str(), retCode); state = Dead; //callStack.pop_back(); // Leave values intact on stack in case caller wants to check them return false; } PC = callStack.back().returnAddr; while (stack.size() > callStack.back().stackTop) { stack.pop_back(); } callStack.pop_back(); //LOG_L(L_DEBUG, "Returning to %s", owner->script->scriptNames[callStack.back().functionId].c_str()); break; case SHADE: r1 = GET_LONG_PC(); break; case DONT_SHADE: r1 = GET_LONG_PC(); break; case CACHE: r1 = GET_LONG_PC(); break; case DONT_CACHE: r1 = GET_LONG_PC(); break; case CALL: { r1 = GET_LONG_PC(); PC--; const string& name = owner->script->scriptNames[r1]; if (name.find("lua_") == 0) { owner->script->code[PC - 1] = LUA_CALL; LuaCall(); break; } owner->script->code[PC - 1] = REAL_CALL; // fall through // } case REAL_CALL: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); if (owner->script->scriptLengths[r1] == 0) { //LOG_L(L_DEBUG, "Preventing call to zero-len script %s", owner->script->scriptNames[r1].c_str()); break; } CallInfo ci; ci.functionId = r1; ci.returnAddr = PC; ci.stackTop = stack.size() - r2; callStack.push_back(ci); paramCount = r2; PC = owner->script->scriptOffsets[r1]; //LOG_L(L_DEBUG, "Calling %s", owner->script->scriptNames[r1].c_str()); break; case LUA_CALL: LuaCall(); break; case POP_STATIC: r1 = GET_LONG_PC(); r2 = POP(); owner->staticVars[r1] = r2; //LOG_L(L_DEBUG, "Pop static var %d val %d", r1, r2); break; case POP_STACK: POP(); break; case START: { r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); if (owner->script->scriptLengths[r1] == 0) { //LOG_L(L_DEBUG, "Preventing start of zero-len script %s", owner->script->scriptNames[r1].c_str()); break; } args.clear(); args.reserve(r2); for (r3 = 0; r3 < r2; ++r3) { r4 = POP(); args.push_back(r4); } CCobThread* thread = new CCobThread(owner); thread->Start(r1, args, true); // Seems that threads should inherit signal mask from creator thread->signalMask = signalMask; //LOG_L(L_DEBUG, "Starting %s %d", owner->script->scriptNames[r1].c_str(), signalMask); } break; case CREATE_LOCAL_VAR: if (paramCount == 0) { stack.push_back(0); } else { paramCount--; } break; case GET_UNIT_VALUE: r1 = POP(); if ((r1 >= LUA0) && (r1 <= LUA9)) { stack.push_back(luaArgs[r1 - LUA0]); break; } r1 = owner->GetUnitVal(r1, 0, 0, 0, 0); stack.push_back(r1); break; case JUMP_NOT_EQUAL: r1 = GET_LONG_PC(); r2 = POP(); if (r2 == 0) { PC = r1; } break; case JUMP: r1 = GET_LONG_PC(); // this seem to be an error in the docs.. //r2 = owner->script->scriptOffsets[callStack.back().functionId] + r1; PC = r1; break; case POP_LOCAL_VAR: r1 = GET_LONG_PC(); r2 = POP(); stack[callStack.back().stackTop + r1] = r2; break; case PUSH_LOCAL_VAR: r1 = GET_LONG_PC(); r2 = stack[callStack.back().stackTop + r1]; stack.push_back(r2); break; case SET_LESS_OR_EQUAL: r2 = POP(); r1 = POP(); if (r1 <= r2) stack.push_back(1); else stack.push_back(0); break; case BITWISE_AND: r1 = POP(); r2 = POP(); stack.push_back(r1 & r2); break; case BITWISE_OR: // seems to want stack contents or'd, result places on stack r1 = POP(); r2 = POP(); stack.push_back(r1 | r2); break; case BITWISE_XOR: r1 = POP(); r2 = POP(); stack.push_back(r1 ^ r2); break; case BITWISE_NOT: r1 = POP(); stack.push_back(~r1); break; case EXPLODE: r1 = GET_LONG_PC(); r2 = POP(); owner->Explode(r1, r2); break; case PLAY_SOUND: r1 = GET_LONG_PC(); r2 = POP(); owner->PlayUnitSound(r1, r2); break; case PUSH_STATIC: r1 = GET_LONG_PC(); stack.push_back(owner->staticVars[r1]); //LOG_L(L_DEBUG, "Push static %d val %d", r1, owner->staticVars[r1]); break; case SET_NOT_EQUAL: r1 = POP(); r2 = POP(); if (r1 != r2) stack.push_back(1); else stack.push_back(0); break; case SET_EQUAL: r1 = POP(); r2 = POP(); if (r1 == r2) stack.push_back(1); else stack.push_back(0); break; case SET_LESS: r2 = POP(); r1 = POP(); if (r1 < r2) stack.push_back(1); else stack.push_back(0); break; case SET_GREATER: r2 = POP(); r1 = POP(); if (r1 > r2) stack.push_back(1); else stack.push_back(0); break; case SET_GREATER_OR_EQUAL: r2 = POP(); r1 = POP(); if (r1 >= r2) stack.push_back(1); else stack.push_back(0); break; case RAND: r2 = POP(); r1 = POP(); r3 = gs->randInt() % (r2 - r1 + 1) + r1; stack.push_back(r3); break; case EMIT_SFX: r1 = POP(); r2 = GET_LONG_PC(); owner->EmitSfx(r1, r2); break; case MUL: r1 = POP(); r2 = POP(); stack.push_back(r1 * r2); break; case SIGNAL: r1 = POP(); owner->Signal(r1); break; case SET_SIGNAL_MASK: r1 = POP(); signalMask = r1; break; case TURN: r2 = POP(); r1 = POP(); r3 = GET_LONG_PC(); r4 = GET_LONG_PC(); //LOG_L(L_DEBUG, "Turning piece %s axis %d to %d speed %d", owner->script->pieceNames[r3].c_str(), r4, r2, r1); owner->Turn(r3, r4, r1, r2); break; case GET: r5 = POP(); r4 = POP(); r3 = POP(); r2 = POP(); r1 = POP(); if ((r1 >= LUA0) && (r1 <= LUA9)) { stack.push_back(luaArgs[r1 - LUA0]); break; } r6 = owner->GetUnitVal(r1, r2, r3, r4, r5); stack.push_back(r6); break; case ADD: r2 = POP(); r1 = POP(); stack.push_back(r1 + r2); break; case SUB: r2 = POP(); r1 = POP(); r3 = r1 - r2; stack.push_back(r3); break; case DIV: r2 = POP(); r1 = POP(); if (r2 != 0) r3 = r1 / r2; else { r3 = 1000; // infinity! ShowError("division by zero"); } stack.push_back(r3); break; case MOD: r2 = POP(); r1 = POP(); if (r2 != 0) stack.push_back(r1 % r2); else { stack.push_back(0); ShowError("modulo division by zero"); } break; case MOVE: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r4 = POP(); r3 = POP(); owner->Move(r1, r2, r3, r4); break; case MOVE_NOW:{ r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r3 = POP(); owner->MoveNow(r1, r2, r3); break;} case TURN_NOW:{ r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); r3 = POP(); owner->TurnNow(r1, r2, r3); break;} case WAIT_TURN: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); //LOG_L(L_DEBUG, "Waiting for turn on piece %s around axis %d", owner->script->pieceNames[r1].c_str(), r2); if (owner->NeedsWait(CCobInstance::ATurn, r1, r2)) { state = WaitTurn; waitPiece = r1; waitAxis = r2; return true; } else break; case WAIT_MOVE: r1 = GET_LONG_PC(); r2 = GET_LONG_PC(); //LOG_L(L_DEBUG, "Waiting for move on piece %s on axis %d", owner->script->pieceNames[r1].c_str(), r2); if (owner->NeedsWait(CCobInstance::AMove, r1, r2)) { state = WaitMove; waitPiece = r1; waitAxis = r2; return true; } break; case SET: r2 = POP(); r1 = POP(); //LOG_L(L_DEBUG, "Setting unit value %d to %d", r1, r2); if ((r1 >= LUA0) && (r1 <= LUA9)) { luaArgs[r1 - LUA0] = r2; break; } owner->SetUnitVal(r1, r2); break; case ATTACH: r3 = POP(); r2 = POP(); r1 = POP(); owner->AttachUnit(r2, r1); break; case DROP: r1 = POP(); owner->DropUnit(r1); break; case LOGICAL_NOT: // Like bitwise, but only on values 1 and 0. r1 = POP(); if (r1 == 0) stack.push_back(1); else stack.push_back(0); break; case LOGICAL_AND: r1 = POP(); r2 = POP(); if (r1 && r2) stack.push_back(1); else stack.push_back(0); break; case LOGICAL_OR: r1 = POP(); r2 = POP(); if (r1 || r2) stack.push_back(1); else stack.push_back(0); break; case LOGICAL_XOR: r1 = POP(); r2 = POP(); if ( (!!r1) ^ (!!r2)) stack.push_back(1); else stack.push_back(0); break; case HIDE: r1 = GET_LONG_PC(); owner->SetVisibility(r1, false); //LOG_L(L_DEBUG, "Hiding %d", r1); break; case SHOW:{ r1 = GET_LONG_PC(); int i; for (i = 0; i < MAX_WEAPONS_PER_UNIT; ++i) if (callStack.back().functionId == owner->script->scriptIndex[COBFN_FirePrimary + COBFN_Weapon_Funcs * i]) break; // If true, we are in a Fire-script and should show a special flare effect if (i < MAX_WEAPONS_PER_UNIT) { owner->ShowFlare(r1); } else { owner->SetVisibility(r1, true); } //LOG_L(L_DEBUG, "Showing %d", r1); break;} default: LOG_L(L_ERROR, "Unknown opcode %x (in %s:%s at %x)", opcode, owner->script->name.c_str(), owner->script->scriptNames[callStack.back().functionId].c_str(), PC - 1); LOG_L(L_ERROR, "Exec trace:"); // ei = execTrace.begin(); // while (ei != execTrace.end()) { // LOG_L(L_ERROR, "PC: %3x opcode: %s", *ei, GetOpcodeName(owner->script->code[*ei]).c_str()); // ++ei; // } state = Dead; return false; } } return (state != Dead); // can arrive here as dead, through CCobInstance::Signal() }