mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
499 lines
13 KiB
C++
499 lines
13 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "twp/twp.h"
|
|
#include "twp/vm.h"
|
|
#include "common/array.h"
|
|
#include "common/algorithm.h"
|
|
#include "common/debug.h"
|
|
#include "common/random.h"
|
|
#include "image/png.h"
|
|
#include "squirrel/squirrel.h"
|
|
#include "squirrel/sqvm.h"
|
|
#include "squirrel/sqobject.h"
|
|
#include "squirrel/sqstring.h"
|
|
#include "squirrel/sqstate.h"
|
|
#include "squirrel/sqtable.h"
|
|
#include "squirrel/sqstdstring.h"
|
|
#include "squirrel/sqstdmath.h"
|
|
#include "squirrel/sqstdio.h"
|
|
#include "squirrel/sqstdaux.h"
|
|
#include "squirrel/sqfuncproto.h"
|
|
#include "squirrel/sqclosure.h"
|
|
|
|
namespace Twp {
|
|
|
|
static Scene *gScene = nullptr;
|
|
static HSQUIRRELVM gVm = nullptr;
|
|
|
|
static SQObjectPtr sqObj(HSQUIRRELVM v, const char *value) {
|
|
SQObjectPtr string = SQString::Create(_ss(v), value);
|
|
return string;
|
|
}
|
|
|
|
static inline SQObject sqObj(HSQUIRRELVM v, int value) {
|
|
SQObject o;
|
|
o._type = OT_INTEGER;
|
|
o._unVal.nInteger = value;
|
|
return o;
|
|
}
|
|
|
|
static SQRESULT get(HSQUIRRELVM v, int i, Common::String &value) {
|
|
const SQChar *val;
|
|
SQRESULT result = sq_getstring(v, i, &val);
|
|
value = val;
|
|
return result;
|
|
}
|
|
|
|
static void getArray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr) {
|
|
sq_pushobject(v, o);
|
|
sq_pushnull(v);
|
|
while (SQ_SUCCEEDED(sq_next(v, -2))) {
|
|
const SQChar *str;
|
|
sq_getstring(v, -1, &str);
|
|
arr.push_back(str);
|
|
sq_pop(v, 2);
|
|
}
|
|
sq_pop(v, 1);
|
|
}
|
|
|
|
static SQRESULT getArray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr) {
|
|
HSQOBJECT obj;
|
|
SQRESULT result = sq_getstackobj(v, i, &obj);
|
|
getArray(v, obj, arr);
|
|
return result;
|
|
}
|
|
|
|
static Thread *thread(HSQUIRRELVM v) {
|
|
return *Common::find_if(gScene->threads.begin(), gScene->threads.end(), [&](Thread *t) {
|
|
return t->threadObj._unVal.pThread == v;
|
|
});
|
|
}
|
|
|
|
template<typename F>
|
|
static SQInteger breakfunc(HSQUIRRELVM v, const F &func) {
|
|
Thread *t = thread(v);
|
|
if (!t)
|
|
return sq_throwerror(v, "failed to get thread");
|
|
t->suspend();
|
|
func(*t);
|
|
return -666;
|
|
}
|
|
|
|
// When called in a function started with startthread, execution is suspended for time seconds.
|
|
// It is an error to call breaktime in a function that was not started with startthread.
|
|
//
|
|
// . code-block:: Squirrel
|
|
// for (local x = 1; x < 4; x += 1) {
|
|
// playSound(soundPhoneRinging)
|
|
// breaktime(5.0)
|
|
// }
|
|
static SQInteger breaktime(HSQUIRRELVM v) {
|
|
SQFloat time;
|
|
if (SQ_FAILED(sq_getfloat(v, 2, &time)))
|
|
return sq_throwerror(v, "failed to get time");
|
|
if (time == 0.f)
|
|
return breakfunc(v, [](Thread &t) { t.numFrames = 1; });
|
|
else
|
|
return breakfunc(v, [&](Thread &t) { t.waitTime = time; });
|
|
}
|
|
|
|
// Returns a random number from from to to inclusively.
|
|
// The number is a pseudo-random number and the game will produce the same sequence of numbers unless primed using randomSeed(seed).
|
|
//
|
|
// .. code-block:: Squirrel
|
|
// wait_time = random(0.5, 2.0)
|
|
static SQInteger sq_random(HSQUIRRELVM v) {
|
|
if (sq_gettype(v, 2) == OT_FLOAT || sq_gettype(v, 3) == OT_FLOAT) {
|
|
SQFloat min, max;
|
|
sq_getfloat(v, 2, &min);
|
|
sq_getfloat(v, 3, &max);
|
|
if (min > max)
|
|
SWAP(min, max);
|
|
float scale = g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
|
|
SQFloat value = min + scale * (max - min);
|
|
sq_pushfloat(v, value);
|
|
} else {
|
|
SQInteger min, max;
|
|
sq_getinteger(v, 2, &min);
|
|
sq_getinteger(v, 3, &max);
|
|
if (min > max)
|
|
SWAP(min, max);
|
|
SQInteger value = g_engine->getRandomSource().getRandomNumberRngSigned(min, max);
|
|
sq_pushinteger(v, value);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// Creates a new, room local object using sheet as the sprite sheet and image as the image name.
|
|
// This object is deleted when the room exits.
|
|
// If sheet parameter not provided, use room's sprite sheet instead.
|
|
// If image is an array, then use that as a sequence of frames for animation.
|
|
// Objects created at runtime can be passed to all the object commands.
|
|
// They do not have verbs or local variables by default, but these can be added when the object is created so it can be used in the construction of sentences.
|
|
static SQInteger createObject(HSQUIRRELVM v) {
|
|
SQInteger numArgs = sq_gettop(v);
|
|
Common::String sheet;
|
|
Common::Array<Common::String> frames;
|
|
SQInteger framesIndex = 2;
|
|
|
|
// get sheet parameter if any
|
|
if (numArgs == 3) {
|
|
if (SQ_FAILED(get(v, 2, sheet)))
|
|
return sq_throwerror(v, "failed to get sheet");
|
|
framesIndex = 3;
|
|
}
|
|
|
|
// get frames parameter if any
|
|
if (numArgs >= 2) {
|
|
switch (sq_gettype(v, framesIndex)) {
|
|
case OT_STRING: {
|
|
Common::String frame;
|
|
get(v, framesIndex, frame);
|
|
frames.push_back(frame);
|
|
} break;
|
|
case OT_ARRAY:
|
|
getArray(v, framesIndex, frames);
|
|
break;
|
|
default:
|
|
return sq_throwerror(v, "Invalid parameter 2: expecting a string or an array");
|
|
}
|
|
}
|
|
|
|
debug("Create object: %s, %u", sheet.c_str(), frames.size());
|
|
|
|
// TODO: share resources load png to surface
|
|
GGPackEntryReader r;
|
|
r.open(gScene->pack, sheet + ".png");
|
|
Image::PNGDecoder d;
|
|
d.loadStream(r);
|
|
const Graphics::Surface *surface = d.getSurface();
|
|
|
|
// load sheet json
|
|
GGPackEntryReader r2;
|
|
r2.open(gScene->pack, sheet + ".json");
|
|
Common::Array<char> data(r2.size());
|
|
r2.read(&data[0], r2.size());
|
|
Common::String s(&data[0], r2.size());
|
|
|
|
debug(s.c_str());
|
|
|
|
// TODO: change to load each frame
|
|
Common::JSONValue *json = Common::JSON::parse(s.c_str());
|
|
const Common::JSONObject &jRect = json->asObject()["frames"]->asObject()[frames[0]]->asObject()["frame"]->asObject();
|
|
|
|
int x = (int)jRect["x"]->asIntegerNumber();
|
|
int y = (int)jRect["y"]->asIntegerNumber();
|
|
int w = (int)jRect["w"]->asIntegerNumber();
|
|
int h = (int)jRect["h"]->asIntegerNumber();
|
|
|
|
delete json;
|
|
|
|
// TODO: create an entity
|
|
Entity e;
|
|
e.surface.copyFrom(*surface);
|
|
e.rect = Common::Rect(x, y, x + w, y + h);
|
|
|
|
static int gId = 3000;
|
|
sq_newtable(v);
|
|
sq_pushstring(v, _SC("_id"), -1);
|
|
sq_pushinteger(v, gId++);
|
|
sq_newslot(v, -3, SQFalse);
|
|
sq_getstackobj(v, -1, &e.obj);
|
|
sq_addref(gVm, &e.obj);
|
|
sq_pop(v, 1);
|
|
|
|
gScene->entities.push_back(e);
|
|
|
|
sq_pushobject(v, e.obj);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static SQInteger _startthread(HSQUIRRELVM v, bool global) {
|
|
SQInteger size = sq_gettop(v);
|
|
|
|
Thread *t = new Thread();
|
|
t->global = global;
|
|
|
|
static uint64 gThreadId = 300000;
|
|
sq_newtable(v);
|
|
sq_pushstring(v, _SC("_id"), -1);
|
|
sq_pushinteger(v, gThreadId++);
|
|
sq_newslot(v, -3, SQFalse);
|
|
sq_getstackobj(v, -1, &t->obj);
|
|
sq_addref(gVm, &t->obj);
|
|
sq_pop(v, 1);
|
|
|
|
sq_resetobject(&t->envObj);
|
|
if (SQ_FAILED(sq_getstackobj(v, 1, &t->envObj)))
|
|
return sq_throwerror(v, "Couldn't get environment from stack");
|
|
sq_addref(gVm, &t->envObj);
|
|
|
|
// create thread and store it on the stack
|
|
sq_newthread(gVm, 1024);
|
|
sq_resetobject(&t->threadObj);
|
|
if (SQ_FAILED(sq_getstackobj(gVm, -1, &t->threadObj)))
|
|
return sq_throwerror(v, "Couldn't get coroutine thread from stack");
|
|
sq_addref(gVm, &t->threadObj);
|
|
|
|
for (int i = 0; i < size - 2; i++) {
|
|
HSQOBJECT arg;
|
|
sq_resetobject(&arg);
|
|
if (SQ_FAILED(sq_getstackobj(v, 3 + i, &arg)))
|
|
return sq_throwerror(v, "Couldn't get coroutine args from stack");
|
|
t->args.push_back(arg);
|
|
sq_addref(gVm, &arg);
|
|
}
|
|
|
|
// get the closure
|
|
sq_resetobject(&t->closureObj);
|
|
if (SQ_FAILED(sq_getstackobj(v, 2, &t->closureObj)))
|
|
return sq_throwerror(v, "Couldn't get coroutine thread from stack");
|
|
sq_addref(gVm, &t->closureObj);
|
|
|
|
const SQChar *name = nullptr;
|
|
if (SQ_SUCCEEDED(sq_getclosurename(v, 2)))
|
|
sq_getstring(v, -1, &name);
|
|
|
|
t->name = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->closureObj)->_function->_sourcename), _closure(t->closureObj)->_function->_lineinfos->_line);
|
|
sq_pop(gVm, 1);
|
|
if (name)
|
|
sq_pop(v, 1); // pop name
|
|
sq_pop(v, 1); // pop closure
|
|
|
|
gScene->threads.push_back(t);
|
|
|
|
debug("create thread %s", t->name.c_str());
|
|
|
|
// call the closure in the thread
|
|
if (!t->call())
|
|
return sq_throwerror(v, "call failed");
|
|
|
|
sq_pushobject(v, t->obj);
|
|
return 1;
|
|
}
|
|
|
|
static SQInteger startthread(HSQUIRRELVM v) {
|
|
return _startthread(v, false);
|
|
}
|
|
|
|
static SQInteger startglobalthread(HSQUIRRELVM v) {
|
|
return _startthread(v, true);
|
|
}
|
|
|
|
static SQInteger objectAt(HSQUIRRELVM v) {
|
|
HSQOBJECT o;
|
|
sq_getstackobj(v, 2, &o);
|
|
|
|
SQObjectPtr id;
|
|
_table(o)->Get(sqObj(v, "_id"), id);
|
|
|
|
Entity *ett = Common::find_if(gScene->entities.begin(), gScene->entities.end(), [&](Entity &e) {
|
|
SQObjectPtr id2;
|
|
_table(e.obj)->Get(sqObj(v, "_id"), id2);
|
|
return _integer(id) == _integer(id2);
|
|
});
|
|
|
|
if (!ett)
|
|
return sq_throwerror(v, "failed to get object");
|
|
|
|
SQInteger x, y;
|
|
if (SQ_FAILED(sq_getinteger(v, 3, &x)))
|
|
return sq_throwerror(v, "failed to get x");
|
|
if (SQ_FAILED(sq_getinteger(v, 4, &y)))
|
|
return sq_throwerror(v, "failed to get y");
|
|
ett->x = x;
|
|
ett->y = y;
|
|
debug("Object at: %lld, %lld", x, y);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck, const SQChar *typemask) {
|
|
sq_pushroottable(v);
|
|
sq_pushstring(v, functionName, -1);
|
|
sq_newclosure(v, f, 0); // create a new function
|
|
sq_setparamscheck(v, nparamscheck, typemask);
|
|
sq_setnativeclosurename(v, -1, functionName);
|
|
sq_newslot(v, -3, SQFalse);
|
|
sq_pop(v, 1); // pops the root table
|
|
}
|
|
|
|
static void sqExec(HSQUIRRELVM v, const char *code) {
|
|
SQInteger top = sq_gettop(v);
|
|
if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), "twp", SQTrue))) {
|
|
sqstd_printcallstack(v);
|
|
return;
|
|
}
|
|
sq_pushroottable(v);
|
|
if (SQ_FAILED(sq_call(v, 1, SQFalse, SQTrue))) {
|
|
sqstd_printcallstack(v);
|
|
sq_pop(v, 1); // removes the closure
|
|
return;
|
|
}
|
|
sq_settop(v, top);
|
|
}
|
|
|
|
static void errorHandler(HSQUIRRELVM v, const SQChar *desc, const SQChar *source, SQInteger line,
|
|
SQInteger column) {
|
|
debug("TWP: desc %s, source: %s (%lld,%lld)", desc, source, line, column);
|
|
}
|
|
|
|
static SQInteger aux_printerror(HSQUIRRELVM v) {
|
|
SQPRINTFUNCTION pf = sq_geterrorfunc(v);
|
|
if (!pf)
|
|
return 0;
|
|
|
|
if (sq_gettop(v) < 1)
|
|
return 0;
|
|
|
|
const SQChar *error = nullptr;
|
|
if (SQ_FAILED(sq_getstring(v, 2, &error))) {
|
|
error = "unknown";
|
|
}
|
|
pf(v, _SC("\nAn error occured in the script: %s\n"), error);
|
|
sqstd_printcallstack(v);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void printfunc(HSQUIRRELVM v, const SQChar *s, ...) {
|
|
char buf[1024 * 1024];
|
|
va_list vl;
|
|
va_start(vl, s);
|
|
vsnprintf(buf, 1024 * 1024, s, vl);
|
|
va_end(vl);
|
|
|
|
debug("TWP: %s", buf);
|
|
}
|
|
|
|
Vm::Vm()
|
|
: _scene(nullptr) {
|
|
gVm = v = sq_open(1024);
|
|
sq_setcompilererrorhandler(v, errorHandler);
|
|
sq_newclosure(v, aux_printerror, 0);
|
|
sq_seterrorhandler(v);
|
|
sq_setprintfunc(v, printfunc, printfunc);
|
|
|
|
sq_pushroottable(v);
|
|
sqstd_register_stringlib(v);
|
|
sqstd_register_mathlib(v);
|
|
sqstd_register_iolib(v);
|
|
sq_pop(v, 1);
|
|
|
|
regFunc(v, sq_random, _SC("random"), 0, nullptr);
|
|
regFunc(v, createObject, _SC("createObject"), 0, nullptr);
|
|
regFunc(v, startthread, _SC("startthread"), 0, nullptr);
|
|
regFunc(v, startglobalthread, _SC("startglobalthread"), 0, nullptr);
|
|
regFunc(v, objectAt, _SC("objectAt"), 0, nullptr);
|
|
regFunc(v, breaktime, _SC("breaktime"), 0, nullptr);
|
|
|
|
SQObject platform = sqObj(v, 666);
|
|
_table(v->_roottable)->NewSlot(sqObj(v, _SC("PLATFORM")), SQObjectPtr(platform));
|
|
}
|
|
|
|
Vm::~Vm() {
|
|
if(_scene) {
|
|
for (int i = 0; i < _scene->threads.size(); i++) {
|
|
delete _scene->threads[i];
|
|
}
|
|
}
|
|
sq_close(v);
|
|
}
|
|
|
|
void Vm::exec(const SQChar *code) {
|
|
sqExec(v, code);
|
|
}
|
|
|
|
void Vm::setScene(Scene *scene) {
|
|
gScene = _scene = scene;
|
|
}
|
|
|
|
Thread::Thread() : paused(false), waitTime(0), numFrames(0), stopRequest(false) {
|
|
}
|
|
|
|
Thread::~Thread() {
|
|
for (int i = 0; i < args.size(); i++) {
|
|
sq_release(gVm, &args[i]);
|
|
}
|
|
sq_release(gVm, &threadObj);
|
|
sq_release(gVm, &envObj);
|
|
sq_release(gVm, &closureObj);
|
|
}
|
|
|
|
bool Thread::call() {
|
|
HSQUIRRELVM v = threadObj._unVal.pThread;
|
|
// call the closure in the thread
|
|
SQInteger top = sq_gettop(v);
|
|
sq_pushobject(v, closureObj);
|
|
sq_pushobject(v, envObj);
|
|
for (int i = 0; i < args.size(); i++) {
|
|
sq_pushobject(v, args[i]);
|
|
}
|
|
if (SQ_FAILED(sq_call(v, 1 + args.size(), SQFalse, SQTrue))) {
|
|
sq_settop(v, top);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Thread::isDead() {
|
|
SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
|
|
return stopRequest || state == 0;
|
|
}
|
|
|
|
bool Thread::isSuspended() {
|
|
SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
|
|
return state != 1;
|
|
}
|
|
|
|
void Thread::suspend() {
|
|
// TODO: pauseable
|
|
if (!isSuspended()) {
|
|
sq_suspendvm(threadObj._unVal.pThread);
|
|
}
|
|
}
|
|
|
|
void Thread::resume() {
|
|
if (!isDead() && isSuspended()) {
|
|
sq_wakeupvm(threadObj._unVal.pThread, SQFalse, SQFalse, SQTrue, SQFalse);
|
|
}
|
|
}
|
|
|
|
bool Thread::update(float elapsed) {
|
|
if (paused) {
|
|
} else if (waitTime > 0) {
|
|
waitTime -= elapsed;
|
|
if (waitTime <= 0) {
|
|
waitTime = 0;
|
|
resume();
|
|
}
|
|
} else if (numFrames > 0) {
|
|
numFrames -= 1;
|
|
numFrames = 0;
|
|
resume();
|
|
}
|
|
return isDead();
|
|
}
|
|
|
|
} // namespace Twp
|