1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2025-01-24 17:26:41 +00:00

Add a safe pointer model to ScriptInterface, additional bug/crash fixes.

This commit introduces pointer checks to various functions;
so that entity_* will no longer crash or produce weird results
if passed a Node pointer, etc.
The checks are disabled by default, but can be enabled in
ScriptInterface.cpp.

Fixed possible crashes in a few more functions due to missing NULL-checks.

There was a "feature" in the single Lua state that it would keep globals
intact until the game was quit. That made any globals from mods "leak"
into the game or other mods. Now it resets the Lua state when a mod
is loaded or closed.
This commit is contained in:
fgenesis 2012-01-03 04:38:28 +01:00
parent becd31770c
commit 4320b8296b
22 changed files with 298 additions and 103 deletions

View file

@ -28,6 +28,7 @@ Beam::Beams Beam::beams;
Beam::Beam(Vector pos, float angle) : Quad()
{
addType(SCO_BEAM);
cull = false;
trace();
//rotation.z = angle;

View file

@ -25,6 +25,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
CollideEntity::CollideEntity() : Entity()
{
addType(SCO_COLLIDE_ENTITY);
this->canBeTargetedByAvatar = true;
weight = 0;
bounceAmount = 0.5f;

View file

@ -167,6 +167,7 @@ bool Entity::canSetBoneLock()
Entity::Entity() : StateMachine(), DFSprite()
{
addType(SCO_ENTITY);
poison = 0.0f;
calledEntityDied = false;
wasUnderWater = true;

View file

@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "../BBGE/StateMachine.h"
#include "../ExternalLibs/tinyxml.h"
#include "../BBGE/SkeletalSprite.h"
#include "../BBGE/ScriptObject.h"
#include "DSQ.h"
#include "Path.h"
@ -193,7 +194,7 @@ enum BounceType
BOUNCE_REAL = 1
};
class Entity : public StateMachine, public DFSprite
class Entity : public ScriptObject, public DFSprite, public StateMachine
{
public:
Entity();

View file

@ -40,6 +40,7 @@ bool IngredientData::hasIET(IngredientEffectType iet)
Ingredient::Ingredient(const Vector &pos, IngredientData *data, int amount)
: Entity(), data(data), amount(amount), gone(false), used(false)
{
addType(SCO_INGREDIENT);
entityType = ET_INGREDIENT;
position = pos;
lifeSpan = 30;

View file

@ -196,7 +196,8 @@ void Mod::applyStart()
core->clearGarbage();
recache();
dsq->continuity.reset();
dsq->scriptInterface.reset();
// load the mod-init.lua file
// which is in the root of the mod's folder
// e.g. _mods/recachetest/
@ -265,6 +266,7 @@ void Mod::stop()
core->settings.runInBackground = false;
debugMenu = false;
shuttingDown = false;
dsq->scriptInterface.reset();
}
void Mod::update(float dt)

View file

@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Path::Path()
{
addType(SCO_PATH);
localWarpType = LOCALWARP_NONE;
effectOn = true;
time = 0;

View file

@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "../BBGE/Base.h"
#include "../BBGE/Particles.h"
#include "../BBGE/ScriptObject.h"
#include "ScriptInterface.h"
#undef PATH_MAX // May be set by a system header.
@ -67,7 +68,7 @@ enum PathShape
PATHSHAPE_CIRCLE = 1
};
class Path
class Path : public ScriptObject
{
public:
Path();

View file

@ -19,6 +19,7 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "ScriptInterface.h"
#include "../BBGE/ScriptObject.h"
extern "C"
{
#include "lua.h"
@ -37,14 +38,22 @@ extern "C"
#include "../BBGE/MathFunctions.h"
// Define this to 1 to check types of pointers passed to functions,
// and warn if a type mismatch is detected. In this case,
// the pointer is treated as NULL, to avoid crashing or undefined behavior.
//#define CHECK_POINTER_TYPES 1
// If true, send all sort of script errors to errorLog instead of debugLog.
// On win32, this pops up message boxes which help to locate errors easily,
// On win32/OSX, this pops up message boxes which help to locate errors easily,
// but can be annoying for regular gameplay.
const bool loudScriptErrors = false;
// This setting causes NULL/0 pointers passed to a function to issue
// a Lua error instead of silently ignoring it.
// Some functions expect this behavior - do not enable!.
const bool throwLuaErrors = false;
// Set this to true to complain (via errorLog()) whenever a script tries to
// Set this to true to complain whenever a script tries to
// get or set a global variable.
const bool complainOnGlobalVar = false;
@ -312,10 +321,75 @@ static inline void luaPushPointer(lua_State *L, void *ptr)
lua_pushnumber(L, 0);
}
static std::string luaFormatStackInfo(lua_State *L)
{
lua_Debug ar;
if (lua_getstack(L, 1, &ar))
lua_getinfo(L, "Sl", &ar);
else
{
snprintf(ar.short_src, sizeof(ar.short_src), "???");
ar.currentline = 0;
}
std::ostringstream os;
os << ar.short_src << ":" << ar.currentline;
return os.str();
}
#if CHECK_POINTER_TYPES
// Not intended to be called.
// Because wild typecasting expects X::_objtype to reside at the same relative
// memory location, be sure this is the case before running into undefined behavior later.
// - The C++ standard allows offsetof() only on POD-types. Oh well, it probably works anyways.
// If it does not compile for some reason, comment it out, hope for the best, and go ahead.
void compile_time_assertions()
{
#define oo(cls) offsetof(cls, _objtype)
#define compile_assert(pred) switch(0){case 0:case (pred):;}
compile_assert(oo(Path) == oo(Entity));
compile_assert(oo(Path) == oo(Ingredient));
compile_assert(oo(Path) == oo(CollideEntity));
compile_assert(oo(Path) == oo(ScriptedEntity));
compile_assert(oo(Path) == oo(Beam));
compile_assert(oo(Path) == oo(Shot));
compile_assert(oo(Path) == oo(Web));
compile_assert(oo(Path) == oo(Bone));
compile_assert(oo(Path) == oo(PauseQuad));
compile_assert(oo(Path) == oo(Avatar));
#undef oo
#undef compile_assert
}
template <typename T>
static void ensureType(lua_State *L, T *& ptr, ScriptObjectType ty)
{
if (ptr)
{
ScriptObject *so = (ScriptObject*)(ptr);
if (!so->isType(ty))
{
std::ostringstream os;
os << "WARNING: " << luaFormatStackInfo(L)
<< ": script passed wrong pointer to function (expected type: "
<< ScriptObject::getTypeString(ty) << "; got: "
<< so->getTypeString() << ')';
scriptError(os.str());
ptr = NULL; // note that the pointer is passed by reference
}
}
}
# define ENSURE_TYPE(ptr, ty) ensureType(L, (ptr), (ty))
#else
# define ENSURE_TYPE(ptr, ty)
#endif
static inline
ScriptedEntity *scriptedEntity(lua_State *L, int slot = 1)
{
ScriptedEntity *se = (ScriptedEntity*)lua_touserdata(L, slot);
ENSURE_TYPE(se, SCO_SCRIPTED_ENTITY);
if (!se)
debugLog("ScriptedEntity invalid pointer.");
return se;
@ -325,25 +399,17 @@ static inline
CollideEntity *collideEntity(lua_State *L, int slot = 1)
{
CollideEntity *ce = (CollideEntity*)lua_touserdata(L, slot);
ENSURE_TYPE(ce, SCO_COLLIDE_ENTITY);
if (!ce)
debugLog("CollideEntity invalid pointer.");
return ce ;
}
static inline
RenderObject *object(lua_State *L, int slot = 1)
{
//RenderObject *obj = dynamic_cast<RenderObject*>((RenderObject*)(int(lua_tonumber(L, slot))));
RenderObject *obj = static_cast<RenderObject*>(lua_touserdata(L, slot));
if (!obj)
debugLog("RenderObject invalid pointer");
return obj;
}
static inline
Beam *beam(lua_State *L, int slot = 1)
{
Beam *b = (Beam*)lua_touserdata(L, slot);
ENSURE_TYPE(b, SCO_BEAM);
if (!b)
debugLog("Beam invalid pointer.");
return b;
@ -364,6 +430,7 @@ static inline
Shot *getShot(lua_State *L, int slot = 1)
{
Shot *shot = (Shot*)lua_touserdata(L, slot);
ENSURE_TYPE(shot, SCO_SHOT);
return shot;
}
@ -371,13 +438,16 @@ static inline
Web *getWeb(lua_State *L, int slot = 1)
{
Web *web = (Web*)lua_touserdata(L, slot);
ENSURE_TYPE(web, SCO_WEB);
return web;
}
static inline
Ingredient *getIng(lua_State *L, int slot = 1)
{
return (Ingredient*)lua_touserdata(L, slot);
Ingredient *ing = (Ingredient*)lua_touserdata(L, slot);
ENSURE_TYPE(ing, SCO_INGREDIENT);
return ing;
}
static inline
@ -402,6 +472,7 @@ static inline
Entity *entity(lua_State *L, int slot = 1)
{
Entity *ent = (Entity*)lua_touserdata(L, slot);
ENSURE_TYPE(ent, SCO_ENTITY);
if (!ent)
{
luaErrorMsg(L, "Entity Invalid Pointer");
@ -421,6 +492,7 @@ static inline
Bone *bone(lua_State *L, int slot = 1)
{
Bone *b = (Bone*)lua_touserdata(L, slot);
ENSURE_TYPE(b, SCO_BONE);
if (!b)
{
luaErrorMsg(L, "Bone Invalid Pointer");
@ -445,6 +517,7 @@ static inline
Path *path(lua_State *L, int slot = 1)
{
Path *p = (Path*)lua_touserdata(L, slot);
ENSURE_TYPE(p, SCO_PATH);
return p;
}
@ -463,28 +536,15 @@ static RenderObject *boneToRenderObject(lua_State *L, int slot = 1)
static PauseQuad *getPauseQuad(lua_State *L, int slot = 1)
{
PauseQuad *q = (PauseQuad*)lua_touserdata(L, slot);
if (q)
return q;
else
ENSURE_TYPE(q, SCO_PAUSEQUAD);
if (!q)
errorLog("Invalid PauseQuad/Particle");
return 0;
return q;
}
static SkeletalSprite *getSkeletalSprite(Entity *e)
{
Avatar *a;
ScriptedEntity *se;
SkeletalSprite *skel = 0;
if ((a = dynamic_cast<Avatar*>(e)) != 0)
{
//a->skeletalSprite.transitionAnimate(lua_tostring(L, 2), 0.15, lua_tointeger(L, 3));
skel = &a->skeletalSprite;
}
else if ((se = dynamic_cast<ScriptedEntity*>(e)) != 0)
{
skel = &se->skeletalSprite;
}
return skel;
return e ? &e->skeletalSprite : NULL;
}
static bool looksLikeGlobal(const char *s)
@ -537,20 +597,8 @@ luaFunc(indexWarnGlobal)
if (doWarn)
{
lua_Debug ar;
if (lua_getstack(L, 1, &ar))
{
lua_getinfo(L, "Sl", &ar);
}
else
{
snprintf(ar.short_src, sizeof(ar.short_src), "???");
ar.currentline = 0;
}
lua_getinfo(L, "Sl", &ar);
std::ostringstream os;
os << "WARNING: " << ar.short_src << ":" << ar.currentline
os << "WARNING: " << luaFormatStackInfo(L)
<< ": script tried to get/call undefined global variable "
<< varname;
scriptError(os.str());
@ -577,22 +625,11 @@ luaFunc(newindexWarnGlobal)
if (doWarn)
{
lua_Debug ar;
if (lua_getstack(L, 1, &ar))
{
lua_getinfo(L, "Sl", &ar);
}
else
{
snprintf(ar.short_src, sizeof(ar.short_src), "???");
ar.currentline = 0;
}
std::ostringstream os;
os << "WARNING: " << ar.short_src << ":" << ar.currentline
os << "WARNING: " << luaFormatStackInfo(L)
<< ": script set global "
<< (lua_type(L, -2) == LUA_TFUNCTION ? "function" : "variable")
<< " " << lua_tostring(L, -1);
<< " " << varname;
scriptError(os.str());
}
@ -611,18 +648,8 @@ luaFunc(indexWarnInstance)
lua_remove(L, -3);
if (lua_isnil(L, -1))
{
lua_Debug ar;
if (lua_getstack(L, 1, &ar))
{
lua_getinfo(L, "Sl", &ar);
}
else
{
snprintf(ar.short_src, sizeof(ar.short_src), "???");
ar.currentline = 0;
}
std::ostringstream os;
os << "WARNING: " << ar.short_src << ":" << ar.currentline
os << "WARNING: " << luaFormatStackInfo(L)
<< ": script tried to get/call undefined instance variable "
<< lua_tostring(L, -2);
errorLog(os.str());
@ -1819,14 +1846,6 @@ luaFunc(loadMap)
luaFunc(entity_followPath)
{
/*
std::ostringstream os2;
os2 << lua_tointeger(L, 1);
errorLog(os2.str());
std::ostringstream os;
os << "Entity: " << scriptedEntity(L)->name << " moving on Path: " << lua_tostring(L, 2);
debugLog(os.str());
*/
Entity *e = entity(L);
if (e)
{
@ -2627,16 +2646,16 @@ luaFunc(entity_setAnimLayerTimeMult)
luaFunc(entity_animate)
{
SkeletalSprite *skel = getSkeletalSprite(entity(L));
// 0.15
// 0.2
float transition = lua_tonumber(L, 5);
if (transition == -1)
transition = 0;
else if (transition == 0)
transition = 0.2;
float ret = skel->transitionAnimate(lua_tostring(L, 2), transition, lua_tointeger(L, 3), lua_tointeger(L, 4));
float ret = 0;
if (skel)
{
float transition = lua_tonumber(L, 5);
if (transition == -1)
transition = 0;
else if (transition == 0)
transition = 0.2;
ret = skel->transitionAnimate(lua_tostring(L, 2), transition, lua_tointeger(L, 3), lua_tointeger(L, 4));
}
luaReturnNum(ret);
}
@ -3642,13 +3661,17 @@ luaFunc(entity_rotateToVec)
luaFunc(entity_update)
{
entity(L)->update(lua_tonumber(L, 2));
Entity *e = entity(L);
if (e)
e->update(lua_tonumber(L, 2));
luaReturnNum(0);
}
luaFunc(entity_updateSkeletal)
{
entity(L)->skeletalSprite.update(lua_tonumber(L, 2));
Entity *e = entity(L);
if (e)
e->skeletalSprite.update(lua_tonumber(L, 2));
luaReturnNum(0);
}
@ -3667,17 +3690,21 @@ luaFunc(entity_msg)
luaFunc(entity_updateCurrents)
{
luaReturnBool(entity(L)->updateCurrents(lua_tonumber(L, 2)));
Entity *e = entity(L);
luaReturnBool(e ? e->updateCurrents(lua_tonumber(L, 2)) : false);
}
luaFunc(entity_updateLocalWarpAreas)
{
luaReturnBool(entity(L)->updateLocalWarpAreas(getBool(L, 2)));
Entity *e = entity(L);
luaReturnBool(e ? e->updateLocalWarpAreas(getBool(L, 2)) : false);
}
luaFunc(entity_updateMovement)
{
scriptedEntity(L)->updateMovement(lua_tonumber(L, 2));
ScriptedEntity *e = scriptedEntity(L);
if (e)
e->updateMovement(lua_tonumber(L, 2));
luaReturnNum(0);
}
@ -6586,12 +6613,14 @@ luaFunc(entity_getTarget)
luaFunc(entity_getTargetPositionX)
{
luaReturnInt(int(entity(L)->getTargetEntity()->position.x));
Entity *e = entity(L);
luaReturnInt(e ? e->getTargetEntity()->position.x : 0);
}
luaFunc(entity_getTargetPositionY)
{
luaReturnInt(int(entity(L)->getTargetEntity()->position.y));
Entity *e = entity(L);
luaReturnNum(e ? e->getTargetEntity()->position.y : 0);
}
luaFunc(entity_isNearObstruction)
@ -7095,7 +7124,6 @@ luaFunc(entity_setFlag)
luaFunc(entity_getFlag)
{
Entity *e = entity(L);
int v = lua_tonumber(L, 2);
int ret = 0;
if (e)
{
@ -8821,9 +8849,21 @@ static const struct {
// F U N C T I O N S
//============================================================================================
ScriptInterface::ScriptInterface()
: baseState(NULL)
{
}
void ScriptInterface::init()
{
baseState = createLuaVM();
if (!baseState)
baseState = createLuaVM();
}
void ScriptInterface::reset()
{
shutdown();
init();
}
lua_State *ScriptInterface::createLuaVM()
@ -8987,6 +9027,8 @@ void ScriptInterface::collectGarbage()
void ScriptInterface::shutdown()
{
destroyLuaVM(baseState);
baseState = NULL;
}
Script *ScriptInterface::openScript(const std::string &file, bool ignoremissing /* = false */)

View file

@ -77,7 +77,9 @@ protected:
class ScriptInterface
{
public:
ScriptInterface();
void init();
void reset();
void collectGarbage();
void shutdown();

View file

@ -27,7 +27,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
bool ScriptedEntity::runningActivation = false;
ScriptedEntity::ScriptedEntity(const std::string &scriptName, Vector position, EntityType et) : CollideEntity(), Segmented(2, 26)
{
{
addType(SCO_SCRIPTED_ENTITY);
crushDelay = 0;
autoSkeletalSpriteUpdate = true;
script = 0;

View file

@ -323,6 +323,7 @@ Shot::Shot(DamageType damageType, Entity *firer, Vector pos, Entity *target, std
Shot::Shot() : Quad(), Segmented(0,0)
{
addType(SCO_SHOT);
extraDamage= 0;
waveTimer = rand()%314;
emitter = 0;

View file

@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "CollideEntity.h"
#include "Segmented.h"
#include "../BBGE/Particles.h"
#include "../BBGE/ScriptObject.h"
struct ShotData
{
@ -69,7 +70,7 @@ struct ShotData
};
class Shot : public Quad, public Segmented
class Shot : public ScriptObject, public Quad, public Segmented
{
public:
//Shot(DamageType damageType, Entity *firer, Vector pos, Entity *target, std::string tex="", float homingness=1000, int maxSpeed=400, int segments=10, float segMin=0.1, float segMax=5, float damage = 1, float lifeTime = 0);
@ -136,7 +137,7 @@ protected:
void onUpdate(float dt);
};
class Beam : public Quad
class Beam : public ScriptObject, public Quad
{
public:
Beam(Vector pos, float angle);

View file

@ -27,6 +27,7 @@ Web::Webs Web::webs;
Web::Web() : RenderObject()
{
addType(SCO_WEB);
webs.push_back(this);
cull = false;
parentEntity = 0;

View file

@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "../BBGE/Quad.h"
#include "Entity.h"
class Web : public RenderObject
class Web : public ScriptObject, public RenderObject
{
public:
Web();

View file

@ -918,6 +918,7 @@ void Quad::onSetTexture()
PauseQuad::PauseQuad() : Quad(), pauseLevel(0)
{
addType(SCO_PAUSEQUAD);
}
void PauseQuad::onUpdate(float dt)

View file

@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#define __quad__
#include "RenderObject.h"
#include "ScriptObject.h"
class QuadLight
{
@ -149,7 +150,7 @@ private:
void initQuad();
};
class PauseQuad : public Quad
class PauseQuad : public ScriptObject, public Quad
{
public:
PauseQuad();

60
BBGE/ScriptObject.cpp Normal file
View file

@ -0,0 +1,60 @@
/*
Copyright (C) 2007, 2012 - Bit-Blot
This file is part of Aquaria.
Aquaria 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <sstream>
#include "ScriptObject.h"
static const char *scriptObjTypeNames[] =
{
/* (1 << 0) */ "Entity",
/* (1 << 1) */ "Ingredient",
/* (1 << 2) */ "CollideEntity",
/* (1 << 3) */ "ScriptedEntity",
/* (1 << 4) */ "Beam",
/* (1 << 5) */ "Shot",
/* (1 << 6) */ "Web",
/* (1 << 7) */ "Bone",
/* (1 << 8) */ "Path/Node",
/* (1 << 9) */ "PauseQuad",
NULL
};
std::string ScriptObject::getTypeString(unsigned int ty)
{
if (ty == SCO_NONE)
return "NO TYPE";
bool more = false;
std::ostringstream os;
for (int i = 0; scriptObjTypeNames[i]; ++i)
{
if (ty & (1 << i))
{
if (more)
os << ", ";
os << scriptObjTypeNames[i];
more = true;
}
}
return os.str();
}

74
BBGE/ScriptObject.h Normal file
View file

@ -0,0 +1,74 @@
/*
Copyright (C) 2007, 2012 - Bit-Blot
This file is part of Aquaria.
Aquaria 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#pragma once
enum ScriptObjectType
{
SCO_NONE = 0x0000,
// If you change this enum, do not forget to adjust the string array in the cpp,
// and to add additional compile time assertions to ScriptInterface.cpp as necessary!
SCO_ENTITY = 0x0001,
SCO_INGREDIENT = 0x0002,
SCO_COLLIDE_ENTITY = 0x0004,
SCO_SCRIPTED_ENTITY = 0x0008,
SCO_BEAM = 0x0010,
SCO_SHOT = 0x0020,
SCO_WEB = 0x0040,
SCO_BONE = 0x0080,
SCO_PATH = 0x0100,
SCO_PAUSEQUAD = 0x0200,
SCO_FORCE_32BIT = 0xFFFFFFFF
};
class ScriptObject
{
public:
ScriptObject()
: _objtype(SCO_NONE)
{
}
virtual ~ScriptObject() {}
inline void addType(ScriptObjectType ty)
{
_objtype = ScriptObjectType(int(ty) | int(_objtype)); // prevent the compiler from crying
}
inline bool isType(ScriptObjectType bt) const
{
return (_objtype & bt) != 0;
}
inline std::string getTypeString() const
{
return getTypeString(_objtype);
}
static std::string getTypeString(unsigned int ty);
// public to allow the static compile check in ScriptInterface.cpp to work
ScriptObjectType _objtype;
};

View file

@ -42,6 +42,7 @@ void SkeletalKeyframe::copyAllButTime(SkeletalKeyframe *copy)
Bone::Bone() : Quad()
{
addType(SCO_BONE);
fileRenderQuad = true;
skeleton = 0;
generateCollisionMask = true;

View file

@ -37,7 +37,7 @@ enum AnimationCommand
class ParticleEffect;
class SkeletalSprite;
class Bone : public Quad
class Bone : public ScriptObject, public Quad
{
public:
Bone();

View file

@ -362,6 +362,7 @@ SET(BBGE_SRCS
${BBGEDIR}/Resource.cpp
${BBGEDIR}/RoundedRect.cpp
${BBGEDIR}/ScreenTransition.cpp
${BBGEDIR}/ScriptObject.cpp
${BBGEDIR}/Shader.cpp
${BBGEDIR}/SkeletalSprite.cpp
${BBGEDIR}/Slider.cpp