Files
commandergenius/project/jni/application/enigma/src/lua.cpp
2010-10-13 17:30:44 +03:00

929 lines
23 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2002,2003,2004 Daniel Heck
*
* 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 2
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "lua.hh"
#include "errors.hh"
#include "main.hh"
#include "world.hh"
#include "config.h"
#include "video.hh"
#include "server.hh"
#include "sound.hh"
#include "options.hh"
#include "lev/Index.hh"
#include "lev/Proxy.hh"
#ifndef CXXLUA
extern "C" {
#include "lualib.h"
#include "tolua++.h"
}
#else
#include "lualib.h"
#include "tolua++.h"
#endif
#include "lua-display.hh"
#include "lua-enigma.hh"
#include "lua-ecl.hh"
#include "ecl.hh"
#include <cassert>
#include "nls.hh"
using namespace std;
using namespace enigma;
using namespace lua;
using ecl::round_down;
using ecl::strf;
using enigma::GridPos;
using world::Object;
using world::GridObject;
using world::ForceField;
namespace lua
{
int EmitSound (lua_State *L);
int EmitSoundGlobal (lua_State *L);
int MakeObject (lua_State *L);
}
namespace
{
lua_State *level_state = 0; // level-local state
lua_State *global_state = 0; // global Lua state
lua::Error _lua_err_code (int i)
{
switch (i) {
case 0: return NO_LUAERROR;
case LUA_ERRRUN: return ERRRUN;
case LUA_ERRFILE: return ERRFILE;
case LUA_ERRSYNTAX: return ERRSYNTAX;
case LUA_ERRMEM: return ERRMEM;
case LUA_ERRERR: return ERRERR;
}
assert (!"Should never get there!");
}
void throwLuaError(lua_State * L, const char * message) {
std::string backtrace = message;
backtrace += "\nBacktrace:\n";
lua_Debug dbgInfo;
int frame = 0;
while (lua_getstack(L, frame, &dbgInfo)) {
lua_getinfo(L, "Sl", &dbgInfo);
if (dbgInfo.source[0] == '@')
// lua code loaded from file
backtrace += ecl::strf("#%d %s: %d\n", frame, dbgInfo.source,
dbgInfo.currentline);
else if (dbgInfo.source[0] == '-' && dbgInfo.source[1] == '-' &&
dbgInfo.source[2] == '@') {
// lua code loaded from string
std::string code = dbgInfo.source;
std::string::size_type slashPosFilenameEnd = code.find('\n');
std::string::size_type slashPosLineStart;
std::string::size_type slashPosLineEnd = slashPosFilenameEnd;
for (int i = 1; i < dbgInfo.currentline; i++) {
slashPosLineStart = slashPosLineEnd;
slashPosLineEnd = code.find('\n', slashPosLineStart + 1);
}
backtrace += ecl::strf("#%d %s: %d\n (%s)\n", frame,
code.substr(2, slashPosFilenameEnd - 2).c_str(),
dbgInfo.currentline - 1,
code.substr(slashPosLineStart + 1, slashPosLineEnd -
slashPosLineStart - 1).c_str());
}
frame++;
}
luaL_error(L, backtrace.c_str());
}
}
/* -------------------- Helper routines -------------------- */
using enigma::Value;
void lua::SetTableVar (lua_State *L,
const char *tablename,
const char *name,
double value)
{
lua_getglobal (L, tablename);
lua_pushstring (L, name);
lua_pushnumber (L, value);
lua_rawset (L, -3);
lua_pop (L, 1);
}
static void
push_value(lua_State *L, const Value &val)
{
switch (val.get_type()) {
case Value::NIL: lua_pushnil(L); break;
case Value::DOUBLE: lua_pushnumber(L, to_double(val)); break;
case Value::STRING: lua_pushstring(L, to_string(val)); break;
}
}
static Value
to_value(lua_State *L, int idx)
{
switch (lua_type(L, idx)) {
case LUA_TNIL: return Value();
case LUA_TNUMBER: return Value(lua_tonumber(L,idx));
case LUA_TSTRING: return Value(lua_tostring(L,idx));
case LUA_TBOOLEAN: return (lua_toboolean(L,idx)) ? Value(1) : Value();
default: throwLuaError(L,"Cannot convert type to Value.");
}
return Value();
}
static bool
is_object(lua_State *L, int idx)
{
return lua_isuserdata(L,idx) && luaL_checkudata(L,idx,"_ENIGMAOBJECT");
}
static Object *
to_object(lua_State *L, int idx)
{
if (lua_isnil(L,idx))
return 0;
if (!is_object(L,idx)) {
throwLuaError(L, "Cannot convert type to an Object");
return 0;
}
return static_cast<Object*>(*(static_cast<void**>(lua_touserdata(L,idx))));
}
static void
pushobject (lua_State *L, Object *obj)
{
Object **udata;
/* Lua does not allow NULL pointers in userdata variables, so
convert them manually to `nil' values. */
if (obj == 0)
lua_pushnil(L);
else
{
udata=(Object**)lua_newuserdata(L,sizeof(int));
*udata=obj;
luaL_getmetatable(L, "_ENIGMAOBJECT");
lua_setmetatable(L, -2);
}
}
/* -------------------- Interface routines -------------------- */
int lua::MakeObject (lua_State *L)
{
const char *name = lua_tostring(L, 1);
if (!name) {
throwLuaError(L, "MakeObject: string expected as argument");
}
Object *obj = world::MakeObject(name);
if (obj == NULL)
throwLuaError(L, ecl::strf("MakeObject: unknown object name '%s'", name).c_str());
pushobject(L, obj);
return 1;
}
static int
en_get_object_template(lua_State *L)
{
Object *obj = world::GetObjectTemplate(lua_tostring(L, 1));
pushobject(L, obj);
return 1;
}
static int
en_set_attrib(lua_State *L)
{
Object *obj = to_object(L,1);
const char *key = lua_tostring(L,2);
if (obj && key)
obj->set_attrib(key, to_value(L, 3));
else
throwLuaError(L, strf("SetAttrib: invalid object or attribute name '%s'", key).c_str());
return 0;
}
static int
en_get_attrib(lua_State *L)
{
Object *obj = to_object(L,1);
const char *key = lua_tostring(L,2);
if (!obj) {
throwLuaError(L, "GetAttrib: invalid object");
return 0;
}
if (!key) {
throwLuaError(L, "GetAttrib: invalid key");
return 0;
}
if (0 == strcmp(key, "kind")) {
throwLuaError(L, "GetAttrib: illegal attribute, use GetKind()");
return 0;
}
const Value *v = obj->get_attrib(key);
if (!v) {
// unknown attribute
lua_pushnil(L);
}
else
push_value(L, *v);
return 1;
}
static int
en_get_kind(lua_State *L)
{
Object *obj = to_object(L,1);
if (!obj) {
throwLuaError(L, "GetKind: invalid object");
return 0;
}
push_value(L, Value(obj->get_kind()));
return 1;
}
static int
en_is_same_object(lua_State *L)
{
Object *obj1 = to_object(L,1);
Object *obj2 = to_object(L,2);
lua_pushboolean(L, obj1 == obj2);
return 1;
}
static int
en_set_floor(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
Floor *fl=0;
if (lua_isnil(L, 3))
fl = 0;
else if (is_object(L,3)) {
fl = static_cast<Floor*>(*(static_cast<void**> (lua_touserdata(L,3))));
if( ! fl)
throwLuaError(L, "object is no valid floor");
} else
throwLuaError(L, "argument 3 must be an Object or nil");
world::SetFloor(GridPos(x,y), fl);
return 0;
}
static int
en_set_item(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
Item *it = dynamic_cast<Item*>(to_object(L, 3));
if( ! it) {
throwLuaError(L, "object is no valid item");
}
world::SetItem(GridPos(x,y), it);
return 0;
}
static int
en_set_stone(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
Stone *st = dynamic_cast<Stone*>(to_object(L, 3));
if( ! st)
throwLuaError(L, "object is no valid stone");
world::SetStone(GridPos(x,y), st);
return 0;
}
static int en_kill_stone(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
world::KillStone(GridPos(x,y));
return 0;
}
static int en_kill_item(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
world::KillItem(GridPos(x,y));
return 0;
}
static int
en_set_actor(lua_State *L)
{
double x = lua_tonumber(L,1);
double y = lua_tonumber(L,2);
Actor *ac = dynamic_cast<Actor*>(to_object(L, 3));
if( ! ac)
throwLuaError(L, "object is no valid actor");
if (world::IsInsideLevel(GridPos(round_down<int>(x), round_down<int>(y))))
world::AddActor(x, y, ac);
else
throwLuaError(L, "position is outside of world");
return 0;
}
static int
en_send_message(lua_State *L)
{
Object *obj = to_object(L, 1);
const char *msg = lua_tostring(L, 2);
Value v;
if (!msg)
throwLuaError(L,"Illegal message");
else if (obj) {
try {
v = world::SendMessage (obj, msg, to_value(L, 3));
}
catch (const XLevelRuntime &e) {
throwLuaError (L, e.what());
}
catch (...) {
throwLuaError (L, "uncaught exception");
}
}
push_value(L, v);
return 0;
}
int lua::EmitSound (lua_State *L)
{
Object *obj = to_object(L, 1);
const char *soundname = lua_tostring(L, 2);
double vol = 1.0;
if (lua_isnumber(L, 3))
vol = lua_tonumber(L, 3);
if (!soundname)
throwLuaError(L,"Illegal sound");
else if (obj) {
GridObject *gobj = dynamic_cast<GridObject*>(obj);
if (gobj) {
if (!gobj->sound_event (soundname, vol)) {
//throwLuaError(L, strf("Can't find sound '%s'", soundname).c_str());
// Don't throw an error when no sound file was found.
// Remember that user sound sets might be incomplete, and
// absolutely correct levels could throw an error here.
// Instead, write the "silence string" to the command line:
sound::WriteSilenceString(soundname);
}
}
}
else
throwLuaError(L, "EmitSound: invalid object");
return 0;
}
int lua::EmitSoundGlobal (lua_State *L)
{
const char *soundname = lua_tostring(L, 1);
double vol = 1.0;
if (lua_isnumber(L, 3))
vol = lua_tonumber(L, 3);
if (!soundname)
throwLuaError(L,"Illegal sound");
else
sound::EmitSoundEventGlobal(soundname, vol);
return 0;
}
static int
en_name_object(lua_State *L)
{
Object *obj = to_object(L, 1);
const char *name = lua_tostring(L, 2);
if (!obj)
throwLuaError(L, "NameObject: Illegal object");
else if (!name)
throwLuaError(L, "NameObject: Illegal name");
else
world::NameObject(obj, name);
return 0;
}
static int
en_get_named_object(lua_State *L)
{
Object *o = world::GetNamedObject(lua_tostring(L,1));
pushobject(L, o);
return 1;
}
static int
en_get_floor(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
Object *o = world::GetFloor(GridPos(x, y));
pushobject(L, o);
return 1;
}
static int
en_get_item(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
Object *o = world::GetItem(GridPos(x, y));
pushobject(L, o);
return 1;
}
static int
en_get_stone(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
Object *o = world::GetStone(GridPos(x, y));
pushobject(L, o);
return 1;
}
static int
en_get_pos(lua_State *L)
{
Object *obj = to_object(L, 1);
GridPos p;
if (!obj) {
throwLuaError(L, "GetPos: Illegal object");
return 0;
}
if (GridObject *gobj = dynamic_cast<GridObject*>(obj))
p = gobj->get_pos();
else if (Actor *a = dynamic_cast<Actor*>(obj)) {
p = GridPos(a->get_pos());
}
else
p = GridPos(-1, -1);
lua_pushnumber(L, double(p.x));
lua_pushnumber(L, double(p.y));
return 2;
}
static int en_add_constant_force(lua_State *L) {
ecl::V2 v;
v[0] = lua_tonumber(L, 1);
v[1] = lua_tonumber(L, 2);
world::SetConstantForce (v);
return 0;
}
static int
en_add_rubber_band (lua_State *L)
{
Actor *a1 = dynamic_cast<Actor*> (to_object(L, 1));
Object *o2 = to_object(L, 2);
Actor *a2 = dynamic_cast<Actor*>(o2);
Stone *st = dynamic_cast<Stone*>(o2);
world::RubberBandData d;
d.strength = lua_tonumber (L, 3);
d.length = lua_tonumber (L, 4);
d.minlength = lua_tonumber (L, 5);
if (!a1)
throwLuaError(L, "AddRubberBand: First argument must be an actor\n");
else {
if (a2)
world::AddRubberBand (a1, a2, d);
else if (st)
world::AddRubberBand (a1, st, d);
else
throwLuaError(L, "AddRubberBand: Second argument must be actor or stone\n");
}
return 0;
}
static int
en_get_ticks(lua_State *L)
{
lua_pushnumber(L, SDL_GetTicks());
return 1;
}
static int
en_is_solved(lua_State *L)
{
// Function depreceated
// - filename is no longer a useful reference for levels
// - levels should not depend on external data for reasons of
// network compatibility and level journaling
throwLuaError(L, "Usage of depreceated function \"IsSolved()\"");
// const char *levelname = lua_tostring(L,1);
// int solved = 0;
//
// levels::Level level (0, 0);
// if (levels::FindLevel (levelname, level)) {
// solved = level.is_solved(options::GetInt("Difficulty"));
// } else
// if (solved)
// lua_pushnumber(L, solved);
// else
// lua_pushnil(L);
return 1;
}
static int
en_add_scramble(lua_State *L)
{
int x = round_down<int>(lua_tonumber(L, 1));
int y = round_down<int>(lua_tonumber(L, 2));
const char *dir = lua_tostring(L,3);
const char *allowed = "wsen";
const char *found = strchr(allowed, dir[0]);
if (found && found[0])
world::AddScramble(GridPos(x,y), enigma::Direction(found-allowed));
else
throwLuaError(L, "AddScramble: Third argument must be one character of \"wsen\"");
return 0;
}
static int
en_set_scramble_intensity(lua_State *L)
{
world::SetScrambleIntensity(int(lua_tonumber(L, 1)));
return 0;
}
static int
en_add_signal (lua_State *L) {
const char *sourcestr = lua_tostring (L, 1);
const char *targetstr = lua_tostring (L, 2);
const char *msg = lua_tostring (L, 3);
using namespace enigma;
GridLoc source, target;
if (sourcestr == 0 || !to_gridloc(sourcestr, source))
throwLuaError (L, "AddSignal: invalid source descriptor");
if (targetstr == 0 || !to_gridloc(targetstr, target))
throwLuaError (L, "AddSignal: invalid target descriptor");
if (msg == 0)
msg = "signal";
world::AddSignal (source, target, msg);
return 0;
}
int loadLib(lua_State *L)
{
const char *id = lua_tostring(L, 1);
lev::Proxy * curProxy = lev::Index::getCurrentProxy();
try {
curProxy->loadDependency(id);
} catch (XLevelLoading &err) {
throwLuaError(L, err.what());
}
return 1;
}
static CFunction globalfuncs[] = {
{FindDataFile, "FindDataFile"},
// {lua::PlaySoundGlobal, "PlaySoundGlobal"},
// {lua::PlaySound, "PlaySound"},
{en_get_ticks, "GetTicks"},
{0,0}
};
static CFunction levelfuncs[] = {
// internal functions
{FindDataFile, "FindDataFile"},
{loadLib, "LoadLib"},
{en_get_object_template,"GetObjectTemplate"},
{lua::MakeObject, "MakeObject"},
{en_set_actor, "SetActor"},
// finding objects
{en_get_named_object, "GetNamedObject"},
{en_get_floor, "GetFloor"},
{en_get_item, "GetItem"},
{en_get_stone, "GetStone"},
// information from objects
{en_get_pos, "GetPos"},
{en_get_attrib, "GetAttrib"},
{en_get_kind, "GetKind"},
{en_is_same_object, "IsSameObject"},
// manipulating objects
{en_set_attrib, "SetAttrib"},
{en_send_message, "SendMessage"},
{en_name_object, "NameObject"},
// sound effects
{lua::EmitSound, "EmitSound"},
// manipulating level
{en_set_floor, "SetFloor"},
{en_set_item, "SetItem"},
{en_set_stone, "SetStone"},
{en_kill_stone, "KillStone"},
{en_kill_item, "KillItem"},
// signals
{en_add_signal, "AddSignal"},
// access/modify global data
{en_get_ticks, "GetTicks"},
{en_is_solved, "IsSolved"},
{en_add_constant_force, "AddConstantForce"},
{en_add_rubber_band, "AddRubberBand"},
{en_add_scramble, "AddScramble"},
{en_set_scramble_intensity, "SetScrambleIntensity"},
{0,0}
};
/* -------------------- lua:: functions -------------------- */
int lua::FindDataFile (lua_State *L)
{
const char *filename = lua_tostring(L, 1);
string absfile;
if (app.resourceFS->findFile(filename, absfile))
lua_pushstring(L, absfile.c_str());
else
lua_pushnil (L);
return 1;
}
void lua::RegisterFuncs(lua_State *L, CFunction *funcs)
{
lua_getglobal(L, "enigma");
for (unsigned i=0; funcs[i].func; ++i) {
lua_pushstring(L, funcs[i].name);
lua_pushcfunction(L, funcs[i].func);
lua_settable(L, -3);
}
lua_pop(L, 1);
}
Error lua::CallFunc(lua_State *L, const char *funcname, const Value& arg, world::Object *obj) {
int retval;
lua_getglobal(L, funcname);
push_value(L, arg);
pushobject(L, obj);
retval=lua_pcall(L,2,0,0);
if (retval!=0) // error
{
lua_setglobal (L, "_LASTERROR") ; //Set _LASTERROR to returned error message
}
return _lua_err_code(retval);
}
Error lua::CallFunc(lua_State *L, const char *funcname, const ByteVec& arg) {
int retval;
lua_getglobal(L, funcname);
lua_pushlstring (L, &arg[0], arg.size());
retval=lua_pcall(L,1,0,0);
if (retval!=0) // error
{
lua_setglobal (L, "_LASTERROR") ; //Set _LASTERROR to returned error message
}
return _lua_err_code(retval);
}
Error lua::DoAbsoluteFile(lua_State *L, const string &filename)
{
int oldtop = lua_gettop(L);
int retval = luaL_loadfile(L, filename.c_str());
if (retval!=0) // error
{
lua_setglobal (L, "_LASTERROR") ; //Set _LASTERROR to returned error message
}
else
{
retval= lua_pcall(L, 0, 0, 0);
if (retval!=0) // error
{
lua_setglobal (L, "_LASTERROR") ; //Set _LASTERROR to returned error message
}
}
lua_settop(L, oldtop);
return _lua_err_code(retval);
}
Error lua::DoGeneralFile(lua_State *L, GameFS * fs, const string &filename)
{
string completefn;
if (fs->findFile(filename, completefn)) {
return lua::DoAbsoluteFile(L, completefn);
}
else {
return _lua_err_code(LUA_ERRFILE);
}
}
Error lua::Dofile(lua_State *L, const string &filename)
{
return lua::DoGeneralFile(L, app.resourceFS, filename);
}
Error lua::DoSysFile(lua_State *L, const string &filename)
{
return lua::DoGeneralFile(L, app.systemFS, filename);
}
void lua::CheckedDoFile (lua_State *L, GameFS * fs, std::string const& fname)
{
string completefn;
if (!fs->findFile(fname, completefn))
{
fprintf(stderr, _("Cannot find '%s'.\n"), fname.c_str());
fprintf(stderr, _("Your installation may be incomplete or invalid.\n"));
exit (1);
}
lua::Error status = lua::DoAbsoluteFile(L, completefn);
if (status != lua::NO_LUAERROR) {
fprintf(stderr, _("There was an error loading '%s'.\n"), completefn.c_str());
fprintf(stderr, _("Your installation may be incomplete or invalid.\n"));
fprintf(stderr, _("Error: '%s'\n"), lua::LastError(L).c_str());
exit (1);
}
}
Error lua::Dobuffer (lua_State *L, const ByteVec &luacode) {
int retval;
const char *buffer = reinterpret_cast<const char *>(&luacode[0]);
retval=luaL_loadbuffer(L, buffer, luacode.size(), "buffer");
if (retval!=0) // error
{
lua_setglobal (L, "_LASTERROR") ; //Set _LASTERROR to returned error message
}
else
{
retval= lua_pcall(L, 0, 0, 0);
if (retval!=0) // error
{
lua_setglobal (L, "_LASTERROR") ; //Set _LASTERROR to returned error message
}
}
return _lua_err_code(retval);
}
string lua::LastError (lua_State *L)
{
lua_getglobal (L, "_LASTERROR");
if (lua_isstring(L,-1)){
return string (lua_tostring (L, -1));
}
else {
return "Lua Error. No error message available.";
}
}
Error lua::DoSubfolderfile(lua_State *L, const string &basefolder, const string &filename) {
std::list <string> fnames = app.resourceFS->findSubfolderFiles(basefolder, filename);
int retval = 0;
while (fnames.size() > 0) {
int oldtop = lua_gettop(L);
string fname = fnames.front();
retval = luaL_loadfile(L, fname.c_str());
if (retval!=0) // error
{
lua_setglobal (L, "_LASTERROR") ; //Set _LASTERROR to returned error message
}
else
{
retval= lua_pcall(L, 0, 0, 0);
if (retval!=0) // error
{
lua_setglobal (L, "_LASTERROR") ; //Set _LASTERROR to returned error message
}
}
fnames.pop_front();
lua_settop(L, oldtop);
}
return _lua_err_code(retval);
}
lua_State *lua::GlobalState()
{
if (global_state == 0) {
lua_State *L = global_state = lua_open();
luaL_openlibs(L);
CheckedDoFile(L, app.systemFS, "compat.lua");
tolua_open(L);
tolua_enigma_open(L);
RegisterFuncs(L, globalfuncs);
}
return global_state;
}
void lua::ShutdownGlobal()
{
assert (global_state);
lua_close(global_state);
global_state = 0;
}
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
lua_State *lua::InitLevel()
{
char buffer[255];
lua_State *L = level_state = lua_open();
luaL_dostring (L, "options={}");
snprintf (buffer, sizeof(buffer), "options.Difficulty = %d",
server::GetDifficulty());
luaL_dostring (L, buffer);
luaL_openlibs(L);
tolua_open(L);
tolua_enigma_open(L);
tolua_px_open(L);
tolua_display_open(L);
RegisterFuncs(L, levelfuncs);
// Create a new metatable for world::Object objects
luaL_newmetatable(L,"_ENIGMAOBJECT");
return L;
}
lua_State *lua::LevelState ()
{
return level_state;
}
void lua::ShutdownLevel() {
if (level_state) {
lua_close(level_state);
level_state = 0;
}
}