1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-12-01 15:35:47 +00:00
Aquaria/Aquaria/ScriptedEntity.cpp
2017-02-05 21:08:01 +01:00

795 lines
16 KiB
C++

/*
Copyright (C) 2007, 2010 - 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 "ScriptedEntity.h"
#include "DSQ.h"
#include "Game.h"
#include "Avatar.h"
#include "Shot.h"
#include "ScriptInterface.h"
bool ScriptedEntity::runningActivation = false;
ScriptedEntity::ScriptedEntity(const std::string &scriptName, Vector position, EntityType et) : CollideEntity(), Segmented(2, 26)
{
addType(SCO_SCRIPTED_ENTITY);
crushDelay = 0;
script = 0;
songNoteFunction = songNoteDoneFunction = true;
addChild(&pullEmitter, PM_STATIC);
hair = 0;
becomeSolidDelay = false;
strandSpacing = 10;
animKeyFunc = true;
canShotHitFunc = true;
setEntityType(et);
layer = LR_ENTITIES;
surfaceMoveDir = 1;
this->position = position;
numSegments = 0;
reverseSegments = false;
manaBallAmount = 1;
this->name = scriptName;
std::string file;
if (!scriptName.empty())
{
if (scriptName[0]=='@' && dsq->mod.isActive())
{
file = dsq->mod.getPath() + "scripts/" + scriptName.substr(1, scriptName.size()) + ".lua";
this->name = scriptName.substr(1, scriptName.size());
}
else if (dsq->mod.isActive())
{
file = dsq->mod.getPath() + "scripts/" + scriptName + ".lua";
if (!exists(file))
{
file = "scripts/entities/" + scriptName + ".lua";
}
}
else
{
file = "scripts/entities/" + scriptName + ".lua";
}
}
script = dsq->scriptInterface.openScript(file);
if (!script)
{
debugLog("Could not load script [" + file + "]");
}
}
ScriptedEntity::~ScriptedEntity()
{
}
void ScriptedEntity::setAutoSkeletalUpdate(bool v)
{
skeletalSprite.ignoreUpdate = !v;
}
void ScriptedEntity::message(const std::string &msg, int v)
{
if (script)
{
if (!script->call("msg", this, msg.c_str(), v))
debugLog(name + " : msg : " + script->getLastError());
}
Entity::message(msg, v);
}
int ScriptedEntity::messageVariadic(lua_State *L, int nparams)
{
if (script)
{
int res = script->callVariadic("msg", L, nparams, this);
if (res < 0)
luaDebugMsg("msg", script->getLastError());
else
return res;
}
return Entity::messageVariadic(L, nparams);
}
int ScriptedEntity::pushLuaVars(lua_State *L)
{
return script ? script->pushLocalVars(L) : 0;
}
void ScriptedEntity::warpSegments()
{
Segmented::warpSegments(position);
}
void ScriptedEntity::init()
{
if (script)
{
if (!script->call("init", this))
luaDebugMsg("init", script->getLastError());
}
Entity::init();
}
void ScriptedEntity::postInit()
{
if (script)
{
if (!script->call("postInit", this))
luaDebugMsg("postInit", script->getLastError());
}
Entity::postInit();
}
void ScriptedEntity::initEmitter(size_t emit, const std::string &file)
{
if (emitters.size() <= emit)
{
emitters.resize(emit+1);
}
if (emitters[emit] != 0)
{
errorLog("Trying to init emitter being used");
return;
}
emitters[emit] = new ParticleEffect;
addChild(emitters[emit], PM_POINTER);
emitters[emit]->load(file);
}
void ScriptedEntity::startEmitter(size_t emit)
{
if(emit >= emitters.size())
return;
if (emitters[emit])
{
emitters[emit]->start();
}
}
void ScriptedEntity::stopEmitter(size_t emit)
{
if(emit >= emitters.size())
return;
if (emitters[emit])
{
emitters[emit]->stop();
}
}
ParticleEffect *ScriptedEntity::getEmitter(size_t emit)
{
return (size_t(emit) < emitters.size()) ? emitters[emit] : NULL;
}
size_t ScriptedEntity::getNumEmitters() const
{
return emitters.size();
}
void ScriptedEntity::registerNewPart(RenderObject *r, const std::string &name)
{
partMap[name] = r;
}
void ScriptedEntity::initSegments(int numSegments, int minDist, int maxDist, std::string bodyTex, std::string tailTex, int w, int h, float taper, bool reverseSegments)
{
this->reverseSegments = reverseSegments;
this->numSegments = numSegments;
this->minDist = minDist;
this->maxDist = maxDist;
segments.resize(numSegments);
for (size_t i = segments.size(); i-- > 0 ; )
{
Quad *q = new Quad;
if (i == segments.size()-1)
q->setTexture(tailTex);
else
q->setTexture(bodyTex);
q->setWidthHeight(w, h);
if (i > 0 && i < segments.size()-1 && taper !=0)
q->scale = Vector(1.0f-(i*taper), 1-(i*taper));
dsq->game->addRenderObject(q, LR_ENTITIES);
segments[i] = q;
}
Segmented::initSegments(position);
}
void ScriptedEntity::setupEntity(const std::string &tex, int lcode)
{
setEntityType(ET_NEUTRAL);
if (!tex.empty())
setTexture(tex);
updateCull = -1;
manaBallAmount = 0;
setState(STATE_IDLE);
this->layer = dsq->getEntityLayerToLayer(lcode);
}
void ScriptedEntity::setupBasicEntity(const std::string& texture, int health, int manaBall, int exp, int money, float collideRadius, int state, int w, int h, int expType, bool hitEntity, int updateCull, int layer)
{
updateCull = -1;
if (texture.empty())
renderQuad = false;
else
setTexture(texture);
this->health = maxHealth = health;
this->collideRadius = collideRadius;
setState(state);
this->manaBallAmount = manaBall;
width = w;
height = h;
setEntityLayer(layer);
}
void ScriptedEntity::setEntityLayer(int lcode)
{
this->layer = dsq->getEntityLayerToLayer(lcode);
}
void ScriptedEntity::initStrands(int num, int segs, int dist, int strandSpacing, Vector color)
{
this->strandSpacing = strandSpacing;
strands.resize(num);
for (size_t i = 0; i < strands.size(); i++)
{
strands[i] = new Strand(position, segs, dist);
strands[i]->color = color;
dsq->game->addRenderObject(strands[i], this->layer);
}
updateStrands(0);
}
void ScriptedEntity::onAlwaysUpdate(float dt)
{
Entity::onAlwaysUpdate(dt);
updateStrands(dt);
if (!isEntityDead() && getState() != STATE_DEAD && getState() != STATE_DEATHSCENE && isPresent())
{
if (frozenTimer > 0)
{
pullEmitter.update(dt);
doFriction(dt, 50);
updateCurrents(dt);
updateMovement(dt);
if (hair)
{
setHairHeadPosition(position);
updateHair(dt);
}
if (skeletalSprite.isLoaded())
dsq->game->handleShotCollisionsSkeletal(this);
else
dsq->game->handleShotCollisions(this);
}
if (isPullable() && !fillGridFromQuad)
{
bool doCrush = false;
crushDelay -= dt;
if (crushDelay < 0)
{
crushDelay = 0.2f;
doCrush = true;
}
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e && e != this && e->life == 1 && e->ridingOnEntity != this)
{
if ((e->position - this->position).isLength2DIn(collideRadius + e->collideRadius))
{
if (this->isEntityProperty(EP_BLOCKER) && doCrush)
{
bool doit = !vel.isLength2DIn(64) || (e->position.y > position.y && vel.y > 0);
if (doit)
{
if (e->getEntityType() == ET_ENEMY && e->isDamageTarget(DT_CRUSH))
{
DamageData d;
d.damageType = DT_CRUSH;
d.attacker = this;
d.damage = 1;
if (e->damage(d))
{
e->sound("RockHit");
dsq->spawnParticleEffect("rockhit", e->position, 0, 0);
}
Vector add = vel;
add.setLength2D(5000*dt);
e->vel += add;
}
}
}
Vector add = e->position - this->position;
add.capLength2D(10000 * dt);
e->vel += add;
e->doCollisionAvoidance(dt, 3, 1);
}
}
}
}
if (isPullable())
{
Entity *followEntity = dsq->game->avatar;
if (followEntity && dsq->game->avatar->pullTarget == this)
{
Vector dist = followEntity->position - this->position;
if (dist.isLength2DIn(followEntity->collideRadius + collideRadius + 16))
{
vel = 0;
}
else if (!dist.isLength2DIn(800))
{
vel.setZero();
dsq->game->avatar->pullTarget->stopPull();
dsq->game->avatar->pullTarget = 0;
}
else if (!dist.isLength2DIn(128))
{
Vector v = dist;
int moveSpeed = 1000;
moveSpeed = 4000;
v.setLength2D(moveSpeed);
vel += v*dt;
setMaxSpeed(dsq->game->avatar->getMaxSpeed());
}
else
{
if (!vel.isZero())
{
Vector sub = vel;
sub.setLength2D(getMaxSpeed()*maxSpeedLerp.x*dt);
vel -= sub;
if (vel.isLength2DIn(100))
vel = 0;
}
}
doCollisionAvoidance(dt, 2, 0.5);
}
}
}
}
void ScriptedEntity::updateStrands(float dt)
{
if (strands.empty()) return;
float angle = rotation.z;
angle = (PI*(360-(angle-90)))/180.0;
float sz = (strands.size()/2);
for (size_t i = 0; i < strands.size(); i++)
{
float diff = (i-sz)*strandSpacing;
if (diff < 0)
strands[i]->position = position - Vector(sinf(angle)*fabsf(diff), cosf(angle)*fabsf(diff));
else
strands[i]->position = position + Vector(sinf(angle)*diff, cosf(angle)*diff);
if (dt > 0)
strands[i]->update(dt);
}
}
void ScriptedEntity::destroy()
{
CollideEntity::destroy();
if (script)
{
dsq->scriptInterface.closeScript(script);
script = 0;
}
}
void ScriptedEntity::song(SongType songType)
{
if (script)
{
if (!script->call("song", this, int(songType)))
debugLog(name + " : " + script->getLastError());
}
}
void ScriptedEntity::shiftWorlds(WorldType lastWorld, WorldType worldType)
{
if (script)
{
if (!script->call("shiftWorlds", this, int(lastWorld), int(worldType)))
debugLog(name + " : " + script->getLastError() + " shiftWorlds");
}
}
void ScriptedEntity::startPull()
{
Entity::startPull();
beforePullMaxSpeed = getMaxSpeed();
becomeSolidDelay = false;
debugLog("HERE!");
if (isEntityProperty(EP_BLOCKER))
{
fillGridFromQuad = false;
dsq->game->reconstructEntityGrid();
}
pullEmitter.load("Pulled");
pullEmitter.start();
// HACK: move this to the lower level at some point
if (isEntityProperty(EP_BLOCKER))
{
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e != this && e->getEntityType() != ET_AVATAR && e->isv(EV_CRAWLING, 1))
{
if ((e->position - position).isLength2DIn(collideRadius+e->collideRadius+32))
{
debugLog(e->name + ": is now riding on : " + name);
e->ridingOnEntity = this;
e->ridingOnEntityOffset = e->position - position;
e->ridingOnEntityOffset.setLength2D(collideRadius);
}
}
}
}
}
void ScriptedEntity::sporesDropped(const Vector &pos, int type)
{
if (script)
{
script->call("sporesDropped", this, pos.x, pos.y, type);
}
}
void ScriptedEntity::stopPull()
{
Entity::stopPull();
pullEmitter.stop();
setMaxSpeed(beforePullMaxSpeed);
}
void ScriptedEntity::onUpdate(float dt)
{
BBGE_PROF(ScriptedEntity_onUpdate);
CollideEntity::onUpdate(dt);
if (becomeSolidDelay)
{
if (vel.isLength2DIn(5))
{
if (!isEntityInside())
{
becomeSolid();
becomeSolidDelay = false;
}
}
}
if (life != 1 || isEntityDead()) return;
if (this->isEntityDead() || this->getState() == STATE_DEATHSCENE || this->getState() == STATE_DEAD)
{
return;
}
if (script)
{
if (!script->call("update", this, dt))
debugLog(name + " : update : " + script->getLastError());
}
if (numSegments > 0)
{
updateSegments(position, reverseSegments);
updateAlpha(alpha.x);
}
}
void ScriptedEntity::onAnimationKeyPassed(int key)
{
if (script && animKeyFunc)
{
if (!script->call("animationKey", this, key))
{
debugLog(this->name + " : " + script->getLastError() + " animationKey");
animKeyFunc = false;
}
}
Entity::onAnimationKeyPassed(key);
}
void ScriptedEntity::lightFlare()
{
if (script && !isEntityDead())
{
script->call("lightFlare", this);
}
}
bool ScriptedEntity::canShotHit(const DamageData &d)
{
bool doDefault = true;
if (script && canShotHitFunc)
{
if (!script->call("canShotHit", this, d.attacker, d.bone, int(d.damageType), d.damage, d.hitPos.x, d.hitPos.y, d.shot, &doDefault))
{
debugLog(name + ": canShotHit function failed");
canShotHitFunc = false;
}
}
if (doDefault)
{
return Entity::canShotHit(d);
}
return false;
}
bool ScriptedEntity::damage(const DamageData &d)
{
if (d.damageType == DT_NONE) return false;
bool doDefault = true;
if (script)
{
if (!script->call("damage", this, d.attacker, d.bone, int(d.damageType), d.damage, d.hitPos.x, d.hitPos.y, d.shot, &doDefault))
{
debugLog(name + ": damage function failed");
}
}
if (doDefault)
{
return Entity::damage(d);
}
return false;
}
void ScriptedEntity::songNote(int note)
{
Entity::songNote(note);
if (script && songNoteFunction)
{
if (!script->call("songNote", this, note))
{
songNoteFunction = false;
debugLog(this->name + " : " + script->getLastError() + " songNote");
}
}
}
void ScriptedEntity::songNoteDone(int note, float len)
{
Entity::songNoteDone(note, len);
if (script && songNoteDoneFunction)
{
if (!script->call("songNoteDone", this, note, len))
{
songNoteDoneFunction = false;
debugLog(this->name + " : " + script->getLastError() + " songNoteDone");
}
}
}
void ScriptedEntity::becomeSolid()
{
float oldRot = 0;
bool doRot=false;
Vector n = dsq->game->getWallNormal(position);
if (!n.isZero())
{
oldRot = rotation.z;
rotateToVec(n, 0);
doRot = true;
}
fillGridFromQuad = true;
dsq->game->reconstructEntityGrid();
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e->ridingOnEntity == this)
{
e->ridingOnEntity = 0;
e->moveOutOfWall();
// if can't get the rider out of the wall, kill it
if (dsq->game->isObstructed(TileVector(e->position)))
{
e->setState(STATE_DEAD);
}
}
}
if (doRot)
{
rotation.z = oldRot;
rotateToVec(n, 0.01f);
}
}
void ScriptedEntity::onHitWall()
{
if (isEntityProperty(EP_BLOCKER) && !fillGridFromQuad && dsq->game->avatar->pullTarget != this)
{
becomeSolidDelay = true;
}
if (isEntityProperty(EP_BLOCKER) && !fillGridFromQuad)
{
Vector n = dsq->game->getWallNormal(position);
if (!n.isZero())
{
rotateToVec(n, 0.2f);
}
}
CollideEntity::onHitWall();
if (script)
{
if (!script->call("hitSurface", this))
debugLog(this->name + " : " + script->getLastError() + " hitSurface");
}
}
void ScriptedEntity::activate()
{
if (runningActivation) return;
Entity::activate();
runningActivation = true;
if (script)
{
if (!script->call("activate", this))
luaDebugMsg("activate", script->getLastError());
}
runningActivation = false;
}
void ScriptedEntity::shotHitEntity(Entity *hit, Shot *shot, Bone *bone)
{
Entity::shotHitEntity(hit, shot, bone);
if (script)
{
script->call("shotHitEntity", this, hit, shot, bone);
}
}
void ScriptedEntity::entityDied(Entity *e)
{
CollideEntity::entityDied(e);
if (script)
{
script->call("entityDied", this, e);
}
}
void ScriptedEntity::luaDebugMsg(const std::string &func, const std::string &msg)
{
debugLog("luaScriptError: " + name + " : " + func + " : " + msg);
}
void ScriptedEntity::onDieNormal()
{
Entity::onDieNormal();
if (script)
{
script->call("dieNormal", this);
}
}
void ScriptedEntity::onDieEaten()
{
Entity::onDieEaten();
if (script)
{
script->call("dieEaten", this);
}
}
void ScriptedEntity::onEnterState(int action)
{
CollideEntity::onEnterState(action);
if (script)
{
if (!script->call("enterState", this))
luaDebugMsg("enterState", script->getLastError());
}
switch(action)
{
case STATE_DEAD:
if (!isGoingToBeEaten())
{
doDeathEffects(manaBallAmount);
dsq->spawnParticleEffect(deathParticleEffect, position);
onDieNormal();
}
else
{
// eaten
doDeathEffects(0);
onDieEaten();
}
destroySegments(1);
for (size_t i = 0; i < strands.size(); i++)
{
strands[i]->safeKill();
}
strands.clear();
// BASE ENTITY CLASS WILL HANDLE CLEANING UP HAIR
break;
}
}
void ScriptedEntity::onExitState(int action)
{
if (script)
{
if (!script->call("exitState", this))
luaDebugMsg("exitState", script->getLastError());
}
CollideEntity::onExitState(action);
}
void ScriptedEntity::deathNotify(RenderObject *r)
{
if (script)
{
if (!script->call("deathNotify", this, r))
luaDebugMsg("deathNotify", script->getLastError());
}
CollideEntity::deathNotify(r);
}