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"
26 #include "DisplayMessage.h"
29 #include "Interface.h"
30 #include "ProjectileServer.h"
36 //to get gradient color
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()
59 memset(travel
, 0, sizeof(travel
)) ;
60 memset(shadow
, 0, sizeof(shadow
)) ;
61 memset(PaletteRes
,0,sizeof(PaletteRes
));
62 memset(smokebam
, 0, sizeof(smokebam
));
67 Projectile::~Projectile()
76 gamedata
->FreePalette(palette
, PaletteRes
);
80 travel_handle
->Stop();
81 travel_handle
.release();
84 if (phase
!= P_UNINITED
) {
85 for (i
= 0; i
< MAX_ORIENT
; ++i
) {
91 core
->GetVideoDriver()->FreeSprite(light
);
95 for(i
=0;i
<child_size
;i
++) {
102 void Projectile::InitExtension()
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
);
120 int Max
= af
->GetCycleCount();
125 if((ExtFlags
&PEF_CYCLE
) && !Seq
) {
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
);
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
++) {
146 Animation
* a
= af
->GetCycle( c
);
149 //animations are started at a random frame position
150 //Always start from 0, unless set otherwise
151 if (!(ExtFlags
&PEF_RANDOM
)) {
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;
171 c
= SixteenToFive
[Cycle
];
172 // orientations go counter-clockwise, starting south
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) {
181 mirror
= true; mirrorvert
= true;
183 // bottom-left quadrant
184 mirror
= true; mirrorvert
= false;
188 c
= SixteenToNine
[Cycle
];
189 if (Cycle
>8) mirror
=true;
195 Animation
* a
= af
->GetCycle( c
);
198 //animations are started at a random frame position
199 //Always start from 0, unless set otherwise
200 if (!(ExtFlags
&PEF_RANDOM
)) {
205 a
->MirrorAnimation();
208 a
->MirrorAnimationVert();
210 a
->gameAnimation
= true;
214 //apply gradient colors
215 void Projectile::SetupPalette(Animation
*anim
[], Palette
*&pal
, const ieByte
*gradients
)
219 for (int i
=0;i
<7;i
++) {
220 Colors
[i
]=gradients
[i
];
222 GetPaletteCopy(anim
, pal
);
224 pal
->SetupPaperdollColours(Colors
, 0);
228 void Projectile::GetPaletteCopy(Animation
*anim
[], Palette
*&pal
)
232 for (unsigned int i
=0;i
<MAX_ORIENT
;i
++) {
234 Sprite2D
* spr
= anim
[i
]->GetFrame(0);
236 pal
= spr
->GetPalette()->Copy();
243 void Projectile::SetBlend()
245 GetPaletteCopy(travel
, palette
);
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
);
261 area
->AddProjectile(pro
, Pos
, FakeTarget
, true);
263 area
->AddProjectile(pro
, Pos
, Target
, false);
266 // added by fuzzie, to make magic missiles instant, maybe wrong place
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
) );
283 //turn off smoke animation if its animation was not found
284 //you might want to issue some warning here
287 // load animations, start sound
288 void Projectile::Setup()
295 ieDword time
= core
->GetGame()->Ticks
;
296 timeStartStep
= time
;
298 if(ExtFlags
&PEF_TEXT
) {
299 Actor
*act
= area
->GetActorByGlobalID(Caster
);
301 displaymsg
->DisplayStringName(StrRef
,0xd7d7be,act
,0);
306 core
->timer
->SetScreenShake( Shake
, Shake
, Shake
);
309 if(ExtFlags
&(PEF_FALLING
|PEF_INCOMING
) ) {
310 if (ExtFlags
&PEF_FALLING
) {
313 Pos
.x
=Destination
.x
+200;
315 Pos
.y
=Destination
.y
-200;
316 NextTarget(Destination
);
319 if(ExtFlags
&PEF_WALL
) {
323 //cone area of effect always disables the travel flag
324 //but also makes the caster immune to the effect
326 if (Extension
->AFlags
&PAF_CONE
) {
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
;
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
) {
361 //there is no travel phase, create the projectile right at the target
362 if (ExtFlags
&PEF_NO_TRAVEL
) {
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
;
375 SetDelay(travel
[0]->GetFrameCount() );
380 if (TFlags
&PTF_COLOUR
) {
381 SetupPalette(travel
, palette
, Gradients
);
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
) {
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
) {
402 Actor
*Projectile::GetTarget()
407 target
= area
->GetActorByGlobalID(Target
);
408 if (!target
) return NULL
;
409 Actor
*original
= area
->GetActorByGlobalID(Caster
);
410 if (original
==target
) {
411 effects
->SetOwner(target
);
414 int res
= effects
->CheckImmunity ( target
);
420 Target
= original
->GetGlobalID();
423 effects
->SetOwner(original
);
426 target
= area
->GetActorByGlobalID(Caster
);
428 effects
->SetOwner(target
);
433 void Projectile::SetDelay(int delay
)
435 extension_delay
=delay
;
436 ExtFlags
|=PEF_FREEZE
;
439 void Projectile::Payload()
448 target
= GetTarget();
449 if (!target
&& (Target
==Caster
)) {
450 //projectile rebounced
454 //the target will be the original caster
455 //in case of single point area target (dimension door)
457 target
= area
->GetActorByGlobalID(FakeTarget
);
459 target
= core
->GetGame()->GetActorByGlobalID(FakeTarget
);
462 target
= area
->GetActorByGlobalID(Caster
);
465 Actor
*source
= area
->GetActorByGlobalID(Caster
);
467 effects
->SetOwner(source
);
469 effects
->SetOwner(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
);
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()
491 Actor
*target
= area
->GetActorByGlobalID(Target
);
497 //reached target, and explodes now
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
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) {
516 if(ExtFlags
&PEF_FADE
) {
517 TFlags
&= ~PTF_TINT
; //turn off area tint
531 //Call this only if Extension exists!
532 int Projectile::CalculateExplosionCount()
535 Actor
*act
= area
->GetActorByGlobalID(Caster
);
537 if (Extension
->AFlags
&PAF_LEV_MAGE
) {
538 count
= act
->GetMageLevel();
540 else if (Extension
->AFlags
&PAF_LEV_CLERIC
) {
541 count
= act
->GetClericLevel();
546 count
= Extension
->ExplosionCount
;
554 void Projectile::EndTravel()
561 //this flag says that the explosion should occur only when triggered
562 if (Extension
->AFlags
&PAF_TRIGGER
) {
566 phase
= P_EXPLODING1
;
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);
584 for(int i
=0;i
<7;i
++) {
585 sca
->SetPalette(pal
[i
], 4+i
*PALSIZE
);
588 sca
->SetOrientation(Orientation
);
593 area
->AddVVCell(sca
);
596 void Projectile::DoStep(unsigned int walk_speed
)
609 if (Pos
==Destination
) {
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
) {
630 //transform into an explosive line
633 if(!(ExtFlags
&PEF_FREEZE
) && travel
[0]) {
634 //switch to 'fading' phase
635 //SetDelay(travel[0]->GetFrameCount());
640 //don't change position
644 //path won't be calculated if speed==0
645 walk_speed
=1500/walk_speed
;
646 ieDword time
= core
->GetGame()->Ticks
;
650 while (step
->Next
&& (( time
- timeStartStep
) >= walk_speed
)) {
653 timeStartStep
= time
;
656 timeStartStep
= timeStartStep
+ walk_speed
;
659 SetOrientation (step
->orient
, false);
664 travel_handle
->SetPos(Pos
.x
, Pos
.y
);
668 NewOrientation
= Orientation
;
675 if (step
->Next
->x
> step
->x
)
676 Pos
.x
+= ( unsigned short )
677 ( ( step
->Next
->x
- Pos
.x
) * ( time
- timeStartStep
) / walk_speed
);
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
);
685 Pos
.y
-= ( unsigned short )
686 ( ( Pos
.y
- step
->Next
->y
) * ( time
- timeStartStep
) / walk_speed
);
689 void Projectile::SetCaster(ieDword caster
)
694 ieDword
Projectile::GetCaster() const
699 void Projectile::NextTarget(const Point
&p
)
703 //call this with destination
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
) {
721 path
= area
->GetLine( Pos
, Destination
, Speed
, Orientation
, GL_PASS
);
724 void Projectile::SetTarget(const Point
&p
)
730 void Projectile::SetTarget(ieDword tar
, bool fake
)
732 Actor
*target
= NULL
;
740 target
= area
->GetActorByGlobalID(tar
);
747 //replan the path in case the target moved
748 if(target
->Pos
!=Destination
) {
749 NextTarget(target
->Pos
);
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
) {
758 NextTarget(target
->Pos
);
763 void Projectile::MoveTo(Map
*map
, const Point
&Des
)
771 void Projectile::ClearPath()
773 PathNode
* thisNode
= path
;
775 PathNode
* nextNode
= thisNode
->Next
;
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
) {
793 //affect only enemies or allies
794 switch (Extension
->AFlags
&PAF_TARGET
) {
796 flags
|=GA_NO_NEUTRAL
|GA_NO_ALLY
;
798 case PAF_PARTY
: //this doesn't exist in IE
802 flags
|=GA_NO_NEUTRAL
|GA_NO_ENEMY
;
808 Actor
*caster
= area
->GetActorByGlobalID(Caster
);
809 if (caster
&& ((Actor
*) caster
)->GetStat(IE_EA
)<EA_GOODCUTOFF
) {
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();
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
) {
843 void Projectile::SetEffectsCopy(EffectQueue
*eq
)
845 if(effects
) delete effects
;
850 effects
= eq
->CopySelf();
853 void Projectile::LineTarget()
859 Actor
*original
= area
->GetActorByGlobalID(Caster
);
861 PathNode
*iter
= path
;
863 Point
pos(iter
->x
,iter
->y
);
864 Actor
*target
= area
->GetActorInRadius(pos
, CalculateTargetFlag(), 1);
865 if (target
&& target
->GetGlobalID()!=Caster
&& prev
!=target
) {
867 int res
= effects
->CheckImmunity ( target
);
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
);
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
);
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
);
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
)) {
911 if (Extension
->AFlags
&PAF_CONE
) {
912 //cone never affects the caster
917 double xdiff
= (*poi
)->Pos
.x
-Pos
.x
;
918 double ydiff
= Pos
.y
-(*poi
)->Pos
.y
;
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();
928 deg
= (int) (atan(xdiff
/ydiff
)*180/M_PI
);
929 if(ydiff
>0) deg
+=180;
935 //not in the right sector of circle
936 if (mindeg
>deg
|| maxdeg
<deg
) {
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);
950 //we already got one target affected in the AOE, this flag says
952 if(Extension
->AFlags
&PAF_AFFECT_ONE
) {
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
961 Actor
*actor
= area
->GetActorByGlobalID(Caster
);
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
) {
977 if (phase
== P_UNINITED
) {
981 int pause
= core
->IsFreezed();
985 //recreate path if target has moved
987 SetTarget(Target
, false);
990 if (phase
== P_TRAVEL
) {
996 void Projectile::Draw(const Region
&screen
)
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
) {
1007 CheckTrigger(Extension
->TriggerRadius
);
1008 if (phase
== P_EXPLODING1
|| phase
== P_EXPLODING2
) {
1009 DrawExplosion(screen
);
1013 //There is no Extension for simple traveling projectiles!
1017 DrawExploded(screen
);
1022 bool Projectile::DrawChildren(const Region
&screen
)
1027 for(int i
=0;i
<child_size
;i
++){
1029 if (children
[i
]->Update()) {
1030 children
[i
]->DrawTravel(screen
);
1043 //draw until all children expire
1044 void Projectile::DrawExploded(const Region
&screen
)
1046 if (DrawChildren(screen
)) {
1052 void Projectile::DrawExplosion(const Region
&screen
)
1054 //This seems to be a needless safeguard
1060 if (travel_handle
) {
1061 travel_handle
->Stop();
1062 travel_handle
.release();
1065 DrawChildren(screen
);
1067 int pause
= core
->IsFreezed();
1072 //Delay explosion, it could even be revoked with PAF_SYNC (see skull trap)
1073 if (extension_delay
) {
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
) {
1086 SetTarget(Target
, false);
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
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
);
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);
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
);
1141 area
->AddVVCell(vvc
);
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
;
1153 //i'm unsure about the need of this
1154 //returns if the explosion animation is fake coloured
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
) {
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
1176 if(apflags
&APF_BOTH
) {
1178 tmp
= Extension
->Secondary
;
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
) {
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
;
1195 if (apflags
&APF_FILL
) {
1196 rad
=core
->Roll(1,rad
,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
1211 pro
->ExtFlags
= ExtFlags
&(PEF_HALFTRANS
|PEF_CYCLE
|PEF_RGB
);
1213 pro
->ColorSpeed
= ColorSpeed
;
1215 if (apflags
&APF_FILL
) {
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
) {
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
);
1242 pro
->MoveTo(area
, Pos
);
1244 pro
->SetTarget(newdest
);
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
;
1262 if (extension_explosioncount
) {
1263 extension_delay
=Extension
->Delay
;
1269 int Projectile::GetTravelPos(int face
)
1272 return travel
[face
]->GetCurrentFrame();
1277 int Projectile::GetShadowPos(int face
)
1280 return shadow
[face
]->GetCurrentFrame();
1285 void Projectile::SetPos(int face
, int frame1
, int frame2
)
1288 travel
[face
]->SetPos(frame1
);
1291 shadow
[face
]->SetPos(frame2
);
1295 //recalculate target and source points (perpendicular bisector)
1296 void Projectile::SetupWall()
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
);
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();
1315 Point
pos(iter
->x
, iter
->y
);
1317 if (SFlags
&PSF_FLYING
) {
1323 video
->BlitGameSprite( frame
, pos
.x
, pos
.y
, flag
, tint
, NULL
, palette
, &screen
);
1328 void Projectile::DrawTravel(const Region
&screen
)
1330 Video
*video
= core
->GetVideoDriver();
1333 if(ExtFlags
&PEF_HALFTRANS
) {
1334 flag
=BLIT_HALFTRANS
;
1339 //static tint (use the tint field)
1340 if(ExtFlags
&PEF_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
));
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..
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
;
1387 newpos
.x
+= (short)(y_vector
*length_of_normal
);
1388 newpos
.y
-= (short)(x_vector
*length_of_normal
);
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
1400 if(ExtFlags
&PEF_UNPOP
) {
1401 frame
= shadow
[0]->NextFrame();
1402 if(shadow
[0]->endReached
) {
1403 ExtFlags
&=~PEF_UNPOP
;
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
);
1419 if (ExtFlags
&PEF_LINE
) {
1420 DrawLine(screen
, face
, flag
);
1425 Sprite2D
*frame
= shadow
[face
]->NextFrame();
1426 video
->BlitGameSprite( frame
, pos
.x
, pos
.y
, flag
, tint
, NULL
, palette
, &screen
);
1429 if (SFlags
&PSF_FLYING
) {
1433 if (ExtFlags
&PEF_PILLAR
) {
1434 //draw all frames simultaneously on top of each other
1435 for(int i
=0;i
<Aim
;i
++) {
1437 Sprite2D
*frame
= travel
[i
]->NextFrame();
1438 video
->BlitGameSprite( frame
, pos
.x
, pos
.y
, flag
, tint
, NULL
, palette
, &screen
);
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);
1460 bool Projectile::PointInRadius(const Point
&p
) const
1463 //better not trigger on projectiles unset/expired
1465 case P_UNINITED
: return false;
1467 if(p
.x
==Pos
.x
&& p
.y
==Pos
.y
) return true;
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;
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);
1485 TFlags
|= PTF_COLOUR
;
1489 void Projectile::StaticTint(const Color
&newtint
)
1492 TFlags
&= ~PTF_TINT
; //turn off area tint
1495 void Projectile::Cleanup()
1497 //neutralise the payload
1500 //diffuse the projectile