1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-11-29 03:33:48 +00:00
Aquaria/Aquaria/Entity.cpp
fgenesis 209964034d Make maxSpeed float, little cleanup, minor Lua API stuff
Plus additional functions that don't use speed types:
+ entity_followPathSpeed()
+ entity_moveToNodeSpeed()
+ entity_swimToNodeSpeed()
+ entity_swimToPositionSpeed()

The following old functions (plus all of the above) return float now
(how long moving will take):

* entity_followPath()
* entity_moveToNode()
* entity_swimToNode)
* entity_swimToPosition()
2015-06-12 02:59:01 +02:00

3162 lines
61 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"
//Shader Entity::blurShader;
void Entity::stopPull()
{
if (dsq->game->avatar->pullTarget == this)
{
dsq->game->avatar->pullTarget = 0;
}
}
void Entity::setIngredientData(const std::string &name)
{
ingredientData = dsq->continuity.getIngredientDataByName(name);
}
void Entity::entityDied(Entity *e)
{
for (int i = 0; i < targets.size(); i++)
{
targets[i] = 0;
}
if (boneLock.on)
{
if (boneLock.entity == e)
{
setBoneLock(BoneLock());
}
}
}
void Entity::setBounceType(BounceType bt)
{
bounceType = bt;
}
BounceType Entity::getBounceType()
{
return bounceType;
}
void Entity::generateCollisionMask(int ovrCollideRadius)
{
if (this->skeletalSprite.isLoaded())
{
for (int i = 0; i < skeletalSprite.bones.size(); i++)
{
if (skeletalSprite.bones[i]->generateCollisionMask)
{
dsq->game->generateCollisionMask(skeletalSprite.bones[i], ovrCollideRadius);
}
}
}
}
/*
void Entity::clearv()
{
for (int i = 0; i < EV_MAX; i++)
{
vs[i] = 0;
}
}
*/
bool Entity::setBoneLock(const BoneLock &boneLock)
{
if (!canSetBoneLock()) return false;
if (boneLock.on && boneLockDelay > 0) return false;
if (this->boneLock.on && boneLock.on) return false;
if (this->boneLock.on && !boneLock.on)
{
boneLockDelay = 0.1;
this->boneLock = boneLock;
}
else
{
if (!boneLock.entity)
return false;
if (boneLock.entity && !boneLock.bone)
{
this->boneLock = boneLock;
this->boneLock.circleOffset = this->position - (boneLock.entity->getWorldPosition());
this->boneLock.circleOffset.setLength2D(boneLock.entity->collideRadius);
this->boneLock.origRot = boneLock.entity->rotation.z;
/*
this->boneLock = boneLock;
this->boneLock.localOffset = this->position - (boneLock.entity->getWorldPosition());
this->boneLock.localOffset = boneLock.entity->getInvRotPosition(this->boneLock.localOffset);
this->boneLock.circleOffset = this->position - (boneLock.entity->position);
this->boneLock.circleOffset.setLength2D(boneLock.entity->collideRadius);
this->boneLock.origRot = boneLock.entity->getWorldRotation();
//this->boneLock.origRot = MathFunctions::toRadians(this->boneLock.origRot);
MathFunctions::calculateAngleBetweenVectorsInRadians(boneLock.entity->position + boneLock.entity->getForward(), boneLock.entity->position, this->boneLock.origRot);
//position, boneLock.entity->position,
MathFunctions::calculateAngleBetweenVectorsInRadians(position, boneLock.entity->position, this->boneLock.offRot);
while (this->boneLock.origRot > PI)
this->boneLock.origRot -= PI;
while (this->boneLock.origRot < 0)
this->boneLock.origRot += PI;
while (this->boneLock.offRot > PI)
this->boneLock.offRot -= PI;
while (this->boneLock.offRot < 0)
this->boneLock.offRot += PI;
//this->boneLock.offRot = atanf(this->boneLock.circleOffset.y / this->boneLock.circleOffset.x);
//
//this->boneLock.localOffset = boneLock.bone->getOriginCollidePosition(this->boneLock.localOffset);
*/
}
else
{
this->boneLock = boneLock;
//this->boneLock.localOffset = this->position - boneLock.bone->getWorldPosition();
this->boneLock.localOffset = this->position - (boneLock.bone->getWorldPosition());
this->boneLock.localOffset = boneLock.bone->getInvRotPosition(this->boneLock.localOffset);
this->boneLock.origRot = boneLock.bone->getWorldRotation();
//this->boneLock.localOffset = boneLock.bone->getOriginCollidePosition(this->boneLock.localOffset);
}
}
setv(EV_BONELOCKED, boneLock.on);
onSetBoneLock();
return true;
}
bool Entity::canSetBoneLock()
{
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.2);
setv(EV_FLIPTOPATH, 1);
setv(EV_NOINPUTNOVEL, 1);
setv(EV_VINEPUSH, 1);
setv(EV_BEASTBURST, 1);
setv(EV_WEBSLOW, 100);
//debugLog("Entity::Entity()");
//clampOnSwitchDir = true;
//registerEntityDied = false;
invincible = false;
lanceDelay = 0;
lance = 0;
lanceTimer = 0;
lanceGfx = 0;
lanceBone = 0;
beautyFlip = true;
fhScale = fvScale = 0;
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;
//renderPass = RENDER_ALL;
//crawling = 0;
ridingOnEntity = 0;
targetRange = 32;
//energyChargeTarget = energyShotTarget = true;
deathSound = "GenericDeath";
entityID = 0;
//assignUniqueID();
hair = 0;
maxSpeedLerp = 1;
fillGridFromQuad = false;
dropChance = 0;
inCurrent = false;
entityProperties.resize(EP_MAX);
for (int i = 0; i < entityProperties.size(); i++)
{
entityProperties[i] = false;
}
entityTypeIdx = -1;
damageTime = vars->entityDamageTime;
slowingToStopPathTimer = 0;
slowingToStopPath = 0;
followPos = 0;
watchingEntity = 0;
swimPath = false;
currentEntityTarget = 0;
deleteOnPathEnd = false;
multColor = Vector(1,1,1);
collideRadius = 24;
entityType = EntityType(0);
targets.resize(10);
attachedTo = 0;
//target = 0;
frozenTimer = 0;
canBeTargetedByAvatar = false;
activationRange = 0;
activationType = ACT_NONE;
pushDamage = 0;
//debugLog("dsq->addEntity()");
dsq->addEntity(this);
maxSpeed = 300;
entityDead = false;
health = maxHealth = 5;
invincibleBreak = false;
activationRadius = 40;
activationRange = 600;
//affectedBySpells = true;
// followAvatar = false;
followEntity = 0;
bubble = 0;
/*
copySkel.parentManagedStatic = 1;
copySkel.updateAfterParent = 1;
addChild(&copySkel);
*/
//debugLog("skeletalSprite init");
skeletalSprite.updateAfterParent = 1;
skeletalSprite.setAnimationKeyNotify(this);
addChild(&skeletalSprite, PM_STATIC);
//debugLog("damageTarget stuff");
setDamageTarget(DT_AVATAR_NATURE, false);
setDamageTarget(DT_AVATAR_LIZAP, true);
setDamageTarget(DT_AVATAR_BUBBLE, false);
setDamageTarget(DT_AVATAR_SEED, false);
stopSoundsOnDeath = false;
//debugLog("End Entity::Entity()");
}
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;
if (wasUnderWater && !isUnderWater(o))
{
sound("splash-outof");
changed = true;
wasUnderWater = false;
}
else if (!wasUnderWater && isUnderWater(o))
{
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[int(ep)] = value;
}
bool Entity::isEntityProperty(EntityProperty ep)
{
return entityProperties[int(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 (int 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;
//debugLog("Starting");
position.data->path.getPathNode(0)->value = position;
position.startPath(time);//, 1.0f/2.0f);
return time;
}
float Entity::moveToPos(Vector dest, float speed, int dieOnPathEnd, bool swim)
{
if(!speed)
speed = getMaxSpeed();
Vector start = position;
followEntity = 0;
//watchingEntity = 0;
position.ensureData();
position.data->path.clear();
position.stop();
swimPath = swim;
//debugLog("Generating path to: " + path->name);
PathFinding::generatePath(this, TileVector(start), TileVector(dest));
//int sz = position.data->path.getNumPathNodes();
//position.data->path.addPathNode(path->nodes[0].position, 1);
//VectorPath old = position.data->path;
/*std::ostringstream os;
os << "Path length: " << sz;
debugLog(os.str());*/
//debugLog("Regenerating section");
//int ms = sz % 12;
/*
if (sz > 12)
{
int node = sz/2;
dsq->pathFinding.generatePath(this, TileVector(position), TileVector(position.path.getPathNode(node)->value));
old.splice(position.data->path, node);
position.data->path = old;
}
*/
this->vel = 0;
//debugLog("Molesting Path");
PathFinding::molestPath(position.data->path);
//position.data->path.realPercentageCalc();
//position.data->path.cut(4);
//debugLog("forcing path to minimum 2 nodes");
PathFinding::forceMinimumPath(position.data->path, start, dest);
//debugLog("Done");
//debugLog("Calculating Time");
float time = position.data->path.getLength()/speed;
//debugLog("Starting");
position.data->path.getPathNode(0)->value = position;
position.startPath(time);//, 1.0f/2.0f);
/*
if (dieOnPathEnd)
position.endOfPathEvent.set(MakeFunctionEvent(Entity, safeKill));
*/
//debugLog("Set delete on Path end");
deleteOnPathEnd = dieOnPathEnd;
//debugLog("End of Generate Path");
//position.startSpeedPath(dsq->continuity.getSpeedType(speedType));
//position.startPath(((position.data->path.getNumPathNodes()*TILE_SIZE*4)-2)/dsq->continuity.getSpeedType(speedType));
return time;
}
void Entity::stopFollowingPath()
{
position.stopPath();
}
void Entity::flipToTarget(Vector pos)
{
//if (dsq->game->avatar->position.x > r->position.x)
//else if (dsq->game->avatar->position.x < r->position.x)
if (pos.x > position.x)
{
if (!isfh())
flipHorizontal();
}
else if (pos.x < position.x)
{
if (isfh())
flipHorizontal();
}
}
Entity* Entity::getTargetEntity(int t)
{
return targets[t];
}
void Entity::setTargetEntity(Entity *e, int t)
{
targets[t] = e;
}
bool Entity::hasTarget(int t)
{
return (targets[t]!=0);
}
void Entity::watchEntity(Entity *e)
{
watchingEntity = e;
}
void Entity::destroy()
{
if (stopSoundsOnDeath)
this->stopAllSounds();
this->unlinkAllSounds();
/*
if (hair)
{
hair->safeKill();
hair = 0;
}
*/
if (hair)
{
// let the engine clean up 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;
/*
checkSurfaceDelay = math.random(3)+1
if entity_clampToSurface(me, 0.5, 3) then
entity_setState(me, STATE_WALL, 2 + math.random(2))
else
checkSurfaceDelay = 0.1
end
*/
}
void Entity::rotateToSurfaceNormal(float t, int n, int rot)
{
Vector v;
if (ridingOnEntity)
{
v = position - ridingOnEntity->position;
}
else
{
if (n == 0)
v = dsq->game->getWallNormal(position);
else
v = dsq->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
//t = 0;
//setCrawling(true);
setv(EV_CRAWLING, 1);
burstTimer.stop();
// do stuff
Vector pos = TileVector(usePos).worldVector();
if (!hitTile.isZero())
{
//debugLog("using hitTile");
pos = hitTile.worldVector();
clamped = true;
}
else
{
//debugLog("NOT using hitTile");
if (vel.getSquaredLength2D() < 1)
{
longCheck:
//debugLog("LongCheck");
for (int i = 0; i < tcheck; i++)
{
int bit = i*TILE_SIZE;
int backBit = (i-1)*TILE_SIZE;
if (dsq->game->isObstructed(TileVector(pos - Vector(bit,0))))
{
TileVector t(pos - Vector(backBit,0));
pos = t.worldVector();
clamped = true;
break;
}
if (dsq->game->isObstructed(TileVector(pos - Vector(0,bit))))
{
TileVector t(pos - Vector(0,backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (dsq->game->isObstructed(TileVector(pos + Vector(bit,0))))
{
TileVector t(pos + Vector(backBit,0));
pos = t.worldVector();
clamped = true;
break;
}
if (dsq->game->isObstructed(TileVector(pos + Vector(0,bit))))
{
TileVector t(pos + Vector(0,backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (dsq->game->isObstructed(TileVector(pos + Vector(-bit,-bit))))
{
TileVector t(pos + Vector(-backBit,-backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (dsq->game->isObstructed(TileVector(pos + Vector(-bit,bit))))
{
TileVector t(pos + Vector(-backBit,backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (dsq->game->isObstructed(TileVector(pos + Vector(bit,-bit))))
{
TileVector t(pos + Vector(backBit,-backBit));
pos = t.worldVector();
clamped = true;
break;
}
if (dsq->game->isObstructed(TileVector(pos + Vector(bit,bit))))
{
TileVector t(pos + Vector(backBit,backBit));
pos = t.worldVector();
clamped = true;
break;
}
}
}
else
{
//debugLog("VelCheck");
Vector v = vel;
v.normalize2D();
for (int i = 0; i < tcheck; i++)
{
if (dsq->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 = dsq->game->getWallNormal(pos);
n *= getv(EV_WALLOUT);
//pos += n;
Vector diff = getWorldPosition() - pos;
//e->offset.interpolateTo(diff, t);
offset = diff;
offset.interpolateTo(n, t);
position = pos;
//e->rotateToSurfaceNormal(t);
rotateToSurfaceNormal(0);
setv(EV_CLAMPING, 1);
//e->position.interpolateTo(pos, t);
/*
debugLog("interpolating position");
e->position.interpolateTo(Vector(0,0,0), t);
*/
}
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);
//health += 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))
{
dsq->game->spawnManaBall(position, manaBallEnergy);
}
}
if (die)
{
setLife(1);
setDecayRate(4);
fadeAlphaWithLife = true;
}
else
{
alpha.interpolateTo(0.01,1);
}
}
else
{
if (die)
{
setLife(1);
setDecayRate(1);
fadeAlphaWithLife = true;
}
else
{
alpha.interpolateTo(0.01,1);
}
scale.interpolateTo(Vector(0,0), 1);
stickToNaijasHead = true;
}
activationType = ACT_NONE;
if (ingredientData)
{
dsq->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 (dsq->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 (dsq->game->isObstructed(test))
{
v = true;
break;
}
}
for (int y = -sz; y <= sz; y++)
{
test = TileVector(t.x, t.y+y);
if (dsq->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 (dsq->game->isObstructed(test))
{
v = true;
break;
}
}
}
break;
case OBSCHECK_8DIR:
{
//debugLog("8dir");
for (int d = 0; d <= sz; d++)
{
test = TileVector(t.x+d, t.y);
if (dsq->game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x-d, t.y);
if (dsq->game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x, t.y+d);
if (dsq->game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x, t.y-d);
if (dsq->game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x-d, t.y-d);
if (dsq->game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x-d, t.y+d);
if (dsq->game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x+d, t.y-d);
if (dsq->game->isObstructed(test))
{
v = true;
break;
}
test = TileVector(t.x+d, t.y+d);
if (dsq->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 && dsq->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);
/*
std::ostringstream os;
os << "position(" << position.x << ", " << position.y << ") - ";
os << "usePosition(" << usePosition.x << ", " << usePosition.y << ") - ";
os << "collidePos(" << collidePos.x << ", " << collidePos.y << ")";
debugLog(os.str());
*/
}
if (radius == 0 || (dsq->game->avatar->getWorldPosition() - usePosition).getSquaredLength2D() < sqr(radius+dsq->game->avatar->collideRadius))
{
if (dmg > 0)
{
DamageData d;
d.damage = dmg;
d.attacker = this;
dsq->game->avatar->damage(d);
}
if (pushTime > 0 && speed > 0)
{
Vector diff = dsq->game->avatar->position - position;
diff.setLength2D(speed);
//dsq->game->avatar->vel += diff;
//dsq->game->avatar->push(v, pushTime, , 0);
dsq->game->avatar->push(diff, pushTime, speed, dmg);
}
else if (speed > 0)
{
dsq->game->avatar->fallOffWall();
Vector diff = dsq->game->avatar->position - position;
diff.setLength2D(speed);
dsq->game->avatar->vel += diff;
}
/*
if (pushTime != 0)
{
Vector v = (dsq->game->avatar->position - this->position);
v.setLength2D(1000);
dsq->game->avatar->push(v, pushTime, 800, 0);
}
*/
//dsq->game->avatar->damage(dmg);
return true;
}
return false;
}
const float sct = 0.15;
//const float blurMax = 0.04;
const float blurMax = 0.01;
const float blurMin = 0.0;
/*
const float blurMax = 0.05;
const float blurMin = 0.0;
*/
void Entity::onFHScale()
{
flipScale.interpolateTo(Vector(1, 1), sct);
_fh = !_fh;
/*
copySkel.fhTo(!_fh);
skeletalSprite.alpha.interpolateTo(1, 0.5);
copySkel.alpha.interpolateTo(0, 0.5);
*/
//skeletalSprite.alpha.interpolateTo(1,sct);
//blurShaderAnim.interpolateTo(Vector(blurMin,0,0), sct);
fhScale = 0;
}
void Entity::onFH()
{
if (!beautyFlip) return;
_fh = !_fh;
if (!fhScale)
{
flipScale = Vector(1,1);
/*
copySkel.children = skeletalSprite.children;
copySkel.scale = skeletalSprite.scale;
copySkel.position = skeletalSprite.scale;
copySkel.animations = skeletalSprite.animations;
*/
//skeletalSprite.alpha.interpolateTo(0, sct*2);
//skeletalSprite.alpha.interpolateTo(0.5, sct);
//flipScale.interpolateTo(Vector(1.5, 1), sct);
flipScale.interpolateTo(Vector(0.6, 1), sct);
//blurShaderAnim = Vector(blurMin);
//blurShaderAnim.interpolateTo(Vector(blurMax,0,0), sct/2);
fhScale = 1;
}
}
void Entity::flipToVel()
{
if ((vel.x < -5 && isfh()) || (vel.x > 5 && !isfh()))
flipHorizontal();
}
void Entity::frozenUpdate(float dt)
{
}
void Entity::update(float dt)
{
/*
if (position.isnan())
position = backupPos;
if (vel.isnan())
vel = backupVel;
*/
/*
if (entityID == 0)
assignUniqueID();
*/
Vector backupPos = position;
Vector backupVel = vel;
bool doUpdate = (updateCull < 0 || (position - core->screenCenter).isLength2DIn(updateCull));
if (doUpdate && !(pauseFreeze && dsq->game->isPaused()))
{
if (!(getEntityType() == ET_AVATAR || getEntityType() == ET_INGREDIENT))
{
if (spiritFreeze && dsq->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;
//safeKill();
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();
}
}
/*
skeletalSprite.setFreeze(false);
skeletalSprite.update(dt);
*/
/*
std::string bgAction;
if (frozenTimer > 0)
{
bgAction = currentAction;
currentAction = "frozen";
}
*/
/*
if (!bgAction.empty())
{
currentAction = bgAction;
}
*/
}
updateBoneLock();
if (position.isNan())
position = backupPos;
if (vel.isNan())
vel = backupVel;
updateSoundPosition();
}
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())))
{
dsq->game->playBurstSound(wallJump);
skeletalSprite.animate("burst");
position.ensureData();
if (wallJump)
position.data->pathTimeMultiplier = 2;
else
position.data->pathTimeMultiplier = 1.5;
burstTimer.start(1);
//void pathBurst();r
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.4);
rotateToVec(Vector(0,-1), 0.1, 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();
}
}
// offset.interpolateTo(sinf(ondulateTimer)*v.getPerpendicularLeft()*32, 0.5);
if (skeletalSprite.isLoaded())
{
if (burstTimer.isActive())
rotateToVec(v, 0.05);
else
rotateToVec(v, 0.2);
Animation *anim = skeletalSprite.getCurrentAnimation();
if (!burstTimer.isActive())
{
if (!anim || anim->name != "swim")
skeletalSprite.transitionAnimate("swim", 0.1, -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);
/*
if (v)
debugLog("isSlowingToStopPath: true");
*/
return v;
}
/*
void Entity::updateAvatarRollPull(float dt)
{
if (dsq->game->avatar->isRolling())
{
if (position - dsq->game->avatar->position)
{
}
}
}
*/
bool Entity::updateCurrents(float dt)
{
inCurrent = false;
int c = 0;
Vector accum;
//if (isUnderWater())
// why?
{
//Path *p = dsq->game->getNearestPath(position, PATH_CURRENT);
if (!dsq->game->isWorldPaused())
{
for (Path *p = dsq->game->getFirstPathOfType(PATH_CURRENT); p; p = p->nextOfType)
{
if (p->active)
{
for (int 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++;
/*
dir.setLength2D(p->currentMod);
vel2 += dir*dt;
vel2.capLength2D(p->currentMod);
*/
}
}
}
}
}
if (inCurrent)
{
accum /= c;
vel2 = accum;
float len = vel2.getLength2D();
float useLen = len;
if (useLen < 500)
useLen = 500;
if (!(this->getEntityType() == ET_AVATAR && dsq->game->avatar->canSwimAgainstCurrents() && dsq->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.2;
if (getEntityType() == ET_AVATAR)
{
if (v < 0)
dsq->rumble((-v)*scale, (1.0f+v)*scale, 0.2);
else
dsq->rumble((1.0f-v)*scale, (v)*scale, 0.1);
}
}
}
if (this->getEntityType() == ET_AVATAR && dsq->game->avatar->canSwimAgainstCurrents())
{
int cap = 100;
if (!vel.isZero())
{
if (vel.dot2D(vel2) < 0 ) // greater than 90 degrees
{
// against current
if (dsq->game->avatar->bursting)
vel2 = 0;
else if (dsq->game->avatar->isSwimming())
vel2.capLength2D(cap);
}
}
else
{
//vel2.capLength2D(cap);
}
}
/*
if (!inCurrent && !vel2.isZero() && !vel2.isInterpolating())
{
vel2.interpolateTo(Vector(0,0,0), 1);
}
*/
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 (dsq->game->isObstructed(TileVector(t.x+x, t.y+y), OT_INVISIBLEIN))
return true;
if (dsq->game->isObstructed(TileVector(t.x-x, t.y+y), OT_INVISIBLEIN))
return true;
if (dsq->game->isObstructed(TileVector(t.x+x, t.y-y), OT_INVISIBLEIN))
return true;
if (dsq->game->isObstructed(TileVector(t.x-x, t.y-y), OT_INVISIBLEIN))
return true;
}
}
return false;
//bool invisibleIn = false;
////Vector f = getForward();
//Vector f = getNormal();
//TileVector t(position);
//float tx = float(t.x);
//float ty = float(t.y);
//float otx = float(t.x);
//float oty = float(t.y);
//
//for (int check = 0; check < 4; check++)
//{
// /*
// std::ostringstream os;
// os << "check: " << check << " p(" << position.x <<", " << position.y << ") f(" << f.x << ", " << f.y << ") t(" << tx << ", " << ty << ") tile found: " << dsq->game->getGrid(TileVector(tx, ty));
// debugLog(os.str());
// */
// if (dsq->game->getGrid(TileVector(tx, ty))==OT_EMPTY)
// {
// }
// else if (dsq->game->getGrid(TileVector(tx, ty))==OT_INVISIBLEIN)
// {
// invisibleIn = true;
// //debugLog("invisible in!");
// break;
// }
// else
// {
// //debugLog("obstruction, aborting");
// break;
// }
// tx -= f.x;
// ty -= f.y;
// /*
// tx = float(t.x) - f.x*float(check);
// ty = float(t.y) - f.y*float(check);
// */
//}
//return invisibleIn;
}
void Entity::moveOutOfWall()
{
/*
Vector v = getWorldCollidePosition(Vector(0,-1));
// HACK: is normalize necessary here? (distance of 1 presumabley)
Vector n = (v - position);
n.normalize2D();
*/
Vector n = getNormal();
TileVector t(position);
int c = 0;
bool useValue = true;
while (dsq->game->isObstructed(t))
{
c++;
if (c > 6)
{
//debugLog("entity: " + name + " exceeded max moveOutOfWall()");
useValue = false;
break;
}
t.x += n.x;
t.y += n.y;
}
if (useValue)
{
position = t.worldVector();
}
}
void Entity::clearDamageTargets()
{
disabledDamageTypes.clear();
}
void Entity::setDamageTarget(DamageType dt, bool v)
{
if (v)
disabledDamageTypes.erase(dt);
else
disabledDamageTypes.insert(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)
{
if (v)
clearDamageTargets(); // clear all disabled -> all allowed now
else
{
for (int i = DT_ENEMY; i < DT_ENEMY_REALMAX; i++)
disabledDamageTypes.insert(DamageType(i));
for (int i = DT_AVATAR; i < DT_AVATAR_REALMAX; i++)
disabledDamageTypes.insert(DamageType(i));
for (int i = DT_AVATAR_MAX; i < DT_REALMAX; i++)
disabledDamageTypes.insert(DamageType(i));
}
}
bool Entity::isDamageTarget(DamageType dt)
{
return disabledDamageTypes.find(dt) == disabledDamageTypes.end();
}
float Entity::getHealthPerc()
{
return float(health) / float(maxHealth);
}
Vector Entity::getMoveVel()
{
return vel + vel2;
}
void Entity::onEndOfLife()
{
if (!calledEntityDied)
{
dsq->game->entityDied(this);
calledEntityDied = true;
}
}
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);
switch (fhScale)
{
case 1:
if (!flipScale.isInterpolating())
onFHScale();
break;
}
//blurShaderAnim.update(dt);
}
//vel2=0;
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;
//hair->color = this->color;
}
if (slowingToStopPath > 0)
{
/*
std::ostringstream os;
os << "slowingToStopPath: " << slowingToStopPath;
debugLog(os.str());
*/
slowingToStopPathTimer += dt;
position.ensureData();
if (slowingToStopPathTimer >= slowingToStopPath)
{
// done
position.data->pathTimeMultiplier = 1;
// stopFollowingPath();
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;
lastMove = v;
if (position.isFollowingPath() && swimPath)
{
movementDetails(v);
}
else
{
if (watchingEntity)
{
Vector v = position - watchingEntity->position;
if (v.x < 0)
{
if (!isfh())
flipHorizontal();
}
else if (v.x > 0)
{
if (isfh())
flipHorizontal();
}
}
}
if (wasFollowing && !isFollowingPath())
{
onPathEnd();
}
multColor.update(dt);
if (bubble)
bubble->position = this->position;
/*
if (frozenTimer > 0)
{
frozenTimer --;
if (frozenTimer <= 0)
{
frozenTimer = 0;
popBubble();
}
}
*/
if (getState() == STATE_PUSH)
{
//vel = pushVec * this->time;
vel = pushVec;
}
/*
else if (this->currentAction == "freeze")
{
if (this->enqueuedAction != "freeze")
{
this->enqueuedAction = "";
}
if (canBeFrozen)
vel = Vector(0,0,0);
}
*/
else if (followEntity)
{
Vector lastPos = position;
Vector off;
int sz = 96;
if (followEntity->vel.getSquaredLength2D() > sqr(1))
{
off = followEntity->vel.getPerpendicularLeft();
switch (followPos)
{
case 0: off.setLength2D(sz); break;
case 1: off.setLength2D(-sz); break;
}
}
else if (followEntity->lastMove.getSquaredLength2D() > sqr(1))
{
off = followEntity->lastMove.getPerpendicularLeft();
switch (followPos)
{
case 0: off.setLength2D(sz); break;
case 1: off.setLength2D(-sz); break;
}
}
Vector mov = followEntity->position + off - this->position;
if (mov.getSquaredLength2D() > sqr(96))
{
//following = true;
int spd = mov.getLength2D();
spd -= 64;
if (spd < 0)
spd = 0;
else if (spd < 400)
spd *= 2;
//spd /= ;
else
spd = 800;
mov.setLength2D(spd);
position += mov * dt;
Vector diff = position - lastPos;
movementDetails(diff);
}
else
{
Animation *anim = skeletalSprite.getCurrentAnimation();
if (!anim || anim->name != "idle")
idle();
}
}
if (stickToNaijasHead)
{
position = dsq->game->avatar->headPosition;
/*
std::ostringstream os;
os << "pos(" << dsq->game->avatar->headPosition.x << ", " <<dsq->game->avatar->headPosition.y << ")";
debugLog(os.str());
*/
}
updateLance(dt);
}
void Entity::updateBoneLock()
{
if (boneLock.on)
{
/*
Vector pos = boneLock.bone->getWorldCollidePosition(boneLock.localOffset);
Vector bpos = boneLock.bone->getWorldPosition();
position = pos;
boneLock.wallNormal = pos - bpos;
boneLock.wallNormal.normalize2D();
rotateToVec(boneLock.wallNormal, 0.01);
*/
Vector lastPosition = position;
if (boneLock.bone)
{
position = boneLock.bone->transformedCollisionMask[boneLock.collisionMaskIndex];
boneLock.wallNormal = boneLock.bone->getCollisionMaskNormal(boneLock.collisionMaskIndex);
rotateToVec(boneLock.wallNormal, 0.01);
}
else
{
Vector currentOffset = getRotatedVector(boneLock.circleOffset, boneLock.entity->rotation.z - boneLock.origRot);
position = boneLock.entity->getWorldPosition() + currentOffset;
boneLock.wallNormal = currentOffset;
boneLock.wallNormal.normalize2D();
rotateToVec(boneLock.wallNormal, 0.01);
}
if (dsq->game->collideCircleWithGrid(position, collideRadius))
{
position = lastPosition;
setBoneLock(BoneLock());
return;
}
/*
Vector bpos = boneLock.bone->getWorldPosition();
boneLock.wallNormal = position - bpos;
rotateToVec(boneLock.wallNormal, 0.01);
*/
//debugLog("wall normal");
/*
Vector p = boneLock.bone->getWorldPosition();
Vector o = boneLock.localOffset;
position = p+o;
boneLock.wallNormal = o;
boneLock.wallNormal.normalize2D();
*/
onUpdateBoneLock();
/*
wallNormal = o;
wallNormal.normalize2D();
rotateToVec(wallNormal, 0);
*/
//position = boneLock.bone->getWorldPosition() + boneLock.localOffset;
//rotation = boneLock.bone->getWorldRotation();
}
}
std::string Entity::getIdleAnimName()
{
return "idle";
}
void Entity::idle()
{
if (isFollowingPath())
stopFollowingPath();
position.stopPath();
perform(Entity::STATE_IDLE);
skeletalSprite.stopAllAnimations();
//skeletalSprite.animate("idle", -1, 0);
onIdle();
skeletalSprite.transitionAnimate(getIdleAnimName(), 0.3, -1);
rotateToVec(Vector(0,-1),0.1);
vel.capLength2D(50);
setRiding(0);
}
void Entity::updateLance(float dt)
{
if (lance == 1)
{
lanceTimer -= dt;
if (lanceTimer < 0)
{
lance = 0;
lanceGfx->setLife(1.0);
lanceGfx->setDecayRate(2);
lanceGfx->fadeAlphaWithLife = 1;
lanceGfx = 0;
lanceTimer = 0;
}
else
{
lanceGfx->fhTo(_fh);
lanceDelay = lanceDelay + dt;
if (lanceDelay > 0.1f)
{
lanceDelay = 0;
dsq->game->fireShot("Lance", this, 0, lanceGfx->getWorldCollidePosition(Vector(-64, 0)));
}
if (lanceBone != 0)
{
lanceGfx->position = lanceBone->getWorldPosition();
lanceGfx->rotation = lanceBone->getWorldRotation();
}
else
{
lanceGfx->position = getWorldPosition();
lanceGfx->rotation = rotation;
}
}
}
}
void Entity::attachLance()
{
std::ostringstream os;
os << "attaching lance to " << this->name;
debugLog(os.str());
lance = 1;
lanceBone = 0;
if (!lanceGfx)
{
lanceGfx = new PauseQuad();
lanceGfx->setTexture("Particles/Lance");
lanceGfx->alpha = 0;
lanceGfx->alpha.interpolateTo(1, 0.5);
dsq->game->addRenderObject(lanceGfx, LR_PARTICLES);
}
lanceTimer = 8;
lanceBone = skeletalSprite.getBoneByName("Lance");
}
void Entity::setRiding(Entity *e)
{
riding = e;
}
Entity* Entity::getRiding()
{
return riding;
}
void Entity::attachEntity(Entity *e, Vector offset)
{
attachedEntities.push_back(e);
//e->position - position
attachedEntitiesOffsets.push_back(offset);
e->attachedTo = this;
//dsq->game->avatar->position - avatarOffset;
}
void Entity::detachEntity(Entity *e)
{
e->attachedTo = 0;
std::vector<Entity*>copyEnts = attachedEntities;
std::vector<Vector> copyOffs = attachedEntitiesOffsets;
attachedEntities.clear();
attachedEntitiesOffsets.clear();
for (int i = 0; i < copyEnts.size(); i++)
{
if (copyEnts[i] != e)
{
attachedEntities.push_back(copyEnts[i]);
attachedEntitiesOffsets.push_back(copyOffs[i]);
}
}
}
//if (fabsf(rotation.z - angle) > 180)
//{
// rotation.z += 360;
//}
/*
if (rotation.z > 270 && angle > -45 && angle < 0)
angle = 360 + angle;
*/
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 (rotation.z >= 270 && angle < 90)
{
rotation.stop();
rotation.z -= 360;
}
if (rotation.z <= 90 && angle > 270)
{
rotation.stop();
rotation.z += 360;
}
*/
/*
if (fabsf(angle - rotation.z) > 180)
{
// something's wrong
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(int i)
{
if (i >= targetPoints.size() || i < 0)
return getEnergyShotTargetPosition();
return targetPoints[i];
}
int Entity::getRandomTargetPoint()
{
if (targetPoints.empty())
return 0;
return rand()%targetPoints.size();
}
bool Entity::isUnderWater(const Vector &override)
{
Vector check = position;
if (!override.isZero())
check = override;
if (dsq->game->useWaterLevel && dsq->game->waterLevel.x > 0 && check.y-collideRadius > dsq->game->waterLevel.x)
return true;
Path *p = dsq->game->getNearestPath(position, PATH_WATERBUBBLE);
if (p && p->active && p->isCoordinateInside(position, collideRadius))
{
waterBubble = p;
return true;
}
if (!dsq->game->useWaterLevel || dsq->game->waterLevel.x == 0) return true;
else
{
if (check.y-collideRadius > dsq->game->waterLevel.x)
{
waterBubble = 0;
return true;
}
}
return false;
}
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;
/*
Vector v = vec;
v.setLength2D(maxSpeed);
*/
setState(STATE_PUSH, time);
pushVec = vec;
pushVec.z = 0;
}
//vel += pushVec;
}
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, int t)
{
if (t < 0 || 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;
}
/* types:
*/
Entity *Entity::findTarget(int dist, int type, int t)
{
targets[t] = 0;
if (type == ET_AVATAR)
{
Vector d = dsq->game->avatar->position - this->position;
if (d.getSquaredLength2D() < sqr(dist))
{
targets[t] = dsq->game->avatar;
}
}
else
{
int closestDist = -1;
Entity *target = 0;
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;
}
}
}
if (target)
{
targets[t] = target;
}
}
return targets[t];
}
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, int t)
{
if (!targets[t]) return;
moveTowards(targets[t]->position, dt, spd);
}
void Entity::moveAroundTarget(float dt, int spd, int dir, int t)
{
if (!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();
//dsq->game->avatar->entityDied(this);
Shot::targetDied(this);
if (!calledEntityDied)
{
dsq->game->entityDied(this);
calledEntityDied = true;
}
if (hair)
{
hair->setLife(1);
hair->setDecayRate(10);
hair->fadeAlphaWithLife = 1;
hair = 0;
}
}
break;
case STATE_PUSH:
{
setMaxSpeed(this->pushMaxSpeed);
}
break;
}
}
bool Entity::isPullable()
{
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.2,0.2);
bubble->scale.interpolateTo(Vector(2,2), 0.5, 0, 0, 1);
bubble->alpha.ensureData();
bubble->alpha.data->path.addPathNode(0.5, 0);
bubble->alpha.data->path.addPathNode(0.5, 0.75);
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;
}
/*
// this is bad, prevents DEATH
// try to prevent exitState from changing from deathscene
if (health <= 0)
{
enqueuedState = STATE_NONE;
}
*/
/*
else if (action == "freezeRecover")
{
enqueuePerform("idle", -1);
}
else if (action == "freeze")
{
if (nextAction == "freeze")
{
}
else
{
popBubble();
//enqueuePerform("idle", -1);
}
}
*/
}
void Entity::popBubble()
{
/*
if (currentAction == "freeze")
{
sound("pop");
enqueuePerform("freezeRecover",1);
}
*/
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::onDamage(int amount, Spell *spell, Entity *attacker)
{
if (bubble)
{
popBubble();
onDamage(1, spell, attacker);
}
return true;
}
*/
bool Entity::isHit()
{
return (damageTimer.isActive());
}
bool Entity::isInvincible()
{
return (invincible);
//|| (invincibleBreak && damageTimer.isActive())
}
void Entity::setInvincible(bool inv)
{
invincible = inv;
}
bool Entity::isInDarkness()
{
for (Element *e = dsq->getFirstElementOnLayer(12); e; e = e->bgLayerNext)
{
if (e->isCoordinateInside(position))
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 (int i = 0; i < dsq->game->getNumPaths(); i++)
{
Path *p = dsq->game->getPath(i);
if (!p->nodes.empty())
{
PathNode *n = &p->nodes[0];
if (p && n) // && core->getNestedMains() == 1
{
if (p->warpMap.empty() && !p->warpNode.empty() && p->isCoordinateInside(position))
{
Path *p2 = dsq->game->getPathByName(p->warpNode);
if (p2)
{
if (affectAvatar)
{
// HACK: do something in the script to get the avatar position
dsq->game->avatar->position = this->position;
dsq->game->preLocalWarp(p->localWarpType);
}
position = p2->getPathNode(0)->position;
if (affectAvatar)
{
// HACK: do something in the script to get the avatar position
dsq->game->avatar->position = this->position;
dsq->game->postLocalWarp();
}
return true;
}
}
}
}
}
return false;
}
void Entity::warpLastPosition()
{
position = lastPosition;
}
void Entity::spawnParticlesFromCollisionMask(const std::string &p, int intv)
{
for (int i = 0; i < skeletalSprite.bones.size(); i++)
{
for (int j = 0; j < skeletalSprite.bones[i]->collisionMask.size(); j+=intv)
{
Vector pos = skeletalSprite.bones[i]->getWorldCollidePosition(skeletalSprite.bones[i]->collisionMask[j]);
dsq->spawnParticleEffect(p, pos);
}
}
}
//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 () return true;
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();
/*
std::ostringstream os;
os << "starting damage timer: " << vars->damageTime;
debugLog(os.str());
*/
if (dmgData.useTimer)
damageTimer.start(damageTime);
//3
//DUPE: same as above
//HACK: hackish
if (d.damageType == DT_AVATAR_BITE)
{
debugLog("Entity::damage bittenEntities.push_back");
dsq->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 || !dsq->game->invincibleOnNested);
if (doDamage)
{
if (d.damage>0)
{
if (entityType == ET_AVATAR)
this->multColor.interpolateTo(Vector(1, 0.1, 0.1), 0.1, 14, 1);
else
this->multColor.interpolateTo(Vector(1, 0.1, 0.1), 0.1, 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 = dsq->game->lastCollidePosition - position;
dist.setLength2D(collideRadius);
position = dsq->game->lastCollidePosition + dist;
setv(EV_CRAWLING, 1);
//setCrawling(true);
}
/*
void Entity::damage(int amount, Spell *spell, Entity *attacker)
{
//if (dsq->continuity.getWorldType() != WT_NORMAL) return;
if (!takeDamage) return;
if (isEntityDead()) return;
if (invincibleBreak && this->multColor.isInterpolating()) return;
if (onDamage(amount, spell, attacker))
{
//color = currentColor;
this->multColor.interpolateTo(Vector(1, 0.5, 0.5), 0.1, 3, 1);
health -= amount;
if (health <= 0)
{
if (attacker)
attacker->getEXP(exp);
health = 0;
entityDead = true;
perform(STATE_DEAD);
}
}
}
*/
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()
{
InterpolatedVector bcolor = color;
InterpolatedVector bscale = scale;
scale *= flipScale;
if (multColor.isInterpolating())
{
color *= multColor;
}
#ifdef AQUARIA_BUILD_SCENEEDITOR
if (dsq->game->isSceneEditorActive() && dsq->game->sceneEditor.editType == ET_ENTITIES)
{
if (dsq->game->sceneEditor.editingEntity == this)
renderBorderColor = Vector(1,1,1);
else
renderBorderColor = Vector(0.5,0.5,0.5);
renderBorder = true;
//errorLog("!^!^$");
}
#endif
// HACK: need to multiply base + etc
skeletalSprite.setColorMult(this->color, this->alpha.x);
/*bool set=false;
if (beautyFlip && blurShader.isLoaded() && flipScale.isInterpolating() && dsq->user.video.blur)
{
//swizzle
blurShader.setValue(color.x, color.y, color.z, blurShaderAnim.x);
blurShader.bind();
set = true;
}*/
Quad::render();
//if (beautyFlip && blurShader.isLoaded() && flipScale.isInterpolating())
//if (set)
// blurShader.unbind();
renderBorder = false;
skeletalSprite.clearColorMult();
color = bcolor;
scale = bscale;
}
void Entity::doGlint(const Vector &position, const Vector &scale, const std::string &tex, RenderObject::BlendTypes bt)
{
float glintTime = 0.4;
Quad *glint = new Quad;
//glint->setBlendType(RenderObject::BLEND_ADD);
glint->setBlendType(bt);
glint->setTexture(tex);
glint->scale = Vector(0.5,0.5);
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.7);
glint->alpha.data->path.addPathNode(0, 1);
glint->alpha.startPath(glintTime);
//glint->rotation.interpolateTo(Vector(0,0,360), 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)
{
BBGE_PROF(Entity_doSpellAvoidance);
Vector accum;
/*
int c = 0;
for (int i = 0; i < dsq->game->spells.size(); i++)
{
Spell *s = dsq->game->spells[i];
if ((s->position - this->position).getSquaredLength2D() < sqr(range))
{
Vector d = this->position - s->position;
d.z=0;
d |= range - d.getLength2D();
accum += d;
c++;
}
}
if (accum.x != 0 || accum.y != 0)
{
accum /= c;
accum /= range;
vel += accum*getMaxSpeed()*mod;
}
*/
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 (int 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)
{
dsq->game->fillGridFromQuad(this, OT_INVISIBLEENT);
}
}
void Entity::assignUniqueID()
{
int id = 1;
while (1)
{
bool isFree = true;
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e != this)
{
if (e->getID() == id)
{
isFree = false;
break;
}
}
}
if (isFree)
{
break;
}
id++;
}
entityID = id;
}
void Entity::setID(int id)
{
entityID = id;
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e != this)
{
if (e->getID() == entityID)
{
std::ostringstream os;
os << "ID conflict between " << name << " and " << e->name;
debugLog(os.str());
e->assignUniqueID();
}
}
}
}
int Entity::getID()
{
/*
for (int i = 0; i < dsq->entities.size(); i++)
{
if (dsq->entities[i] == this)
return i+1;
}
return 0;
*/
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 (waterBubble && isUnderWater())
{
//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))
{
/*
std::ostringstream os;
os << "b( " << b.x << ", " << b.y << ")";
debugLog(os.str());
*/
accum -= b;
c++;
//vel -= accum*getMaxSpeed()*mod;
//return true;
}
}
}
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 < dsq->game->getWaterLevel())
{
waterBlocked = true;
}
if (waterBlocked || dsq->game->isObstructed(checkT))
{
if (dsq->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);
dsq->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()
{
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);
}