1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-12-01 15:35:47 +00:00
Aquaria/Aquaria/Avatar.cpp

6994 lines
144 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/AfterEffect.h"
#include "../BBGE/MathFunctions.h"
#include "Avatar.h"
#include "Game.h"
#include "Shot.h"
#include "GridRender.h"
#include "Web.h"
#include "Hair.h"
const float MULT_DMG_CRABCOSTUME = 0.75f;
const float MULT_DMG_FISHFORM = 1.5f;
const float MULT_DMG_SEAHORSEARMOR = 0.6f;
const float MULT_MAXSPEED_BEASTFORM = 1.2f;
const float MULT_MAXSPEED_FISHFORM = 1.5f;
const float MULT_DMG_EASY = 0.5f;
const float JELLYCOSTUME_HEALTHPERC = 0.5f;
const float JELLYCOSTUME_HEALDELAY = 2.0f;
const float JELLYCOSTUME_HEALAMOUNT = 0.5f;
const float biteTimerBiteRange = 0.6f;
const float biteTimerMax = 3;
const float biteDelayPeriod = 0.08f;
const size_t normalTendrilHits = 3;
const size_t rollTendrilHits = 4;
const size_t maxTendrilHits = 6;
const float fireDelayTime = 0.2f;
const int maxShieldPoints = 8;
const int minMouse = 60;
int SongIcon::notesOpen = 0;
Avatar *avatar = 0;
const Vector BLIND_COLOR = Vector(0.1f, 0.1f, 0.1f);
const float ANIM_TRANSITION = 0.2f;
//const float MANA_RECHARGE_RATE = 1.0;
const int AURA_SHIELD_RADIUS = 64;
//const int TARGET_RANGE = 1024;
const int TARGET_RANGE = 1024; // 650
const int TARGET_GRACE_RANGE = 200;
//const int TARGET_RANGE = 700;
//const int TARGET_RANGE = 64;
const float NOTE_SCALE = 0.75f;
const int singingInterfaceRadius = 100;
const int openSingingInterfaceRadius = 128;
//164
const int BURST_DISTANCE = 200;
const int STOP_DISTANCE = 48;
const int maxMouse = BURST_DISTANCE;
//const int SHOCK_RANGE = 700;
//const int SHOCK_RANGE = 1000;
const int SPIRIT_RANGE = 2000;
const float QUICK_SONG_CAST_DELAY = 0.4f;
const float BURST_RECOVER_RATE = 1.2f; // 3.0 // 0.75
const float BURST_USE_RATE = 1.5f; //0.9 //1.5;
const float BURST_ACCEL = 4000; //2000 // 1000
// Minimum time between two splash effects (seconds).
const float SPLASH_INTERVAL = 0.2f;
//const float TUMMY_TIME = 6.0;
//const float chargeMax = 2.0;
// Axis input distance (0.0-1.0) at which we start moving.
const float JOYSTICK_LOW_THRESHOLD = 0.2f;
// Axis input distance at which we move full speed.
const float JOYSTICK_HIGH_THRESHOLD = 0.6f;
// Axis input distance at which we accept a note.
const float JOYSTICK_NOTE_THRESHOLD = 0.6f;
// Mouse cursor distance (from note icon, in virtual pixels) below which
// we accept a note.
const float NOTE_ACCEPT_DISTANCE = 25;
// Joystick input angle offset (from note icon, in degrees) below which
// we accept a note.
const float NOTE_ACCEPT_ANGLE_OFFSET = 15;
const int COLLIDE_RADIUS_NORMAL = 10;
const int COLLIDE_RADIUS_FISH = 8;
const int COLLIDE_RANGE_NORMAL = 2;
const int COLLIDE_RANGE_FISH = 1;
const float COLLIDE_MOD_NORMAL = 1.0f;
const float COLLIDE_MOD_FISH = 0.1f;
const int requiredDualFormCharge = 3;
Vector Target::getWorldPosition()
{
Vector ret;
if (e)
{
ret = e->getTargetPoint(targetPt);
}
return ret;
}
void Avatar::bindInput()
{
ActionMapper::clearActions();
ActionMapper::clearCreatedEvents();
for(size_t i = 0; i < dsq->user.control.actionSets.size(); ++i)
{
const ActionSet& as = dsq->user.control.actionSets[i];
int sourceID = (int)i;
as.importAction(this, "PrimaryAction", ACTION_PRIMARY, sourceID);
as.importAction(this, "SecondaryAction", ACTION_SECONDARY, sourceID);
as.importAction(this, "SwimUp", ACTION_SWIMUP, sourceID);
as.importAction(this, "SwimDown", ACTION_SWIMDOWN, sourceID);
as.importAction(this, "SwimLeft", ACTION_SWIMLEFT, sourceID);
as.importAction(this, "SwimRight", ACTION_SWIMRIGHT, sourceID);
as.importAction(this, "SongSlot1", ACTION_SONGSLOT1, sourceID);
as.importAction(this, "SongSlot2", ACTION_SONGSLOT2, sourceID);
as.importAction(this, "SongSlot3", ACTION_SONGSLOT3, sourceID);
as.importAction(this, "SongSlot4", ACTION_SONGSLOT4, sourceID);
as.importAction(this, "SongSlot5", ACTION_SONGSLOT5, sourceID);
as.importAction(this, "SongSlot6", ACTION_SONGSLOT6, sourceID);
as.importAction(this, "SongSlot7", ACTION_SONGSLOT7, sourceID);
as.importAction(this, "SongSlot8", ACTION_SONGSLOT8, sourceID);
as.importAction(this, "SongSlot9", ACTION_SONGSLOT9, sourceID);
as.importAction(this, "SongSlot10", ACTION_SONGSLOT10, sourceID);
as.importAction(this, "Revert", ACTION_REVERT, sourceID);
as.importAction(this, "Look", ACTION_LOOK, sourceID);
as.importAction(this, "Roll", ACTION_ROLL, sourceID);
}
}
// note: z is set to 1.0 when we want the aim to be used as the shot direction
// otherwise the shot will head straight to the target
Vector Avatar::getAim()
{
Vector d;
if (dsq->inputMode == INPUT_JOYSTICK)
{
for(size_t i = 0; i < core->getNumJoysticks(); ++i)
if(Joystick *j = core->getJoystick(i))
if(j->isEnabled() && !j->rightStick.isZero())
{
d = j->rightStick * 300;
d.z = 1;
break;
}
if(d.isZero())
for(size_t i = 0; i < core->getNumJoysticks(); ++i)
if(Joystick *j = core->getJoystick(i))
if(j->isEnabled() && !j->position.isZero())
{
d = j->position * 300;
break;
}
}
else
{
d = dsq->getGameCursorPosition() - position;
d.z = 1;
}
if (d.isZero())
d = getForwardAim();
return d;
}
Vector Avatar::getForwardAim()
{
Vector aim = getForward();
// getForward() points toward Naija's head, but it makes more sense
// to shoot in the direction she's facing, so rotate the aim vector.
aim = getRotatedVector(aim, isfh() ? 90 : -90);
return aim;
}
void Avatar::onAnimationKeyPassed(int key)
{
Entity::onAnimationKeyPassed(key);
}
Vector randCirclePos(Vector position, int radius)
{
float a = ((rand()%360)*(2*PI))/360.0f;
return position + Vector(sinf(a), cosf(a))*radius;
}
SongIconParticle::SongIconParticle(Vector color, Vector pos, size_t note)
: note(note)
{
cull = false;
//fastTransform = true;
setTexture("particles/glow");
setWidthHeight(32);
float life = 1.0;
toIcon = 0;
this->color = color;
position = pos;
alpha.ensureData();
alpha.data->path.addPathNode(0, 0);
alpha.data->path.addPathNode(0.4f, 0.2f); // .8
alpha.data->path.addPathNode(0.2f, 0.8f); // .4
alpha.data->path.addPathNode(0, 1);
alpha.startPath(life);
scale.ensureData();
scale.data->path.addPathNode(Vector(0.5f,0.5f), 0);
scale.data->path.addPathNode(Vector(1,1), 0.5f);
scale.data->path.addPathNode(Vector(0.5f,0.5f), 1);
scale.startPath(life);
setLife(life);
setDecayRate(1);
//if (rand()%6 <= 2)
setBlendType(RenderObject::BLEND_ADD);
float smallestDist = HUGE_VALF;
SongIcon *closest = 0;
for (size_t i = 0; i < avatar->songIcons.size(); i++)
{
if (i != note)
{
Vector diff = (position - avatar->songIcons[i]->position);
float dist = diff.getSquaredLength2D();
if (dist < smallestDist)
{
smallestDist = dist;
closest = avatar->songIcons[i];
}
}
}
// find nearest song icon
if (closest)
{
toIcon = closest;
}
}
void SongIconParticle::onUpdate(float dt)
{
Quad::onUpdate(dt);
if (toIcon)
{
Vector add = (toIcon->position - position);
add.setLength2D(200*dt);
velocity += add;
velocity.capLength2D(50);
}
}
SongIcon::SongIcon(size_t note) : Quad(), note(note)
{
open = false;
alphaMod = 0.9f;
/*
std::ostringstream os;
os << "SongIcon" << note;
setTexture(os.str());
*/
//setTexture("Cursor-Sing");
std::ostringstream os;
os << "Song/NoteSymbol" << note;
os.str();
setTexture(os.str());
scale = Vector(NOTE_SCALE, NOTE_SCALE);
cursorIsIn = false;
delay = 0;
counter = 0;
width = 40;
height = 40;
minTime = 0;
ptimer = 0;
noteColor = dsq->getNoteColor(note);
//color = dsq->getNoteColor(note)*0.75f + Vector(1,1,1)*0.25f;
color = dsq->getNoteColor(note);
len = 0;
channel = BBGE_AUDIO_NOCHANNEL;
rippleTimer = 0;
glow = new Quad;
glow->setTexture("particles/bigglow");
glow->followCamera = 1;
glow->rotation.interpolateTo(Vector(0,0,360), 10, -1);
glow->alpha = 0;
glow->setBlendType(RenderObject::BLEND_ADD);
glow->scale = Vector(0.5, 0.5);
glow->color = dsq->getNoteColor(note);
dsq->game->addRenderObject(glow, LR_PARTICLES2);
}
void SongIcon::destroy()
{
Quad::destroy();
}
void SongIcon::spawnParticles(float dt)
{
float intv = 0.1f;
// do stuff!
ptimer += dt;
while (ptimer > intv)
{
ptimer -= intv;
SongIconParticle *s = new SongIconParticle(noteColor, randCirclePos(position, 16), note);
s->followCamera = true;
dsq->game->addRenderObject(s, LR_HUD);
}
}
void SongIcon::onUpdate(float dt)
{
Quad::onUpdate(dt);
if (!avatar->singing)
return;
if (alpha.x == 0 && !alpha.isInterpolating())
alpha.interpolateTo(0.3f, 0.1f);
if (delay > 0)
{
delay -= dt;
if (delay < 0)
{
delay = 0;
//channel = BBGE_AUDIO_NOCHANNEL;
}
}
if (counter > 0)
{
counter -= dt;
if (counter < 0)
{
counter = 0;
closeNote();
}
}
if (alpha.x > 0.5f)
{
spawnParticles(dt);
}
if (open)
{
len += dt;
avatar->setHeadTexture("Singing", 0.1f);
}
if (alpha.x == 1)
{
if (isCoordinateInRadius(core->mouse.position, NOTE_ACCEPT_DISTANCE))
{
//if (delay == 0)
if (true)
{
if (!cursorIsIn)
// highlighted for the first time
{
cursorIsIn = true;
openNote();
}
else
{
if (minTime > 0)
{
minTime -= dt;
if (minTime < 0)
{
minTime = 0;
}
}
}
}
}
else if (!isCoordinateInRadius(core->mouse.position, NOTE_ACCEPT_DISTANCE*1.25f))
{
if (cursorIsIn)
{
cursorIsIn = false;
closeNote();
}
}
}
if (alpha.x <= 0 && delay == 0) // && channel != BBGE_AUDIO_NOCHANNEL
{
closeNote();
}
if (open)
{
if (dsq->user.video.noteEffects)
{
rippleTimer -= dt;
if (rippleTimer <= 0)
{
//rippleTimer = 1.0f - ((7 - note)/7.0f)*0.7f;
rippleTimer = 0.5f - (note/7.0f)*0.4f;
if (core->afterEffectManager)
{
core->afterEffectManager->addEffect(new ShockEffect(position - Vector(400, 300) + Vector(core->width/2, core->height/2),
core->screenCenter,0.009f,0.015f,18,0.2f, 0.9f + (note*0.08f) ));
}
}
}
}
if (glow)
{
glow->position = position;
}
}
void SongIcon::openNote()
{
//if (delay > 0) return;
scale.interpolateTo(Vector(1.2f, 1.2f), 0.1f);
if (dsq->user.video.noteEffects)
{
glow->scale = Vector(0.5f,0.5f);
glow->scale.interpolateTo(Vector(1.0f, 1.0f), 2, -1, 1, 1);
glow->alpha.interpolateTo(0.6f, 0.2f, 0, 0, 1);
}
/*
std::ostringstream os;
os << "Note"
*/
std::string sfx = dsq->game->getNoteName(note);
open = true;
internalOffset = Vector(-5, 0);
internalOffset.interpolateTo(Vector(5, 0), 0.08f, -1, 1);
avatar->singNote(this->note);
// this should never get called:
if (channel != BBGE_AUDIO_NOCHANNEL)
{
dsq->sound->fadeSfx(channel, SFT_OUT, 0.2f);
//dsq->sound->fadeSfx(channel, SFT_OUT, 0.2);
channel = BBGE_AUDIO_NOCHANNEL;
}
//dsq->sound->stopSfx(channel);
PlaySfx play;
play.name = sfx;
channel = dsq->sound->playSfx(play);
rippleTimer = 0;
minTime = 0.05f;
counter = 3.2f;
float glowLife = 0.5;
{
Quad *q = new Quad("particles/glow", position);
q->scale.interpolateTo(Vector(10, 10), glowLife+0.1f);
q->alpha.ensureData();
q->alpha.data->path.addPathNode(0,0);
q->alpha.data->path.addPathNode(0.75f,0.2f);
q->alpha.data->path.addPathNode(0,1);
q->alpha.startPath(glowLife);
q->color = dsq->getNoteColor(note); //*0.5f + Vector(0.5, 0.5, 0.5)
q->setBlendType(RenderObject::BLEND_ADD);
q->followCamera = 1;
dsq->game->addRenderObject(q, LR_HUD);
q->setDecayRate(1/(glowLife+0.1f));
}
{
std::ostringstream os2;
os2 << "Song/NoteSymbol" << note;
Quad *q = new Quad(os2.str(), position);
q->color = 0;
q->scale = Vector(0.5,0.5);
q->scale.interpolateTo(Vector(2, 2), glowLife+0.1f);
//q->scale.interpolateTo(Vector(10, 10), glowLife+0.1f);
q->alpha.ensureData();
q->alpha.data->path.addPathNode(0,0);
q->alpha.data->path.addPathNode(0.5f,0.2f);
q->alpha.data->path.addPathNode(0,1);
q->alpha.startPath(glowLife);
//q->setBlendType(RenderObject::BLEND_ADD);
q->followCamera = 1;
dsq->game->addRenderObject(q, LR_HUD);
q->setDecayRate(1/(glowLife+0.1f));
}
avatar->songInterfaceTimer = 1.0f;
notesOpen++;
/*
std::ostringstream os2;
os2 << "notesOpen: " << notesOpen;
debugLog(os2.str());
*/
if (notesOpen > 0)
{
len = 0;
FOR_ENTITIES(i)
{
Entity *e = *i;
if ((e->position - dsq->game->avatar->position).getSquaredLength2D() < sqr(1000))
{
e->songNote(note);
}
}
for (size_t i = 0; i < dsq->game->getNumPaths(); i++)
{
Path *p = dsq->game->getPath(i);
if (!p->nodes.empty())
{
if ((p->nodes[0].position - dsq->game->avatar->position).getSquaredLength2D() < sqr(1000))
{
p->songNote(note);
}
}
}
}
}
void SongIcon::closeNote()
{
//if (delay > 0) return;
scale.interpolateTo(Vector(NOTE_SCALE, NOTE_SCALE), 0.1f);
if (dsq->game->avatar->isSinging() && dsq->user.video.noteEffects)
glow->alpha.interpolateTo(0.3f, 1.5f, 0, 0, 1);
else
glow->alpha.interpolateTo(0, 1.5f, 0, 0, 1);
glow->scale.interpolateTo(Vector(0.5f, 0.5f), 0.5f);
cursorIsIn = false;
if (channel != BBGE_AUDIO_NOCHANNEL)
{
dsq->sound->fadeSfx(channel, SFT_OUT, 1.0);
channel = BBGE_AUDIO_NOCHANNEL;
//delay = 0.5;
}
if (open)
{
internalOffset.stop();
internalOffset = Vector(0,0);
notesOpen--;
open = false;
FOR_ENTITIES(i)
{
Entity *e = *i;
int dist = (e->position - dsq->game->avatar->position).getSquaredLength2D();
if (e != dsq->game->avatar && dist < sqr(1000))
{
e->songNoteDone(note, len);
}
}
for (size_t i = 0; i < dsq->game->getNumPaths(); i++)
{
Path *p = dsq->game->getPath(i);
if (!p->nodes.empty())
{
if ((p->nodes[0].position - dsq->game->avatar->position).getSquaredLength2D() < sqr(1000))
{
p->songNoteDone(note, len);
}
}
}
}
/*
std::ostringstream os;
os << "notesOpen: " << notesOpen;
debugLog(os.str());
*/
if (notesOpen <= 0)
{
notesOpen = 0;
if (dsq->continuity.form == FORM_NORMAL)
avatar->setHeadTexture("");
}
}
void SongIcon::openInterface()
{
delay = 0;
alpha.interpolateTo(1, 0.1f);
}
void SongIcon::closeInterface()
{
closeNote();
delay = 0;
alpha.interpolateTo(0, 0.1f);
}
AvatarState::AvatarState()
{
abilityDelay = 0;
outOfWaterTimer = 0;
backFlip = false;
nearWall = false;
wasUnderWater = true;
blind = false;
lockedToWall = false;
shotDelay = 0;
spellCharge = 0;
leachTimer = 0;
swimTimer = 0;
rollTimer = 0;
updateLookAtTime = 0;
lookAtEntity = 0;
blinkTimer = 0;
}
void Avatar::toggleMovement(bool on)
{
canMove = on;
}
bool Avatar::isLockable()
{
return (bursting || !_isUnderWater) && (boneLockDelay == 0) && canLockToWall();
}
bool Avatar::isSinging()
{
return singing;
}
void Avatar::applyWorldEffects(WorldType type)
{
static bool oldfh=false;
if (type == WT_SPIRIT)
{
//skeletalSprite.transitionAnimate("ball", 0.1, -1);
//skeletalSprite.alpha.interpolateTo(0, 1);
//skeletalSprite.alpha = 0;
//dsq->game->addRenderObject(&skeletalSprite, LR_ENTITIES);
removeChild(&skeletalSprite);
skeletalSprite.position = position;
skeletalSprite.setFreeze(true);
skeletalSprite.scale = scale;
skeletalSprite.alpha.interpolateTo(0.5, 1);
//dsq->game->addRenderObject(&skeletalSprite, LR_ENTITIES);
skeletalSprite.rotation.z = rotation.z;
skeletalSprite.rotationOffset.z = rotationOffset.z;
oldfh = skeletalSprite.isfh();
skeletalSprite.fhTo(isfh());
renderQuad = true;
setTexture("glow");
width = 256;
height = 256;
setBlendType(BLEND_ADD);
fader->alpha.interpolateTo(0.75, 1);
dsq->sound->toggleEffectMusic(SFX_FLANGE, true);
}
else
{
//skeletalSprite.transitionAnimate("idle", 1, -1);
//skeletalSprite.alpha.interpolateTo(1, 1);
//skeletalSprite.alpha = 1;
//dsq->game->removeRenderObject(&skeletalSprite);
skeletalSprite.setFreeze(false);
if (!skeletalSprite.getParent())
{
addChild(&skeletalSprite, PM_STATIC);
}
skeletalSprite.position = Vector(0,0,0);
skeletalSprite.scale = Vector(1,1,1);
renderQuad = false;
setBlendType(BLEND_DEFAULT);
fader->alpha.interpolateTo(0, 1);
bool newfh = skeletalSprite.isfh();
skeletalSprite.fhTo(oldfh);
skeletalSprite.rotation.z = 0;
skeletalSprite.rotationOffset.z = 0;
fhTo(newfh);
dsq->sound->toggleEffectMusic(SFX_FLANGE, false);
}
}
void Avatar::startFlourish()
{
std::string anim = dsq->continuity.getInternalFormName() + "-flourish";
//if (skeletalSprite.getAnimation(anim))
Animation *fanim = skeletalSprite.getAnimation(anim);
if (fanim)
{
flourishTimer.start(fanim->getAnimationLength()-0.2f);
flourishPowerTimer.start(fanim->getAnimationLength()*0.5f);
}
skeletalSprite.transitionAnimate(anim, 0.1f, 0, ANIMLAYER_FLOURISH);
flourish = true;
float rotz = rotationOffset.z;
if (this->isfh())
rotationOffset = Vector(0,0,rotz+360);
else
rotationOffset = Vector(0,0,rotz-360);
FormType f = dsq->continuity.form;
if (f != FORM_NORMAL && f != FORM_BEAST && f != FORM_FISH && f != FORM_SUN && f != FORM_NATURE)
{
rotationOffset.z *= -1;
}
if (f == FORM_ENERGY || f == FORM_DUAL)
{
rotationOffset.z *= 2;
}
if (f == FORM_BEAST)
{
Vector v = getNormal();
if (!v.isZero())
{
v *= 400;
vel += v;
}
}
rotationOffset.interpolateTo(Vector(0,0,rotz), 0.8f, 0, 0, 1);
}
void Avatar::onIdle()
{
if (dsq->game->li)
{
if (dsq->game->li->getState() == STATE_HUG && riding)
{
dsq->game->li->setState(STATE_IDLE);
}
}
//stillTimer.stop();
stopBurst();
if (movingOn)
{
dsq->setMousePosition(Vector(400,300));
}
skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
stopRoll();
closeSingingInterface();
fallOffWall();
dsq->gameSpeed.stopPath();
dsq->gameSpeed.interpolateTo(1,0);
}
std::string Avatar::getBurstAnimName()
{
std::string ret;
switch(dsq->continuity.form)
{
case FORM_ENERGY:
ret = "energyburst";
break;
default:
ret = "burst";
break;
}
return ret;
}
std::string Avatar::getRollAnimName()
{
std::string ret;
switch(dsq->continuity.form)
{
case FORM_ENERGY:
ret = "energyroll";
break;
default:
ret = "roll";
break;
}
return ret;
}
std::string Avatar::getIdleAnimName()
{
std::string ret="idle";
switch(dsq->continuity.form)
{
case FORM_ENERGY:
ret="energyidle";
break;
case FORM_NORMAL:
case FORM_BEAST:
case FORM_NATURE:
case FORM_SPIRIT:
case FORM_DUAL:
case FORM_FISH:
case FORM_SUN:
case FORM_MAX:
case FORM_NONE:
break;
}
return ret;
}
void Avatar::clampPosition()
{
lastPosition = position;
}
void Avatar::updatePosition()
{
updateHair(0);
}
void Avatar::updateHair(float dt)
{
static float hairTimer = 0;
Bone *b = skeletalSprite.getBoneByIdx(0);
if (hair && b)
{
hair->alpha.x = alpha.x;
hair->color.x = color.x * multColor.x;
hair->color.y = color.y * multColor.y;
hair->color.z = color.z * multColor.z;
Vector headPos = b->getWorldCollidePosition(Vector(12,-32,0));
hair->setHeadPosition(headPos);
Vector diff = headPos - position;
Vector diff2;
if (!isfh())
diff2 = diff.getPerpendicularLeft();
else
diff2 = diff.getPerpendicularRight();
Vector diff3 = position - headPos;
if (state.lockedToWall && wallPushVec.y < 0 && (fabsf(wallPushVec.y) > fabsf(wallPushVec.x)))
{
if (isfh())
{
diff3 = Vector(-50, -25);
}
else
diff3 = Vector(50,-25);
}
float len =diff2.getLength2D();
diff3.setLength2D(len);
/*
diff.y = -diff.y;
diff = (diff + diff2)/2.0f;
*/
hairTimer += dt;
while (hairTimer > 2.0f)
{
hairTimer -= 2.0f;
}
float useTimer = hairTimer;
if (useTimer > 1.0f)
useTimer = 1.0f - (hairTimer-1);
float frc = 0.333333f;
diff = (diff2*(frc*(1.0f-(useTimer*0.5f))) + diff3*(frc) + Vector(0,len)*(frc*(0.5f+useTimer*0.5f)));
if (_isUnderWater)
{
diff.setLength2D(400);
//if (!vel.isLength2DIn(10))
hair->exertForce(diff, dt);
}
else
{
diff.setLength2D(400);
hair->exertForce(diff, dt);
}
if (!vel2.isZero())
hair->exertForce(vel2, dt);
hair->updatePositions();
}
}
void Avatar::updateDamageVisualEffects()
{
int damageThreshold = float(maxHealth/5.0f)*3.0f;
Quad *damageSprite = dsq->game->damageSprite;
if (health <= damageThreshold)
{
//dsq->game->damageSprite->alpha.interpolateTo(0.9, 0.5);
float a = ((damageThreshold - health)/float(damageThreshold))*1.0f;
damageSprite->alpha.interpolateTo(a, 0.3f);
/*
std::ostringstream os;
os << "damageSprite alpha: " << a;
debugLog(os.str());
*/
if(!damageSprite->scale.isInterpolating())
{
damageSprite->scale = Vector(1,1);
damageSprite->scale.interpolateTo(Vector(1.2f, 1.2f), 0.5f, -1, 1);
}
/*
if (health <= 0)
{
dsq->game->sceneColor.interpolateTo(Vector(1,0.5,0.5), 0.75);
}
*/
}
else
{
damageSprite->alpha.interpolateTo(0, 0.3f);
}
}
void Avatar::checkUpgradeForShot(Shot *s)
{
if (dsq->continuity.energyMult <= 1)
s->extraDamage = dsq->continuity.energyMult;
else
s->extraDamage = dsq->continuity.energyMult * 0.75f;
if (s->extraDamage > 0)
{
Quad *glow = new Quad("particles/glow", Vector(0,0));
glow->color = Vector(1,0,0);
glow->color.interpolateTo(Vector(1,0.5f,0.5f), 0.1f, -1, 1);
glow->setBlendType(BLEND_ADD);
glow->scale = Vector(4, 4) + (s->extraDamage*Vector(2,2));
glow->scale.interpolateTo(Vector(16,16)+ (s->extraDamage*Vector(2,2)), 0.5f, -1, 1);
s->addChild(glow, PM_POINTER);
}
}
void Avatar::onDamage(DamageData &d)
{
Entity::onDamage(d);
if (dsq->difficulty == DSQ::DIFF_EASY)
{
if (d.damage > 0)
d.damage *= MULT_DMG_EASY;
}
skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
if (dsq->continuity.form == FORM_NORMAL)
{
if (nocasecmp(dsq->continuity.costume, "CC")==0)
{
d.damage *= MULT_DMG_CRABCOSTUME;
}
}
if (riding != 0)
{
if (nocasecmp(dsq->continuity.costume, "seahorse")==0)
{
d.damage *= MULT_DMG_SEAHORSEARMOR;
}
}
if (dsq->continuity.form == FORM_FISH)
{
d.damage *= MULT_DMG_FISHFORM;
}
if ((core->isNested() && dsq->game->invincibleOnNested) || dsq->game->invinciblity)
{
d.damage = 0;
d.damageType = DT_NONE;
return;
}
if (d.damageType == DT_ENEMY_INK)
{
setBlind(d.effectTime);
return;
}
if (d.damageType == DT_ENEMY_POISON)
{
dsq->continuity.setPoison(1, d.effectTime);
}
if (dsq->continuity.defenseMultTimer.isActive())
{
d.damage *= dsq->continuity.defenseMult;
}
if (dsq->continuity.invincibleTimer.isActive())
d.damage = 0;
if (!canDie)
{
if ((health - d.damage) <= 0)
{
float i = d.damage;
while (i >= 0)
{
if ((health - i) > 0)
{
d.damage = i;
break;
}
i -= 0.5f;
}
}
}
if ((!invincible || !dsq->game->invincibleOnNested) && !(invincibleBreak && damageTimer.isActive() && d.useTimer) && !dsq->continuity.invincibleTimer.isActive())
{
if (d.damageType == DT_ENEMY_ACTIVEPOISON)
core->sound->playSfx("Poison");
else
core->sound->playSfx("Pain");
setHeadTexture("Pain", 1);
int r = (rand()%2)+1;
std::ostringstream os;
os << "basicHit" << r;
skeletalSprite.transitionAnimate(os.str(), 0.05f, 0, ANIMLAYER_OVERRIDE);
/*
if (d.attacker)
{
// this will probably cause a crash!
state.lookAtEntity = d.attacker;
}
*/
if (d.damage > 0)
{
float healthWillBe = health-d.damage;
// determines length of shader blur as well
float t = 0.5f;
if (healthWillBe<=0)
t = 2;
dsq->rumble(d.damage, d.damage, 0.4f, _lastActionSourceID, _lastActionInputDevice);
if (d.damage > 0)
{
//dsq->shakeCamera(5, t);
if (d.damage >= 1)
{
float shake = d.damage*2;
if (shake > 10)
shake = 10;
dsq->shakeCamera(shake, t);
}
if (healthWillBe <= 2 && d.damageType != DT_ENEMY_ACTIVEPOISON)
{
//if (!dsq->gameSpeed.isInterpolating() && dsq->gameSpeed.x==1)
{
dsq->gameSpeed.stop();
dsq->gameSpeed.stopPath();
dsq->gameSpeed.x = 1;
dsq->overlayRed->alpha.ensureData();
dsq->overlayRed->alpha.data->path.clear();
dsq->overlayRed->alpha.data->path.addPathNode(0, 0);
dsq->overlayRed->alpha.data->path.addPathNode(1, 0);
dsq->overlayRed->alpha.data->path.addPathNode(0, 1);
dsq->overlayRed->alpha.startPath(1);
dsq->sound->playSfx("heartbeat");
if (healthWillBe < 2 && healthWillBe >= 1 && !dsq->game->hasPlayedLow)
{
dsq->emote.playSfx(EMOTE_NAIJALOW);
dsq->game->hasPlayedLow = 1;
}
dsq->gameSpeed.ensureData();
dsq->gameSpeed.data->path.clear();
dsq->gameSpeed.data->path.addPathNode(1, 0);
dsq->gameSpeed.data->path.addPathNode(0.25f, 0.1f);
dsq->gameSpeed.data->path.addPathNode(0.25f, 0.4f);
dsq->gameSpeed.data->path.addPathNode(1, 1);
dsq->gameSpeed.startPath(2);
//dsq->gameSpeed.interpolateTo(0.7, 3);
}
//dsq->emote();
}
}
hitEmitter.load("NaijaHit");
hitEmitter.start();
playHitSound();
}
}
}
void Avatar::playHitSound()
{
int hitSound = (rand()%8)+1;
static int lastHitSound = 0;
if (lastHitSound == hitSound)
{
hitSound ++;
if (hitSound > 8)
hitSound = 1;
}
std::ostringstream os;
os << "hit" << hitSound;
core->sound->playSfx(os.str());
}
void Avatar::onHealthChange(float change)
{
updateDamageVisualEffects();
}
void Avatar::revive()
{
entityDead = false;
health = 0;
heal(maxHealth);
}
void Avatar::updateDualFormChargeEffects()
{
}
void Avatar::lostTarget(int i, Entity *e)
{
dsq->sound->playSfx("target-unlock");
}
void Avatar::entityDied(Entity *e)
{
Entity::entityDied(e);
for (size_t i = 0; i < targets.size(); i++)
{
if (targets[i].e == e)
{
lostTarget(i, 0);
targets[i].e = 0;
targetUpdateDelay = 100;
targets.clear();
break;
}
}
if (state.lookAtEntity==e)
state.lookAtEntity = 0;
// eating
if (e->isGoingToBeEaten())
{
EatType et = e->getEatType();
switch(et)
{
case EAT_FILE:
{
dsq->continuity.eatBeast(e->eatData);
}
break;
case EAT_DEFAULT:
case EAT_MAX:
case EAT_NONE:
break;
}
}
//debugLog("Entity died");
//e->lastDamage.damageType == DT_AVATAR_ENERGYBLAST &&
//debugLog("Entity died");
if (e->lastDamage.form == FORM_DUAL && e->lastDamage.damageType == DT_AVATAR_SHOCK)
{
dsq->continuity.dualFormCharge ++;
updateDualFormChargeEffects();
dsq->spawnParticleEffect("SpiritSteal", e->position);
//dsq->spawnParticleEffect("SpiritBeacon", position);
core->sound->playSfx("DualForm-Absorb");
if (dsq->continuity.dualFormCharge == requiredDualFormCharge)
core->sound->playSfx("DualForm-Charge");
}
/*
std::ostringstream os;
os << "lastDamage.form = " << e->lastDamage.form;
debugLog(os.str());
*/
}
void Avatar::enableInput()
{
ActionMapper::enableInput();
dsq->game->toggleMiniMapRender(1);
if (!dsq->game->isApplyingState())
dsq->toggleCursor(true);
if (movingOn)
{
dsq->setMousePosition(Vector(400,300));
}
if (dsq->continuity.form == FORM_ENERGY)
{
for (size_t i = 0; i < targetQuads.size(); i++)
targetQuads[i]->start();
}
setInvincible(false);
// can't do that here, cause it'll break the hug
//stillTimer.stop();
}
void Avatar::disableInput()
{
ActionMapper::disableInput();
// can't do that here, cause it'll break the hug
//stillTimer.stop();
closeSingingInterface();
dsq->game->toggleMiniMapRender(0);
dsq->toggleCursor(false);
endCharge();
clearTargets();
if (movingOn)
{
dsq->setMousePosition(Vector(400,300));
}
for (size_t i = 0; i < targetQuads.size(); i++)
{
targetQuads[i]->stop();
}
setInvincible(true);
}
void Avatar::clearTargets()
{
for (size_t i = 0; i < targets.size(); i++)
{
if (targets[i].e)
{
lostTarget(i, 0);
}
targets[i].e = 0;
}
}
void Avatar::openSingingInterface(InputDevice device)
{
if (!singing && health > 0 && !isEntityDead() && !blockSinging)
{
//core->mouse.position = Vector(400,300);
if (device != INPUT_MOUSE)
{
core->centerMouse();
//core->setMousePosition(Vector(400,300));
}
core->setMouseConstraintCircle(core->center, singingInterfaceRadius);
stopRoll();
singing = true;
currentSongIdx = SONG_NONE;
// make the singing icons appear
for (size_t i = 0; i < songIcons.size(); i++)
{
songIcons[i]->openInterface();
}
currentSong.notes.clear();
songInterfaceTimer = 0;
dsq->game->songLineRender->clear();
if (device == INPUT_JOYSTICK)
{
core->setMousePosition(core->center);
}
}
}
void Avatar::closeSingingInterface()
{
if (dsq->game->songLineRender)
dsq->game->songLineRender->clear();
if (singing)
{
core->setMouseConstraint(false);
quickSongCastDelay = 1;
// HACK: this prevents being "locked" away from the seahorse... so naija can
// be in singing range of the seahorse
applyRidingPosition();
singing = false;
for (size_t i = 0; i < songIcons.size(); i++)
{
songIcons[i]->closeInterface();
}
if (dsq->continuity.form == FORM_NORMAL)
setHeadTexture("");
currentSongIdx = dsq->continuity.checkSongAssisted(currentSong);
if (currentSongIdx != SONG_NONE)
{
dsq->continuity.castSong(currentSongIdx);
currentSongIdx = SONG_NONE;
}
}
}
void Avatar::toggleCape(bool on)
{
if (!hair) return;
if (!on)
hair->alphaMod = 0;
else
hair->alphaMod = 1;
}
void Avatar::refreshDualFormModel()
{
//charging = 0;
if (dsq->continuity.dualFormMode == Continuity::DUALFORM_NAIJA)
refreshModel("Naija", "DualForm_Naija");
else if (dsq->continuity.dualFormMode == Continuity::DUALFORM_LI)
refreshModel("Naija", "DualForm_Li");
}
void Avatar::updateDualFormGlow(float dt)
{
if (dsq->continuity.form == FORM_DUAL && bone_dualFormGlow)
{
float perc = 1;
if (requiredDualFormCharge != 0)
perc = float(dsq->continuity.dualFormCharge)/float(requiredDualFormCharge);
if (perc > 1)
perc = 1;
bone_dualFormGlow->alpha = perc*0.5f + 0.1f;
bone_dualFormGlow->scale.interpolateTo(Vector(perc, perc), 0.2f);
}
}
void Avatar::changeForm(FormType form, bool effects, bool onInit, FormType lastForm)
{
/*
if (core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter, 0.1,0.03,15,0.2f, 0.5));
*/
/*
if (pullTarget)
{
pullTarget->stopPull();
pullTarget = 0;
}
*/
if (form == FORM_DUAL && !dsq->continuity.hasLi())
return;
if (!canChangeForm) return;
std::ostringstream os;
os << "changeForm: " << form;
debugLog(os.str());
/*
if (dsq->game)
dsq->game->clearControlHint();
*/
if (lastForm == FORM_NONE)
lastForm = dsq->continuity.form;
endCharge();
std::ostringstream os2;
os2 << "lastForm: " << lastForm;
debugLog(os2.str());
for (size_t i = 0; i < targetQuads.size(); i++)
{
if (targetQuads[i])
targetQuads[i]->stop();
}
if (bone_dualFormGlow)
bone_dualFormGlow->scale = 0;
clearTargets();
if (form != FORM_NORMAL)
stopAura();
switch (lastForm)
{
case FORM_FISH:
{
// check nearby area
//bool isTooCloseToWall=false;
if (isNearObstruction(3))
{
Vector n = dsq->game->getWallNormal(position);
if (!n.isZero())
{
n *= 400;
vel += n;
}
return;
}
//rotationOffset.interpolateTo(Vector(0,0,0), 0.5);
collideRadius = COLLIDE_RADIUS_NORMAL;
setCanLockToWall(true);
setCollisionAvoidanceData(COLLIDE_RANGE_NORMAL, COLLIDE_MOD_NORMAL);
}
break;
case FORM_SUN:
lightFormGlow->alpha.interpolateTo(0, 0.5);
lightFormGlowCone->alpha.interpolateTo(0, 0.5);
break;
case FORM_SPIRIT:
//position.interpolateTo(bodyPosition, 2, 0);
position = bodyPosition;
dsq->continuity.warpLiToAvatar();
spiritBeaconEmitter.start();
setCanActivateStuff(true);
setCanLockToWall(true);
setCanBurst(true);
setDamageTarget(DT_WALLHURT, true);
break;
case FORM_BEAST:
setCanSwimAgainstCurrents(false);
break;
case FORM_DUAL:
if (dsq->continuity.hasLi())
{
dsq->game->li->alpha = 1;
dsq->game->li->position = position;
dsq->game->li->setState(STATE_IDLE);
}
break;
case FORM_NATURE:
setDamageTarget(DT_WALLHURT, true);
break;
default:
if (leftHandEmitter && rightHandEmitter)
{
leftHandEmitter->stop();
rightHandEmitter->stop();
}
break;
}
elementEffectMult = 1;
state.abilityDelay = 0;
formAbilityDelay = 0;
dsq->continuity.form = form;
formTimer = 0;
if (effects)
{
if (core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter,0.08f,0.05f,22,0.2f, 1.2f));
switch(form)
{
case FORM_ENERGY:
core->sound->playSfx("EnergyForm");
/*
dsq->game->tintColor.path.addPathNode(Vector(1,1,1),0);
dsq->game->tintColor.path.addPathNode(Vector(1.5,1.5,4),0.25);
dsq->game->tintColor.path.addPathNode(Vector(4,1.5,1),0.5);
dsq->game->tintColor.path.addPathNode(Vector(1,1,1),0.5);
dsq->game->tintColor.startPath(2);
*/
/*
dsq->game->tintColor = Vector(1,1,3);
dsq->game->tintColor.interpolateTo(Vector(1,1,1), 1);
*/
break;
case FORM_NORMAL:
core->sound->playSfx("NormalForm");
break;
case FORM_BEAST:
core->sound->playSfx("BeastForm");
break;
case FORM_FISH:
core->sound->playSfx("FishForm");
break;
case FORM_SUN:
core->sound->playSfx("SunForm");
break;
case FORM_NATURE:
core->sound->playSfx("NatureForm");
break;
case FORM_SPIRIT:
spiritBeaconEmitter.start();
break;
case FORM_DUAL:
core->sound->playSfx("DualForm");
break;
case FORM_NONE:
case FORM_MAX:
break;
}
/*
dsq->overlay->color = Vector(1,1,1);
dsq->overlay->alpha.interpolateTo(1.0, 0.2);
avatar->disableInput();
setv(EV_NOINPUTNOVEL, 0);
core->main(0.2);
setv(EV_NOINPUTNOVEL, 1);
dsq->overlay->alpha.interpolateTo(0, 0.2);
dsq->overlay->color.interpolateTo(0, 0.4);
*/
dsq->overlay->color = Vector(1,1,1);
dsq->overlay->alpha = 1;
dsq->overlay->alpha.interpolateTo(0, 0.5);
dsq->overlay->color.interpolateTo(0, 1.0);
}
/*
if (form != FORM_ENERGY)
{
dsq->game->sceneColor3.interpolateTo(Vector(1,1,1), 0.2);
}
*/
if (hair)
{
hair->alphaMod = 0;
}
switch (form)
{
case FORM_ENERGY:
refreshModel("Naija", "EnergyForm");
for (size_t i = 0; i < targetQuads.size(); i++)
targetQuads[i]->start();
leftHandEmitter->load("EnergyFormHandGlow");
leftHandEmitter->start();
rightHandEmitter->load("EnergyFormHandGlow");
rightHandEmitter->start();
break;
case FORM_FISH:
{
fallOffWall();
setBoneLock(BoneLock());
refreshModel("FishForm", "");
//rotationOffset.interpolateTo(Vector(0,0,-90), 0.5);
//refreshModel("NaijaFish", "");
collideRadius = COLLIDE_RADIUS_FISH;
setCanLockToWall(false);
setCollisionAvoidanceData(COLLIDE_RANGE_FISH, COLLIDE_MOD_FISH);
elementEffectMult = 0.4f;
}
break;
case FORM_SUN:
{
refreshModel("Naija", "SunForm");
lightFormGlow->moveToFront();
lightFormGlow->alpha.interpolateTo(0.75f, 1);
lightFormGlowCone->alpha.interpolateTo(0.4f, 1);
lightFormGlow->alphaMod = 0;
lightFormGlowCone->alphaMod = 0;
}
break;
case FORM_NORMAL:
{
if (lastForm == FORM_SPIRIT)
{
dsq->continuity.shiftWorlds();
fallOffWall();
}
refreshNormalForm();
//skeletalSprite.loadSkeletal("child");
}
break;
case FORM_NATURE:
refreshModel("Naija", "NatureForm");
if (hair)
{
hair->setTexture("Naija/Cape-NatureForm");
hair->alphaMod = 1.0;
}
setDamageTarget(DT_WALLHURT, false);
break;
case FORM_BEAST:
{
refreshModel("Naija", "BeastForm");
setCanSwimAgainstCurrents(true);
}
break;
case FORM_SPIRIT:
bodyPosition = position;
bodyOffset = offset;
fallOffWall();
dsq->continuity.shiftWorlds();
setCanActivateStuff(false);
setCanLockToWall(false);
setCanBurst(false);
setDamageTarget(DT_WALLHURT, false);
elementEffectMult = 0;
if (onInit)
{
skeletalSprite.alphaMod = 0;
canChangeForm = false;
}
/*
if (hair)
hair->alphaMod = lastHairAlphaMod;
*/
break;
case FORM_DUAL:
{
if (dsq->continuity.hasLi())
{
dsq->game->li->setState(STATE_WAIT);
dsq->game->li->alpha = 0;
}
//dualFormMode = DUALFORM_LI;
refreshDualFormModel();
/*
for (int i = 0; i < targetQuads.size(); i++)
targetQuads[i]->start();
*/
}
break;
default:
break;
}
setHeadTexture("");
if (effects)
avatar->enableInput();
//if (onInit) {
//idle();//skeletalSprite.animate("idle", -1, 0);
//}
}
void Avatar::singNote(int note)
{
currentSong.notes.push_back(note);
}
void Avatar::updateSingingInterface(float dt)
{
if (songIcons.size()>0 && songIcons[0]->alpha.x > 0)
{
if (dsq->inputMode != INPUT_JOYSTICK && !core->mouse.change.isZero())
{
if (dsq->game->songLineRender && songIcons[0]->alpha.x == 1)
{
float smallestDist = HUGE_VALF;
int closest = -1;
for (size_t i = 0; i < songIcons.size(); i++)
{
float dist = (songIcons[i]->position - core->mouse.position).getSquaredLength2D();
if (dist < smallestDist)
{
smallestDist = dist;
closest = i;
}
}
dsq->game->songLineRender->newPoint(core->mouse.position, songIcons[closest]->noteColor);
}
}
if (health <= 0 || isEntityDead())
{
closeSingingInterface();
}
else
{
if (dsq->inputMode == INPUT_JOYSTICK)
{
Vector d;
for(size_t i = 0; i < core->getNumJoysticks(); ++i)
if(Joystick *j = core->getJoystick(i))
if(j->isEnabled())
{
d = j->position;
if(!d.isZero())
break;
}
if (d.isLength2DIn(JOYSTICK_NOTE_THRESHOLD))
{
core->setMousePosition(core->center);
}
else
{
// Choose the closest note based on the joystick input
// angle (rather than the resultant cursor position).
// But if we already have an active note and we're not
// within the note-accept threshold, maintain the
// current note instead.
float angle = (atan2f(-d.y, d.x) * 180 / PI) + 90;
if (angle < 0)
angle += 360;
int closestNote = (int)floorf(angle/45 + 0.5f);
float angleOffset = fabsf(angle - closestNote*45);
if (closestNote == 8)
closestNote = 0;
bool setNote = (angleOffset <= NOTE_ACCEPT_ANGLE_OFFSET);
if (!setNote)
{
bool alreadyAtNote = false;
for (size_t i = 0; i < songIcons.size(); i++)
{
const float dist = (songIcons[i]->position - core->mouse.position).getSquaredLength2D();
if (dist <= sqr(NOTE_ACCEPT_DISTANCE))
{
alreadyAtNote = true;
break;
}
}
if (!alreadyAtNote)
setNote = true;
}
if (setNote)
core->setMousePosition(songIcons[closestNote]->position);
}
}
setSongIconPositions();
}
}
}
void Avatar::setSongIconPositions()
{
float radIncr = (2*PI)/float(songIcons.size());
float rad = 0;
for (size_t i = 0; i < songIcons.size(); i++)
{
songIcons[i]->position = Vector(400,300)+/*this->position + */Vector(sinf(rad)*singingInterfaceRadius, cosf(rad)*singingInterfaceRadius);
rad += radIncr;
}
}
const int chkDist = 2500*2500;
Target Avatar::getNearestTarget(const Vector &checkPos, const Vector &distPos, Entity *source, DamageType dt, bool override, std::vector<Target> *ignore)
{
BBGE_PROF(Avatar_getNearestTarget);
Target t;
Vector targetPosition;
int targetPt = -1;
Entity *closest = 0;
int highestPriority = -999;
float smallestDist = HUGE_VALF;
Entity *e = 0;
FOR_ENTITIES(i)
{
e = *i;
/*
int j;
for (j = 0; j < targets.size(); j++)
{
if (targets[j].e == e) break;
}
if (j != targets.size()) continue;
*/
//e &&
if (e != this && e->targetPriority >= highestPriority && this->pullTarget != e && e->isDamageTarget(dt) && dsq->game->isValidTarget(e, this))
{
if (e->position.isNan())
//if (false)
{
std::ostringstream os;
os << "NAN position entity name: " << e->name << " type: " << e->getEntityType();
debugLog(os.str());
continue;
}
else
{
int dist = (e->position - position).getSquaredLength2D();
if (dist < chkDist)
{
int numTargetPoints = e->getNumTargetPoints();
bool clearAfter = false;
if (numTargetPoints == 0)
{
if (ignore)
{
size_t j = 0;
for (; j < ignore->size(); j++)
{
if ((*ignore)[j].e == e)
break;
}
if (j != ignore->size()) continue;
}
e->addTargetPoint(e->getEnergyShotTargetPosition());
clearAfter = true;
numTargetPoints = 1;
}
if (numTargetPoints > 0)
{
for (int i = 0; i < numTargetPoints; i++)
{
if (ignore)
{
size_t j = 0;
for (; j < ignore->size(); j++)
{
if ((*ignore)[j].e == e && (*ignore)[j].targetPt == i)
break;
}
if (j != ignore->size()) continue;
}
float dist = (e->getTargetPoint(i) - distPos).getSquaredLength2D();
//float dist = (e->getTargetPoint(i) - distPos).getLength2D();
if (dist < sqr(TARGET_RANGE+e->getTargetRange()))
{
if (override || (checkPos - e->getTargetPoint(i)).isLength2DIn(64))
{
dist = (e->getTargetPoint(i) - checkPos).getSquaredLength2D();
if (dist < smallestDist)
{
highestPriority = e->targetPriority;
targetPosition = e->getTargetPoint(i);
closest = e;
smallestDist = dist;
targetPt = i;
}
}
}
}
}
if (clearAfter)
e->clearTargetPoints();
}
}
}
}
t.e = closest;
t.pos = targetPosition;
t.targetPt = targetPt;
return t;
}
float maxTargetDelay = 0.5;
bool wasDown = false;
void Avatar::updateTargets(float dt, bool override)
{
DamageType damageType = DT_AVATAR_ENERGYBLAST;
for (size_t i = 0; i < targets.size(); i++)
{
if (!targets[i].e
|| !targets[i].e->isPresent()
|| targets[i].e->getState() == STATE_DEATHSCENE
|| !dsq->game->isValidTarget(targets[i].e, this))
{
targets.clear();
break;
}
}
if ((dsq->inputMode == INPUT_MOUSE || dsq->inputMode == INPUT_KEYBOARD) && !(wasDown && core->mouse.buttons.right))
{
wasDown = false;
float mod = 1;
if (isCharging())
mod = maxTargetDelay*10;
targetUpdateDelay += dt*mod;
}
if (targetUpdateDelay > maxTargetDelay || override)
{
maxTargetDelay = 0;
std::vector<Target> oldTargets = targets;
if ((dsq->continuity.form == FORM_ENERGY) && ((core->mouse.buttons.right && state.spellCharge > 0.3f) || override))
//&& state.spellCharge > 0.2f /*&& state.spellCharge < 0.5f*/
{
// crappy hack for now, assuming one target:
targets.clear();
Vector dir = getAim();
Vector checkPos = position + dir;
Vector distPos = position;
if (!(dsq->getGameCursorPosition() - distPos).isLength2DIn(4))
{
Target t;
t = getNearestTarget(checkPos, distPos, this, damageType, override, &targets);
if (t.e)
{
//if ((t.getWorldPosition() - dsq->getGameCursorPosition()).isLength2DIn(64))
{
// found a target?
targets.push_back(t);
targetUpdateDelay = 0;
if (!override && core->mouse.buttons.right)
{
maxTargetDelay = 90;
dsq->spawnParticleEffect("TargetAquired", t.pos);
wasDown = true;
}
}
}
}
if (targets.empty())
{
for (size_t i = 0; i < oldTargets.size(); i++)
{
Entity *e = oldTargets[i].e;
if (e)
{
int dist = (e->getTargetPoint(oldTargets[i].targetPt) - distPos).getSquaredLength2D();
if (dist < sqr(TARGET_RANGE+e->getTargetRange()))
{
targets.push_back(oldTargets[i]);
}
}
else
{
targets.clear();
break;
}
}
}
}
}
else
{
for (size_t i = 0; i < targets.size(); i++)
{
Entity *e = targets[i].e;
if (e)
{
if (!(position - e->position).isLength2DIn(e->getTargetRange() + TARGET_RANGE + TARGET_GRACE_RANGE) || !dsq->game->isValidTarget(e, this) || !e->isDamageTarget(damageType))
{
lostTarget(i, targets[i].e);
targets[i].e = 0;
targetUpdateDelay = maxTargetDelay;
wasDown = false;
}
}
}
}
}
void Avatar::updateTargetQuads(float dt)
{
const Vector cursorpos = dsq->getGameCursorPosition();
particleManager->setSuckPosition(1, cursorpos);
/*
for (int i = 0; i < targetQuads.size(); i++)
{
}
*/
static Entity *lastTargetE = 0;
const float tt = 0.02f;
for (size_t i = 0; i < targets.size(); i++)
{
if (targets[i].e)
{
targetQuads[i]->alpha.interpolateTo(1, 0.1f);
Entity *e = targets[i].e;
if (lastTargetE != e)
{
dsq->sound->playSfx("target-lock");
lastTargetE = e;
}
else
{
//targetQuads[i]->position.interpolateTo(targets[i].pos, 0.01);
}
targetQuads[i]->position.interpolateTo(targets[i].pos, tt);
targets[i].pos = e->getTargetPoint(targets[i].targetPt);
if (i == 0)
{
particleManager->setSuckPosition(1, targets[i].pos); // suckpos 1 is overridden elsewhere later
particleManager->setSuckPosition(2, targets[i].pos);
}
/*
Emitter *em = targetQuads[i];
if (!em->isRunning())
{
em->start();
}
*/
}
else
{
targetQuads[i]->position = cursorpos;
//targetQuads[i]->alpha.interpolateTo(0, 0.1);
}
}
if (targets.empty())
{
for (size_t i = 0; i < targetQuads.size(); i++)
{
if (lastTargetE != 0)
{
lastTargetE = 0;
}
//targetQuads[i]->position.interpolateTo(dsq->getGameCursorPosition(),tt);
/*
std::ostringstream os;
os << "setting targetQuads[i] to game cursor, is running = " << targetQuads[i]->isRunning();
debugLog(os.str());
*/
targetQuads[i]->position = cursorpos;
if (dsq->continuity.form == FORM_ENERGY && isInputEnabled())
{
if (dsq->inputMode == INPUT_JOYSTICK && targetQuads[i]->isRunning())
{
targetQuads[i]->stop();
}
else if (dsq->inputMode != INPUT_JOYSTICK && !targetQuads[i]->isRunning())
{
targetQuads[i]->start();
}
}
/*
if (targetQuads[i]->isRunning())
{
targetQuads[i]->stop();
}
*/
}
}
}
//fireAtNearestValidEntity("Fire", DT_AVATAR_ENERGYBLAST);
bool Avatar::fireAtNearestValidEntity(const std::string &shot)
{
if (state.swimTimer > 0)
state.swimTimer -= 0.5f;
skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
targetUpdateDelay = 0;
if (targetUpdateDelay < 0)
targetUpdateDelay = 0;
//bool big = false;
Vector dir;
Vector p = position;
if(boneLeftArm)
p = boneLeftArm->getWorldPosition();
//&& !dsq->game->isObstructed(TileVector(position))
/*
if (dsq->inputMode == INPUT_MOUSE && state.lockedToWall )
dir = dsq->getGameCursorPosition() - p;
else
*/
dir = getAim();
ShotData *shotData = Shot::getShotData(shot);
bool aimAt = (dir.z == 1.0f);
//bool aimAt = true;
dir.z = 0;
Vector targetPosition;
//std::vector<Target>targets;
bool firedShot = false;
//int homing = 0;
/*
if (target)
{
if (dsq->inputMode != INPUT_JOYSTICK && vel.isLength2DIn(50))
{
}
else
{
}
homing = home;
}
else
homing = 0;
*/
/*
if (!dir.isLength2DIn(2))
{
*/
Shot *s = 0;
bool clearTargets = false;
// allow autoAim if desired
if ((dsq->inputMode == INPUT_JOYSTICK && !aimAt) || dsq->user.control.autoAim)
{
if (targets.empty())
{
// force a grab of the nearest targets
updateTargets(shotData->damageType, true);
// clear the targets after
clearTargets = true;
}
}
if (!targets.empty())
{
//homing = home;
for (size_t i = 0; i < targets.size(); i++)
{
/*
if (!aimAt)
{
dir = targets[i].pos - p;
}
*/
/*
std::ostringstream os;
os << "shotdir(" << dir.x << ", " << dir.y << ")";
debugLog(os.str());
*/
/*
Vector oldDir = dir;
dir.normalize2D();
dir = (dir + oldDir)/2.0f;
*/
if (!aimAt)
{
dir = (targets[i].e->getTargetPoint(targets[i].targetPt) - p);
}
s = dsq->game->fireShot(shot, this, targets[i].e);
s->setAimVector(dir);
s->setTargetPoint(targets[i].targetPt);
/*
if (dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY2))
{
s = dsq->game->fireShot("EnergyBlast2", this, targets[i].e);
s->setAimVector(dir);
s->setTargetPoint(targets[i].targetPt);
}
else
{
s = dsq->game->fireShot("EnergyBlast", this, targets[i].e);
s->setAimVector(dir);
s->setTargetPoint(targets[i].targetPt);
}
*/
}
}
else
{
//if (!dir.isLength2DIn(2) || dsq->inputMode == INPUT_JOYSTICK)
if (true)
{
s = dsq->game->fireShot(shot, this);
if (dir.isLength2DIn(2))
{
if (!vel.isLength2DIn(2))
s->setAimVector(vel);
else // standing still
s->setAimVector(getForwardAim());
}
else
{
s->setAimVector(dir);
}
/*
if (dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY2))
{
s = dsq->game->fireShot("EnergyBlast2", this);
s->setAimVector(dir);
}
else
{
s = dsq->game->fireShot("EnergyBlast", this);
s->setAimVector(dir);
}
*/
}
}
if (s)
{
checkUpgradeForShot(s);
skeletalSprite.transitionAnimate("fireBlast", 0.1f, 0, ANIMLAYER_ARMOVERRIDE);
s->position = p;
//s->damageType = dt;
/*
if (!targets.empty())
s->damage = float(damage)/float(targets.size());
*/
firedShot = true;
}
if (clearTargets)
{
targets.clear();
// try to avoid targets sticking
updateTargetQuads(shotData->damageType);
}
return firedShot;
}
void Avatar::switchDualFormMode()
{
//debugLog("dualForm: changing");
dsq->sound->playSfx("dualform-switch");
dsq->overlay->color = Vector(1,1,1);
dsq->fade(1, 0);
dsq->fade(0, 0.5);
if (dsq->continuity.dualFormMode == Continuity::DUALFORM_NAIJA)
dsq->continuity.dualFormMode = Continuity::DUALFORM_LI;
else
dsq->continuity.dualFormMode = Continuity::DUALFORM_NAIJA;
refreshDualFormModel();
}
bool Avatar::hasThingToActivate()
{
return ((pathToActivate != 0) || (entityToActivate != 0));
}
void Avatar::formAbility()
{
if (hasThingToActivate()) return;
//debugLog("form ability function");
switch(dsq->continuity.form)
{
case FORM_DUAL:
{
debugLog("dual form ability");
/*
if (this->getVectorToCursorFromScreenCentre().isLength2DIn(minMouse))
{
debugLog("in and changing");
if (dualFormMode == DUALFORM_NAIJA)
dualFormMode = DUALFORM_LI;
else
dualFormMode = DUALFORM_NAIJA;
refreshDualFormModel();
}
else
*/
{
/*
if (chargeLevelAttained == 2)
{
if (dualFormMode == DUALFORM_NAIJA)
dualFormMode = DUALFORM_LI;
else
dualFormMode = DUALFORM_NAIJA;
refreshDualFormModel();
}
else
*/
{
if (dsq->continuity.dualFormMode == Continuity::DUALFORM_NAIJA)
{
// ~~~~~~~~~~ SOUL SCREAM
if (chargeLevelAttained == 1)
{
if (dsq->continuity.dualFormCharge >= requiredDualFormCharge)
{
core->sound->playSfx("DualForm-Scream");
if (core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter,0.08f,0.05f,22,0.2f, 1.2f));
dsq->continuity.dualFormCharge = 0;
dsq->shakeCamera(25, 2);
core->globalScale = Vector(0.4f, 0.4f);
core->globalScaleChanged();
myZoom = Vector(0.4f, 0.4f);
/*
setv(EV_NOINPUTNOVEL, 0);
core->globalScale = Vector(1.5, 1.5);
core->main(0.5);
setv(EV_NOINPUTNOVEL, 1);
*/
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e->getEntityType() == ET_ENEMY && e != this)
{
if (e->isv(EV_SOULSCREAMRADIUS, -1) || (e->position - position).isLength2DIn(1000 + e->getv(EV_SOULSCREAMRADIUS)))
{
DamageData d;
d.damage = 20;
d.damageType = DT_AVATAR_DUALFORMNAIJA;
d.attacker = this;
d.form = dsq->continuity.form;
e->damage(d);
}
}
}
/*
setv(EV_NOINPUTNOVEL, 0);
core->main(0.5);
dsq->screenTransition->capture();
dsq->screenTransition->go(0.5);
myZoom = Vector(1,1);
setv(EV_NOINPUTNOVEL, 1);
*/
}
else
{
core->sound->playSfx("Denied");
}
}
}
else if (dsq->continuity.dualFormMode == Continuity::DUALFORM_LI)
{
if (chargeLevelAttained == 1)
{
int i = 0;
int num = 5;
for (; i < num; i++)
{
Shot *s = dsq->game->fireShot("DualForm", this, 0, position, 0);
//*0.5f + getAim()*0.5f
Vector v1 = this->getTendrilAimVector(i, num);
Vector v2 = getAim();
v1.normalize2D();
v2.normalize2D();
s->setAimVector(v1*0.1f + v2*0.9f);
}
core->sound->playSfx("DualForm-Shot");
dsq->spawnParticleEffect("DualFormFire", position);
/*
didShockDamage = false;
doShock("DualFormLiTendril");
*/
}
else
{
core->sound->playSfx("Denied");
/*
if (!fireDelay)
{
if (fireAtNearestValidEntity("DualFormLi"))
{
fireDelay = fireDelayTime;
}
}
*/
}
}
}
}
}
break;
case FORM_ENERGY:
{
if (chargeLevelAttained == 2)
{
if (dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY2))
doShock("EnergyTendril2");
else
doShock("EnergyTendril");
if (!state.lockedToWall)
skeletalSprite.animate("energyChargeAttack", 0, ANIMLAYER_UPPERBODYIDLE);
/*
if (core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter, 0.1,0.03,30,0.2f, 1.5));
*/
dsq->playVisualEffect(VFX_SHOCK, position, this);
}
else
{
if (!fireDelay)
{
std::string shotName;
if (dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY2))
shotName = "EnergyBlast2";
else
shotName = "EnergyBlast";
if (fireAtNearestValidEntity(shotName))
{
fireDelay = fireDelayTime;
}
}
}
}
break;
case FORM_NATURE:
if (formAbilityDelay == 0)
{
formAbilityDelay = 0.2f;
//Vector pos = dsq->getGameCursorPosition() - position;
Vector pos = getAim();
if (!pos.isZero())
pos.setLength2D(16);
pos += position;
std::string seedName;
if (chargeLevelAttained == 0)
seedName = "SeedFlower";
else if (chargeLevelAttained == 2)
seedName = "SeedUberVine";
dsq->game->fireShot(seedName, this, 0, pos, getAim());
/*
Vector pos = getAim();
if (!pos.isZero())
pos.setLength2D(64);
pos += position;
//dsq->spawnParticleEffect("Fertilizer", pos);
Entity *e = 0;
std::string seedName;
if (chargeLevelAttained == 0)
seedName = "SeedFlower";
else if (chargeLevelAttained == 2)
seedName = "SeedUberVine";
e = dsq->game->createEntity(seedName, 0, pos, 0, false, "");
Vector add = pos - position;
add.setLength2D(800);
e->vel += add;
*/
/*
if (chargeLevelAttained == 0)
{
}
else if (chargeLevelAttained == 1)
{
e->setState(STATE_CHARGE1);
}
else if (chargeLevelAttained == 2)
{
e->setState(STATE_CHARGE2);
}
e->update(0);
*/
// idle = charge 0
// attack = charge1
// something = charge2
/*
FOR_ENTITIES (i)
{
Entity *e = *i;
if (e && e->getEntityType() == ET_ENEMY && e->isDamageTarget(DT_AVATAR_NATURE))
{
if ((e->position - pos).isLength2DIn(128))
{
DamageData d;
d.damageType = DT_AVATAR_NATURE;
d.damage = 1;
d.attacker = this;
e->damage(d);
}
}
}
*/
}
break;
case FORM_BEAST:
{
if (!dsq->continuity.isNaijaEatsEmpty())
{
EatData *d = dsq->continuity.getLastNaijaEat();
if (!d->shot.empty())
{
int num = getNumShots()-2;
for (int i = 0; i < num; i++)
{
bool playSfx = true;
if (i > 0)
playSfx = false;
Shot *s = dsq->game->fireShot(d->shot, this, 0, Vector(0,0,0), Vector(0,0,0), playSfx);
if (s->shotData && s->shotData->damage > 0)
{
s->extraDamage = 1;
}
Entity *target = 0;
if (s->shotData->homing > 0)
{
Vector p = dsq->getGameCursorPosition();
target = dsq->game->getNearestEntity(p, 800, this, ET_ENEMY, s->getDamageType());
}
if (target)
{
s->target = target;
}
if (bone_head)
{
s->position = bone_head->getWorldPosition();
}
else
{
s->position = this->position;
}
Vector aim = getVectorToCursor();
if (aim.isZero())
aim = getForwardAim();
if (num == 1)
s->setAimVector(aim);
else
{
aim.normalize2D();
s->setAimVector(getTendrilAimVector(i, num)*0.1f + aim*0.9f);
}
if (s->shotData && s->shotData->avatarKickBack)
{
Vector d = s->velocity;
d.setLength2D(-s->shotData->avatarKickBack);
float effect = 1;
if (!isUnderWater())
effect = 0.4f;
push(d, s->shotData->avatarKickBackTime * effect, s->shotData->avatarKickBack * effect, 0);
}
}
debugLog("firing: " + d->shot);
d->ammo--;
}
if (d->ammo <= 0)
dsq->continuity.removeLastNaijaEat();
//dsq->continuity.removeEatData(eats.size()-1);
}
/*
switch(inTummy)
{
case EAT_BASICSHOT:
{
tummyAmount --;
// FIRE!
if (tummyAmount <= 0)
{
//fireAtNearestValidEntity("Vomit", inTummy, DT_AVATAR_VOMIT, 6000);
inTummy = EAT_NONE;
}
}
break;
}
*/
/*
if (inTummy > 0)
{
if (inTummy > 3)
inTummy = 3;
if (fireAtNearestValidEntity("Vomit", inTummy, DT_AVATAR_VOMIT, 6000))
{
inTummy = 0;
}
}
*/
/*
if (ability == 0)
{
Vector bitePos = position;
Vector offset = vel;
offset.setLength2D(128);
bitePos += offset;
FOR_ENTITIES (i)
{
Entity *e = *i;
if (e && (e->position - bitePos).getSquaredLength2D() < sqr(64))
{
DamageData d;
d.attacker = this;
d.damage = 2;
e->damage(d);
heal(1);
}
}
}
else
{
}
*/
}
break;
case FORM_SUN:
{
if (formAbilityDelay == 0 && chargeLevelAttained==1)
{
core->sound->playSfx("SunForm");
//dsq->spawnParticleEffect("LightFlare", position);
chargeEmitter->load("SunFlare");
chargeEmitter->start();
PauseQuad *q = new PauseQuad;
q->setTexture("Naija/LightFormGlow");
q->position = position;
q->setWidthHeight(1024, 1024);
q->setLife(1);
q->setDecayRate(0.05f);
q->fadeAlphaWithLife = 1;
q->scale = Vector(0,0);
q->scale.interpolateTo(Vector(2,2), 0.1f);
dsq->game->addRenderObject(q, LR_ELEMENTS13);
q->moveToFront();
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e != this && (e->position - position).isLength2DIn(2048))
{
e->lightFlare();
}
}
//formAbilityDelay = 0.1;
}
}
break;
case FORM_SPIRIT:
// spirit beacon
// absorbs nearby shots, and respawns the player if in a "SPIRITBEACON" node
if (formAbilityDelay == 0)
{
core->sound->playSfx("Spirit-Beacon");
//dsq->spawnParticleEffect("SpiritBeacon", position);
std::list<Shot*> delShots;
Shot::Shots::iterator i;
for (i = Shot::shots.begin(); i != Shot::shots.end(); i++)
{
Shot *s = (*i);
if (s->isActive() && s->shotData && !s->shotData->invisible)
{
if (!s->firer || s->firer->getEntityType()==ET_ENEMY)
{
if ((s->position - position).isLength2DIn(256))
{
//s->safeKill();
delShots.push_back(s);
spiritEnergyAbsorbed++;
}
}
}
}
for (std::list<Shot*>::iterator j = delShots.begin(); j != delShots.end(); j++)
{
Shot *s = (*j);
s->safeKill();
}
if (spiritEnergyAbsorbed > 4)
{
dsq->game->spawnManaBall(position, 1);
spiritEnergyAbsorbed = 0;
}
spiritBeaconEmitter.start();
formAbilityDelay = 1.0;
Path *p = dsq->game->getNearestPath(position, "SPIRITBEACON");
if (p && p->isCoordinateInside(position))
{
bodyPosition = position;
if (pullTarget)
{
pullTarget->position = position;
}
revert();
}
else
{
Path *p = dsq->game->getNearestPath(position, PATH_SPIRITPORTAL);
if (p && p->isCoordinateInside(position))
{
changeForm(FORM_NORMAL);
dsq->game->warpToSceneFromNode(p);
}
}
}
break;
case FORM_FISH:
{
}
break;
case FORM_NORMAL:
case FORM_NONE:
case FORM_MAX:
break;
}
}
Vector Avatar::getTendrilAimVector(int i, int max)
{
float a = float(float(i)/float(max))*PI*2;
Vector aim(sinf(a), cosf(a));
if (state.lockedToWall)
{
Vector n = dsq->game->getWallNormal(position);
if (!n.isZero())
{
aim = aim*0.4f + n*0.6f;
}
}
return aim;
}
size_t Avatar::getNumShots()
{
size_t thits = normalTendrilHits;
if (flourishPowerTimer.isActive())
{
if (lastBurstType == BURST_WALL)
thits = maxTendrilHits;
else
thits = rollTendrilHits;
}
else
{
if (bursting)
{
if (lastBurstType == BURST_WALL)
thits = rollTendrilHits;
}
}
return thits;
}
void Avatar::doShock(const std::string &shotName)
{
size_t c = 0;
std::vector <Entity*> entitiesToHit;
std::vector <Target> localTargets;
bool clearTargets = true;
size_t thits = getNumShots();
if (!targets.empty() && targets[0].e != 0)
{
clearTargets = false;
for (size_t i = 0; i < thits; i++)
{
entitiesToHit.push_back(targets[0].e);
}
}
else
{
localTargets.clear();
while (c < thits)
{
Target t = getNearestTarget(position, position, this, DT_AVATAR_SHOCK, true, &localTargets);
if (t.e)
{
localTargets.push_back(t);
entitiesToHit.push_back(t.e);
targets.push_back(t);
c ++;
}
else
{
break;
}
}
if (!localTargets.empty())
{
while (entitiesToHit.size()<thits)
{
for (size_t i = 0; i < localTargets.size(); i++)
{
if (!(entitiesToHit.size()<thits))
break;
entitiesToHit.push_back(localTargets[i].e);
targets.push_back(localTargets[i]);
}
}
}
localTargets.clear();
}
Vector aim = getAim();
aim.normalize2D();
int sz = entitiesToHit.size();
if (sz == 0)
{
for (size_t i = 0; i < thits; i++)
{
Shot *s = dsq->game->fireShot(shotName, this, 0);
s->setAimVector(getTendrilAimVector(i, thits));
checkUpgradeForShot(s);
}
}
else
{
for (int i = 0; i < sz; i++)
{
Entity *e = entitiesToHit[i];
if (e)
{
Shot *s = dsq->game->fireShot(shotName, this, e);
if (!targets.empty())
{
for (size_t j = 0; j < targets.size(); j++)
{
if (targets[j].e == e)
s->targetPt = targets[j].targetPt;
}
}
s->setAimVector(getTendrilAimVector(i, thits));
checkUpgradeForShot(s);
}
}
}
if (clearTargets)
{
targets.clear();
}
}
void Avatar::formAbilityUpdate(float dt)
{
switch(dsq->continuity.form)
{
case FORM_FISH:
{
if (core->mouse.buttons.right)
{
const float bubbleRate = 0.2f;
state.abilityDelay -= dt;
if (state.abilityDelay < 0)
state.abilityDelay = 0;
if (state.abilityDelay == 0)
{
state.abilityDelay = bubbleRate;
//state.abilityDelay -= bubbleRate;
// spawn bubble
//Entity *bubble = dsq->game->createEntity("FishFormBubble", 0, position, 0, false, "");
Vector dir = getAim();
dir.normalize2D();
dsq->game->fireShot("FishFormBubble", this, 0, position+dir*16, dir);
}
}
}
break;
case FORM_ENERGY:
case FORM_NORMAL:
case FORM_BEAST:
case FORM_NATURE:
case FORM_SPIRIT:
case FORM_DUAL:
case FORM_SUN:
case FORM_MAX:
case FORM_NONE:
break;
}
}
bool Avatar::isMouseInputEnabled()
{
if (!inputEnabled) return false;
//if (dsq->continuity.getWorldType() != WT_NORMAL) return false;
//if (getState() != STATE_IDLE) return false;
if (dsq->game->isPaused()) return false;
return true;
}
void Avatar::rmbd(int source, InputDevice device)
{
if (!isMouseInputEnabled() || isEntityDead()) return;
if (dsq->continuity.form == FORM_NORMAL )
{
if (device == INPUT_MOUSE)
{
Vector diff = getVectorToCursorFromScreenCentre();
if (diff.getSquaredLength2D() < sqr(openSingingInterfaceRadius))
openSingingInterface(device);
}
else
{
openSingingInterface(device);
}
}
else
{
startCharge();
}
}
void Avatar::rmbu(int source, InputDevice device)
{
if (!isMouseInputEnabled() || isEntityDead()) return;
if (charging)
{
if (!entityToActivate && !pathToActivate)
formAbility();
endCharge();
}
dsq->cursorGlow->alpha.interpolateTo(0, 0.2f);
dsq->cursorBlinker->alpha.interpolateTo(0, 0.2f);
if (singing)
{
closeSingingInterface();
}
if (entityToActivate)
{
activateEntity = entityToActivate;
entityToActivate = 0;
}
if (pathToActivate)
{
pathToActivate->activate();
pathToActivate = 0;
}
}
bool Avatar::canCharge()
{
switch(dsq->continuity.form)
{
case FORM_ENERGY:
case FORM_DUAL:
case FORM_NATURE:
case FORM_SUN:
return true;
break;
case FORM_BEAST:
case FORM_NORMAL:
case FORM_SPIRIT:
case FORM_FISH:
case FORM_MAX:
case FORM_NONE:
break;
}
return false;
}
void Avatar::startCharge()
{
if (!isCharging() && canCharge())
{
if (dsq->loops.charge != BBGE_AUDIO_NOCHANNEL)
{
core->sound->stopSfx(dsq->loops.charge);
dsq->loops.charge = BBGE_AUDIO_NOCHANNEL;
}
PlaySfx sfx;
sfx.name = "ChargeLoop";
sfx.loops = -1;
dsq->loops.charge = core->sound->playSfx(sfx);
state.spellCharge = 0;
chargeLevelAttained = 0;
switch(dsq->continuity.form)
{
case FORM_ENERGY:
chargingEmitter->load("ChargingEnergy");
break;
case FORM_NATURE:
chargingEmitter->load("ChargingNature");
break;
case FORM_SUN:
chargingEmitter->load("ChargingEnergy");
break;
case FORM_DUAL:
chargingEmitter->load("ChargingDualForm");
break;
default:
chargingEmitter->load("ChargingGeneric");
break;
}
chargingEmitter->start();
charging = true;
}
if (!canCharge())
{
formAbility();
}
}
void Avatar::setBlockSinging(bool v)
{
blockSinging = v;
if (v)
{
currentSong.notes.clear(); // abort singing without triggering a song, if queued
closeSingingInterface();
}
}
bool Avatar::canSetBoneLock()
{
return true;
}
void Avatar::onSetBoneLock()
{
Entity::onSetBoneLock();
if (boneLock.on)
{
skeletalSprite.transitionAnimate("wallLookUp", 0.2f, -1);
lockToWallCommon();
state.lockedToWall = 1;
wallNormal = boneLock.localOffset;
wallNormal.normalize2D();
rotateToVec(wallNormal, 0.1f);
}
else
{
if (state.lockedToWall)
{
fallOffWall();
}
}
}
void Avatar::onUpdateBoneLock()
{
Entity::onUpdateBoneLock();
wallNormal = boneLock.wallNormal;
rotateToVec(wallNormal, 0.01f);
}
void Avatar::lmbd(int source, InputDevice device)
{
if (!isMouseInputEnabled()) return;
// getstopdistance
if (_isUnderWater)
{
Vector v = getVectorToCursor();
if (v.isLength2DIn(getStopDistance()) && !v.isLength2DIn(minMouse))
{
if (state.lockedToWall)
{
fallOffWall();
}
}
}
}
void Avatar::fallOffWall()
{
//stillTimer.stop();
if (state.lockedToWall)
{
lockToWallFallTimer = 0;
state.nearWall = false;
state.lockedToWall = false;
setBoneLock(BoneLock());
idle();
offset.interpolateTo(Vector(0,0), 0.1f);
if (!wallNormal.isZero())
{
Vector velSet = wallNormal;
velSet.setLength2D(200);
vel += velSet;
}
//doCollisionAvoidance(dt, 5, 1);
}
}
void Avatar::lmbu(int source, InputDevice device)
{
if (!isMouseInputEnabled()) return;
if (dsq->continuity.toggleMoveMode)
movingOn = !movingOn;
if (isSinging())
{
// switch menu
}
}
bool Avatar::isCharging()
{
return charging;
}
void Avatar::endCharge()
{
if (charging)
{
if (dsq->loops.charge != BBGE_AUDIO_NOCHANNEL)
{
core->sound->stopSfx(dsq->loops.charge);
dsq->loops.charge = BBGE_AUDIO_NOCHANNEL;
}
chargingEmitter->stop();
charging = false;
state.spellCharge = 0;
}
}
Vector Avatar::getWallNormal(TileVector t)
{
return dsq->game->getWallNormal(t.worldVector(), 5)*-1;
}
bool Avatar::isSwimming()
{
return swimming;
}
void Avatar::lockToWallCommon()
{
swimEmitter.stop();
skeletalSprite.stopAllAnimations();
rotationOffset.interpolateTo(0, 0.01f);
fallGravityTimer = 0;
dsq->spawnParticleEffect("LockToWall", position);
stopBurst();
stopRoll();
core->sound->playSfx("LockToWall");
//bursting = false;
this->burst = 1;
//lastLockToWallPos = position;
state.lockToWallDelay.start(0.2f);
state.lockedToWall = true;
lockToWallFallTimer = -1;
// move this to its own function?
state.backFlip = false;
skeletalSprite.getAnimationLayer(ANIMLAYER_OVERRIDE)->stopAnimation();
}
void Avatar::lockToWall()
{
if (riding) return;
if (inCurrent && !canSwimAgainstCurrents()) return;
if (!canLockToWall()) return;
if (state.lockedToWall) return;
if (vel.x == 0 && vel.y == 0) return;
if (dsq->game->isPaused()) return;
TileVector t(position);
Vector m = vel;
m.setLength2D(3);
t.x += int(m.x);
t.y += int(m.y);
bool good = true;
if (!dsq->game->isObstructed(t))
{
do
{
TileVector test;
test = TileVector(t.x, t.y+1);
if (dsq->game->isObstructed(test))
{
t = test;
break;
}
test = TileVector(t.x, t.y-1);
if (dsq->game->isObstructed(test))
{
t = test;
break;
}
test = TileVector(t.x-1, t.y);
if (dsq->game->isObstructed(test))
{
t = test;
break;
}
test = TileVector(t.x+1, t.y);
if (dsq->game->isObstructed(test))
{
t = test;
break;
}
test = TileVector(t.x+1, t.y+1);
if (dsq->game->isObstructed(test))
{
t = test;
break;
}
test = TileVector(t.x-1, t.y+1);
if (dsq->game->isObstructed(test))
{
t = test;
break;
}
test = TileVector(t.x+1, t.y-1);
if (dsq->game->isObstructed(test))
{
t = test;
break;
}
test = TileVector(t.x-1, t.y-1);
if (dsq->game->isObstructed(test))
{
t = test;
break;
}
good = false;
}
while(0);
}
if (dsq->game->isObstructed(t, OT_HURT) && isDamageTarget(DT_WALLHURT))
{
good = false;
}
if (good)
{
wallNormal = dsq->game->getWallNormal(position);
bool outOfWaterHit = (!_isUnderWater && !(wallNormal.y < -0.1f));
if (wallNormal.isZero() )
{
debugLog("COULD NOT FIND NORMAL, GOING TO BOUNCE");
return;
}
else
{
if (!dsq->mod.isActive() && !dsq->continuity.getFlag("lockedToWall"))
{
if (!dsq->game->isControlHint()){
dsq->continuity.setFlag("lockedToWall", 1);
dsq->game->setControlHint(dsq->continuity.stringBank.get(13), 1, 0, 0, 6, "", true);
}
}
lockToWallCommon();
if (outOfWaterHit)
lockToWallFallTimer = 0.4f;
else
lockToWallFallTimer = -1;
wallPushVec = wallNormal;
wallPushVec *= 2000;
wallPushVec.z = 0;
skeletalSprite.stopAllAnimations();
if (wallPushVec.y < 0 && (fabsf(wallPushVec.y) > fabsf(wallPushVec.x)))
{
skeletalSprite.transitionAnimate("wallLookUp", 0.2f, -1);
}
else
{
skeletalSprite.transitionAnimate("wall", 0.2f, -1);
}
rotateToVec(wallPushVec, 0.1f);
offset.stop();
int tileType = dsq->game->getGrid(t);
Vector offdiff = t.worldVector() - position;
if (!offdiff.isZero())
{
if (tileType & OT_INVISIBLEIN)
{
Vector adjust = offdiff;
adjust.setLength2D(TILE_SIZE/2);
offdiff -= adjust;
}
else
{
Vector adjust = offdiff;
adjust.setLength2D(TILE_SIZE*2);
offdiff -= adjust;
}
}
float spd = vel.getLength2D();
if (spd < 1000)
spd = 1000;
Vector diff = offset - offdiff;
float len = diff.getLength2D();
float time = 0;
if (len > 0)
{
time = len/spd;
}
offset.interpolateTo(offdiff, time);
wallLockTile = t;
vel = Vector(0,0,0);
vel2 = 0;
}
}
else
{
//debugLog("COULD NOT FIND TILE TO GRAB ONTO");
//position = opos;
}
}
void Avatar::applyTripEffects()
{
color.interpolateTo(BLIND_COLOR, 0.5f);
tripper->alpha.interpolateTo(1, 8);
tripper->color = Vector(1, 1, 1);
tripper->rotation.z = 0;
tripper->rotation.interpolateTo(Vector(0, 0, 360), 10, -1);
tripper->scale = Vector(1.25f, 1.25f, 1.25f);
tripper->scale.interpolateTo(Vector(1.3f, 1.3f, 1.3f), 2, -1, 1, 1);
if (dsq->loops.trip != BBGE_AUDIO_NOCHANNEL)
{
dsq->sound->stopSfx(dsq->loops.trip);
dsq->loops.trip = BBGE_AUDIO_NOCHANNEL;
}
PlaySfx play;
play.name = "TripLoop";
play.loops = -1;
play.fade = SFT_IN;
play.time = 1;
dsq->loops.trip = dsq->sound->playSfx(play);
}
void Avatar::removeTripEffects()
{
color.interpolateTo(Vector(1,1,1),0.5);
tripper->alpha.interpolateTo(0, 4);
if (dsq->loops.trip != BBGE_AUDIO_NOCHANNEL)
{
dsq->sound->fadeSfx(dsq->loops.trip, SFT_OUT, 3);
dsq->loops.trip = BBGE_AUDIO_NOCHANNEL;
}
}
void Avatar::applyBlindEffects()
{
// screen black
// character black
color.interpolateTo(BLIND_COLOR, 0.5f);
blinder->alpha.interpolateTo(1, 0.5f);
blinder->rotation.z = 0;
blinder->rotation.interpolateTo(Vector(0, 0, 360), 10, -1);
blinder->scale = Vector(1.25f, 1.25f, 1.25f);
blinder->scale.interpolateTo(Vector(1.3f, 1.3f, 1.3f), 2, -1, 1, 1);
//dsq->toggleMuffleSound(1);
}
void Avatar::removeBlindEffects()
{
color.interpolateTo(Vector(1,1,1),0.5f);
blinder->alpha.interpolateTo(0, 0.5f);
//dsq->toggleMuffleSound(0);
}
void Avatar::setBlind(float time)
{
if (time == 0)
{
removeBlindEffects();
return;
}
if (!state.blind)
{
applyBlindEffects();
}
state.blind = true;
if (time > state.blindTimer.getValue())
///*state.blindTimer.getValue() + */
state.blindTimer.start(time);
}
void Avatar::setNearestPullTarget()
{
const float maxDistSqr = sqr(800);
float smallestDist = maxDistSqr;
Entity *closest = 0;
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e)
{
if ((e->isPullable()) && e->life == 1)
{
float dist = (e->position - position).getSquaredLength2D();
if (dist < smallestDist)
{
closest = e;
smallestDist = dist;
}
}
}
}
if (closest)
{
pullTarget = closest;
pullTarget->startPull();
}
}
void Avatar::createWeb()
{
web = new Web;
web->setParentEntity(this);
dsq->game->addRenderObject(web, LR_ENTITIES);
curWebPoint = web->addPoint(dsq->game->avatar->position);
curWebPoint = web->addPoint(dsq->game->avatar->position);
}
void Avatar::clearWeb()
{
if (web)
{
web->setExistence(25);
//web->setLife(1);
//web->setDecayRate(1.0f/30.0f);
//web->fadeAlphaWithLife = 1;
web = 0;
}
}
Avatar::Avatar() : Entity(), ActionMapper()
{
canDie = true;
urchinDelay = 0;
jellyDelay = 0;
curWebPoint = 0;
web = 0;
bone_dualFormGlow = 0;
bone_head = 0;
boneLeftHand = 0;
boneRightHand = 0;
boneLeftArm = 0;
boneFish2 = 0;
lastWaterBubble = 0;
lastJumpOutFromWaterBubble = false;
lastBurstType = BURST_NONE;
dsq->loops.shield = BBGE_AUDIO_NOCHANNEL;
leftHandEmitter = rightHandEmitter = 0;
boneLeftHand = boneRightHand = 0;
canChangeForm = true;
biteTimer = 0;
dsq->loops.charge = BBGE_AUDIO_NOCHANNEL;
//heartbeat = 0;
headTextureTimer = 0;
bone_dualFormGlow = 0;
//dsq->continuity.dualFormCharge = 0;
//dsq->continuity.dualFormMode = Continuity::DUALFORM_NAIJA;
debugLog("Avatar 1");
//registerEntityDied = true;
setv(EV_ENTITYDIED, 1);
wallBurstTimer = 0;
beautyFlip = false;
invincibleBreak = true;
targetUpdateDelay = 0;
biteDelay = 0;
songInterfaceTimer = 0;
quickSongCastDelay = 0;
flourish = false;
_isUnderWater = false;
blockSinging = false;
singing = false;
spiritEnergyAbsorbed = 0;
joystickMove = false;
debugLog("setCanLeaveWater");
setCanLeaveWater(true);
debugLog("setOverrideRenderPass");
setOverrideRenderPass(1);
debugLog("Done those");
/*
setRenderPass(2);
*/
rippleDelay = 0;
ripples = false;
fallGravityTimer = 0;
lastOutOfWaterMaxSpeed = 0;
//chargeGraphic = 0;
shieldPoints = auraTimer = 0;
glow = 0;
fireDelay = 0;
looking = false;
rollDidOne = 0;
lastQuad = lastQuadDir = rollDelay = rolling = 0;
chargeLevelAttained = 0;
activeAura = AURA_NONE;
movingOn = false;
currentMaxSpeed = 0;
pullTarget = 0;
revertTimer = 0;
currentSongIdx = -1;
leaches = 0;
debugLog("Avatar vars->");
damageTime = vars->avatarDamageTime;
activateEntity = 0;
canMove = true;
//scale = Vector(0.5, 0.5);
scale = Vector(0.5, 0.5);
debugLog("Avatar 2");
//scale = Vector(1.0, 1.0);
//setTexture("Naija-sprite2");
renderQuad = false;
name = "Naija";
setEntityType(ET_AVATAR);
targets.resize(1);
entityToActivate = 0;
pathToActivate = 0;
zoomOverriden = false;
canWarp = true;
blinder = 0;
zoomVel = 0;
myZoom = Vector(1,1);
this->pushingOffWallEffect = 0;
lockToWallFallTimer = 0;
swimming = false;
charging = false;
bursting = false;
burst = 1;
burstDelay = 0;
splashDelay = 0;
avatar = this;
swimming = false;
debugLog("Avatar 3");
hair = new Hair();
hair->setTexture("Naija/Cape");
hair->setOverrideRenderPass(1);
hair->setRenderPass(1);
dsq->game->addRenderObject(hair, LR_ENTITIES);
debugLog("Avatar 4");
bindInput();
debugLog("Avatar 5");
blinder = new PauseQuad;
blinder->position = Vector(400, 300, 4.5);
blinder->setTexture("particles/blinder");
//blinder->width = blinder->height = 810;
blinder->autoWidth = AUTO_VIRTUALWIDTH;
blinder->autoHeight = AUTO_VIRTUALWIDTH;
blinder->scale = Vector(1.0125f,1.0125f);
blinder->followCamera = 1;
blinder->alpha = 0;
dsq->game->addRenderObject(blinder, LR_AFTER_EFFECTS);
tripper = new PauseQuad;
tripper->position = Vector(400,300);
tripper->setTexture("particles/tripper");
//tripper->setWidthHeight(810, 810);
tripper->autoWidth = AUTO_VIRTUALWIDTH;
tripper->autoHeight = AUTO_VIRTUALWIDTH;
tripper->scale = Vector(1.0125f, 1.0125f);
tripper->followCamera = 1;
tripper->alpha = 0;
dsq->game->addRenderObject(tripper, LR_AFTER_EFFECTS);
songIcons.resize(8);
size_t i = 0;
for (i = 0; i < songIcons.size(); i++)
{
songIcons[i] = new SongIcon(i);
songIcons[i]->alpha = 0;
songIcons[i]->followCamera = 1;
dsq->game->addRenderObject(songIcons[i], LR_HUD);
}
setSongIconPositions();
fader = new Quad;
fader->position = Vector(400,300);
fader->setTexture("fader");
fader->setWidthHeight(core->getVirtualWidth()+10);
fader->followCamera = 1;
fader->alpha = 0;
dsq->game->addRenderObject(fader, LR_AFTER_EFFECTS);
debugLog("Avatar 6");
targetQuads.resize(targets.size());
for (i = 0; i < targets.size(); i++)
{
targetQuads[i] = new ParticleEffect;
/*
targetQuads[i]->setTexture("missingImage");
targetQuads[i]->alpha = 0;
*/
targetQuads[i]->load("EnergyBlastTarget");
// HACK: should have its own layer?
dsq->game->addRenderObject(targetQuads[i], LR_PARTICLES);
}
lightFormGlow = new Quad("Naija/LightFormGlow", 0);
lightFormGlow->alpha = 0;
lightFormGlow->scale.interpolateTo(Vector(5.5f, 5.5f), 0.4f, -1, 1);
//lightFormGlow->positionSnapTo = &position;
dsq->game->addRenderObject(lightFormGlow, LR_ELEMENTS13);
lightFormGlowCone = new Quad("Naija/LightFormGlowCone", 0);
lightFormGlowCone->alpha = 0;
lightFormGlowCone->scale = Vector(1, 6); // 4.5
dsq->game->addRenderObject(lightFormGlowCone, LR_ELEMENTS13);
debugLog("Avatar 7");
addChild(&regenEmitter, PM_STATIC);
regenEmitter.load("FoodEffectRegen");
addChild(&speedEmitter, PM_STATIC);
speedEmitter.load("FoodEffectSpeed");
addChild(&defenseEmitter, PM_STATIC);
defenseEmitter.load("FoodEffectDefense");
addChild(&invincibleEmitter, PM_STATIC);
invincibleEmitter.load("FoodEffectInvincible");
addChild(&auraEmitter, PM_STATIC);
addChild(&auraLowEmitter, PM_STATIC);
addChild(&auraHitEmitter, PM_STATIC);
auraHitEmitter.load("AuraShieldHit");
chargingEmitter = new ParticleEffect;
dsq->getTopStateData()->addRenderObject(chargingEmitter, LR_PARTICLES);
chargeEmitter = new ParticleEffect;
dsq->getTopStateData()->addRenderObject(chargeEmitter, LR_PARTICLES_TOP);
leftHandEmitter = new ParticleEffect;
dsq->getTopStateData()->addRenderObject(leftHandEmitter, LR_PARTICLES);
rightHandEmitter = new ParticleEffect;
dsq->getTopStateData()->addRenderObject(rightHandEmitter, LR_PARTICLES);
addChild(&biteLeftEmitter, PM_STATIC);
biteLeftEmitter.load("BiteLeft");
addChild(&biteRightEmitter, PM_STATIC);
biteRightEmitter.load("BiteRight");
addChild(&wakeEmitter, PM_STATIC);
wakeEmitter.load("Wake");
addChild(&swimEmitter, PM_STATIC);
swimEmitter.load("Swim");
addChild(&plungeEmitter, PM_STATIC);
plungeEmitter.load("Plunge");
plungeEmitter.position = Vector(0,-100);
addChild(&spiritBeaconEmitter, PM_STATIC);
spiritBeaconEmitter.load("SpiritBeacon");
addChild(&healEmitter, PM_STATIC);
addChild(&hitEmitter, PM_STATIC);
addChild(&rollLeftEmitter, PM_STATIC);
addChild(&rollRightEmitter, PM_STATIC);
rollRightEmitter.load("RollRight");
rollLeftEmitter.load("RollLeft");
debugLog("Avatar 8");
perform(STATE_IDLE);
skeletalSprite.animate(getIdleAnimName(),-1);
debugLog("Avatar 9");
setDamageTarget(DT_AVATAR_LANCE, false);
//changeForm(FORM_NORMAL, false);
refreshNormalForm();
if(dsq->continuity.form == FORM_FISH)
collideRadius = COLLIDE_RADIUS_FISH;
else
collideRadius = COLLIDE_RADIUS_NORMAL;
// defaults for normal form
_canActivateStuff = true;
_canBurst = true;
_canLockToWall = true;
_canSwimAgainstCurrents = false;
_canCollideWithShots = true;
_collisionAvoidMod = COLLIDE_MOD_NORMAL;
_collisionAvoidRange = COLLIDE_RANGE_NORMAL;
_seeMapMode = SEE_MAP_DEFAULT;
blockBackFlip = false;
elementEffectMult = 1;
_lastActionSourceID = 9999;
_lastActionInputDevice = INPUT_NODEVICE;
}
void Avatar::revert()
{
if (canChangeForm)
{
if (dsq->continuity.form != FORM_NORMAL)
changeForm(FORM_NORMAL);
}
}
void Avatar::onHeal(int type)
{
if (type == 1)
{
healEmitter.load("Heal");
healEmitter.start();
}
}
void Avatar::refreshNormalForm()
{
std::string c = dsq->continuity.costume;
if (c.empty())
c = "Naija";
refreshModel("Naija", c);
if(hair)
{
hair->alphaMod = 1.0;
if (!c.empty() && c!="Naija")
{
if(!hair->setTexture("naija/cape-"+c))
hair->alphaMod = 0;
}
else
hair->setTexture("naija/cape");
}
}
void Avatar::refreshModel(std::string file, const std::string &skin, bool forceIdle)
{
stringToLower(file);
bool loadedSkeletal = false;
if (!skeletalSprite.isLoaded() || nocasecmp(skeletalSprite.filenameLoaded, file)!=0)
{
skeletalSprite.loadSkeletal(file);
loadedSkeletal = true;
}
if (!skin.empty())
skeletalSprite.loadSkin(skin);
if (file == "beast")
{
skeletalSprite.scale = Vector(1.25,1.25);
}
else
skeletalSprite.scale = Vector(1,1);
Animation *anim = skeletalSprite.getCurrentAnimation(0);
if (forceIdle || (!anim || loadedSkeletal))
{
idle();
}
if (file == "naija")
{
bone_head = skeletalSprite.getBoneByIdx(1);
boneLeftArm = skeletalSprite.getBoneByName("LeftArm");
boneFish2 = skeletalSprite.getBoneByName("Fish2");
if(boneFish2)
boneFish2->alpha = 0;
bone_dualFormGlow = skeletalSprite.getBoneByName("DualFormGlow");
if (bone_dualFormGlow)
{
bone_dualFormGlow->scale = 0;
bone_dualFormGlow->setBlendType(BLEND_ADD);
}
boneLeftHand = skeletalSprite.getBoneByName("LeftArm");
boneRightHand = skeletalSprite.getBoneByName("RightArm");
}
else
{
bone_dualFormGlow = 0;
bone_head = 0;
boneLeftArm = boneFish2 = 0;
boneLeftHand = boneRightHand = 0;
}
core->resetTimer();
skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
}
Avatar::~Avatar()
{
}
void Avatar::destroy()
{
Entity::destroy();
if (dsq->loops.shield != BBGE_AUDIO_NOCHANNEL)
{
core->sound->fadeSfx(dsq->loops.shield, SFT_OUT, 1);
dsq->loops.shield = BBGE_AUDIO_NOCHANNEL;
}
if (dsq->loops.current != BBGE_AUDIO_NOCHANNEL)
{
core->sound->fadeSfx(dsq->loops.current, SFT_OUT, 1);
dsq->loops.current = BBGE_AUDIO_NOCHANNEL;
}
avatar = 0;
}
void Avatar::startBackFlip()
{
if (boneLock.on) return;
if (riding) return;
if (blockBackFlip) return;
skeletalSprite.getAnimationLayer(ANIMLAYER_OVERRIDE)->transitionAnimate("backflip", 0.2f, 0);
vel.x = -vel.x*0.25f;
state.backFlip = true;
}
void Avatar::stopBackFlip()
{
if (state.backFlip)
{
//skeletalSprite.getAnimationLayer(ANIMLAYER_OVERRIDE)->stopAnimation();
skeletalSprite.getAnimationLayer(ANIMLAYER_OVERRIDE)->transitionAnimate("backflip2", 0.2f, 0);
state.backFlip = false;
}
}
void Avatar::startBurstCommon()
{
skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
bittenEntities.clear();
flourish = false;
if (dsq->continuity.form == FORM_BEAST)
{
setHeadTexture("Bite");
}
burstTimer = 0;
setBoneLock(BoneLock());
biteTimer = 0;
if (dsq->continuity.form == FORM_BEAST)
{
if (isfh())
biteRightEmitter.start();
else
biteLeftEmitter.start();
}
}
void Avatar::startBurst()
{
if (!riding && canBurst() && (joystickMove || getVectorToCursor().getSquaredLength2D() > sqr(BURST_DISTANCE))
&& getState() != STATE_PUSH && (!skeletalSprite.getCurrentAnimation() || (skeletalSprite.getCurrentAnimation()->name != "spin"))
&& _isUnderWater && !isActing(ACTION_ROLL, -1))
{
if (!bursting && burst == 1)
{
dsq->rumble(0.2f, 0.2f, 0.2f, _lastActionSourceID, _lastActionInputDevice);
if (dsq->continuity.form != FORM_BEAST)
wakeEmitter.start();
dsq->game->playBurstSound(pushingOffWallEffect>0);
skeletalSprite.animate(getBurstAnimName(), 0);
bursting = true;
burst = 1.0f;
ripples = true;
startBurstCommon();
lastBurstType = BURST_NORMAL;
}
else if (bursting && burstTimer > 0.3f)
{
if (!flourish && !state.nearWall)
//&& dsq->continuity.form == FORM_NORMAL)
{
//if (rand()%100 < 50)
if (true)
{
startFlourish();
}
/*
else
{
skeletalSprite.transitionAnimate("flourish2", 0.1, 0, 3);
flourish = true;
if (this->isfh())
rotationOffset = Vector(0,0,-360);
else
rotationOffset = Vector(0,0,360);
rotationOffset.interpolateTo(Vector(0,0,0), 0.8, 0, 0, 1);
}
*/
//burst += 0.1;
if (!vel.isZero())
{
Vector add = vel;
add.setLength2D(50);
vel2 += add;
}
}
}
}
}
void Avatar::startWallBurst(bool useCursor)
{
//if (!bursting && burst == 1 )
{
Vector goDir;
//goDir = getVectorToCursorFromScreenCentre();
goDir = getVectorToCursor();
if (goDir.isLength2DIn(BURST_DISTANCE))
{
if (!goDir.isLength2DIn(minMouse))
fallOffWall();
return;
}
goDir.normalize2D();
if (_isUnderWater && dsq->continuity.form != FORM_BEAST)
wakeEmitter.start();
offset.interpolateTo(Vector(0,0), 0.05f);
dsq->spawnParticleEffect("WallBoost", position+offset, rotation.z);
if (goDir.x != 0 || goDir.y != 0)
{
lastBurstType = BURST_WALL;
dsq->rumble(0.22f, 0.22f, 0.2f, _lastActionSourceID, _lastActionInputDevice);
bittenEntities.clear();
if (useCursor)
{
wallPushVec = (goDir*0.75f + wallNormal*0.25f);
//wallPushVec = goDir;
}
else
{
float v = goDir.dot2D(wallNormal);
if (v <= -0.8f)
wallPushVec = wallNormal;
else
{
wallPushVec = (goDir*0.5f + wallNormal*0.5f);
}
}
//wallPushVec = (goDir*0.9f + wallNormal*0.1f);
wallPushVec.setLength2D(vars->maxWallJumpBurstSpeed);
position.stop();
pushingOffWallEffect = 0.5;
vel = wallPushVec;
this->state.lockedToWall = false;
skeletalSprite.stopAllAnimations();
dsq->game->playBurstSound(pushingOffWallEffect>0);
skeletalSprite.animate(getBurstAnimName(), 0);
bursting = true;
burst = 1.5;
ripples = true;
startBurstCommon();
}
}
}
Vector Avatar::getKeyDir()
{
Vector dir;
if (isActing(ACTION_SWIMLEFT, -1))
dir += Vector(-1,0);
if (isActing(ACTION_SWIMRIGHT, -1))
dir += Vector(1,0);
if (isActing(ACTION_SWIMUP, -1))
dir += Vector(0,-1);
if (isActing(ACTION_SWIMDOWN, -1))
dir += Vector(0,1);
if (dir.x != 0 && dir.y != 0)
dir/=2;
return dir;
}
Vector Avatar::getFakeCursorPosition()
{
if (dsq->inputMode == INPUT_KEYBOARD)
{
return getKeyDir() * 350;
}
if (dsq->inputMode == INPUT_JOYSTICK)
{
float axisInput = 0;
Joystick *j = 0;
for(size_t i = 0; i < core->getNumJoysticks(); ++i)
if( ((j = core->getJoystick(i))) )
if(j->isEnabled())
{
axisInput = j->position.getLength2D();
if(axisInput >= JOYSTICK_LOW_THRESHOLD)
{
const float axisMult = (maxMouse - minMouse) / (JOYSTICK_HIGH_THRESHOLD - JOYSTICK_LOW_THRESHOLD);
const float distance = minMouse + ((axisInput - JOYSTICK_LOW_THRESHOLD) * axisMult);
return (j->position * (distance / axisInput));
}
}
}
return Vector(0,0,0);
}
Vector Avatar::getVectorToCursorFromScreenCentre()
{
if (game->cameraOffBounds)
return getVectorToCursor();
else
{
if (dsq->inputMode != INPUT_MOUSE)
return getFakeCursorPosition();
return (core->mouse.position+offset) - Vector(400,300);
}
}
Vector Avatar::getVectorToCursor(bool trueMouse)
{
//return getVectorToCursorFromScreenCentre();
Vector pos = dsq->getGameCursorPosition();
if (!trueMouse && dsq->inputMode != INPUT_MOUSE)
return getFakeCursorPosition();
return pos - (position+offset);
//return core->mouse.position - Vector(400,300);
}
void Avatar::action(int id, int state, int source, InputDevice device)
{
if(dsq->game->isIgnoreAction((AquariaActions)id))
return;
_lastActionSourceID = source;
_lastActionInputDevice = device;
if (id == ACTION_PRIMARY) { if (state) lmbd(source, device); else lmbu(source, device); }
if (id == ACTION_SECONDARY) { if (state) rmbd(source, device); else rmbu(source, device); }
if (id == ACTION_REVERT && !state)
revert();
if (id == ACTION_PRIMARY && state)// !state
{
if (dsq->isMiniMapCursorOkay())
{
if (this->state.lockedToWall)
{
Vector test = getVectorToCursor();
if (test.isLength2DIn(minMouse))
{
fallOffWall();
}
else
{
if (boneLock.entity)
wallNormal = boneLock.wallNormal;
if (isUnderWater())
{
test.normalize2D();
float dott = wallNormal.dot2D(test);
burst = 1;
bursting = false;
// normal is 90 degrees within/on the right side
if (dott > 0)
{
startWallBurst(true);
}
else
{
startWallBurst(false);
}
}
else
{
test.normalize2D();
float dott = wallNormal.dot2D(test);
if (dott > -0.3f)
{
burst = 1;
bursting = false;
startWallBurst(true);
}
else
{
// nothing
}
}
}
}
else
{
startBurst();
// FIXME: This is a quick hack to make sure the burst
// transition animation is played when using joystick or
// keyboard control. The same thing should probably be
// done for wall bursts, but the movement there is fast
// enough that people probably won't notice, so I skipped
// that. Sorry about the ugliness. --achurch
if (device != INPUT_MOUSE)
skeletalSprite.transitionAnimate("swim", ANIM_TRANSITION, -1);
}
}
}
else if (id >= ACTION_SONGSLOT1 && id < ACTION_SONGSLOTEND)
{
if (canQuickSong())
{
int count = (id - ACTION_SONGSLOT1)+1;
bool cast = false;
if (dsq->continuity.form == FORM_SPIRIT)
{
revert();
cast = true;
}
else
{
switch(count)
{
case 1:
{
if (dsq->continuity.form != FORM_NORMAL)
{
revert();
cast = true;
}
}
break;
case 2:
if (dsq->continuity.form != FORM_ENERGY)
{
dsq->continuity.castSong(SONG_ENERGYFORM);
cast = true;
}
break;
case 3:
if (dsq->continuity.form != FORM_BEAST)
{
dsq->continuity.castSong(SONG_BEASTFORM);
cast = true;
}
break;
case 4:
if (dsq->continuity.form != FORM_NATURE)
{
dsq->continuity.castSong(SONG_NATUREFORM);
cast = true;
}
break;
case 5:
if (dsq->continuity.form != FORM_SUN)
{
dsq->continuity.castSong(SONG_SUNFORM);
cast = true;
}
break;
case 6:
if (dsq->continuity.form != FORM_FISH)
{
dsq->continuity.castSong(SONG_FISHFORM);
cast = true;
}
break;
case 7:
if (dsq->continuity.form != FORM_SPIRIT)
{
dsq->continuity.castSong(SONG_SPIRITFORM);
cast = true;
}
break;
case 8:
if (dsq->continuity.form != FORM_DUAL)
{
dsq->continuity.castSong(SONG_DUALFORM);
cast = true;
}
break;
default:
if (dsq->continuity.form == FORM_NORMAL)
{
switch(count)
{
case 9:
{
dsq->continuity.castSong(SONG_SHIELDAURA);
cast = true;
}
break;
case 10:
{
dsq->continuity.castSong(SONG_BIND);
cast = true;
}
break;
}
}
break;
}
}
if (cast)
{
quickSongCastDelay = QUICK_SONG_CAST_DELAY;
}
}
}
}
void Avatar::doBindSong()
{
if (pullTarget)
{
pullTarget->stopPull();
pullTarget = 0;
core->sound->playSfx("Denied");
}
else
{
dsq->game->bindIngredients();
setNearestPullTarget();
if (!pullTarget)
{
core->sound->playSfx("Denied");
}
else
{
core->sound->playSfx("Bind");
}
}
}
void Avatar::doShieldSong()
{
core->sound->playSfx("Shield-On");
activateAura(AURA_SHIELD);
}
void Avatar::render()
{
if (dsq->continuity.form == FORM_SPIRIT && !skeletalSprite.getParent())
{
skeletalSprite.position = bodyPosition+bodyOffset;
skeletalSprite.color = Vector(0.2f, 0.3f, 0.6f);
skeletalSprite.render();
skeletalSprite.color = Vector(1,1,1);
}
Entity::render();
}
void Avatar::onRender()
{
Entity::onRender();
}
void Avatar::onEnterState(int action)
{
Entity::onEnterState(action);
if (action == STATE_PUSH)
{
state.lockedToWall = false;
Animation *a = skeletalSprite.getCurrentAnimation();
if (!a || (a && a->name != "pushed"))
skeletalSprite.animate("pushed", 0);
}
}
void Avatar::onExitState(int action)
{
Entity::onExitState(action);
if (action == STATE_TRANSFORM)
{
setState(STATE_IDLE);
}
else if (action == STATE_PUSH)
{
skeletalSprite.transitionAnimate("spin", 0.1f);
}
}
void Avatar::splash(bool down)
{
if (splashDelay > 0)
{
lastJumpOutFromWaterBubble = false;
return;
}
splashDelay = SPLASH_INTERVAL;
if (down)
{
sound("splash-into", rolling ? 0.9f : 1.0f);
//dsq->postProcessingFx.disable(FXT_RADIALBLUR);
if (_isUnderWater && core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter,0.08f,0.05f,22,0.2f, 1.2f));
dsq->rumble(0.7f, 0.7f, 0.2f, _lastActionSourceID, _lastActionInputDevice);
plungeEmitter.start();
core->sound->playSfx("GoUnder");
}
else
{
sound("splash-outof");
/*
dsq->postProcessingFx.enable(FXT_RADIALBLUR);
dsq->postProcessingFx.radialBlurColor = Vector(1,1,1);
dsq->postProcessingFx.intensity = 0.1;
*/
core->sound->playSfx("Emerge");
}
// make a splash effect @ current position
// FIXME: This is broken for waterfalls/bubbles (e.g. the waterfalls
// in the Turtle Cave). Not sure how best to fix it in the current
// code. --achurch
Vector hsplash = avatar->getHeadPosition();
hsplash.y = dsq->game->waterLevel.x;
core->createParticleEffect("HeadSplash", hsplash, LR_PARTICLES);
float a = 0;
if (waterBubble || lastJumpOutFromWaterBubble)
{
lastJumpOutFromWaterBubble = false;
if (waterBubble)
{
Vector diff = position - waterBubble->nodes[0].position;
a = MathFunctions::getAngleToVector(diff, 180);
}
else if (lastWaterBubble)
{
Vector diff = position - lastWaterBubble->nodes[0].position;
a = MathFunctions::getAngleToVector(diff, 0);
}
else
a = MathFunctions::getAngleToVector(vel+vel2, 0);
}
dsq->spawnParticleEffect("Splash", position, a);
}
void Avatar::clampVelocity()
{
/*
std::ostringstream os;
os << "currentMaxSpeed: " << currentMaxSpeed;
debugLog(os.str());
*/
float useSpeedMult = dsq->continuity.speedMult;
bool inCurrent = isInCurrent();
bool withCurrent = false;
if (inCurrent)
{
// if vel2 and vel are pointing the same way
if (vel2.dot2D(vel) > 0)
{
withCurrent = true;
}
}
if (!inCurrent || (inCurrent && withCurrent))
{
if (dsq->continuity.form == FORM_FISH)
{
useSpeedMult *= MULT_MAXSPEED_FISHFORM;
}
}
else
{
useSpeedMult = 1;
}
if (dsq->continuity.form == FORM_BEAST)
{
useSpeedMult *= MULT_MAXSPEED_BEASTFORM;
}
if (currentState == STATE_PUSH)
{
currentMaxSpeed = pushMaxSpeed;
}
setMaxSpeed(currentMaxSpeed * useSpeedMult * dsq->continuity.speedMult2);
vel.capLength2D(getMaxSpeed() /* * maxSpeedLerp.x*/);
}
void Avatar::activateAura(AuraType aura)
{
activeAura = aura;
auraTimer = 30;
if (aura == AURA_SHIELD)
{
shieldPoints = maxShieldPoints;
if (auraLowEmitter.isRunning())
auraLowEmitter.stop();
auraEmitter.load("AuraShield");
auraEmitter.start();
if (dsq->loops.shield == BBGE_AUDIO_NOCHANNEL)
{
PlaySfx play;
play.name = "Shield-Loop";
play.fade = SFT_IN;
play.time = 1;
play.loops = -1;
dsq->loops.shield = core->sound->playSfx(play);
}
}
}
void Avatar::updateAura(float dt)
{
if (auraTimer > 0 && dsq->continuity.form != FORM_SPIRIT)
{
switch(activeAura)
{
case AURA_SHIELD:
{
//shieldPosition = position + Vector(cosf(auraTimer*4)*100, sinf(auraTimer*4)*100);
shieldPosition = position;
/*
float a = ((rotation.z)*PI)/180.0f + PI*0.5f;
shieldPosition = position + Vector(cosf(a)*100, sinf(a)*100);
*/
for (Shot::Shots::iterator i = Shot::shots.begin(); i != Shot::shots.end(); ++i)
{
Shot *s = *i;
if (s->isActive() && dsq->game->isDamageTypeEnemy(s->getDamageType()) && s->firer != this
&& (!s->shotData || !s->shotData->ignoreShield))
{
Vector diff = s->position - shieldPosition;
if (diff.getSquaredLength2D() < sqr(AURA_SHIELD_RADIUS))
{
shieldPoints -= s->getDamage();
auraHitEmitter.start();
dsq->spawnParticleEffect("ReflectShot", s->position);
core->sound->playSfx("Shield-Hit");
s->position += diff;
//s->target = 0;
diff.setLength2D(s->maxSpeed);
s->velocity = diff;
s->reflectFromEntity(this);
}
}
}
}
break;
case AURA_THING:
case AURA_HEAL:
case AURA_NONE:
break;
}
auraTimer -= dt;
if (auraTimer < 5 || shieldPoints < 2)
{
if (auraEmitter.isRunning())
{
auraEmitter.stop();
auraLowEmitter.load("AuraShieldLow");
auraLowEmitter.start();
}
}
if (auraTimer < 0 || shieldPoints < 0)
{
stopAura();
}
}
}
void Avatar::stopAura()
{
auraTimer = 0;
activeAura = AURA_NONE;
auraEmitter.stop();
auraLowEmitter.stop();
if (dsq->loops.shield != BBGE_AUDIO_NOCHANNEL)
{
core->sound->fadeSfx(dsq->loops.shield, SFT_OUT, 1);
dsq->loops.shield = BBGE_AUDIO_NOCHANNEL;
}
}
void Avatar::setHeadTexture(const std::string &name, float time)
{
if (!bone_head) return;
if (dsq->continuity.form == FORM_NORMAL /*&& dsq->continuity.costume.empty()*/)
{
if (!name.empty() && (nocasecmp(lastHeadTexture, "singing")==0)) return;
lastHeadTexture = name;
stringToUpper(lastHeadTexture);
std::string t = "Naija/";
if (!dsq->continuity.costume.empty())
{
if (nocasecmp(dsq->continuity.costume, "end")==0)
t += "Naija2";
else
t += dsq->continuity.costume;
}
else if (dsq->continuity.form == FORM_BEAST)
{
t += "Beast";
}
else
{
t += "Naija2";
}
t += "-Head";
if (!name.empty())
{
t += "-" + name;
}
bone_head->setTexture(t);
headTextureTimer = time;
}
}
void Avatar::chargeVisualEffect(const std::string &tex)
{
float time = 0.4f;
Quad *chargeEffect = new Quad;
chargeEffect->setBlendType(BLEND_ADD);
chargeEffect->alpha.ensureData();
chargeEffect->alpha.data->path.addPathNode(0, 0);
chargeEffect->alpha.data->path.addPathNode(0.6f, 0.1f);
chargeEffect->alpha.data->path.addPathNode(0.6f, 0.9f);
chargeEffect->alpha.data->path.addPathNode(0, 1);
chargeEffect->alpha.startPath(time);
chargeEffect->setTexture(tex);
//chargeEffect->positionSnapTo = &this->position;
chargeEffect->position = this->position;
chargeEffect->setPositionSnapTo(&position);
chargeEffect->setLife(1);
chargeEffect->setDecayRate(1.0f/time);
chargeEffect->scale = Vector(0.1f, 0.1f);
chargeEffect->scale.interpolateTo(Vector(1,1),time);
//chargeEffect->rotation.interpolateTo(Vector(0,0,360), time);
dsq->game->addRenderObject(chargeEffect, LR_PARTICLES);
}
void Avatar::updateFormVisualEffects(float dt)
{
switch (dsq->continuity.form)
{
case FORM_ENERGY:
{
Vector hairDir(96, -96);
if (this->isfh())
{
hairDir.x = -hairDir.x;
}
}
break;
case FORM_SUN:
{
lightFormGlow->position = this->position;
lightFormGlowCone->position = this->position;
float angle=0;
MathFunctions::calculateAngleBetweenVectorsInDegrees(Vector(0,0,0), getVectorToCursorFromScreenCentre(), angle);
angle = 180-(360-angle);
//lightFormGlowCone->rotation.interpolateTo(Vector(0,0,angle), 0.1);
lightFormGlowCone->rotation = Vector(0,0,angle);
static float lfgTimer = 0;
lfgTimer += dt;
if (lfgTimer > 0.5f)
{
//debugLog("lightFormGlow to front");
lightFormGlow->moveToFront();
lightFormGlowCone->moveToFront();
lfgTimer = 0;
}
if (this->isInDarkness())
{
lightFormGlowCone->alphaMod = 1;
lightFormGlow->alphaMod = 1;
}
else
{
lightFormGlow->alphaMod = 0;
lightFormGlowCone->alphaMod = 0;
}
}
break;
case FORM_SPIRIT:
skeletalSprite.update(dt);
skeletalSprite.position = bodyPosition;
break;
case FORM_NORMAL:
case FORM_BEAST:
case FORM_NATURE:
case FORM_DUAL:
case FORM_FISH:
case FORM_MAX:
case FORM_NONE:
break;
}
}
void Avatar::stopBurst()
{
burst = 0;
//burstDelay = BURST_DELAY;
burstDelay = 0;
bursting = false;
wakeEmitter.stop();
ripples = false;
biteLeftEmitter.stop();
biteRightEmitter.stop();
}
int Avatar::getCursorQuadrant()
{
//Vector diff = getVectorToCursorFromScreenCentre();
Vector diff = getVectorToCursor();
if (diff.isLength2DIn(40))
{
stopRoll();
return -999;
}
if (diff.y < 0)
return diff.x < 0 ? 4 : 1;
else
return diff.x < 0 ? 3 : 2;
}
int Avatar::getQuadrantDirection(int lastQuad, int quad)
{
int diff = quad - lastQuad;
if ((lastQuad==4 && quad == 1))
{
diff = 1;
}
if (lastQuad==1 && quad==4)
{
diff = -1;
}
if (abs(diff) != 1)
diff = 0;
return diff;
}
void Avatar::startRoll(int dir)
{
if (!rolling && !state.backFlip)
{
if (dsq->continuity.form == FORM_ENERGY && dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY1))
{
rollRightEmitter.load("EnergyRollRight");
rollLeftEmitter.load("EnergyRollLeft");
}
else
{
rollRightEmitter.load("RollRight");
rollLeftEmitter.load("RollLeft");
}
}
Animation *a = skeletalSprite.getCurrentAnimation();
if (!a || a->name != getRollAnimName())
{
skeletalSprite.transitionAnimate(getRollAnimName(), 0.2f, -1);
}
rollRightEmitter.stop();
rollLeftEmitter.stop();
if (_isUnderWater)
{
if (dir >= 1)
rollRightEmitter.start();
if (dir <= -1)
rollLeftEmitter.start();
}
//dsq->playVisualEffect(VFX_RIPPLE, Vector());
rolling = true;
if (dsq->loops.roll == BBGE_AUDIO_NOCHANNEL && _isUnderWater)
{
PlaySfx play;
play.name = "RollLoop";
play.fade = SFT_IN;
play.time = 1;
play.vol = 1;
play.loops = -1;
dsq->loops.roll = core->sound->playSfx(play);
}
else if (dsq->loops.roll != BBGE_AUDIO_NOCHANNEL && !_isUnderWater)
{
core->sound->fadeSfx(dsq->loops.roll, SFT_OUT, 0.5f);
}
rollDir = dir;
if (_isUnderWater && core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter,0.08f,0.05f,22,0.2f, 1.2f));
//rollDelay = 0.3;
}
void Avatar::stopRoll()
{
rolling = false;
lastQuadDir = lastQuad = 0;
rollDelay = 0;
rollDidOne = 0;
rollLeftEmitter.stop();
rollRightEmitter.stop();
state.rollTimer = 0;
if (dsq->loops.roll != BBGE_AUDIO_NOCHANNEL)
{
core->sound->fadeSfx(dsq->loops.roll, SFT_OUT, 1);
dsq->loops.roll = BBGE_AUDIO_NOCHANNEL;
}
}
void Avatar::stopWallJump()
{
wallBurstTimer = 0;
}
void Avatar::updateWallJump(float dt)
{
if (wallBurstTimer > 0)
{
wallBurstTimer -= dt;
if (wallBurstTimer < 0)
{
// wall jump failed!
stopWallJump();
}
}
}
void Avatar::updateRoll(float dt)
{
if (!inputEnabled || dsq->game->isWorldPaused() || riding)
{
if (rolling)
stopRoll();
return;
}
if (state.lockedToWall || isSinging()) return;
if (rollDelay > 0)
{
rollDelay -= dt;
if (rollDelay <= 0)
{
// stop the animation
stopRoll();
}
}
const bool rollact = isActing(ACTION_ROLL, -1);
if (!_isUnderWater && rollact)
{
stopRoll();
}
if (!core->mouse.buttons.left && dsq->inputMode == INPUT_MOUSE && !rollact)
{
if (rolling)
stopRoll();
return;
}
if (rolling)
{
if (dsq->continuity.form == FORM_ENERGY && dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY1))
{
FOR_ENTITIES(i)
{
Entity *e = *i;
if (e->getEntityType() == ET_ENEMY && (e->position - this->position).isLength2DIn(256) && e->isDamageTarget(DT_AVATAR_ENERGYROLL))
{
DamageData d;
d.damage = dt*15;
d.damageType = DT_AVATAR_ENERGYROLL;
d.attacker = this;
e->damage(d);
}
}
}
state.rollTimer += dt;
if (state.rollTimer > 0.55f)
{
state.rollTimer = 0;
if (dsq->continuity.form == FORM_DUAL)
{
switchDualFormMode();
state.rollTimer = -1;
}
}
// NOTE: does this fix the roll problem?
if (rollDelay <= 0)
stopRoll();
}
if (rollact)
{
if (_isUnderWater)
{
if (rollDelay < 0.5f)
{
startRoll(isfh()?1:-1);
}
float amt = dt * 1000;
if (isfh())
{
rotation.z += amt;
}
else
{
rotation.z -= amt;
}
rotation.capRotZ360();
rollDelay = 1.0;
}
}
else
{
int quad = getCursorQuadrant();
if (lastQuad != quad)
{
int quadDir = 0;
if (lastQuad != 0)
{
quadDir = getQuadrantDirection(lastQuad, quad);
if (quadDir != 0 && lastQuadDir == quadDir && rollDelay > 0)
{
if (rolling)
{
startRoll(quadDir);
}
else
{
if (rollDidOne==1)
rollDidOne = 2;
else if (rollDidOne == 2)
startRoll(quadDir);
else
rollDidOne = 1;
}
}
}
lastQuadDir = quadDir;
lastQuad = quad;
rollDelay = 0.2f;
}
}
}
int Avatar::getStopDistance()
{
return STOP_DISTANCE;
}
int Avatar::getBurstDistance()
{
return BURST_DISTANCE;
}
void Avatar::setWasUnderWater()
{
state.wasUnderWater = isUnderWater();
}
bool Avatar::canActivateStuff()
{
return _canActivateStuff;
}
void Avatar::setCanActivateStuff(bool on)
{
_canActivateStuff = on;
}
void Avatar::setCollisionAvoidanceData(int range, float mod)
{
_collisionAvoidRange = range;
_collisionAvoidMod = mod;
}
bool Avatar::canQuickSong()
{
return !isSinging() && !isEntityDead() && isInputEnabled() && quickSongCastDelay <= 0;
}
void Avatar::applyRidingPosition()
{
if (riding)
{
position = riding->getRidingPosition();
lastPosition = position;
rotation.z = riding->getRidingRotation();
if (riding->getRidingFlip())
{
if (!isfh())
flipHorizontal();
}
else
{
if (isfh())
flipHorizontal();
}
//state.wasUnderWater = _isUnderWater;
}
}
void Avatar::adjustHeadRot()
{
if (bone_head)
{
// 0 to 30 range
if (bone_head->rotation.z > 0)
{
bone_head->internalOffset.x = (bone_head->rotation.z/30.0f)*5;
//bone_head->internalOffset.y = (bone_head->rotation.z/30.0f)*1;
}
// 0 to -10 range
if (bone_head->rotation.z < 0)
{
bone_head->internalOffset.x = (bone_head->rotation.z/(-10.0f))*-4;
bone_head->internalOffset.y = (bone_head->rotation.z/(-10.0f))*-2;
}
}
}
void Avatar::endOfGameState()
{
state.lookAtEntity = 0;
setInvincible(true);
}
bool didRotationFix = true;
void timerEffectStart(Timer *timer, ParticleEffect *effect)
{
if (timer->isActive() && !effect->isRunning())
{
effect->start();
}
else if (!timer->isActive() && effect->isRunning())
{
effect->stop();
}
}
void Avatar::updateFoodParticleEffects()
{
timerEffectStart(&dsq->continuity.speedMultTimer, &speedEmitter);
timerEffectStart(&dsq->continuity.defenseMultTimer, &defenseEmitter);
timerEffectStart(&dsq->continuity.invincibleTimer, &invincibleEmitter);
timerEffectStart(&dsq->continuity.regenTimer, &regenEmitter);
}
void Avatar::updateLookAt(float dt)
{
//if (dsq->overlay->alpha != 0) return;
if (dsq->game->isShuttingDownGameState()) return;
if (headTextureTimer > 0)
{
headTextureTimer -= dt;
if (headTextureTimer <= 0)
{
headTextureTimer = 0;
setHeadTexture("");
}
}
if (dsq->continuity.form == FORM_FISH)
{
Bone *b = skeletalSprite.getBoneByIdx(0);
if (b)
b->setAnimated(Bone::ANIM_ALL);
return;
}
const float blinkTime = 5.0;
state.blinkTimer += dt;
if (state.blinkTimer > blinkTime)
{
if (lastHeadTexture.empty())
{
setHeadTexture("blink", 0.1f);
if (chance(50))
{
state.blinkTimer = blinkTime-0.2f;
}
else
{
state.blinkTimer = rand()%2;
}
}
else
{
state.blinkTimer -= dt;
}
}
if (bone_head)
{
const float lookAtTime = 0.8f;
if (core->mouse.buttons.middle && !state.lockedToWall && isInputEnabled())
{
didRotationFix = false;
bone_head->setAnimated(Bone::ANIM_POS);
bone_head->lookAt(dsq->getGameCursorPosition(), lookAtTime, -10, 30, -90);
adjustHeadRot();
}
else
{
if (state.lookAtEntity && (state.lookAtEntity->isEntityDead() || state.lookAtEntity->isDead() || state.lookAtEntity->isv(EV_LOOKAT,0) || swimming))
{
state.lookAtEntity = 0;
}
// find an object of interest
if (isv(EV_LOOKAT, 1) && state.lookAtEntity && !skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->isAnimating() && !state.lockedToWall && !swimming)
{
didRotationFix = false;
bone_head->setAnimated(Bone::ANIM_POS);
bone_head->lookAt(state.lookAtEntity->getLookAtPoint(), lookAtTime, -10, 30, -90);
if (!((state.lookAtEntity->position - position).isLength2DIn(1000)))
{
state.lookAtEntity = 0;
}
state.updateLookAtTime += dt;
adjustHeadRot();
}
else
{
bone_head->setAnimated(Bone::ANIM_ALL);
if (!didRotationFix && !bone_head->rotationOffset.isInterpolating())
{
float t = 1;
didRotationFix = true;
float oldRot = bone_head->rotation.z;
skeletalSprite.updateBones();
bone_head->rotationOffset.z = oldRot - bone_head->rotation.z;
bone_head->rotationOffset.interpolateTo(Vector(0,0,0), t);
bone_head->internalOffset.interpolateTo(Vector(0,0,0), t);
}
state.updateLookAtTime += dt*4*2;
bone_head->internalOffset.interpolateTo(Vector(0,0), 0.2f);
}
if (state.updateLookAtTime > 1.5f)
{
state.lookAtEntity = dsq->game->getNearestEntity(position, 800, this, ET_NOTYPE, DT_NONE, LR_ENTITIES0, LR_ENTITIES2);
if (state.lookAtEntity && state.lookAtEntity->isv(EV_LOOKAT, 1))
{
state.updateLookAtTime = 0;
if (!state.lookAtEntity->naijaReaction.empty())
{
setHeadTexture(state.lookAtEntity->naijaReaction, 1.5f);
}
}
else
{
state.lookAtEntity = 0;
}
}
}
}
}
Vector Avatar::getHeadPosition()
{
if (bone_head)
return bone_head->getWorldPosition();
return position;
}
bool lastCursorKeyboard = false;
void Avatar::onUpdate(float dt)
{
BBGE_PROF(Avatar_onUpdate);
looking = 0;
if (lightFormGlow)
{
if (dsq->continuity.light)
{
lightFormGlow->scale = Vector(6,6) + Vector(4,4)*dsq->continuity.light;
}
else
{
lightFormGlow->scale = Vector(6,6);
}
}
applyRidingPosition();
if (activateEntity)
{
activateEntity->activate();
activateEntity = 0;
}
if (bone_head)
headPosition = bone_head->getWorldPosition();
//vel /= 0;
if (vel.isNan())
{
debugLog("detected velocity NaN");
vel = Vector(0,0);
}
if (fireDelay > 0)
{
fireDelay -= dt;
if (fireDelay < 0)
{
fireDelay = 0;
}
}
if (isInputEnabled())
{
if (web)
{
if (!webBitTimer.isActive())
{
webBitTimer.start(0.5);
}
web->setPoint(curWebPoint, position);
if (webBitTimer.updateCheck(dt))
{
webBitTimer.start(0.5);
curWebPoint = web->addPoint(position);
}
}
if (!dsq->game->isPaused() && isActing(ACTION_LOOK, -1) && !dsq->game->avatar->isSinging() && dsq->game->avatar->isInputEnabled() && !dsq->game->isInGameMenu())
{
looking = 1;
}
else
{
looking = 0;
}
}
else
{
looking = 0;
}
Entity::onUpdate(dt);
if (isEntityDead() && skeletalSprite.getCurrentAnimation()->name != "dead")
{
fallOffWall();
biteLeftEmitter.stop();
biteRightEmitter.stop();
wakeEmitter.stop();
rollLeftEmitter.stop();
rollRightEmitter.stop();
dsq->game->toggleOverrideZoom(false);
if (dsq->continuity.form != FORM_NORMAL)
changeForm(FORM_NORMAL);
setHeadTexture("Pain");
core->globalScale.interpolateTo(Vector(5,5),3);
rotation.interpolateTo(Vector(0,0,0), 0.1f);
skeletalSprite.animate("dead");
}
if (isEntityDead())
{
dsq->game->toggleOverrideZoom(false);
}
if (dsq->user.control.targeting)
updateTargets(dt, false);
else
targets.clear();
updateTargetQuads(dt);
updateDualFormGlow(dt);
updateLookAt(dt);
updateFoodParticleEffects();
if (!dsq->game->isPaused())
myZoom.update(dt);
_isUnderWater = isUnderWater();
splashDelay -= dt;
if (splashDelay < 0)
splashDelay = 0;
// JUMPING OUT
if (!_isUnderWater && state.wasUnderWater)
{
// "falling" out, not bursting out
int fallOutSpeed = 200;
/*
if (waterBubble)
fallOutSpeed = 400;
*/
//bool waterBubbleRect = (waterBubble && waterBubble->pathShape == PATHSHAPE_RECT);
//&& !waterBubbleRect
if (!riding && (!bursting && vel.isLength2DIn(fallOutSpeed)))
{
if (waterBubble)
{
// prevent from falling out
// if circle, clamp
waterBubble->clampPosition(&position);
vel *= 0.5f;
startBurstCommon();
}
else
{
if (!dsq->game->waterLevel.isInterpolating())
{
if (vel.y < 0)
vel.y = -vel.y*0.5f;
position.y = dsq->game->waterLevel.x + collideRadius;
}
}
}
else
{
if (waterBubble)
lastJumpOutFromWaterBubble = true;
else
lastJumpOutFromWaterBubble = false;
lastWaterBubble = waterBubble;
waterBubble = 0;
BBGE_PROF(Avatar_splashOut);
splash(false);
if (dsq->continuity.form != FORM_FISH)
{
vel *= vars->jumpVelocityMod; // 1.25f;
vel.capLength2D(2000);
currentMaxSpeed *= 2.0f;
}
else
{
vel *= 1.5f;
vel.capLength2D(1500);
currentMaxSpeed *= 1.5f;
}
// total max speed
fallGravityTimer = 0.0;
wakeEmitter.stop();
biteTimer = 0;
//stopBurst();
// if first time
if (!dsq->mod.isActive() && dsq->continuity.getFlag("leftWater")==0 && dsq->game->sceneName.find("veil")!=std::string::npos)
{
setInvincible(true);
setv(EV_NOINPUTNOVEL, 0);
setWasUnderWater();
if (vel.y > -500)
vel.y = -500;
dsq->continuity.setFlag("leftWater", 1);
core->sound->fadeMusic(SFT_OUT, 2);
//("Veil");
dsq->game->avatar->disableInput();
dsq->gameSpeed.interpolateTo(0.1f, 0.5f);
//dsq->sound->setMusicFader(0.5, 0.5);
core->sound->playSfx("NaijaGasp");
core->run(0.75);
dsq->voiceOnce("Naija_VeilCrossing");
core->run(10*0.1f);
dsq->gameSpeed.interpolateTo(1, 0.2f);
dsq->sound->playMusic("Veil", SLT_LOOP, SFT_CROSS, 20);
//dsq->sound->setMusicFader(1.0, 1);
dsq->game->avatar->enableInput();
setv(EV_NOINPUTNOVEL, 1);
setInvincible(false);
//dsq->continuity.setFlag("leftWater", 0);
}
state.outOfWaterTimer = 0;
state.outOfWaterVel = vel;
//startBackFlip();
}
if (currentMaxSpeed > dsq->v.maxOutOfWaterSpeed)
{
currentMaxSpeed = dsq->v.maxOutOfWaterSpeed;
}
if (currentMaxSpeed < 1200)
{
currentMaxSpeed = 1200;
}
}
// JUMPING IN
else if (_isUnderWater && !state.wasUnderWater)
{
// falling in
splash(true);
lastOutOfWaterMaxSpeed = getMaxSpeed();
lastOutOfWaterMaxSpeed *= 0.75f;
if (lastOutOfWaterMaxSpeed > 1000)
lastOutOfWaterMaxSpeed = 1000;
fallGravityTimer = 0.5;
if (rolling)
fallGravityTimer *= 1.5f;
stopBurst();
if (state.backFlip)
{
stopBackFlip();
}
}
state.wasUnderWater = _isUnderWater;
if (!_isUnderWater)
{
state.outOfWaterTimer += dt;
if (state.outOfWaterTimer > 100)
state.outOfWaterTimer = 100;
}
if (!state.backFlip && !_isUnderWater && state.outOfWaterTimer < 0.1f && !riding && !boneLock.on)
{
const int check = 64;
Vector m = getVectorToCursor();
if (state.outOfWaterVel.x < 0 && m.x > check)
{
startBackFlip();
}
if (state.outOfWaterVel.x > 0 && m.x < -check)
{
startBackFlip();
}
}
if (isEntityDead())
{
updateHair(dt);
}
if (isEntityDead()) return;
if (flourishTimer.updateCheck(dt))
{
flourish = 0;
rotationOffset.z = 0;
}
if (isInputEnabled())
stillTimer.update(dt);
if (vel.isZero()) //&& !isSinging())
{
if (!stillTimer.isActive())
{
stillTimer.startStopWatch();
//debugLog("start stillTimer");
}
}
else
{
stillTimer.stop();
}
flourishPowerTimer.updateCheck(dt);
if (isSinging())
{
if (songInterfaceTimer < 1)
songInterfaceTimer += dt;
}
if (quickSongCastDelay>0)
{
quickSongCastDelay -= dt;
if (quickSongCastDelay < 0)
quickSongCastDelay = 0;
}
if (ripples && _isUnderWater)
{
rippleDelay -= dt;
if (rippleDelay < 0)
{
if (core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),position+offset,0.04f,0.06f,15,0.2f));
rippleDelay = 0.15f;
}
}
if (dsq->continuity.tripTimer.isActive())
{
static int tripCount = 0;
tripDelay -= dt;
if (tripDelay < 0)
{
tripDelay = 0.15f;
tripCount ++;
if (tripCount > 10)
{
float p = dsq->continuity.tripTimer.getPerc();
if (p > 0.6f)
{
if (core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),position+offset,0.04f,0.06f,15,0.2f));
}
else
{
if (core->afterEffectManager)
core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),position+offset,0.4f,0.6f,15,0.2f));
}
if (p > 0.75f){}
else if (p > 0.5f)
{
dsq->shakeCamera(2, 4);
if (chance(80))
{
if (chance(60))
dsq->emote.playSfx(EMOTE_NAIJALAUGH);
else
dsq->emote.playSfx(EMOTE_NAIJAEVILLAUGH);
}
}
else
{
if (p < 0.2f)
dsq->shakeCamera(10, 4);
else
dsq->shakeCamera(5, 4);
tripper->color.interpolateTo(Vector(1, 0.2f, 0.2f), 3);
if (chance(75))
dsq->emote.playSfx(EMOTE_NAIJAUGH);
}
tripCount = 0;
}
}
}
if (position.isInterpolating())
{
lastPosition = position;
}
updateFormVisualEffects(dt);
updateRoll(dt);
updateWallJump(dt);
if (formAbilityDelay > 0)
{
formAbilityDelay -= dt;
if (formAbilityDelay < 0)
formAbilityDelay = 0;
}
//updateCursor(dt);
if (getState() == STATE_PUSH)
{
/*
if (rotation.z < 0)
rotation.z += 360;
if (rotation.z > 360)
rotation.z -= 360;
*/
rotateToVec(vel, 0, -90);
if (vel.x < 0&& !isfh())
flipHorizontal();
else if (vel.x > 0 && isfh())
flipHorizontal();
}
updateAura(dt);
updateSingingInterface(dt);
if (pullTarget)
{
if (pullTarget->life < 1 || !pullTarget->isPullable())
{
pullTarget->stopPull();
pullTarget = 0;
}
}
formTimer += dt;
if (dsq->continuity.form == FORM_SPIRIT)
{
if (formTimer > 1)
{
if (!(bodyPosition - position).isLength2DIn(SPIRIT_RANGE))
{
changeForm(FORM_NORMAL);
}
}
// here
if (!_isUnderWater)
{
changeForm(FORM_NORMAL);
}
}
// revert stuff
float revertGrace = 0.4f;
static bool revertButtonsAreDown = false;
if (inputEnabled && (dsq->inputMode == INPUT_KEYBOARD || dsq->inputMode == INPUT_MOUSE) && (!pathToActivate && !entityToActivate))
{
if (dsq->continuity.form != FORM_NORMAL && (core->mouse.pure_buttons.left && core->mouse.pure_buttons.right) && getVectorToCursor(true).isLength2DIn(minMouse))
{
if (!revertButtonsAreDown)
{
revertTimer = revertGrace;
revertButtonsAreDown = true;
}
else if (revertButtonsAreDown)
{
if (revertTimer > 0)
{
revertTimer -= dt;
if (revertTimer < 0)
{
revertTimer = 0;
}
}
}
}
//&& !isActing(ACTION_PRIMARY) && !isActing(ACTION_SECONDARY)
else if ((!core->mouse.pure_buttons.left && !core->mouse.pure_buttons.right))
{
if (revertTimer > 0 && getVectorToCursor(true).isLength2DIn(minMouse) && state.spellCharge < revertGrace+0.1f)
{
revert();
//changeForm(FORM_NORMAL);
}
revertButtonsAreDown = false;
revertTimer = 0;
}
}
else
{
revertButtonsAreDown = false;
}
//if (core->getNestedMains() == 1)
{
if (getState() != STATE_TRANSFORM && !dsq->game->isWorldPaused())
{
formAbilityUpdate(dt);
}
if (state.useItemDelay.updateCheck(dt))
{
}
ActionMapper::onUpdate(dt);
if (inputEnabled)
{
if (state.blind)
{
if (state.blindTimer.updateCheck(dt))
{
state.blind = false;
removeBlindEffects();
}
}
}
if (boneLock.entity != 0)
{
/*
std::ostringstream os;
os << "boneLock.wallNormal(" << boneLock.wallNormal.x << ", " << boneLock.wallNormal.y << ")";
debugLog(os.str());
*/
if (!_isUnderWater && !(boneLock.wallNormal.y < -0.03f))
{
if (lockToWallFallTimer == -1)
lockToWallFallTimer = 0.4f;
}
else
lockToWallFallTimer = -1;
}
if (lockToWallFallTimer > 0)
{
lockToWallFallTimer -= dt;
if (lockToWallFallTimer <= 0)
{
fallOffWall();
}
}
if (state.lockToWallDelay.updateCheck(dt))
{
}
if (pushingOffWallEffect > 0)
{
pushingOffWallEffect -= dt;
if (pushingOffWallEffect <= 0)
{
pushingOffWallEffect = 0;
if (vel.getSquaredLength2D() > sqr(1200))
{
vel.setLength2D(1200);
}
}
}
if (charging)
{
state.spellCharge += dt;
switch (dsq->continuity.form)
{
case FORM_SUN:
{
if (state.spellCharge > 1.5f && chargeLevelAttained <1)
{
chargeLevelAttained = 1;
core->sound->playSfx("PowerUp");
chargingEmitter->load("ChargingEnergy2");
}
}
break;
case FORM_DUAL:
{
if (state.spellCharge >= 1.4f && chargeLevelAttained<1)
{
chargeLevelAttained = 1;
core->sound->playSfx("PowerUp");
//debugLog("charge visual effect 2");
chargeEmitter->load("ChargeDualForm");
chargeEmitter->start();
chargingEmitter->load("ChargedDualForm");
chargingEmitter->start();
}
}
break;
case FORM_ENERGY:
{
if (state.spellCharge >= 1.5f && chargeLevelAttained<2)
{
chargeLevelAttained = 2;
core->sound->playSfx("PowerUp");
//debugLog("charge visual effect 2");
chargeEmitter->load("ChargeEnergy");
chargeEmitter->start();
chargingEmitter->load("ChargingEnergy2");
//chargeVisualEffect("particles/energy-charge-2");
}
}
break;
case FORM_NATURE:
{
if (state.spellCharge >= 0.9f && chargeLevelAttained<2)
{
chargeLevelAttained = 2;
core->sound->playSfx("PowerUp");
chargeEmitter->load("ChargeNature2");
chargeEmitter->start();
chargingEmitter->load("ChargingNature2");
chargingEmitter->start();
}
}
break;
case FORM_NORMAL:
case FORM_BEAST:
case FORM_SPIRIT:
case FORM_FISH:
case FORM_MAX:
case FORM_NONE:
break;
}
}
/*
float angle = PI - ((rotation.z/180)*PI);
int height = 25;
*/
//hair->hairNodes[0].position = position + Vector(sinf(angle)*height, cosf(angle)*height);
if (biteTimer < biteTimerMax)
{
biteTimer += dt;
}
else
{
biteLeftEmitter.stop();
biteRightEmitter.stop();
biteTimer = biteTimerMax;
}
if (biteTimer > biteTimerBiteRange)
{
biteLeftEmitter.stop();
biteRightEmitter.stop();
}
/*
std::ostringstream os;
os << "biteTimer: " << biteTimer;
debugLog(os.str());
*/
if (isInputEnabled())
{
if (dsq->continuity.form == FORM_NORMAL && nocasecmp(dsq->continuity.costume, "urchin") == 0)
{
if (!isEntityDead() && health > 0)
{
urchinDelay -= dt;
if (urchinDelay < 0)
{
urchinDelay = 0.1f;
dsq->game->fireShot("urchin", this, 0, position + offset);
}
}
}
if (dsq->continuity.form == FORM_NORMAL && nocasecmp(dsq->continuity.costume, "jelly")==0)
{
if (!isEntityDead() && health > 0)
{
if (health < (maxHealth*JELLYCOSTUME_HEALTHPERC))
{
jellyDelay -= dt;
if (jellyDelay < 0)
{
jellyDelay = JELLYCOSTUME_HEALDELAY;
Vector d;
if (!vel.isZero())
{
d = vel;
d.setLength2D(16);
}
dsq->game->spawnManaBall(position + offset + d, JELLYCOSTUME_HEALAMOUNT);
//Shot *s = dsq->game->fireShot("urchin", this, 0, getWorldPosition());
}
}
}
}
}
if (dsq->continuity.form == FORM_BEAST && bone_head && biteTimer < biteTimerBiteRange && biteTimer > 0)
{
biteDelay -= dt;
if (biteDelay < 0)
{
biteDelay = biteDelayPeriod;
Vector p = bone_head->getWorldPosition();
std::string shot = "Bite";
if (dsq->continuity.biteMult > 1)
{
shot = "SuperBite";
}
dsq->game->fireShot(shot, this, 0, p);
//s->setAimVector(getNormal());
}
}
if (dsq->continuity.form == FORM_FISH && dsq->continuity.fishPoisonTimer.isActive())
{
if (!vel.isLength2DIn(16))
{
static float fishPoison = 0;
fishPoison += dt;
if (fishPoison > 0.2f)
{
fishPoison = 0;
dsq->game->fireShot("FishPoison", this, 0, position);
}
}
}
if (!state.lockedToWall && _isUnderWater && !dsq->game->isWorldPaused() && canMove)
{
if (bursting)
{
//debugLog("bursting~!");
burst -= dt * BURST_USE_RATE;
burstTimer += dt;
/*
std::ostringstream os;
os << "burst: " << burst;
debugLog(os.str());
*/
if (burst <= 0)
{
stopBurst();
}
}
}
if (inputEnabled && _isUnderWater)
{
if(bursting)
{
}
else if (burstDelay > 0)
{
burstDelay -= dt;
if (burstDelay <= 0)
burstDelay = 0;
}
else if (burst < 1)
{
burst += BURST_RECOVER_RATE * dt;
if (burst >= 1)
burst = 1;
}
}
bool moved = false;
//check to make sure there's still a wall there, if not fall off
if (state.lockedToWall)
{
rotateToVec(wallPushVec, dt*2);
if (!boneLock.on && !dsq->game->isObstructed(wallLockTile))
{
//debugLog("Dropping from wall");
fallOffWall();
}
}
if (getState() != STATE_PUSH && !state.lockedToWall && inputEnabled && _isUnderWater && canMove)
{
float a = 800*dt;
Vector addVec;
bool isMovingSlow = false;
float len = 0;
if (dsq->isMiniMapCursorOkay() && !isActing(ACTION_ROLL, -1) &&
_isUnderWater && !riding && !boneLock.on &&
(movingOn || ((dsq->inputMode == INPUT_JOYSTICK || dsq->inputMode== INPUT_KEYBOARD) || (core->mouse.buttons.left || bursting))))
{
if (dsq->inputMode == INPUT_MOUSE || !this->singing)
{
addVec = getVectorToCursorFromScreenCentre();//getVectorToCursor();
if (dsq->inputMode == INPUT_MOUSE)
{
static Vector lastAddVec;
if (!isActing(ACTION_PRIMARY, -1) && bursting)
{
addVec = lastAddVec;
}
if (bursting)
{
lastAddVec = addVec;
}
}
if (addVec.isLength2DIn(minMouse))
{
if (dsq->inputMode == INPUT_JOYSTICK)
addVec = Vector(0,0,0);
}
if (!addVec.isLength2DIn(minMouse))
{
//if (core->mouse.buttons.left)
{
len = addVec.getLength2D();
if (len > 100)
addVec.setLength2D(a *2);
else
addVec.setLength2D(a);
addVec *= dsq->continuity.speedMult;
//128
if (len < maxMouse && !bursting)
{
isMovingSlow = true;
}
}
}
else
{
// stop movement
// For joystick/keyboard control, don't stop unless
// the Swim (primary action) button is pressed with
// no movement input. --achurch
if ((dsq->inputMode == INPUT_MOUSE || isActing(ACTION_PRIMARY, -1))
&& addVec.isLength2DIn(STOP_DISTANCE))
{
vel *= 0.9f;
if (!rolling)
rotation.interpolateTo(Vector(0,0,0), 0.1f);
if (vel.isLength2DIn(50))
{
if (bursting)
{
stopBurst();
}
}
}
addVec = Vector(0,0,0);
}
}
}
if (!rolling && !state.backFlip && !flourish)
{
if (addVec.x > 0)
{
if (!isfh())
flipHorizontal();
}
if (addVec.x < 0)
{
if (isfh())
flipHorizontal();
}
}
// will not get here if not underwater
if (isLockable())
lockToWall();
if ((addVec.x != 0 || addVec.y != 0))
{
currentMaxSpeed=0;
vel += addVec;
if (bursting)
{
Vector add = addVec;
add.setLength2D(BURST_ACCEL*dt);
vel += add;
if (pushingOffWallEffect > 0)
currentMaxSpeed = vars->maxWallJumpBurstSpeed;
else
currentMaxSpeed = vars->maxBurstSpeed;
}
else
{
if (pushingOffWallEffect > 0)
currentMaxSpeed = vars->maxWallJumpSpeed;
else
{
if (isMovingSlow)
{
currentMaxSpeed = vars->maxSlowSwimSpeed;
}
else
currentMaxSpeed = vars->maxSwimSpeed;
}
}
if (leaches > 0)
{
currentMaxSpeed -= leaches*60;
}
if (state.blind)
currentMaxSpeed -= 100;
if (currentMaxSpeed < 0)
currentMaxSpeed = 1;
if (getState() == STATE_TRANSFORM)
rotateToVec(addVec, 0.1f, 90);
else
{
if (rolling)
{
// here for roll key?
// seems like this isn't reached
//if (isActing("roll"))
if (isActing(ACTION_ROLL, -1))
{
//debugLog("here");
}
else
{
float t = 0;
if (dsq->inputMode == INPUT_KEYBOARD)
t = 0.1f;
rotateToVec(addVec, t);
}
}
else if (bursting && flourish)
{
}
else
{
if (!state.nearWall && !flourish)
rotateToVec(addVec, 0.1f);
}
}
moved = true;
if ((!swimming || (swimming && !bursting && skeletalSprite.getCurrentAnimation()->name != "swim")) && !state.lockedToWall)
{
swimming = true;
//Animation *a = skeletalSprite.getCurrentAnimation();
if (getState() == STATE_IDLE && !rolling)
{
skeletalSprite.transitionAnimate("swim", ANIM_TRANSITION, -1);
}
//animate(anim_swim);
}
skeletalSprite.setTimeMultiplier(1);
Animation *anim=skeletalSprite.getCurrentAnimation();
if (!bursting && (anim && anim->name == "swim"))
{
float velLen = vel.getLength2D();
float time = velLen / 1200.0f;
if (velLen > 1200)
time = 1;
//skeletalSprite.setTimeMultiplier(time*3);// 5
//skeletalSprite.setTimeMultiplier(time*3.5f);
//skeletalSprite.setTimeMultiplier(time*4);
//animator.timePeriod = 1.5f*(1.0f-time);
skeletalSprite.setTimeMultiplier(time*4.5f);
}
else
{
if (currentAnim != getBurstAnimName() && skeletalSprite.getCurrentAnimation()->name != getBurstAnimName() && !state.lockedToWall)
{
if (getState() == STATE_IDLE && !rolling)
skeletalSprite.transitionAnimate(getBurstAnimName(), ANIM_TRANSITION);
}
}
}
}
//int currentSwimSpeed = 400;
//if (dsq->continuity.getWorldType() == WT_SPIRIT)
/*
if (dsq->continuity.form == FORM_SPIRIT)
{
currentSwimSpeed *= 0.3f;
}
*/
if (!_isUnderWater && !state.lockedToWall)
{
//currentSwimSpeed *= 1.5f;
//float a = *dt;
// base on where the mouse is
/*
Vector addVec;
addVec = getVectorToCursorFromScreenCentre();
addVec.setLength2D(a);
*/
// gravity
float fallMod = 1.5;
if (dsq->continuity.form == FORM_SPIRIT)
{
fallMod = 1.0;
}
vel += Vector(0,980)*dt*fallMod;
if (!rolling && !state.backFlip && !flourish)
{
if (vel.x != 0 || vel.y != 0)
rotateToVec(vel, 0.1f);
if (vel.x > 0)
{
if (!isfh())
flipHorizontal();
}
if (vel.x < 0)
{
if (isfh())
flipHorizontal();
}
}
if (rolling && !state.backFlip)
{
Vector v = getVectorToCursorFromScreenCentre();
rotateToVec(v, 0.01f);
}
if (isLockable())
lockToWall();
}
if (!moved)
{
if (swimming)
{
swimming = false;
if (dsq->continuity.form == FORM_FISH)
rotation.interpolateTo(0, 0.2f);
}
// "friction"
//vel += -vel*0.999f*dt;
if (_isUnderWater)
{
/*
std::ostringstream os;
os << "fric(" << vel.x << ", " << vel.y;
debugLog(os.str());
*/
if (isInCurrent())
doFriction(dt*5);
else
doFriction(dt);
}
}
}
if (_isUnderWater && isInCurrent())
{
if (dsq->loops.current == BBGE_AUDIO_NOCHANNEL)
{
PlaySfx play;
play.name = "CurrentLoop";
play.vol = 1;
play.time = 1;
play.fade = SFT_IN;
play.loops = -1;
dsq->loops.current = core->sound->playSfx(play);
}
}
else
{
if (dsq->loops.current != BBGE_AUDIO_NOCHANNEL)
{
core->sound->fadeSfx(dsq->loops.current, SFT_OUT, 1);
dsq->loops.current = BBGE_AUDIO_NOCHANNEL;
}
}
if (!swimming && _isUnderWater)
{
if (!inCurrent)
currentMaxSpeed = vars->maxSwimSpeed;
if (!state.lockedToWall && !bursting)
{
if (getState() == STATE_IDLE && inputEnabled)
{
Animation *a = skeletalSprite.getCurrentAnimation(0);
if (a && a->name != getIdleAnimName() && a->name != "pushed" && a->name != "spin" && !rolling)
skeletalSprite.transitionAnimate(getIdleAnimName(), ANIM_TRANSITION, -1);
}
}
}
if (_isUnderWater && fallGravityTimer)
{
fallGravityTimer -= dt;
currentMaxSpeed = lastOutOfWaterMaxSpeed;
if (fallGravityTimer < 0)
fallGravityTimer = 0;
}
clampVelocity();
if (swimming)
{
if (!rolling && !internalOffset.isInterpolating())
{
int spread = 8;
//int rotSpread = 45;
float t = 1;
internalOffset = Vector(-spread, 0);
internalOffset.interpolateTo(Vector(spread, 0), t, -1, 1, 1);
for (int i = 0; i < int((t*0.5f)/0.01f); i++)
{
internalOffset.update(0.01f);
}
}
if (dsq->continuity.form != FORM_ENERGY && dsq->continuity.form != FORM_DUAL && dsq->continuity.form != FORM_FISH)
{
if (leaches <= 0 && !bursting && !skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->animating)
{
state.swimTimer += dt;
if (state.swimTimer > 5)
{
state.swimTimer = 0 - rand()%3;
static int lastSwimExtra = -1;
int maxAnim = 4;
int anim = rand()%maxAnim;
if (anim == lastSwimExtra)
anim++;
if (anim >= maxAnim)
anim = 0;
lastSwimExtra = anim;
anim ++;
std::ostringstream os;
os << "swimExtra-" << anim;
skeletalSprite.transitionAnimate(os.str(), 0.5, 0, ANIMLAYER_UPPERBODYIDLE);
}
}
}
}
else
{
}
if (!swimming || rolling)
{
//state.swimTimer = 0;
if (skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->animating)
{
skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
}
internalOffset.interpolateTo(Vector(0,0),0.5f);
}
checkNearWall();
//if (core->getNestedMains()==1)
{
Vector zoomSurface(0.55f, 0.55f);
Vector zoomMove(vars->zoomMove, vars->zoomMove), zoomStop(vars->zoomStop, vars->zoomStop), zoomNaija(vars->zoomNaija, vars->zoomNaija);
float cheatLen = getMoveVel().getSquaredLength2D();
if (cheatLen > sqr(250) && _isUnderWater && !state.lockedToWall)
{
if (!swimEmitter.isRunning())
swimEmitter.start();
}
else
{
swimEmitter.stop();
}
Vector targetScale(1,1);
if (zoomOverriden || isEntityDead() || core->globalScale.isInterpolating())
{
}
else
{
if (dsq->game->waterLevel.x > 0 && fabsf(avatar->position.y - dsq->game->waterLevel.x) < 800)
{
float time = 0.5f;
if (!myZoom.isInterpolating() || ((!core->globalScale.data || core->globalScale.data->target != zoomSurface) && myZoom.data->timePeriod != time))
{
myZoom.interpolateTo(zoomSurface, time, 0, 0, 1);
}
}
else if (avatar->looking == 2)
{
float time = 1.0f;
if (!myZoom.isInterpolating() || ((!core->globalScale.data || core->globalScale.data->target != zoomNaija) && myZoom.data->timePeriod != time))
{
/*
std::ostringstream os;
os << "zooming in on Naija: " << zoomNaija.x;
debugLog(os.str());
*/
myZoom.interpolateTo(zoomNaija, time, 0, 0, 1);
}
}
else if ((cheatLen > sqr(250) && cheatLen < sqr(1000)) || attachedTo || avatar->looking==1)
{
float time = 3;
if (avatar->looking)
{
time = 1.0f;
}
if (!myZoom.isInterpolating() || ((!core->globalScale.data || core->globalScale.data->target != zoomMove) && myZoom.data->timePeriod != time))
myZoom.interpolateTo(zoomMove, time, 0, 0, 1);
}
else if (cheatLen < sqr(210) && !state.lockedToWall && stillTimer.getValue() > 4 && !isSinging())
{
float time = 10;
if (!myZoom.isInterpolating() || (myZoom.data->target != zoomStop && myZoom.data->timePeriod != time))
myZoom.interpolateTo(zoomStop, time, 0, 0, 1);
}
else if (cheatLen >= sqr(1000))
{
float time = 1.6f;
if (!myZoom.isInterpolating() || (myZoom.data->target != zoomMove && myZoom.data->timePeriod != time))
myZoom.interpolateTo(zoomMove, time, 0, 0, 1);
}
if (myZoom.x < game->maxZoom)
{
core->globalScale.x = game->maxZoom;
core->globalScale.y = game->maxZoom;
}
else
{
core->globalScale.x = myZoom.x;
core->globalScale.y = myZoom.y;
}
core->globalScaleChanged();
}
if (!state.lockedToWall && !bursting && _isUnderWater && swimming && !isFollowingPath() && _collisionAvoidRange > 0)
{
doCollisionAvoidance(dt, _collisionAvoidRange, _collisionAvoidMod, 0, 800, OT_HURT);
}
if (!game->isShuttingDownGameState())
{
updateCurrents(dt);
updateVel2(dt);
}
else
{
vel2 = Vector(0,0,0);
}
if (!state.lockedToWall && !isFollowingPath() && !riding)
{
/*collideCheck:*/
// Beware: This code may cause clamping vel to zero if the framerate is very high.
// Starting with zero vel, low difftimes will cause an addVec small enough that this
// check will always trigger, and vel will never get larger than zero.
// Under water and swimming check should hopefully prevent this from happening. -- FG
if (_isUnderWater && !isSwimming() && vel.getLength2D() < sqr(2))
{
vel = Vector(0,0,0);
}
Vector moveVel;
if (!isInputEnabled() && isv(EV_NOINPUTNOVEL, 1))
{
vel2=vel=Vector(0,0);
}
else
{
moveVel = getMoveVel();
}
if (!moveVel.isZero())
{
bool collided = false;
/*
std::ostringstream os;
os << "vel (" << vel.x << ", " << vel.y << ")";
debugLog(os.str());
*/
Vector mov = (moveVel * dt);
Vector omov = mov;
mov.capLength2D(TILE_SIZE);
/*
if (mov.getSquaredLength2D() > sqr(TILE_SIZE))
mov.setLength2D(TILE_SIZE);
*/
if (omov.getSquaredLength2D() > 0)
{
while (omov.getSquaredLength2D() > 0)
{
if (omov.getSquaredLength2D() < sqr(TILE_SIZE))
{
mov = omov;
omov = Vector(0,0);
}
else
omov -= mov;
lastPosition = position;
Vector newPosition = position + mov;
//Vector testPosition = position + (vel *dt)*2;
position = newPosition;
if (dsq->game->collideCircleWithGrid(position, collideRadius))
{
if (dsq->game->lastCollideTileType == OT_HURT
&& isDamageTarget(DT_WALLHURT))
{
DamageData d;
d.damage = 1;
d.damageType = DT_WALLHURT;
damage(d);
vel2 = Vector(0,0,0);
//doCollisionAvoidance(1, 3, 1);
/*
Vector v = dsq->game->getWallNormal(position);
if (!v.isZero())
{
vel += v * 500;
}
*/
}
collided = true;
if (currentState == STATE_PUSH)
{
dsq->sound->playSfx("rockhit");
dsq->spawnParticleEffect("rockhit", position);
if (pushDamage)
{
DamageData d;
d.damage = pushDamage;
damage(d);
}
setState(STATE_IDLE);
}
if (!_isUnderWater)
{
/*
doBounce();
position = lastPosition;
*/
// this is the out of water bounce...
//debugLog("above water bounce");
Vector n = getWallNormal(TileVector(lastPosition));
n *= vel.getLength2D();
/*
std::ostringstream os;
os << "vel(" << vel.x << ", " << vel.y << ") n(" << n.x << ", " << n.y << ")";
debugLog(os.str());
*/
//flopping = true;
vel = -vel;
//vel = (vel*0.5f + n*0.5f);
if (vel.y < 0)
{
//debugLog("Vel less than 0");
}
if (vel.y == 0)
{
//debugLog("Vel was 0");
vel = getWallNormal(TileVector(position));
vel.setLength2D(500);
}
if (vel.isLength2DIn(500))
vel.setLength2D(500);
vel.capLength2D(800);
position = lastPosition;
this->doCollisionAvoidance(1, 4, 0.5, 0, 500);
/*
vel = -vel;
if (vel.y < 0)
vel.y *= 2;
position = lastPosition;
*/
}
else
{
position = lastPosition;
// works as long as not buried in wall ... yep. =(
if (dsq->game->isObstructed(TileVector(position)))
{
//vel = -vel*0.5f;
vel = 0;
}
else
{
// && dsq->game->getPercObsInArea(position, 4) < 0.75f
if (!inCurrent)
{
if (bursting)
{
//vel = 0;
}
else
{
if (!_isUnderWater && dsq->continuity.form == FORM_FISH)
{
vel = 0;
}
else
{
// this bounce is what causes naija to get wedged
float len = vel.getLength2D();
Vector n = dsq->game->getWallNormal(position, 5);
if (n.x == 0 && n.y == 0)
{
vel = 0;
}
else
{
//n.setLength2D(len/2);
//vel += n;
n.setLength2D(len);
vel = (n+vel)*0.5f;
//vel = (n + vel)*0.5f;
}
}
}
}
}
}
}
if (!collided && checkWarpAreas()) collided = true;
if (collided) break;
// DO NOT COLLIDE WITH LR_ENTITIES
// ENTITY SCRIPTS WILL HANDLE Entity V. Entity COLLISIONS
}
}
}
}
}
//fuuugly
// you said it!
if (riding || boneLock.on)
{
checkWarpAreas();
}
applyRidingPosition();
updateHair(dt);
chargeEmitter->position = chargingEmitter->position = position + offset;
// particle sets
if (leftHandEmitter && rightHandEmitter && boneLeftHand && boneRightHand)
{
leftHandEmitter->position = boneLeftHand->getWorldCollidePosition(Vector(0, 16));
rightHandEmitter->position = boneRightHand->getWorldCollidePosition(Vector(0,16));
}
if(canCollideWithShots())
dsq->game->handleShotCollisions(this, (activeAura == AURA_SHIELD));
if(!core->particlesPaused && elementEffectMult > 0)
{
ElementUpdateList& elems = dsq->game->elementInteractionList;
for (ElementUpdateList::iterator it = elems.begin(); it != elems.end(); ++it)
{
(*it)->doInteraction(this, elementEffectMult, 16);
}
}
}
void Avatar::checkNearWall()
{
state.nearWall = false;
if (!inCurrent && bursting && !state.lockedToWall && !vel.isZero() && !riding && _isUnderWater)
{
int checkRange = 11;
Vector v = vel;
v.normalize2D();
TileVector oT(position);
TileVector t=oT,lastT=oT;
bool obs = false;
for (int i = 1; i < checkRange; i++)
{
t.x = oT.x + v.x*i;
t.y = oT.y + v.y*i;
if (dsq->game->isObstructed(t, ~OT_HURT))
{
obs = true;
break;
}
lastT = t;
}
if (obs)
{
Vector n = dsq->game->getWallNormal(t.worldVector());
if (!n.isZero())
{
state.nearWall = true;
float t=0.2f;
rotateToVec(n, t, 0);
skeletalSprite.transitionAnimate("wall", t);
}
else
state.nearWall = false;
}
else
{
state.nearWall = false;
}
}
}
void Avatar::onWarp()
{
avatar->setv(EV_NOINPUTNOVEL, 0);
closeSingingInterface();
}
bool Avatar::checkWarpAreas()
{
size_t i = 0;
for (i = 0; i < dsq->game->getNumPaths(); i++)
{
bool warp = false;
Path *p = dsq->game->getPath(i);
if (p && p->active && !p->nodes.empty())
{
PathNode *n = &p->nodes[0];
if (n)
{
Vector backPos;
if (!p->vox.empty())
{
if (p->isCoordinateInside(position))
{
if (p->replayVox == 1)
{
dsq->voice(p->vox);
p->replayVox = 2;
}
else
{
dsq->voiceOnce(p->vox);
}
}
}
if (!p->warpMap.empty() && p->pathType == PATH_WARP)
{
int range = 512;
switch(p->warpType)
{
case 'C':
if ((position - n->position).getSquaredLength2D() < sqr(range))
{
warp = true;
}
break;
default:
{
if (p->isCoordinateInside(position))
{
warp = true;
backPos = p->getBackPos(position);
}
}
break;
}
}
if (warp)
{
if (avatar->canWarp)
dsq->game->warpToSceneFromNode(p);
else
{
avatar->position = backPos;
//avatar->vel = -avatar->vel * 0.5f;
Vector n = p->getEnterNormal();
n.setLength2D(avatar->vel.getLength2D());
avatar->vel += n;
avatar->vel *= 0.5f;
}
return true;
}
if (core->getNestedMains() == 1 && !riding)
{
if (p->warpMap.empty() && !p->warpNode.empty())
{
if (p->isCoordinateInside(position))
{
Path *p2 = dsq->game->getPathByName(p->warpNode);
if (p2)
{
dsq->game->preLocalWarp(p->localWarpType);
dsq->game->avatar->position = p2->getPathNode(0)->position;
dsq->game->postLocalWarp();
//dsq->fade(0, t);
return true;
}
}
}
}
}
}
}
return false;
}