1 /* GemRB - Infinity Engine Emulator
2  * Copyright (C) 2006 The GemRB Project
3  *
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.
8  *
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.
13  *
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.
17  *
18  *
19  */
20 
21 #include "Projectile.h"
22 
23 #include "AnimationFactory.h"
24 #include "DisplayMessage.h"
25 #include "Game.h"
26 #include "GameData.h"
27 #include "GlobalTimer.h"
28 #include "Image.h"
29 #include "Interface.h"
30 #include "ProjectileServer.h"
31 #include "Sprite2D.h"
32 #include "VEFObject.h"
33 #include "RNG.h"
34 #include "Scriptable/Actor.h"
35 #include "ScriptedAnimation.h"
36 
37 #include <cmath>
38 #include <cstdlib>
39 
40 namespace GemRB {
41 
42 constexpr uint8_t PALSIZE = 32;
43 
44 static const ieByte SixteenToNine[MAX_ORIENT]={0,1,2,3,4,5,6,7,8,7,6,5,4,3,2,1};
45 static const ieByte SixteenToFive[MAX_ORIENT]={0,1,2,3,4,3,2,1,0,1,2,3,4,3,2,1};
46 
47 static ProjectileServer *server = NULL;
48 
Projectile()49 Projectile::Projectile()
50 {
51 	autofree = false;
52 	Extension = NULL;
53 	area = NULL;
54 	palette = NULL;
55 	Pos.empty();
56 	Destination = Pos;
57 	Orientation = 0;
58 	NewOrientation = 0;
59 	path = NULL;
60 	step = NULL;
61 	timeStartStep = 0;
62 	phase = P_UNINITED;
63 	effects = NULL;
64 	children = NULL;
65 	child_size = 0;
66 	memset(travel, 0, sizeof(travel)) ;
67 	memset(shadow, 0, sizeof(shadow)) ;
68 	memset(PaletteRes,0,sizeof(PaletteRes));
69 	memset(smokebam, 0, sizeof(smokebam));
70 	light = NULL;
71 	pathcounter = 0x7fff;
72 	FakeTarget = 0;
73 	bend = 0;
74 	drawSpark = 0;
75 	ZPos = 0;
76 	extension_delay = 0;
77 	Range = 0;
78 	ColorSpeed = Shake = TFlags = Seq1 = Seq2 = ExtFlags = 0;
79 	IDSType = IDSValue = IDSType2 = IDSValue2 = StrRef = 0;
80 	LightX = LightY = LightZ = Aim = type = SparkColor = 0;
81 	SmokeSpeed = SmokeAnimID = Caster = Target = Level = 0;
82 	extension_explosioncount = extension_targetcount = 0;
83 	Speed = 20;
84 	SFlags = PSF_FLYING;
85 	if (!server)
86 		server = core->GetProjectileServer();
87 }
88 
~Projectile()89 Projectile::~Projectile()
90 {
91 	int i;
92 
93 	if (autofree) {
94 		free(Extension);
95 	}
96 	delete effects;
97 
98 	ClearPath();
99 
100 	if (travel_handle) {
101 		//allow an explosion sound to finish completely
102 		travel_handle->StopLooping();
103 	}
104 
105 	if (phase != P_UNINITED) {
106 		for (i = 0; i < MAX_ORIENT; ++i) {
107 			if(travel[i])
108 				delete travel[i];
109 			if(shadow[i])
110 				delete shadow[i];
111 		}
112 	}
113 
114 	if(children) {
115 		for(i=0;i<child_size;i++) {
116 			delete children[i];
117 		}
118 		free (children);
119 	}
120 }
121 
InitExtension()122 void Projectile::InitExtension()
123 {
124 	autofree = false;
125 	if (!Extension) {
126 		Extension = (ProjectileExtension *) calloc( 1, sizeof(ProjectileExtension));
127 	}
128 }
129 
CreateAnimations(Animation ** anims,const ieResRef bamres,int Seq)130 void Projectile::CreateAnimations(Animation **anims, const ieResRef bamres, int Seq)
131 {
132 	AnimationFactory* af = ( AnimationFactory* )
133 		gamedata->GetFactoryResource( bamres,
134 				IE_BAM_CLASS_ID, IE_NORMAL );
135 
136 	if (!af) {
137 		return;
138 	}
139 
140 	int Max = af->GetCycleCount();
141 	if (!Max) {
142 		return;
143 	}
144 
145 	if((ExtFlags&PEF_CYCLE) && !Seq) {
146 		Seq=RAND(0, Max-1);
147 	}
148 
149 	//this hack is needed because bioware .pro files are sometimes
150 	//reporting bigger face count than possible by the animation
151 	if (Aim>Max) Aim=Max;
152 
153 	if(ExtFlags&PEF_PILLAR) {
154 		CreateCompositeAnimation(anims, af, Seq);
155 	} else {
156 		CreateOrientedAnimations(anims, af, Seq);
157 	}
158 }
159 
160 //Seq is the first cycle to use in the composite
161 //Aim is the number of cycles
CreateCompositeAnimation(Animation ** anims,AnimationFactory * af,int Seq)162 void Projectile::CreateCompositeAnimation(Animation **anims, AnimationFactory *af, int Seq)
163 {
164 	for (int Cycle = 0; Cycle<Aim; Cycle++) {
165 		int c = Cycle+Seq;
166 		Animation* a = af->GetCycle( c );
167 		anims[Cycle] = a;
168 		if (!a) continue;
169 		//animations are started at a random frame position
170 		//Always start from 0, unless set otherwise
171 		if (!(ExtFlags&PEF_RANDOM)) {
172 			a->SetPos(0);
173 		}
174 
175 		a->gameAnimation = true;
176 	}
177 }
178 
179 //Seq is the cycle to use in case of single orientations
180 //Aim is the number of Orientations
181 // FIXME: seems inefficient that we load up MAX_ORIENT animations even for those with a single orientation (default case)
CreateOrientedAnimations(Animation ** anims,AnimationFactory * af,int Seq)182 void Projectile::CreateOrientedAnimations(Animation **anims, AnimationFactory *af, int Seq)
183 {
184 	for (int Cycle = 0; Cycle<MAX_ORIENT; Cycle++) {
185 		bool mirror = false, mirrorvert = false;
186 		int c;
187 		switch(Aim) {
188 		default:
189 			c = Seq;
190 			break;
191 		case 5:
192 			c = SixteenToFive[Cycle];
193 			// orientations go counter-clockwise, starting south
194 			if (Cycle <= 4) {
195 				// bottom-right quadrant
196 				mirror = false; mirrorvert = false;
197 			} else if (Cycle <= 8) {
198 				// top-right quadrant
199 				mirror = false; mirrorvert = true;
200 			} else if (Cycle < 12) {
201 				// top-left quadrant
202 				mirror = true; mirrorvert = true;
203 			} else {
204 				// bottom-left quadrant
205 				mirror = true; mirrorvert = false;
206 			}
207 			break;
208 		case 9:
209 			c = SixteenToNine[Cycle];
210 			if (Cycle>8) mirror=true;
211 			break;
212 		case 16:
213 			c=Cycle;
214 			break;
215 		}
216 		Animation* a = af->GetCycle( c );
217 		anims[Cycle] = a;
218 		if (!a) continue;
219 		//animations are started at a random frame position
220 		//Always start from 0, unless set otherwise
221 		if (!(ExtFlags&PEF_RANDOM)) {
222 			a->SetPos(0);
223 		}
224 
225 		if (mirror) {
226 			a->MirrorAnimation();
227 		}
228 		if (mirrorvert) {
229 			a->MirrorAnimationVert();
230 		}
231 		a->gameAnimation = true;
232 	}
233 }
234 
235 //apply gradient colors
SetupPalette(Animation * anim[],PaletteHolder & pal,const ieByte * gradients)236 void Projectile::SetupPalette(Animation *anim[], PaletteHolder &pal, const ieByte *gradients)
237 {
238 	ieDword Colors[7];
239 
240 	for (int i=0;i<7;i++) {
241 		Colors[i]=gradients[i];
242 	}
243 	GetPaletteCopy(anim, pal);
244 	if (pal) {
245 		pal->SetupPaperdollColours(Colors, 0);
246 	}
247 }
248 
GetPaletteCopy(Animation * anim[],PaletteHolder & pal)249 void Projectile::GetPaletteCopy(Animation *anim[], PaletteHolder &pal)
250 {
251 	if (pal)
252 		return;
253 	for (unsigned int i=0;i<MAX_ORIENT;i++) {
254 		if (anim[i]) {
255 			Holder<Sprite2D> spr = anim[i]->GetFrame(0);
256 			if (spr) {
257 				pal = spr->GetPalette()->Copy();
258 				break;
259 			}
260 		}
261 	}
262 }
263 
SetBlend(int brighten)264 void Projectile::SetBlend(int brighten)
265 {
266 	GetPaletteCopy(travel, palette);
267 	if (!palette)
268 		return;
269 	if (!palette->HasAlpha()) {
270 		palette->CreateShadedAlphaChannel();
271 	}
272 	if (brighten) {
273 		palette->Brighten();
274 	}
275 }
276 
277 //create another projectile with type-1 (iterate magic missiles and call lightning)
CreateIteration()278 void Projectile::CreateIteration()
279 {
280 	Projectile *pro = server->GetProjectileByIndex(type-1);
281 	pro->SetEffectsCopy(effects, Pos);
282 	pro->SetCaster(Caster, Level);
283 	if (ExtFlags&PEF_CURVE) {
284 		pro->bend=bend+1;
285 		pro->Speed = Speed; // fix the different speed of MAGICMIS.pro compared to SPMAGMIS.pro
286 	}
287 
288 	if (FakeTarget) {
289 		area->AddProjectile(pro, Pos, FakeTarget, true);
290 	} else {
291 		area->AddProjectile(pro, Pos, Target, false);
292 	}
293 
294 	// added by fuzzie, to make magic missiles instant, maybe wrong place
295 	pro->Setup();
296 }
297 
GetSmokeAnim()298 void Projectile::GetSmokeAnim()
299 {
300 	int AvatarsRowNum=CharAnimations::GetAvatarsCount();
301 
302 	SmokeAnimID&=0xfff0; //this is a hack, i'm too lazy to figure out the subtypes
303 
304 	for(int i=0;i<AvatarsRowNum;i++) {
305 		AvatarStruct *as = CharAnimations::GetAvatarStruct(i);
306 		if (as->AnimID==SmokeAnimID) {
307 			memcpy(smokebam, as->Prefixes, sizeof(ieResRef) );
308 			return;
309 		}
310 	}
311 	//turn off smoke animation if its animation was not found
312 	//you might want to issue some warning here
313 	TFlags&=PTF_SMOKE;
314 }
315 // load animations, start sound
Setup()316 void Projectile::Setup()
317 {
318 	tint.r=128;
319 	tint.g=128;
320 	tint.b=128;
321 	tint.a=255;
322 
323 	ieDword time = core->GetGame()->Ticks;
324 	timeStartStep = time;
325 
326 	if(ExtFlags&PEF_TEXT) {
327 		Actor *act = area->GetActorByGlobalID(Caster);
328 		if(act) {
329 			displaymsg->DisplayStringName(StrRef, DMC_LIGHTGREY, act,0);
330 		}
331 	}
332 
333 	//falling = vertical
334 	//incoming = right side
335 	//both = left side
336 	if(ExtFlags&(PEF_FALLING|PEF_INCOMING) ) {
337 		if (ExtFlags&PEF_INCOMING) {
338 			if (ExtFlags&PEF_FALLING) {
339 				Pos.x=Destination.x-200;
340 			} else {
341 				Pos.x=Destination.x+200;
342 			}
343 		}
344 		else {
345 			Pos.x=Destination.x;
346 		}
347 		Pos.y=Destination.y-200;
348 		NextTarget(Destination);
349 	}
350 
351 	if(ExtFlags&PEF_WALL) {
352 		SetupWall();
353 	}
354 
355 	//cone area of effect always disables the travel flag
356 	//but also makes the caster immune to the effect
357 	if (Extension) {
358 		if (Extension->AFlags&PAF_CONE) {
359 			NewOrientation = Orientation = GetOrient(Destination, Pos);
360 			Destination=Pos;
361 			ExtFlags|=PEF_NO_TRAVEL;
362 		}
363 
364 		//this flag says the first explosion is delayed
365 		//(works for delaying triggers too)
366 		//getting the explosion count here, so an absent caster won't cut short
367 		//on the explosion count
368 		if(Extension->AFlags&PAF_DELAY) {
369 			extension_delay=Extension->Delay;
370 		} else {
371 			extension_delay=0;
372 		}
373 		extension_explosioncount=CalculateExplosionCount();
374 	}
375 
376 	//set any static tint
377 	if(ExtFlags&PEF_TINT) {
378 		uint8_t idx = PALSIZE/2;
379 		const auto& pal32 = core->GetPalette32(Gradients[0]);
380 		const Color& tmpColor = pal32[idx];
381 		// PALSIZE is usually 12, but pst has it at 32, which is now the default, so make sure we're not trying to read an empty (black) entry
382 
383 		if (tmpColor.r + tmpColor.g + tmpColor.b == 0) idx = 6;
384 		StaticTint(pal32[idx]);
385 	}
386 
387 	CreateAnimations(travel, BAMRes1, Seq1);
388 
389 	if (TFlags&PTF_SHADOW) {
390 		CreateAnimations(shadow, BAMRes2, Seq2);
391 	}
392 
393 	if (TFlags&PTF_SMOKE) {
394 		GetSmokeAnim();
395 	}
396 
397 	//there is no travel phase, create the projectile right at the target
398 	if (ExtFlags&PEF_NO_TRAVEL) {
399 		Pos = Destination;
400 
401 		//the travel projectile should linger after explosion
402 		if(ExtFlags&PEF_POP) {
403 			//the explosion consists of a pop in/hold/pop out of the travel projectile (dimension door)
404 			if(travel[0] && shadow[0]) {
405 				extension_delay = travel[0]->GetFrameCount()*2+shadow[0]->GetFrameCount();
406 				//SetDelay( travel[0]->GetFrameCount()*2+shadow[0]->GetFrameCount());
407 				travel[0]->Flags|=A_ANI_PLAYONCE;
408 				shadow[0]->Flags|=A_ANI_PLAYONCE;
409 			}
410 		} else {
411 			if(travel[0]) {
412 				extension_delay = travel[0]->GetFrameCount();
413 				travel[0]->Flags|=A_ANI_PLAYONCE;
414 				//SetDelay(travel[0]->GetFrameCount() );
415 			}
416 		}
417 	}
418 
419 	if (TFlags&PTF_COLOUR) {
420 		SetupPalette(travel, palette, Gradients);
421 	} else {
422 		palette = gamedata->GetPalette(PaletteRes);
423 	}
424 
425 	if (TFlags&PTF_LIGHT) {
426 		light = core->GetVideoDriver()->CreateLight(LightX, LightZ);
427 	}
428 	if (TFlags&PTF_TRANS) {
429 		SetBlend(TFlags&PTF_BRIGHTEN);
430 	}
431 	if (SFlags&PSF_FLYING) {
432 		ZPos = FLY_HEIGHT;
433 	}
434 	phase = P_TRAVEL;
435 	travel_handle = core->GetAudioDrv()->Play(FiringSound, SFX_CHAN_MISSILE,
436 				Pos.x, Pos.y, (SFlags & PSF_LOOPING ? GEM_SND_LOOPING : 0));
437 
438 	//create more projectiles
439 	if(ExtFlags&PEF_ITERATION) {
440 		CreateIteration();
441 	}
442 }
443 
GetTarget()444 Actor *Projectile::GetTarget()
445 {
446 	Actor *target;
447 
448 	if (Target) {
449 		target = area->GetActorByGlobalID(Target);
450 		if (!target) return NULL;
451 		Actor *original = area->GetActorByGlobalID(Caster);
452 		if (!effects) {
453 			return target;
454 		}
455 		if (original == target && !effects->HasHostileEffects()) {
456 			effects->SetOwner(target);
457 			return target;
458 		}
459 
460 		int res = effects->CheckImmunity ( target );
461 		//resisted
462 		if (!res) {
463 			return NULL;
464 		}
465 		if (res==-1) {
466 			if (original) {
467 				Target = original->GetGlobalID();
468 				target = original;
469 			} else {
470 				Log(DEBUG, "Projectile", "GetTarget: caster not found, bailing out!");
471 				return NULL;
472 			}
473 		}
474 		effects->SetOwner(original);
475 		return target;
476 	} else {
477 		Log(DEBUG, "Projectile", "GetTarget: Target not set or dummy, using caster!");
478 	}
479 	target = area->GetActorByGlobalID(Caster);
480 	if (target) {
481 		effects->SetOwner(target);
482 	}
483 	return target;
484 }
485 
SetDelay(int delay)486 void Projectile::SetDelay(int delay)
487 {
488 	extension_delay=delay;
489 	ExtFlags|=PEF_FREEZE;
490 }
491 
492 //copied from Actor.cpp
493 #define ATTACKROLL    20
494 #define WEAPON_FIST        0
495 #define WEAPON_BYPASS      0x10000
496 
FailedIDS(const Actor * target) const497 bool Projectile::FailedIDS(const Actor *target) const
498 {
499 	bool fail = !EffectQueue::match_ids( target, IDSType, IDSValue);
500 	if (ExtFlags&PEF_NOTIDS) {
501 		fail = !fail;
502 	}
503 	if (ExtFlags&PEF_BOTH) {
504 		if (!fail) {
505 			fail = !EffectQueue::match_ids( target, IDSType2, IDSValue2);
506 			if (ExtFlags&PEF_NOTIDS2) {
507 				fail = !fail;
508 			}
509 		}
510 	}
511 	else
512 	{
513 		if (fail && IDSType2) {
514 			fail = !EffectQueue::match_ids( target, IDSType2, IDSValue2);
515 			if (ExtFlags&PEF_NOTIDS2) {
516 				fail = !fail;
517 			}
518 		}
519 	}
520 
521 	if (!fail) {
522 		if(ExtFlags&PEF_TOUCH) {
523 			Actor *caster = core->GetGame()->GetActorByGlobalID(Caster);
524 			if (caster) {
525 				//TODO move this to Actor
526 				//TODO some projectiles use melee attack (fist), others use projectile attack
527 				//this apparently depends on the spell's SpellForm (normal vs. projectile)
528 				int roll = caster->LuckyRoll(1, ATTACKROLL, 0);
529 				if (roll==1) {
530 					return true; //critical failure
531 				}
532 
533 				if (!(target->GetStat(IE_STATE_ID)&STATE_CRIT_PROT))  {
534 					if (roll >= (ATTACKROLL - (int) caster->GetStat(IE_CRITICALHITBONUS))) {
535 						return false; //critical success
536 					}
537 				}
538 
539 				//handle attack type here, weapon depends on it too?
540 				int tohit = caster->GetToHit(WEAPON_FIST, target);
541 				//damage type, should be generic?
542 				// ignore the armor bonus
543 				int defense = target->GetDefense(0, WEAPON_BYPASS, caster);
544 				if(target->IsReverseToHit()) {
545 					fail = roll + defense < tohit;
546 				} else {
547 					fail = tohit + roll < defense;
548 				}
549 			}
550 		}
551 	}
552 
553 	return fail;
554 }
555 
Payload()556 void Projectile::Payload()
557 {
558 	if(Shake) {
559 		core->timer.SetScreenShake(Point(Shake, Shake), Shake);
560 		Shake = 0;
561 	}
562 
563 	//allow area affecting projectile with a spell
564 	if(!(effects || SuccSpell[0] || (!Target && FailSpell[0]))) {
565 		return;
566 	}
567 
568 	// PEF_CONTINUE never has a Target (LightningBolt)
569 	// if we were to try to get one we would end up damaging
570 	// either the caster, or the original target of the spell
571 	// which are probably both nowhere near the projectile at this point
572 	// all effects are applied as the projectile travels
573 	if (ExtFlags & PEF_CONTINUE) {
574 		delete effects;
575 		effects = NULL;
576 		return;
577 	}
578 
579 	Actor *target;
580 	Scriptable *Owner;
581 
582 	if (Target) {
583 		target = GetTarget();
584 	} else {
585 		//the target will be the original caster
586 		//in case of single point area target (dimension door)
587 		if (FakeTarget) {
588 			target = area->GetActorByGlobalID(FakeTarget);
589 			if (!target) {
590 				target = core->GetGame()->GetActorByGlobalID(FakeTarget);
591 			}
592 		} else {
593 			target = area->GetActorByGlobalID(Caster);
594 		}
595 	}
596 
597 	if (target) {
598 		Owner = area->GetScriptableByGlobalID(Caster);
599 		if (!Owner) {
600 			Log(WARNING, "Projectile", "Payload: Caster not found, using target!");
601 			Owner = target;
602 		}
603 		//apply this spell on target when the projectile fails
604 		if (FailedIDS(target)) {
605 			if (FailSpell[0]) {
606 				if (Target) {
607 					core->ApplySpell(FailSpell, target, Owner, Level);
608 				} else {
609 					//no Target, using the fake target as owner
610 					core->ApplySpellPoint(FailSpell, area, Destination, target, Level);
611 				}
612 			}
613 		} else {
614 			//apply this spell on the target when the projectile succeeds
615 			if (SuccSpell[0]) {
616 				core->ApplySpell(SuccSpell, target, Owner, Level);
617 			}
618 
619 			if(ExtFlags&PEF_RGB) {
620 				target->SetColorMod(0xff, RGBModifier::ADD, ColorSpeed, RGB);
621 			}
622 
623 			if (effects) {
624 				effects->SetOwner(Owner);
625 				effects->AddAllEffects(target, Destination);
626 			}
627 		}
628 	}
629 
630 	delete effects;
631 	effects = NULL;
632 }
633 
ApplyDefault()634 void Projectile::ApplyDefault()
635 {
636 	Actor *actor = area->GetActorByGlobalID(Caster);
637 	if (actor) {
638 		//name is the projectile's name
639 		//for simplicity, we apply a spell of the same name
640 		core->ApplySpell(name, actor, actor, Level);
641 	}
642 }
643 
StopSound()644 void Projectile::StopSound()
645 {
646 	if (travel_handle) {
647 		travel_handle->Stop();
648 		travel_handle.release();
649 	}
650 }
651 
UpdateSound()652 void Projectile::UpdateSound()
653 {
654 	if (!(SFlags&PSF_SOUND2)) {
655 		StopSound();
656 	}
657 	if (!travel_handle || !travel_handle->Playing()) {
658 		travel_handle = core->GetAudioDrv()->Play(ArrivalSound, SFX_CHAN_MISSILE,
659 				Pos.x, Pos.y, (SFlags & PSF_LOOPING2 ? GEM_SND_LOOPING : 0));
660 		SFlags|=PSF_SOUND2;
661 	}
662 }
663 
664 //control the phase change when the projectile reached its target
665 //possible actions: vanish, hover over point, explode
666 //depends on the area extension
667 //play explosion sound
ChangePhase()668 void Projectile::ChangePhase()
669 {
670 	if (Target) {
671 		Actor *target = area->GetActorByGlobalID(Target);
672 		if (!target) {
673 			phase = P_EXPIRED;
674 			return;
675 		}
676 	}
677 
678 	if (phase == P_TRAVEL) {
679 		if ((ExtFlags&PEF_DELAY) && extension_delay) {
680 			 extension_delay--;
681 			 UpdateSound();
682 			 return;
683 		}
684 	}
685 
686 	//reached target, and explodes now
687 	if (!Extension) {
688 		//there are no-effect projectiles, like missed arrows
689 		//Payload can redirect the projectile in case of projectile reflection
690 		if (phase == P_TRAVEL) {
691 			if(ExtFlags&PEF_DEFSPELL) {
692 				ApplyDefault();
693 			}
694 			StopSound();
695 			Payload();
696 			phase = P_TRAVEL2;
697 		}
698 		//freeze on target, this is recommended only for child projectiles
699 		//as the projectile won't go away on its own
700 		if(ExtFlags&PEF_FREEZE) {
701 			if(extension_delay) {
702 				if (extension_delay>0) {
703 					extension_delay--;
704 					UpdateSound();
705 				}
706 				return;
707 			}
708 		}
709 
710 		if (phase == P_TRAVEL2) {
711 			if (extension_delay) {
712 				 extension_delay--;
713 				 return;
714 			}
715 		}
716 
717 		if(ExtFlags&PEF_FADE) {
718 			TFlags &= ~PTF_TINT; //turn off area tint
719 			tint.a--;
720 			if(tint.a>0) {
721 				return;
722 			}
723 		}
724 	}
725 
726 	EndTravel();
727 }
728 
729 //Call this only if Extension exists!
CalculateExplosionCount()730 int Projectile::CalculateExplosionCount()
731 {
732 	int count = 0;
733 	Actor *act = area->GetActorByGlobalID(Caster);
734 	if(act) {
735 		if (Extension->AFlags&PAF_LEV_MAGE) {
736 			count = act->GetMageLevel();
737 		}
738 		else if (Extension->AFlags&PAF_LEV_CLERIC) {
739 			count = act->GetClericLevel();
740 		}
741 	}
742 
743 	if (!count) {
744 		 count = std::max<int>(1, Extension->ExplosionCount);
745 	}
746 	return count;
747 }
748 
EndTravel()749 void Projectile::EndTravel()
750 {
751 	StopSound();
752 	UpdateSound();
753 	if(!Extension) {
754 		phase = P_EXPIRED;
755 		return;
756 	}
757 
758 	//this flag says that the explosion should occur only when triggered
759 	if (Extension->AFlags&PAF_TRIGGER) {
760 		phase = P_TRIGGER;
761 		return;
762 	} else {
763 		phase = P_EXPLODING1;
764 	}
765 }
766 
767 //Note: trails couldn't be higher than VVC, but this shouldn't be a problem
AddTrail(const ieResRef BAM,const ieByte * pal) const768 int Projectile::AddTrail(const ieResRef BAM, const ieByte *pal) const
769 {
770 /*
771 	VEFObject *vef=gamedata->GetVEFObject(BAM,0);
772 	if (!vef) return 0;
773 	ScriptedAnimation *sca=vef->GetSingleObject();
774 	if (!sca) return 0;
775 */
776 	ScriptedAnimation* sca=gamedata->GetScriptedAnimation(BAM,0);
777 	if (!sca) return 0;
778 	VEFObject *vef = new VEFObject(sca);
779 
780 	if(pal) {
781 		if (ExtFlags & PEF_TINT) {
782 			const auto& pal32 = core->GetPalette32( pal[0] );
783 			sca->Tint = pal32[PALSIZE/2];
784 			sca->Transparency |= BlitFlags::COLOR_MOD;
785 		} else {
786 			for(int i=0;i<7;i++) {
787 				sca->SetPalette(pal[i], 4+i*PALSIZE);
788 			}
789 		}
790 	}
791 	sca->SetOrientation(Orientation);
792 	sca->PlayOnce();
793 	sca->SetBlend();
794 	sca->Pos = Pos;
795 	area->AddVVCell(vef);
796 	return sca->GetSequenceDuration(AI_UPDATE_TIME);
797 }
798 
DoStep(unsigned int walk_speed)799 void Projectile::DoStep(unsigned int walk_speed)
800 {
801 	if(pathcounter) {
802 		pathcounter--;
803 	} else {
804 		ClearPath();
805 	}
806 
807 	//intro trailing, drawn only once at the beginning
808 	if (pathcounter==0x7ffe) {
809 		for(int i=0;i<3;i++) {
810 			if(!TrailSpeed[i] && TrailBAM[i][0]) {
811 				extension_delay = AddTrail(TrailBAM[i], (ExtFlags&PEF_TINT)?Gradients:NULL);
812 			}
813 		}
814 	}
815 
816 	if (!path) {
817 		ChangePhase();
818 		return;
819 	}
820 
821 	if (Pos==Destination) {
822 		ClearPath();
823 		ChangePhase();
824 		return;
825 	}
826 
827 	//don't bug out on 0 smoke frequency like the original IE
828 	if ((TFlags&PTF_SMOKE) && SmokeSpeed) {
829 		if(!(pathcounter%SmokeSpeed)) {
830 			AddTrail(smokebam, SmokeGrad);
831 		}
832 	}
833 
834 	for(int i=0;i<3;i++) {
835 		if(TrailSpeed[i] && !(pathcounter%TrailSpeed[i])) {
836 			AddTrail(TrailBAM[i], (ExtFlags&PEF_TINT)?Gradients:NULL);
837 		}
838 	}
839 
840 	if (ExtFlags&PEF_LINE) {
841 		if(Extension) {
842 			//transform into an explosive line
843 			EndTravel();
844 		} else {
845 			if(!(ExtFlags&PEF_FREEZE) && travel[0]) {
846 				//switch to 'fading' phase
847 				//SetDelay(travel[0]->GetFrameCount());
848 				SetDelay(100);
849 			}
850 			ChangePhase();
851 		}
852 		//don't change position
853 		return;
854 	}
855 
856 	//path won't be calculated if speed==0
857 	walk_speed=1500/walk_speed;
858 	ieDword time = core->GetGame()->Ticks;
859 	if (!step) {
860 		step = path;
861 	}
862 
863 	const PathNode *start = step;
864 	while (step->Next && (( time - timeStartStep ) >= walk_speed)) {
865 		unsigned int count = Speed;
866 		while (step->Next && count) {
867 			step = step->Next;
868 			--count;
869 		}
870 		timeStartStep = timeStartStep + walk_speed;
871 
872 		if (!walk_speed) {
873 			break;
874 		}
875 	}
876 
877 	if (ExtFlags & PEF_CONTINUE) {
878 		// check for every step along the way
879 		// the test case is lightning bolt, its a long projectile,
880 		LineTarget(start, step->Next);
881 	}
882 
883 	SetOrientation (step->orient, false);
884 
885 	Pos.x=step->x;
886 	Pos.y=step->y;
887 	if (travel_handle) {
888 		travel_handle->SetPos(Pos.x, Pos.y);
889 	}
890 	if (!step->Next) {
891 		ClearPath();
892 		NewOrientation = Orientation;
893 		ChangePhase();
894 		return;
895 	}
896 	if (!walk_speed) {
897 		return;
898 	}
899 
900 	if (SFlags&PSF_SPARKS) {
901 		drawSpark = 1;
902 	}
903 
904 	if (step->Next->x > step->x)
905 		Pos.x += ( unsigned short )
906 			( ( step->Next->x - Pos.x ) * ( time - timeStartStep ) / walk_speed );
907 	else
908 		Pos.x -= ( unsigned short )
909 			( ( Pos.x - step->Next->x ) * ( time - timeStartStep ) / walk_speed );
910 	if (step->Next->y > step->y)
911 		Pos.y += ( unsigned short )
912 			( ( step->Next->y - Pos.y ) * ( time - timeStartStep ) / walk_speed );
913 	else
914 		Pos.y -= ( unsigned short )
915 			( ( Pos.y - step->Next->y ) * ( time - timeStartStep ) / walk_speed );
916 
917 }
918 
SetCaster(ieDword caster,int level)919 void Projectile::SetCaster(ieDword caster, int level)
920 {
921 	Caster=caster;
922 	Level=level;
923 }
924 
GetCaster() const925 ieDword Projectile::GetCaster() const
926 {
927 	return Caster;
928 }
929 
NextTarget(const Point & p)930 void Projectile::NextTarget(const Point &p)
931 {
932 	ClearPath();
933 	Destination = p;
934 	if (!Speed) {
935 		Pos = Destination;
936 		return;
937 	}
938 	NewOrientation = Orientation = GetOrient(Destination, Pos);
939 
940 	//this hack ensures that the projectile will go away after its time
941 	//by the time it reaches this part, it was already expired, so Target
942 	//needs to be cleared.
943 	if(ExtFlags&PEF_NO_TRAVEL) {
944 		Target = 0;
945 		Destination = Pos;
946 		return;
947 	}
948 
949 	int flags = (ExtFlags&PEF_BOUNCE) ? GL_REBOUND : GL_PASS;
950 	int stepping = (ExtFlags & PEF_LINE) ? Speed : 1;
951 	path = area->GetLine(Pos, Destination, stepping, Orientation, flags);
952 }
953 
SetTarget(const Point & p)954 void Projectile::SetTarget(const Point &p)
955 {
956 	Target = 0;
957 	NextTarget(p);
958 }
959 
SetTarget(ieDword tar,bool fake)960 void Projectile::SetTarget(ieDword tar, bool fake)
961 {
962 	Actor *target = NULL;
963 
964 	if (fake) {
965 		Target = 0;
966 		FakeTarget = tar;
967 		return;
968 	} else {
969 		Target = tar;
970 		target = area->GetActorByGlobalID(tar);
971 	}
972 
973 	if (!target) {
974 		phase = P_EXPIRED;
975 		return;
976 	}
977 
978 	if (ExtFlags&PEF_CONTINUE) {
979 		const Point& A = Origin;
980 		const Point& B = target->Pos;
981 		double angle = atan2(B.y - A.y, B.x - A.x);
982 		double adjustedRange = Feet2Pixels(Range, angle);
983 		Point C(A.x + adjustedRange * cos(angle), A.y + adjustedRange * sin(angle));
984 		SetTarget(C);
985 	} else {
986 		//replan the path in case the target moved
987 		if(target->Pos!=Destination) {
988 			NextTarget(target->Pos);
989 			return;
990 		}
991 
992 		//replan the path in case the source moved (only for line projectiles)
993 		if(ExtFlags&PEF_LINE) {
994 			Actor *c = area->GetActorByGlobalID(Caster);
995 			if(c && c->Pos!=Pos) {
996 				Pos=c->Pos;
997 				NextTarget(target->Pos);
998 			}
999 		}
1000 	}
1001 }
1002 
MoveTo(Map * map,const Point & Des)1003 void Projectile::MoveTo(Map *map, const Point &Des)
1004 {
1005 	area = map;
1006 	Origin = Des;
1007 	Pos = Des;
1008 	Destination = Des;
1009 }
1010 
ClearPath()1011 void Projectile::ClearPath()
1012 {
1013 	PathNode* thisNode = path;
1014 	while (thisNode) {
1015 		PathNode* nextNode = thisNode->Next;
1016 		delete( thisNode );
1017 		thisNode = nextNode;
1018 	}
1019 	path = NULL;
1020 	step = NULL;
1021 }
1022 
CalculateTargetFlag() const1023 int Projectile::CalculateTargetFlag() const
1024 {
1025 	//if there are any, then change phase to exploding
1026 	int flags = GA_NO_DEAD|GA_NO_UNSCHEDULED;
1027 	bool checkingEA = false;
1028 
1029 	if (Extension) {
1030 		if (Extension->AFlags&PAF_NO_WALL) {
1031 			flags|=GA_NO_LOS;
1032 		}
1033 
1034 		//projectiles don't affect dead/inanimate normally
1035 		if (Extension->AFlags&PAF_INANIMATE) {
1036 			flags&=~GA_NO_DEAD;
1037 		}
1038 
1039 		//affect only enemies or allies
1040 		switch (Extension->AFlags&PAF_TARGET) {
1041 		case PAF_ENEMY:
1042 			flags|=GA_NO_NEUTRAL|GA_NO_ALLY;
1043 			break;
1044 		case PAF_PARTY: //this doesn't exist in IE
1045 			flags|=GA_NO_ENEMY;
1046 			break;
1047 		case PAF_TARGET:
1048 			flags|=GA_NO_NEUTRAL|GA_NO_ENEMY;
1049 			break;
1050 		default:
1051 			return flags;
1052 		}
1053 		if (Extension->AFlags & PAF_TARGET) {
1054 			checkingEA = true;
1055 		}
1056 
1057 		//this is the only way to affect neutrals and enemies
1058 		if (Extension->APFlags&APF_INVERT_TARGET) {
1059 			flags^=(GA_NO_ALLY|GA_NO_ENEMY);
1060 		}
1061 	}
1062 
1063 	const Scriptable *caster = area->GetScriptableByGlobalID(Caster);
1064 	const Actor *act = nullptr;
1065 	if (caster && caster->Type == ST_ACTOR) {
1066 		act = (const Actor *) caster;
1067 	}
1068 	if (caster && (!checkingEA || (act && act->GetStat(IE_EA) < EA_GOODCUTOFF))) {
1069 		return flags;
1070 	}
1071 	// iwd2 ar6050 has doors casting chain lightning :)
1072 	if (caster && caster->Type != ST_ACTOR && checkingEA) {
1073 		return flags;
1074 	}
1075 	// make everyone an enemy of neutrals
1076 	if (act && checkingEA && act->GetStat(IE_EA) > EA_GOODCUTOFF && act->GetStat(IE_EA) < EA_EVILCUTOFF) {
1077 		if ((Extension->AFlags & PAF_TARGET) == PAF_ENEMY) return GA_NO_NEUTRAL | (flags & GA_NO_LOS);
1078 		if ((Extension->AFlags & PAF_TARGET) == PAF_TARGET) return GA_NO_ALLY | GA_NO_ENEMY | (flags & GA_NO_LOS);
1079 	}
1080 
1081 	return flags^(GA_NO_ALLY|GA_NO_ENEMY);
1082 }
1083 
1084 //get actors covered in area of trigger radius
CheckTrigger(unsigned int radius)1085 void Projectile::CheckTrigger(unsigned int radius)
1086 {
1087 	if (phase == P_TRIGGER) {
1088 		//special trigger flag, explode only if the trigger animation has
1089 		//passed a hardcoded sequence number
1090 		if (Extension->AFlags&PAF_TRIGGER_D) {
1091 			if (travel[Orientation]) {
1092 				int anim = travel[Orientation]->GetCurrentFrameIndex();
1093 				if (anim<30)
1094 					return;
1095 			}
1096 		}
1097 	}
1098 	if (area->GetActorInRadius(Pos, CalculateTargetFlag(), radius)) {
1099 		if (phase == P_TRIGGER) {
1100 			phase = P_EXPLODING1;
1101 			extension_delay = Extension->Delay;
1102 		}
1103 	} else if (phase == P_EXPLODING1) {
1104 		//the explosion is revoked
1105 		if (Extension->AFlags&PAF_SYNC) {
1106 			phase = P_TRIGGER;
1107 		}
1108 	}
1109 }
1110 
SetEffectsCopy(const EffectQueue * eq,const Point & source)1111 void Projectile::SetEffectsCopy(const EffectQueue *eq, const Point &source)
1112 {
1113 	delete effects;
1114 	if(!eq) {
1115 		effects=NULL;
1116 		return;
1117 	}
1118 	effects = eq->CopySelf();
1119 	effects->ModifyAllEffectSources(source);
1120 }
1121 
LineTarget() const1122 void Projectile::LineTarget() const
1123 {
1124 	LineTarget(path, nullptr);
1125 }
1126 
LineTarget(const PathNode * beg,const PathNode * end) const1127 void Projectile::LineTarget(const PathNode *beg, const PathNode *end) const
1128 {
1129 	if (!effects) {
1130 		return;
1131 	}
1132 
1133 	Actor *original = area->GetActorByGlobalID(Caster);
1134 	int targetFlags = CalculateTargetFlag();
1135 	const PathNode *iter = beg;
1136 
1137 	do {
1138 		const PathNode *first = iter;
1139 		const PathNode *last = iter;
1140 		unsigned int orient = first->orient;
1141 		while (iter && iter != end && iter->orient == orient) {
1142 			last = iter;
1143 			iter = iter->Next;
1144 		}
1145 
1146 		const Point s(short(first->x), short(first->y));
1147 		const Point d(short(last->x), short(last->y));
1148 		const std::vector<Actor *> &actors = area->GetAllActors();
1149 
1150 		for (Actor *target : actors) {
1151 			if (target->GetGlobalID() == Caster) {
1152 				continue;
1153 			}
1154 			if (!target->ValidTarget(targetFlags)) {
1155 				continue;
1156 			}
1157 			double t = 0.0;
1158 			if (PersonalLineDistance(s, d, target, &t) > 1) {
1159 				continue;
1160 			}
1161 			if (t < 0.0 && first->Parent != nullptr && first->Parent->orient == orient) {
1162 				// skip; assume we've hit the target before
1163 				continue;
1164 			} else if (t > 1.0 && last->Next != nullptr && last->Next->orient == orient) {
1165 				// skip; assume we'll hit it after
1166 				continue;
1167 			}
1168 
1169 			if (effects->CheckImmunity(target) > 0) {
1170 				EffectQueue *eff = effects->CopySelf();
1171 				eff->SetOwner(original);
1172 				if (ExtFlags & PEF_RGB) {
1173 					target->SetColorMod(0xff, RGBModifier::ADD, ColorSpeed, RGB);
1174 				}
1175 
1176 				eff->AddAllEffects(target, target->Pos);
1177 				delete eff;
1178 			}
1179 		}
1180 	} while (iter && iter != end);
1181 }
1182 
1183 //secondary projectiles target all in the explosion radius
SecondaryTarget()1184 void Projectile::SecondaryTarget()
1185 {
1186 	//fail will become true if the projectile utterly failed to find a target
1187 	//if the spell was already applied on explosion, ignore this
1188 	bool fail= !!(Extension->APFlags&APF_SPELLFAIL) && !(ExtFlags&PEF_DEFSPELL);
1189 	int mindeg = 0;
1190 	int maxdeg = 0;
1191 	int degOffset = 0;
1192 
1193 	//the AOE (area of effect) is cone shaped
1194 	if (Extension->AFlags&PAF_CONE) {
1195 		// see CharAnimations.cpp for a nice visualization of the orientation directions
1196 		// they start at 270° and go anticlockwise, so we have to rotate (reflect over y=-x) to match what math functions expect
1197 		// TODO: check if we can ignore this and use the angle between caster pos and target pos (are they still available here?)
1198 		char saneOrientation = 12 - Orientation;
1199 		if (saneOrientation < 0) saneOrientation = MAX_ORIENT + saneOrientation;
1200 
1201 		// for cone angles (widths) bigger than 22.5 we will always have a range of values greater than 360
1202 		// to normalize into [0,360] we use an orientation dependent factor that is then accounted for in later calculations
1203 		mindeg = (saneOrientation * (720 / MAX_ORIENT) - Extension->ConeWidth) / 2;
1204 		if (mindeg < 0) {
1205 			degOffset = -mindeg;
1206 			//mindeg = 0;
1207 		} else if (mindeg + Extension->ConeWidth > 360) {
1208 			degOffset = -(mindeg - 360 + Extension->ConeWidth);
1209 		}
1210 		mindeg += degOffset;
1211 		maxdeg = mindeg + Extension->ConeWidth;
1212 	}
1213 
1214 	if (Extension->DiceCount) {
1215 		//precalculate the maximum affected target count in case of PAF_AFFECT_ONE
1216 		extension_targetcount = core->Roll(Extension->DiceCount, Extension->DiceSize, 0);
1217 	} else {
1218 		//this is the default case (for original engine)
1219 		extension_targetcount = 1;
1220 	}
1221 
1222 	int radius = Extension->ExplosionRadius / 16;
1223 	std::vector<Actor *> actors = area->GetAllActorsInRadius(Pos, CalculateTargetFlag(), radius);
1224 	for (const Actor *actor : actors) {
1225 		ieDword targetID = actor->GetGlobalID();
1226 
1227 		//this flag is actually about ignoring the caster (who is at the center)
1228 		if ((SFlags & PSF_IGNORE_CENTER) && Caster == targetID) {
1229 			continue;
1230 		}
1231 
1232 		//IDS targeting for area projectiles
1233 		if (FailedIDS(actor)) {
1234 			continue;
1235 		}
1236 
1237 		if (Extension->AFlags&PAF_CONE) {
1238 			//cone never affects the caster
1239 			if (Caster == targetID) {
1240 				continue;
1241 			}
1242 			double xdiff = actor->Pos.x - Pos.x;
1243 			double ydiff = Pos.y - actor->Pos.y;
1244 			int deg;
1245 
1246 			// a dragon will definitely be easier to hit than a mouse
1247 			// but nothing checks the personal space of possible targets in the original either #384
1248 			if (ydiff) {
1249 				// ensure [0,360] range: transform [-180,180] from atan2, but also take orientation correction factor into account
1250 				deg = (int) (std::atan2(ydiff, xdiff) * 180/M_PI);
1251 				deg = ((deg % 360) + 360 + degOffset) % 360;
1252 			} else {
1253 				if (xdiff < 0) {
1254 					deg = 180;
1255 				} else {
1256 					deg = 0;
1257 				}
1258 			}
1259 
1260 			//not in the right sector of circle
1261 			if (mindeg>deg || maxdeg<deg) {
1262 				continue;
1263 			}
1264 		}
1265 
1266 		Projectile *pro = server->GetProjectileByIndex(Extension->ExplProjIdx);
1267 		pro->SetEffectsCopy(effects, Pos);
1268 		//copy the additional effects reference to the child projectile
1269 		//but only when there is a spell to copy
1270 		if (SuccSpell[0])
1271 			memcpy(pro->SuccSpell, SuccSpell, sizeof(ieResRef) );
1272 		pro->SetCaster(Caster, Level);
1273 		//this is needed to apply the success spell on the center point
1274 		pro->SetTarget(Pos);
1275 		//TODO:actually some of the splash projectiles are a good example of faketarget
1276 		//projectiles (that don't follow the target, but still hit)
1277 		area->AddProjectile(pro, Pos, targetID, false);
1278 		fail=false;
1279 
1280 		//we already got one target affected in the AOE, this flag says
1281 		//that was enough (the GemRB extension can repeat this a random time (x d y)
1282 		if(Extension->AFlags&PAF_AFFECT_ONE) {
1283 			if (extension_targetcount<=0) {
1284 				break;
1285 			}
1286 			//if target counting is per HD and this target is an actor, use the xp level field
1287 			//otherwise count it as one
1288 			if (Extension->APFlags & APF_COUNT_HD) {
1289 				extension_targetcount -= actor->GetXPLevel(true);
1290 			} else {
1291 				extension_targetcount--;
1292 			}
1293 		}
1294 	}
1295 
1296 	//In case of utter failure, apply a spell of the same name on the caster
1297 	//this feature is used by SCHARGE, PRTL_OP and PRTL_CL in the HoW pack
1298 	if(fail) {
1299 		ApplyDefault();
1300 	}
1301 }
1302 
Update()1303 int Projectile::Update()
1304 {
1305 	//if reached target explode
1306 	//if target doesn't exist expire
1307 	if (phase == P_EXPIRED) {
1308 		return 0;
1309 	}
1310 	if (phase == P_UNINITED) {
1311 		Setup();
1312 	}
1313 
1314 	int pause = core->IsFreezed();
1315 	if (pause) {
1316 		return 1;
1317 	}
1318 
1319 	const Game *game = core->GetGame();
1320 	if (game && game->IsTimestopActive() && !(TFlags&PTF_TIMELESS)) {
1321 		return 1;
1322 	}
1323 
1324 	//recreate path if target has moved
1325 	if(Target) {
1326 		SetTarget(Target, false);
1327 	}
1328 
1329 	if (phase == P_TRAVEL || phase == P_TRAVEL2) {
1330 		DoStep(Speed);
1331 	}
1332 	return 1;
1333 }
1334 
Draw(const Region & viewport)1335 void Projectile::Draw(const Region& viewport)
1336 {
1337 	switch (phase) {
1338 		case P_UNINITED:
1339 			return;
1340 		case P_TRIGGER: case P_EXPLODING1:case P_EXPLODING2:
1341 			//This extension flag is to enable the travel projectile at
1342 			//trigger/explosion time
1343 			if (Extension->AFlags&PAF_VISIBLE) {
1344 			//if (!Extension || (Extension->AFlags&PAF_VISIBLE)) {
1345 				DrawTravel(viewport);
1346 			}
1347 			/*
1348 			if (!Extension) {
1349 				return;
1350 			}*/
1351 			CheckTrigger(Extension->TriggerRadius);
1352 			if (phase == P_EXPLODING1 || phase == P_EXPLODING2) {
1353 				DrawExplosion(viewport);
1354 			}
1355 			break;
1356 		case P_TRAVEL: case P_TRAVEL2:
1357 			//There is no Extension for simple traveling projectiles!
1358 			DrawTravel(viewport);
1359 			return;
1360 		default:
1361 			DrawExploded(viewport);
1362 			return;
1363 	}
1364 }
1365 
DrawChildren(const Region & vp)1366 bool Projectile::DrawChildren(const Region& vp)
1367 {
1368 	bool drawn = false;
1369 
1370 	if (children) {
1371 		for(int i=0;i<child_size;i++){
1372 			if(children[i]) {
1373 				if (children[i]->Update()) {
1374 					children[i]->DrawTravel(vp);
1375 					drawn = true;
1376 				} else {
1377 					delete children[i];
1378 					children[i]=NULL;
1379 				}
1380 			}
1381 		}
1382 	}
1383 
1384 	return drawn;
1385 }
1386 
1387 //draw until all children expire
DrawExploded(const Region & viewport)1388 void Projectile::DrawExploded(const Region& viewport)
1389 {
1390 	if (DrawChildren(viewport)) {
1391 		return;
1392 	}
1393 	phase = P_EXPIRED;
1394 }
1395 
SpawnFragment(Point & dest)1396 void Projectile::SpawnFragment(Point &dest)
1397 {
1398 	Projectile *pro = server->GetProjectileByIndex(Extension->FragProjIdx);
1399 	if (pro) {
1400 //		if (Extension->AFlags&PAF_SECONDARY) {
1401 //				pro->SetEffectsCopy(effects);
1402 //		}
1403 		pro->SetCaster(Caster, Level);
1404 		if (pro->ExtFlags&PEF_RANDOM) {
1405 			dest.x+=core->Roll(1,Extension->TileX, -Extension->TileX/2);
1406 			dest.y+=core->Roll(1,Extension->TileY, -Extension->TileY/2);
1407 		}
1408 		area->AddProjectile(pro, dest, dest);
1409 	}
1410 }
1411 
DrawExplosion(const Region & vp)1412 void Projectile::DrawExplosion(const Region& vp)
1413 {
1414 	//This seems to be a needless safeguard
1415 	if (!Extension) {
1416 		phase = P_EXPIRED;
1417 		return;
1418 	}
1419 
1420 	StopSound();
1421 	DrawChildren(vp);
1422 
1423 	int pause = core->IsFreezed();
1424 	if (pause) {
1425 		return;
1426 	}
1427 
1428 	//Delay explosion, it could even be revoked with PAF_SYNC (see skull trap)
1429 	if (extension_delay) {
1430 		extension_delay--;
1431 		return;
1432 	}
1433 
1434 	//0 and 1 have the same effect (1 explosion)
1435 	if (extension_explosioncount) {
1436 		extension_explosioncount--;
1437 	}
1438 
1439 	//Line targets are actors between source and destination point
1440 	if(ExtFlags&PEF_LINE) {
1441 		if (Target) {
1442 			SetTarget(Target, false);
1443 		}
1444 		LineTarget();
1445 	}
1446 
1447 	int apflags = Extension->APFlags;
1448 	int aoeflags = Extension->AFlags;
1449 
1450 	//no idea what is PAF_SECONDARY
1451 	//probably it is to alter some behaviour in the secondary
1452 	//projectile generation
1453 	//In trapskul.pro it isn't set, yet it has a secondary (invisible) projectile
1454 	//All area effects are created by secondary projectiles
1455 
1456 	//the secondary projectile will target everyone in the area of effect
1457 	SecondaryTarget();
1458 
1459 	//draw fragment graphics animation at the explosion center
1460 	if (aoeflags&PAF_FRAGMENT) {
1461 		//there is a character animation in the center of the explosion
1462 		//which will go towards the edges (flames, etc)
1463 		//Extension->ExplColor fake color for single shades (blue,green,red flames)
1464 		//Extension->FragAnimID the animation id for the character animation
1465 		//This color is not used in the original game
1466 		Point pos = Pos - vp.Origin();
1467 		area->Sparkle(0, Extension->ExplColor, SPARKLE_EXPLOSION, pos, Extension->FragAnimID, GetZPos());
1468 	}
1469 
1470 	if(Shake) {
1471 		core->timer.SetScreenShake(Point(Shake, Shake), Shake);
1472 		Shake = 0;
1473 	}
1474 
1475 	//the center of the explosion could be another projectile played over the target
1476 	//warning: this projectile doesn't inherit any effects, so its payload function
1477 	//won't be doing anything (any effect of PAF_SECONDARY?)
1478 
1479   //remove PAF_SECONDARY if it is buggy, but that will break the 'HOLD' projectile
1480 	if ((Extension->AFlags&PAF_SECONDARY) && Extension->FragProjIdx) {
1481 		if (apflags&APF_TILED) {
1482 			int i,j;
1483 			int radius = Extension->ExplosionRadius;
1484 
1485 			for (i=-radius;i<radius;i+=Extension->TileX) {
1486 				for(j=-radius;j<radius;j+=Extension->TileY) {
1487 					if (i*i+j*j<radius*radius) {
1488 						Point p(Pos.x+i, Pos.y+j);
1489 						SpawnFragment(p);
1490 					}
1491 				}
1492 			}
1493 		} else {
1494 			SpawnFragment(Pos);
1495 		}
1496 	}
1497 
1498 	//the center of the explosion is based on hardcoded explosion type (this is fireball.cpp in the original engine)
1499 	//these resources are listed in areapro.2da and served by ProjectileServer.cpp
1500 
1501 	//draw it only once, at the time of explosion
1502 	if (phase==P_EXPLODING1) {
1503 		core->GetAudioDrv()->Play(Extension->SoundRes, SFX_CHAN_MISSILE, Pos.x, Pos.y);
1504 		//play VVC in center
1505 		//FIXME: make it possible to play VEF too?
1506 		if (aoeflags&PAF_VVC) {
1507 			ScriptedAnimation* vvc = gamedata->GetScriptedAnimation(Extension->VVCRes, false);
1508 			if (vvc) {
1509 				if (apflags & APF_VVCPAL) {
1510 					//if the palette is used as tint (as opposed to clown colorset) tint the vvc
1511 					if (apflags & APF_TINT) {
1512 						const auto& pal32 = core->GetPalette32(Extension->ExplColor);
1513 						vvc->Tint = pal32[PALSIZE/2];
1514 						vvc->Transparency |= BlitFlags::COLOR_MOD;
1515 					} else {
1516 						vvc->SetPalette(Extension->ExplColor);
1517 					}
1518 				}
1519 				//if the trail oriented, then the center is oriented too
1520 				if (ExtFlags&PEF_TRAIL) {
1521 					vvc->SetOrientation(Orientation);
1522 				}
1523 				vvc->Pos = Pos;
1524 				vvc->PlayOnce();
1525 				vvc->SetBlend();
1526 				//quick hack to use the single object envelope
1527 				area->AddVVCell(new VEFObject(vvc));
1528 			}
1529 			// bg2 comet has the explosion split into two vvcs, with just a starting cycle difference
1530 			// until we actually need two vvc fields in the extension, let's just hack around it
1531 			if (!stricmp(Extension->VVCRes, "SPCOMEX1")) {
1532 				ScriptedAnimation* vvc = gamedata->GetScriptedAnimation("SPCOMEX2", false);
1533 				if (vvc) {
1534 					vvc->Pos = Pos;
1535 					vvc->PlayOnce();
1536 					vvc->SetBlend();
1537 					area->AddVVCell(new VEFObject(vvc));
1538 				}
1539 			}
1540 		}
1541 
1542 		phase=P_EXPLODING2;
1543 	} else {
1544 		core->GetAudioDrv()->Play(Extension->AreaSound, SFX_CHAN_MISSILE, Pos.x, Pos.y);
1545 	}
1546 
1547 	//the spreading animation is in the first column
1548 	const char *tmp = Extension->Spread;
1549 	if(tmp[0]) {
1550 		//i'm unsure about the need of this
1551 		//returns if the explosion animation is fake coloured
1552 		if (!children) {
1553 			child_size = (Extension->ExplosionRadius+15)/16;
1554 			//more sprites if the whole area needs to be filled
1555 			if (apflags&APF_FILL) child_size*=2;
1556 			if (apflags&APF_SPREAD) child_size*=2;
1557 			if (apflags&APF_BOTH) child_size/=2; //intentionally decreases
1558 			if (apflags&APF_MORE) child_size*=2;
1559 			children = (Projectile **) calloc(sizeof(Projectile *), child_size);
1560 		}
1561 
1562 		//zero cone width means single line area of effect
1563 		if((aoeflags&PAF_CONE) && !Extension->ConeWidth) {
1564 			child_size = 1;
1565 		}
1566 
1567 		int initial = child_size;
1568 
1569 		for(int i=0;i<initial;i++) {
1570 			//leave this slot free, it is residue from the previous flare up
1571 			if (children[i])
1572 				continue;
1573 			if(apflags&APF_BOTH) {
1574 				if(RAND(0,1)) {
1575 					tmp = Extension->Secondary;
1576 				} else {
1577 					tmp = Extension->Spread;
1578 				}
1579 			}
1580 			//create a custom projectile with single traveling effect
1581 			Projectile *pro = server->CreateDefaultProjectile((unsigned int) ~0);
1582 			strnlwrcpy(pro->BAMRes1, tmp, 8);
1583 			if (ExtFlags&PEF_TRAIL) {
1584 				pro->Aim = Aim;
1585 			}
1586 			pro->SetEffects(NULL);
1587 			//calculate the child projectile's target point, it is either
1588 			//a perimeter or an inside point of the explosion radius
1589 			int rad = Extension->ExplosionRadius;
1590 			Point newdest;
1591 
1592 			if (apflags&APF_FILL) {
1593 				rad=core->Roll(1,rad,0);
1594 			}
1595 			int max = 360;
1596 			int add = 0;
1597 			if (aoeflags&PAF_CONE) {
1598 				max=Extension->ConeWidth;
1599 				add=(Orientation*45-max)/2;
1600 			}
1601 			max=core->Roll(1,max,add);
1602 			double degree=max*M_PI/180;
1603 			newdest.x = (int) -(rad * std::sin(degree) );
1604 			newdest.y = (int) (rad * std::cos(degree) );
1605 
1606 			//these fields and flags are always inherited by all children
1607 			pro->Speed = Speed;
1608 			pro->ExtFlags = ExtFlags&(PEF_HALFTRANS|PEF_CYCLE|PEF_RGB);
1609 			pro->RGB = RGB;
1610 			pro->ColorSpeed = ColorSpeed;
1611 
1612 			if (apflags&APF_FILL) {
1613 				int delay;
1614 
1615 				//a bit of difference in case crowding is needed
1616 				//make this a separate flag if speed difference
1617 				//is not always wanted
1618 				pro->Speed-=RAND(0,7);
1619 
1620 				delay = Extension->Delay*extension_explosioncount;
1621 				if(apflags&APF_BOTH) {
1622 					if (delay) {
1623 						delay = RAND(0, delay-1);
1624 					}
1625 				}
1626 				//this needs to be commented out for ToB horrid wilting
1627 				//if(ExtFlags&PEF_FREEZE) {
1628 					delay += Extension->Delay;
1629 				//}
1630 				pro->SetDelay(delay);
1631 			}
1632 
1633 			newdest.x+=Destination.x;
1634 			newdest.y+=Destination.y;
1635 
1636 			if (apflags&APF_SCATTER) {
1637 				pro->MoveTo(area, newdest);
1638 			} else {
1639 				pro->MoveTo(area, Pos);
1640 			}
1641 			pro->SetTarget(newdest);
1642 			pro->autofree=true;
1643 
1644 			//sets up the gradient color for the explosion animation
1645 			if (apflags&(APF_PALETTE|APF_TINT) ) {
1646 				pro->SetGradient(Extension->ExplColor, !(apflags&APF_PALETTE));
1647 			}
1648 			//i'm unsure if we need blending for all anims or just the tinted ones
1649 			pro->TFlags|=PTF_TRANS;
1650 			//random frame is needed only for some of these, make it an areapro flag?
1651 			if( !(ExtFlags&PEF_CYCLE) || (ExtFlags&PEF_RANDOM) ) {
1652 				pro->ExtFlags|=PEF_RANDOM;
1653 			}
1654 
1655 			pro->Setup();
1656 
1657 			// currently needed by bg2/how Web (less obvious in bg1)
1658 			// TODO: original behaviour was: play once, wait in final frame, hide, wait, repeat
1659 			if (pro->travel[0] && Extension->APFlags & APF_PLAYONCE) {
1660 				// set on all orients while we don't force one for single-orientation animations (see CreateOrientedAnimations)
1661 				for (int i=0; i<MAX_ORIENT; i++) {
1662 					if (pro->travel[i]) pro->travel[i]->Flags |= A_ANI_PLAYONCE;
1663 				}
1664 			}
1665 
1666 			children[i]=pro;
1667 		}
1668 	}
1669 
1670 	if (extension_explosioncount) {
1671 		extension_delay=Extension->Delay;
1672 	} else {
1673 		phase = P_EXPLODED;
1674 	}
1675 }
1676 
GetTravelPos(int face) const1677 int Projectile::GetTravelPos(int face) const
1678 {
1679 	if (travel[face]) {
1680 		return travel[face]->GetCurrentFrameIndex();
1681 	}
1682 	return 0;
1683 }
1684 
GetShadowPos(int face) const1685 int Projectile::GetShadowPos(int face) const
1686 {
1687 	if (shadow[face]) {
1688 		return shadow[face]->GetCurrentFrameIndex();
1689 	}
1690 	return 0;
1691 }
1692 
SetPos(int face,int frame1,int frame2)1693 void Projectile::SetPos(int face, int frame1, int frame2)
1694 {
1695 	if (travel[face]) {
1696 		travel[face]->SetPos(frame1);
1697 	}
1698 	if (shadow[face]) {
1699 		shadow[face]->SetPos(frame2);
1700 	}
1701 }
1702 
1703 //recalculate target and source points (perpendicular bisector)
SetupWall()1704 void Projectile::SetupWall()
1705 {
1706 	Point p1, p2;
1707 
1708 	p1.x=(Pos.x+Destination.x)/2;
1709 	p1.y=(Pos.y+Destination.y)/2;
1710 
1711 	p2.x=p1.x+(Pos.y-Destination.y);
1712 	p2.y=p1.y+(Pos.x-Destination.x);
1713 	Pos=p1;
1714 	SetTarget(p2);
1715 }
1716 
DrawLine(const Region & vp,int face,BlitFlags flag)1717 void Projectile::DrawLine(const Region& vp, int face, BlitFlags flag)
1718 {
1719 	Game *game = core->GetGame();
1720 	PathNode *iter = path;
1721 	Holder<Sprite2D> frame;
1722 	if (game && game->IsTimestopActive() && !(TFlags&PTF_TIMELESS)) {
1723 		frame = travel[face]->LastFrame();
1724 		flag |= BlitFlags::GREY;
1725 	} else {
1726 		frame = travel[face]->NextFrame();
1727 	}
1728 
1729 	Color tint2 = tint;
1730 	if (game) game->ApplyGlobalTint(tint2, flag);
1731 	while(iter) {
1732 		Point pos(iter->x - vp.x, iter->y - vp.y);
1733 
1734 		if (SFlags&PSF_FLYING) {
1735 			pos.y-=FLY_HEIGHT;
1736 		}
1737 
1738 		Draw(frame, pos, flag, tint2);
1739 		iter = iter->Next;
1740 	}
1741 }
1742 
DrawTravel(const Region & viewport)1743 void Projectile::DrawTravel(const Region& viewport)
1744 {
1745 	Game *game = core->GetGame();
1746 	BlitFlags flag;
1747 
1748 	if(ExtFlags&PEF_HALFTRANS) {
1749 		flag = BlitFlags::HALFTRANS;
1750 	} else {
1751 		flag = BlitFlags::NONE;
1752 	}
1753 
1754 	//static tint (use the tint field)
1755 	if(ExtFlags&PEF_TINT) {
1756 		flag |= BlitFlags::COLOR_MOD;
1757 	}
1758 
1759 	//Area tint
1760 	if (TFlags&PTF_TINT) {
1761 		tint = area->LightMap->GetPixel( Pos.x / 16, Pos.y / 12);
1762 		tint.a = 255;
1763 		flag |= BlitFlags::COLOR_MOD;
1764 	}
1765 
1766 	unsigned int face = GetNextFace();
1767 	if (face!=Orientation) {
1768 		SetPos(face, GetTravelPos(face), GetShadowPos(face));
1769 	}
1770 
1771 	Point pos = Pos - viewport.Origin();
1772 	if(bend && phase == P_TRAVEL && Origin != Destination) {
1773 		double total_distance = Distance(Origin, Destination);
1774 		double travelled_distance = Distance(Origin, Pos);
1775 
1776 		// distance travelled along the line, from 0.0 to 1.0
1777 		double travelled = travelled_distance / total_distance;
1778 		assert(travelled <= 1.0);
1779 
1780 		// input to sin(): 0 to pi gives us an arc
1781 		double arc_angle = travelled * M_PI;
1782 
1783 		// calculate the distance between the arc and the current pos
1784 		// (this could use travelled and a larger constant multiplier,
1785 		// to make the arc size fixed rather than relative to the total
1786 		// distance to travel)
1787 		double length_of_normal = travelled_distance * std::sin(arc_angle) * 0.3 * ((bend / 2) + 1);
1788 		if (bend % 2) length_of_normal = -length_of_normal;
1789 
1790 		// adjust the to-be-rendered point by that distance
1791 		double x_vector = (Destination.x - Origin.x) / total_distance,
1792 			y_vector = (Destination.y - Origin.y) / total_distance;
1793 		Point newpos = pos;
1794 		newpos.x += (short)(y_vector*length_of_normal);
1795 		newpos.y -= (short)(x_vector*length_of_normal);
1796 		pos = newpos;
1797 	}
1798 
1799 	// set up the tint for the rest of the blits, but don't overwrite the saved one
1800 	Color tint2 = tint;
1801 	BlitFlags flags = flag;
1802 
1803 	if (TFlags&PTF_TINT && game) {
1804 		game->ApplyGlobalTint(tint2, flags);
1805 	}
1806 
1807 	if (light) {
1808 		Draw(light, pos, flags^flag, tint2);
1809 	}
1810 
1811 	if (ExtFlags&PEF_POP) {
1812 			//draw pop in/hold/pop out animation sequences
1813 			Holder<Sprite2D> frame;
1814 			if (game && game->IsTimestopActive() && !(TFlags&PTF_TIMELESS)) {
1815 				frame = travel[face]->LastFrame();
1816 				flags |= BlitFlags::GREY;
1817 			} else {
1818 				if (ExtFlags&PEF_UNPOP) {
1819 					frame = shadow[0]->NextFrame();
1820 					if (shadow[0]->endReached) {
1821 						ExtFlags &= ~PEF_UNPOP;
1822 					}
1823 				} else {
1824 					frame = travel[0]->NextFrame();
1825 					if (travel[0]->endReached) {
1826 						travel[0]->playReversed = true;
1827 						travel[0]->SetPos(0);
1828 						ExtFlags |= PEF_UNPOP;
1829 						frame = shadow[0]->NextFrame();
1830 					}
1831 				}
1832 			}
1833 			Draw(frame, pos, flags, tint2);
1834 			return;
1835 	}
1836 
1837 	if (ExtFlags&PEF_LINE) {
1838 		DrawLine(viewport, face, flag);
1839 		return;
1840 	}
1841 
1842 	if (shadow[face]) {
1843 		Holder<Sprite2D> frame = shadow[face]->NextFrame();
1844 		Draw(frame, pos, flags, tint2);
1845 	}
1846 
1847 	pos.y-=GetZPos();
1848 
1849 	if (ExtFlags&PEF_PILLAR) {
1850 		//draw all frames simultaneously on top of each other
1851 		for(int i=0;i<Aim;i++) {
1852 			if (travel[i]) {
1853 				Holder<Sprite2D> frame = travel[i]->NextFrame();
1854 				Draw(frame, pos, flags, tint2);
1855 				pos.y-=frame->Frame.y;
1856 			}
1857 		}
1858 	} else {
1859 		if (travel[face]) {
1860 			Holder<Sprite2D> frame;
1861 			if (game && game->IsTimestopActive() && !(TFlags&PTF_TIMELESS)) {
1862 				frame = travel[face]->LastFrame();
1863 				flags |= BlitFlags::GREY; // move higher if it interferes with other tints badly
1864 			} else {
1865 				frame = travel[face]->NextFrame();
1866 			}
1867 			Draw(frame, pos, flags, tint2);
1868 		}
1869 	}
1870 
1871 	if (drawSpark) {
1872 		area->Sparkle(0,SparkColor, SPARKLE_EXPLOSION, pos, 0, GetZPos() );
1873 		drawSpark = 0;
1874 	}
1875 
1876 }
1877 
GetZPos() const1878 int Projectile::GetZPos() const
1879 {
1880 	return ZPos;
1881 }
1882 
SetIdentifiers(const char * resref,ieWord id)1883 void Projectile::SetIdentifiers(const char *resref, ieWord id)
1884 {
1885 	strnuprcpy(name, resref, 8);
1886 	type=id;
1887 }
1888 
PointInRadius(const Point & p) const1889 bool Projectile::PointInRadius(const Point &p) const
1890 {
1891 	switch(phase) {
1892 		//better not trigger on projectiles unset/expired
1893 		case P_EXPIRED:
1894 		case P_UNINITED: return false;
1895 		case P_TRAVEL:
1896 			if(p.x==Pos.x && p.y==Pos.y) return true;
1897 			return false;
1898 		default:
1899 			if(p.x==Pos.x && p.y==Pos.y) return true;
1900 			if (!Extension) return false;
1901 			if (Distance(p,Pos)<Extension->ExplosionRadius) return true;
1902 	}
1903 	return false;
1904 }
1905 
1906 //Set gradient color, if type is true then it is static tint, otherwise it is paletted color
SetGradient(int gradient,bool type)1907 void Projectile::SetGradient(int gradient, bool type)
1908 {
1909 	//gradients are unsigned chars, so this works
1910 	memset(Gradients, gradient, 7);
1911 	if (type) {
1912 		ExtFlags|=PEF_TINT;
1913 	} else {
1914 		TFlags |= PTF_COLOUR;
1915 	}
1916 }
1917 
StaticTint(const Color & newtint)1918 void Projectile::StaticTint(const Color &newtint)
1919 {
1920 	tint = newtint;
1921 	TFlags &= ~PTF_TINT; //turn off area tint
1922 }
1923 
GetPhase() const1924 int Projectile::GetPhase() const
1925 {
1926 	return phase;
1927 }
1928 
Cleanup()1929 void Projectile::Cleanup()
1930 {
1931 	//neutralise the payload
1932 	delete effects;
1933 	effects = NULL;
1934 	//diffuse the projectile
1935 	phase=P_EXPIRED;
1936 }
1937 
Draw(Holder<Sprite2D> spr,const Point & p,BlitFlags flags,Color tint) const1938 void Projectile::Draw(Holder<Sprite2D> spr, const Point& p, BlitFlags flags, Color tint) const
1939 {
1940 	Video *video = core->GetVideoDriver();
1941 	PaletteHolder pal = (spr->Bpp == 8) ? palette : nullptr;
1942 	if (flags & BlitFlags::COLOR_MOD) {
1943 		// FIXME: this may not apply universally
1944 		flags |= BlitFlags::ALPHA_MOD;
1945 	}
1946 	video->BlitGameSpriteWithPalette(spr, pal, p, flags|BlitFlags::BLENDED, tint);
1947 }
1948 
1949 }
1950