1 /*
2 Copyright (C) 2007, 2010 - Bit-Blot
3
4 This file is part of Aquaria.
5
6 Aquaria is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15 See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21 #include "../BBGE/AfterEffect.h"
22 #include "../BBGE/MathFunctions.h"
23
24 #include "Avatar.h"
25 #include "Game.h"
26 #include "Shot.h"
27 #include "GridRender.h"
28
29 //#include "CommonEvents.h"
30
31 //#include <float.h>
32
33 //#define AQ_TEST_QUADTRAIL
34
35 #ifdef AQ_TEST_QUADTRAIL
36 #include "QuadTrail.h"
37
38 QuadTrail *quadTrail = 0;
39 #endif
40
41 Path *lastWaterBubble = 0;
42 bool lastJumpOutFromWaterBubble = false;
43
44 bool useSpiritDistance = true;
45 bool inSpiritWorld = false;
46
47 const float MULT_DMG_CRABCOSTUME = 0.75;
48 const float MULT_DMG_FISHFORM = 1.5;
49 const float MULT_DMG_SEAHORSEARMOR = 0.6;
50
51 const float MULT_MAXSPEED_BEASTFORM = 1.2;
52 const float MULT_MAXSPEED_FISHFORM = 1.5;
53
54 const float MULT_DMG_EASY = 0.5;
55
56 const float JELLYCOSTUME_HEALTHPERC = 0.5;
57 const float JELLYCOSTUME_HEALDELAY = 2.0;
58 const float JELLYCOSTUME_HEALAMOUNT = 0.5;
59
60 const float biteTimerBiteRange = 0.6;
61 const float biteTimerMax = 3;
62 const float biteDelayPeriod = 0.08;
63 const int normalTendrilHits = 3;
64 const int rollTendrilHits = 4;
65 const int maxTendrilHits = 6;
66
67 const float fireDelayTime = 0.2;
68 const int maxShieldPoints = 8;
69 const int minMouse = 60;
70 int SongIcon::notesOpen = 0;
71 Avatar *avatar = 0;
72 const Vector BLIND_COLOR = Vector(0.1, 0.1, 0.1);
73 const float ANIM_TRANSITION = 0.2;
74 const float MANA_RECHARGE_RATE = 1.0;
75 const int AURA_SHIELD_RADIUS = 64;
76 //const int TARGET_RANGE = 1024;
77 const int TARGET_RANGE = 1024; // 650
78 const int TARGET_GRACE_RANGE = 200;
79 //const int TARGET_RANGE = 700;
80 //const int TARGET_RANGE = 64;
81 const float NOTE_SCALE = 0.75;
82 const int singingInterfaceRadius = 100;
83 const int openSingingInterfaceRadius = 128;
84 //164
85 const int BURST_DISTANCE = 200;
86 const int STOP_DISTANCE = 48;
87 const int maxMouse = BURST_DISTANCE;
88 //const int SHOCK_RANGE = 700;
89 const int SHOCK_RANGE = 1000;
90 const int SPIRIT_RANGE = 2000;
91
92 const float QUICK_SONG_CAST_DELAY = 0.4;
93
94 const float BURST_RECOVER_RATE = 1.2; // 3.0 // 0.75
95 const float BURST_USE_RATE = 1.5; //0.9 //1.5;
96 const float BURST_ACCEL = 4000; //2000 // 1000
97
98 // Minimum time between two splash effects (seconds).
99 const float SPLASH_INTERVAL = 0.2;
100
101 const float TUMMY_TIME = 6.0;
102
103 const float chargeMax = 2.0;
104
105 // Axis input distance (0.0-1.0) at which we start moving.
106 const float JOYSTICK_LOW_THRESHOLD = 0.2;
107 // Axis input distance at which we move full speed.
108 const float JOYSTICK_HIGH_THRESHOLD = 0.6;
109 // Axis input distance at which we accept a note.
110 const float JOYSTICK_NOTE_THRESHOLD = 0.6;
111
112 // Mouse cursor distance (from note icon, in virtual pixels) below which
113 // we accept a note.
114 const float NOTE_ACCEPT_DISTANCE = 25;
115 // Joystick input angle offset (from note icon, in degrees) below which
116 // we accept a note.
117 const float NOTE_ACCEPT_ANGLE_OFFSET = 15;
118
119 const int COLLIDE_RADIUS_NORMAL = 10;
120 const int COLLIDE_RADIUS_FISH = 8;
121
122 const int COLLIDE_RANGE_NORMAL = 2;
123 const int COLLIDE_RANGE_FISH = 1;
124
125 const float COLLIDE_MOD_NORMAL = 1.0f;
126 const float COLLIDE_MOD_FISH = 0.1f;
127
128 const int requiredDualFormCharge = 3;
129
130 bool usingDigital = false;
131
132 Bone *bone_head = 0;
133 Bone *bone_dualFormGlow = 0;
134
135
136 bool _isUnderWater;
137
138 //HRECORD avatarRecord = 0;
139
140
getWorldPosition()141 Vector Target::getWorldPosition()
142 {
143 Vector ret;
144 if (e)
145 {
146 ret = e->getTargetPoint(targetPt);
147 }
148 return ret;
149 }
150
bindInput()151 void Avatar::bindInput()
152 {
153 ActionMapper::clearActions();
154 ActionMapper::clearCreatedEvents();
155
156
157 dsq->user.control.actionSet.importAction(this, "PrimaryAction", ACTION_PRIMARY);
158 dsq->user.control.actionSet.importAction(this, "SecondaryAction", ACTION_SECONDARY);
159
160 dsq->user.control.actionSet.importAction(this, "Revert", ACTION_REVERT);
161
162 dsq->user.control.actionSet.importAction(this, "SwimUp", ACTION_SWIMUP);
163 dsq->user.control.actionSet.importAction(this, "SwimDown", ACTION_SWIMDOWN);
164 dsq->user.control.actionSet.importAction(this, "SwimLeft", ACTION_SWIMLEFT);
165 dsq->user.control.actionSet.importAction(this, "SwimRight", ACTION_SWIMRIGHT);
166
167 /*
168 dsq->user.control.actionSet.importAction(this, "SingUp", ACTION_SINGUP);
169 dsq->user.control.actionSet.importAction(this, "SingDown", ACTION_SINGDOWN);
170 dsq->user.control.actionSet.importAction(this, "SingLeft", ACTION_SINGLEFT);
171 dsq->user.control.actionSet.importAction(this, "SingRight", ACTION_SINGRIGHT);
172 */
173
174 dsq->user.control.actionSet.importAction(this, "SongSlot1", ACTION_SONGSLOT1);
175 dsq->user.control.actionSet.importAction(this, "SongSlot2", ACTION_SONGSLOT2);
176 dsq->user.control.actionSet.importAction(this, "SongSlot3", ACTION_SONGSLOT3);
177 dsq->user.control.actionSet.importAction(this, "SongSlot4", ACTION_SONGSLOT4);
178 dsq->user.control.actionSet.importAction(this, "SongSlot5", ACTION_SONGSLOT5);
179 dsq->user.control.actionSet.importAction(this, "SongSlot6", ACTION_SONGSLOT6);
180 dsq->user.control.actionSet.importAction(this, "SongSlot7", ACTION_SONGSLOT7);
181 dsq->user.control.actionSet.importAction(this, "SongSlot8", ACTION_SONGSLOT8);
182 dsq->user.control.actionSet.importAction(this, "SongSlot9", ACTION_SONGSLOT9);
183 dsq->user.control.actionSet.importAction(this, "SongSlot10", ACTION_SONGSLOT10);
184
185 dsq->user.control.actionSet.importAction(this, "Look", ACTION_LOOK);
186
187 /*
188 dsq->user.control.actionSet.importAction(this, "SongSlot5", "f5");
189 dsq->user.control.actionSet.importAction(this, "SongSlot6", "f6");
190 dsq->user.control.actionSet.importAction(this, "SongSlot7", "f7");
191 dsq->user.control.actionSet.importAction(this, "SongSlot8", "f8");
192 */
193
194 dsq->user.control.actionSet.importAction(this, "Roll", ACTION_ROLL);
195
196 /*
197 // song note keys
198 addAction("s1", KEY_1);
199 addAction("s2", KEY_2);
200 addAction("s3", KEY_3);
201 addAction("s4", KEY_4);
202 addAction("s5", KEY_5);
203 addAction("s6", KEY_6);
204 addAction("s7", KEY_7);
205 addAction("s8", KEY_8);
206 */
207
208 }
209
getNotesOpen()210 int Avatar::getNotesOpen()
211 {
212 return SongIcon::notesOpen;
213 }
214
215 // note: z is set to 1.0 when we want the aim to be used as the shot direction
216 // otherwise the shot will head straight to the target
getAim()217 Vector Avatar::getAim()
218 {
219 Vector d;
220 if (dsq->inputMode == INPUT_JOYSTICK)
221 {
222 if (!core->joystick.rightStick.isZero())
223 {
224 d = core->joystick.rightStick * 300;
225 d.z = 1;
226 }
227 else
228 {
229 d = core->joystick.position * 300;
230 d.z = 0;
231 }
232 }
233 else if (dsq->inputMode == INPUT_KEYBOARD)
234 {
235 d = dsq->getGameCursorPosition() - position;
236 d.z = 1;
237 }
238 else
239 {
240 d = dsq->getGameCursorPosition() - position;
241 d.z = 1;
242 }
243 if (d.isZero())
244 d = getForwardAim();
245 return d;
246 }
247
getForwardAim()248 Vector Avatar::getForwardAim()
249 {
250 Vector aim = getForward();
251 // getForward() points toward Naija's head, but it makes more sense
252 // to shoot in the direction she's facing, so rotate the aim vector.
253 aim = getRotatedVector(aim, isfh() ? 90 : -90);
254 return aim;
255 }
256
postInit()257 void Avatar::postInit()
258 {
259 // post init isn't early enough
260 /*
261 Entity::postInit();
262 */
263 }
264
onAnimationKeyPassed(int key)265 void Avatar::onAnimationKeyPassed(int key)
266 {
267 Entity::onAnimationKeyPassed(key);
268 }
269
doBounce()270 void Avatar::doBounce()
271 {
272 float ba = 0.75;
273 if (isRolling())
274 ba = 1.0;
275 float len = vel.getLength2D();
276 Vector I = vel/len;
277 Vector N = dsq->game->getWallNormal(position);
278
279 if (!N.isZero())
280 {
281 //2*(-I dot N)*N + I
282 vel = 2*(-I.dot(N))*N + I;
283 vel.setLength2D(len*ba);
284 }
285 }
286
randCirclePos(Vector position,int radius)287 Vector randCirclePos(Vector position, int radius)
288 {
289 float a = ((rand()%360)*(2*PI))/360.0f;
290 return position + Vector(sinf(a), cosf(a))*radius;
291 }
292
SongIconParticle(Vector color,Vector pos,int note)293 SongIconParticle::SongIconParticle(Vector color, Vector pos, int note)
294 : note(note)
295 {
296 cull = false;
297 //fastTransform = true;
298 setTexture("particles/glow");
299
300 setWidthHeight(32);
301
302 float life = 1.0;
303
304 toIcon = 0;
305 this->color = color;
306 position = pos;
307
308 alpha.ensureData();
309 alpha.data->path.addPathNode(0, 0);
310 alpha.data->path.addPathNode(0.4, 0.2); // .8
311 alpha.data->path.addPathNode(0.2, 0.8); // .4
312 alpha.data->path.addPathNode(0, 1);
313 alpha.startPath(life);
314
315 scale.ensureData();
316 scale.data->path.addPathNode(Vector(0.5,0.5), 0);
317 scale.data->path.addPathNode(Vector(1,1), 0.5);
318 scale.data->path.addPathNode(Vector(0.5,0.5), 1);
319 scale.startPath(life);
320
321 setLife(life);
322 setDecayRate(1);
323
324 //if (rand()%6 <= 2)
325 setBlendType(RenderObject::BLEND_ADD);
326
327 float smallestDist = HUGE_VALF;
328 SongIcon *closest = 0;
329 for (int i = 0; i < avatar->songIcons.size(); i++)
330 {
331 if (i != note)
332 {
333 Vector diff = (position - avatar->songIcons[i]->position);
334 float dist = diff.getSquaredLength2D();
335 if (dist < smallestDist)
336 {
337 smallestDist = dist;
338 closest = avatar->songIcons[i];
339 }
340 }
341 }
342 // find nearest song icon
343 if (closest)
344 {
345 toIcon = closest;
346 }
347 }
348
onUpdate(float dt)349 void SongIconParticle::onUpdate(float dt)
350 {
351 Quad::onUpdate(dt);
352
353 if (toIcon)
354 {
355 Vector add = (toIcon->position - position);
356 add.setLength2D(200*dt);
357 velocity += add;
358 velocity.capLength2D(50);
359 }
360 }
361
SongIcon(int note)362 SongIcon::SongIcon(int note) : Quad(), note(note)
363 {
364 open = false;
365 alphaMod = 0.9;
366 /*
367 std::ostringstream os;
368 os << "SongIcon" << note;
369 setTexture(os.str());
370 */
371 //setTexture("Cursor-Sing");
372 std::ostringstream os;
373 os << "Song/NoteSymbol" << note;
374 os.str();
375 setTexture(os.str());
376
377 scale = Vector(NOTE_SCALE, NOTE_SCALE);
378 cursorIsIn = false;
379 delay = 0;
380 counter = 0;
381 width = 40;
382 height = 40;
383
384 minTime = 0;
385 ptimer = 0;
386 noteColor = dsq->getNoteColor(note);
387 //color = dsq->getNoteColor(note)*0.75f + Vector(1,1,1)*0.25f;
388 color = dsq->getNoteColor(note);
389 len = 0;
390
391 channel = BBGE_AUDIO_NOCHANNEL;
392
393 rippleTimer = 0;
394
395 glow = new Quad;
396 glow->setTexture("particles/bigglow");
397 glow->followCamera = 1;
398 glow->rotation.interpolateTo(Vector(0,0,360), 10, -1);
399 glow->alpha = 0;
400 glow->setBlendType(RenderObject::BLEND_ADD);
401 glow->scale = Vector(0.5, 0.5);
402 glow->color = dsq->getNoteColor(note);
403 dsq->game->addRenderObject(glow, LR_PARTICLES2);
404 }
405
destroy()406 void SongIcon::destroy()
407 {
408 Quad::destroy();
409 }
410
spawnParticles(float dt)411 void SongIcon::spawnParticles(float dt)
412 {
413 float intv = 0.1;
414 // do stuff!
415 ptimer += dt;
416 while (ptimer > intv)
417 {
418 ptimer -= intv;
419 SongIconParticle *s = new SongIconParticle(noteColor, randCirclePos(position, 16), note);
420 s->followCamera = true;
421 dsq->game->addRenderObject(s, LR_HUD);
422 }
423 }
424
onUpdate(float dt)425 void SongIcon::onUpdate(float dt)
426 {
427 Quad::onUpdate(dt);
428
429 if (!avatar->singing)
430 return;
431
432 if (alpha.x == 0 && !alpha.isInterpolating())
433 alpha.interpolateTo(0.3, 0.1);
434 if (delay > 0)
435 {
436 delay -= dt;
437 if (delay < 0)
438 {
439 delay = 0;
440 //channel = BBGE_AUDIO_NOCHANNEL;
441 }
442 }
443 if (counter > 0)
444 {
445 counter -= dt;
446 if (counter < 0)
447 {
448 counter = 0;
449 closeNote();
450 }
451 }
452 if (alpha.x > 0.5f)
453 {
454 spawnParticles(dt);
455 }
456 if (open)
457 {
458 len += dt;
459 avatar->setHeadTexture("Singing", 0.1);
460 }
461 if (alpha.x == 1)
462 {
463 if (isCoordinateInRadius(core->mouse.position, NOTE_ACCEPT_DISTANCE))
464 {
465 //if (delay == 0)
466 if (true)
467 {
468 if (!cursorIsIn)
469 // highlighted for the first time
470 {
471 cursorIsIn = true;
472 openNote();
473 }
474 else
475 {
476 if (minTime > 0)
477 {
478 minTime -= dt;
479 if (minTime < 0)
480 {
481 minTime = 0;
482 }
483 }
484
485 }
486 }
487 }
488 else if (!isCoordinateInRadius(core->mouse.position, NOTE_ACCEPT_DISTANCE*1.25f))
489 {
490 if (cursorIsIn)
491 {
492 cursorIsIn = false;
493 closeNote();
494 }
495 }
496 }
497 if (alpha.x <= 0 && delay == 0) // && channel != BBGE_AUDIO_NOCHANNEL
498 {
499 closeNote();
500 }
501
502 if (open)
503 {
504 if (dsq->user.video.noteEffects)
505 {
506 rippleTimer -= dt;
507 if (rippleTimer <= 0)
508 {
509 //rippleTimer = 1.0f - ((7 - note)/7.0f)*0.7f;
510 rippleTimer = 0.5f - (note/7.0f)*0.4f;
511
512 if (core->afterEffectManager)
513 {
514 core->afterEffectManager->addEffect(new ShockEffect(position - Vector(400, 300) + Vector(core->width/2, core->height/2),
515 core->screenCenter,0.009f,0.015f,18,0.2f, 0.9f + (note*0.08f) ));
516 }
517 }
518 }
519 }
520
521 if (glow)
522 {
523 glow->position = position;
524 }
525 }
526
openNote()527 void SongIcon::openNote()
528 {
529 //if (delay > 0) return;
530 scale.interpolateTo(Vector(1.2, 1.2), 0.1);
531
532 if (dsq->user.video.noteEffects)
533 {
534 glow->scale = Vector(0.5,0.5);
535 glow->scale.interpolateTo(Vector(1.0, 1.0), 2, -1, 1, 1);
536
537 glow->alpha.interpolateTo(0.6, 0.2, 0, 0, 1);
538 }
539
540 /*
541 std::ostringstream os;
542 os << "Note"
543 */
544
545 std::string sfx = dsq->game->getNoteName(note);
546
547 open = true;
548
549 internalOffset = Vector(-5, 0);
550 internalOffset.interpolateTo(Vector(5, 0), 0.08, -1, 1);
551
552 avatar->singNote(this->note);
553
554 // this should never get called:
555 if (channel != BBGE_AUDIO_NOCHANNEL)
556 {
557 dsq->sound->fadeSfx(channel, SFT_OUT, 0.2);
558 //dsq->sound->fadeSfx(channel, SFT_OUT, 0.2);
559 channel = BBGE_AUDIO_NOCHANNEL;
560 }
561 //dsq->sound->stopSfx(channel);
562
563
564 PlaySfx play;
565 play.name = sfx;
566 channel = dsq->sound->playSfx(play);
567
568
569 rippleTimer = 0;
570
571 minTime = 0.05;
572 counter = 3.2;
573
574 float glowLife = 0.5;
575
576 {
577 Quad *q = new Quad("particles/glow", position);
578 q->scale.interpolateTo(Vector(10, 10), glowLife+0.1f);
579 q->alpha.ensureData();
580 q->alpha.data->path.addPathNode(0,0);
581 q->alpha.data->path.addPathNode(0.75,0.2);
582 q->alpha.data->path.addPathNode(0,1);
583 q->alpha.startPath(glowLife);
584 q->color = dsq->getNoteColor(note); //*0.5f + Vector(0.5, 0.5, 0.5)
585 q->setBlendType(RenderObject::BLEND_ADD);
586 q->followCamera = 1;
587 dsq->game->addRenderObject(q, LR_HUD);
588 q->setDecayRate(1/(glowLife+0.1f));
589 }
590
591 {
592 std::ostringstream os2;
593 os2 << "Song/NoteSymbol" << note;
594
595 Quad *q = new Quad(os2.str(), position);
596 q->color = 0;
597 q->scale = Vector(0.5,0.5);
598 q->scale.interpolateTo(Vector(2, 2), glowLife+0.1f);
599 //q->scale.interpolateTo(Vector(10, 10), glowLife+0.1f);
600 q->alpha.ensureData();
601 q->alpha.data->path.addPathNode(0,0);
602 q->alpha.data->path.addPathNode(0.5,0.2);
603 q->alpha.data->path.addPathNode(0,1);
604 q->alpha.startPath(glowLife);
605 //q->setBlendType(RenderObject::BLEND_ADD);
606 q->followCamera = 1;
607 dsq->game->addRenderObject(q, LR_HUD);
608 q->setDecayRate(1/(glowLife+0.1f));
609 }
610
611 avatar->songInterfaceTimer = 1.0;
612
613 notesOpen++;
614 /*
615 std::ostringstream os2;
616 os2 << "notesOpen: " << notesOpen;
617 debugLog(os2.str());
618 */
619 if (notesOpen > 0)
620 {
621 len = 0;
622
623 FOR_ENTITIES(i)
624 {
625 Entity *e = *i;
626 if ((e->position - dsq->game->avatar->position).getSquaredLength2D() < sqr(1000))
627 {
628 e->songNote(note);
629 }
630 }
631 for (int i = 0; i < dsq->game->getNumPaths(); i++)
632 {
633 Path *p = dsq->game->getPath(i);
634 if (!p->nodes.empty())
635 {
636 if ((p->nodes[0].position - dsq->game->avatar->position).getSquaredLength2D() < sqr(1000))
637 {
638 p->songNote(note);
639 }
640 }
641 }
642 }
643 }
644
closeNote()645 void SongIcon::closeNote()
646 {
647 //if (delay > 0) return;
648 scale.interpolateTo(Vector(NOTE_SCALE, NOTE_SCALE), 0.1);
649
650 if (dsq->game->avatar->isSinging() && dsq->user.video.noteEffects)
651 glow->alpha.interpolateTo(0.3, 1.5, 0, 0, 1);
652 else
653 glow->alpha.interpolateTo(0, 1.5, 0, 0, 1);
654 glow->scale.interpolateTo(Vector(0.5, 0.5), 0.5);
655
656
657 cursorIsIn = false;
658
659 if (channel != BBGE_AUDIO_NOCHANNEL)
660 {
661 dsq->sound->fadeSfx(channel, SFT_OUT, 1.0);
662 channel = BBGE_AUDIO_NOCHANNEL;
663 //delay = 0.5;
664 }
665
666 if (open)
667 {
668 internalOffset.stop();
669 internalOffset = Vector(0,0);
670 notesOpen--;
671 open = false;
672
673 FOR_ENTITIES(i)
674 {
675 Entity *e = *i;
676 int dist = (e->position - dsq->game->avatar->position).getSquaredLength2D();
677 if (e != dsq->game->avatar && dist < sqr(1000))
678 {
679 e->songNoteDone(note, len);
680 }
681 }
682 for (int i = 0; i < dsq->game->getNumPaths(); i++)
683 {
684 Path *p = dsq->game->getPath(i);
685 if (!p->nodes.empty())
686 {
687 if ((p->nodes[0].position - dsq->game->avatar->position).getSquaredLength2D() < sqr(1000))
688 {
689 p->songNoteDone(note, len);
690 }
691 }
692 }
693 }
694
695 /*
696 std::ostringstream os;
697 os << "notesOpen: " << notesOpen;
698 debugLog(os.str());
699 */
700
701 if (notesOpen <= 0)
702 {
703 notesOpen = 0;
704 if (dsq->continuity.form == FORM_NORMAL)
705 avatar->setHeadTexture("");
706 }
707 }
708
openInterface()709 void SongIcon::openInterface()
710 {
711 delay = 0;
712 alpha.interpolateTo(1, 0.1);
713 }
714
closeInterface()715 void SongIcon::closeInterface()
716 {
717 closeNote();
718 delay = 0;
719 alpha.interpolateTo(0, 0.1);
720 }
721
AvatarState()722 AvatarState::AvatarState()
723 {
724 abilityDelay = 0;
725 outOfWaterTimer = 0;
726 backFlip = false;
727 nearWall = false;
728 wasUnderWater = true;
729 blind = false;
730 lockedToWall = false;
731 shotDelay = 0;
732 spellCharge = 0;
733 leachTimer = 0;
734 swimTimer = 0;
735 rollTimer = 0;
736 updateLookAtTime = 0;
737 lookAtEntity = 0;
738 blinkTimer = 0;
739 }
740
toggleMovement(bool on)741 void Avatar::toggleMovement(bool on)
742 {
743 canMove = on;
744 }
745
isLockable()746 bool Avatar::isLockable()
747 {
748 return (bursting || !_isUnderWater) && (boneLockDelay == 0) && canLockToWall();
749 }
750
isSinging()751 bool Avatar::isSinging()
752 {
753 return singing;
754 }
755
applyWorldEffects(WorldType type)756 void Avatar::applyWorldEffects(WorldType type)
757 {
758 static bool oldfh=false;
759
760 if (type == WT_SPIRIT)
761 {
762 //skeletalSprite.transitionAnimate("ball", 0.1, -1);
763 //skeletalSprite.alpha.interpolateTo(0, 1);
764 //skeletalSprite.alpha = 0;
765 //dsq->game->addRenderObject(&skeletalSprite, LR_ENTITIES);
766
767 removeChild(&skeletalSprite);
768 skeletalSprite.position = position;
769 skeletalSprite.setFreeze(true);
770 skeletalSprite.scale = scale;
771 skeletalSprite.alpha.interpolateTo(0.5, 1);
772 //dsq->game->addRenderObject(&skeletalSprite, LR_ENTITIES);
773 skeletalSprite.rotation.z = rotation.z;
774 skeletalSprite.rotationOffset.z = rotationOffset.z;
775
776
777 oldfh = skeletalSprite.isfh();
778 skeletalSprite.fhTo(isfh());
779
780 renderQuad = true;
781 setTexture("glow");
782 width = 256;
783 height = 256;
784 setBlendType(BLEND_ADD);
785 fader->alpha.interpolateTo(0.75, 1);
786
787 dsq->sound->toggleEffectMusic(SFX_FLANGE, true);
788 }
789 else
790 {
791 //skeletalSprite.transitionAnimate("idle", 1, -1);
792 //skeletalSprite.alpha.interpolateTo(1, 1);
793 //skeletalSprite.alpha = 1;
794 //dsq->game->removeRenderObject(&skeletalSprite);
795
796 skeletalSprite.setFreeze(false);
797 if (!skeletalSprite.getParent())
798 {
799 addChild(&skeletalSprite, PM_STATIC);
800 }
801 skeletalSprite.position = Vector(0,0,0);
802 skeletalSprite.scale = Vector(1,1,1);
803 renderQuad = false;
804 setBlendType(BLEND_DEFAULT);
805 fader->alpha.interpolateTo(0, 1);
806
807 bool newfh = skeletalSprite.isfh();
808 skeletalSprite.fhTo(oldfh);
809 skeletalSprite.rotation.z = 0;
810 skeletalSprite.rotationOffset.z = 0;
811
812 fhTo(newfh);
813
814 dsq->sound->toggleEffectMusic(SFX_FLANGE, false);
815 }
816 }
817
startFlourish()818 void Avatar::startFlourish()
819 {
820 std::string anim = dsq->continuity.getInternalFormName() + "-flourish";
821 //if (skeletalSprite.getAnimation(anim))
822 Animation *fanim = skeletalSprite.getAnimation(anim);
823 if (fanim)
824 {
825 flourishTimer.start(fanim->getAnimationLength()-0.2f);
826 flourishPowerTimer.start(fanim->getAnimationLength()*0.5f);
827 }
828 skeletalSprite.transitionAnimate(anim, 0.1, 0, ANIMLAYER_FLOURISH);
829 flourish = true;
830
831 float rotz = rotationOffset.z;
832 if (this->isfh())
833 rotationOffset = Vector(0,0,rotz+360);
834 else
835 rotationOffset = Vector(0,0,rotz-360);
836
837 FormType f = dsq->continuity.form;
838 if (f != FORM_NORMAL && f != FORM_BEAST && f != FORM_FISH && f != FORM_SUN && f != FORM_NATURE)
839 {
840 rotationOffset.z *= -1;
841 }
842 if (f == FORM_ENERGY || f == FORM_DUAL)
843 {
844 rotationOffset.z *= 2;
845 }
846
847 if (f == FORM_BEAST)
848 {
849 Vector v = getNormal();
850 if (!v.isZero())
851 {
852 v *= 400;
853 vel += v;
854 }
855 }
856
857 rotationOffset.interpolateTo(Vector(0,0,rotz), 0.8, 0, 0, 1);
858 }
859
onIdle()860 void Avatar::onIdle()
861 {
862 if (dsq->game->li)
863 {
864 if (dsq->game->li->getState() == STATE_HUG && riding)
865 {
866 dsq->game->li->setState(STATE_IDLE);
867 }
868 }
869 //stillTimer.stop();
870 stopBurst();
871 if (movingOn)
872 {
873 dsq->setMousePosition(Vector(400,300));
874 }
875 skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
876 stopRoll();
877 closeSingingInterface();
878 fallOffWall();
879
880 dsq->gameSpeed.stopPath();
881 dsq->gameSpeed.interpolateTo(1,0);
882 }
883
getBurstAnimName()884 std::string Avatar::getBurstAnimName()
885 {
886 std::string ret;
887 switch(dsq->continuity.form)
888 {
889 case FORM_ENERGY:
890 ret = "energyburst";
891 break;
892 default:
893 ret = "burst";
894 break;
895 }
896 return ret;
897 }
898
getRollAnimName()899 std::string Avatar::getRollAnimName()
900 {
901 std::string ret;
902 switch(dsq->continuity.form)
903 {
904 case FORM_ENERGY:
905 ret = "energyroll";
906 break;
907 default:
908 ret = "roll";
909 break;
910 }
911 return ret;
912 }
913
getIdleAnimName()914 std::string Avatar::getIdleAnimName()
915 {
916 std::string ret="idle";
917 switch(dsq->continuity.form)
918 {
919 case FORM_ENERGY:
920 ret="energyidle";
921 break;
922 }
923 return ret;
924 }
925
clampPosition()926 void Avatar::clampPosition()
927 {
928 lastPosition = position;
929 }
930
updatePosition()931 void Avatar::updatePosition()
932 {
933 updateHair(0);
934 }
935
updateHair(float dt)936 void Avatar::updateHair(float dt)
937 {
938 static float hairTimer = 0;
939 Bone *b = skeletalSprite.getBoneByIdx(0);
940 if (hair && b)
941 {
942 hair->alpha.x = alpha.x;
943 hair->color.x = color.x * multColor.x;
944 hair->color.y = color.y * multColor.y;
945 hair->color.z = color.z * multColor.z;
946 Vector headPos = b->getWorldCollidePosition(Vector(12,-32,0));
947
948 hair->setHeadPosition(headPos);
949 Vector diff = headPos - position;
950
951 Vector diff2;
952 if (!isfh())
953 diff2 = diff.getPerpendicularLeft();
954 else
955 diff2 = diff.getPerpendicularRight();
956
957 Vector diff3 = position - headPos;
958
959 if (state.lockedToWall && wallPushVec.y < 0 && (fabsf(wallPushVec.y) > fabsf(wallPushVec.x)))
960 {
961 if (isfh())
962 {
963 diff3 = Vector(-50, -25);
964 }
965 else
966 diff3 = Vector(50,-25);
967 }
968
969 float len =diff2.getLength2D();
970 diff3.setLength2D(len);
971 /*
972 diff.y = -diff.y;
973 diff = (diff + diff2)/2.0f;
974 */
975
976 hairTimer += dt;
977 while (hairTimer > 2.0f)
978 {
979 hairTimer -= 2.0f;
980 }
981 float useTimer = hairTimer;
982 if (useTimer > 1.0f)
983 useTimer = 1.0f - (hairTimer-1);
984 float frc = 0.333333;
985 diff = (diff2*(frc*(1.0f-(useTimer*0.5f))) + diff3*(frc) + Vector(0,len)*(frc*(0.5f+useTimer*0.5f)));
986
987
988
989 if (_isUnderWater)
990 {
991 diff.setLength2D(400);
992 //if (!vel.isLength2DIn(10))
993 hair->exertForce(diff, dt);
994 }
995 else
996 {
997 diff.setLength2D(400);
998 hair->exertForce(diff, dt);
999 }
1000 if (!vel2.isZero())
1001 hair->exertForce(vel2, dt);
1002 hair->updatePositions();
1003 }
1004 }
1005
updateDamageVisualEffects()1006 void Avatar::updateDamageVisualEffects()
1007 {
1008 int damageThreshold = float(maxHealth/5.0f)*3.0f;
1009 if (health <= damageThreshold)
1010 {
1011 //dsq->game->damageSprite->alpha.interpolateTo(0.9, 0.5);
1012 float a = ((damageThreshold - health)/float(damageThreshold))*1.0f;
1013 dsq->game->damageSprite->alpha.interpolateTo(a, 0.3);
1014
1015 /*
1016 std::ostringstream os;
1017 os << "damageSprite alpha: " << a;
1018 debugLog(os.str());
1019 */
1020
1021 dsq->game->damageSprite->scale = Vector(1,1);
1022 dsq->game->damageSprite->scale.interpolateTo(Vector(1.2, 1.2), 0.5, -1, 1);
1023
1024 /*
1025 if (health <= 0)
1026 {
1027 dsq->game->sceneColor.interpolateTo(Vector(1,0.5,0.5), 0.75);
1028 }
1029 */
1030 }
1031 else
1032 {
1033 dsq->game->damageSprite->alpha.interpolateTo(0, 0.3);
1034 }
1035 }
1036
checkUpgradeForShot(Shot * s)1037 void Avatar::checkUpgradeForShot(Shot *s)
1038 {
1039 if (dsq->continuity.energyMult <= 1)
1040 s->extraDamage = dsq->continuity.energyMult * 1;
1041 else
1042 s->extraDamage = dsq->continuity.energyMult * 0.75f;
1043
1044 if (s->extraDamage > 0)
1045 {
1046 Quad *glow = new Quad("particles/glow", Vector(0,0));
1047 glow->color = Vector(1,0,0);
1048 glow->color.interpolateTo(Vector(1,0.5,0.5), 0.1, -1, 1);
1049 glow->setBlendType(BLEND_ADD);
1050 glow->scale = Vector(4, 4) + (s->extraDamage*Vector(2,2));
1051 glow->scale.interpolateTo(Vector(16,16)+ (s->extraDamage*Vector(2,2)), 0.5, -1, 1);
1052 s->addChild(glow, PM_POINTER);
1053 }
1054 }
1055
onDamage(DamageData & d)1056 void Avatar::onDamage(DamageData &d)
1057 {
1058 Entity::onDamage(d);
1059
1060
1061 if (dsq->difficulty == DSQ::DIFF_EASY)
1062 {
1063 if (d.damage > 0)
1064 d.damage *= MULT_DMG_EASY;
1065 }
1066
1067 skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
1068 if (dsq->continuity.form == FORM_NORMAL)
1069 {
1070 if (nocasecmp(dsq->continuity.costume, "CC")==0)
1071 {
1072 d.damage *= MULT_DMG_CRABCOSTUME;
1073 }
1074 }
1075
1076 if (riding != 0)
1077 {
1078 if (nocasecmp(dsq->continuity.costume, "seahorse")==0)
1079 {
1080 d.damage *= MULT_DMG_SEAHORSEARMOR;
1081 }
1082 }
1083
1084 if (dsq->continuity.form == FORM_FISH)
1085 {
1086 d.damage *= MULT_DMG_FISHFORM;
1087 }
1088
1089 if ((core->isNested() && dsq->game->invincibleOnNested) || dsq->game->invinciblity)
1090 {
1091 d.damage = 0;
1092 d.damageType = DT_NONE;
1093 return;
1094 }
1095
1096 if (d.damageType == DT_ENEMY_INK)
1097 {
1098 setBlind(d.effectTime);
1099 return;
1100 }
1101
1102 if (d.damageType == DT_ENEMY_POISON)
1103 {
1104 dsq->continuity.setPoison(1, d.effectTime);
1105 }
1106
1107 if (dsq->continuity.defenseMultTimer.isActive())
1108 {
1109 d.damage *= dsq->continuity.defenseMult;
1110 }
1111
1112 if (dsq->continuity.invincibleTimer.isActive())
1113 d.damage = 0;
1114
1115 if (!canDie)
1116 {
1117 if ((health - d.damage) <= 0)
1118 {
1119 float i = d.damage;
1120 while (i >= 0)
1121 {
1122 if ((health - i) > 0)
1123 {
1124 d.damage = i;
1125 break;
1126 }
1127
1128 i -= 0.5f;
1129 }
1130 }
1131 }
1132
1133 if ((!invincible || !dsq->game->invincibleOnNested) && !(invincibleBreak && damageTimer.isActive() && d.useTimer) && !dsq->continuity.invincibleTimer.isActive())
1134 {
1135 if (d.damageType == DT_ENEMY_ACTIVEPOISON)
1136 core->sound->playSfx("Poison");
1137 else
1138 core->sound->playSfx("Pain");
1139
1140
1141 setHeadTexture("Pain", 1);
1142
1143 int r = (rand()%2)+1;
1144 std::ostringstream os;
1145 os << "basicHit" << r;
1146 skeletalSprite.transitionAnimate(os.str(), 0.05, 0, ANIMLAYER_OVERRIDE);
1147
1148 /*
1149 if (d.attacker)
1150 {
1151 // this will probably cause a crash!
1152 state.lookAtEntity = d.attacker;
1153 }
1154 */
1155
1156 if (d.damage > 0)
1157 {
1158 float healthWillBe = health-d.damage;
1159 // determines length of shader blur as well
1160 float t = 0.5;
1161 if (healthWillBe<=0)
1162 t = 2;
1163
1164 dsq->rumble(d.damage, d.damage, 0.4);
1165 if (d.damage > 0)
1166 {
1167 //dsq->shakeCamera(5, t);
1168 if (d.damage >= 1)
1169 {
1170 float shake = d.damage*2;
1171 if (shake > 10)
1172 shake = 10;
1173 dsq->shakeCamera(shake, t);
1174 }
1175
1176 if (healthWillBe <= 2 && d.damageType != DT_ENEMY_ACTIVEPOISON)
1177 {
1178 //if (!dsq->gameSpeed.isInterpolating() && dsq->gameSpeed.x==1)
1179
1180 {
1181 dsq->gameSpeed.stop();
1182 dsq->gameSpeed.stopPath();
1183 dsq->gameSpeed.x = 1;
1184
1185 dsq->overlayRed->alpha.ensureData();
1186 dsq->overlayRed->alpha.data->path.clear();
1187 dsq->overlayRed->alpha.data->path.addPathNode(0, 0);
1188 dsq->overlayRed->alpha.data->path.addPathNode(1.0, 0.1);
1189 dsq->overlayRed->alpha.data->path.addPathNode(0, 1.0);
1190 dsq->overlayRed->alpha.startPath(1);
1191
1192 dsq->sound->playSfx("heartbeat");
1193
1194 if (healthWillBe < 2 && healthWillBe >= 1 && !dsq->game->hasPlayedLow)
1195 {
1196 dsq->emote.playSfx(EMOTE_NAIJALOW);
1197 dsq->game->hasPlayedLow = 1;
1198 }
1199
1200
1201 dsq->gameSpeed.ensureData();
1202 dsq->gameSpeed.data->path.clear();
1203 dsq->gameSpeed.data->path.addPathNode(1, 0);
1204 dsq->gameSpeed.data->path.addPathNode(0.25, 0.1);
1205 dsq->gameSpeed.data->path.addPathNode(0.25, 0.4);
1206 dsq->gameSpeed.data->path.addPathNode(1.0, 1.0);
1207
1208 dsq->gameSpeed.startPath(2);
1209
1210 //dsq->gameSpeed.interpolateTo(0.7, 3);
1211 }
1212 //dsq->emote();
1213 }
1214 }
1215 hitEmitter.load("NaijaHit");
1216 hitEmitter.start();
1217
1218 playHitSound();
1219 }
1220 }
1221 }
1222
playHitSound()1223 void Avatar::playHitSound()
1224 {
1225 int hitSound = (rand()%8)+1;
1226 static int lastHitSound = 0;
1227 if (lastHitSound == hitSound)
1228 {
1229 hitSound ++;
1230 if (hitSound > 8)
1231 hitSound = 1;
1232 }
1233 std::ostringstream os;
1234 os << "hit" << hitSound;
1235 core->sound->playSfx(os.str());
1236 }
1237
1238 const int beatHealth = 3;
updateHeartbeatSfx(float t)1239 void Avatar::updateHeartbeatSfx(float t)
1240 {
1241 /*
1242 if (heartbeat)
1243 {
1244 BASS_CHANNELINFO info;
1245 BASS_ChannelGetInfo(heartbeat, &info);
1246 int num = (beatHealth - health);
1247 float wantFreq = 1000 + num*300;
1248 float useFreq = ((wantFreq*info.freq)/1000.0f);
1249 float vol = 75 + (num*25)*0.5f;
1250 vol *= (core->sound->getUseSfxVol()/100.0f);
1251 //int vol = 100;
1252 BASS_ChannelSlideAttributes(heartbeat, useFreq, vol, -101, 1000.0f*t);
1253 }
1254 */
1255 }
1256
onHealthChange(float change)1257 void Avatar::onHealthChange(float change)
1258 {
1259 updateDamageVisualEffects();
1260
1261 if (health <= beatHealth && health > 0)
1262 {
1263 /*
1264 if (!heartbeat)
1265 {
1266 //debugLog("starting heartbeat");
1267 heartbeat = core->sound->playSfx("Heartbeat", 255, 0, 1000, 1);
1268 //core->sound->playSfx("Heartbeat");
1269 }
1270 */
1271 updateHeartbeatSfx(0.5);
1272 }
1273 if (health > beatHealth)
1274 {
1275 /*
1276 if (heartbeat)
1277 {
1278 //debugLog("stopping heartbeat");
1279 BASS_CHANNELINFO info;
1280 BASS_ChannelGetInfo(heartbeat, &info);
1281 BASS_ChannelSlideAttributes(heartbeat, info.freq, -2, -101, 1000*2);
1282 heartbeat = 0;
1283 }
1284 */
1285 }
1286 }
1287
revive()1288 void Avatar::revive()
1289 {
1290 entityDead = false;
1291 health = 0;
1292 heal(maxHealth);
1293 }
1294
updateDualFormChargeEffects()1295 void Avatar::updateDualFormChargeEffects()
1296 {
1297 }
1298
lostTarget(int i,Entity * e)1299 void Avatar::lostTarget(int i, Entity *e)
1300 {
1301 dsq->sound->playSfx("target-unlock");
1302 }
1303
entityDied(Entity * e)1304 void Avatar::entityDied(Entity *e)
1305 {
1306 Entity::entityDied(e);
1307 for (int i = 0; i < targets.size(); i++)
1308 {
1309 if (targets[i].e == e)
1310 {
1311 lostTarget(i, 0);
1312 targets[i].e = 0;
1313 targetUpdateDelay = 100;
1314 targets.clear();
1315 break;
1316 }
1317 }
1318
1319 if (state.lookAtEntity==e)
1320 state.lookAtEntity = 0;
1321
1322 // eating
1323 if (e->isGoingToBeEaten())
1324 {
1325 EatType et = e->getEatType();
1326 switch(et)
1327 {
1328 case EAT_FILE:
1329 {
1330 dsq->continuity.eatBeast(e->eatData);
1331 }
1332 break;
1333 }
1334 }
1335
1336 //debugLog("Entity died");
1337 //e->lastDamage.damageType == DT_AVATAR_ENERGYBLAST &&
1338 //debugLog("Entity died");
1339 if (e->lastDamage.form == FORM_DUAL && e->lastDamage.damageType == DT_AVATAR_SHOCK)
1340 {
1341 dsq->continuity.dualFormCharge ++;
1342 updateDualFormChargeEffects();
1343 dsq->spawnParticleEffect("SpiritSteal", e->position);
1344 //dsq->spawnParticleEffect("SpiritBeacon", position);
1345 core->sound->playSfx("DualForm-Absorb");
1346 if (dsq->continuity.dualFormCharge == requiredDualFormCharge)
1347 core->sound->playSfx("DualForm-Charge");
1348 }
1349 /*
1350 std::ostringstream os;
1351 os << "lastDamage.form = " << e->lastDamage.form;
1352 debugLog(os.str());
1353 */
1354 }
1355
enableInput()1356 void Avatar::enableInput()
1357 {
1358 ActionMapper::enableInput();
1359 dsq->game->toggleMiniMapRender(1);
1360
1361 if (!dsq->game->isApplyingState())
1362 dsq->toggleCursor(true);
1363
1364 if (movingOn)
1365 {
1366 dsq->setMousePosition(Vector(400,300));
1367 }
1368
1369 if (dsq->continuity.form == FORM_ENERGY)
1370 {
1371 for (int i = 0; i < targetQuads.size(); i++)
1372 targetQuads[i]->start();
1373 }
1374
1375 setInvincible(false);
1376 // can't do that here, cause it'll break the hug
1377 //stillTimer.stop();
1378 }
1379
disableInput()1380 void Avatar::disableInput()
1381 {
1382 ActionMapper::disableInput();
1383
1384 // can't do that here, cause it'll break the hug
1385 //stillTimer.stop();
1386
1387 closeSingingInterface();
1388 dsq->game->toggleMiniMapRender(0);
1389 dsq->toggleCursor(false);
1390 endCharge();
1391 clearTargets();
1392 if (movingOn)
1393 {
1394 dsq->setMousePosition(Vector(400,300));
1395 }
1396
1397 for (int i = 0; i < targetQuads.size(); i++)
1398 {
1399 targetQuads[i]->stop();
1400 }
1401
1402 setInvincible(true);
1403 }
1404
clearTargets()1405 void Avatar::clearTargets()
1406 {
1407 for (int i = 0; i < targets.size(); i++)
1408 {
1409 if (targets[i].e)
1410 {
1411 lostTarget(i, 0);
1412 }
1413 targets[i].e = 0;
1414 }
1415 }
1416
openSingingInterface()1417 void Avatar::openSingingInterface()
1418 {
1419 if (!singing && health > 0 && !isEntityDead() && !blockSinging)
1420 {
1421 //core->mouse.position = Vector(400,300);
1422 if (dsq->inputMode != INPUT_MOUSE)
1423 {
1424 core->centerMouse();
1425 //core->setMousePosition(Vector(400,300));
1426 }
1427
1428 core->setMouseConstraintCircle(core->center, singingInterfaceRadius);
1429 stopRoll();
1430 singing = true;
1431 currentSongIdx = SONG_NONE;
1432
1433 // make the singing icons appear
1434 for (int i = 0; i < songIcons.size(); i++)
1435 {
1436 songIcons[i]->openInterface();
1437 }
1438 currentSong.notes.clear();
1439
1440 songInterfaceTimer = 0;
1441
1442 dsq->game->songLineRender->clear();
1443
1444
1445 if (dsq->inputMode == INPUT_JOYSTICK)
1446 {
1447 core->setMousePosition(core->center);
1448 }
1449 }
1450 }
1451
closeSingingInterface()1452 void Avatar::closeSingingInterface()
1453 {
1454
1455 if (dsq->game->songLineRender)
1456 dsq->game->songLineRender->clear();
1457 if (singing)
1458 {
1459 core->setMouseConstraint(false);
1460 usingDigital = false;
1461 quickSongCastDelay = 1;
1462
1463 // HACK: this prevents being "locked" away from the seahorse... so naija can
1464 // be in singing range of the seahorse
1465 applyRidingPosition();
1466 singing = false;
1467
1468 for (int i = 0; i < songIcons.size(); i++)
1469 {
1470 songIcons[i]->closeInterface();
1471 }
1472
1473 if (dsq->continuity.form == FORM_NORMAL)
1474 setHeadTexture("");
1475
1476 currentSongIdx = dsq->continuity.checkSongAssisted(currentSong);
1477 if (currentSongIdx != SONG_NONE)
1478 {
1479 dsq->continuity.castSong(currentSongIdx);
1480 currentSongIdx = SONG_NONE;
1481 }
1482 }
1483 }
1484
toggleCape(bool on)1485 void Avatar::toggleCape(bool on)
1486 {
1487 if (!hair) return;
1488
1489 if (!on)
1490 hair->alphaMod = 0;
1491 else
1492 hair->alphaMod = 1;
1493 }
1494
refreshDualFormModel()1495 void Avatar::refreshDualFormModel()
1496 {
1497 //charging = 0;
1498 if (dsq->continuity.dualFormMode == Continuity::DUALFORM_NAIJA)
1499 refreshModel("Naija", "DualForm_Naija");
1500 else if (dsq->continuity.dualFormMode == Continuity::DUALFORM_LI)
1501 refreshModel("Naija", "DualForm_Li");
1502 }
1503
updateDualFormGlow(float dt)1504 void Avatar::updateDualFormGlow(float dt)
1505 {
1506 if (dsq->continuity.form == FORM_DUAL && bone_dualFormGlow)
1507 {
1508
1509 float perc = 1;
1510 if (requiredDualFormCharge != 0)
1511 perc = float(dsq->continuity.dualFormCharge)/float(requiredDualFormCharge);
1512 if (perc > 1)
1513 perc = 1;
1514 bone_dualFormGlow->alpha = perc*0.5f + 0.1f;
1515 bone_dualFormGlow->scale.interpolateTo(Vector(perc, perc), 0.2);
1516 }
1517 }
1518
changeForm(FormType form,bool effects,bool onInit,FormType lastForm)1519 void Avatar::changeForm(FormType form, bool effects, bool onInit, FormType lastForm)
1520 {
1521 /*
1522 if (core->afterEffectManager)
1523 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter, 0.1,0.03,15,0.2f, 0.5));
1524 */
1525
1526
1527 /*
1528 if (pullTarget)
1529 {
1530 pullTarget->stopPull();
1531 pullTarget = 0;
1532 }
1533 */
1534
1535 if (form == FORM_DUAL && !dsq->continuity.hasLi())
1536 return;
1537
1538 if (!canChangeForm) return;
1539
1540 std::ostringstream os;
1541 os << "changeForm: " << form;
1542 debugLog(os.str());
1543
1544 /*
1545 if (dsq->game)
1546 dsq->game->clearControlHint();
1547 */
1548
1549 if (lastForm == FORM_NONE)
1550 lastForm = dsq->continuity.form;
1551
1552 endCharge();
1553
1554 std::ostringstream os2;
1555 os2 << "lastForm: " << lastForm;
1556 debugLog(os2.str());
1557
1558 for (int i = 0; i < targetQuads.size(); i++)
1559 {
1560 if (targetQuads[i])
1561 targetQuads[i]->stop();
1562 }
1563
1564
1565 if (bone_dualFormGlow)
1566 bone_dualFormGlow->scale = 0;
1567
1568 clearTargets();
1569
1570 if (form != FORM_NORMAL)
1571 stopAura();
1572
1573 switch (lastForm)
1574 {
1575 case FORM_FISH:
1576 {
1577 // check nearby area
1578 //bool isTooCloseToWall=false;
1579
1580 if (isNearObstruction(3))
1581 {
1582 Vector n = dsq->game->getWallNormal(position);
1583 if (!n.isZero())
1584 {
1585 n *= 400;
1586 vel += n;
1587 }
1588
1589 return;
1590 }
1591 //rotationOffset.interpolateTo(Vector(0,0,0), 0.5);
1592
1593 collideRadius = COLLIDE_RADIUS_NORMAL;
1594 setCanLockToWall(true);
1595 setCollisionAvoidanceData(COLLIDE_RANGE_NORMAL, COLLIDE_MOD_NORMAL);
1596 }
1597 break;
1598 case FORM_SUN:
1599 lightFormGlow->alpha.interpolateTo(0, 0.5);
1600 lightFormGlowCone->alpha.interpolateTo(0, 0.5);
1601 break;
1602 case FORM_SPIRIT:
1603 //position.interpolateTo(bodyPosition, 2, 0);
1604 position = bodyPosition;
1605 dsq->continuity.warpLiToAvatar();
1606 spiritBeaconEmitter.start();
1607 setCanActivateStuff(true);
1608 setCanLockToWall(true);
1609 setCanBurst(true);
1610 setDamageTarget(DT_WALLHURT, true);
1611 break;
1612 case FORM_BEAST:
1613 setCanSwimAgainstCurrents(false);
1614 break;
1615 case FORM_DUAL:
1616 if (dsq->continuity.hasLi())
1617 {
1618 dsq->game->li->alpha = 1;
1619 dsq->game->li->position = position;
1620 dsq->game->li->setState(STATE_IDLE);
1621 }
1622 break;
1623 case FORM_NATURE:
1624 setDamageTarget(DT_WALLHURT, true);
1625 break;
1626 default:
1627 if (leftHandEmitter && rightHandEmitter)
1628 {
1629 leftHandEmitter->stop();
1630 rightHandEmitter->stop();
1631 }
1632 break;
1633 }
1634
1635 elementEffectMult = 1;
1636 state.abilityDelay = 0;
1637 formAbilityDelay = 0;
1638 dsq->continuity.form = form;
1639 formTimer = 0;
1640 if (effects)
1641 {
1642 if (core->afterEffectManager)
1643 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter,0.08,0.05,22,0.2f, 1.2));
1644
1645 switch(form)
1646 {
1647 case FORM_ENERGY:
1648 core->sound->playSfx("EnergyForm");
1649 /*
1650 dsq->game->tintColor.path.addPathNode(Vector(1,1,1),0);
1651 dsq->game->tintColor.path.addPathNode(Vector(1.5,1.5,4),0.25);
1652 dsq->game->tintColor.path.addPathNode(Vector(4,1.5,1),0.5);
1653 dsq->game->tintColor.path.addPathNode(Vector(1,1,1),0.5);
1654 dsq->game->tintColor.startPath(2);
1655 */
1656
1657 /*
1658 dsq->game->tintColor = Vector(1,1,3);
1659 dsq->game->tintColor.interpolateTo(Vector(1,1,1), 1);
1660 */
1661
1662 break;
1663 case FORM_NORMAL:
1664 core->sound->playSfx("NormalForm");
1665 break;
1666 case FORM_BEAST:
1667 core->sound->playSfx("BeastForm");
1668 break;
1669 case FORM_FISH:
1670 core->sound->playSfx("FishForm");
1671 break;
1672 case FORM_SUN:
1673 core->sound->playSfx("SunForm");
1674 break;
1675 case FORM_NATURE:
1676 core->sound->playSfx("NatureForm");
1677 break;
1678 case FORM_SPIRIT:
1679 spiritBeaconEmitter.start();
1680 break;
1681 case FORM_DUAL:
1682 core->sound->playSfx("DualForm");
1683 break;
1684 }
1685
1686 /*
1687 dsq->overlay->color = Vector(1,1,1);
1688 dsq->overlay->alpha.interpolateTo(1.0, 0.2);
1689 avatar->disableInput();
1690 setv(EV_NOINPUTNOVEL, 0);
1691 core->main(0.2);
1692 setv(EV_NOINPUTNOVEL, 1);
1693 dsq->overlay->alpha.interpolateTo(0, 0.2);
1694 dsq->overlay->color.interpolateTo(0, 0.4);
1695 */
1696 dsq->overlay->color = Vector(1,1,1);
1697 dsq->overlay->alpha = 1;
1698 dsq->overlay->alpha.interpolateTo(0, 0.5);
1699 dsq->overlay->color.interpolateTo(0, 1.0);
1700 }
1701 /*
1702 if (form != FORM_ENERGY)
1703 {
1704 dsq->game->sceneColor3.interpolateTo(Vector(1,1,1), 0.2);
1705 }
1706 */
1707 float lastHairAlphaMod = 0;
1708 if (hair)
1709 {
1710 hair->alphaMod = 0;
1711 lastHairAlphaMod = hair->alphaMod;
1712 }
1713 switch (form)
1714 {
1715 case FORM_ENERGY:
1716 refreshModel("Naija", "EnergyForm");
1717 for (int i = 0; i < targetQuads.size(); i++)
1718 targetQuads[i]->start();
1719 leftHandEmitter->load("EnergyFormHandGlow");
1720 leftHandEmitter->start();
1721 rightHandEmitter->load("EnergyFormHandGlow");
1722 rightHandEmitter->start();
1723 break;
1724 case FORM_FISH:
1725 {
1726 fallOffWall();
1727
1728 setBoneLock(BoneLock());
1729
1730 refreshModel("FishForm", "");
1731 //rotationOffset.interpolateTo(Vector(0,0,-90), 0.5);
1732 //refreshModel("NaijaFish", "");
1733
1734 collideRadius = COLLIDE_RADIUS_FISH;
1735 setCanLockToWall(false);
1736 setCollisionAvoidanceData(COLLIDE_RANGE_FISH, COLLIDE_MOD_FISH);
1737 elementEffectMult = 0.4f;
1738 }
1739 break;
1740 case FORM_SUN:
1741 {
1742 refreshModel("Naija", "SunForm");
1743 lightFormGlow->moveToFront();
1744 lightFormGlow->alpha.interpolateTo(0.75, 1);
1745 lightFormGlowCone->alpha.interpolateTo(0.4, 1);
1746
1747 lightFormGlow->alphaMod = 0;
1748 lightFormGlowCone->alphaMod = 0;
1749 }
1750 break;
1751 case FORM_NORMAL:
1752 {
1753 if (lastForm == FORM_SPIRIT)
1754 {
1755 dsq->continuity.shiftWorlds();
1756 fallOffWall();
1757 }
1758 refreshNormalForm();
1759 //skeletalSprite.loadSkeletal("child");
1760 }
1761 break;
1762 case FORM_NATURE:
1763 refreshModel("Naija", "NatureForm");
1764 if (hair)
1765 {
1766 hair->setTexture("Naija/Cape-NatureForm");
1767 hair->alphaMod = 1.0;
1768 }
1769 setDamageTarget(DT_WALLHURT, false);
1770
1771 break;
1772 case FORM_BEAST:
1773 {
1774 refreshModel("Naija", "BeastForm");
1775 setCanSwimAgainstCurrents(true);
1776 }
1777 break;
1778 case FORM_SPIRIT:
1779 bodyPosition = position;
1780 bodyOffset = offset;
1781 fallOffWall();
1782 dsq->continuity.shiftWorlds();
1783 setCanActivateStuff(false);
1784 setCanLockToWall(false);
1785 setCanBurst(false);
1786 setDamageTarget(DT_WALLHURT, false);
1787 elementEffectMult = 0;
1788
1789 if (onInit)
1790 {
1791 skeletalSprite.alphaMod = 0;
1792 canChangeForm = false;
1793 useSpiritDistance = false;
1794 inSpiritWorld = true;
1795 }
1796 /*
1797 if (hair)
1798 hair->alphaMod = lastHairAlphaMod;
1799 */
1800 break;
1801 case FORM_DUAL:
1802 {
1803 if (dsq->continuity.hasLi())
1804 {
1805 dsq->game->li->setState(STATE_WAIT);
1806 dsq->game->li->alpha = 0;
1807 }
1808 //dualFormMode = DUALFORM_LI;
1809 refreshDualFormModel();
1810 /*
1811 for (int i = 0; i < targetQuads.size(); i++)
1812 targetQuads[i]->start();
1813 */
1814 }
1815 break;
1816 default:
1817 break;
1818 }
1819 setHeadTexture("");
1820 if (effects)
1821 avatar->enableInput();
1822
1823 //if (onInit) {
1824 //idle();//skeletalSprite.animate("idle", -1, 0);
1825 //}
1826 }
1827
getLastNote()1828 int Avatar::getLastNote()
1829 {
1830 return lastNote;
1831 }
1832
singNote(int note)1833 void Avatar::singNote(int note)
1834 {
1835 currentSong.notes.push_back(note);
1836 lastNote = note;
1837 }
1838
updateSingingInterface(float dt)1839 void Avatar::updateSingingInterface(float dt)
1840 {
1841 if (songIcons.size()>0 && songIcons[0]->alpha.x > 0)
1842 {
1843 if (dsq->inputMode != INPUT_JOYSTICK && !core->mouse.change.isZero())
1844 {
1845 if (dsq->game->songLineRender && songIcons[0]->alpha.x == 1)
1846 {
1847 float smallestDist = HUGE_VALF;
1848 int closest = -1;
1849 for (int i = 0; i < songIcons.size(); i++)
1850 {
1851 float dist = (songIcons[i]->position - core->mouse.position).getSquaredLength2D();
1852 if (dist < smallestDist)
1853 {
1854 smallestDist = dist;
1855 closest = i;
1856 }
1857 }
1858
1859 dsq->game->songLineRender->newPoint(core->mouse.position, songIcons[closest]->noteColor);
1860 }
1861 }
1862
1863 if (health <= 0 || isEntityDead())
1864 {
1865 closeSingingInterface();
1866 }
1867 else
1868 {
1869 if (dsq->inputMode == INPUT_JOYSTICK)
1870 {
1871 Vector d = dsq->joystick.position;
1872
1873 if (d.isLength2DIn(JOYSTICK_NOTE_THRESHOLD))
1874 {
1875 core->setMousePosition(core->center);
1876 }
1877 else
1878 {
1879 // Choose the closest note based on the joystick input
1880 // angle (rather than the resultant cursor position).
1881 // But if we already have an active note and we're not
1882 // within the note-accept threshold, maintain the
1883 // current note instead.
1884 float angle = (atan2f(-d.y, d.x) * 180 / PI) + 90;
1885 if (angle < 0)
1886 angle += 360;
1887 int closestNote = (int)floorf(angle/45 + 0.5f);
1888 float angleOffset = fabsf(angle - closestNote*45);
1889 if (closestNote == 8)
1890 closestNote = 0;
1891
1892 bool setNote = (angleOffset <= NOTE_ACCEPT_ANGLE_OFFSET);
1893 if (!setNote)
1894 {
1895 bool alreadyAtNote = false;
1896 for (int i = 0; i < songIcons.size(); i++)
1897 {
1898 const float dist = (songIcons[i]->position - core->mouse.position).getSquaredLength2D();
1899 if (dist <= sqr(NOTE_ACCEPT_DISTANCE))
1900 {
1901 alreadyAtNote = true;
1902 break;
1903 }
1904 }
1905 if (!alreadyAtNote)
1906 setNote = true;
1907 }
1908
1909 if (setNote)
1910 core->setMousePosition(songIcons[closestNote]->position);
1911 }
1912 }
1913
1914 setSongIconPositions();
1915 }
1916 }
1917 }
1918
setSongIconPositions()1919 void Avatar::setSongIconPositions()
1920 {
1921 float radIncr = (2*PI)/float(songIcons.size());
1922 float rad = 0;
1923 for (int i = 0; i < songIcons.size(); i++)
1924 {
1925 songIcons[i]->position = Vector(400,300)+/*this->position + */Vector(sinf(rad)*singingInterfaceRadius, cosf(rad)*singingInterfaceRadius);
1926 rad += radIncr;
1927 }
1928 }
1929
1930 const int chkDist = 2500*2500;
1931
getNearestTarget(const Vector & checkPos,const Vector & distPos,Entity * source,DamageType dt,bool override,std::vector<Target> * ignore,EntityList * entityList)1932 Target Avatar::getNearestTarget(const Vector &checkPos, const Vector &distPos, Entity *source, DamageType dt, bool override, std::vector<Target> *ignore, /*FIXME:unused*/ EntityList *entityList)
1933 {
1934 BBGE_PROF(Avatar_getNearestTarget);
1935 Target t;
1936
1937 Vector targetPosition;
1938 int targetPt = -1;
1939 Entity *closest = 0;
1940 int highestPriority = -999;
1941 float smallestDist = HUGE_VALF;
1942 Entity *e = 0;
1943 FOR_ENTITIES(i)
1944 {
1945 e = *i;
1946 /*
1947 int j;
1948 for (j = 0; j < targets.size(); j++)
1949 {
1950 if (targets[j].e == e) break;
1951 }
1952 if (j != targets.size()) continue;
1953 */
1954
1955 //e &&
1956 if (e != this && e->targetPriority >= highestPriority && this->pullTarget != e && e->isDamageTarget(dt) && dsq->game->isValidTarget(e, this))
1957 {
1958
1959
1960
1961 if (e->position.isNan())
1962 //if (false)
1963 {
1964 std::ostringstream os;
1965 os << "NAN position entity name: " << e->name << " type: " << e->getEntityType();
1966 debugLog(os.str());
1967 continue;
1968 }
1969 else
1970 {
1971 int dist = (e->position - position).getSquaredLength2D();
1972 if (dist < chkDist)
1973 {
1974 int numTargetPoints = e->getNumTargetPoints();
1975 bool clearAfter = false;
1976 if (numTargetPoints == 0)
1977 {
1978 if (ignore)
1979 {
1980 int j = 0;
1981 for (; j < ignore->size(); j++)
1982 {
1983 if ((*ignore)[j].e == e)
1984 break;
1985 }
1986 if (j != ignore->size()) continue;
1987 }
1988 e->addTargetPoint(e->getEnergyShotTargetPosition());
1989 clearAfter = true;
1990 numTargetPoints = 1;
1991 }
1992 if (numTargetPoints > 0)
1993 {
1994 for (int i = 0; i < numTargetPoints; i++)
1995 {
1996 if (ignore)
1997 {
1998 int j = 0;
1999 for (; j < ignore->size(); j++)
2000 {
2001 if ((*ignore)[j].e == e && (*ignore)[j].targetPt == i)
2002 break;
2003 }
2004 if (j != ignore->size()) continue;
2005 }
2006 float dist = (e->getTargetPoint(i) - distPos).getSquaredLength2D();
2007 //float dist = (e->getTargetPoint(i) - distPos).getLength2D();
2008 if (dist < sqr(TARGET_RANGE+e->getTargetRange()))
2009 {
2010 if (override || (checkPos - e->getTargetPoint(i)).isLength2DIn(64))
2011 {
2012 dist = (e->getTargetPoint(i) - checkPos).getSquaredLength2D();
2013 if (dist < smallestDist)
2014 {
2015 highestPriority = e->targetPriority;
2016 targetPosition = e->getTargetPoint(i);
2017 closest = e;
2018 smallestDist = dist;
2019 targetPt = i;
2020 }
2021 }
2022 }
2023 }
2024 }
2025 if (clearAfter)
2026 e->clearTargetPoints();
2027 }
2028 }
2029 }
2030 }
2031 t.e = closest;
2032 t.pos = targetPosition;
2033 t.targetPt = targetPt;
2034 return t;
2035 }
2036
2037 float maxTargetDelay = 0.5;
2038 bool wasDown = false;
updateTargets(float dt,bool override)2039 void Avatar::updateTargets(float dt, bool override)
2040 {
2041 DamageType damageType = DT_AVATAR_ENERGYBLAST;
2042 for (int i = 0; i < targets.size(); i++)
2043 {
2044 if (!targets[i].e
2045 || !targets[i].e->isPresent()
2046 || targets[i].e->getState() == STATE_DEATHSCENE
2047 || !dsq->game->isValidTarget(targets[i].e, this))
2048 {
2049 targets.clear();
2050 break;
2051 }
2052 }
2053 if ((dsq->inputMode == INPUT_MOUSE || dsq->inputMode == INPUT_KEYBOARD) && !(wasDown && core->mouse.buttons.right))
2054 {
2055 wasDown = false;
2056 float mod = 1;
2057 if (isCharging())
2058 mod = maxTargetDelay*10;
2059 targetUpdateDelay += dt*mod;
2060 }
2061
2062 if (targetUpdateDelay > maxTargetDelay || override)
2063 {
2064 maxTargetDelay = 0;
2065 std::vector<Target> oldTargets = targets;
2066 if ((dsq->continuity.form == FORM_ENERGY) && ((core->mouse.buttons.right && state.spellCharge > 0.3f) || override))
2067 //&& state.spellCharge > 0.2f /*&& state.spellCharge < 0.5f*/
2068 {
2069 // crappy hack for now, assuming one target:
2070 targets.clear();
2071
2072
2073 Vector dir = getAim();
2074 Vector checkPos = position + dir;
2075 Vector distPos = position;
2076 if (!(dsq->getGameCursorPosition() - distPos).isLength2DIn(4))
2077 {
2078 Target t;
2079 t = getNearestTarget(checkPos, distPos, this, damageType, override, &targets);
2080 if (t.e)
2081 {
2082 //if ((t.getWorldPosition() - dsq->getGameCursorPosition()).isLength2DIn(64))
2083 {
2084
2085
2086 // found a target?
2087 targets.push_back(t);
2088
2089 targetUpdateDelay = 0;
2090 if (!override && core->mouse.buttons.right)
2091 {
2092 maxTargetDelay = 90;
2093
2094 dsq->spawnParticleEffect("TargetAquired", t.pos);
2095 wasDown = true;
2096 }
2097 }
2098 }
2099 }
2100 if (targets.empty())
2101 {
2102 for (int i = 0; i < oldTargets.size(); i++)
2103 {
2104 Entity *e = oldTargets[i].e;
2105 if (e)
2106 {
2107 int dist = (e->getTargetPoint(oldTargets[i].targetPt) - distPos).getSquaredLength2D();
2108 if (dist < sqr(TARGET_RANGE+e->getTargetRange()))
2109 {
2110 targets.push_back(oldTargets[i]);
2111 }
2112 }
2113 else
2114 {
2115 targets.clear();
2116 break;
2117 }
2118 }
2119 }
2120 }
2121 }
2122 else
2123 {
2124 for (int i = 0; i < targets.size(); i++)
2125 {
2126 Entity *e = targets[i].e;
2127 if (e)
2128 {
2129 if (!(position - e->position).isLength2DIn(e->getTargetRange() + TARGET_RANGE + TARGET_GRACE_RANGE) || !dsq->game->isValidTarget(e, this) || !e->isDamageTarget(damageType))
2130 {
2131 lostTarget(i, targets[i].e);
2132 targets[i].e = 0;
2133 targetUpdateDelay = maxTargetDelay;
2134 wasDown = false;
2135 }
2136 }
2137 }
2138 }
2139 }
2140
loseTargets()2141 void Avatar::loseTargets()
2142 {
2143 for (int i = 0; i < targets.size(); i++)
2144 {
2145 Entity *e = targets[i].e;
2146 if (e)
2147 {
2148 lostTarget(i, targets[i].e);
2149 targets[i].e = 0;
2150 targetUpdateDelay = maxTargetDelay;
2151 }
2152 }
2153 }
2154
updateTargetQuads(float dt)2155 void Avatar::updateTargetQuads(float dt)
2156 {
2157
2158 const Vector cursorpos = dsq->getGameCursorPosition();
2159 particleManager->setSuckPosition(1, cursorpos);
2160
2161 /*
2162 for (int i = 0; i < targetQuads.size(); i++)
2163 {
2164
2165 }
2166 */
2167
2168 static Entity *lastTargetE = 0;
2169 const float tt = 0.02;
2170 for (int i = 0; i < targets.size(); i++)
2171 {
2172 if (targets[i].e)
2173 {
2174
2175 targetQuads[i]->alpha.interpolateTo(1, 0.1);
2176 Entity *e = targets[i].e;
2177 if (lastTargetE != e)
2178 {
2179 dsq->sound->playSfx("target-lock");
2180 lastTargetE = e;
2181 }
2182 else
2183 {
2184 //targetQuads[i]->position.interpolateTo(targets[i].pos, 0.01);
2185 }
2186 targetQuads[i]->position.interpolateTo(targets[i].pos, tt);
2187 targets[i].pos = e->getTargetPoint(targets[i].targetPt);
2188 if (i == 0)
2189 {
2190 particleManager->setSuckPosition(1, targets[i].pos); // suckpos 1 is overridden elsewhere later
2191 particleManager->setSuckPosition(2, targets[i].pos);
2192 }
2193
2194 /*
2195 Emitter *em = targetQuads[i];
2196 if (!em->isRunning())
2197 {
2198 em->start();
2199 }
2200 */
2201 }
2202 else
2203 {
2204 targetQuads[i]->position = cursorpos;
2205 //targetQuads[i]->alpha.interpolateTo(0, 0.1);
2206 }
2207 }
2208
2209 if (targets.empty())
2210 {
2211 for (int i = 0; i < targetQuads.size(); i++)
2212 {
2213 if (lastTargetE != 0)
2214 {
2215 lastTargetE = 0;
2216 }
2217 //targetQuads[i]->position.interpolateTo(dsq->getGameCursorPosition(),tt);
2218 /*
2219 std::ostringstream os;
2220 os << "setting targetQuads[i] to game cursor, is running = " << targetQuads[i]->isRunning();
2221 debugLog(os.str());
2222 */
2223
2224 targetQuads[i]->position = cursorpos;
2225 if (dsq->continuity.form == FORM_ENERGY && isInputEnabled())
2226 {
2227 if (dsq->inputMode == INPUT_JOYSTICK && targetQuads[i]->isRunning())
2228 {
2229 targetQuads[i]->stop();
2230 }
2231 else if (dsq->inputMode != INPUT_JOYSTICK && !targetQuads[i]->isRunning())
2232 {
2233 targetQuads[i]->start();
2234 }
2235 }
2236
2237 /*
2238 if (targetQuads[i]->isRunning())
2239 {
2240 targetQuads[i]->stop();
2241 }
2242 */
2243 }
2244 }
2245 }
2246
2247 //fireAtNearestValidEntity("Fire", DT_AVATAR_ENERGYBLAST);
fireAtNearestValidEntity(const std::string & shot)2248 bool Avatar::fireAtNearestValidEntity(const std::string &shot)
2249 {
2250 if (state.swimTimer > 0)
2251 state.swimTimer -= 0.5f;
2252
2253 skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
2254
2255 targetUpdateDelay = 0;
2256 if (targetUpdateDelay < 0)
2257 targetUpdateDelay = 0;
2258 //bool big = false;
2259
2260 Vector dir;
2261 Vector p = position;
2262 p = boneLeftArm->getWorldPosition();
2263 //&& !dsq->game->isObstructed(TileVector(position))
2264 /*
2265 if (dsq->inputMode == INPUT_MOUSE && state.lockedToWall )
2266 dir = dsq->getGameCursorPosition() - p;
2267 else
2268 */
2269 dir = getAim();
2270
2271 ShotData *shotData = Shot::getShotData(shot);
2272
2273
2274 bool aimAt = (dir.z == 1.0f);
2275 //bool aimAt = true;
2276 dir.z = 0;
2277 Vector targetPosition;
2278
2279 //std::vector<Target>targets;
2280
2281 bool firedShot = false;
2282 //int homing = 0;
2283 /*
2284 if (target)
2285 {
2286 if (dsq->inputMode != INPUT_JOYSTICK && vel.isLength2DIn(50))
2287 {
2288 }
2289 else
2290 {
2291
2292 }
2293 homing = home;
2294 }
2295 else
2296 homing = 0;
2297 */
2298
2299 /*
2300 if (!dir.isLength2DIn(2))
2301 {
2302 */
2303 Shot *s = 0;
2304 bool clearTargets = false;
2305
2306 // allow autoAim if desired
2307 if ((dsq->inputMode == INPUT_JOYSTICK && !aimAt) || dsq->user.control.autoAim)
2308 {
2309 if (targets.empty())
2310 {
2311 // force a grab of the nearest targets
2312 updateTargets(shotData->damageType, true);
2313 // clear the targets after
2314 clearTargets = true;
2315 }
2316 }
2317
2318 if (!targets.empty())
2319 {
2320 //homing = home;
2321 for (int i = 0; i < targets.size(); i++)
2322 {
2323 /*
2324 if (!aimAt)
2325 {
2326 dir = targets[i].pos - p;
2327 }
2328 */
2329 /*
2330 std::ostringstream os;
2331 os << "shotdir(" << dir.x << ", " << dir.y << ")";
2332 debugLog(os.str());
2333 */
2334
2335
2336 /*
2337 Vector oldDir = dir;
2338
2339 dir.normalize2D();
2340 dir = (dir + oldDir)/2.0f;
2341 */
2342
2343 if (!aimAt)
2344 {
2345 dir = (targets[i].e->getTargetPoint(targets[i].targetPt) - p);
2346 }
2347
2348 s = dsq->game->fireShot(shot, this, targets[i].e);
2349 s->setAimVector(dir);
2350 s->setTargetPoint(targets[i].targetPt);
2351
2352 /*
2353 if (dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY2))
2354 {
2355 s = dsq->game->fireShot("EnergyBlast2", this, targets[i].e);
2356 s->setAimVector(dir);
2357 s->setTargetPoint(targets[i].targetPt);
2358 }
2359 else
2360 {
2361 s = dsq->game->fireShot("EnergyBlast", this, targets[i].e);
2362 s->setAimVector(dir);
2363 s->setTargetPoint(targets[i].targetPt);
2364 }
2365 */
2366 }
2367 }
2368 else
2369 {
2370 //if (!dir.isLength2DIn(2) || dsq->inputMode == INPUT_JOYSTICK)
2371 if (true)
2372 {
2373 s = dsq->game->fireShot(shot, this);
2374
2375 if (dir.isLength2DIn(2))
2376 {
2377 if (!vel.isLength2DIn(2))
2378 s->setAimVector(vel);
2379 else // standing still
2380 s->setAimVector(getForwardAim());
2381 }
2382 else
2383 {
2384 s->setAimVector(dir);
2385 }
2386 /*
2387 if (dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY2))
2388 {
2389 s = dsq->game->fireShot("EnergyBlast2", this);
2390 s->setAimVector(dir);
2391 }
2392 else
2393 {
2394 s = dsq->game->fireShot("EnergyBlast", this);
2395 s->setAimVector(dir);
2396 }
2397 */
2398 }
2399 }
2400
2401 if (s)
2402 {
2403 checkUpgradeForShot(s);
2404
2405
2406
2407 skeletalSprite.transitionAnimate("fireBlast", 0.1, 0, ANIMLAYER_ARMOVERRIDE);
2408 s->position = p;
2409 //s->damageType = dt;
2410 /*
2411 if (!targets.empty())
2412 s->damage = float(damage)/float(targets.size());
2413 */
2414 firedShot = true;
2415 }
2416
2417 if (clearTargets)
2418 {
2419 targets.clear();
2420 // try to avoid targets sticking
2421 updateTargetQuads(shotData->damageType);
2422 }
2423
2424 return firedShot;
2425 }
2426
getFacing()2427 Vector Avatar::getFacing()
2428 {
2429 if (vel.isLength2DIn(2) && rotation.z == 0)
2430 {
2431 if (isfh())
2432 return Vector(1,0);
2433 else
2434 return Vector(-1,0);
2435 }
2436 return getForward();
2437 }
2438
switchDualFormMode()2439 void Avatar::switchDualFormMode()
2440 {
2441 //debugLog("dualForm: changing");
2442
2443 dsq->sound->playSfx("dualform-switch");
2444
2445 dsq->overlay->color = Vector(1,1,1);
2446 dsq->fade(1, 0);
2447 dsq->fade(0, 0.5);
2448
2449 if (dsq->continuity.dualFormMode == Continuity::DUALFORM_NAIJA)
2450 dsq->continuity.dualFormMode = Continuity::DUALFORM_LI;
2451 else
2452 dsq->continuity.dualFormMode = Continuity::DUALFORM_NAIJA;
2453
2454 refreshDualFormModel();
2455 }
2456
hasThingToActivate()2457 bool Avatar::hasThingToActivate()
2458 {
2459 return ((pathToActivate != 0) || (entityToActivate != 0));
2460 }
2461
formAbility(int ability)2462 void Avatar::formAbility(int ability)
2463 {
2464 if (hasThingToActivate()) return;
2465 //debugLog("form ability function");
2466 switch(dsq->continuity.form)
2467 {
2468 case FORM_DUAL:
2469 {
2470 debugLog("dual form ability");
2471 /*
2472 if (this->getVectorToCursorFromScreenCentre().isLength2DIn(minMouse))
2473 {
2474 debugLog("in and changing");
2475 if (dualFormMode == DUALFORM_NAIJA)
2476 dualFormMode = DUALFORM_LI;
2477 else
2478 dualFormMode = DUALFORM_NAIJA;
2479 refreshDualFormModel();
2480 }
2481 else
2482 */
2483 {
2484 /*
2485 if (chargeLevelAttained == 2)
2486 {
2487 if (dualFormMode == DUALFORM_NAIJA)
2488 dualFormMode = DUALFORM_LI;
2489 else
2490 dualFormMode = DUALFORM_NAIJA;
2491 refreshDualFormModel();
2492 }
2493 else
2494 */
2495 {
2496 if (dsq->continuity.dualFormMode == Continuity::DUALFORM_NAIJA)
2497 {
2498
2499 // ~~~~~~~~~~ SOUL SCREAM
2500
2501 if (chargeLevelAttained == 1)
2502 {
2503 if (dsq->continuity.dualFormCharge >= requiredDualFormCharge)
2504 {
2505 core->sound->playSfx("DualForm-Scream");
2506
2507 if (core->afterEffectManager)
2508 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter,0.08,0.05,22,0.2f, 1.2));
2509
2510 dsq->continuity.dualFormCharge = 0;
2511 dsq->shakeCamera(25, 2);
2512
2513 core->globalScale = Vector(0.4, 0.4);
2514 core->globalScaleChanged();
2515 myZoom = Vector(0.4, 0.4);
2516
2517 /*
2518 setv(EV_NOINPUTNOVEL, 0);
2519 core->globalScale = Vector(1.5, 1.5);
2520 core->main(0.5);
2521 setv(EV_NOINPUTNOVEL, 1);
2522 */
2523
2524
2525 FOR_ENTITIES(i)
2526 {
2527 Entity *e = *i;
2528 if (e->getEntityType() == ET_ENEMY && e != this)
2529 {
2530 if (e->isv(EV_SOULSCREAMRADIUS, -1) || (e->position - position).isLength2DIn(1000 + e->getv(EV_SOULSCREAMRADIUS)))
2531 {
2532 DamageData d;
2533 d.damage = 20;
2534 d.damageType = DT_AVATAR_DUALFORMNAIJA;
2535 d.attacker = this;
2536 d.form = dsq->continuity.form;
2537 e->damage(d);
2538 }
2539 }
2540 }
2541
2542 /*
2543 setv(EV_NOINPUTNOVEL, 0);
2544 core->main(0.5);
2545 dsq->screenTransition->capture();
2546 dsq->screenTransition->go(0.5);
2547 myZoom = Vector(1,1);
2548 setv(EV_NOINPUTNOVEL, 1);
2549 */
2550 }
2551 else
2552 {
2553 core->sound->playSfx("Denied");
2554 }
2555 }
2556 }
2557 else if (dsq->continuity.dualFormMode == Continuity::DUALFORM_LI)
2558 {
2559 if (chargeLevelAttained == 1)
2560 {
2561 int i = 0;
2562 int num = 5;
2563 for (; i < num; i++)
2564 {
2565 Shot *s = dsq->game->fireShot("DualForm", this, 0, position, 0);
2566 //*0.5f + getAim()*0.5f
2567 Vector v1 = this->getTendrilAimVector(i, num);
2568 Vector v2 = getAim();
2569 v1.normalize2D();
2570 v2.normalize2D();
2571 s->setAimVector(v1*0.1f + v2*0.9f);
2572 }
2573 core->sound->playSfx("DualForm-Shot");
2574 dsq->spawnParticleEffect("DualFormFire", position);
2575
2576 /*
2577 didShockDamage = false;
2578 doShock("DualFormLiTendril");
2579 */
2580 }
2581 else
2582 {
2583 core->sound->playSfx("Denied");
2584 /*
2585 if (!fireDelay)
2586 {
2587 if (fireAtNearestValidEntity("DualFormLi"))
2588 {
2589 fireDelay = fireDelayTime;
2590 }
2591 }
2592 */
2593 }
2594 }
2595 }
2596 }
2597 /*
2598 else if (ability == 1)
2599 {
2600
2601 }
2602 */
2603 }
2604 break;
2605 case FORM_ENERGY:
2606 {
2607 if (ability == 0)
2608 {
2609 if (chargeLevelAttained == 2)
2610 {
2611 if (dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY2))
2612 doShock("EnergyTendril2");
2613 else
2614 doShock("EnergyTendril");
2615 if (!state.lockedToWall)
2616 skeletalSprite.animate("energyChargeAttack", 0, ANIMLAYER_UPPERBODYIDLE);
2617
2618 /*
2619 if (core->afterEffectManager)
2620 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter, 0.1,0.03,30,0.2f, 1.5));
2621 */
2622 dsq->playVisualEffect(VFX_SHOCK, position, this);
2623 }
2624 else
2625 {
2626 if (!fireDelay)
2627 {
2628 std::string shotName;
2629 if (dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY2))
2630 shotName = "EnergyBlast2";
2631 else
2632 shotName = "EnergyBlast";
2633
2634 if (fireAtNearestValidEntity(shotName))
2635 {
2636 fireDelay = fireDelayTime;
2637 }
2638 }
2639 }
2640 }
2641 }
2642 break;
2643 case FORM_NATURE:
2644 // no abilities
2645 {
2646 //debugLog("rock ability");
2647 if (ability == 0)
2648 {
2649 if (formAbilityDelay == 0)
2650 {
2651 formAbilityDelay = 0.2;
2652 //Vector pos = dsq->getGameCursorPosition() - position;
2653
2654 Vector pos = getAim();
2655 if (!pos.isZero())
2656 pos.setLength2D(16);
2657 pos += position;
2658
2659 std::string seedName;
2660 if (chargeLevelAttained == 0)
2661 seedName = "SeedFlower";
2662 else if (chargeLevelAttained == 2)
2663 seedName = "SeedUberVine";
2664
2665 dsq->game->fireShot(seedName, this, 0, pos, getAim());
2666
2667
2668 /*
2669 Vector pos = getAim();
2670 if (!pos.isZero())
2671 pos.setLength2D(64);
2672 pos += position;
2673
2674
2675 //dsq->spawnParticleEffect("Fertilizer", pos);
2676
2677 Entity *e = 0;
2678 std::string seedName;
2679 if (chargeLevelAttained == 0)
2680 seedName = "SeedFlower";
2681 else if (chargeLevelAttained == 2)
2682 seedName = "SeedUberVine";
2683
2684 e = dsq->game->createEntity(seedName, 0, pos, 0, false, "");
2685
2686 Vector add = pos - position;
2687 add.setLength2D(800);
2688 e->vel += add;
2689 */
2690
2691 /*
2692 if (chargeLevelAttained == 0)
2693 {
2694 }
2695 else if (chargeLevelAttained == 1)
2696 {
2697 e->setState(STATE_CHARGE1);
2698 }
2699 else if (chargeLevelAttained == 2)
2700 {
2701 e->setState(STATE_CHARGE2);
2702 }
2703
2704 e->update(0);
2705 */
2706 // idle = charge 0
2707 // attack = charge1
2708 // something = charge2
2709 /*
2710 FOR_ENTITIES (i)
2711 {
2712 Entity *e = *i;
2713 if (e && e->getEntityType() == ET_ENEMY && e->isDamageTarget(DT_AVATAR_NATURE))
2714 {
2715 if ((e->position - pos).isLength2DIn(128))
2716 {
2717 DamageData d;
2718 d.damageType = DT_AVATAR_NATURE;
2719 d.damage = 1;
2720 d.attacker = this;
2721 e->damage(d);
2722 }
2723 }
2724 }
2725 */
2726 }
2727 }
2728 }
2729 break;
2730 case FORM_BEAST:
2731 {
2732
2733 if (!dsq->continuity.isNaijaEatsEmpty())
2734 {
2735 EatData *d = dsq->continuity.getLastNaijaEat();
2736 if (!d->shot.empty())
2737 {
2738 int num = getNumShots()-2;
2739
2740 for (int i = 0; i < num; i++)
2741 {
2742 bool playSfx = true;
2743 if (i > 0)
2744 playSfx = false;
2745 Shot *s = dsq->game->fireShot(d->shot, this, 0, Vector(0,0,0), Vector(0,0,0), playSfx);
2746 if (s->shotData && s->shotData->damage > 0)
2747 {
2748 s->extraDamage = 1;
2749 }
2750
2751 Entity *target = 0;
2752 if (s->shotData->homing > 0)
2753 {
2754 Vector p = dsq->getGameCursorPosition();
2755 target = dsq->game->getNearestEntity(p, 800, this, ET_ENEMY, s->getDamageType());
2756 }
2757 if (target)
2758 {
2759 s->target = target;
2760 }
2761
2762 if (bone_head)
2763 {
2764 s->position = bone_head->getWorldPosition();
2765 }
2766 else
2767 {
2768 s->position = this->position;
2769 }
2770
2771 Vector aim = getVectorToCursor();
2772 if (aim.isZero())
2773 aim = getForwardAim();
2774 if (num == 1)
2775 s->setAimVector(aim);
2776 else
2777 {
2778 aim.normalize2D();
2779 s->setAimVector(getTendrilAimVector(i, num)*0.1f + aim*0.9f);
2780 }
2781
2782 if (s->shotData && s->shotData->avatarKickBack)
2783 {
2784 Vector d = s->velocity;
2785 d.setLength2D(-s->shotData->avatarKickBack);
2786 float effect = 1;
2787 if (!isUnderWater())
2788 effect = 0.4;
2789 push(d, s->shotData->avatarKickBackTime * effect, s->shotData->avatarKickBack * effect, 0);
2790 }
2791 }
2792
2793 debugLog("firing: " + d->shot);
2794 d->ammo--;
2795 }
2796 if (d->ammo <= 0)
2797 dsq->continuity.removeLastNaijaEat();
2798 //dsq->continuity.removeEatData(eats.size()-1);
2799 }
2800
2801 /*
2802 switch(inTummy)
2803 {
2804 case EAT_BASICSHOT:
2805 {
2806 tummyAmount --;
2807 // FIRE!
2808 if (tummyAmount <= 0)
2809 {
2810
2811 //fireAtNearestValidEntity("Vomit", inTummy, DT_AVATAR_VOMIT, 6000);
2812 inTummy = EAT_NONE;
2813 }
2814 }
2815 break;
2816 }
2817 */
2818 /*
2819 if (inTummy > 0)
2820 {
2821 if (inTummy > 3)
2822 inTummy = 3;
2823 if (fireAtNearestValidEntity("Vomit", inTummy, DT_AVATAR_VOMIT, 6000))
2824 {
2825 inTummy = 0;
2826 }
2827 }
2828 */
2829 /*
2830 if (ability == 0)
2831 {
2832 Vector bitePos = position;
2833 Vector offset = vel;
2834 offset.setLength2D(128);
2835 bitePos += offset;
2836
2837 FOR_ENTITIES (i)
2838 {
2839 Entity *e = *i;
2840 if (e && (e->position - bitePos).getSquaredLength2D() < sqr(64))
2841 {
2842 DamageData d;
2843 d.attacker = this;
2844 d.damage = 2;
2845 e->damage(d);
2846 heal(1);
2847 }
2848 }
2849 }
2850 else
2851 {
2852 }
2853 */
2854 }
2855 break;
2856 case FORM_SUN:
2857 {
2858 if (formAbilityDelay == 0 && chargeLevelAttained==1)
2859 {
2860 core->sound->playSfx("SunForm");
2861 //dsq->spawnParticleEffect("LightFlare", position);
2862
2863 chargeEmitter->load("SunFlare");
2864 chargeEmitter->start();
2865
2866 PauseQuad *q = new PauseQuad;
2867 q->setTexture("Naija/LightFormGlow");
2868 q->position = position;
2869 q->setWidthHeight(1024, 1024);
2870 q->setLife(1);
2871 q->setDecayRate(0.05);
2872 q->fadeAlphaWithLife = 1;
2873 q->scale = Vector(0,0);
2874 q->scale.interpolateTo(Vector(2,2), 0.1);
2875 dsq->game->addRenderObject(q, LR_ELEMENTS13);
2876 q->moveToFront();
2877
2878 FOR_ENTITIES(i)
2879 {
2880 Entity *e = *i;
2881 if (e != this && (e->position - position).isLength2DIn(2048))
2882 {
2883 e->lightFlare();
2884 }
2885 }
2886 //formAbilityDelay = 0.1;
2887 }
2888 }
2889 break;
2890 case FORM_SPIRIT:
2891 // spirit beacon
2892 // absorbs nearby shots, and respawns the player if in a "SPIRITBEACON" node
2893 if (formAbilityDelay == 0)
2894 {
2895 core->sound->playSfx("Spirit-Beacon");
2896 //dsq->spawnParticleEffect("SpiritBeacon", position);
2897 std::list<Shot*> delShots;
2898 Shot::Shots::iterator i;
2899 for (i = Shot::shots.begin(); i != Shot::shots.end(); i++)
2900 {
2901 Shot *s = (*i);
2902 if (s->isActive() && s->shotData && !s->shotData->invisible)
2903 {
2904 if (!s->firer || s->firer->getEntityType()==ET_ENEMY)
2905 {
2906 if ((s->position - position).isLength2DIn(256))
2907 {
2908 //s->safeKill();
2909 delShots.push_back(s);
2910 spiritEnergyAbsorbed++;
2911 }
2912 }
2913 }
2914 }
2915 for (std::list<Shot*>::iterator j = delShots.begin(); j != delShots.end(); j++)
2916 {
2917 Shot *s = (*j);
2918 s->safeKill();
2919 }
2920 if (spiritEnergyAbsorbed > 4)
2921 {
2922 dsq->game->spawnManaBall(position, 1);
2923 spiritEnergyAbsorbed = 0;
2924 }
2925 spiritBeaconEmitter.start();
2926 formAbilityDelay = 1.0;
2927
2928 Path *p = dsq->game->getNearestPath(position, "SPIRITBEACON");
2929 if (p && p->isCoordinateInside(position))
2930 {
2931 bodyPosition = position;
2932 if (pullTarget)
2933 {
2934 pullTarget->position = position;
2935 }
2936 revert();
2937 }
2938 else
2939 {
2940 Path *p = dsq->game->getNearestPath(position, PATH_SPIRITPORTAL);
2941 if (p && p->isCoordinateInside(position))
2942 {
2943 if (inSpiritWorld)
2944 changeForm(FORM_NORMAL);
2945 dsq->game->warpToSceneFromNode(p);
2946 }
2947 }
2948 }
2949 break;
2950 case FORM_FISH:
2951 {
2952
2953 }
2954 break;
2955 }
2956 }
2957
getTendrilAimVector(int i,int max)2958 Vector Avatar::getTendrilAimVector(int i, int max)
2959 {
2960 float a = float(float(i)/float(max))*PI*2;
2961 Vector aim(sinf(a), cosf(a));
2962 if (state.lockedToWall)
2963 {
2964 Vector n = dsq->game->getWallNormal(position);
2965 if (!n.isZero())
2966 {
2967 aim = aim*0.4f + n*0.6f;
2968 }
2969 }
2970 return aim;
2971 }
2972
getNumShots()2973 int Avatar::getNumShots()
2974 {
2975 int thits = normalTendrilHits;
2976 if (flourishPowerTimer.isActive())
2977 {
2978 if (lastBurstType == BURST_WALL)
2979 thits = maxTendrilHits;
2980 else
2981 thits = rollTendrilHits;
2982 }
2983 else
2984 {
2985 if (bursting)
2986 {
2987 if (lastBurstType == BURST_WALL)
2988 thits = rollTendrilHits;
2989 }
2990 }
2991 return thits;
2992 }
2993
doShock(const std::string & shotName)2994 void Avatar::doShock(const std::string &shotName)
2995 {
2996
2997
2998
2999 int c = 0;
3000 //int maxHit = 2 + dsq->continuity.getSpellLevel(SPELL_SHOCK)*2;
3001 //int maxHit = 4;
3002 std::vector <Entity*> entitiesToHit;
3003 std::vector <Target> localTargets;
3004 bool clearTargets = true;
3005
3006 int thits = getNumShots();
3007
3008 /*
3009 if (skeletalSprite.getAnimationLayer(ANIMLAYER_FLOURISH)->getCurrentAnimation())
3010 {
3011 thits = maxTendrilHits;
3012 }
3013 */
3014
3015 if (!targets.empty() && targets[0].e != 0)
3016 {
3017 clearTargets = false;
3018 for (int i = 0; i < thits; i++)
3019 {
3020 entitiesToHit.push_back(targets[0].e);
3021 }
3022 }
3023 else
3024 {
3025 //std::vector <Target> localTargets;
3026
3027 localTargets.clear();
3028
3029 int range = 800;
3030 EntityList entityList;
3031 FOR_ENTITIES(i)
3032 {
3033 Entity *e = *i;
3034 if (e != this && e->isPresent() && e->isDamageTarget(DT_AVATAR_SHOCK) && (e->position - position).isLength2DIn(range))
3035 {
3036 entityList.push_back(e);
3037 }
3038 }
3039
3040 while (c < thits)
3041 {
3042 Target t = getNearestTarget(position, position, this, DT_AVATAR_SHOCK, true, &localTargets, &entityList);
3043 if (t.e)
3044 {
3045 localTargets.push_back(t);
3046 entitiesToHit.push_back(t.e);
3047 targets.push_back(t);
3048 c ++;
3049 }
3050 else
3051 {
3052 break;
3053 }
3054 }
3055
3056 if (!localTargets.empty())
3057 {
3058 while (entitiesToHit.size()<thits)
3059 {
3060 for (int i = 0; i < localTargets.size(); i++)
3061 {
3062 if (!(entitiesToHit.size()<thits))
3063 break;
3064 entitiesToHit.push_back(localTargets[i].e);
3065 targets.push_back(localTargets[i]);
3066 }
3067 }
3068 }
3069 localTargets.clear();
3070 }
3071
3072 Vector aim = getAim();
3073 aim.normalize2D();
3074 int sz = entitiesToHit.size();
3075
3076
3077
3078
3079 if (sz == 0)
3080 {
3081 for (int i = 0; i < thits; i++)
3082 {
3083 Shot *s = dsq->game->fireShot(shotName, this, 0);
3084
3085 s->setAimVector(getTendrilAimVector(i, thits));
3086
3087 checkUpgradeForShot(s);
3088 }
3089 }
3090 else
3091 {
3092 for (int i = 0; i < sz; i++)
3093 {
3094 Entity *e = entitiesToHit[i];
3095 if (e)
3096 {
3097 Shot *s = dsq->game->fireShot(shotName, this, e);
3098 if (!targets.empty())
3099 {
3100 for (int j = 0; j < targets.size(); j++)
3101 {
3102 if (targets[j].e == e)
3103 s->targetPt = targets[j].targetPt;
3104 }
3105 }
3106 /*
3107 else if (!localTargets.empty())
3108 {
3109 for (int j = 0; j < localTargets.size(); j++)
3110 {
3111 if (localTargets[j].e == e)
3112 s->targetPt = localTargets[j].targetPt;
3113 }
3114 }
3115 */
3116 Vector d = e->position - position;
3117 /*
3118 float a = float(float(i)/float(sz))*PI*2;
3119 Vector aim(sinf(a), cosf(a));
3120
3121 swizzleTendrilAimVector(aim);
3122 */
3123 s->setAimVector(getTendrilAimVector(i, thits));
3124 checkUpgradeForShot(s);
3125 /*
3126 float ang = 0;
3127 MathFunctions::calculateAngleBetweenVectorsInRadians(Vector(0,-1), d, ang);
3128 float a = i-(sz/2);
3129 Vector adjust = a*spread + ang;
3130 d.normalize2D();
3131 s->setAimVector((d + adjust)/2);
3132 */
3133 }
3134 }
3135 }
3136
3137 if (clearTargets)
3138 {
3139 targets.clear();
3140 }
3141 }
3142
formAbilityUpdate(float dt)3143 void Avatar::formAbilityUpdate(float dt)
3144 {
3145 switch(dsq->continuity.form)
3146 {
3147 case FORM_FISH:
3148 {
3149 if (core->mouse.buttons.right)
3150 {
3151 const float bubbleRate = 0.2;
3152
3153 state.abilityDelay -= dt;
3154 if (state.abilityDelay < 0)
3155 state.abilityDelay = 0;
3156
3157 if (state.abilityDelay == 0)
3158 {
3159 state.abilityDelay = bubbleRate;
3160 //state.abilityDelay -= bubbleRate;
3161 // spawn bubble
3162 //Entity *bubble = dsq->game->createEntity("FishFormBubble", 0, position, 0, false, "");
3163 Vector dir = getAim();
3164 dir.normalize2D();
3165
3166 dsq->game->fireShot("FishFormBubble", this, 0, position+dir*16, dir);
3167 }
3168 }
3169 }
3170 break;
3171 case FORM_ENERGY:
3172 {
3173 }
3174 break;
3175 }
3176 }
3177
isMouseInputEnabled()3178 bool Avatar::isMouseInputEnabled()
3179 {
3180 if (!inputEnabled) return false;
3181 //if (dsq->continuity.getWorldType() != WT_NORMAL) return false;
3182 //if (getState() != STATE_IDLE) return false;
3183 if (dsq->game->isPaused()) return false;
3184 return true;
3185 }
3186
rmbd()3187 void Avatar::rmbd()
3188 {
3189 //core->setDockIcon("BitBlot");
3190 if (!isMouseInputEnabled() || isEntityDead()) return;
3191 if (dsq->continuity.form == FORM_NORMAL )
3192 {
3193 //if (isCoordinateInRadius(dsq->getGameCursorPosition(), 96))
3194 ///Vector diff = core->mouse.position - c;
3195 if (dsq->inputMode == INPUT_MOUSE)
3196 {
3197 Vector diff = getVectorToCursorFromScreenCentre();
3198 if (diff.getSquaredLength2D() < sqr(openSingingInterfaceRadius))
3199 openSingingInterface();
3200 }
3201 else
3202 {
3203 openSingingInterface();
3204 }
3205 }
3206 else
3207 {
3208 startCharge(0);
3209 }
3210 }
3211
rmbu()3212 void Avatar::rmbu()
3213 {
3214 if (!isMouseInputEnabled() || isEntityDead()) return;
3215
3216 if (charging)
3217 {
3218 if (!entityToActivate && !pathToActivate)
3219 formAbility(0);
3220
3221 endCharge();
3222 }
3223
3224
3225 dsq->cursorGlow->alpha.interpolateTo(0, 0.2);
3226 dsq->cursorBlinker->alpha.interpolateTo(0, 0.2);
3227
3228 if (singing)
3229 {
3230 closeSingingInterface();
3231 }
3232
3233
3234 if (entityToActivate)
3235 {
3236 activateEntity = entityToActivate;
3237 entityToActivate = 0;
3238 }
3239 if (pathToActivate)
3240 {
3241 pathToActivate->activate();
3242 pathToActivate = 0;
3243 }
3244 }
3245
canCharge(int ability)3246 bool Avatar::canCharge(int ability)
3247 {
3248 switch(dsq->continuity.form)
3249 {
3250 case FORM_ENERGY:
3251 if (ability == 0) return true;
3252 break;
3253 case FORM_BEAST:
3254 return false;
3255 break;
3256 case FORM_DUAL:
3257 return true;
3258 break;
3259 case FORM_NATURE:
3260 if (ability == 0)
3261 return true;
3262 break;
3263 case FORM_SUN:
3264 return true;
3265 break;
3266 }
3267 return false;
3268 }
3269
startCharge(int ability)3270 void Avatar::startCharge(int ability)
3271 {
3272 if (!isCharging() && canCharge(ability))
3273 {
3274 if (dsq->loops.charge != BBGE_AUDIO_NOCHANNEL)
3275 {
3276 core->sound->stopSfx(dsq->loops.charge);
3277 dsq->loops.charge = BBGE_AUDIO_NOCHANNEL;
3278 }
3279
3280 PlaySfx sfx;
3281 sfx.name = "ChargeLoop";
3282 sfx.loops = -1;
3283 dsq->loops.charge = core->sound->playSfx(sfx);
3284
3285 state.spellCharge = 0;
3286 chargeLevelAttained = 0;
3287
3288 switch(dsq->continuity.form)
3289 {
3290 case FORM_ENERGY:
3291 chargingEmitter->load("ChargingEnergy");
3292 break;
3293 case FORM_NATURE:
3294 chargingEmitter->load("ChargingNature");
3295 break;
3296 case FORM_SUN:
3297 chargingEmitter->load("ChargingEnergy");
3298 break;
3299 case FORM_DUAL:
3300 chargingEmitter->load("ChargingDualForm");
3301 break;
3302 default:
3303 chargingEmitter->load("ChargingGeneric");
3304 break;
3305 }
3306
3307 chargingEmitter->start();
3308
3309 charging = true;
3310
3311 }
3312 if (!canCharge(ability))
3313 {
3314 formAbility(ability);
3315 }
3316 }
3317
setBlockSinging(bool v)3318 void Avatar::setBlockSinging(bool v)
3319 {
3320 blockSinging = v;
3321 if (v)
3322 {
3323 currentSong.notes.clear(); // abort singing without triggering a song, if queued
3324 closeSingingInterface();
3325 }
3326 }
3327
canSetBoneLock()3328 bool Avatar::canSetBoneLock()
3329 {
3330 return true;
3331 }
3332
onSetBoneLock()3333 void Avatar::onSetBoneLock()
3334 {
3335 Entity::onSetBoneLock();
3336
3337 if (boneLock.on)
3338 {
3339 skeletalSprite.transitionAnimate("wallLookUp", 0.2, -1);
3340 lockToWallCommon();
3341 state.lockedToWall = 1;
3342 wallNormal = boneLock.localOffset;
3343 wallNormal.normalize2D();
3344 rotateToVec(wallNormal, 0.1);
3345 }
3346 else
3347 {
3348 if (state.lockedToWall)
3349 {
3350 fallOffWall();
3351 }
3352 }
3353 }
3354
onUpdateBoneLock()3355 void Avatar::onUpdateBoneLock()
3356 {
3357 Entity::onUpdateBoneLock();
3358
3359 wallNormal = boneLock.wallNormal;
3360 rotateToVec(wallNormal, 0.01);
3361 }
3362
lmbd()3363 void Avatar::lmbd()
3364 {
3365 if (!isMouseInputEnabled()) return;
3366
3367 // getstopdistance
3368 if (_isUnderWater)
3369 {
3370 Vector v = getVectorToCursor();
3371 if (v.isLength2DIn(getStopDistance()) && !v.isLength2DIn(minMouse))
3372 {
3373 if (state.lockedToWall)
3374 {
3375 fallOffWall();
3376 }
3377 }
3378 }
3379 }
3380
fallOffWall()3381 void Avatar::fallOffWall()
3382 {
3383 //stillTimer.stop();
3384 if (state.lockedToWall)
3385 {
3386 lockToWallFallTimer = 0;
3387 state.nearWall = false;
3388 state.lockedToWall = false;
3389
3390 setBoneLock(BoneLock());
3391
3392
3393 idle();
3394 offset.interpolateTo(Vector(0,0), 0.1);
3395 if (!wallNormal.isZero())
3396 {
3397 Vector velSet = wallNormal;
3398 velSet.setLength2D(200);
3399 vel += velSet;
3400 }
3401 //doCollisionAvoidance(dt, 5, 1);
3402 }
3403 }
3404
lmbu()3405 void Avatar::lmbu()
3406 {
3407 if (!isMouseInputEnabled()) return;
3408
3409 if (dsq->continuity.toggleMoveMode)
3410 movingOn = !movingOn;
3411
3412 if (isSinging())
3413 {
3414 // switch menu
3415 }
3416 }
3417
isCharging()3418 bool Avatar::isCharging()
3419 {
3420 return charging;
3421 }
3422
endCharge()3423 void Avatar::endCharge()
3424 {
3425 if (charging)
3426 {
3427 if (dsq->loops.charge != BBGE_AUDIO_NOCHANNEL)
3428 {
3429 core->sound->stopSfx(dsq->loops.charge);
3430 dsq->loops.charge = BBGE_AUDIO_NOCHANNEL;
3431 }
3432
3433 chargingEmitter->stop();
3434
3435 charging = false;
3436 state.spellCharge = 0;
3437 }
3438 }
3439
getWallNormal(TileVector t)3440 Vector Avatar::getWallNormal(TileVector t)
3441 {
3442 return dsq->game->getWallNormal(t.worldVector(), 5)*-1;
3443 }
3444
getSingingInterfaceRadius()3445 int Avatar::getSingingInterfaceRadius()
3446 {
3447 return singingInterfaceRadius;
3448 }
3449
getOpenSingingInterfaceRadius()3450 int Avatar::getOpenSingingInterfaceRadius()
3451 {
3452 return openSingingInterfaceRadius;
3453 }
3454
isSwimming()3455 bool Avatar::isSwimming()
3456 {
3457 return swimming;
3458 }
3459
lockToWallCommon()3460 void Avatar::lockToWallCommon()
3461 {
3462 swimEmitter.stop();
3463
3464 skeletalSprite.stopAllAnimations();
3465 rotationOffset.interpolateTo(0, 0.01);
3466
3467 fallGravityTimer = 0;
3468
3469 dsq->spawnParticleEffect("LockToWall", position);
3470 stopBurst();
3471 stopRoll();
3472 core->sound->playSfx("LockToWall", 1.0);
3473 //bursting = false;
3474 this->burst = 1;
3475 //lastLockToWallPos = position;
3476
3477 state.lockToWallDelay.start(0.2);
3478 state.lockedToWall = true;
3479
3480 lockToWallFallTimer = -1;
3481
3482 // move this to its own function?
3483 state.backFlip = false;
3484 skeletalSprite.getAnimationLayer(ANIMLAYER_OVERRIDE)->stopAnimation();
3485 }
3486
lockToWall()3487 void Avatar::lockToWall()
3488 {
3489 if (riding) return;
3490 if (inCurrent && !canSwimAgainstCurrents()) return;
3491 if (!canLockToWall()) return;
3492 if (state.lockedToWall) return;
3493 if (vel.x == 0 && vel.y == 0) return;
3494 if (dsq->game->isPaused()) return;
3495
3496 TileVector t(position);
3497 Vector m = vel;
3498 m.setLength2D(3);
3499 t.x += int(m.x);
3500 t.y += int(m.y);
3501
3502 bool good = true;
3503 if (!dsq->game->isObstructed(t))
3504 {
3505 do
3506 {
3507 TileVector test;
3508
3509 test = TileVector(t.x, t.y+1);
3510 if (dsq->game->isObstructed(test))
3511 {
3512 t = test;
3513 break;
3514 }
3515 test = TileVector(t.x, t.y-1);
3516 if (dsq->game->isObstructed(test))
3517 {
3518 t = test;
3519 break;
3520 }
3521 test = TileVector(t.x-1, t.y);
3522 if (dsq->game->isObstructed(test))
3523 {
3524 t = test;
3525 break;
3526 }
3527 test = TileVector(t.x+1, t.y);
3528 if (dsq->game->isObstructed(test))
3529 {
3530 t = test;
3531 break;
3532 }
3533 test = TileVector(t.x+1, t.y+1);
3534 if (dsq->game->isObstructed(test))
3535 {
3536 t = test;
3537 break;
3538 }
3539 test = TileVector(t.x-1, t.y+1);
3540 if (dsq->game->isObstructed(test))
3541 {
3542 t = test;
3543 break;
3544 }
3545 test = TileVector(t.x+1, t.y-1);
3546 if (dsq->game->isObstructed(test))
3547 {
3548 t = test;
3549 break;
3550 }
3551 test = TileVector(t.x-1, t.y-1);
3552 if (dsq->game->isObstructed(test))
3553 {
3554 t = test;
3555 break;
3556 }
3557
3558 good = false;
3559 }
3560 while(0);
3561 }
3562
3563 if (dsq->game->isObstructed(t, OT_HURT) && isDamageTarget(DT_WALLHURT))
3564 {
3565 good = false;
3566 }
3567 if (good)
3568 {
3569 wallNormal = dsq->game->getWallNormal(position);
3570 bool outOfWaterHit = (!_isUnderWater && !(wallNormal.y < -0.1f));
3571 if (wallNormal.isZero() )
3572 {
3573 debugLog("COULD NOT FIND NORMAL, GOING TO BOUNCE");
3574 return;
3575 }
3576 else
3577 {
3578 if (!dsq->mod.isActive() && !dsq->continuity.getFlag("lockedToWall"))
3579 {
3580
3581 if (!dsq->game->isControlHint()){
3582 dsq->continuity.setFlag("lockedToWall", 1);
3583 dsq->game->setControlHint(dsq->continuity.stringBank.get(13), 1, 0, 0, 6, "", true);
3584 }
3585 }
3586
3587 lockToWallCommon();
3588
3589 if (outOfWaterHit)
3590 lockToWallFallTimer = 0.4;
3591 else
3592 lockToWallFallTimer = -1;
3593
3594 wallPushVec = wallNormal;
3595 wallPushVec *= 2000;
3596 wallPushVec.z = 0;
3597 skeletalSprite.stopAllAnimations();
3598 if (wallPushVec.y < 0 && (fabsf(wallPushVec.y) > fabsf(wallPushVec.x)))
3599 {
3600 skeletalSprite.transitionAnimate("wallLookUp", 0.2, -1);
3601 }
3602 else
3603 {
3604 skeletalSprite.transitionAnimate("wall", 0.2, -1);
3605 }
3606 rotateToVec(wallPushVec, 0.1);
3607
3608 offset.stop();
3609
3610 int tileType = dsq->game->getGrid(t);
3611 Vector offdiff = t.worldVector() - position;
3612 if (!offdiff.isZero())
3613 {
3614 if (tileType & OT_INVISIBLEIN)
3615 {
3616 Vector adjust = offdiff;
3617 adjust.setLength2D(TILE_SIZE/2);
3618 offdiff -= adjust;
3619 }
3620 else
3621 {
3622 Vector adjust = offdiff;
3623 adjust.setLength2D(TILE_SIZE*2);
3624 offdiff -= adjust;
3625 }
3626 }
3627
3628 float spd = vel.getLength2D();
3629 if (spd < 1000)
3630 spd = 1000;
3631
3632 Vector diff = offset - offdiff;
3633 float len = diff.getLength2D();
3634 float time = 0;
3635 if (len > 0)
3636 {
3637 time = len/spd;
3638 }
3639 offset.interpolateTo(offdiff, time);
3640
3641 wallLockTile = t;
3642
3643 vel = Vector(0,0,0);
3644 vel2 = 0;
3645 }
3646 }
3647 else
3648 {
3649 //debugLog("COULD NOT FIND TILE TO GRAB ONTO");
3650 //position = opos;
3651 }
3652 }
3653
applyTripEffects()3654 void Avatar::applyTripEffects()
3655 {
3656 color.interpolateTo(BLIND_COLOR, 0.5);
3657
3658 tripper->alpha.interpolateTo(1, 8);
3659
3660 tripper->color = Vector(1, 1, 1);
3661 tripper->rotation.z = 0;
3662 tripper->rotation.interpolateTo(Vector(0, 0, 360), 10, -1);
3663 tripper->scale = Vector(1.25, 1.25, 1.25);
3664 tripper->scale.interpolateTo(Vector(1.3, 1.3, 1.3), 2, -1, 1, 1);
3665
3666 if (dsq->loops.trip != BBGE_AUDIO_NOCHANNEL)
3667 {
3668 dsq->sound->stopSfx(dsq->loops.trip);
3669 dsq->loops.trip = BBGE_AUDIO_NOCHANNEL;
3670 }
3671
3672 PlaySfx play;
3673 play.name = "TripLoop";
3674 play.loops = -1;
3675 play.fade = SFT_IN;
3676 play.time = 1;
3677 dsq->loops.trip = dsq->sound->playSfx(play);
3678 }
3679
removeTripEffects()3680 void Avatar::removeTripEffects()
3681 {
3682 color.interpolateTo(Vector(1,1,1),0.5);
3683 tripper->alpha.interpolateTo(0, 4);
3684
3685 if (dsq->loops.trip != BBGE_AUDIO_NOCHANNEL)
3686 {
3687 dsq->sound->fadeSfx(dsq->loops.trip, SFT_OUT, 3);
3688 dsq->loops.trip = BBGE_AUDIO_NOCHANNEL;
3689 }
3690 }
3691
applyBlindEffects()3692 void Avatar::applyBlindEffects()
3693 {
3694 // screen black
3695
3696 // character black
3697 color.interpolateTo(BLIND_COLOR, 0.5);
3698 blinder->alpha.interpolateTo(1, 0.5);
3699
3700 blinder->rotation.z = 0;
3701 blinder->rotation.interpolateTo(Vector(0, 0, 360), 10, -1);
3702 blinder->scale = Vector(1.25, 1.25, 1.25);
3703 blinder->scale.interpolateTo(Vector(1.3, 1.3, 1.3), 2, -1, 1, 1);
3704
3705 //dsq->toggleMuffleSound(1);
3706 }
3707
removeBlindEffects()3708 void Avatar::removeBlindEffects()
3709 {
3710 color.interpolateTo(Vector(1,1,1),0.5);
3711 blinder->alpha.interpolateTo(0, 0.5);
3712 //dsq->toggleMuffleSound(0);
3713 }
3714
setBlind(float time)3715 void Avatar::setBlind(float time)
3716 {
3717 if (time == 0)
3718 {
3719 removeBlindEffects();
3720 return;
3721 }
3722
3723 if (!state.blind)
3724 {
3725 applyBlindEffects();
3726 }
3727 state.blind = true;
3728 if (time > state.blindTimer.getValue())
3729 ///*state.blindTimer.getValue() + */
3730 state.blindTimer.start(time);
3731 }
3732
setNearestPullTarget()3733 void Avatar::setNearestPullTarget()
3734 {
3735 const float maxDistSqr = sqr(800);
3736 float smallestDist = maxDistSqr;
3737 Entity *closest = 0;
3738 FOR_ENTITIES(i)
3739 {
3740 Entity *e = *i;
3741 if (e)
3742 {
3743 if ((e->isPullable()) && e->life == 1)
3744 {
3745 float dist = (e->position - position).getSquaredLength2D();
3746 if (dist < smallestDist)
3747 {
3748 closest = e;
3749 smallestDist = dist;
3750 }
3751 }
3752 }
3753 }
3754 if (closest)
3755 {
3756 pullTarget = closest;
3757 pullTarget->startPull();
3758 }
3759 }
3760
createWeb()3761 void Avatar::createWeb()
3762 {
3763 web = new Web;
3764 web->setParentEntity(this);
3765 dsq->game->addRenderObject(web, LR_ENTITIES);
3766 curWebPoint = web->addPoint(dsq->game->avatar->position);
3767 curWebPoint = web->addPoint(dsq->game->avatar->position);
3768 }
3769
clearWeb()3770 void Avatar::clearWeb()
3771 {
3772 if (web)
3773 {
3774 web->setExistence(25);
3775 //web->setLife(1);
3776 //web->setDecayRate(1.0f/30.0f);
3777 //web->fadeAlphaWithLife = 1;
3778
3779 web = 0;
3780 }
3781 }
3782
Avatar()3783 Avatar::Avatar() : Entity(), ActionMapper()
3784 {
3785 canDie = true;
3786
3787 urchinDelay = 0;
3788 jellyDelay = 0;
3789 #ifdef AQ_TEST_QUADTRAIL
3790 quadTrail = new QuadTrail(100, 32);
3791 quadTrail->setTexture("Particles/QuadTrail");
3792 quadTrail->setBlendType(BLEND_ADD);
3793 dsq->game->addRenderObject(quadTrail, LR_PARTICLES);
3794 #endif
3795
3796 curWebPoint = 0;
3797
3798 web = 0;
3799
3800 lastBurstType = BURST_NONE;
3801 dsq->loops.shield = BBGE_AUDIO_NOCHANNEL;
3802 leftHandEmitter = rightHandEmitter = 0;
3803 boneLeftHand = boneRightHand = 0;
3804 canChangeForm = true;
3805 biteTimer = 0;
3806 dsq->loops.charge = BBGE_AUDIO_NOCHANNEL;
3807 //heartbeat = 0;
3808
3809 lastNote = -1;
3810 headTextureTimer = 0;
3811 bone_dualFormGlow = 0;
3812 //dsq->continuity.dualFormCharge = 0;
3813 //dsq->continuity.dualFormMode = Continuity::DUALFORM_NAIJA;
3814 debugLog("Avatar 1");
3815
3816 //registerEntityDied = true;
3817 setv(EV_ENTITYDIED, 1);
3818 wallBurstTimer = 0;
3819 beautyFlip = false;
3820 invincibleBreak = true;
3821 targetUpdateDelay = 0;
3822 biteDelay = 0;
3823 songInterfaceTimer = 0;
3824 quickSongCastDelay = 0;
3825 flourish = false;
3826
3827 blockSinging = false;
3828 singing = false;
3829
3830 spiritEnergyAbsorbed = 0;
3831 joystickMove = false;
3832
3833 debugLog("setCanLeaveWater");
3834
3835 setCanLeaveWater(true);
3836
3837 debugLog("setOverrideRenderPass");
3838
3839 setOverrideRenderPass(1);
3840
3841 debugLog("Done those");
3842 /*
3843 setRenderPass(2);
3844 */
3845 rippleDelay = 0;
3846 ripples = false;
3847 fallGravityTimer = 0;
3848 lastOutOfWaterMaxSpeed = 0;
3849 //chargeGraphic = 0;
3850 shieldPoints = auraTimer = 0;
3851 glow = 0;
3852
3853 fireDelay = 0;
3854 looking = false;
3855 rollDidOne = 0;
3856 lastQuad = lastQuadDir = rollDelay = rolling = 0;
3857 chargeLevelAttained = 0;
3858 activeAura = AURA_NONE;
3859 movingOn = false;
3860 currentMaxSpeed = 0;
3861 pullTarget = 0;
3862 revertTimer = 0;
3863 currentSongIdx = -1;
3864 leaches = 0;
3865
3866
3867 debugLog("Avatar vars->");
3868
3869 damageTime = vars->avatarDamageTime;
3870
3871 activateEntity = 0;
3872 canMove = true;
3873 //scale = Vector(0.5, 0.5);
3874 scale = Vector(0.5, 0.5);
3875
3876 debugLog("Avatar 2");
3877 //scale = Vector(1.0, 1.0);
3878 //setTexture("Naija-sprite2");
3879 renderQuad = false;
3880 name = "Naija";
3881 setEntityType(ET_AVATAR);
3882
3883 targets.resize(1);
3884
3885 entityToActivate = 0;
3886 pathToActivate = 0;
3887 zoomOverriden = false;
3888 canWarp = true;
3889 blinder = 0;
3890 zoomVel = 0;
3891
3892 myZoom = Vector(1,1);
3893 this->pushingOffWallEffect = 0;
3894 lockToWallFallTimer = 0;
3895 swimming = false;
3896 charging = false;
3897 bursting = false;
3898 burst = 1;
3899 burstDelay = 0;
3900 splashDelay = 0;
3901 avatar = this;
3902
3903 swimming = false;
3904
3905 debugLog("Avatar 3");
3906 hair = new Hair();
3907 hair->setTexture("Naija/Cape");
3908 hair->setOverrideRenderPass(1);
3909 hair->setRenderPass(1);
3910 dsq->game->addRenderObject(hair, LR_ENTITIES);
3911
3912 debugLog("Avatar 4");
3913
3914 bindInput();
3915
3916 debugLog("Avatar 5");
3917
3918 blinder = new PauseQuad;
3919 blinder->position = Vector(400, 300, 4.5);
3920 blinder->setTexture("particles/blinder");
3921 //blinder->width = blinder->height = 810;
3922 blinder->autoWidth = AUTO_VIRTUALWIDTH;
3923 blinder->autoHeight = AUTO_VIRTUALWIDTH;
3924 blinder->scale = Vector(1.0125,1.0125);
3925 blinder->followCamera = 1;
3926
3927 blinder->alpha = 0;
3928 dsq->game->addRenderObject(blinder, LR_AFTER_EFFECTS);
3929
3930 tripper = new PauseQuad;
3931 tripper->position = Vector(400,300);
3932 tripper->setTexture("particles/tripper");
3933 //tripper->setWidthHeight(810, 810);
3934 tripper->autoWidth = AUTO_VIRTUALWIDTH;
3935 tripper->autoHeight = AUTO_VIRTUALWIDTH;
3936 tripper->scale = Vector(1.0125, 1.0125);
3937 tripper->followCamera = 1;
3938 tripper->alpha = 0;
3939 dsq->game->addRenderObject(tripper, LR_AFTER_EFFECTS);
3940
3941 songIcons.resize(8);
3942 int i = 0;
3943 for (i = 0; i < songIcons.size(); i++)
3944 {
3945 songIcons[i] = new SongIcon(i);
3946 songIcons[i]->alpha = 0;
3947 songIcons[i]->followCamera = 1;
3948 dsq->game->addRenderObject(songIcons[i], LR_HUD);
3949 }
3950
3951 setSongIconPositions();
3952
3953 fader = new Quad;
3954 fader->position = Vector(400,300);
3955 fader->setTexture("fader");
3956 fader->setWidthHeight(core->getVirtualWidth()+10);
3957 fader->followCamera = 1;
3958 fader->alpha = 0;
3959 dsq->game->addRenderObject(fader, LR_AFTER_EFFECTS);
3960
3961 debugLog("Avatar 6");
3962
3963 targetQuads.resize(targets.size());
3964
3965 for (i = 0; i < targets.size(); i++)
3966 {
3967 targetQuads[i] = new ParticleEffect;
3968 /*
3969 targetQuads[i]->setTexture("missingImage");
3970 targetQuads[i]->alpha = 0;
3971 */
3972 targetQuads[i]->load("EnergyBlastTarget");
3973
3974 // HACK: should have its own layer?
3975 dsq->game->addRenderObject(targetQuads[i], LR_PARTICLES);
3976 }
3977
3978 lightFormGlow = new Quad("Naija/LightFormGlow", 0);
3979 lightFormGlow->alpha = 0;
3980
3981 lightFormGlow->scale.interpolateTo(Vector(5.5, 5.5), 0.4, -1, 1);
3982 //lightFormGlow->positionSnapTo = &position;
3983 dsq->game->addRenderObject(lightFormGlow, LR_ELEMENTS13);
3984
3985 lightFormGlowCone = new Quad("Naija/LightFormGlowCone", 0);
3986 lightFormGlowCone->alpha = 0;
3987 lightFormGlowCone->scale = Vector(1, 6); // 4.5
3988 dsq->game->addRenderObject(lightFormGlowCone, LR_ELEMENTS13);
3989
3990
3991 debugLog("Avatar 7");
3992
3993 addChild(®enEmitter, PM_STATIC);
3994 regenEmitter.load("FoodEffectRegen");
3995
3996 addChild(&speedEmitter, PM_STATIC);
3997 speedEmitter.load("FoodEffectSpeed");
3998
3999 addChild(&defenseEmitter, PM_STATIC);
4000 defenseEmitter.load("FoodEffectDefense");
4001
4002 addChild(&invincibleEmitter, PM_STATIC);
4003 invincibleEmitter.load("FoodEffectInvincible");
4004
4005 addChild(&auraEmitter, PM_STATIC);
4006
4007 addChild(&auraLowEmitter, PM_STATIC);
4008
4009 addChild(&auraHitEmitter, PM_STATIC);
4010 auraHitEmitter.load("AuraShieldHit");
4011
4012 chargingEmitter = new ParticleEffect;
4013 dsq->getTopStateData()->addRenderObject(chargingEmitter, LR_PARTICLES);
4014
4015 chargeEmitter = new ParticleEffect;
4016 dsq->getTopStateData()->addRenderObject(chargeEmitter, LR_PARTICLES_TOP);
4017
4018 leftHandEmitter = new ParticleEffect;
4019 dsq->getTopStateData()->addRenderObject(leftHandEmitter, LR_PARTICLES);
4020
4021 rightHandEmitter = new ParticleEffect;
4022 dsq->getTopStateData()->addRenderObject(rightHandEmitter, LR_PARTICLES);
4023
4024 addChild(&biteLeftEmitter, PM_STATIC);
4025 biteLeftEmitter.load("BiteLeft");
4026
4027 addChild(&biteRightEmitter, PM_STATIC);
4028 biteRightEmitter.load("BiteRight");
4029
4030 addChild(&wakeEmitter, PM_STATIC);
4031 wakeEmitter.load("Wake");
4032
4033 addChild(&swimEmitter, PM_STATIC);
4034 swimEmitter.load("Swim");
4035
4036 addChild(&plungeEmitter, PM_STATIC);
4037 plungeEmitter.load("Plunge");
4038 plungeEmitter.position = Vector(0,-100);
4039
4040 addChild(&spiritBeaconEmitter, PM_STATIC);
4041 spiritBeaconEmitter.load("SpiritBeacon");
4042
4043 addChild(&healEmitter, PM_STATIC);
4044
4045 addChild(&hitEmitter, PM_STATIC);
4046
4047 addChild(&rollLeftEmitter, PM_STATIC);
4048
4049 addChild(&rollRightEmitter, PM_STATIC);
4050
4051 rollRightEmitter.load("RollRight");
4052 rollLeftEmitter.load("RollLeft");
4053
4054 debugLog("Avatar 8");
4055
4056 perform(STATE_IDLE);
4057 skeletalSprite.animate(getIdleAnimName(),-1);
4058
4059 debugLog("Avatar 9");
4060
4061 setDamageTarget(DT_AVATAR_LANCE, false);
4062
4063 //changeForm(FORM_NORMAL, false);
4064
4065 refreshNormalForm();
4066
4067 if(dsq->continuity.form == FORM_FISH)
4068 collideRadius = COLLIDE_RADIUS_FISH;
4069 else
4070 collideRadius = COLLIDE_RADIUS_NORMAL;
4071
4072 // defaults for normal form
4073 _canActivateStuff = true;
4074 _canBurst = true;
4075 _canLockToWall = true;
4076 _canSwimAgainstCurrents = false;
4077 _canCollideWithShots = true;
4078
4079 _collisionAvoidMod = COLLIDE_MOD_NORMAL;
4080 _collisionAvoidRange = COLLIDE_RANGE_NORMAL;
4081
4082 _seeMapMode = SEE_MAP_DEFAULT;
4083
4084 blockBackFlip = false;
4085 elementEffectMult = 1;
4086 }
4087
revert()4088 void Avatar::revert()
4089 {
4090 if (canChangeForm)
4091 {
4092 if (dsq->continuity.form != FORM_NORMAL)
4093 changeForm(FORM_NORMAL);
4094 }
4095 }
4096
onHeal(int type)4097 void Avatar::onHeal(int type)
4098 {
4099 if (type == 1)
4100 {
4101 healEmitter.load("Heal");
4102 healEmitter.start();
4103 }
4104 }
4105
refreshNormalForm()4106 void Avatar::refreshNormalForm()
4107 {
4108 std::string c = dsq->continuity.costume;
4109 if (c.empty())
4110 c = "Naija";
4111 refreshModel("Naija", c);
4112 if(hair)
4113 {
4114 hair->alphaMod = 1.0;
4115 if (!c.empty() && c!="Naija")
4116 {
4117 if(!hair->setTexture("naija/cape-"+c))
4118 hair->alphaMod = 0;
4119 }
4120 else
4121 hair->setTexture("naija/cape");
4122 }
4123 }
4124
refreshModel(std::string file,const std::string & skin,bool forceIdle)4125 void Avatar::refreshModel(std::string file, const std::string &skin, bool forceIdle)
4126 {
4127 stringToLower(file);
4128
4129 bool loadedSkeletal = false;
4130
4131 if (!skeletalSprite.isLoaded() || nocasecmp(skeletalSprite.filenameLoaded, file)!=0)
4132 {
4133 skeletalSprite.loadSkeletal(file);
4134 loadedSkeletal = true;
4135 }
4136 if (!skin.empty())
4137 skeletalSprite.loadSkin(skin);
4138
4139 if (file == "beast")
4140 {
4141 skeletalSprite.scale = Vector(1.25,1.25);
4142 }
4143 else
4144 skeletalSprite.scale = Vector(1,1);
4145
4146 Animation *anim = skeletalSprite.getCurrentAnimation(0);
4147 if (forceIdle || (!anim || loadedSkeletal))
4148 {
4149 idle();
4150 }
4151
4152 if (file == "naija")
4153 {
4154 bone_head = skeletalSprite.getBoneByIdx(1);
4155 boneRightFoot = skeletalSprite.getBoneByName("RightFoot");
4156 boneLeftFoot = skeletalSprite.getBoneByName("LeftFoot");
4157 boneRightArm = skeletalSprite.getBoneByName("RightArm");
4158 boneLeftArm = skeletalSprite.getBoneByName("LeftArm");
4159 boneFish2 = skeletalSprite.getBoneByName("Fish2");
4160 boneFish2->alpha = 0;
4161 bone_dualFormGlow = skeletalSprite.getBoneByName("DualFormGlow");
4162 bone_dualFormGlow->scale = 0;
4163 bone_dualFormGlow->setBlendType(BLEND_ADD);
4164
4165 boneLeftHand = skeletalSprite.getBoneByName("LeftArm");
4166 boneRightHand = skeletalSprite.getBoneByName("RightArm");
4167 }
4168 else
4169 {
4170 bone_dualFormGlow = 0;
4171 bone_head = 0;
4172 boneRightFoot = boneLeftFoot = boneRightArm = boneLeftArm = boneFish2 = skeletalSprite.getBoneByIdx(0);
4173 boneLeftHand = boneRightHand = 0;
4174 }
4175
4176 core->resetTimer();
4177
4178 skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
4179 }
4180
~Avatar()4181 Avatar::~Avatar()
4182 {
4183 songIcons.clear();
4184 }
4185
destroy()4186 void Avatar::destroy()
4187 {
4188 Entity::destroy();
4189
4190 if (dsq->loops.shield != BBGE_AUDIO_NOCHANNEL)
4191 {
4192 core->sound->fadeSfx(dsq->loops.shield, SFT_OUT, 1);
4193 dsq->loops.shield = BBGE_AUDIO_NOCHANNEL;
4194 }
4195 if (dsq->loops.current != BBGE_AUDIO_NOCHANNEL)
4196 {
4197 core->sound->fadeSfx(dsq->loops.current, SFT_OUT, 1);
4198 dsq->loops.current = BBGE_AUDIO_NOCHANNEL;
4199 }
4200
4201 avatar = 0;
4202 }
4203
startBackFlip()4204 void Avatar::startBackFlip()
4205 {
4206 if (boneLock.on) return;
4207 if (riding) return;
4208 if (blockBackFlip) return;
4209
4210 skeletalSprite.getAnimationLayer(ANIMLAYER_OVERRIDE)->transitionAnimate("backflip", 0.2, 0);
4211 vel.x = -vel.x*0.25f;
4212 state.backFlip = true;
4213 }
4214
stopBackFlip()4215 void Avatar::stopBackFlip()
4216 {
4217 if (state.backFlip)
4218 {
4219 //skeletalSprite.getAnimationLayer(ANIMLAYER_OVERRIDE)->stopAnimation();
4220 skeletalSprite.getAnimationLayer(ANIMLAYER_OVERRIDE)->transitionAnimate("backflip2", 0.2, 0);
4221 state.backFlip = false;
4222 }
4223 }
4224
startBurstCommon()4225 void Avatar::startBurstCommon()
4226 {
4227 skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
4228
4229 bittenEntities.clear();
4230 flourish = false;
4231 if (dsq->continuity.form == FORM_BEAST)
4232 {
4233 setHeadTexture("Bite");
4234 }
4235 burstTimer = 0;
4236
4237 setBoneLock(BoneLock());
4238
4239 biteTimer = 0;
4240
4241 if (dsq->continuity.form == FORM_BEAST)
4242 {
4243 if (isfh())
4244 biteRightEmitter.start();
4245 else
4246 biteLeftEmitter.start();
4247 }
4248 }
4249
startBurst()4250 void Avatar::startBurst()
4251 {
4252 if (!riding && canBurst() && (joystickMove || getVectorToCursor().getSquaredLength2D() > sqr(BURST_DISTANCE))
4253 && getState() != STATE_PUSH && (!skeletalSprite.getCurrentAnimation() || (skeletalSprite.getCurrentAnimation()->name != "spin"))
4254 && _isUnderWater && !isActing(ACTION_ROLL))
4255 {
4256 if (!bursting && burst == 1)
4257 {
4258 dsq->rumble(0.2, 0.2, 0.2);
4259 if (dsq->continuity.form != FORM_BEAST)
4260 wakeEmitter.start();
4261 dsq->game->playBurstSound(pushingOffWallEffect>0);
4262 skeletalSprite.animate(getBurstAnimName(), 0);
4263 bursting = true;
4264 burst = 1.0;
4265 ripples = true;
4266 startBurstCommon();
4267
4268 lastBurstType = BURST_NORMAL;
4269 }
4270 else if (bursting && burstTimer > 0.3f)
4271 {
4272 if (!flourish && !state.nearWall)
4273 //&& dsq->continuity.form == FORM_NORMAL)
4274 {
4275 //if (rand()%100 < 50)
4276 if (true)
4277 {
4278 startFlourish();
4279 }
4280 /*
4281 else
4282 {
4283 skeletalSprite.transitionAnimate("flourish2", 0.1, 0, 3);
4284 flourish = true;
4285 if (this->isfh())
4286 rotationOffset = Vector(0,0,-360);
4287 else
4288 rotationOffset = Vector(0,0,360);
4289 rotationOffset.interpolateTo(Vector(0,0,0), 0.8, 0, 0, 1);
4290 }
4291 */
4292 //burst += 0.1;
4293 if (!vel.isZero())
4294 {
4295 Vector add = vel;
4296 add.setLength2D(50);
4297 vel2 += add;
4298 }
4299 }
4300 }
4301 }
4302 }
4303
startWallBurst(bool useCursor)4304 void Avatar::startWallBurst(bool useCursor)
4305 {
4306 //if (!bursting && burst == 1 )
4307 {
4308 Vector goDir;
4309
4310 //goDir = getVectorToCursorFromScreenCentre();
4311 goDir = getVectorToCursor();
4312
4313 if (goDir.isLength2DIn(BURST_DISTANCE))
4314 {
4315 if (!goDir.isLength2DIn(minMouse))
4316 fallOffWall();
4317 return;
4318 }
4319 goDir.normalize2D();
4320
4321 if (_isUnderWater && dsq->continuity.form != FORM_BEAST)
4322 wakeEmitter.start();
4323
4324 offset.interpolateTo(Vector(0,0), 0.05);
4325
4326 dsq->spawnParticleEffect("WallBoost", position+offset, rotation.z);
4327 if (goDir.x != 0 || goDir.y != 0)
4328 {
4329 lastBurstType = BURST_WALL;
4330
4331 dsq->rumble(0.22, 0.22, 0.2);
4332 bittenEntities.clear();
4333 if (useCursor)
4334 {
4335 wallPushVec = (goDir*0.75f + wallNormal*0.25f);
4336 //wallPushVec = goDir;
4337 }
4338 else
4339 {
4340 float v = goDir.dot2D(wallNormal);
4341 if (v <= -0.8f)
4342 wallPushVec = wallNormal;
4343 else
4344 {
4345 wallPushVec = (goDir*0.5f + wallNormal*0.5f);
4346 }
4347 }
4348 //wallPushVec = (goDir*0.9f + wallNormal*0.1f);
4349 wallPushVec.setLength2D(vars->maxWallJumpBurstSpeed);
4350
4351 position.stop();
4352 pushingOffWallEffect = 0.5;
4353 vel = wallPushVec;
4354 this->state.lockedToWall = false;
4355 skeletalSprite.stopAllAnimations();
4356 dsq->game->playBurstSound(pushingOffWallEffect>0);
4357 skeletalSprite.animate(getBurstAnimName(), 0);
4358 bursting = true;
4359 burst = 1.5;
4360 ripples = true;
4361
4362 startBurstCommon();
4363 }
4364 }
4365 }
4366
getKeyDir()4367 Vector Avatar::getKeyDir()
4368 {
4369 Vector dir;
4370 if (isActing(ACTION_SWIMLEFT))
4371 dir += Vector(-1,0);
4372 if (isActing(ACTION_SWIMRIGHT))
4373 dir += Vector(1,0);
4374 if (isActing(ACTION_SWIMUP))
4375 dir += Vector(0,-1);
4376 if (isActing(ACTION_SWIMDOWN))
4377 dir += Vector(0,1);
4378
4379 if (dir.x != 0 && dir.y != 0)
4380 dir/=2;
4381
4382 return dir;
4383 }
4384
getFakeCursorPosition()4385 Vector Avatar::getFakeCursorPosition()
4386 {
4387 if (dsq->inputMode == INPUT_KEYBOARD)
4388 {
4389 return getKeyDir() * 350;
4390 }
4391 if (dsq->inputMode == INPUT_JOYSTICK)
4392 {
4393 const float axisInput = core->joystick.position.getLength2D();
4394 if (axisInput < JOYSTICK_LOW_THRESHOLD)
4395 {
4396 return Vector(0,0,0);
4397 }
4398 else
4399 {
4400 const float axisMult = (maxMouse - minMouse) / (JOYSTICK_HIGH_THRESHOLD - JOYSTICK_LOW_THRESHOLD);
4401 const float distance = minMouse + ((axisInput - JOYSTICK_LOW_THRESHOLD) * axisMult);
4402 return (core->joystick.position * (distance / axisInput));
4403 }
4404 }
4405 return Vector(0,0,0);
4406 }
4407
getVectorToCursorFromScreenCentre()4408 Vector Avatar::getVectorToCursorFromScreenCentre()
4409 {
4410 if (game->cameraOffBounds)
4411 return getVectorToCursor();
4412 else
4413 {
4414 if (dsq->inputMode != INPUT_MOUSE)
4415 return getFakeCursorPosition();
4416 return (core->mouse.position+offset) - Vector(400,300);
4417 }
4418 }
4419
getVectorToCursor(bool trueMouse)4420 Vector Avatar::getVectorToCursor(bool trueMouse)
4421 {
4422 //return getVectorToCursorFromScreenCentre();
4423 Vector pos = dsq->getGameCursorPosition();
4424
4425
4426 if (!trueMouse && dsq->inputMode != INPUT_MOUSE)
4427 return getFakeCursorPosition();
4428
4429 return pos - (position+offset);
4430 //return core->mouse.position - Vector(400,300);
4431 }
4432
action(int id,int state)4433 void Avatar::action(int id, int state)
4434 {
4435 if(dsq->game->isIgnoreAction((AquariaActions)id))
4436 return;
4437
4438 if (id == ACTION_PRIMARY) { if (state) lmbd(); else lmbu(); }
4439 if (id == ACTION_SECONDARY) { if (state) rmbd(); else rmbu(); }
4440
4441 if (id == ACTION_REVERT && !state)
4442 revert();
4443
4444 if (id == ACTION_PRIMARY && state)// !state
4445 {
4446 if (dsq->isMiniMapCursorOkay())
4447 {
4448 if (this->state.lockedToWall)
4449 {
4450 Vector test = getVectorToCursor();
4451 if (test.isLength2DIn(minMouse))
4452 {
4453 fallOffWall();
4454 }
4455 else
4456 {
4457 if (boneLock.entity)
4458 wallNormal = boneLock.wallNormal;
4459
4460 if (isUnderWater())
4461 {
4462 test.normalize2D();
4463 float dott = wallNormal.dot2D(test);
4464 burst = 1;
4465 bursting = false;
4466 // normal is 90 degrees within/on the right side
4467 if (dott > 0)
4468 {
4469 startWallBurst(true);
4470 }
4471 else
4472 {
4473 startWallBurst(false);
4474 }
4475 }
4476 else
4477 {
4478 test.normalize2D();
4479 float dott = wallNormal.dot2D(test);
4480
4481 if (dott > -0.3f)
4482 {
4483 burst = 1;
4484 bursting = false;
4485 startWallBurst(true);
4486 }
4487 else
4488 {
4489 // nothing
4490 }
4491 }
4492 }
4493
4494 }
4495 else
4496 {
4497 startBurst();
4498 // FIXME: This is a quick hack to make sure the burst
4499 // transition animation is played when using joystick or
4500 // keyboard control. The same thing should probably be
4501 // done for wall bursts, but the movement there is fast
4502 // enough that people probably won't notice, so I skipped
4503 // that. Sorry about the ugliness. --achurch
4504 if (dsq->inputMode != INPUT_MOUSE)
4505 skeletalSprite.transitionAnimate("swim", ANIM_TRANSITION, -1);
4506 }
4507 }
4508 }
4509
4510 else if (id >= ACTION_SONGSLOT1 && id < ACTION_SONGSLOTEND)
4511 {
4512 if (canQuickSong())
4513 {
4514 int count = (id - ACTION_SONGSLOT1)+1;
4515
4516 bool cast = false;
4517
4518 if (dsq->continuity.form == FORM_SPIRIT)
4519 {
4520 revert();
4521 cast = true;
4522 }
4523 else
4524 {
4525 switch(count)
4526 {
4527 case 1:
4528 {
4529 if (dsq->continuity.form != FORM_NORMAL)
4530 {
4531 revert();
4532 cast = true;
4533 }
4534 }
4535 break;
4536 case 2:
4537 if (dsq->continuity.form != FORM_ENERGY)
4538 {
4539 dsq->continuity.castSong(SONG_ENERGYFORM);
4540 cast = true;
4541 }
4542 break;
4543 case 3:
4544 if (dsq->continuity.form != FORM_BEAST)
4545 {
4546 dsq->continuity.castSong(SONG_BEASTFORM);
4547 cast = true;
4548 }
4549 break;
4550 case 4:
4551 if (dsq->continuity.form != FORM_NATURE)
4552 {
4553 dsq->continuity.castSong(SONG_NATUREFORM);
4554 cast = true;
4555 }
4556 break;
4557 case 5:
4558 if (dsq->continuity.form != FORM_SUN)
4559 {
4560 dsq->continuity.castSong(SONG_SUNFORM);
4561 cast = true;
4562 }
4563 break;
4564 case 6:
4565 if (dsq->continuity.form != FORM_FISH)
4566 {
4567 dsq->continuity.castSong(SONG_FISHFORM);
4568 cast = true;
4569 }
4570 break;
4571 case 7:
4572 if (dsq->continuity.form != FORM_SPIRIT)
4573 {
4574 dsq->continuity.castSong(SONG_SPIRITFORM);
4575 cast = true;
4576 }
4577 break;
4578 case 8:
4579 if (dsq->continuity.form != FORM_DUAL)
4580 {
4581 dsq->continuity.castSong(SONG_DUALFORM);
4582 cast = true;
4583 }
4584 break;
4585 default:
4586 if (dsq->continuity.form == FORM_NORMAL)
4587 {
4588 switch(count)
4589 {
4590 case 9:
4591 {
4592 dsq->continuity.castSong(SONG_SHIELDAURA);
4593 cast = true;
4594 }
4595 break;
4596 case 10:
4597 {
4598 dsq->continuity.castSong(SONG_BIND);
4599 cast = true;
4600 }
4601 break;
4602 }
4603 }
4604 break;
4605 }
4606 }
4607
4608
4609 if (cast)
4610 {
4611 quickSongCastDelay = QUICK_SONG_CAST_DELAY;
4612 }
4613 }
4614 }
4615 }
4616
doBindSong()4617 void Avatar::doBindSong()
4618 {
4619 if (pullTarget)
4620 {
4621 pullTarget->stopPull();
4622 pullTarget = 0;
4623 core->sound->playSfx("Denied");
4624 }
4625 else
4626 {
4627 dsq->game->bindIngredients();
4628 setNearestPullTarget();
4629 if (!pullTarget)
4630 {
4631 core->sound->playSfx("Denied");
4632 }
4633 else
4634 {
4635 core->sound->playSfx("Bind");
4636 }
4637 }
4638 }
4639
doShieldSong()4640 void Avatar::doShieldSong()
4641 {
4642 core->sound->playSfx("Shield-On");
4643 activateAura(AURA_SHIELD);
4644 }
4645
render()4646 void Avatar::render()
4647 {
4648
4649 if (dsq->continuity.form == FORM_SPIRIT && !skeletalSprite.getParent())
4650 {
4651 skeletalSprite.position = bodyPosition+bodyOffset;
4652 skeletalSprite.color = Vector(0.2, 0.3, 0.6);
4653 skeletalSprite.render();
4654 skeletalSprite.color = Vector(1,1,1);
4655 }
4656
4657 Entity::render();
4658
4659 }
4660
onRender()4661 void Avatar::onRender()
4662 {
4663 Entity::onRender();
4664 }
4665
getBeamWidth()4666 int Avatar::getBeamWidth()
4667 {
4668 const int MAX_BEAM_LEN = 50;
4669 Vector mov = dsq->getGameCursorPosition() - this->position;
4670 mov.setLength2D(1);
4671 TileVector t(position);
4672 Vector tile(t.x, t.y);
4673 int c = 0;
4674 while (c < MAX_BEAM_LEN)
4675 {
4676 bool hit = false;
4677 tile += mov;
4678 TileVector t;
4679 t.x = int(tile.x);
4680 t.y = int(tile.y);
4681 if (dsq->game->isObstructed(t))
4682 {
4683 hit = true;
4684 }
4685
4686 FOR_ENTITIES(i)
4687 {
4688 Entity *e = *i;
4689 if (e != this)
4690 {
4691 TileVector et(e->position);
4692 Vector t1(et.x, et.y);
4693 Vector t2(tile.x, tile.y);
4694 Vector diff = t1-t2;
4695 if (diff.getSquaredLength2D() <= 1)
4696 {
4697 // HACK: replace damage function
4698 //e->damage(1, 0, this);
4699 hit = true;
4700 }
4701 }
4702 }
4703 if (hit)
4704 break;
4705 c++;
4706 }
4707 return c * TILE_SIZE;
4708 }
4709
onEnterState(int action)4710 void Avatar::onEnterState(int action)
4711 {
4712 Entity::onEnterState(action);
4713 if (action == STATE_PUSH)
4714 {
4715 state.lockedToWall = false;
4716 Animation *a = skeletalSprite.getCurrentAnimation();
4717 if (!a || (a && a->name != "pushed"))
4718 skeletalSprite.animate("pushed", 0);
4719 }
4720 }
4721
onExitState(int action)4722 void Avatar::onExitState(int action)
4723 {
4724 Entity::onExitState(action);
4725 if (action == STATE_TRANSFORM)
4726 {
4727 setState(STATE_IDLE);
4728 }
4729 else if (action == STATE_PUSH)
4730 {
4731 skeletalSprite.transitionAnimate("spin", 0.1);
4732 }
4733 }
4734
splash(bool down)4735 void Avatar::splash(bool down)
4736 {
4737 if (splashDelay > 0)
4738 {
4739 lastJumpOutFromWaterBubble = false;
4740 return;
4741 }
4742 splashDelay = SPLASH_INTERVAL;
4743
4744 if (down)
4745 {
4746 sound("splash-into", rolling ? 0.9f : 1.0f);
4747 //dsq->postProcessingFx.disable(FXT_RADIALBLUR);
4748 if (_isUnderWater && core->afterEffectManager)
4749 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter,0.08,0.05,22,0.2f, 1.2));
4750 dsq->rumble(0.7, 0.7, 0.2);
4751 plungeEmitter.start();
4752
4753 core->sound->playSfx("GoUnder");
4754 }
4755 else
4756 {
4757 sound("splash-outof");
4758 /*
4759 dsq->postProcessingFx.enable(FXT_RADIALBLUR);
4760 dsq->postProcessingFx.radialBlurColor = Vector(1,1,1);
4761 dsq->postProcessingFx.intensity = 0.1;
4762 */
4763
4764 core->sound->playSfx("Emerge");
4765 }
4766 // make a splash effect @ current position
4767
4768 // FIXME: This is broken for waterfalls/bubbles (e.g. the waterfalls
4769 // in the Turtle Cave). Not sure how best to fix it in the current
4770 // code. --achurch
4771 Vector hsplash = avatar->getHeadPosition();
4772 hsplash.y = dsq->game->waterLevel.x;
4773 core->createParticleEffect("HeadSplash", hsplash, LR_PARTICLES);
4774
4775 float a = 0;
4776
4777 if (waterBubble || lastJumpOutFromWaterBubble)
4778 {
4779 lastJumpOutFromWaterBubble = false;
4780
4781 if (waterBubble)
4782 {
4783 Vector diff = position - waterBubble->nodes[0].position;
4784 a = MathFunctions::getAngleToVector(diff, 180);
4785 }
4786 else if (lastWaterBubble)
4787 {
4788 Vector diff = position - lastWaterBubble->nodes[0].position;
4789 a = MathFunctions::getAngleToVector(diff, 0);
4790 }
4791 else
4792 a = MathFunctions::getAngleToVector(vel+vel2, 0);
4793 }
4794
4795 dsq->spawnParticleEffect("Splash", position, a);
4796 }
4797
clampVelocity()4798 void Avatar::clampVelocity()
4799 {
4800 /*
4801 std::ostringstream os;
4802 os << "currentMaxSpeed: " << currentMaxSpeed;
4803 debugLog(os.str());
4804 */
4805 float useSpeedMult = dsq->continuity.speedMult;
4806 bool inCurrent = isInCurrent();
4807 bool withCurrent = false;
4808
4809 if (inCurrent)
4810 {
4811 // if vel2 and vel are pointing the same way
4812 if (vel2.dot2D(vel) > 0)
4813 {
4814 withCurrent = true;
4815 }
4816 }
4817
4818 if (!inCurrent || (inCurrent && withCurrent))
4819 {
4820 if (dsq->continuity.form == FORM_FISH)
4821 {
4822 useSpeedMult *= MULT_MAXSPEED_FISHFORM;
4823 }
4824 }
4825 else
4826 {
4827 useSpeedMult = 1;
4828 }
4829
4830 if (dsq->continuity.form == FORM_BEAST)
4831 {
4832 useSpeedMult *= MULT_MAXSPEED_BEASTFORM;
4833 }
4834
4835 if (currentState == STATE_PUSH)
4836 {
4837 currentMaxSpeed = pushMaxSpeed;
4838 }
4839
4840
4841 setMaxSpeed(currentMaxSpeed * useSpeedMult * dsq->continuity.speedMult2);
4842
4843 vel.capLength2D(getMaxSpeed() /* * maxSpeedLerp.x*/);
4844 }
4845
activateAura(AuraType aura)4846 void Avatar::activateAura(AuraType aura)
4847 {
4848 activeAura = aura;
4849 auraTimer = 30;
4850 if (aura == AURA_SHIELD)
4851 {
4852 shieldPoints = maxShieldPoints;
4853 if (auraLowEmitter.isRunning())
4854 auraLowEmitter.stop();
4855 auraEmitter.load("AuraShield");
4856 auraEmitter.start();
4857 if (dsq->loops.shield == BBGE_AUDIO_NOCHANNEL)
4858 {
4859 PlaySfx play;
4860 play.name = "Shield-Loop";
4861 play.fade = SFT_IN;
4862 play.time = 1;
4863 play.loops = -1;
4864 dsq->loops.shield = core->sound->playSfx(play);
4865 }
4866 }
4867 }
4868
updateAura(float dt)4869 void Avatar::updateAura(float dt)
4870 {
4871 if (auraTimer > 0 && dsq->continuity.form != FORM_SPIRIT)
4872 {
4873 switch(activeAura)
4874 {
4875 case AURA_SHIELD:
4876 {
4877 //shieldPosition = position + Vector(cosf(auraTimer*4)*100, sinf(auraTimer*4)*100);
4878 shieldPosition = position;
4879 /*
4880 float a = ((rotation.z)*PI)/180.0f + PI*0.5f;
4881 shieldPosition = position + Vector(cosf(a)*100, sinf(a)*100);
4882 */
4883 for (Shot::Shots::iterator i = Shot::shots.begin(); i != Shot::shots.end(); ++i)
4884 {
4885 Shot *s = *i;
4886 if (s->isActive() && dsq->game->isDamageTypeEnemy(s->getDamageType()) && s->firer != this
4887 && (!s->shotData || !s->shotData->ignoreShield))
4888 {
4889
4890 Vector diff = s->position - shieldPosition;
4891 if (diff.getSquaredLength2D() < sqr(AURA_SHIELD_RADIUS))
4892 {
4893 shieldPoints -= s->getDamage();
4894 auraHitEmitter.start();
4895 dsq->spawnParticleEffect("ReflectShot", s->position);
4896 core->sound->playSfx("Shield-Hit");
4897 s->position += diff;
4898 //s->target = 0;
4899 diff.setLength2D(s->maxSpeed);
4900 s->velocity = diff;
4901 s->reflectFromEntity(this);
4902 }
4903 }
4904 }
4905 }
4906 break;
4907 }
4908
4909 auraTimer -= dt;
4910 if (auraTimer < 5 || shieldPoints < 2)
4911 {
4912 if (auraEmitter.isRunning())
4913 {
4914 auraEmitter.stop();
4915 auraLowEmitter.load("AuraShieldLow");
4916 auraLowEmitter.start();
4917 }
4918 }
4919
4920 if (auraTimer < 0 || shieldPoints < 0)
4921 {
4922 stopAura();
4923 }
4924 }
4925 }
4926
stopAura()4927 void Avatar::stopAura()
4928 {
4929 auraTimer = 0;
4930 activeAura = AURA_NONE;
4931 auraEmitter.stop();
4932 auraLowEmitter.stop();
4933 if (dsq->loops.shield != BBGE_AUDIO_NOCHANNEL)
4934 {
4935 core->sound->fadeSfx(dsq->loops.shield, SFT_OUT, 1);
4936 dsq->loops.shield = BBGE_AUDIO_NOCHANNEL;
4937 }
4938 }
4939
setHeadTexture(const std::string & name,float time)4940 void Avatar::setHeadTexture(const std::string &name, float time)
4941 {
4942 if (!bone_head) return;
4943 if (dsq->continuity.form == FORM_NORMAL /*&& dsq->continuity.costume.empty()*/)
4944 {
4945 if (!name.empty() && (nocasecmp(lastHeadTexture, "singing")==0)) return;
4946
4947 lastHeadTexture = name;
4948 stringToUpper(lastHeadTexture);
4949 std::string t = "Naija/";
4950
4951 if (!dsq->continuity.costume.empty())
4952 {
4953 if (nocasecmp(dsq->continuity.costume, "end")==0)
4954 t += "Naija2";
4955 else
4956 t += dsq->continuity.costume;
4957 }
4958 else if (dsq->continuity.form == FORM_BEAST)
4959 {
4960 t += "Beast";
4961 }
4962 else
4963 {
4964 t += "Naija2";
4965 }
4966 t += "-Head";
4967 if (!name.empty())
4968 {
4969 t += "-" + name;
4970 }
4971 bone_head->setTexture(t);
4972
4973 headTextureTimer = time;
4974 }
4975 }
4976
chargeVisualEffect(const std::string & tex)4977 void Avatar::chargeVisualEffect(const std::string &tex)
4978 {
4979 float time = 0.4;
4980 Quad *chargeEffect = new Quad;
4981 chargeEffect->setBlendType(BLEND_ADD);
4982 chargeEffect->alpha.ensureData();
4983 chargeEffect->alpha.data->path.addPathNode(0, 0);
4984 chargeEffect->alpha.data->path.addPathNode(0.6, 0.1);
4985 chargeEffect->alpha.data->path.addPathNode(0.6, 0.9);
4986 chargeEffect->alpha.data->path.addPathNode(0, 1.0);
4987 chargeEffect->alpha.startPath(time);
4988 chargeEffect->setTexture(tex);
4989 //chargeEffect->positionSnapTo = &this->position;
4990 chargeEffect->position = this->position;
4991 chargeEffect->setPositionSnapTo(&position);
4992 chargeEffect->setLife(1);
4993 chargeEffect->setDecayRate(1.0f/time);
4994 chargeEffect->scale = Vector(0.1, 0.1);
4995 chargeEffect->scale.interpolateTo(Vector(1,1),time);
4996 //chargeEffect->rotation.interpolateTo(Vector(0,0,360), time);
4997 dsq->game->addRenderObject(chargeEffect, LR_PARTICLES);
4998 }
4999
updateFormVisualEffects(float dt)5000 void Avatar::updateFormVisualEffects(float dt)
5001 {
5002 switch (dsq->continuity.form)
5003 {
5004 case FORM_ENERGY:
5005 {
5006 Vector hairDir(96, -96);
5007 if (this->isfh())
5008 {
5009 hairDir.x = -hairDir.x;
5010 }
5011 }
5012 break;
5013 case FORM_SUN:
5014 {
5015 lightFormGlow->position = this->position;
5016 lightFormGlowCone->position = this->position;
5017
5018 float angle=0;
5019 MathFunctions::calculateAngleBetweenVectorsInDegrees(Vector(0,0,0), getVectorToCursorFromScreenCentre(), angle);
5020 angle = 180-(360-angle);
5021
5022 //lightFormGlowCone->rotation.interpolateTo(Vector(0,0,angle), 0.1);
5023 lightFormGlowCone->rotation = Vector(0,0,angle);
5024
5025 static float lfgTimer = 0;
5026 lfgTimer += dt;
5027 if (lfgTimer > 0.5f)
5028 {
5029 //debugLog("lightFormGlow to front");
5030 lightFormGlow->moveToFront();
5031 lightFormGlowCone->moveToFront();
5032 lfgTimer = 0;
5033 }
5034
5035 if (this->isInDarkness())
5036 {
5037 lightFormGlowCone->alphaMod = 1;
5038 lightFormGlow->alphaMod = 1;
5039 }
5040 else
5041 {
5042 lightFormGlow->alphaMod = 0;
5043 lightFormGlowCone->alphaMod = 0;
5044 }
5045 }
5046 break;
5047 case FORM_SPIRIT:
5048 skeletalSprite.update(dt);
5049 skeletalSprite.position = bodyPosition;
5050 break;
5051 }
5052 }
5053
stopBurst()5054 void Avatar::stopBurst()
5055 {
5056 burst = 0;
5057 //burstDelay = BURST_DELAY;
5058 burstDelay = 0;
5059 bursting = false;
5060 wakeEmitter.stop();
5061 ripples = false;
5062
5063 biteLeftEmitter.stop();
5064 biteRightEmitter.stop();
5065 }
5066
getCursorQuadrant()5067 int Avatar::getCursorQuadrant()
5068 {
5069 //Vector diff = getVectorToCursorFromScreenCentre();
5070 Vector diff = getVectorToCursor();
5071 if (diff.isLength2DIn(40))
5072 {
5073 stopRoll();
5074 return -999;
5075 }
5076
5077 if (diff.y < 0)
5078 return diff.x < 0 ? 4 : 1;
5079 else
5080 return diff.x < 0 ? 3 : 2;
5081 }
5082
getQuadrantDirection(int lastQuad,int quad)5083 int Avatar::getQuadrantDirection(int lastQuad, int quad)
5084 {
5085 int diff = quad - lastQuad;
5086 if ((lastQuad==4 && quad == 1))
5087 {
5088 diff = 1;
5089 }
5090 if (lastQuad==1 && quad==4)
5091 {
5092 diff = -1;
5093 }
5094 if (abs(diff) != 1)
5095 diff = 0;
5096 return diff;
5097 }
5098
startRoll(int dir)5099 void Avatar::startRoll(int dir)
5100 {
5101 if (!rolling && !state.backFlip)
5102 {
5103 if (dsq->continuity.form == FORM_ENERGY && dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY1))
5104 {
5105 rollRightEmitter.load("EnergyRollRight");
5106 rollLeftEmitter.load("EnergyRollLeft");
5107 }
5108 else
5109 {
5110 rollRightEmitter.load("RollRight");
5111 rollLeftEmitter.load("RollLeft");
5112 }
5113 }
5114
5115 Animation *a = skeletalSprite.getCurrentAnimation();
5116 if (!a || a->name != getRollAnimName())
5117 {
5118 skeletalSprite.transitionAnimate(getRollAnimName(), 0.2, -1);
5119 }
5120
5121 rollRightEmitter.stop();
5122 rollLeftEmitter.stop();
5123
5124 if (_isUnderWater)
5125 {
5126 if (dir >= 1)
5127 rollRightEmitter.start();
5128
5129 if (dir <= -1)
5130 rollLeftEmitter.start();
5131 }
5132
5133 //dsq->playVisualEffect(VFX_RIPPLE, Vector());
5134 rolling = true;
5135
5136 if (dsq->loops.roll == BBGE_AUDIO_NOCHANNEL && _isUnderWater)
5137 {
5138 PlaySfx play;
5139 play.name = "RollLoop";
5140 play.fade = SFT_IN;
5141 play.time = 1;
5142 play.vol = 1;
5143 play.loops = -1;
5144 dsq->loops.roll = core->sound->playSfx(play);
5145 }
5146 else if (dsq->loops.roll != BBGE_AUDIO_NOCHANNEL && !_isUnderWater)
5147 {
5148 core->sound->fadeSfx(dsq->loops.roll, SFT_OUT, 0.5);
5149 }
5150
5151 rollDir = dir;
5152
5153 if (_isUnderWater && core->afterEffectManager)
5154 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),core->screenCenter,0.08,0.05,22,0.2f, 1.2));
5155
5156
5157 //rollDelay = 0.3;
5158 }
5159
stopRoll()5160 void Avatar::stopRoll()
5161 {
5162 rolling = false;
5163 lastQuadDir = lastQuad = 0;
5164 rollDelay = 0;
5165 rollDidOne = 0;
5166 rollLeftEmitter.stop();
5167 rollRightEmitter.stop();
5168 state.rollTimer = 0;
5169
5170 if (dsq->loops.roll != BBGE_AUDIO_NOCHANNEL)
5171 {
5172 core->sound->fadeSfx(dsq->loops.roll, SFT_OUT, 1);
5173 dsq->loops.roll = BBGE_AUDIO_NOCHANNEL;
5174 }
5175 }
5176
5177
stopWallJump()5178 void Avatar::stopWallJump()
5179 {
5180 wallBurstTimer = 0;
5181 }
5182
updateWallJump(float dt)5183 void Avatar::updateWallJump(float dt)
5184 {
5185 if (wallBurstTimer > 0)
5186 {
5187 wallBurstTimer -= dt;
5188 if (wallBurstTimer < 0)
5189 {
5190 // wall jump failed!
5191 stopWallJump();
5192 }
5193 }
5194 }
5195
updateRoll(float dt)5196 void Avatar::updateRoll(float dt)
5197 {
5198 if (!inputEnabled || dsq->game->isWorldPaused() || riding)
5199 {
5200 if (rolling)
5201 stopRoll();
5202 return;
5203 }
5204 if (state.lockedToWall || isSinging()) return;
5205
5206 if (rollDelay > 0)
5207 {
5208 rollDelay -= dt;
5209 if (rollDelay <= 0)
5210 {
5211 // stop the animation
5212 stopRoll();
5213 }
5214 }
5215
5216 if (!_isUnderWater && isActing(ACTION_ROLL))
5217 {
5218 stopRoll();
5219 }
5220
5221 if (!core->mouse.buttons.left && dsq->inputMode == INPUT_MOUSE && !isActing(ACTION_ROLL))
5222 {
5223 if (rolling)
5224 stopRoll();
5225 return;
5226 }
5227
5228 if (rolling)
5229 {
5230 if (dsq->continuity.form == FORM_ENERGY && dsq->continuity.hasFormUpgrade(FORMUPGRADE_ENERGY1))
5231 {
5232 FOR_ENTITIES(i)
5233 {
5234 Entity *e = *i;
5235 if (e->getEntityType() == ET_ENEMY && (e->position - this->position).isLength2DIn(256) && e->isDamageTarget(DT_AVATAR_ENERGYROLL))
5236 {
5237 DamageData d;
5238 d.damage = dt*15;
5239 d.damageType = DT_AVATAR_ENERGYROLL;
5240 d.attacker = this;
5241 e->damage(d);
5242 }
5243 }
5244 }
5245
5246 state.rollTimer += dt;
5247 if (state.rollTimer > 0.55f)
5248 {
5249 state.rollTimer = 0;
5250 if (dsq->continuity.form == FORM_DUAL)
5251 {
5252 switchDualFormMode();
5253 state.rollTimer = -1;
5254 }
5255 }
5256
5257 // NOTE: does this fix the roll problem?
5258 if (rollDelay <= 0)
5259 stopRoll();
5260 }
5261
5262 if (isActing(ACTION_ROLL))
5263 {
5264 if (_isUnderWater)
5265 {
5266 if (rollDelay < 0.5f)
5267 {
5268 startRoll(isfh()?1:-1);
5269 }
5270 float amt = dt * 1000;
5271 if (isfh())
5272 {
5273 rotation.z += amt;
5274 }
5275 else
5276 {
5277 rotation.z -= amt;
5278 }
5279 rotation.capRotZ360();
5280
5281 rollDelay = 1.0;
5282 }
5283 }
5284 else
5285 {
5286 int quad = getCursorQuadrant();
5287 if (lastQuad != quad)
5288 {
5289 int quadDir = 0;
5290 if (lastQuad != 0)
5291 {
5292 quadDir = getQuadrantDirection(lastQuad, quad);
5293 if (quadDir != 0 && lastQuadDir == quadDir && rollDelay > 0)
5294 {
5295 if (rolling)
5296 {
5297 startRoll(quadDir);
5298 }
5299 else
5300 {
5301 if (rollDidOne==1)
5302 rollDidOne = 2;
5303 else if (rollDidOne == 2)
5304 startRoll(quadDir);
5305 else
5306 rollDidOne = 1;
5307 }
5308 }
5309 }
5310
5311 lastQuadDir = quadDir;
5312
5313 lastQuad = quad;
5314
5315 rollDelay = 0.2;
5316 }
5317 }
5318 }
5319
getStopDistance()5320 int Avatar::getStopDistance()
5321 {
5322 return STOP_DISTANCE;
5323 }
5324
getBurstDistance()5325 int Avatar::getBurstDistance()
5326 {
5327 return BURST_DISTANCE;
5328 }
5329
setWasUnderWater()5330 void Avatar::setWasUnderWater()
5331 {
5332 state.wasUnderWater = isUnderWater();
5333 }
5334
canActivateStuff()5335 bool Avatar::canActivateStuff()
5336 {
5337 return _canActivateStuff;
5338 }
5339
setCanActivateStuff(bool on)5340 void Avatar::setCanActivateStuff(bool on)
5341 {
5342 _canActivateStuff = on;
5343 }
5344
setCollisionAvoidanceData(int range,float mod)5345 void Avatar::setCollisionAvoidanceData(int range, float mod)
5346 {
5347 _collisionAvoidRange = range;
5348 _collisionAvoidMod = mod;
5349 }
5350
canQuickSong()5351 bool Avatar::canQuickSong()
5352 {
5353 return !isSinging() && !isEntityDead() && isInputEnabled() && quickSongCastDelay <= 0;
5354 }
5355
updateJoystick(float dt)5356 void Avatar::updateJoystick(float dt)
5357 {
5358 if (canQuickSong())
5359 {
5360
5361 if (core->joystick.dpadUp)
5362 {
5363 if (dsq->continuity.hasSong(SONG_ENERGYFORM) && dsq->continuity.form != FORM_ENERGY)
5364 {
5365 quickSongCastDelay = QUICK_SONG_CAST_DELAY;
5366 dsq->continuity.castSong(SONG_ENERGYFORM);
5367 }
5368 }
5369 else if (core->joystick.dpadDown && dsq->continuity.hasSong(SONG_BEASTFORM) && dsq->continuity.form != FORM_BEAST)
5370 {
5371 quickSongCastDelay = QUICK_SONG_CAST_DELAY;
5372 dsq->continuity.castSong(SONG_BEASTFORM);
5373 }
5374 else if (core->joystick.dpadLeft && dsq->continuity.hasSong(SONG_SUNFORM) && dsq->continuity.form != FORM_SUN)
5375 {
5376 quickSongCastDelay = QUICK_SONG_CAST_DELAY;
5377 dsq->continuity.castSong(SONG_SUNFORM);
5378 }
5379 else if (core->joystick.dpadRight && dsq->continuity.hasSong(SONG_NATUREFORM) && dsq->continuity.form != FORM_NATURE)
5380 {
5381 quickSongCastDelay = QUICK_SONG_CAST_DELAY;
5382 dsq->continuity.castSong(SONG_NATUREFORM);
5383 }
5384
5385 }
5386 }
5387
applyRidingPosition()5388 void Avatar::applyRidingPosition()
5389 {
5390 if (riding)
5391 {
5392 position = riding->getRidingPosition();
5393 lastPosition = position;
5394 rotation.z = riding->getRidingRotation();
5395
5396 if (riding->getRidingFlip())
5397 {
5398 if (!isfh())
5399 flipHorizontal();
5400 }
5401 else
5402 {
5403 if (isfh())
5404 flipHorizontal();
5405 }
5406 //state.wasUnderWater = _isUnderWater;
5407 }
5408 }
5409
adjustHeadRot()5410 void Avatar::adjustHeadRot()
5411 {
5412 if (bone_head)
5413 {
5414 // 0 to 30 range
5415 if (bone_head->rotation.z > 0)
5416 {
5417 bone_head->internalOffset.x = (bone_head->rotation.z/30.0f)*5;
5418 //bone_head->internalOffset.y = (bone_head->rotation.z/30.0f)*1;
5419 }
5420 // 0 to -10 range
5421 if (bone_head->rotation.z < 0)
5422 {
5423 bone_head->internalOffset.x = (bone_head->rotation.z/(-10.0f))*-4;
5424 bone_head->internalOffset.y = (bone_head->rotation.z/(-10.0f))*-2;
5425 }
5426 }
5427 }
5428
endOfGameState()5429 void Avatar::endOfGameState()
5430 {
5431 state.lookAtEntity = 0;
5432 setInvincible(true);
5433 }
5434
5435 bool didRotationFix = true;
5436
timerEffectStart(Timer * timer,ParticleEffect * effect)5437 void timerEffectStart(Timer *timer, ParticleEffect *effect)
5438 {
5439 if (timer->isActive() && !effect->isRunning())
5440 {
5441 effect->start();
5442 }
5443 else if (!timer->isActive() && effect->isRunning())
5444 {
5445 effect->stop();
5446 }
5447 }
5448
updateFoodParticleEffects()5449 void Avatar::updateFoodParticleEffects()
5450 {
5451 timerEffectStart(&dsq->continuity.speedMultTimer, &speedEmitter);
5452 timerEffectStart(&dsq->continuity.defenseMultTimer, &defenseEmitter);
5453 timerEffectStart(&dsq->continuity.invincibleTimer, &invincibleEmitter);
5454 timerEffectStart(&dsq->continuity.regenTimer, ®enEmitter);
5455 }
5456
updateLookAt(float dt)5457 void Avatar::updateLookAt(float dt)
5458 {
5459 //if (dsq->overlay->alpha != 0) return;
5460 if (dsq->game->isShuttingDownGameState()) return;
5461 if (headTextureTimer > 0)
5462 {
5463 headTextureTimer -= dt;
5464 if (headTextureTimer <= 0)
5465 {
5466 headTextureTimer = 0;
5467 setHeadTexture("");
5468 }
5469 }
5470
5471 if (dsq->continuity.form == FORM_FISH)
5472 {
5473 Bone *b = skeletalSprite.getBoneByIdx(0);
5474 if (b)
5475 b->setAnimated(Bone::ANIM_ALL);
5476 return;
5477 }
5478
5479 const float blinkTime = 5.0;
5480 state.blinkTimer += dt;
5481 if (state.blinkTimer > blinkTime)
5482 {
5483 if (lastHeadTexture.empty())
5484 {
5485 setHeadTexture("blink", 0.1);
5486 if (chance(50))
5487 {
5488 state.blinkTimer = blinkTime-0.2f;
5489 }
5490 else
5491 {
5492 state.blinkTimer = rand()%2;
5493 }
5494 }
5495 else
5496 {
5497 state.blinkTimer -= dt;
5498 }
5499 }
5500
5501 if (bone_head)
5502 {
5503 const float lookAtTime = 0.8;
5504 if (core->mouse.buttons.middle && !state.lockedToWall && isInputEnabled())
5505 {
5506 didRotationFix = false;
5507 bone_head->setAnimated(Bone::ANIM_POS);
5508 bone_head->lookAt(dsq->getGameCursorPosition(), lookAtTime, -10, 30, -90);
5509 adjustHeadRot();
5510 }
5511 else
5512 {
5513 if (state.lookAtEntity && (state.lookAtEntity->isEntityDead() || state.lookAtEntity->isDead() || state.lookAtEntity->isv(EV_LOOKAT,0) || swimming))
5514 {
5515 state.lookAtEntity = 0;
5516 }
5517 // find an object of interest
5518 if (isv(EV_LOOKAT, 1) && state.lookAtEntity && !skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->isAnimating() && !state.lockedToWall && !swimming)
5519 {
5520 didRotationFix = false;
5521 bone_head->setAnimated(Bone::ANIM_POS);
5522 bone_head->lookAt(state.lookAtEntity->getLookAtPoint(), lookAtTime, -10, 30, -90);
5523
5524 if (!((state.lookAtEntity->position - position).isLength2DIn(1000)))
5525 {
5526 state.lookAtEntity = 0;
5527 }
5528 state.updateLookAtTime += dt;
5529 adjustHeadRot();
5530 }
5531 else
5532 {
5533 bone_head->setAnimated(Bone::ANIM_ALL);
5534
5535 if (!didRotationFix && !bone_head->rotationOffset.isInterpolating())
5536 {
5537 float t = 1;
5538 didRotationFix = true;
5539 float oldRot = bone_head->rotation.z;
5540
5541 skeletalSprite.updateBones();
5542
5543 bone_head->rotationOffset.z = oldRot - bone_head->rotation.z;
5544 bone_head->rotationOffset.interpolateTo(Vector(0,0,0), t);
5545 bone_head->internalOffset.interpolateTo(Vector(0,0,0), t);
5546 }
5547
5548 state.updateLookAtTime += dt*4*2;
5549 bone_head->internalOffset.interpolateTo(Vector(0,0), 0.2);
5550 }
5551
5552 if (state.updateLookAtTime > 1.5f)
5553 {
5554 state.lookAtEntity = dsq->game->getNearestEntity(position, 800, this, ET_NOTYPE, DT_NONE, LR_ENTITIES0, LR_ENTITIES2);
5555 if (state.lookAtEntity && state.lookAtEntity->isv(EV_LOOKAT, 1))
5556 {
5557 state.updateLookAtTime = 0;
5558
5559 if (!state.lookAtEntity->naijaReaction.empty())
5560 {
5561 setHeadTexture(state.lookAtEntity->naijaReaction, 1.5);
5562 }
5563 }
5564 else
5565 {
5566 state.lookAtEntity = 0;
5567
5568 }
5569 }
5570 }
5571 }
5572 }
5573
getHeadPosition()5574 Vector Avatar::getHeadPosition()
5575 {
5576 if (bone_head)
5577 return bone_head->getWorldPosition();
5578 return position;
5579 }
5580
5581 bool lastCursorKeyboard = false;
5582
onUpdate(float dt)5583 void Avatar::onUpdate(float dt)
5584 {
5585 BBGE_PROF(Avatar_onUpdate);
5586
5587 looking = 0;
5588
5589 #ifdef AQ_TEST_QUADTRAIL
5590 quadTrail->addPoint(position);
5591 #endif
5592
5593 if (lightFormGlow)
5594 {
5595 if (dsq->continuity.light)
5596 {
5597 lightFormGlow->scale = Vector(6,6) + Vector(4,4)*dsq->continuity.light;
5598 }
5599 else
5600 {
5601 lightFormGlow->scale = Vector(6,6);
5602 }
5603 }
5604
5605 applyRidingPosition();
5606 if (activateEntity)
5607 {
5608 activateEntity->activate();
5609 activateEntity = 0;
5610 }
5611 if (bone_head)
5612 headPosition = bone_head->getWorldPosition();
5613
5614 //vel /= 0;
5615 if (vel.isNan())
5616 {
5617 debugLog("detected velocity NaN");
5618 vel = Vector(0,0);
5619 }
5620
5621 if (fireDelay > 0)
5622 {
5623 fireDelay -= dt;
5624 if (fireDelay < 0)
5625 {
5626 fireDelay = 0;
5627 }
5628 }
5629
5630 if (isInputEnabled())
5631 {
5632 if (web)
5633 {
5634 if (!webBitTimer.isActive())
5635 {
5636 webBitTimer.start(0.5);
5637 }
5638 web->setPoint(curWebPoint, position);
5639
5640 if (webBitTimer.updateCheck(dt))
5641 {
5642 webBitTimer.start(0.5);
5643
5644 curWebPoint = web->addPoint(position);
5645 }
5646 }
5647
5648 if (!dsq->game->isPaused() && isActing(ACTION_LOOK) && !dsq->game->avatar->isSinging() && dsq->game->avatar->isInputEnabled() && !dsq->game->isInGameMenu())
5649 {
5650 looking = 1;
5651 }
5652 else
5653 {
5654 looking = 0;
5655 }
5656 }
5657 else
5658 {
5659 looking = 0;
5660 }
5661
5662
5663 Entity::onUpdate(dt);
5664
5665 if (isEntityDead() && skeletalSprite.getCurrentAnimation()->name != "dead")
5666 {
5667 fallOffWall();
5668 biteLeftEmitter.stop();
5669 biteRightEmitter.stop();
5670 wakeEmitter.stop();
5671 rollLeftEmitter.stop();
5672 rollRightEmitter.stop();
5673 dsq->game->toggleOverrideZoom(false);
5674 if (dsq->continuity.form != FORM_NORMAL)
5675 changeForm(FORM_NORMAL);
5676 setHeadTexture("Pain");
5677 core->globalScale.interpolateTo(Vector(5,5),3);
5678 rotation.interpolateTo(Vector(0,0,0), 0.1);
5679 skeletalSprite.animate("dead");
5680 }
5681 if (isEntityDead())
5682 {
5683 dsq->game->toggleOverrideZoom(false);
5684 }
5685
5686 if (dsq->user.control.targeting)
5687 updateTargets(dt, false);
5688 else
5689 targets.clear();
5690
5691 updateTargetQuads(dt);
5692
5693 updateDualFormGlow(dt);
5694 updateLookAt(dt);
5695
5696 updateFoodParticleEffects();
5697
5698 if (!dsq->game->isPaused())
5699 myZoom.update(dt);
5700
5701 _isUnderWater = isUnderWater();
5702
5703 splashDelay -= dt;
5704 if (splashDelay < 0)
5705 splashDelay = 0;
5706
5707 // JUMPING OUT
5708 if (!_isUnderWater && state.wasUnderWater)
5709 {
5710
5711 // "falling" out, not bursting out
5712 int fallOutSpeed = 200;
5713 /*
5714 if (waterBubble)
5715 fallOutSpeed = 400;
5716 */
5717 //bool waterBubbleRect = (waterBubble && waterBubble->pathShape == PATHSHAPE_RECT);
5718
5719 //&& !waterBubbleRect
5720 if (!riding && (!bursting && vel.isLength2DIn(fallOutSpeed)))
5721 {
5722
5723 if (waterBubble)
5724 {
5725 // prevent from falling out
5726 // if circle, clamp
5727 waterBubble->clampPosition(&position);
5728 vel *= 0.5f;
5729 startBurstCommon();
5730 }
5731 else
5732 {
5733 if (!dsq->game->waterLevel.isInterpolating())
5734 {
5735 if (vel.y < 0)
5736 vel.y = -vel.y*0.5f;
5737 position.y = dsq->game->waterLevel.x + collideRadius;
5738 }
5739 }
5740 }
5741 else
5742 {
5743 if (waterBubble)
5744 lastJumpOutFromWaterBubble = true;
5745 else
5746 lastJumpOutFromWaterBubble = false;
5747
5748 lastWaterBubble = waterBubble;
5749 waterBubble = 0;
5750 BBGE_PROF(Avatar_splashOut);
5751 splash(false);
5752
5753 if (dsq->continuity.form != FORM_FISH)
5754 {
5755 vel *= vars->jumpVelocityMod; // 1.25f;
5756 vel.capLength2D(2000);
5757 currentMaxSpeed *= 2.0f;
5758 }
5759 else
5760 {
5761 vel *= 1.5f;
5762 vel.capLength2D(1500);
5763 currentMaxSpeed *= 1.5f;
5764 }
5765
5766 // total max speed
5767 fallGravityTimer = 0.0;
5768
5769 wakeEmitter.stop();
5770 biteTimer = 0;
5771 //stopBurst();
5772
5773 // if first time
5774 if (!dsq->mod.isActive() && dsq->continuity.getFlag("leftWater")==0 && dsq->game->sceneName.find("veil")!=std::string::npos)
5775 {
5776 setInvincible(true);
5777 setv(EV_NOINPUTNOVEL, 0);
5778
5779 setWasUnderWater();
5780
5781 if (vel.y > -500)
5782 vel.y = -500;
5783 dsq->continuity.setFlag("leftWater", 1);
5784
5785 core->sound->fadeMusic(SFT_OUT, 2);
5786 //("Veil");
5787 dsq->game->avatar->disableInput();
5788 dsq->gameSpeed.interpolateTo(0.1, 0.5);
5789
5790 //dsq->sound->setMusicFader(0.5, 0.5);
5791 core->sound->playSfx("NaijaGasp");
5792 core->main(0.75);
5793
5794
5795
5796 dsq->voiceOnce("Naija_VeilCrossing");
5797 core->main(10*0.1f);
5798
5799 dsq->gameSpeed.interpolateTo(1, 0.2);
5800
5801 dsq->sound->playMusic("Veil", SLT_LOOP, SFT_CROSS, 20);
5802
5803 //dsq->sound->setMusicFader(1.0, 1);
5804
5805 dsq->game->avatar->enableInput();
5806
5807 setv(EV_NOINPUTNOVEL, 1);
5808
5809 setInvincible(false);
5810
5811 //dsq->continuity.setFlag("leftWater", 0);
5812
5813 }
5814
5815
5816 state.outOfWaterTimer = 0;
5817 state.outOfWaterVel = vel;
5818 //startBackFlip();
5819 }
5820
5821 if (currentMaxSpeed > dsq->v.maxOutOfWaterSpeed)
5822 {
5823 currentMaxSpeed = dsq->v.maxOutOfWaterSpeed;
5824 }
5825 if (currentMaxSpeed < 1200)
5826 {
5827 currentMaxSpeed = 1200;
5828 }
5829 }
5830 // JUMPING IN
5831 else if (_isUnderWater && !state.wasUnderWater)
5832 {
5833 // falling in
5834 splash(true);
5835
5836 lastOutOfWaterMaxSpeed = getMaxSpeed();
5837 lastOutOfWaterMaxSpeed *= 0.75f;
5838
5839 if (lastOutOfWaterMaxSpeed > 1000)
5840 lastOutOfWaterMaxSpeed = 1000;
5841
5842 fallGravityTimer = 0.5;
5843 if (rolling)
5844 fallGravityTimer *= 1.5f;
5845 stopBurst();
5846
5847 if (state.backFlip)
5848 {
5849 stopBackFlip();
5850 }
5851
5852 }
5853
5854 state.wasUnderWater = _isUnderWater;
5855
5856 if (!_isUnderWater)
5857 {
5858 state.outOfWaterTimer += dt;
5859 if (state.outOfWaterTimer > 100)
5860 state.outOfWaterTimer = 100;
5861 }
5862
5863
5864 if (!state.backFlip && !_isUnderWater && state.outOfWaterTimer < 0.1f && !riding && !boneLock.on)
5865 {
5866 const int check = 64;
5867 Vector m = getVectorToCursor();
5868 if (state.outOfWaterVel.x < 0 && m.x > check)
5869 {
5870 startBackFlip();
5871 }
5872 if (state.outOfWaterVel.x > 0 && m.x < -check)
5873 {
5874 startBackFlip();
5875 }
5876 }
5877
5878 if (isEntityDead())
5879 {
5880 updateHair(dt);
5881 }
5882
5883 if (isEntityDead()) return;
5884
5885 if (flourishTimer.updateCheck(dt))
5886 {
5887 flourish = 0;
5888 rotationOffset.z = 0;
5889 }
5890
5891 if (isInputEnabled())
5892 stillTimer.update(dt);
5893
5894 if (vel.isZero()) //&& !isSinging())
5895 {
5896 if (!stillTimer.isActive())
5897 {
5898 stillTimer.startStopWatch();
5899 //debugLog("start stillTimer");
5900 }
5901 }
5902 else
5903 {
5904 stillTimer.stop();
5905 }
5906
5907 flourishPowerTimer.updateCheck(dt);
5908
5909 if (isSinging())
5910 {
5911 if (songInterfaceTimer < 1)
5912 songInterfaceTimer += dt;
5913 }
5914 updateJoystick(dt);
5915
5916 if (quickSongCastDelay>0)
5917 {
5918 quickSongCastDelay -= dt;
5919 if (quickSongCastDelay < 0)
5920 quickSongCastDelay = 0;
5921 }
5922 if (ripples && _isUnderWater)
5923 {
5924 rippleDelay -= dt;
5925 if (rippleDelay < 0)
5926 {
5927 if (core->afterEffectManager)
5928 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),position+offset,0.04,0.06,15,0.2f));
5929 rippleDelay = 0.15;
5930 }
5931 }
5932
5933 if (dsq->continuity.tripTimer.isActive())
5934 {
5935 static int tripCount = 0;
5936 tripDelay -= dt;
5937 if (tripDelay < 0)
5938 {
5939 tripDelay = 0.15;
5940 tripCount ++;
5941 if (tripCount > 10)
5942 {
5943 /*
5944 // hacktastic
5945 EMOTE_NAIJAEVILLAUGH = 0
5946 EMOTE_NAIJAGIGGLE = 1
5947 EMOTE_NAIJALAUGH = 2
5948 EMOTE_NAIJASADSIGH = 3
5949 EMOTE_NAIJASIGH = 4
5950 EMOTE_NAIJAWOW = 5
5951 EMOTE_NAIJAUGH = 6
5952 */
5953 float p = dsq->continuity.tripTimer.getPerc();
5954 if (p > 0.6f)
5955 {
5956 if (core->afterEffectManager)
5957 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),position+offset,0.04,0.06,15,0.2f));
5958 }
5959 else
5960 {
5961 if (core->afterEffectManager)
5962 core->afterEffectManager->addEffect(new ShockEffect(Vector(core->width/2, core->height/2),position+offset,0.4,0.6,15,0.2f));
5963 }
5964 if (p > 0.75f){}
5965 else if (p > 0.5f)
5966 {
5967 dsq->shakeCamera(2, 4);
5968 if (chance(80))
5969 {
5970 if (chance(60))
5971 dsq->emote.playSfx(2);
5972 else
5973 dsq->emote.playSfx(0);
5974 }
5975 }
5976 else
5977 {
5978 if (p < 0.2f)
5979 dsq->shakeCamera(10, 4);
5980 else
5981 dsq->shakeCamera(5, 4);
5982 tripper->color.interpolateTo(Vector(1, 0.2, 0.2), 3);
5983 if (chance(75))
5984 dsq->emote.playSfx(6);
5985 }
5986
5987 tripCount = 0;
5988 }
5989 }
5990 }
5991
5992 if (position.isInterpolating())
5993 {
5994 lastPosition = position;
5995 }
5996
5997 updateFormVisualEffects(dt);
5998 updateRoll(dt);
5999 updateWallJump(dt);
6000
6001 if (formAbilityDelay > 0)
6002 {
6003 formAbilityDelay -= dt;
6004 if (formAbilityDelay < 0)
6005 formAbilityDelay = 0;
6006 }
6007 //updateCursor(dt);
6008
6009 if (getState() == STATE_PUSH)
6010 {
6011 /*
6012 if (rotation.z < 0)
6013 rotation.z += 360;
6014 if (rotation.z > 360)
6015 rotation.z -= 360;
6016 */
6017 rotateToVec(vel, 0, -90);
6018 if (vel.x < 0&& !isfh())
6019 flipHorizontal();
6020 else if (vel.x > 0 && isfh())
6021 flipHorizontal();
6022 }
6023
6024 updateAura(dt);
6025
6026 updateSingingInterface(dt);
6027
6028 if (pullTarget)
6029 {
6030 if (pullTarget->life < 1 || !pullTarget->isPullable())
6031 {
6032 pullTarget->stopPull();
6033 pullTarget = 0;
6034 }
6035 }
6036
6037 formTimer += dt;
6038
6039
6040 if (dsq->continuity.form == FORM_SPIRIT)
6041 {
6042 if (useSpiritDistance)
6043 {
6044 if (formTimer > 1)
6045 {
6046 if (!(bodyPosition - position).isLength2DIn(SPIRIT_RANGE))
6047 {
6048 changeForm(FORM_NORMAL);
6049 }
6050 }
6051 }
6052 // here
6053 if (!_isUnderWater)
6054 {
6055 changeForm(FORM_NORMAL);
6056 }
6057 }
6058
6059 // revert stuff
6060 float revertGrace = 0.4f;
6061 static bool revertButtonsAreDown = false;
6062 if (inputEnabled && (dsq->inputMode == INPUT_KEYBOARD || dsq->inputMode == INPUT_MOUSE) && (!pathToActivate && !entityToActivate))
6063 {
6064 if (dsq->continuity.form != FORM_NORMAL && (core->mouse.pure_buttons.left && core->mouse.pure_buttons.right) && getVectorToCursor(true).isLength2DIn(minMouse))
6065 {
6066 if (!revertButtonsAreDown)
6067 {
6068 revertTimer = revertGrace;
6069 revertButtonsAreDown = true;
6070 }
6071 else if (revertButtonsAreDown)
6072 {
6073 if (revertTimer > 0)
6074 {
6075 revertTimer -= dt;
6076 if (revertTimer < 0)
6077 {
6078 revertTimer = 0;
6079 }
6080 }
6081 }
6082 }
6083 //&& !isActing(ACTION_PRIMARY) && !isActing(ACTION_SECONDARY)
6084 else if ((!core->mouse.pure_buttons.left && !core->mouse.pure_buttons.right))
6085 {
6086 if (revertTimer > 0 && getVectorToCursor(true).isLength2DIn(minMouse) && state.spellCharge < revertGrace+0.1f)
6087 {
6088 revert();
6089 //changeForm(FORM_NORMAL);
6090 }
6091 revertButtonsAreDown = false;
6092 revertTimer = 0;
6093 }
6094 }
6095 else
6096 {
6097 revertButtonsAreDown = false;
6098 }
6099
6100 //if (core->getNestedMains() == 1)
6101 {
6102 if (getState() != STATE_TRANSFORM && !dsq->game->isWorldPaused())
6103 {
6104 formAbilityUpdate(dt);
6105 }
6106
6107 if (state.useItemDelay.updateCheck(dt))
6108 {
6109 }
6110
6111 ActionMapper::onUpdate(dt);
6112
6113 if (inputEnabled)
6114 {
6115 if (state.blind)
6116 {
6117 if (state.blindTimer.updateCheck(dt))
6118 {
6119 state.blind = false;
6120 removeBlindEffects();
6121 }
6122 }
6123 }
6124
6125
6126 if (boneLock.entity != 0)
6127 {
6128 /*
6129 std::ostringstream os;
6130 os << "boneLock.wallNormal(" << boneLock.wallNormal.x << ", " << boneLock.wallNormal.y << ")";
6131 debugLog(os.str());
6132 */
6133 if (!_isUnderWater && !(boneLock.wallNormal.y < -0.03f))
6134 {
6135 if (lockToWallFallTimer == -1)
6136 lockToWallFallTimer = 0.4;
6137 }
6138 else
6139 lockToWallFallTimer = -1;
6140 }
6141
6142 if (lockToWallFallTimer > 0)
6143 {
6144 lockToWallFallTimer -= dt;
6145 if (lockToWallFallTimer <= 0)
6146 {
6147 fallOffWall();
6148 }
6149 }
6150
6151 if (state.lockToWallDelay.updateCheck(dt))
6152 {
6153 }
6154
6155 if (pushingOffWallEffect > 0)
6156 {
6157 pushingOffWallEffect -= dt;
6158 if (pushingOffWallEffect <= 0)
6159 {
6160 pushingOffWallEffect = 0;
6161 if (vel.getSquaredLength2D() > sqr(1200))
6162 {
6163 vel.setLength2D(1200);
6164 }
6165 }
6166 }
6167
6168 if (charging)
6169 {
6170 state.spellCharge += dt;
6171 switch (dsq->continuity.form)
6172 {
6173 case FORM_SUN:
6174 {
6175 if (state.spellCharge > 1.5f && chargeLevelAttained <1)
6176 {
6177 chargeLevelAttained = 1.5;
6178 core->sound->playSfx("PowerUp");
6179 chargingEmitter->load("ChargingEnergy2");
6180 }
6181 }
6182 break;
6183 case FORM_DUAL:
6184 {
6185 if (state.spellCharge >= 1.4f && chargeLevelAttained<1)
6186 {
6187 chargeLevelAttained = 1;
6188
6189 core->sound->playSfx("PowerUp");
6190 //debugLog("charge visual effect 2");
6191 chargeEmitter->load("ChargeDualForm");
6192 chargeEmitter->start();
6193
6194 chargingEmitter->load("ChargedDualForm");
6195 chargingEmitter->start();
6196 }
6197 }
6198 break;
6199 case FORM_ENERGY:
6200 {
6201 if (state.spellCharge >= 1.5f && chargeLevelAttained<2)
6202 {
6203 chargeLevelAttained = 2;
6204 core->sound->playSfx("PowerUp");
6205 //debugLog("charge visual effect 2");
6206 chargeEmitter->load("ChargeEnergy");
6207 chargeEmitter->start();
6208
6209
6210 chargingEmitter->load("ChargingEnergy2");
6211 //chargeVisualEffect("particles/energy-charge-2");
6212 }
6213 }
6214 break;
6215 case FORM_NATURE:
6216 {
6217 if (state.spellCharge >= 0.9f && chargeLevelAttained<2)
6218 {
6219 chargeLevelAttained = 2;
6220 core->sound->playSfx("PowerUp");
6221 chargeEmitter->load("ChargeNature2");
6222 chargeEmitter->start();
6223
6224 chargingEmitter->load("ChargingNature2");
6225 chargingEmitter->start();
6226 }
6227 }
6228 break;
6229 }
6230 }
6231 /*
6232 float angle = PI - ((rotation.z/180)*PI);
6233 int height = 25;
6234 */
6235 //hair->hairNodes[0].position = position + Vector(sinf(angle)*height, cosf(angle)*height);
6236
6237
6238 if (biteTimer < biteTimerMax)
6239 {
6240 biteTimer += dt;
6241 }
6242 else
6243 {
6244 biteLeftEmitter.stop();
6245 biteRightEmitter.stop();
6246 biteTimer = biteTimerMax;
6247 }
6248
6249 if (biteTimer > biteTimerBiteRange)
6250 {
6251 biteLeftEmitter.stop();
6252 biteRightEmitter.stop();
6253 }
6254
6255 /*
6256 std::ostringstream os;
6257 os << "biteTimer: " << biteTimer;
6258 debugLog(os.str());
6259 */
6260
6261 if (isInputEnabled())
6262 {
6263 if (dsq->continuity.form == FORM_NORMAL && nocasecmp(dsq->continuity.costume, "urchin") == 0)
6264 {
6265 if (!isEntityDead() && health > 0)
6266 {
6267 urchinDelay -= dt;
6268 if (urchinDelay < 0)
6269 {
6270 urchinDelay = 0.1;
6271
6272 dsq->game->fireShot("urchin", this, 0, position + offset);
6273 }
6274 }
6275 }
6276
6277 if (dsq->continuity.form == FORM_NORMAL && nocasecmp(dsq->continuity.costume, "jelly")==0)
6278 {
6279 if (!isEntityDead() && health > 0)
6280 {
6281 if (health < (maxHealth*JELLYCOSTUME_HEALTHPERC))
6282 {
6283 jellyDelay -= dt;
6284 if (jellyDelay < 0)
6285 {
6286 jellyDelay = JELLYCOSTUME_HEALDELAY;
6287
6288 Vector d;
6289 if (!vel.isZero())
6290 {
6291 d = vel;
6292 d.setLength2D(16);
6293 }
6294
6295 dsq->game->spawnManaBall(position + offset + d, JELLYCOSTUME_HEALAMOUNT);
6296
6297 //Shot *s = dsq->game->fireShot("urchin", this, 0, getWorldPosition());
6298 }
6299 }
6300 }
6301 }
6302 }
6303
6304 if (dsq->continuity.form == FORM_BEAST && bone_head && biteTimer < biteTimerBiteRange && biteTimer > 0)
6305 {
6306 biteDelay -= dt;
6307 if (biteDelay < 0)
6308 {
6309 biteDelay = biteDelayPeriod;
6310
6311 Vector p = bone_head->getWorldPosition();
6312 std::string shot = "Bite";
6313 if (dsq->continuity.biteMult > 1)
6314 {
6315 shot = "SuperBite";
6316 }
6317 dsq->game->fireShot(shot, this, 0, p);
6318 //s->setAimVector(getNormal());
6319 }
6320 }
6321
6322 if (dsq->continuity.form == FORM_FISH && dsq->continuity.fishPoisonTimer.isActive())
6323 {
6324 if (!vel.isLength2DIn(16))
6325 {
6326 static float fishPoison = 0;
6327 fishPoison += dt;
6328 if (fishPoison > 0.2f)
6329 {
6330 fishPoison = 0;
6331 dsq->game->fireShot("FishPoison", this, 0, position);
6332 }
6333 }
6334 }
6335
6336 if (!state.lockedToWall && _isUnderWater && !dsq->game->isWorldPaused() && canMove)
6337 {
6338 if (bursting)
6339 {
6340 //debugLog("bursting~!");
6341 burst -= dt * BURST_USE_RATE;
6342 burstTimer += dt;
6343
6344 /*
6345 std::ostringstream os;
6346 os << "burst: " << burst;
6347 debugLog(os.str());
6348 */
6349 if (burst <= 0)
6350 {
6351 stopBurst();
6352 }
6353 }
6354
6355
6356 }
6357 if (inputEnabled && _isUnderWater)
6358 {
6359 if(bursting)
6360 {
6361 }
6362 else if (burstDelay > 0)
6363 {
6364 burstDelay -= dt;
6365 if (burstDelay <= 0)
6366 burstDelay = 0;
6367 }
6368 else if (burst < 1)
6369 {
6370 burst += BURST_RECOVER_RATE * dt;
6371 if (burst >= 1)
6372 burst = 1;
6373 }
6374 }
6375
6376 bool moved = false;
6377
6378 //check to make sure there's still a wall there, if not fall off
6379 if (state.lockedToWall)
6380 {
6381 rotateToVec(wallPushVec, dt*2);
6382 if (!boneLock.on && !dsq->game->isObstructed(wallLockTile))
6383 {
6384 //debugLog("Dropping from wall");
6385 fallOffWall();
6386 }
6387 }
6388
6389 if (getState() != STATE_PUSH && !state.lockedToWall && inputEnabled && _isUnderWater && canMove)
6390 {
6391 float a = 800*dt;
6392 Vector addVec;
6393
6394 bool isMovingSlow = false;
6395
6396 float len = 0;
6397
6398 if (dsq->isMiniMapCursorOkay() && !isActing(ACTION_ROLL) &&
6399 _isUnderWater && !riding && !boneLock.on &&
6400 (movingOn || ((dsq->inputMode == INPUT_JOYSTICK || dsq->inputMode== INPUT_KEYBOARD) || (core->mouse.buttons.left || bursting))))
6401 {
6402 if (dsq->inputMode == INPUT_MOUSE || !this->singing)
6403 {
6404 addVec = getVectorToCursorFromScreenCentre();//getVectorToCursor();
6405
6406 if (dsq->inputMode == INPUT_MOUSE)
6407 {
6408 static Vector lastAddVec;
6409 if (!isActing(ACTION_PRIMARY) && bursting)
6410 {
6411 addVec = lastAddVec;
6412 }
6413
6414 if (bursting)
6415 {
6416 lastAddVec = addVec;
6417 }
6418 }
6419
6420 if (addVec.isLength2DIn(minMouse))
6421 {
6422 if (dsq->inputMode == INPUT_JOYSTICK)
6423 addVec = Vector(0,0,0);
6424 }
6425
6426 if (!addVec.isLength2DIn(minMouse))
6427 {
6428 //if (core->mouse.buttons.left)
6429 {
6430 len = addVec.getLength2D();
6431 if (len > 100)
6432 addVec.setLength2D(a *2);
6433 else
6434 addVec.setLength2D(a);
6435
6436 addVec *= dsq->continuity.speedMult;
6437
6438 //128
6439 if (len < maxMouse && !bursting)
6440 {
6441 isMovingSlow = true;
6442 }
6443 }
6444 }
6445 else
6446 {
6447 // stop movement
6448 // For joystick/keyboard control, don't stop unless
6449 // the Swim (primary action) button is pressed with
6450 // no movement input. --achurch
6451 if ((dsq->inputMode == INPUT_MOUSE || isActing(ACTION_PRIMARY))
6452 && addVec.isLength2DIn(STOP_DISTANCE))
6453 {
6454 vel *= 0.9f;
6455 if (!rolling)
6456 rotation.interpolateTo(Vector(0,0,0), 0.1);
6457 if (vel.isLength2DIn(50))
6458 {
6459 if (bursting)
6460 {
6461 stopBurst();
6462 }
6463 }
6464 }
6465 addVec = Vector(0,0,0);
6466 }
6467 }
6468 }
6469
6470 if (!rolling && !state.backFlip && !flourish)
6471 {
6472 if (addVec.x > 0)
6473 {
6474 if (!isfh())
6475 flipHorizontal();
6476 }
6477 if (addVec.x < 0)
6478 {
6479 if (isfh())
6480 flipHorizontal();
6481 }
6482 }
6483
6484 // will not get here if not underwater
6485 if (isLockable())
6486 lockToWall();
6487 if ((addVec.x != 0 || addVec.y != 0))
6488 {
6489 currentMaxSpeed=0;
6490 vel += addVec;
6491
6492 if (bursting)
6493 {
6494 Vector add = addVec;
6495 add.setLength2D(BURST_ACCEL*dt);
6496 vel += add;
6497
6498 if (pushingOffWallEffect > 0)
6499 currentMaxSpeed = vars->maxWallJumpBurstSpeed;
6500 else
6501 currentMaxSpeed = vars->maxBurstSpeed;
6502
6503 }
6504 else
6505 {
6506 if (pushingOffWallEffect > 0)
6507 currentMaxSpeed = vars->maxWallJumpSpeed;
6508 else
6509 {
6510 if (isActing(ACTION_SLOW) || isMovingSlow)
6511 {
6512 currentMaxSpeed = vars->maxSlowSwimSpeed;
6513 }
6514 else
6515 currentMaxSpeed = vars->maxSwimSpeed;
6516 }
6517 }
6518
6519 if (leaches > 0)
6520 {
6521 currentMaxSpeed -= leaches*60;
6522 }
6523
6524 if (state.blind)
6525 currentMaxSpeed -= 100;
6526
6527 if (currentMaxSpeed < 0)
6528 currentMaxSpeed = 1;
6529
6530 if (getState() == STATE_TRANSFORM)
6531 rotateToVec(addVec, 0.1, 90);
6532 else
6533 {
6534 if (rolling)
6535 {
6536 // here for roll key?
6537 // seems like this isn't reached
6538 //if (isActing("roll"))
6539 if (isActing(ACTION_ROLL))
6540 {
6541 //debugLog("here");
6542 }
6543 else
6544 {
6545 float t = 0;
6546 if (dsq->inputMode == INPUT_KEYBOARD)
6547 t = 0.1;
6548 rotateToVec(addVec, t);
6549 }
6550 }
6551 else if (bursting && flourish)
6552 {
6553
6554 }
6555 else
6556 {
6557 if (!state.nearWall && !flourish)
6558 rotateToVec(addVec, 0.1);
6559 }
6560 }
6561
6562 moved = true;
6563 if ((!swimming || (swimming && !bursting && skeletalSprite.getCurrentAnimation()->name != "swim")) && !state.lockedToWall)
6564 {
6565 swimming = true;
6566 //Animation *a = skeletalSprite.getCurrentAnimation();
6567
6568 if (getState() == STATE_IDLE && !rolling)
6569 {
6570 skeletalSprite.transitionAnimate("swim", ANIM_TRANSITION, -1);
6571 }
6572 //animate(anim_swim);
6573 }
6574 skeletalSprite.setTimeMultiplier(1);
6575 Animation *anim=skeletalSprite.getCurrentAnimation();
6576 if (!bursting && (anim && anim->name == "swim"))
6577 {
6578 float velLen = vel.getLength2D();
6579 float time = velLen / 1200.0f;
6580 if (velLen > 1200)
6581 time = 1;
6582 //skeletalSprite.setTimeMultiplier(time*3);// 5
6583 //skeletalSprite.setTimeMultiplier(time*3.5f);
6584 //skeletalSprite.setTimeMultiplier(time*4);
6585 //animator.timePeriod = 1.5f*(1.0f-time);
6586 skeletalSprite.setTimeMultiplier(time*4.5f);
6587 }
6588 else
6589 {
6590 if (currentAnim != getBurstAnimName() && skeletalSprite.getCurrentAnimation()->name != getBurstAnimName() && !state.lockedToWall)
6591 {
6592 if (getState() == STATE_IDLE && !rolling)
6593 skeletalSprite.transitionAnimate(getBurstAnimName(), ANIM_TRANSITION);
6594 }
6595 }
6596 }
6597 }
6598
6599 //int currentSwimSpeed = 400;
6600 //if (dsq->continuity.getWorldType() == WT_SPIRIT)
6601 /*
6602 if (dsq->continuity.form == FORM_SPIRIT)
6603 {
6604 currentSwimSpeed *= 0.3f;
6605 }
6606 */
6607 if (!_isUnderWater && !state.lockedToWall)
6608 {
6609 //currentSwimSpeed *= 1.5f;
6610 //float a = *dt;
6611 // base on where the mouse is
6612 /*
6613 Vector addVec;
6614 addVec = getVectorToCursorFromScreenCentre();
6615 addVec.setLength2D(a);
6616 */
6617
6618 // gravity
6619 float fallMod = 1.5;
6620 if (dsq->continuity.form == FORM_SPIRIT)
6621 {
6622 fallMod = 1.0;
6623 }
6624 vel += Vector(0,980)*dt*fallMod;
6625
6626 if (!rolling && !state.backFlip && !flourish)
6627 {
6628 if (vel.x != 0 || vel.y != 0)
6629 rotateToVec(vel, 0.1);
6630
6631 if (vel.x > 0)
6632 {
6633 if (!isfh())
6634 flipHorizontal();
6635 }
6636 if (vel.x < 0)
6637 {
6638 if (isfh())
6639 flipHorizontal();
6640 }
6641 }
6642 if (rolling && !state.backFlip)
6643 {
6644 Vector v = getVectorToCursorFromScreenCentre();
6645 rotateToVec(v, 0.01);
6646 }
6647 if (isLockable())
6648 lockToWall();
6649 }
6650
6651 if (!moved)
6652 {
6653 if (swimming)
6654 {
6655 swimming = false;
6656 if (dsq->continuity.form == FORM_FISH)
6657 rotation.interpolateTo(0, 0.2);
6658 }
6659 // "friction"
6660 //vel += -vel*0.999f*dt;
6661 if (_isUnderWater)
6662 {
6663 /*
6664 std::ostringstream os;
6665 os << "fric(" << vel.x << ", " << vel.y;
6666 debugLog(os.str());
6667 */
6668 if (isInCurrent())
6669 doFriction(dt*5);
6670 else
6671 doFriction(dt);
6672 }
6673 }
6674
6675 }
6676
6677 if (_isUnderWater && isInCurrent())
6678 {
6679 if (dsq->loops.current == BBGE_AUDIO_NOCHANNEL)
6680 {
6681 PlaySfx play;
6682 play.name = "CurrentLoop";
6683 play.vol = 1;
6684 play.time = 1;
6685 play.fade = SFT_IN;
6686 play.loops = -1;
6687 dsq->loops.current = core->sound->playSfx(play);
6688 }
6689 }
6690 else
6691 {
6692 if (dsq->loops.current != BBGE_AUDIO_NOCHANNEL)
6693 {
6694 core->sound->fadeSfx(dsq->loops.current, SFT_OUT, 1);
6695 dsq->loops.current = BBGE_AUDIO_NOCHANNEL;
6696 }
6697 }
6698
6699 if (!swimming && _isUnderWater)
6700 {
6701 if (!inCurrent)
6702 currentMaxSpeed = vars->maxSwimSpeed;
6703
6704 if (!state.lockedToWall && !bursting)
6705 {
6706 if (getState() == STATE_IDLE && inputEnabled)
6707 {
6708 Animation *a = skeletalSprite.getCurrentAnimation(0);
6709 if (a && a->name != getIdleAnimName() && a->name != "pushed" && a->name != "spin" && !rolling)
6710 skeletalSprite.transitionAnimate(getIdleAnimName(), ANIM_TRANSITION, -1);
6711 }
6712 }
6713 }
6714
6715 if (_isUnderWater && fallGravityTimer)
6716 {
6717 fallGravityTimer -= dt;
6718 currentMaxSpeed = lastOutOfWaterMaxSpeed;
6719 if (fallGravityTimer < 0)
6720 fallGravityTimer = 0;
6721 }
6722
6723
6724
6725 clampVelocity();
6726
6727 if (swimming)
6728 {
6729 if (!rolling && !internalOffset.isInterpolating())
6730 {
6731 int spread = 8;
6732 //int rotSpread = 45;
6733 float t = 1;
6734
6735 internalOffset = Vector(-spread, 0);
6736 internalOffset.interpolateTo(Vector(spread, 0), t, -1, 1, 1);
6737
6738 for (int i = 0; i < int((t*0.5f)/0.01f); i++)
6739 {
6740 internalOffset.update(0.01);
6741 }
6742 }
6743
6744 if (dsq->continuity.form != FORM_ENERGY && dsq->continuity.form != FORM_DUAL && dsq->continuity.form != FORM_FISH)
6745 {
6746 if (leaches <= 0 && !bursting && !skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->animating)
6747 {
6748 state.swimTimer += dt;
6749
6750 if (state.swimTimer > 5)
6751 {
6752 state.swimTimer = 0 - rand()%3;
6753 static int lastSwimExtra = -1;
6754 int maxAnim = 4;
6755 int anim = rand()%maxAnim;
6756 if (anim == lastSwimExtra)
6757 anim++;
6758 if (anim >= maxAnim)
6759 anim = 0;
6760 lastSwimExtra = anim;
6761 anim ++;
6762
6763 std::ostringstream os;
6764 os << "swimExtra-" << anim;
6765 skeletalSprite.transitionAnimate(os.str(), 0.5, 0, ANIMLAYER_UPPERBODYIDLE);
6766 }
6767 }
6768 }
6769 }
6770 else
6771 {
6772
6773 }
6774
6775 if (!swimming || rolling)
6776 {
6777 //state.swimTimer = 0;
6778 if (skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->animating)
6779 {
6780 skeletalSprite.getAnimationLayer(ANIMLAYER_UPPERBODYIDLE)->stopAnimation();
6781 }
6782 internalOffset.interpolateTo(Vector(0,0),0.5);
6783 }
6784
6785 checkNearWall();
6786 //if (core->getNestedMains()==1)
6787 {
6788 Vector zoomSurface(0.55, 0.55);
6789 Vector zoomMove(vars->zoomMove, vars->zoomMove), zoomStop(vars->zoomStop, vars->zoomStop), zoomNaija(vars->zoomNaija, vars->zoomNaija);
6790 float cheatLen = getMoveVel().getSquaredLength2D();
6791
6792 if (cheatLen > sqr(250) && _isUnderWater && !state.lockedToWall)
6793 {
6794 if (!swimEmitter.isRunning())
6795 swimEmitter.start();
6796 }
6797 else
6798 {
6799 swimEmitter.stop();
6800 }
6801
6802 Vector targetScale(1,1);
6803 if (zoomOverriden || isEntityDead() || core->globalScale.isInterpolating())
6804 {
6805
6806 }
6807 else
6808 {
6809 if (dsq->game->waterLevel.x > 0 && fabsf(avatar->position.y - dsq->game->waterLevel.x) < 800)
6810 {
6811 float time = 0.5;
6812 if (!myZoom.isInterpolating() || ((!core->globalScale.data || core->globalScale.data->target != zoomSurface) && myZoom.data->timePeriod != time))
6813 {
6814 myZoom.interpolateTo(zoomSurface, time, 0, 0, 1);
6815 }
6816 }
6817 else if (avatar->looking == 2)
6818 {
6819 float time = 1.0;
6820 if (!myZoom.isInterpolating() || ((!core->globalScale.data || core->globalScale.data->target != zoomNaija) && myZoom.data->timePeriod != time))
6821 {
6822 /*
6823 std::ostringstream os;
6824 os << "zooming in on Naija: " << zoomNaija.x;
6825 debugLog(os.str());
6826 */
6827 myZoom.interpolateTo(zoomNaija, time, 0, 0, 1);
6828 }
6829 }
6830 else if ((cheatLen > sqr(250) && cheatLen < sqr(1000)) || attachedTo || avatar->looking==1)
6831 {
6832 float time = 3;
6833 if (avatar->looking)
6834 {
6835 time = 1.0;
6836 }
6837 if (!myZoom.isInterpolating() || ((!core->globalScale.data || core->globalScale.data->target != zoomMove) && myZoom.data->timePeriod != time))
6838 myZoom.interpolateTo(zoomMove, time, 0, 0, 1);
6839 }
6840 else if (cheatLen < sqr(210) && !state.lockedToWall && stillTimer.getValue() > 4 && !isSinging())
6841 {
6842 float time = 10;
6843 if (!myZoom.isInterpolating() || (myZoom.data->target != zoomStop && myZoom.data->timePeriod != time))
6844 myZoom.interpolateTo(zoomStop, time, 0, 0, 1);
6845 }
6846 else if (cheatLen >= sqr(1000))
6847 {
6848 float time = 1.6;
6849 if (!myZoom.isInterpolating() || (myZoom.data->target != zoomMove && myZoom.data->timePeriod != time))
6850 myZoom.interpolateTo(zoomMove, time, 0, 0, 1);
6851 }
6852
6853 if (myZoom.x < game->maxZoom)
6854 {
6855 core->globalScale.x = game->maxZoom;
6856 core->globalScale.y = game->maxZoom;
6857 }
6858 else
6859 {
6860 core->globalScale.x = myZoom.x;
6861 core->globalScale.y = myZoom.y;
6862 }
6863 core->globalScaleChanged();
6864
6865 }
6866
6867 if (!state.lockedToWall && !bursting && _isUnderWater && swimming && !isFollowingPath() && _collisionAvoidRange > 0)
6868 {
6869 doCollisionAvoidance(dt, _collisionAvoidRange, _collisionAvoidMod, 0, 800, OT_HURT);
6870 }
6871
6872 if (!game->isShuttingDownGameState())
6873 {
6874 updateCurrents(dt);
6875 updateVel2(dt);
6876 }
6877 else
6878 {
6879 vel2 = Vector(0,0,0);
6880 }
6881
6882 if (!state.lockedToWall && !isFollowingPath() && !riding)
6883 {
6884 /*collideCheck:*/
6885
6886 // Beware: This code may cause clamping vel to zero if the framerate is very high.
6887 // Starting with zero vel, low difftimes will cause an addVec small enough that this
6888 // check will always trigger, and vel will never get larger than zero.
6889 // Under water and swimming check should hopefully prevent this from happening. -- FG
6890 if (_isUnderWater && !isSwimming() && vel.getLength2D() < sqr(2))
6891 {
6892 vel = Vector(0,0,0);
6893 }
6894 Vector moveVel;
6895 if (!isInputEnabled() && isv(EV_NOINPUTNOVEL, 1))
6896 {
6897 vel2=vel=Vector(0,0);
6898 }
6899 else
6900 {
6901 moveVel = getMoveVel();
6902 }
6903 if (!moveVel.isZero())
6904 {
6905 bool collided = false;
6906
6907 /*
6908 std::ostringstream os;
6909 os << "vel (" << vel.x << ", " << vel.y << ")";
6910 debugLog(os.str());
6911 */
6912 Vector mov = (moveVel * dt);
6913 Vector omov = mov;
6914 mov.capLength2D(TILE_SIZE);
6915 /*
6916 if (mov.getSquaredLength2D() > sqr(TILE_SIZE))
6917 mov.setLength2D(TILE_SIZE);
6918 */
6919 if (omov.getSquaredLength2D() > 0)
6920 {
6921 while (omov.getSquaredLength2D() > 0)
6922 {
6923 if (omov.getSquaredLength2D() < sqr(TILE_SIZE))
6924 {
6925 mov = omov;
6926 omov = Vector(0,0);
6927 }
6928 else
6929 omov -= mov;
6930
6931 lastPosition = position;
6932 Vector newPosition = position + mov;
6933 //Vector testPosition = position + (vel *dt)*2;
6934 position = newPosition;
6935
6936 if (dsq->game->collideCircleWithGrid(position, collideRadius))
6937 {
6938 if (dsq->game->lastCollideTileType == OT_HURT
6939 && isDamageTarget(DT_WALLHURT))
6940 {
6941 DamageData d;
6942 d.damage = 1;
6943 d.damageType = DT_WALLHURT;
6944 damage(d);
6945 vel2 = Vector(0,0,0);
6946 //doCollisionAvoidance(1, 3, 1);
6947 /*
6948 Vector v = dsq->game->getWallNormal(position);
6949 if (!v.isZero())
6950 {
6951 vel += v * 500;
6952 }
6953 */
6954 }
6955 collided = true;
6956
6957 if (currentState == STATE_PUSH)
6958 {
6959 dsq->sound->playSfx("rockhit");
6960 dsq->spawnParticleEffect("rockhit", position);
6961 if (pushDamage)
6962 {
6963 DamageData d;
6964 d.damage = pushDamage;
6965 damage(d);
6966 }
6967 setState(STATE_IDLE);
6968 }
6969
6970 if (!_isUnderWater)
6971 {
6972 /*
6973 doBounce();
6974 position = lastPosition;
6975 */
6976
6977 // this is the out of water bounce...
6978
6979 //debugLog("above water bounce");
6980
6981
6982
6983 Vector n = getWallNormal(TileVector(lastPosition));
6984 n *= vel.getLength2D();
6985
6986 /*
6987 std::ostringstream os;
6988 os << "vel(" << vel.x << ", " << vel.y << ") n(" << n.x << ", " << n.y << ")";
6989 debugLog(os.str());
6990 */
6991
6992 //flopping = true;
6993
6994 vel = -vel;
6995
6996 //vel = (vel*0.5f + n*0.5f);
6997 if (vel.y < 0)
6998 {
6999 //debugLog("Vel less than 0");
7000
7001 }
7002 if (vel.y == 0)
7003 {
7004 //debugLog("Vel was 0");
7005 vel = getWallNormal(TileVector(position));
7006 vel.setLength2D(500);
7007 }
7008
7009 if (vel.isLength2DIn(500))
7010 vel.setLength2D(500);
7011
7012
7013 vel.capLength2D(800);
7014
7015 position = lastPosition;
7016 this->doCollisionAvoidance(1, 4, 0.5, 0, 500);
7017
7018
7019 /*
7020 vel = -vel;
7021 if (vel.y < 0)
7022 vel.y *= 2;
7023 position = lastPosition;
7024 */
7025 }
7026 else
7027 {
7028 position = lastPosition;
7029
7030 // works as long as not buried in wall ... yep. =(
7031 if (dsq->game->isObstructed(TileVector(position)))
7032 {
7033 //vel = -vel*0.5f;
7034 vel = 0;
7035 }
7036 else
7037 {
7038 // && dsq->game->getPercObsInArea(position, 4) < 0.75f
7039 if (!inCurrent)
7040 {
7041 if (bursting)
7042 {
7043 //vel = 0;
7044 }
7045 else
7046 {
7047 if (!_isUnderWater && dsq->continuity.form == FORM_FISH)
7048 {
7049 vel = 0;
7050 }
7051 else
7052 {
7053 // this bounce is what causes naija to get wedged
7054 float len = vel.getLength2D();
7055 Vector n = dsq->game->getWallNormal(position, 5);
7056 if (n.x == 0 && n.y == 0)
7057 {
7058 vel = 0;
7059 }
7060 else
7061 {
7062 //n.setLength2D(len/2);
7063 //vel += n;
7064 n.setLength2D(len);
7065 vel = (n+vel)*0.5f;
7066 //vel = (n + vel)*0.5f;
7067 }
7068 }
7069 }
7070 }
7071 }
7072
7073 }
7074 }
7075
7076
7077 if (!collided && checkWarpAreas()) collided = true;
7078 if (collided) break;
7079
7080 // DO NOT COLLIDE WITH LR_ENTITIES
7081 // ENTITY SCRIPTS WILL HANDLE Entity V. Entity COLLISIONS
7082
7083 }
7084 }
7085 }
7086 }
7087 }
7088
7089 //fuuugly
7090 // you said it!
7091 if (riding || boneLock.on)
7092 {
7093 checkWarpAreas();
7094 }
7095 applyRidingPosition();
7096 updateHair(dt);
7097 chargeEmitter->position = chargingEmitter->position = position + offset;
7098
7099 // particle sets
7100 if (leftHandEmitter && rightHandEmitter && boneLeftHand && boneRightHand)
7101 {
7102 leftHandEmitter->position = boneLeftHand->getWorldCollidePosition(Vector(0, 16));
7103 rightHandEmitter->position = boneRightHand->getWorldCollidePosition(Vector(0,16));
7104 }
7105
7106 if(canCollideWithShots())
7107 dsq->game->handleShotCollisions(this, (activeAura == AURA_SHIELD));
7108
7109
7110 if(!core->particlesPaused && elementEffectMult > 0)
7111 {
7112 ElementUpdateList& elems = dsq->game->elementInteractionList;
7113 for (ElementUpdateList::iterator it = elems.begin(); it != elems.end(); ++it)
7114 {
7115 (*it)->doInteraction(this, elementEffectMult, 16);
7116 }
7117 }
7118 }
7119
7120
checkNearWall()7121 void Avatar::checkNearWall()
7122 {
7123 state.nearWall = false;
7124
7125 if (!inCurrent && bursting && !state.lockedToWall && !vel.isZero() && !riding && _isUnderWater)
7126 {
7127 int checkRange = 11;
7128 Vector v = vel;
7129 v.normalize2D();
7130 TileVector oT(position);
7131 TileVector t=oT,lastT=oT;
7132 bool obs = false;
7133 for (int i = 1; i < checkRange; i++)
7134 {
7135 t.x = oT.x + v.x*i;
7136 t.y = oT.y + v.y*i;
7137 if (dsq->game->isObstructed(t, ~OT_HURT))
7138 {
7139 obs = true;
7140 break;
7141 }
7142 lastT = t;
7143 }
7144 if (obs)
7145 {
7146 Vector n = dsq->game->getWallNormal(t.worldVector());
7147 if (!n.isZero())
7148 {
7149 state.nearWall = true;
7150 float t=0.2;
7151 rotateToVec(n, t, 0);
7152 skeletalSprite.transitionAnimate("wall", t);
7153 }
7154 else
7155 state.nearWall = false;
7156 }
7157 else
7158 {
7159 state.nearWall = false;
7160 }
7161 }
7162 }
7163
onWarp()7164 void Avatar::onWarp()
7165 {
7166 avatar->setv(EV_NOINPUTNOVEL, 0);
7167 closeSingingInterface();
7168 }
7169
checkWarpAreas()7170 bool Avatar::checkWarpAreas()
7171 {
7172 int i = 0;
7173 for (i = 0; i < dsq->game->getNumPaths(); i++)
7174 {
7175 bool warp = false;
7176 Path *p = dsq->game->getPath(i);
7177 if (p && p->active && !p->nodes.empty())
7178 {
7179 PathNode *n = &p->nodes[0];
7180 if (n)
7181 {
7182 Vector backPos;
7183 if (!p->vox.empty())
7184 {
7185 if (p->isCoordinateInside(position))
7186 {
7187 if (p->replayVox == 1)
7188 {
7189 dsq->voice(p->vox);
7190 p->replayVox = 2;
7191 }
7192 else
7193 {
7194 dsq->voiceOnce(p->vox);
7195 }
7196 }
7197 }
7198 if (!p->warpMap.empty() && p->pathType == PATH_WARP)
7199 {
7200 int range = 512;
7201 switch(p->warpType)
7202 {
7203 case 'C':
7204 if ((position - n->position).getSquaredLength2D() < sqr(range))
7205 {
7206 warp = true;
7207 }
7208 break;
7209 default:
7210 {
7211 if (p->isCoordinateInside(position))
7212 {
7213 warp = true;
7214 backPos = p->getBackPos(position);
7215 }
7216 }
7217 break;
7218 }
7219 }
7220 if (warp)
7221 {
7222 if (avatar->canWarp)
7223 dsq->game->warpToSceneFromNode(p);
7224 else
7225 {
7226 avatar->position = backPos;
7227 //avatar->vel = -avatar->vel * 0.5f;
7228 Vector n = p->getEnterNormal();
7229 n.setLength2D(avatar->vel.getLength2D());
7230 avatar->vel += n;
7231 avatar->vel *= 0.5f;
7232 }
7233 return true;
7234 }
7235 if (core->getNestedMains() == 1 && !riding)
7236 {
7237 if (p->warpMap.empty() && !p->warpNode.empty())
7238 {
7239 if (p->isCoordinateInside(position))
7240 {
7241 Path *p2 = dsq->game->getPathByName(p->warpNode);
7242 if (p2)
7243 {
7244 dsq->game->preLocalWarp(p->localWarpType);
7245 dsq->game->avatar->position = p2->getPathNode(0)->position;
7246 dsq->game->postLocalWarp();
7247 //dsq->fade(0, t);
7248 return true;
7249 }
7250
7251 }
7252 }
7253 }
7254 }
7255 }
7256
7257 }
7258 for (i = 0; i < dsq->game->warpAreas.size(); i++)
7259 {
7260 WarpArea *a = &dsq->game->warpAreas[i];
7261 if (a->radius)
7262 {
7263 Vector diff = a->position - this->position;
7264 if (diff.getSquaredLength2D() < sqr(a->radius))
7265 {
7266 if (canWarp)
7267 {
7268 dsq->game->warpToArea(a);
7269 return false;
7270 }
7271 else
7272 {
7273 position = lastPosition;
7274 vel = -diff;
7275 return true;
7276 }
7277 }
7278 }
7279 else
7280 {
7281 if (position.x > a->position.x - a->w && position.x < a->position.x + a->w)
7282 {
7283 if (position.y > a->position.y - a->h && position.y < a->position.y + a->h)
7284 {
7285 if (canWarp)
7286 {
7287 dsq->game->warpToArea(a);
7288 return false;
7289 }
7290 else
7291 {
7292 position = lastPosition;
7293 vel = -vel*1.1f;
7294 }
7295 }
7296 }
7297 }
7298 }
7299 return false;
7300 }
7301