/* 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 *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 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::vectortargets; 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 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::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 entitiesToHit; std::vector 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()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(®enEmitter, 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, ®enEmitter); } 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; }