factored out the EFFv2 saving into EFFImporter
[gemrb.git] / gemrb / core / Projectile.cpp
blob576ec5f3d5c35a433aa89c9abf9cab8f162ea5ed
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2006 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "Projectile.h"
23 #include "win32def.h"
25 #include "Audio.h"
26 #include "DisplayMessage.h"
27 #include "Game.h"
28 #include "GameData.h"
29 #include "Interface.h"
30 #include "ProjectileServer.h"
31 #include "Video.h"
33 #include <cmath>
34 #include <cstdlib>
36 //to get gradient color
37 #define PALSIZE 12
39 static const ieByte SixteenToNine[MAX_ORIENT]={0,1,2,3,4,5,6,7,8,7,6,5,4,3,2,1};
40 static const ieByte SixteenToFive[MAX_ORIENT]={0,1,2,3,4,3,2,1,0,1,2,3,4,3,2,1};
42 Projectile::Projectile()
44 autofree = false;
45 Extension = NULL;
46 area = NULL;
47 palette = NULL;
48 Pos.empty();
49 Destination = Pos;
50 Orientation = 0;
51 NewOrientation = 0;
52 path = NULL;
53 step = NULL;
54 timeStartStep = 0;
55 phase = P_UNINITED;
56 effects = NULL;
57 children = NULL;
58 child_size = 0;
59 memset(travel, 0, sizeof(travel)) ;
60 memset(shadow, 0, sizeof(shadow)) ;
61 memset(PaletteRes,0,sizeof(PaletteRes));
62 memset(smokebam, 0, sizeof(smokebam));
63 light = NULL;
64 pathcounter = 0x7fff;
67 Projectile::~Projectile()
69 int i;
71 if (autofree) {
72 free(Extension);
74 delete effects;
76 gamedata->FreePalette(palette, PaletteRes);
77 ClearPath();
79 if (travel_handle) {
80 travel_handle->Stop();
81 travel_handle.release();
84 if (phase != P_UNINITED) {
85 for (i = 0; i < MAX_ORIENT; ++i) {
86 if(travel[i])
87 delete travel[i];
88 if(shadow[i])
89 delete shadow[i];
91 core->GetVideoDriver()->FreeSprite(light);
94 if(children) {
95 for(i=0;i<child_size;i++) {
96 delete children[i];
98 free (children);
102 void Projectile::InitExtension()
104 autofree = false;
105 if (!Extension) {
106 Extension = (ProjectileExtension *) calloc( 1, sizeof(ProjectileExtension));
110 void Projectile::CreateAnimations(Animation **anims, const ieResRef bamres, int Seq)
112 AnimationFactory* af = ( AnimationFactory* )
113 gamedata->GetFactoryResource( bamres,
114 IE_BAM_CLASS_ID, IE_NORMAL );
116 if (!af) {
117 return;
120 int Max = af->GetCycleCount();
121 if (!Max) {
122 return;
125 if((ExtFlags&PEF_CYCLE) && !Seq) {
126 Seq=rand()%Max;
129 //this hack is needed because bioware .pro files are sometimes
130 //reporting bigger face count than possible by the animation
131 if (Aim>Max) Aim=Max;
133 if(ExtFlags&PEF_PILLAR) {
134 CreateCompositeAnimation(anims, af, Seq);
135 } else {
136 CreateOrientedAnimations(anims, af, Seq);
140 //Seq is the first cycle to use in the composite
141 //Aim is the number of cycles
142 void Projectile::CreateCompositeAnimation(Animation **anims, AnimationFactory *af, int Seq)
144 for (int Cycle = 0; Cycle<Aim; Cycle++) {
145 int c = Cycle+Seq;
146 Animation* a = af->GetCycle( c );
147 anims[Cycle] = a;
148 if (!a) continue;
149 //animations are started at a random frame position
150 //Always start from 0, unless set otherwise
151 if (!(ExtFlags&PEF_RANDOM)) {
152 a->SetPos(0);
155 a->gameAnimation = true;
159 //Seq is the cycle to use in case of single orientations
160 //Aim is the number of Orientations
161 void Projectile::CreateOrientedAnimations(Animation **anims, AnimationFactory *af, int Seq)
163 for (int Cycle = 0; Cycle<MAX_ORIENT; Cycle++) {
164 bool mirror = false, mirrorvert = false;
165 int c;
166 switch(Aim) {
167 default:
168 c = Seq;
169 break;
170 case 5:
171 c = SixteenToFive[Cycle];
172 // orientations go counter-clockwise, starting south
173 if (Cycle <= 4) {
174 // bottom-right quadrant
175 mirror = false; mirrorvert = false;
176 } else if (Cycle <= 8) {
177 // top-right quadrant
178 mirror = false; mirrorvert = true;
179 } else if (Cycle < 12) {
180 // top-left quadrant
181 mirror = true; mirrorvert = true;
182 } else {
183 // bottom-left quadrant
184 mirror = true; mirrorvert = false;
186 break;
187 case 9:
188 c = SixteenToNine[Cycle];
189 if (Cycle>8) mirror=true;
190 break;
191 case 16:
192 c=Cycle;
193 break;
195 Animation* a = af->GetCycle( c );
196 anims[Cycle] = a;
197 if (!a) continue;
198 //animations are started at a random frame position
199 //Always start from 0, unless set otherwise
200 if (!(ExtFlags&PEF_RANDOM)) {
201 a->SetPos(0);
204 if (mirror) {
205 a->MirrorAnimation();
207 if (mirrorvert) {
208 a->MirrorAnimationVert();
210 a->gameAnimation = true;
214 //apply gradient colors
215 void Projectile::SetupPalette(Animation *anim[], Palette *&pal, const ieByte *gradients)
217 ieDword Colors[7];
219 for (int i=0;i<7;i++) {
220 Colors[i]=gradients[i];
222 GetPaletteCopy(anim, pal);
223 if (pal) {
224 pal->SetupPaperdollColours(Colors, 0);
228 void Projectile::GetPaletteCopy(Animation *anim[], Palette *&pal)
230 if (pal)
231 return;
232 for (unsigned int i=0;i<MAX_ORIENT;i++) {
233 if (anim[i]) {
234 Sprite2D* spr = anim[i]->GetFrame(0);
235 if (spr) {
236 pal = spr->GetPalette()->Copy();
237 break;
243 void Projectile::SetBlend()
245 GetPaletteCopy(travel, palette);
246 if (!palette)
247 return;
248 if (!palette->alpha) {
249 palette->CreateShadedAlphaChannel();
253 //create another projectile with type-1 (iterate magic missiles and call lightning)
254 void Projectile::CreateIteration()
256 ProjectileServer *server = core->GetProjectileServer();
257 Projectile *pro = server->GetProjectileByIndex(type-1);
258 pro->SetEffectsCopy(effects);
259 pro->SetCaster(Caster);
260 if (FakeTarget) {
261 area->AddProjectile(pro, Pos, FakeTarget, true);
262 } else {
263 area->AddProjectile(pro, Pos, Target, false);
266 // added by fuzzie, to make magic missiles instant, maybe wrong place
267 pro->Setup();
270 void Projectile::GetSmokeAnim()
272 int AvatarsRowNum=CharAnimations::GetAvatarsCount();
274 SmokeAnimID&=0xfff0; //this is a hack, i'm too lazy to figure out the subtypes
276 for(int i=0;i<AvatarsRowNum;i++) {
277 AvatarStruct *as = CharAnimations::GetAvatarStruct(i);
278 if (as->AnimID==SmokeAnimID) {
279 memcpy(smokebam, as->Prefixes, sizeof(ieResRef) );
280 return;
283 //turn off smoke animation if its animation was not found
284 //you might want to issue some warning here
285 TFlags&=PTF_SMOKE;
287 // load animations, start sound
288 void Projectile::Setup()
290 tint.r=128;
291 tint.g=128;
292 tint.b=128;
293 tint.a=255;
295 ieDword time = core->GetGame()->Ticks;
296 timeStartStep = time;
298 if(ExtFlags&PEF_TEXT) {
299 Actor *act = area->GetActorByGlobalID(Caster);
300 if(act) {
301 displaymsg->DisplayStringName(StrRef,0xd7d7be,act,0);
305 if(Shake) {
306 core->timer->SetScreenShake( Shake, Shake, Shake);
309 if(ExtFlags&(PEF_FALLING|PEF_INCOMING) ) {
310 if (ExtFlags&PEF_FALLING) {
311 Pos.x=Destination.x;
312 } else {
313 Pos.x=Destination.x+200;
315 Pos.y=Destination.y-200;
316 NextTarget(Destination);
319 if(ExtFlags&PEF_WALL) {
320 SetupWall();
323 //cone area of effect always disables the travel flag
324 //but also makes the caster immune to the effect
325 if (Extension) {
326 if (Extension->AFlags&PAF_CONE) {
327 Destination=Pos;
328 ExtFlags|=PEF_NO_TRAVEL;
331 //this flag says the first explosion is delayed
332 //(works for delaying triggers too)
333 //getting the explosion count here, so an absent caster won't cut short
334 //on the explosion count
335 if(Extension->AFlags&PAF_DELAY) {
336 extension_delay=Extension->Delay;
337 } else {
338 extension_delay=0;
340 extension_explosioncount=CalculateExplosionCount();
343 //set any static tint
344 if(ExtFlags&PEF_TINT) {
345 Color tmpColor[PALSIZE];
347 core->GetPalette( Gradients[0], PALSIZE, tmpColor );
348 StaticTint(tmpColor[PALSIZE/2]);
351 CreateAnimations(travel, BAMRes1, Seq1);
353 if (TFlags&PTF_SHADOW) {
354 CreateAnimations(shadow, BAMRes2, Seq2);
357 if (TFlags&PTF_SMOKE) {
358 GetSmokeAnim();
361 //there is no travel phase, create the projectile right at the target
362 if (ExtFlags&PEF_NO_TRAVEL) {
363 Pos = Destination;
365 //the travel projectile should linger after explosion
366 if(ExtFlags&PEF_POP) {
367 //the explosion consists of a pop in/hold/pop out of the travel projectile (dimension door)
368 if(travel[0] && shadow[0]) {
369 SetDelay(travel[0]->GetFrameCount()*2+shadow[0]->GetFrameCount() );
370 travel[0]->Flags|=A_ANI_PLAYONCE;
371 shadow[0]->Flags|=A_ANI_PLAYONCE;
373 } else {
374 if(travel[0]) {
375 SetDelay(travel[0]->GetFrameCount() );
380 if (TFlags&PTF_COLOUR) {
381 SetupPalette(travel, palette, Gradients);
382 } else {
383 gamedata->FreePalette(palette, PaletteRes);
384 palette=gamedata->GetPalette(PaletteRes);
387 if (TFlags&PTF_LIGHT) {
388 light = core->GetVideoDriver()->CreateLight(LightX, LightZ);
390 if (TFlags&PTF_BLEND) {
391 SetBlend();
393 phase = P_TRAVEL;
394 travel_handle = core->GetAudioDrv()->Play(SoundRes1, Pos.x, Pos.y, (SFlags & PSF_LOOPING ? GEM_SND_LOOPING : 0));
396 //create more projectiles
397 if(ExtFlags&PEF_ITERATION) {
398 CreateIteration();
402 Actor *Projectile::GetTarget()
404 Actor *target;
406 if (Target) {
407 target = area->GetActorByGlobalID(Target);
408 if (!target) return NULL;
409 Actor *original = area->GetActorByGlobalID(Caster);
410 if (original==target) {
411 effects->SetOwner(target);
412 return target;
414 int res = effects->CheckImmunity ( target );
415 //resisted
416 if (!res) {
417 return NULL;
419 if (res==-1) {
420 Target = original->GetGlobalID();
421 return NULL;
423 effects->SetOwner(original);
424 return target;
426 target = area->GetActorByGlobalID(Caster);
427 if (target) {
428 effects->SetOwner(target);
430 return target;
433 void Projectile::SetDelay(int delay)
435 extension_delay=delay;
436 ExtFlags|=PEF_FREEZE;
439 void Projectile::Payload()
441 Actor *target;
443 if(!effects) {
444 return;
447 if (Target) {
448 target = GetTarget();
449 if (!target && (Target==Caster)) {
450 //projectile rebounced
451 return;
453 } else {
454 //the target will be the original caster
455 //in case of single point area target (dimension door)
456 if (FakeTarget) {
457 target = area->GetActorByGlobalID(FakeTarget);
458 if (!target) {
459 target = core->GetGame()->GetActorByGlobalID(FakeTarget);
461 } else {
462 target = area->GetActorByGlobalID(Caster);
465 Actor *source = area->GetActorByGlobalID(Caster);
466 if (source) {
467 effects->SetOwner(source);
468 } else {
469 effects->SetOwner(target);
472 if (target) {
473 if(ExtFlags&PEF_RGB) {
474 target->SetColorMod(0xff, RGBModifier::ADD, ColorSpeed,
475 RGB >> 8, RGB >> 16, RGB >> 24);
478 effects->AddAllEffects(target, Destination);
480 delete effects;
481 effects = NULL;
484 //control the phase change when the projectile reached its target
485 //possible actions: vanish, hover over point, explode
486 //depends on the area extension
487 //play explosion sound
488 void Projectile::ChangePhase()
490 if (Target) {
491 Actor *target = area->GetActorByGlobalID(Target);
492 if (!target) {
493 phase = P_EXPIRED;
494 return;
497 //reached target, and explodes now
498 if (!Extension) {
499 if (travel_handle) {
500 travel_handle->Stop();
501 travel_handle.release();
503 //there are no-effect projectiles, like missed arrows
504 //Payload can redirect the projectile in case of projectile reflection
505 Payload();
506 //freeze on target, this is recommended only for child projectiles
507 //as the projectile won't go away on its own
508 if(ExtFlags&PEF_FREEZE) {
509 if(extension_delay) {
510 if (extension_delay>0) {
511 extension_delay--;
513 return;
516 if(ExtFlags&PEF_FADE) {
517 TFlags &= ~PTF_TINT; //turn off area tint
518 tint.a--;
519 if(tint.a>0) {
520 return;
524 phase = P_EXPIRED;
525 return;
528 EndTravel();
531 //Call this only if Extension exists!
532 int Projectile::CalculateExplosionCount()
534 int count = 0;
535 Actor *act = area->GetActorByGlobalID(Caster);
536 if(act) {
537 if (Extension->AFlags&PAF_LEV_MAGE) {
538 count = act->GetMageLevel();
540 else if (Extension->AFlags&PAF_LEV_CLERIC) {
541 count = act->GetClericLevel();
545 if (!count) {
546 count = Extension->ExplosionCount;
548 if (!count) {
549 count = 1;
551 return count;
554 void Projectile::EndTravel()
556 if(!Extension) {
557 phase = P_EXPIRED;
558 return;
561 //this flag says that the explosion should occur only when triggered
562 if (Extension->AFlags&PAF_TRIGGER) {
563 phase = P_TRIGGER;
564 return;
565 } else {
566 phase = P_EXPLODING1;
569 if (travel_handle) {
570 travel_handle->Stop();
571 travel_handle.release();
574 //this sound is not played for projectiles waiting for trigger
575 //it is probably also played when a travel projectile ends its mission
576 core->GetAudioDrv()->Play(SoundRes2, Pos.x, Pos.y);
579 void Projectile::AddTrail(ieResRef BAM, const ieByte *pal)
581 ScriptedAnimation *sca=gamedata->GetScriptedAnimation(BAM,0);
582 if (!sca) return;
583 if(pal) {
584 for(int i=0;i<7;i++) {
585 sca->SetPalette(pal[i], 4+i*PALSIZE);
588 sca->SetOrientation(Orientation);
589 sca->PlayOnce();
590 sca->SetBlend();
591 sca->XPos += Pos.x;
592 sca->YPos += Pos.y;
593 area->AddVVCell(sca);
596 void Projectile::DoStep(unsigned int walk_speed)
598 if(pathcounter) {
599 pathcounter--;
600 } else {
601 ClearPath();
604 if (!path) {
605 ChangePhase();
606 return;
609 if (Pos==Destination) {
610 ClearPath();
611 ChangePhase();
612 return;
615 //don't bug out on 0 smoke frequency like the original IE
616 if ((TFlags&PTF_SMOKE) && SmokeSpeed) {
617 if(!(pathcounter%SmokeSpeed)) {
618 AddTrail(smokebam, SmokeGrad);
622 for(int i=0;i<3;i++) {
623 if(TrailSpeed[i] && !(pathcounter%TrailSpeed[i])) {
624 AddTrail(TrailBAM[i], 0);
628 if (ExtFlags&PEF_LINE) {
629 if(Extension) {
630 //transform into an explosive line
631 EndTravel();
632 } else {
633 if(!(ExtFlags&PEF_FREEZE) && travel[0]) {
634 //switch to 'fading' phase
635 //SetDelay(travel[0]->GetFrameCount());
636 SetDelay(100);
638 ChangePhase();
640 //don't change position
641 return;
644 //path won't be calculated if speed==0
645 walk_speed=1500/walk_speed;
646 ieDword time = core->GetGame()->Ticks;
647 if (!step) {
648 step = path;
650 while (step->Next && (( time - timeStartStep ) >= walk_speed)) {
651 step = step->Next;
652 if (!walk_speed) {
653 timeStartStep = time;
654 break;
656 timeStartStep = timeStartStep + walk_speed;
659 SetOrientation (step->orient, false);
661 Pos.x=step->x;
662 Pos.y=step->y;
663 if (travel_handle) {
664 travel_handle->SetPos(Pos.x, Pos.y);
666 if (!step->Next) {
667 ClearPath();
668 NewOrientation = Orientation;
669 ChangePhase();
670 return;
672 if (!walk_speed) {
673 return;
675 if (step->Next->x > step->x)
676 Pos.x += ( unsigned short )
677 ( ( step->Next->x - Pos.x ) * ( time - timeStartStep ) / walk_speed );
678 else
679 Pos.x -= ( unsigned short )
680 ( ( Pos.x - step->Next->x ) * ( time - timeStartStep ) / walk_speed );
681 if (step->Next->y > step->y)
682 Pos.y += ( unsigned short )
683 ( ( step->Next->y - Pos.y ) * ( time - timeStartStep ) / walk_speed );
684 else
685 Pos.y -= ( unsigned short )
686 ( ( Pos.y - step->Next->y ) * ( time - timeStartStep ) / walk_speed );
689 void Projectile::SetCaster(ieDword caster)
691 Caster=caster;
694 ieDword Projectile::GetCaster() const
696 return Caster;
699 void Projectile::NextTarget(const Point &p)
701 ClearPath();
702 Destination = p;
703 //call this with destination
704 if (path) {
705 return;
707 if (!Speed) {
708 Pos = Destination;
709 return;
711 NewOrientation = Orientation = GetOrient(Destination, Pos);
713 //this hack ensures that the projectile will go away after its time
714 //by the time it reaches this part, it was already expired, so Target
715 //needs to be cleared.
716 if(ExtFlags&PEF_NO_TRAVEL) {
717 Target = 0;
718 Destination = Pos;
719 return;
721 path = area->GetLine( Pos, Destination, Speed, Orientation, GL_PASS );
724 void Projectile::SetTarget(const Point &p)
726 Target = 0;
727 NextTarget(p);
730 void Projectile::SetTarget(ieDword tar, bool fake)
732 Actor *target = NULL;
734 if (fake) {
735 Target = 0;
736 FakeTarget = tar;
737 return;
738 } else {
739 Target = tar;
740 target = area->GetActorByGlobalID(tar);
743 if (!target) {
744 phase = P_EXPIRED;
745 return;
747 //replan the path in case the target moved
748 if(target->Pos!=Destination) {
749 NextTarget(target->Pos);
750 return;
753 //replan the path in case the source moved (only for line projectiles)
754 if(ExtFlags&PEF_LINE) {
755 Actor *c = area->GetActorByGlobalID(Caster);
756 if(c && c->Pos!=Pos) {
757 Pos=c->Pos;
758 NextTarget(target->Pos);
763 void Projectile::MoveTo(Map *map, const Point &Des)
765 area = map;
766 Origin = Des;
767 Pos = Des;
768 Destination = Des;
771 void Projectile::ClearPath()
773 PathNode* thisNode = path;
774 while (thisNode) {
775 PathNode* nextNode = thisNode->Next;
776 delete( thisNode );
777 thisNode = nextNode;
779 path = NULL;
780 step = NULL;
783 int Projectile::CalculateTargetFlag()
785 //if there are any, then change phase to exploding
786 int flags = GA_NO_DEAD;
788 //projectiles don't affect dead/inanimate normally
789 if (Extension->AFlags&PAF_INANIMATE) {
790 flags&=~GA_NO_DEAD;
793 //affect only enemies or allies
794 switch (Extension->AFlags&PAF_TARGET) {
795 case PAF_ENEMY:
796 flags|=GA_NO_NEUTRAL|GA_NO_ALLY;
797 break;
798 case PAF_PARTY: //this doesn't exist in IE
799 flags|=GA_NO_ENEMY;
800 break;
801 case PAF_TARGET:
802 flags|=GA_NO_NEUTRAL|GA_NO_ENEMY;
803 break;
804 default:
805 return flags;
808 Actor *caster = area->GetActorByGlobalID(Caster);
809 if (caster && ((Actor *) caster)->GetStat(IE_EA)<EA_GOODCUTOFF) {
810 return flags;
813 return flags^(GA_NO_ALLY|GA_NO_ENEMY);
816 //get actors covered in area of trigger radius
817 void Projectile::CheckTrigger(unsigned int radius)
819 if (phase == P_TRIGGER) {
820 //special trigger flag, explode only if the trigger animation has
821 //passed a hardcoded sequence number
822 if (Extension->AFlags&PAF_TRIGGER_D) {
823 if (travel[Orientation]) {
824 int anim = travel[Orientation]->GetCurrentFrame();
825 if (anim<30)
826 return;
830 if (area->GetActorInRadius(Pos, CalculateTargetFlag(), radius)) {
831 if (phase == P_TRIGGER) {
832 phase = P_EXPLODING1;
833 extension_delay = Extension->Delay;
835 } else if (phase == P_EXPLODING1) {
836 //the explosion is revoked
837 if (Extension->AFlags&PAF_SYNC) {
838 phase = P_TRIGGER;
843 void Projectile::SetEffectsCopy(EffectQueue *eq)
845 if(effects) delete effects;
846 if(!eq) {
847 effects=NULL;
848 return;
850 effects = eq->CopySelf();
853 void Projectile::LineTarget()
855 if(!effects) {
856 return;
859 Actor *original = area->GetActorByGlobalID(Caster);
860 Actor *prev = NULL;
861 PathNode *iter = path;
862 while(iter) {
863 Point pos(iter->x,iter->y);
864 Actor *target = area->GetActorInRadius(pos, CalculateTargetFlag(), 1);
865 if (target && target->GetGlobalID()!=Caster && prev!=target) {
866 prev = target;
867 int res = effects->CheckImmunity ( target );
868 if (res>0) {
869 EffectQueue *eff = effects->CopySelf();
870 eff->SetOwner(original);
871 if(ExtFlags&PEF_RGB) {
872 target->SetColorMod(0xff, RGBModifier::ADD, ColorSpeed,
873 RGB >> 8, RGB >> 16, RGB >> 24);
876 eff->AddAllEffects(target, target->Pos);
879 iter = iter->Next;
883 //secondary projectiles target all in the explosion radius
884 void Projectile::SecondaryTarget()
886 //fail will become true if the projectile utterly failed to find a target
887 bool fail= !!(Extension->APFlags&APF_SPELLFAIL);
888 int mindeg = 0;
889 int maxdeg = 0;
891 //the AOE (area of effect) is cone shaped
892 if (Extension->AFlags&PAF_CONE) {
893 mindeg=(Orientation*45-Extension->ConeWidth)/2;
894 maxdeg=mindeg+Extension->ConeWidth;
897 ProjectileServer *server = core->GetProjectileServer();
898 int radius = Extension->ExplosionRadius;
899 Actor **actors = area->GetAllActorsInRadius(Pos, CalculateTargetFlag(), radius);
900 Actor **poi=actors;
902 while(*poi) {
903 ieDword Target = (*poi)->GetGlobalID();
905 //this flag is actually about ignoring the caster (who is at the center)
906 if ((SFlags & PSF_IGNORE_CENTER) && (Caster==Target)) {
907 poi++;
908 continue;
911 if (Extension->AFlags&PAF_CONE) {
912 //cone never affects the caster
913 if(Caster==Target) {
914 poi++;
915 continue;
917 double xdiff = (*poi)->Pos.x-Pos.x;
918 double ydiff = Pos.y-(*poi)->Pos.y;
919 int deg;
921 //fixme: a dragon will definitely be easier to hit than a mouse
922 //nothing checks on the personal space of the possible target
924 //unsigned int dist = (unsigned int) sqrt(xdiff*xdiff+ydiff*ydiff);
925 //int width = (*poi)->GetAnims()->GetCircleSize();
927 if (ydiff) {
928 deg = (int) (atan(xdiff/ydiff)*180/M_PI);
929 if(ydiff>0) deg+=180;
930 } else {
931 if (xdiff<0) deg=90;
932 else deg = 270;
935 //not in the right sector of circle
936 if (mindeg>deg || maxdeg<deg) {
937 poi++;
938 continue;
941 Projectile *pro = server->GetProjectileByIndex(Extension->ExplProjIdx);
942 pro->SetEffectsCopy(effects);
943 pro->SetCaster(Caster);
944 //TODO:actually some of the splash projectiles are a good example of faketarget
945 //projectiles (that don't follow the target, but still hit)
946 area->AddProjectile(pro, Pos, Target, false);
947 poi++;
948 fail=false;
950 //we already got one target affected in the AOE, this flag says
951 //that was enough
952 if(Extension->AFlags&PAF_AFFECT_ONE) {
953 break;
956 free(actors);
958 //In case of utter failure, apply a spell of the same name on the caster
959 //this feature is used by SCHARGE, PRTL_OP and PRTL_CL in the HoW pack
960 if(fail) {
961 Actor *actor = area->GetActorByGlobalID(Caster);
962 if (actor) {
963 //name is the projectile's name
964 //for simplicity, we apply a spell of the same name
965 core->ApplySpell(name, actor, actor, 0);
970 int Projectile::Update()
972 //if reached target explode
973 //if target doesn't exist expire
974 if (phase == P_EXPIRED) {
975 return 0;
977 if (phase == P_UNINITED) {
978 Setup();
981 int pause = core->IsFreezed();
982 if (pause) {
983 return 1;
985 //recreate path if target has moved
986 if(Target) {
987 SetTarget(Target, false);
990 if (phase == P_TRAVEL) {
991 DoStep(Speed);
993 return 1;
996 void Projectile::Draw(const Region &screen)
998 switch (phase) {
999 case P_UNINITED:
1000 return;
1001 case P_TRIGGER: case P_EXPLODING1:case P_EXPLODING2:
1002 //This extension flag is to enable the travel projectile at
1003 //trigger/explosion time
1004 if (Extension->AFlags&PAF_VISIBLE) {
1005 DrawTravel(screen);
1007 CheckTrigger(Extension->TriggerRadius);
1008 if (phase == P_EXPLODING1 || phase == P_EXPLODING2) {
1009 DrawExplosion(screen);
1011 break;
1012 case P_TRAVEL:
1013 //There is no Extension for simple traveling projectiles!
1014 DrawTravel(screen);
1015 return;
1016 default:
1017 DrawExploded(screen);
1018 return;
1022 bool Projectile::DrawChildren(const Region &screen)
1024 bool drawn = false;
1026 if (children) {
1027 for(int i=0;i<child_size;i++){
1028 if(children[i]) {
1029 if (children[i]->Update()) {
1030 children[i]->DrawTravel(screen);
1031 drawn = true;
1032 } else {
1033 delete children[i];
1034 children[i]=NULL;
1040 return drawn;
1043 //draw until all children expire
1044 void Projectile::DrawExploded(const Region &screen)
1046 if (DrawChildren(screen)) {
1047 return;
1049 phase = P_EXPIRED;
1052 void Projectile::DrawExplosion(const Region &screen)
1054 //This seems to be a needless safeguard
1055 if (!Extension) {
1056 phase = P_EXPIRED;
1057 return;
1060 if (travel_handle) {
1061 travel_handle->Stop();
1062 travel_handle.release();
1065 DrawChildren(screen);
1067 int pause = core->IsFreezed();
1068 if (pause) {
1069 return;
1072 //Delay explosion, it could even be revoked with PAF_SYNC (see skull trap)
1073 if (extension_delay) {
1074 extension_delay--;
1075 return;
1078 //0 and 1 have the same effect (1 explosion)
1079 if (extension_explosioncount) {
1080 extension_explosioncount--;
1083 //Line targets are actors between source and destination point
1084 if(ExtFlags&PEF_LINE) {
1085 if (Target) {
1086 SetTarget(Target, false);
1088 LineTarget();
1091 //no idea what is PAF_SECONDARY
1092 //probably it is to alter some behaviour in the secondary
1093 //projectile generation
1094 //In trapskul.pro it isn't set, yet it has a secondary (invisible) projectile
1095 //All area effects are created by secondary projectiles
1097 //the secondary projectile will target everyone in the area of effect
1098 SecondaryTarget();
1100 //draw fragment graphics animation at the explosion center
1101 if (Extension->AFlags&PAF_FRAGMENT) {
1102 //there is a character animation in the center of the explosion
1103 //which will go towards the edges (flames, etc)
1104 //Extension->ExplColor fake color for single shades (blue,green,red flames)
1105 //Extension->FragAnimID the animation id for the character animation
1106 //This color is not used in the original game
1107 area->Sparkle(0, Extension->ExplColor, SPARKLE_EXPLOSION, Pos, Extension->FragAnimID);
1110 ProjectileServer *server = core->GetProjectileServer();
1111 //the center of the explosion could be another projectile played over the target
1112 if (Extension->FragProjIdx) {
1113 Projectile *pro = server->GetProjectileByIndex(Extension->FragProjIdx);
1114 if (pro) {
1115 area->AddProjectile(pro, Pos, Pos);
1119 //the center of the explosion is based on hardcoded explosion type (this is fireball.cpp in the original engine)
1120 //these resources are listed in areapro.2da and served by ProjectileServer.cpp
1121 int apflags = Extension->APFlags;
1123 //draw it only once, at the time of explosion
1124 if (phase==P_EXPLODING1) {
1125 core->GetAudioDrv()->Play(Extension->SoundRes, Pos.x, Pos.y);
1126 //play VVC in center
1127 if (Extension->AFlags&PAF_VVC) {
1128 ScriptedAnimation* vvc = gamedata->GetScriptedAnimation(Extension->VVCRes, false);
1129 if (vvc) {
1130 if (apflags & APF_VVCPAL) {
1131 vvc->SetPalette(Extension->ExplColor);
1133 //if the trail oriented, then the center is oriented too
1134 if (ExtFlags&PEF_TRAIL) {
1135 vvc->SetOrientation(Orientation);
1137 vvc->XPos+=Pos.x;
1138 vvc->YPos+=Pos.y;
1139 vvc->PlayOnce();
1140 vvc->SetBlend();
1141 area->AddVVCell(vvc);
1145 phase=P_EXPLODING2;
1146 } else {
1147 core->GetAudioDrv()->Play(Extension->AreaSound, Pos.x, Pos.y);
1150 //the spreading animation is in the first column
1151 const char *tmp = Extension->Spread;
1152 if(tmp[0]) {
1153 //i'm unsure about the need of this
1154 //returns if the explosion animation is fake coloured
1155 if (!children) {
1156 child_size = (Extension->ExplosionRadius+15)/16;
1157 //more sprites if the whole area needs to be filled
1158 if (apflags&APF_FILL) child_size*=2;
1159 if (apflags&APF_SPREAD) child_size*=2;
1160 if (apflags&APF_BOTH) child_size/=2; //intentionally decreases
1161 if (apflags&APF_MORE) child_size*=2;
1162 children = (Projectile **) calloc(sizeof(Projectile *), child_size);
1165 //zero cone width means single line area of effect
1166 if((Extension->AFlags&PAF_CONE) && !Extension->ConeWidth) {
1167 child_size = 1;
1170 int initial = child_size;
1172 for(int i=0;i<initial;i++) {
1173 //leave this slot free, it is residue from the previous flare up
1174 if (children[i])
1175 continue;
1176 if(apflags&APF_BOTH) {
1177 if(rand()&1) {
1178 tmp = Extension->Secondary;
1179 } else {
1180 tmp = Extension->Spread;
1183 //create a custom projectile with single traveling effect
1184 Projectile *pro = server->CreateDefaultProjectile((unsigned int) ~0);
1185 strnlwrcpy(pro->BAMRes1, tmp, 8);
1186 if (ExtFlags&PEF_TRAIL) {
1187 pro->Aim = Aim;
1189 pro->SetEffects(NULL);
1190 //calculate the child projectile's target point, it is either
1191 //a perimeter or an inside point of the explosion radius
1192 int rad = Extension->ExplosionRadius;
1193 Point newdest;
1195 if (apflags&APF_FILL) {
1196 rad=core->Roll(1,rad,0);
1198 int max = 360;
1199 int add = 0;
1200 if (Extension->AFlags&PAF_CONE) {
1201 max=Extension->ConeWidth;
1202 add=(Orientation*45-max)/2;
1204 max=core->Roll(1,max,add);
1205 double degree=max*M_PI/180;
1206 newdest.x = (int) -(rad * sin(degree) );
1207 newdest.y = (int) (rad * cos(degree) );
1209 //these fields and flags are always inherited by all children
1210 pro->Speed = Speed;
1211 pro->ExtFlags = ExtFlags&(PEF_HALFTRANS|PEF_CYCLE|PEF_RGB);
1212 pro->RGB = RGB;
1213 pro->ColorSpeed = ColorSpeed;
1215 if (apflags&APF_FILL) {
1216 int delay;
1218 //a bit of difference in case crowding is needed
1219 //make this a separate flag if speed difference
1220 //is not always wanted
1221 pro->Speed-=rand()&7;
1223 delay = Extension->Delay*extension_explosioncount;
1224 if(apflags&APF_BOTH) {
1225 if (delay) {
1226 delay = rand()%delay;
1229 //this needs to be commented out for ToB horrid wilting
1230 //if(ExtFlags&PEF_FREEZE) {
1231 delay += Extension->Delay;
1233 pro->SetDelay(delay);
1236 newdest.x+=Destination.x;
1237 newdest.y+=Destination.y;
1239 if (apflags&APF_SCATTER) {
1240 pro->MoveTo(area, newdest);
1241 } else {
1242 pro->MoveTo(area, Pos);
1244 pro->SetTarget(newdest);
1245 pro->autofree=true;
1247 //sets up the gradient color for the explosion animation
1248 if (apflags&(APF_PALETTE|APF_TINT) ) {
1249 pro->SetGradient(Extension->ExplColor, !(apflags&APF_PALETTE));
1251 //i'm unsure if we need blending for all anims or just the tinted ones
1252 pro->TFlags|=PTF_BLEND;
1253 //random frame is needed only for some of these, make it an areapro flag?
1254 if( !(ExtFlags&PEF_CYCLE) || (ExtFlags&PEF_RANDOM) ) {
1255 pro->ExtFlags|=PEF_RANDOM;
1257 pro->Setup();
1258 children[i]=pro;
1262 if (extension_explosioncount) {
1263 extension_delay=Extension->Delay;
1264 } else {
1265 phase = P_EXPLODED;
1269 int Projectile::GetTravelPos(int face)
1271 if (travel[face]) {
1272 return travel[face]->GetCurrentFrame();
1274 return 0;
1277 int Projectile::GetShadowPos(int face)
1279 if (shadow[face]) {
1280 return shadow[face]->GetCurrentFrame();
1282 return 0;
1285 void Projectile::SetPos(int face, int frame1, int frame2)
1287 if (travel[face]) {
1288 travel[face]->SetPos(frame1);
1290 if (shadow[face]) {
1291 shadow[face]->SetPos(frame2);
1295 //recalculate target and source points (perpendicular bisector)
1296 void Projectile::SetupWall()
1298 Point p1, p2;
1300 p1.x=(Pos.x+Destination.x)/2;
1301 p1.y=(Pos.y+Destination.y)/2;
1303 p2.x=p1.x+(Pos.y-Destination.y);
1304 p2.y=p1.y+(Pos.x-Destination.x);
1305 Pos=p1;
1306 SetTarget(p2);
1309 void Projectile::DrawLine(const Region &screen, int face, ieDword flag)
1311 Video *video = core->GetVideoDriver();
1312 PathNode *iter = path;
1313 Sprite2D *frame = travel[face]->NextFrame();
1314 while(iter) {
1315 Point pos(iter->x, iter->y);
1317 if (SFlags&PSF_FLYING) {
1318 pos.y-=FLY_HEIGHT;
1320 pos.x+=screen.x;
1321 pos.y+=screen.y;
1323 video->BlitGameSprite( frame, pos.x, pos.y, flag, tint, NULL, palette, &screen);
1324 iter = iter->Next;
1328 void Projectile::DrawTravel(const Region &screen)
1330 Video *video = core->GetVideoDriver();
1331 ieDword flag;
1333 if(ExtFlags&PEF_HALFTRANS) {
1334 flag=BLIT_HALFTRANS;
1335 } else {
1336 flag = 0;
1339 //static tint (use the tint field)
1340 if(ExtFlags&PEF_TINT) {
1341 flag|=BLIT_TINTED;
1344 //Area tint
1345 if (TFlags&PTF_TINT) {
1346 tint = area->LightMap->GetPixel( Pos.x / 16, Pos.y / 12);
1347 flag |= BLIT_TINTED;
1350 unsigned int face = GetNextFace();
1351 if (face!=Orientation) {
1352 SetPos(face, GetTravelPos(face), GetShadowPos(face));
1354 Point pos = Pos;
1355 pos.x+=screen.x;
1356 pos.y+=screen.y;
1358 if(ExtFlags&PEF_CURVE && phase == P_TRAVEL && Origin != Destination && type >= 68 && type <= 77) {
1359 // TODO: we want projectile# (starting at 0) because the lower-level
1360 // casts should be near the centre, so this is hard-coding the
1361 // magic missile id..
1362 int id = type - 68;
1364 double total_distance = Distance(Origin, Destination);
1365 double travelled_distance = Distance(Origin, Pos);
1367 // distance travelled along the line, from 0.0 to 1.0
1368 double travelled = travelled_distance / total_distance;
1369 assert(travelled <= 1.0);
1371 // input to sin(): 0 to pi gives us an arc
1372 double arc_angle = travelled * M_PI;
1374 //printf("id %d, travelled %f, angle %f\n", id, travelled, arc_angle);
1376 // calculate the distance between the arc and the current pos
1377 // (this could use travelled and a larger constant multiplier,
1378 // to make the arc size fixed rather than relative to the total
1379 // distance to travel)
1380 double length_of_normal = travelled_distance * sin(arc_angle) * 0.3 * ((id / 2) + 1);
1381 if (id % 2) length_of_normal = -length_of_normal;
1383 // adjust the to-be-rendered point by that distance
1384 double x_vector = (Destination.x - Origin.x) / total_distance,
1385 y_vector = (Destination.y - Origin.y) / total_distance;
1386 Point newpos = pos;
1387 newpos.x += (short)(y_vector*length_of_normal);
1388 newpos.y -= (short)(x_vector*length_of_normal);
1389 pos = newpos;
1392 if (light) {
1393 video->BlitGameSprite( light, pos.x, pos.y, 0, tint, NULL, NULL, &screen);
1396 if (ExtFlags&PEF_POP) {
1397 //draw pop in/hold/pop out animation sequences
1398 Sprite2D *frame;
1400 if(ExtFlags&PEF_UNPOP) {
1401 frame = shadow[0]->NextFrame();
1402 if(shadow[0]->endReached) {
1403 ExtFlags&=~PEF_UNPOP;
1405 } else {
1406 frame = travel[0]->NextFrame();
1407 if(travel[0]->endReached) {
1408 travel[0]->playReversed=true;
1409 travel[0]->SetPos(0);
1410 ExtFlags|=PEF_UNPOP;
1411 frame = shadow[0]->NextFrame();
1415 video->BlitGameSprite( frame, pos.x, pos.y, flag, tint, NULL, palette, &screen);
1416 return;
1419 if (ExtFlags&PEF_LINE) {
1420 DrawLine(screen, face, flag);
1421 return;
1424 if (shadow[face]) {
1425 Sprite2D *frame = shadow[face]->NextFrame();
1426 video->BlitGameSprite( frame, pos.x, pos.y, flag, tint, NULL, palette, &screen);
1429 if (SFlags&PSF_FLYING) {
1430 pos.y-=FLY_HEIGHT;
1433 if (ExtFlags&PEF_PILLAR) {
1434 //draw all frames simultaneously on top of each other
1435 for(int i=0;i<Aim;i++) {
1436 if (travel[i]) {
1437 Sprite2D *frame = travel[i]->NextFrame();
1438 video->BlitGameSprite( frame, pos.x, pos.y, flag, tint, NULL, palette, &screen);
1439 pos.y-=frame->YPos;
1442 } else {
1443 if (travel[face]) {
1444 Sprite2D *frame = travel[face]->NextFrame();
1445 video->BlitGameSprite( frame, pos.x, pos.y, flag, tint, NULL, palette, &screen);
1449 if (SFlags&PSF_SPARKS) {
1450 area->Sparkle(0,SparkColor,SPARKLE_EXPLOSION,pos);
1454 void Projectile::SetIdentifiers(const char *resref, ieWord id)
1456 strnuprcpy(name, resref, 8);
1457 type=id;
1460 bool Projectile::PointInRadius(const Point &p) const
1462 switch(phase) {
1463 //better not trigger on projectiles unset/expired
1464 case P_EXPIRED:
1465 case P_UNINITED: return false;
1466 case P_TRAVEL:
1467 if(p.x==Pos.x && p.y==Pos.y) return true;
1468 return false;
1469 default:
1470 if(p.x==Pos.x && p.y==Pos.y) return true;
1471 if (!Extension) return false;
1472 if (Distance(p,Pos)<Extension->ExplosionRadius) return true;
1474 return false;
1477 //Set gradient color, if type is true then it is static tint, otherwise it is paletted color
1478 void Projectile::SetGradient(int gradient, bool type)
1480 //gradients are unsigned chars, so this works
1481 memset(Gradients, gradient, 7);
1482 if (type) {
1483 ExtFlags|=PEF_TINT;
1484 } else {
1485 TFlags |= PTF_COLOUR;
1489 void Projectile::StaticTint(const Color &newtint)
1491 tint = newtint;
1492 TFlags &= ~PTF_TINT; //turn off area tint
1495 void Projectile::Cleanup()
1497 //neutralise the payload
1498 delete effects;
1499 effects = NULL;
1500 //diffuse the projectile
1501 phase=P_EXPIRED;