1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2025-02-28 08:59:53 +00:00
Aquaria/Aquaria/Entity.cpp
2024-10-17 06:00:51 +02:00

2604 lines
47 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 "../BBGE/MathFunctions.h"
#include "Entity.h"
#include "DSQ.h"
#include "Game.h"
#include "Avatar.h"
#include "ScriptedEntity.h"
#include "Shot.h"
#include "PathFinding.h"
#include "Hair.h"
LanceData::LanceData()
: delay(0), timer(0), gfx(NULL), bone(NULL)
{
}
LanceData::~LanceData()
{
if(gfx)
{
gfx->setLife(1.0);
gfx->setDecayRate(2);
gfx->fadeAlphaWithLife = 1;
}
}
void Entity::stopPull()
{
if (game->avatar->pullTarget == this)
{
game->avatar->pullTarget = 0;
}
}
void Entity::setIngredientData(const std::string &name)
{
ingredientData = dsq->continuity.getIngredientDataByName(name);
}
void Entity::entityDied(Entity *e)
{
for (size_t i = 0; i < targets.size(); i++)
if(targets[i] == e)
targets[i] = NULL;
if (boneLock.on && boneLock.entity == e)
{
setBoneLock(BoneLock(), true);
}
if(riding == e)
riding = NULL;
if(ridingOnEntity == e)
ridingOnEntity = NULL;
}
void Entity::setBounceType(BounceType bt)
{
bounceType = bt;
}
BounceType Entity::getBounceType()
{
return bounceType;
}
void Entity::generateCollisionMask(float ovrCollideRadius)
{
if (this->skeletalSprite.isLoaded())
{
for (size_t i = 0; i < skeletalSprite.bones.size(); i++)
{
if (skeletalSprite.bones[i]->generateCollisionMask)
{
game->generateCollisionMask(skeletalSprite.bones[i], ovrCollideRadius);
}
}
}
}
bool Entity::setBoneLock(const BoneLock &bl, bool force)
{
if(!force)
{
if (bl.on && boneLockDelay > 0) return false;
if (boneLock.on && bl.on) return false;
}
if (boneLock.on && !bl.on)
{
boneLockDelay = 0.1f;
boneLock = bl;
}
else
{
if (!bl.entity)
return false;
boneLock = bl;
Quad *lockObj = bl.bone;
if(!lockObj)
lockObj = bl.entity;
Vector posAndRot = lockObj->getWorldPositionAndRotation();
boneLock.origRot = posAndRot.z;
posAndRot.z = 0;
Vector offs = position - posAndRot;
boneLock.circleOffset = offs;
}
setv(EV_BONELOCKED, bl.on);
onSetBoneLock();
updateBoneLock();
return true;
}
Entity::Entity()
{
addType(SCO_ENTITY);
poison = 0.0f;
calledEntityDied = false;
wasUnderWater = true;
waterBubble = 0;
ridingFlip = false;
ridingRotation = 0;
boneLockDelay = 0;
ingredientData = 0;
for (int i = 0; i < EV_MAX; i++)
{
vs[i] = 0;
}
setv(EV_COLLIDELEVEL, 1);
setv(EV_LOOKAT, 1);
setv(EV_SWITCHCLAMP, 1);
setvf(EV_CLAMPTRANSF, 0.2f);
setv(EV_FLIPTOPATH, 1);
setv(EV_NOINPUTNOVEL, 1);
setv(EV_VINEPUSH, 1);
setv(EV_BEASTBURST, 1);
setv(EV_WEBSLOW, 100);
invincible = false;
lancedata = NULL;
beautyFlip = true;
fhScale = false;
flipScale = Vector(1,1);
wasUnderWater = true;
deathScene = false;
dieTimer = 0;
bounceType = BOUNCE_SIMPLE;
riding = 0;
eatType = EAT_DEFAULT;
stickToNaijasHead = false;
spiritFreeze = true;
pauseFreeze = true;
canLeaveWater = false;
targetPriority = 0;
ridingOnEntity = 0;
targetRange = 32;
deathSound = "GenericDeath";
entityID = 0;
hair = 0;
maxSpeedLerp = 1;
fillGridFromQuad = false;
fillGridFromSkel = false;
dropChance = 0;
inCurrent = false;
for (size_t i = 0; i < EP_MAX; i++)
{
entityProperties[i] = false;
}
damageTime = vars->entityDamageTime;
slowingToStopPathTimer = 0;
slowingToStopPath = 0;
swimPath = false;
currentEntityTarget = 0;
deleteOnPathEnd = false;
multColor = Vector(1,1,1);
collideRadius = 24;
entityType = EntityType(0);
frozenTimer = 0;
canBeTargetedByAvatar = false;
activationRange = 0;
activationType = ACT_NONE;
pushDamage = 0;
dsq->addEntity(this);
maxSpeed = 300;
entityDead = false;
health = maxHealth = 5;
invincibleBreak = false;
activationRadius = 40;
activationRange = 600;
bubble = 0;
skeletalSprite.setAnimationKeyNotify(this);
addChild(&skeletalSprite, PM_NONE);
setDamageTarget(DT_AVATAR_NATURE, false);
setDamageTarget(DT_AVATAR_LIZAP, true);
setDamageTarget(DT_AVATAR_BUBBLE, false);
setDamageTarget(DT_AVATAR_SEED, false);
stopSoundsOnDeath = false;
minimapIcon = 0;
renderPass = RENDER_ALL;
}
Entity::~Entity()
{
delete minimapIcon;
delete lancedata;
}
void Entity::setDeathScene(bool v)
{
deathScene = v;
}
void Entity::setCanLeaveWater(bool v)
{
canLeaveWater = v;
}
bool Entity::checkSplash(const Vector &o)
{
Path *lastWaterBubble = waterBubble;
Vector check = position;
if (!o.isZero())
check = o;
bool changed = false;
bool uw = isUnderWater(o);
if (wasUnderWater && !uw)
{
sound("splash-outof");
changed = true;
wasUnderWater = false;
}
else if (!wasUnderWater && uw)
{
sound("splash-into");
changed = true;
wasUnderWater = true;
}
if (changed)
{
float angle;
if (!wasUnderWater && waterBubble)
{
Vector diff = position - waterBubble->nodes[0].position;
angle = MathFunctions::getAngleToVector(diff, 0) + 180;
}
else if (wasUnderWater && lastWaterBubble)
{
Vector diff = position - lastWaterBubble->nodes[0].position;
angle = MathFunctions::getAngleToVector(diff, 0);
}
else
{
angle = MathFunctions::getAngleToVector(vel+vel2, 0);
}
dsq->spawnParticleEffect("Splash", check, angle);
}
return changed;
}
void Entity::setSpiritFreeze(bool v)
{
spiritFreeze = v;
}
void Entity::setPauseFreeze(bool v)
{
pauseFreeze = v;
}
void Entity::setEntityProperty(EntityProperty ep, bool value)
{
entityProperties[ep] = value;
}
bool Entity::isEntityProperty(EntityProperty ep) const
{
return entityProperties[ep];
}
Vector Entity::getRidingPosition()
{
if (ridingPosition.isZero())
return position;
return ridingPosition;
}
float Entity::getRidingRotation()
{
if (ridingRotation == 0)
return rotation.z;
return ridingRotation;
}
void Entity::setRidingFlip(bool on)
{
ridingFlip = on;
}
bool Entity::getRidingFlip()
{
return ridingFlip;
}
void Entity::setRidingData(const Vector &pos, float rot, bool fh)
{
setRidingPosition(pos);
setRidingRotation(rot);
setRidingFlip(fh);
}
void Entity::setRidingPosition(const Vector &pos)
{
ridingPosition = pos;
}
void Entity::setRidingRotation(float rot)
{
ridingRotation = rot;
}
void Entity::doFriction(float dt)
{
if (vel.getSquaredLength2D() > 0)
{
const float velStopLen = 10;
bool wasIn = vel.isLength2DIn(velStopLen);
Vector d = vel;
d.setLength2D(vars->frictionForce);
vel -= d * dt;
if (!wasIn && vel.isLength2DIn(velStopLen))
vel = 0;
}
}
void Entity::doFriction(float dt, float len)
{
Vector v = vel;
if (!v.isZero())
{
v.setLength2D(dt * len);
vel -= v;
}
}
void Entity::setName(const std::string &name)
{
this->name = name;
}
float Entity::followPath(Path *p, float speed, int dir, bool deleteOnEnd)
{
if(!speed)
speed = getMaxSpeed();
deleteOnPathEnd = deleteOnEnd;
position.stopPath();
position.ensureData();
position.data->path.clear();
if (dir)
{
for (int i = p->nodes.size()-1; i >=0; i--)
{
PathNode pn = p->nodes[i];
position.data->path.addPathNode(pn.position, 1.0f-(float(i/float(p->nodes.size()))));
}
}
else
{
for (size_t i = 0; i < p->nodes.size(); i++)
{
PathNode pn = p->nodes[i];
position.data->path.addPathNode(pn.position, float(i/float(p->nodes.size())));
}
}
//debugLog("Calculating Time");
float time = position.data->path.getLength()/speed;
position.data->path.getPathNode(0)->value = position;
position.startPath(time);
return time;
}
float Entity::moveToPos(Vector dest, float speed, int dieOnPathEnd, bool swim)
{
if(!speed)
speed = getMaxSpeed();
Vector start = position;
position.ensureData();
position.data->path.clear();
position.stop();
swimPath = swim;
PathFinding::generatePath(this, TileVector(start), TileVector(dest));
//debugLog("Regenerating section");
this->vel = 0;
//debugLog("Molesting Path");
PathFinding::molestPath(position.data->path);
//debugLog("forcing path to minimum 2 nodes");
PathFinding::forceMinimumPath(position.data->path, start, dest);
float time = position.data->path.getLength()/speed;
position.data->path.getPathNode(0)->value = position;
position.startPath(time);
deleteOnPathEnd = dieOnPathEnd;
return time;
}
void Entity::stopFollowingPath()
{
position.stopPath();
}
void Entity::flipToPos(Vector pos)
{
if (pos.x > position.x)
{
if (!isfh())
flipHorizontal();
}
else if (pos.x < position.x)
{
if (isfh())
flipHorizontal();
}
}
Entity* Entity::getTargetEntity(size_t t) const
{
return t < targets.size() ? targets[t] : NULL;
}
void Entity::setTargetEntity(Entity *e, size_t t)
{
if(t < targets.size())
{
targets[t] = e;
return;
}
if(!e)
return;
if(targets.size() <= t)
targets.resize(t + 1);
targets[t] = e;
}
void Entity::destroy()
{
if (stopSoundsOnDeath)
this->stopAllSounds();
this->unlinkAllSounds();
if (hair)
{
hair = 0;
}
Shot::targetDied(this);
dsq->removeEntity(this);
Quad::destroy();
}
bool Entity::checkSurface(int tcheck, int state, float statet)
{
TileVector hitTile;
if (isNearObstruction(tcheck, OBSCHECK_8DIR, &hitTile))
{
if (clampToSurface(tcheck, Vector(0,0), hitTile))
{
setState(state, statet);
return true;
}
}
return false;
}
void Entity::rotateToSurfaceNormal(float t, int n, int rot)
{
Vector v;
if (ridingOnEntity)
{
v = position - ridingOnEntity->position;
}
else
{
if (n == 0)
v = game->getWallNormal(position);
else
v = game->getWallNormal(position, n);
}
if (!v.isZero())
{
rotateToVec(v, t, rot);
}
}
int Entity::getv(EV ev)
{
return vs[ev];
}
float Entity::getvf(EV ev)
{
return float(vs[ev])*0.01f;
}
bool Entity::isv(EV ev, int v)
{
return vs[ev] == v;
}
void Entity::setv(EV ev, int v)
{
vs[ev] = v;
}
void Entity::setvf(EV ev, float v)
{
vs[ev] = int(v * 100.0f);
}
bool Entity::clampToSurface(int tcheck, Vector usePos, TileVector hitTile)
{
float t = getvf(EV_CLAMPTRANSF);
if (usePos.isZero())
usePos = position;
if (tcheck == 0)
tcheck = 40;
bool clamped = false;
// HACK: ensure entity gets to location
setv(EV_CRAWLING, 1);
burstTimer.stop();
// do stuff
Vector pos = TileVector(usePos).worldVector();
if (!hitTile.isZero())
{
pos = hitTile.worldVector();
clamped = true;
}
else
{
if (vel.getSquaredLength2D() < 1)
{
longCheck:
for (int i = 0; i < tcheck; i++)
{
int bit = i*TILE_SIZE;
int backBit = (i-1)*TILE_SIZE;
if (game->isObstructed(TileVector(pos - Vector(bit,0))))
{
TileVector t(pos - Vector(backBit,0));
pos = t.worldVector();
clamped = true;
break;
}
if (game->isObstructed(TileVector(pos - Vector(0,bit))))
{
TileVector t(pos - Vector(0,backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (game->isObstructed(TileVector(pos + Vector(bit,0))))
{
TileVector t(pos + Vector(backBit,0));
pos = t.worldVector();
clamped = true;
break;
}
if (game->isObstructed(TileVector(pos + Vector(0,bit))))
{
TileVector t(pos + Vector(0,backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (game->isObstructed(TileVector(pos + Vector(-bit,-bit))))
{
TileVector t(pos + Vector(-backBit,-backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (game->isObstructed(TileVector(pos + Vector(-bit,bit))))
{
TileVector t(pos + Vector(-backBit,backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (game->isObstructed(TileVector(pos + Vector(bit,-bit))))
{
TileVector t(pos + Vector(backBit,-backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (game->isObstructed(TileVector(pos + Vector(bit,bit))))
{
TileVector t(pos + Vector(backBit,backBit));
pos = t.worldVector();
clamped = true;
break;
}
}
}
else
{
Vector v = vel;
v.normalize2D();
for (int i = 0; i < tcheck; i++)
{
if (game->isObstructed(TileVector(pos + v*TILE_SIZE*i)))
{
TileVector t(pos + v*TILE_SIZE*(i-1));
pos = t.worldVector();
clamped = true;
break;
}
}
if (!clamped)
goto longCheck;
}
}
if (t > 0)
{
Vector n = game->getWallNormal(pos);
n *= getv(EV_WALLOUT);
Vector diff = getWorldPosition() - pos;
offset = diff;
offset.interpolateTo(n, t);
position = pos;
rotateToSurfaceNormal(0);
setv(EV_CLAMPING, 1);
}
else
position = pos;
if (clamped)
{
vel = Vector(0,0,0);
vel2 = Vector(0,0,0);
}
return clamped;
}
void Entity::heal(float a, int type)
{
if (!entityDead)
{
health += a;
if (health > maxHealth) health = maxHealth;
onHealthChange(a);
onHeal(type);
}
}
void Entity::revive(float a)
{
entityDead = false;
health = 0;
heal(a);
if (getState() != STATE_IDLE)
perform(STATE_IDLE);
onHealthChange(a);
}
bool Entity::isGoingToBeEaten()
{
return (eatType != EAT_NONE && (lastDamage.damageType == DT_AVATAR_BITE || lastDamage.damageType == DT_AVATAR_PETBITE));
}
void Entity::doDeathEffects(float manaBallEnergy, bool die)
{
if (deathScene || !isGoingToBeEaten())
{
if (manaBallEnergy)
{
if (chance(dropChance))
{
game->spawnManaBall(position, manaBallEnergy);
}
}
if (die)
{
setLife(1);
setDecayRate(4);
fadeAlphaWithLife = true;
}
else
{
alpha.interpolateTo(0.01f,1);
}
}
else
{
if (die)
{
setLife(1);
setDecayRate(1);
fadeAlphaWithLife = true;
}
else
{
alpha.interpolateTo(0.01f,1);
}
scale.interpolateTo(Vector(0,0), 1);
stickToNaijasHead = true;
}
activationType = ACT_NONE;
if (ingredientData)
{
game->spawnIngredientFromEntity(this, ingredientData);
}
}
bool Entity::isNearObstruction(int sz, int type, TileVector *hitTile)
{
bool v=false;
TileVector t(position);
TileVector test;
switch(type)
{
case OBSCHECK_RANGE:
{
for (int x = -sz; x <= sz; x++)
{
for (int y = -sz; y <= sz; y++)
{
test = TileVector(t.x+x, t.y+y);
if (game->isObstructed(test))
{
v = true;
break;
}
}
}
}
break;
case OBSCHECK_4DIR:
{
for (int x = -sz; x <= sz; x++)
{
test = TileVector(t.x+x, t.y);
if (game->isObstructed(test))
{
v = true;
break;
}
}
for (int y = -sz; y <= sz; y++)
{
test = TileVector(t.x, t.y+y);
if (game->isObstructed(test))
{
v = true;
break;
}
}
}
break;
case OBSCHECK_DOWN:
{
for (int y = 0; y <= sz; y++)
{
test = TileVector(t.x, t.y+y);
if (game->isObstructed(test))
{
v = true;
break;
}
}
}
break;
case OBSCHECK_8DIR:
{
for (int d = 0; d <= sz; d++)
{
test = TileVector(t.x+d, t.y);
if (game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x-d, t.y);
if (game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x, t.y+d);
if (game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x, t.y-d);
if (game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x-d, t.y-d);
if (game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x-d, t.y+d);
if (game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x+d, t.y-d);
if (game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x+d, t.y+d);
if (game->isObstructed(test))
{
v = true;
break;
}
}
}
break;
}
if (hitTile)
*hitTile = test;
return v;
}
bool Entity::touchAvatarDamage(int radius, float dmg, const Vector &override, float speed, float pushTime, Vector collidePos)
{
if (isv(EV_BEASTBURST, 1) && isDamageTarget(DT_AVATAR_BITE) && dsq->continuity.form == FORM_BEAST && game->avatar->bursting)
{
return false;
}
// this whole function has to be rewritten
Vector usePosition = position;
if (override.x != -1)
{
usePosition = override;
}
if (!collidePos.isZero())
{
usePosition = getWorldCollidePosition(collidePos);
}
if (radius == 0 || (game->avatar->getWorldPosition() - usePosition).getSquaredLength2D() < sqr(radius+game->avatar->collideRadius))
{
if (dmg > 0)
{
DamageData d;
d.damage = dmg;
d.attacker = this;
game->avatar->damage(d);
}
if (pushTime > 0 && speed > 0)
{
Vector diff = game->avatar->position - position;
diff.setLength2D(speed);
game->avatar->push(diff, pushTime, speed, dmg);
}
else if (speed > 0)
{
game->avatar->fallOffWall();
Vector diff = game->avatar->position - position;
diff.setLength2D(speed);
game->avatar->vel += diff;
}
return true;
}
return false;
}
const float sct = 0.15f;
void Entity::onFHScale()
{
flipScale.interpolateTo(Vector(1, 1), sct);
_fh = !_fh;
fhScale = false;
}
void Entity::onFH()
{
if (!beautyFlip) return;
_fh = !_fh;
if (!fhScale)
{
flipScale = Vector(1,1);
flipScale.interpolateTo(Vector(0.6f, 1), sct);
fhScale = true;
}
}
void Entity::flipToVel()
{
if ((vel.x < -5 && isfh()) || (vel.x > 5 && !isfh()))
flipHorizontal();
}
void Entity::frozenUpdate(float dt)
{
}
void Entity::update(float dt)
{
Vector backupPos = position;
Vector backupVel = vel;
bool doUpdate = (updateCull < 0 || (position - core->screenCenter).isLength2DIn(updateCull));
if (doUpdate && !(pauseFreeze && game->isPaused()))
{
if (!(getEntityType() == ET_AVATAR || getEntityType() == ET_INGREDIENT))
{
if (spiritFreeze && game->isWorldPaused())
{
// possible bug here because of return
return;
//dt *= 0.5f;
}
//if (dsq->continuity.getWorldType() != WT_NORMAL)
// return;
}
//skeletalSprite.setFreeze(true);
if (frozenTimer == 0 || getState() == STATE_PUSH)
Quad::update(dt);
onAlwaysUpdate(dt);
// always, always update:
if (damageTimer.updateCheck(dt))
{
multColor.stop();
multColor = Vector(1,1,1);
}
if (!multColor.isInterpolating())
{
multColor = Vector(1,1,1);
}
vineDamageTimer.update(dt);
if (dieTimer > 0)
{
dieTimer -= dt;
if (dieTimer <0)
{
dieTimer = 0;
setLife(1);
setDecayRate(1);
fadeAlphaWithLife = 1;
}
}
StateMachine::onUpdate(dt);
if (frozenTimer > 0)
{
onUpdateFrozen(dt);
if (bubble)
bubble->position = this->position;
frozenTimer -= dt;
if (frozenTimer <= 0)
{
frozenTimer = 0;
popBubble();
}
}
}
updateBoneLock();
if (position.isNan())
position = backupPos;
if (vel.isNan())
vel = backupVel;
updateSoundPosition();
if(minimapIcon)
minimapIcon->update(dt);
}
void Entity::postUpdate(float dt)
{
updateBoneLock();
}
bool Entity::isAvatarAttackTarget()
{
return getEntityType() == ET_ENEMY && canBeTargetedByAvatar;
}
bool Entity::pathBurst(bool wallJump)
{
if (skeletalSprite.isLoaded() && (wallJump || (!wallJump && !burstTimer.isActive())))
{
game->playBurstSound(wallJump);
skeletalSprite.animate("burst");
position.ensureData();
if (wallJump)
position.data->pathTimeMultiplier = 2;
else
position.data->pathTimeMultiplier = 1.5;
burstTimer.start(1);
return true;
}
return false;
}
bool Entity::isFollowingPath()
{
return position.isFollowingPath();
}
void Entity::onPathEnd()
{
if (deleteOnPathEnd)
{
safeKill();
}
else
{
if (swimPath)
{
offset.interpolateTo(Vector(0, 0), 0.4f);
rotateToVec(Vector(0,-1), 0.1f, 0);
if (skeletalSprite.isLoaded())
{
skeletalSprite.animate("idle", -1);
}
position.ensureData();
int num = position.data->path.getNumPathNodes();
if (num >= 2)
{
Vector v2 = position.data->path.getPathNode(num-1)->value;
Vector v1 = position.data->path.getPathNode(num-2)->value;
Vector v = v2 - v1;
if (isv(EV_FLIPTOPATH, 1))
{
if (v.x > 0)
{
if (!isfh())
flipHorizontal();
}
if (v.x < 0)
{
if (isfh())
flipHorizontal();
}
}
}
}
}
}
void Entity::movementDetails(Vector v)
{
v.normalize2D();
if (isv(EV_FLIPTOPATH, 1))
{
if (v.x < 0)
{
if (isfh())
flipHorizontal();
}
else if (v.x > 0)
{
if (!isfh())
flipHorizontal();
}
}
if (skeletalSprite.isLoaded())
{
if (burstTimer.isActive())
rotateToVec(v, 0.05f);
else
rotateToVec(v, 0.2f);
Animation *anim = skeletalSprite.getCurrentAnimation();
if (!burstTimer.isActive())
{
if (!anim || anim->name != "swim")
skeletalSprite.transitionAnimate("swim", 0.1f, -1);
}
}
}
void Entity::slowToStopPath(float t)
{
slowingToStopPath = t;
std::ostringstream os;
os << "slowingToStopPath: " << slowingToStopPath;
debugLog(os.str());
slowingToStopPathTimer = 0;
}
bool Entity::isSlowingToStopPath()
{
bool v = (slowingToStopPath > 0);
return v;
}
bool Entity::updateCurrents(float dt)
{
inCurrent = false;
int c = 0;
Vector accum;
//if (isUnderWater())
// why?
{
if (!game->isWorldPaused())
{
for (Path *p = game->getFirstPathOfType(PATH_CURRENT); p; p = p->nextOfType)
{
if (p->active)
{
for (size_t n = 1; n < p->nodes.size(); n++)
{
PathNode *node2 = &p->nodes[n];
PathNode *node1 = &p->nodes[n-1];
Vector dir = node2->position - node1->position;
if (isTouchingLine(node1->position, node2->position, position, collideRadius + p->rect.getWidth()/2))
{
inCurrent = true;
dir.setLength2D(p->currentMod);
accum += dir;
c++;
}
}
}
}
}
if (inCurrent)
{
accum /= c;
vel2 = accum;
float len = vel2.getLength2D();
float useLen = len;
if (useLen < 500)
useLen = 500;
if (!(this->getEntityType() == ET_AVATAR && game->avatar->canSwimAgainstCurrents() && game->avatar->bursting))
{
doCollisionAvoidance(1, 4, 1, &vel2, useLen);
}
doCollisionAvoidance(dt, 3, 1, 0, useLen);
Vector dist = -vel2;
dist.normalize2D();
float v = dist.x;
float scale = 0.2f;
if (getEntityType() == ET_AVATAR)
{
Avatar *a = game->avatar;
if (v < 0)
dsq->rumble((-v)*scale, (1.0f+v)*scale, 0.2f, a->getLastActionSourceID(), a->getLastActionInputDevice());
else
dsq->rumble((1.0f-v)*scale, (v)*scale, 0.1f, a->getLastActionSourceID(), a->getLastActionInputDevice());
}
}
}
if (this->getEntityType() == ET_AVATAR && game->avatar->canSwimAgainstCurrents())
{
int cap = 100;
if (!vel.isZero())
{
if (vel.dot2D(vel2) < 0 ) // greater than 90 degrees
{
// against current
if (game->avatar->bursting)
vel2 = 0;
else if (game->avatar->isSwimming())
vel2.capLength2D(cap);
}
}
else
{
}
}
return inCurrent;
}
const float minVel2Len = 10;
void Entity::updateVel2(float dt, bool override)
{
if ((override || !inCurrent) && !vel2.isZero())
{
bool wasUnder = vel.isLength2DIn(minVel2Len);
// PATCH_KEY: vel2 doesn't build up if dt is low
// 1000 * dt
Vector d = vel2;
d.setLength2D(1000*dt);
bool xg = (vel2.x > 0), yg = (vel2.y > 0);
vel2 -= d;
// PATCH_KEY: could this be what breaks it? never gets out of 10?
//|| vel2.isLength2DIn(10)
if ((xg != (vel2.x > 0)) || (yg != (vel2.y >0)) || (!wasUnder && vel2.isLength2DIn(minVel2Len)))
vel2 = 0;
}
}
bool Entity::isSittingOnInvisibleIn()
{
TileVector t(position);
for (int x = 0; x < 4; x++)
{
for (int y = 0; y < 4; y++)
{
if (game->isObstructed(TileVector(t.x+x, t.y+y), OT_INVISIBLEIN))
return true;
if (game->isObstructed(TileVector(t.x-x, t.y+y), OT_INVISIBLEIN))
return true;
if (game->isObstructed(TileVector(t.x+x, t.y-y), OT_INVISIBLEIN))
return true;
if (game->isObstructed(TileVector(t.x-x, t.y-y), OT_INVISIBLEIN))
return true;
}
}
return false;
}
void Entity::moveOutOfWall()
{
Vector n = getNormal();
TileVector t(position);
int c = 0;
bool useValue = true;
while (game->isObstructed(t))
{
c++;
if (c > 6)
{
useValue = false;
break;
}
t.x += n.x;
t.y += n.y;
}
if (useValue)
{
position = t.worldVector();
}
}
void Entity::setDamageTarget(DamageType dt, bool v)
{
DisabledDamageTypes::iterator it = std::lower_bound(disabledDamageTypes.begin(), disabledDamageTypes.end(), dt);
if (v)
{
if(it != disabledDamageTypes.end() && *it == dt)
disabledDamageTypes.erase(it);
}
else
{
if(it == disabledDamageTypes.end())
disabledDamageTypes.push_back(dt);
else if(*it != dt)
disabledDamageTypes.insert(it, dt);
}
}
void Entity::setEatType(EatType et, const std::string &file)
{
eatType = et;
if (eatType == EAT_FILE)
{
EatData *e = dsq->continuity.getEatData(file);
if (e)
eatData = *e;
}
}
void Entity::setAllDamageTargets(bool v)
{
disabledDamageTypes.clear();
if(!v)
{
for (int i = DT_ENEMY; i < DT_ENEMY_REALMAX; i++)
disabledDamageTypes.push_back(DamageType(i));
for (int i = DT_AVATAR; i < DT_AVATAR_REALMAX; i++)
disabledDamageTypes.push_back(DamageType(i));
for (int i = DT_AVATAR_MAX; i < DT_REALMAX; i++)
disabledDamageTypes.push_back(DamageType(i));
}
}
bool Entity::isDamageTarget(DamageType dt)
{
return !std::binary_search(disabledDamageTypes.begin(), disabledDamageTypes.end(), dt);
}
float Entity::getHealthPerc()
{
return float(health) / float(maxHealth);
}
Vector Entity::getMoveVel()
{
return vel + vel2;
}
void Entity::onEndOfLife()
{
if (!calledEntityDied)
{
game->entityDied(this);
calledEntityDied = true;
}
skeletalSprite.safeKill();
}
void Entity::setPoison(float m, float t)
{
poison = m;
poisonTimer.start(t);
if (poison)
poisonBitTimer.start(dsq->continuity.poisonBitTime);
}
void Entity::onUpdate(float dt)
{
if (isv(EV_CLAMPING, 1))
{
if (!offset.isInterpolating())
{
setv(EV_CLAMPING, 0);
}
}
vel2.update(dt);
if (boneLockDelay > 0)
{
boneLockDelay -= dt;
if (boneLockDelay < 0)
boneLockDelay = 0;
}
if (beautyFlip)
{
flipScale.update(dt);
if (fhScale && !flipScale.isInterpolating())
onFHScale();
}
Vector lastPos = position;
if (ridingOnEntity)
{
position = ridingOnEntity->position + ridingOnEntityOffset;
}
if (hair)
{
hair->color.x = color.x * multColor.x;
hair->color.y = color.y * multColor.y;
hair->color.z = color.z * multColor.z;
}
if (slowingToStopPath > 0)
{
slowingToStopPathTimer += dt;
position.ensureData();
if (slowingToStopPathTimer >= slowingToStopPath)
{
position.data->pathTimeMultiplier = 1;
idle();
slowingToStopPath = 0;
slowingToStopPathTimer = 0;
}
else
{
position.data->pathTimeMultiplier = 1.0f - (slowingToStopPathTimer / slowingToStopPath);
}
}
maxSpeedLerp.update(dt);
velocity.z = 0;
vel.z = 0;
bool wasFollowing = false;
if (isFollowingPath())
wasFollowing = true;
if (burstTimer.updateCheck(dt))
{
position.ensureData();
position.data->pathTimeMultiplier = 1;
}
if (poisonTimer.updateCheck(dt))
{
poison = 0;
}
if (currentState != STATE_DEATHSCENE && currentState != STATE_DEAD)
{
if (poison)
{
if (poisonBitTimer.updateCheck(dt))
{
poisonBitTimer.start(dsq->continuity.poisonBitTime);
DamageData d;
d.damageType = DT_ENEMY_ACTIVEPOISON;
d.useTimer = false;
d.damage = 0.5f*poison;
damage(d);
dsq->spawnParticleEffect("PoisonBubbles", position);
}
}
}
Quad::onUpdate(dt);
Vector v = position - lastPos;
if (position.isFollowingPath() && swimPath)
{
movementDetails(v);
}
if (wasFollowing && !isFollowingPath())
{
onPathEnd();
}
multColor.update(dt);
if (bubble)
bubble->position = this->position;
if (getState() == STATE_PUSH)
{
vel = pushVec;
}
if (stickToNaijasHead)
{
position = game->avatar->headPosition;
}
updateLance(dt);
skeletalSprite.update(dt);
}
void Entity::updateBoneLock()
{
if (boneLock.on)
{
Vector lastPosition = position;
const Quad *lockObj = boneLock.bone;
if(!lockObj)
lockObj = boneLock.entity;
Vector posAndRot = lockObj->getWorldPositionAndRotation();
Vector currentOffset = getRotatedVector(boneLock.circleOffset, posAndRot.z - boneLock.origRot);
posAndRot.z = 0;
position = posAndRot + currentOffset;
currentOffset.normalize2D();
boneLock.wallNormal = currentOffset;
rotateToVec(currentOffset, 0.01f);
if (game->collideCircleWithGrid(position, collideRadius))
{
position = lastPosition;
setBoneLock(BoneLock());
return;
}
onUpdateBoneLock();
}
}
std::string Entity::getIdleAnimName()
{
return "idle";
}
void Entity::idle()
{
if (isFollowingPath())
stopFollowingPath();
position.stopPath();
perform(Entity::STATE_IDLE);
skeletalSprite.stopAllAnimations();
onIdle();
skeletalSprite.transitionAnimate(getIdleAnimName(), 0.3f, -1);
rotateToVec(Vector(0,-1),0.1f);
vel.capLength2D(50);
setRiding(0);
}
void Entity::updateLance(float dt)
{
if(!lancedata)
return;
lancedata->timer -= dt;
if (lancedata->timer < 0)
{
delete lancedata;
lancedata = NULL;
}
else
{
lancedata->gfx->fhTo(_fh);
lancedata->delay += dt;
if (lancedata->delay > 0.1f)
{
lancedata->delay = 0;
game->fireShot("Lance", this, 0, lancedata->gfx->getWorldCollidePosition(Vector(-64, 0)));
}
if (lancedata->bone)
{
Vector pr = lancedata->bone->getWorldPositionAndRotation();
lancedata->gfx->position.x = pr.x;
lancedata->gfx->position.y = pr.y;
lancedata->gfx->rotation = pr.z;
}
else
{
lancedata->gfx->position = getWorldPosition();
lancedata->gfx->rotation = rotation;
}
}
}
void Entity::attachLance()
{
std::ostringstream os;
os << "attaching lance to " << this->name;
debugLog(os.str());
lancedata = new LanceData();
lancedata->timer = 8;
lancedata->bone = skeletalSprite.getBoneByName("Lance");
PauseQuad *q = new PauseQuad();
q = new PauseQuad();
q->setTexture("Particles/Lance");
q->alpha = 0;
q->alpha.interpolateTo(1, 0.5);
game->addRenderObject(q, LR_PARTICLES);
lancedata->gfx = q;
}
void Entity::setRiding(Entity *e)
{
riding = e;
}
Entity* Entity::getRiding()
{
return riding;
}
void Entity::rotateToVec(Vector addVec, float time, float offsetAngle)
{
// HACK: this mucks up wall normals for some reason
// if (vel.getSquaredLength2D() <= 0) return;
if (addVec.x == 0 && addVec.y == 0)
{
rotation.interpolateTo(Vector(0,0,0), time, 0);
}
else
{
float angle = MathFunctions::getAngleToVector(addVec, offsetAngle);
if (rotation.z <= -90 && angle >= 90)
{
rotation.z = 360 + rotation.z;
}
if (rotation.z >= 90 && angle <= -90)
rotation.z = rotation.z - 360;
if (time == 0)
rotation = Vector(0,0,angle);
else
rotation.interpolateTo(Vector(0,0,angle), time, 0);
}
}
void Entity::clearTargetPoints()
{
targetPoints.clear();
}
void Entity::addTargetPoint(const Vector &point)
{
targetPoints.push_back(point);
}
int Entity::getNumTargetPoints()
{
return targetPoints.size();
}
Vector Entity::getTargetPoint(size_t i)
{
if (i >= targetPoints.size())
return getEnergyShotTargetPosition();
return targetPoints[i];
}
int Entity::getRandomTargetPoint()
{
if (targetPoints.empty())
return 0;
return rand()%targetPoints.size();
}
bool Entity::isUnderWater()
{
return _isUnderWaterPos(position);
}
bool Entity::isUnderWater(const Vector& overridePos)
{
return _isUnderWaterPos(overridePos.isZero() ? position : overridePos);
}
bool Entity::_isUnderWaterPos(const Vector& pos)
{
UnderWaterResult res = game->isUnderWater(pos, collideRadius);
waterBubble = res.waterbubble;
return res.uw;
}
void Entity::push(const Vector &vec, float time, float maxSpeed, float dmg)
{
if (!this->isEntityDead())
{
pushDamage = dmg;
if (maxSpeed == 0)
{
maxSpeed = this->maxSpeed;
}
this->pushMaxSpeed = maxSpeed;
setState(STATE_PUSH, time);
pushVec = vec;
pushVec.z = 0;
}
}
void Entity::setMaxSpeed(float ms)
{
maxSpeed = ms;
}
int Entity::getMaxSpeed()
{
return maxSpeed;
}
void Entity::songNote(int note)
{
}
void Entity::songNoteDone(int note, float len)
{
}
void Entity::sound(const std::string &sound, float freq, float fadeOut)
{
dsq->playPositionalSfx(sound, position, freq, fadeOut, this);
updateSoundPosition();
}
Vector Entity::getEnergyShotTargetPosition()
{
return getWorldPosition();
}
bool Entity::isTargetInRange(int range, size_t t)
{
if (t >= targets.size())
{
std::ostringstream os;
os << "isTargetInRange: invalid target index: " << t;
debugLog(os.str());
return false;
}
if (!targets[t])
{
debugLog ("null target");
return false;
}
return ((targets[t]->position - this->position).getSquaredLength2D() < sqr(range));
}
void Entity::setEntityType(EntityType et)
{
entityType = et;
}
EntityType Entity::getEntityType()
{
return entityType;
}
Entity *Entity::findTarget(int dist, int type, size_t t)
{
Entity *target = NULL;
if (type == ET_AVATAR)
{
Vector d = game->avatar->position - this->position;
if (d.getSquaredLength2D() < sqr(dist))
{
target = game->avatar;
}
}
else
{
int closestDist = -1;
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e != this && e->getEntityType() == type && e->health > 0)
{
int d = (e->position - this->position).getSquaredLength2D();
if (d < sqr(dist) && (d < closestDist || closestDist == -1))
{
closestDist = d;
target = e;
}
}
}
}
setTargetEntity(target, t);
return target;
}
void Entity::moveTowards(Vector p, float dt, int spd)
{
Vector d = p - this->position;
d.setLength2D(spd*dt);
vel += d;
}
void Entity::moveAround(Vector p, float dt, int spd, int dir)
{
Vector d = p - this->position;
if (!dir)
d = Vector(-d.y, d.x);
else
d = Vector(d.y, -d.x);
d.setLength2D(spd*dt);
vel += d;
}
void Entity::moveTowardsAngle(int angle, float dt, int spd)
{
Vector p(sinf(MathFunctions::toRadians(angle))*16+position.x, cosf(MathFunctions::toRadians(angle))*16+position.y);
moveTowards(p, dt, spd);
}
void Entity::moveAroundAngle(int angle, float dt, int spd, int dir)
{
Vector p(sinf(MathFunctions::toRadians(angle))*16+position.x, cosf(MathFunctions::toRadians(angle))*16+position.y);
moveAround(p, dt, spd, dir);
}
void Entity::moveTowardsTarget(float dt, int spd, size_t t)
{
if (t >= targets.size() || !targets[t]) return;
moveTowards(targets[t]->position, dt, spd);
}
void Entity::moveAroundTarget(float dt, int spd, int dir, size_t t)
{
if (t >= targets.size() || !targets[t]) return;
moveAround(targets[t]->position, dt, spd, dir);
}
Vector Entity::getLookAtPoint()
{
if (lookAtPoint.isZero())
return position;
return lookAtPoint;
}
void Entity::moveAroundEntity(float dt, int spd, int dir, Entity *e)
{
Vector d = e->position - this->position;
if (!dir)
d = Vector(-d.y, d.x);
else
d = Vector(d.y, -d.x);
d.setLength2D(spd*dt);
vel += d;
}
void Entity::onEnterState(int action)
{
switch (action)
{
case STATE_DEAD:
{
if (!isGoingToBeEaten())
{
if (!deathSound.empty())
sound(deathSound, (800 + rand()%400)/1000.0f);
}
else
{
sound("Gulp");
}
popBubble();
Shot::targetDied(this);
if (!calledEntityDied)
{
game->entityDied(this);
calledEntityDied = true;
}
if (hair)
{
hair->setLife(1);
hair->setDecayRate(10);
hair->fadeAlphaWithLife = 1;
hair = 0;
}
if(minimapIcon)
{
minimapIcon->alpha.interpolateTo(0, 0.1f);
}
}
break;
case STATE_PUSH:
{
setMaxSpeed(this->pushMaxSpeed);
}
break;
}
}
bool Entity::isPullable() const
{
return ((isEntityProperty(EP_MOVABLE)) || (frozenTimer > 0));
}
void Entity::freeze(float time)
{
//HACK: prevent freeze from freezing if the enemy won't deal with it properly
//if (isv(EV_MOVEMENT, 0)) return;
vel = 0;
frozenTimer = time;
disableMotionBlur();
vel.capLength2D(100);
onFreeze();
if (!bubble)
{
bubble = new Quad;
bubble->setTexture("spell-bubble");
bubble->position = this->position;
bubble->scale = Vector(0.2f,0.2f);
bubble->scale.interpolateTo(Vector(2,2), 0.5f, 0, 0, 1);
bubble->alpha.ensureData();
bubble->alpha.data->path.addPathNode(0.5f, 0);
bubble->alpha.data->path.addPathNode(0.5f, 0.75f);
bubble->alpha.data->path.addPathNode(0, 1);
bubble->alpha.startPath(time+time*0.25f);
core->getTopStateData()->addRenderObject(bubble, LR_PARTICLES);
}
}
void Entity::onExitState(int action)
{
switch(action)
{
case STATE_PUSH:
{
setState(STATE_IDLE);
}
break;
case STATE_PUSHDELAY:
{
setState(STATE_IDLE);
}
break;
case STATE_DEATHSCENE:
{
setState(STATE_DEAD);
}
break;
}
}
void Entity::popBubble()
{
if (bubble)
{
frozenTimer = 0;
sound ("Pop");
bubble->setLife(1);
bubble->setDecayRate(4);
bubble->fadeAlphaWithLife = bubble->alpha.x;
bubble = 0;
dsq->spawnParticleEffect("PopEnemyBubble", position);
}
}
bool Entity::isHit()
{
return (damageTimer.isActive());
}
bool Entity::isInvincible()
{
return (invincible);
}
void Entity::setInvincible(bool inv)
{
invincible = inv;
}
bool Entity::isInDarkness() const
{
const TileStorage& ts = dsq->tilemgr.tilestore[12];
const size_t n = ts.tiles.size();
for(size_t i = 0; i < n; ++i)
{
const TileData& t = ts.tiles[i];
if(t.isVisible() && t.isCoordinateInside(position.x, position.y))
return true;
}
return false;
}
bool Entity::canSetState(int state)
{
if (enqueuedState == STATE_DEAD || currentState == STATE_DEAD || nextState == STATE_DEAD)
{
std::ostringstream os;
os << "entity: " << name << " tried to set state to: " << state << " when in/entering dead";
debugLog(os.str());
return false;
}
else if (enqueuedState == STATE_DEATHSCENE || currentState == STATE_DEATHSCENE || nextState == STATE_DEATHSCENE)
{
if (state == STATE_DEAD)
return true;
else
{
std::ostringstream os;
os << "entity: " << name << " tried to set state to: " << state << " when in/entering deathscene";
debugLog(os.str());
return false;
}
}
return true;
}
bool Entity::updateLocalWarpAreas(bool affectAvatar)
{
for (size_t i = 0; i < game->getNumPaths(); i++)
{
Path *p = game->getPath(i);
if (!p->nodes.empty())
{
PathNode *n = &p->nodes[0];
if (p && n)
{
if (p->warpMap.empty() && !p->warpNode.empty() && p->isCoordinateInside(position))
{
Path *p2 = game->getPathByName(p->warpNode);
if (p2)
{
if (affectAvatar)
{
// HACK: do something in the script to get the avatar position
game->avatar->position = this->position;
game->preLocalWarp(p->localWarpType);
}
position = p2->getPathNode(0)->position;
if (affectAvatar)
{
// HACK: do something in the script to get the avatar position
game->avatar->position = this->position;
game->postLocalWarp();
}
return true;
}
}
}
}
}
return false;
}
void Entity::warpLastPosition()
{
position = lastPosition;
}
void Entity::spawnParticlesFromCollisionMask(const char *p, unsigned intv, int layer, float rotz)
{
for (size_t i = 0; i < skeletalSprite.bones.size(); i++)
skeletalSprite.bones[i]->spawnParticlesFromCollisionMask(p, intv, layer, rotz);
}
//Entity *e, Entity *attacker, Bone *bone, SpellType spell, int dmg)
// not sure why this returns bool
// return true if did damage, else false
bool Entity::damage(const DamageData &dmgData)
{
DamageData d = dmgData;
if (d.damageType == DT_NONE)
return false;
if (isEntityDead())
{
//DUPE: same as below
//HACK: hackish
return false;
}
onDamage(d);
lastDamage = d;
if (invincibleBreak && damageTimer.isActive() && dmgData.useTimer) return false;
this->multColor = Vector(1,1,1);
this->multColor.stop();
if (dmgData.useTimer)
damageTimer.start(damageTime);
//DUPE: same as above
//HACK: hackish
if (d.damageType == DT_AVATAR_BITE)
{
debugLog("Entity::damage bittenEntities.push_back");
game->avatar->bittenEntities.push_back(this);
}
if (d.damage > 0 && frozenTimer)
{
popBubble();
}
//HACK: fish form freeze
if (d.damageType == DT_AVATAR_BUBBLE && isDamageTarget(DT_AVATAR_BUBBLE))
{
freeze(30);
}
if (d.damageType == DT_AVATAR_VINE)
{
if (vineDamageTimer.isDone())
{
vineDamageTimer.start(0.25);
}
else
return false;
}
if (d.damageType == DT_ENEMY_POISON)
{
if (getEntityType() != ET_AVATAR)
{
setPoison(1, d.effectTime);
}
}
bool doDamage = !invincible;
if (entityType == ET_AVATAR)
doDamage = (!invincible || !game->invincibleOnNested);
if (doDamage)
{
if (d.damage>0)
{
if (entityType == ET_AVATAR)
this->multColor.interpolateTo(Vector(1, 0.1f, 0.1f), 0.1f, 14, 1);
else
this->multColor.interpolateTo(Vector(1, 0.1f, 0.1f), 0.1f, 4, 1);
}
health -= d.damage;
if (health <= 0)
{
health = 0;
entityDead = true;
if (deathScene)
setState(STATE_DEATHSCENE, 0);
else
setState(STATE_DEAD);
}
onHealthChange(-d.damage);
}
return true;
}
void Entity::clampToHit()
{
Vector dist = game->lastCollidePosition - position;
dist.setLength2D(collideRadius);
position = game->lastCollidePosition + dist;
setv(EV_CRAWLING, 1);
}
void Entity::doEntityAvoidance(float dt, int range, float mod, Entity *ignore)
{
Vector accum;
int c = 0;
Vector diff;
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e != this && e != ignore && e->ridingOnEntity != this && !e->getv(EV_NOAVOID))
{
diff = (this->position - e->position);
if (diff.isLength2DIn(range) && !diff.isZero())
{
diff.setLength2D(range - diff.getLength2D());
accum += diff;
c++;
}
}
}
if (accum.x != 0 || accum.y != 0)
{
accum /= c;
accum /= range;
vel += accum*getMaxSpeed()*mod;
}
}
void Entity::render(const RenderState& rsold) const
{
// This is special-cased for entities:
// An entity that has a renderpass set is supposed to apply this to all
// children regardless of their setting.
// (In earlier versions this functionality was implemented via an overrideRenderPass
// but that doesn't exist anymore)
// -> Wait for the correct pass until we even bother to try rendering this entity
if(renderPass != RENDER_ALL && rsold.pass != renderPass)
return;
RenderState rs(rsold);
rs.color *= color;
rs.scale *= flipScale;
if (multColor.isInterpolating())
rs.color *= multColor;
rs.alpha *= alpha.x;
if (game->isSceneEditorActive() && game->sceneEditor.editType == ET_ENTITIES)
{
if (game->sceneEditor.editingEntity == this)
rs.renderBorderColor = Vector(1,1,1);
else
rs.renderBorderColor = Vector(0.5,0.5,0.5);
rs.forceRenderBorder = true;
}
// if we have an override render pass set:
// from this point, render all children in this pass
// regardless of what they specify
if(renderPass != RENDER_ALL && rs.pass == renderPass)
rs.pass = RENDER_ALL;
Quad::render(rs);
}
void Entity::doGlint(const Vector &position, const Vector &scale, const std::string &tex, BlendType bt)
{
float glintTime = 0.4f;
Quad *glint = new Quad;
glint->setBlendType(bt);
glint->setTexture(tex);
glint->scale = Vector(0.5f,0.5f);
glint->position = position;
glint->scale.interpolateTo(scale, glintTime);
glint->alpha.ensureData();
glint->alpha.data->path.addPathNode(1, 0);
glint->alpha.data->path.addPathNode(1, 0.7f);
glint->alpha.data->path.addPathNode(0, 1);
glint->alpha.startPath(glintTime);
glint->rotation.z = this->rotation.z;
glint->setLife(glintTime);
glint->setDecayRate(1);
core->getTopStateData()->addRenderObject(glint, LR_PARTICLES);
}
void Entity::addIgnoreShotDamageType(DamageType dt)
{
ignoreShotDamageTypes.push_back(dt);
}
void Entity::doSpellAvoidance(float dt, int range, float mod)
{
Vector accum;
int c = 0;
for (Shot::Shots::iterator i = Shot::shots.begin(); i != Shot::shots.end(); i++)
{
Shot *s = (Shot*)(*i);
if (s->isActive() && (s->position - this->position).getSquaredLength2D() < sqr(range))
{
for (size_t j = 0; j < ignoreShotDamageTypes.size(); j++)
{
if (s->getDamageType() == ignoreShotDamageTypes[j])
{
continue;
}
}
Vector d = this->position - s->position;
if (!d.isZero())
{
d.setLength2D(range - d.getLength2D());
}
accum += d;
c++;
}
}
if (accum.x != 0 || accum.y != 0)
{
accum /= c;
accum /= range;
vel += accum*getMaxSpeed()*mod;
}
}
void Entity::fillGrid()
{
if (fillGridFromQuad)
{
game->fillGridFromQuad(this, OT_INVISIBLEENT, fillGridFromQuad == 1);
}
if (fillGridFromSkel)
{
bool trim = fillGridFromSkel == 1;
for(size_t i = 0; i < skeletalSprite.bones.size(); ++i)
{
Bone *b = skeletalSprite.bones[i];
if(b->generateCollisionMask && b->renderQuad)
game->fillGridFromQuad(b, OT_INVISIBLEENT, trim);
}
}
}
// caller must make sure that the ID is unused
void Entity::setID(int id)
{
entityID = id;
}
int Entity::getID()
{
return entityID;
}
void Entity::shotHitEntity(Entity *hit, Shot *shot, Bone *b)
{
}
bool Entity::doCollisionAvoidance(float dt, int search, float mod, Vector *vp, float overrideMaxSpeed, int ignoreObs, bool onlyVP)
{
Vector accum;
int c = 0;
bool isInWaterBubble = false;
if (isUnderWater() && waterBubble) // isUnderWater() may set waterBubble
{
//debugLog("collision avoidance in bubble");
isInWaterBubble = true;
if (!canLeaveWater)
{
Vector a = position - waterBubble->nodes[0].position;
Vector b = a;
b.setLength2D((waterBubble->rect.getWidth()*0.5f) - b.getLength2D());
if (b.isLength2DIn(search*TILE_SIZE))
{
accum -= b;
c++;
}
}
}
if (!overrideMaxSpeed)
overrideMaxSpeed = getMaxSpeed();
if (vp == 0)
vp = &this->vel;
Vector vel = *vp;
int minDist=-1;
TileVector t(position);
TileVector useTile;
const int blockObs = ~ignoreObs;
float totalDist = sqrtf(float(sqr((search*2)*TILE_SIZE)+sqr((search*2)*TILE_SIZE)));
for (int x = -search; x <= search; x++)
{
for (int y = -search; y <= search; y++)
{
TileVector checkT(t.x+x, t.y+y);
bool waterBlocked=false;
if (!isInWaterBubble && !canLeaveWater && checkT.worldVector().y - collideRadius < game->getWaterLevel())
{
waterBlocked = true;
}
if (waterBlocked || game->isObstructed(checkT))
{
if (game->isObstructed(checkT, blockObs))
{
Vector vtc(t.x+x, t.y+y);
Vector vt(t.x, t.y);
int dist = (vt-vtc).getSquaredLength2D();
if (minDist == -1 || dist<minDist)
{
minDist = dist;
useTile = TileVector(t.x+x, t.y+y);
}
Vector v = position - checkT.worldVector();
accum += v;
c++;
}
}
}
}
if (c > 0)
{
accum /= float(c) * (totalDist/2);
accum.setLength2D(1.0f - accum.getLength2D());
if (onlyVP)
{
*vp += accum*overrideMaxSpeed*mod;
if (!(*vp).isLength2DIn(overrideMaxSpeed))
(*vp).capLength2D(overrideMaxSpeed);
}
else
vel += accum*overrideMaxSpeed*mod;
}
if (!onlyVP)
*vp = vel;
if (c > 0)
return true;
return false;
}
void Entity::initHair(int numSegments, float segmentLength, float width, const std::string &tex)
{
if (hair)
{
errorLog("Trying to init hair when hair is already present");
}
hair = new Hair(numSegments, segmentLength, width);
hair->setTexture(tex);
game->addRenderObject(hair, layer);
}
void Entity::setHairHeadPosition(const Vector &pos)
{
if (hair)
{
hair->setHeadPosition(pos);
}
}
void Entity::updateHair(float dt)
{
if (hair)
{
hair->updatePositions();
}
}
void Entity::exertHairForce(const Vector &force, float dt)
{
if (hair)
{
hair->exertForce(force, dt);
}
}
bool Entity::isEntityInside() const
{
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e && e->life == 1 && e != this && e->ridingOnEntity != this && isCoordinateInside(e->position))
return true;
}
return false;
}
void Entity::updateSoundPosition()
{
SoundHolder::updateSoundPosition(position.x + offset.x, position.y + offset.y);
}
MinimapIcon *Entity::ensureMinimapIcon()
{
if(!minimapIcon)
minimapIcon = new MinimapIcon;
return minimapIcon;
}
bool Entity::isNormalLayer() const
{
return layer == LR_ENTITIES || layer == LR_ENTITIES0 || layer == LR_ENTITIES2 || layer == LR_ENTITIES_MINUS2 || layer == LR_ENTITIES_MINUS3;
}