1 // WL_ACT2.C
2
3 #include <stdio.h>
4 #include <climits>
5 #include <math.h>
6 #include "actor.h"
7 #include "m_random.h"
8 #include "wl_act.h"
9 #include "wl_def.h"
10 #include "wl_menu.h"
11 #include "id_ca.h"
12 #include "id_sd.h"
13 #include "id_vl.h"
14 #include "id_vh.h"
15 #include "id_us.h"
16 #include "language.h"
17 #include "thingdef/thingdef.h"
18 #include "thingdef/thingdef_expression.h"
19 #include "wl_agent.h"
20 #include "wl_draw.h"
21 #include "wl_game.h"
22 #include "wl_state.h"
23
24 static const angle_t dirangle[9] = {0,ANGLE_45,2*ANGLE_45,3*ANGLE_45,4*ANGLE_45,
25 5*ANGLE_45,6*ANGLE_45,7*ANGLE_45,0};
26
CheckDoorMovement(AActor * actor)27 static inline bool CheckDoorMovement(AActor *actor)
28 {
29 MapTile::Side direction;
30 switch(actor->dir)
31 {
32 default:
33 return false;
34 case north:
35 direction = MapTile::South;
36 break;
37 case south:
38 direction = MapTile::North;
39 break;
40 case east:
41 direction = MapTile::West;
42 break;
43 case west:
44 direction = MapTile::East;
45 break;
46 }
47
48 if(actor->distance < 0) // Waiting for door?
49 {
50 MapSpot spot = map->GetSpot(actor->tilex, actor->tiley, 0);
51 spot = spot->GetAdjacent(direction, true);
52 if(spot->slideAmount[direction] != 0xffff)
53 return true;
54 // Door is open, go on
55 actor->distance = TILEGLOBAL;
56 TryWalk(actor);
57 }
58 return false;
59 }
60
A_Face(AActor * self,AActor * target,angle_t maxturn)61 void A_Face(AActor *self, AActor *target, angle_t maxturn)
62 {
63 double angle = atan2 ((double) (target->x - self->x), (double) (target->y - self->y));
64 if (angle<0)
65 angle = (M_PI*2+angle);
66 angle_t iangle = (angle_t) (angle*ANGLE_180/M_PI) - ANGLE_90;
67
68 if(maxturn > 0 && maxturn < self->angle - iangle)
69 {
70 if(self->angle > iangle)
71 {
72 if(self->angle - iangle < ANGLE_180)
73 self->angle -= maxturn;
74 else
75 self->angle += maxturn;
76 }
77 else
78 {
79 if(iangle - self->angle < ANGLE_180)
80 self->angle += maxturn;
81 else
82 self->angle -= maxturn;
83 }
84 }
85 else
86 self->angle = iangle;
87 }
88
89 //==========================================================================
90 //
91 // P_AproxDistance
92 //
93 // Gives an estimation of distance (not exact)
94 // From Doom.
95 //
96 //==========================================================================
97
P_AproxDistance(fixed dx,fixed dy)98 fixed P_AproxDistance (fixed dx, fixed dy)
99 {
100 dx = abs(dx);
101 dy = abs(dy);
102 return (dx < dy) ? dx+dy-(dx>>1) : dx+dy-(dy>>1);
103 }
104
105 /*
106 =============================================================================
107
108 LOCAL CONSTANTS
109
110 =============================================================================
111 */
112
113 #define BJRUNSPEED 2048
114 #define BJJUMPSPEED 680
115
116 /*
117 =============================================================================
118
119 LOCAL VARIABLES
120
121 =============================================================================
122 */
123
124
125 dirtype dirtable[9] = {northwest,north,northeast,west,nodir,east,
126 southwest,south,southeast};
127
128 /*
129 ===================
130 =
131 = ProjectileTryMove
132 =
133 = returns true if move ok
134 ===================
135 */
136
ProjectileTryMove(AActor * ob)137 bool ProjectileTryMove (AActor *ob)
138 {
139 int xl,yl,xh,yh,x,y;
140 MapSpot check;
141
142 xl = (ob->x-ob->radius) >> TILESHIFT;
143 yl = (ob->y-ob->radius) >> TILESHIFT;
144
145 xh = (ob->x+ob->radius) >> TILESHIFT;
146 yh = (ob->y+ob->radius) >> TILESHIFT;
147
148 //
149 // check for solid walls
150 //
151 for (y=yl;y<=yh;y++)
152 for (x=xl;x<=xh;x++)
153 {
154 const bool checkLines[4] =
155 {
156 (ob->x+ob->radius) > ((x+1)<<TILESHIFT),
157 (ob->y-ob->radius) < (y<<TILESHIFT),
158 (ob->x-ob->radius) < (x<<TILESHIFT),
159 (ob->y+ob->radius) > ((y+1)<<TILESHIFT)
160 };
161 check = map->GetSpot(x, y, 0);
162 if (check->tile)
163 {
164 for(unsigned short i = 0;i < 4;++i)
165 {
166 if(check->slideAmount[i] != 0xFFFF && checkLines[i])
167 return false;
168 }
169 }
170 }
171
172 return true;
173 }
174
175 /*
176 =================
177 =
178 = T_Projectile
179 =
180 =================
181 */
182
183 static FRandom pr_explodemissile("ExplodeMissile");
T_ExplodeProjectile(AActor * self,AActor * target)184 void T_ExplodeProjectile(AActor *self, AActor *target)
185 {
186 PlaySoundLocActor(self->deathsound, self);
187
188 const Frame *deathstate = NULL;
189 if(target && (target->flags & FL_SHOOTABLE)) // Fleshy!
190 deathstate = self->FindState(NAME_XDeath);
191 if(!deathstate)
192 deathstate = self->FindState(NAME_Death);
193
194 if(deathstate)
195 {
196 self->flags &= ~FL_MISSILE;
197 self->SetState(deathstate);
198
199 if((self->flags & FL_RANDOMIZE) && self->ticcount > 0)
200 {
201 self->ticcount -= pr_explodemissile() & 7;
202 if(self->ticcount < 1)
203 self->ticcount = 1;
204 }
205 }
206 else
207 self->Destroy();
208 }
209
T_Projectile(AActor * self)210 void T_Projectile (AActor *self)
211 {
212 int steps = 1;
213 fixed movex = self->velx;
214 fixed movey = self->vely;
215
216 // Projectiles can't move faster than their radius in a tic or collision
217 // detection can be off.
218 {
219 fixed maxmove = self->radius - FRACUNIT/64;
220 if(maxmove <= 0) // Really small projectile? Prevent problems with division
221 maxmove = FRACUNIT/2;
222
223 fixed vel = MAX(abs(movex), abs(movey));
224 if(vel > maxmove)
225 steps = 1 + vel / maxmove;
226
227 movex /= steps;
228 movey /= steps;
229 }
230
231 AActor *lastHit = NULL; // For ripping, so we only hit an actor once per tic
232 do
233 {
234 self->x += movex;
235 self->y += movey;
236
237 if (!ProjectileTryMove (self))
238 {
239 T_ExplodeProjectile(self, NULL);
240 return;
241 }
242
243 const bool playermissile = !!(self->flags & FL_PLAYERMISSILE);
244 AActor::Iterator iter = AActor::GetIterator();
245 while(iter.Next())
246 {
247 AActor *check = iter;
248 if(check == self)
249 continue;
250
251 // Pass through allies
252 if(playermissile)
253 {
254 if(check == players[0].mo)
255 continue;
256 }
257 else
258 {
259 if(check->flags & FL_ISMONSTER)
260 continue;
261 }
262
263 if((check->flags & (FL_SHOOTABLE|FL_SOLID)) && lastHit != check)
264 {
265 fixed deltax = abs(self->x - check->x);
266 fixed deltay = abs(self->y - check->y);
267 fixed radius = check->radius + self->radius;
268 if(deltax < radius && deltay < radius)
269 {
270 lastHit = check;
271 if(check->flags & FL_SHOOTABLE)
272 {
273 if(check != players[0].mo)
274 DamageActor(check, self->GetDamage());
275 else
276 TakeDamage(self->GetDamage(), self);
277
278 if(!(self->flags & FL_RIPPER) || (check->flags & FL_DONTRIP))
279 {
280 T_ExplodeProjectile(self, check);
281 return;
282 }
283 }
284 // Eventually this will need an actual height check.
285 else if(check->projectilepassheight != 0)
286 {
287 T_ExplodeProjectile(self, check);
288 return;
289 }
290 }
291 }
292 }
293 }
294 while(--steps);
295 }
296
297 /*
298 ==================
299 =
300 = A_Scream
301 =
302 ==================
303 */
304
ACTION_FUNCTION(A_Scream)305 ACTION_FUNCTION(A_Scream)
306 {
307 PlaySoundLocActor(self->deathsound, self);
308 return true;
309 }
310
311 /*
312 ==================
313 =
314 = A_CustomMissile
315 =
316 ==================
317 */
318
ACTION_FUNCTION(A_CustomMissile)319 ACTION_FUNCTION(A_CustomMissile)
320 {
321 enum
322 {
323 CMF_AIMOFFSET = 1
324 };
325
326 ACTION_PARAM_STRING(missiletype, 0);
327 ACTION_PARAM_DOUBLE(spawnheight, 1);
328 ACTION_PARAM_INT(spawnoffset, 2);
329 ACTION_PARAM_DOUBLE(angleOffset, 3);
330 ACTION_PARAM_INT(flags, 4);
331
332 fixed newx = self->x + spawnoffset*finesine[self->angle>>ANGLETOFINESHIFT]/64;
333 fixed newy = self->y + spawnoffset*finecosine[self->angle>>ANGLETOFINESHIFT]/64;
334
335 double angle = (flags & CMF_AIMOFFSET) ?
336 atan2 ((double) (self->y - players[0].mo->y), (double) (players[0].mo->x - self->x)) :
337 atan2 ((double) (newy - players[0].mo->y), (double) (players[0].mo->x - newx));
338 if (angle<0)
339 angle = (M_PI*2+angle);
340 angle_t iangle = (angle_t) (angle*ANGLE_180/M_PI) + (angle_t) ((angleOffset*ANGLE_45)/45);
341
342 const ClassDef *cls = ClassDef::FindClass(missiletype);
343 if(!cls)
344 return false;
345 AActor *newobj = AActor::Spawn(cls, newx, newy, 0, SPAWN_AllowReplacement);
346 newobj->angle = iangle;
347
348 newobj->velx = FixedMul(newobj->speed,finecosine[iangle>>ANGLETOFINESHIFT]);
349 newobj->vely = -FixedMul(newobj->speed,finesine[iangle>>ANGLETOFINESHIFT]);
350 return true;
351 }
352
353 //
354 // spectre
355 //
356
357
358 /*
359 ===============
360 =
361 = A_Dormant
362 =
363 ===============
364 */
365
ACTION_FUNCTION(A_Dormant)366 ACTION_FUNCTION(A_Dormant)
367 {
368 AActor::Iterator iter = AActor::GetIterator();
369 while(iter.Next())
370 {
371 AActor *actor = iter;
372 if(actor == self || !(actor->flags&(FL_SHOOTABLE|FL_SOLID)))
373 continue;
374
375 fixed radius = self->radius + actor->radius;
376 if(abs(self->x - actor->x) < radius &&
377 abs(self->y - actor->y) < radius)
378 return false;
379 }
380
381 self->flags |= FL_AMBUSH | FL_SHOOTABLE | FL_SOLID;
382 self->flags &= ~(FL_ATTACKMODE|FL_COUNTKILL);
383 self->dir = nodir;
384 self->SetState(self->SeeState);
385 return true;
386 }
387
388 /*
389 ============================================================================
390
391 STAND
392
393 ============================================================================
394 */
395
396
397 /*
398 ===============
399 =
400 = A_Look
401 =
402 ===============
403 */
404
ACTION_FUNCTION(A_Look)405 ACTION_FUNCTION(A_Look)
406 {
407 ACTION_PARAM_INT(flags, 0);
408 ACTION_PARAM_DOUBLE(minseedist, 1);
409 ACTION_PARAM_DOUBLE(maxseedist, 2);
410 ACTION_PARAM_DOUBLE(maxheardist, 3);
411 ACTION_PARAM_DOUBLE(fov, 4);
412
413 // FOV of 0 indicates default
414 if(fov < 0.00001)
415 fov = 180;
416
417 SightPlayer(self, minseedist, maxseedist, maxheardist, fov);
418 return true;
419 }
420 // Create A_LookEx as an alias to A_Look since we're technically emulating this
421 // ZDoom function with A_Look.
ACTION_ALIAS(A_Look,A_LookEx)422 ACTION_ALIAS(A_Look, A_LookEx)
423
424
425 /*
426 ============================================================================
427
428 CHASE
429
430 ============================================================================
431 */
432
433 /*
434 =================
435 =
436 = T_Chase
437 =
438 =================
439 */
440
441 bool CheckMeleeRange(AActor *inflictor, AActor *inflictee, fixed range)
442 {
443 fixed r = inflictor->meleerange + inflictee->radius + range;
444 return abs(inflictee->x - inflictor->x) <= r && abs(inflictee->y - inflictor->y) <= r;
445 }
446
447 /*
448 ===============
449 =
450 = SelectPathDir
451 =
452 ===============
453 */
454
SelectPathDir(AActor * ob)455 void SelectPathDir (AActor *ob)
456 {
457 if (!TryWalk (ob))
458 ob->dir = nodir;
459 }
460
461 FRandom pr_chase("Chase");
ACTION_FUNCTION(A_Chase)462 ACTION_FUNCTION(A_Chase)
463 {
464 enum
465 {
466 CHF_DONTDODGE = 1,
467 CHF_BACKOFF = 2,
468 CHF_NOSIGHTCHECK = 4,
469 CHF_NOPLAYACTIVE = 8
470 };
471
472 ACTION_PARAM_STATE(melee, 0, self->MeleeState);
473 ACTION_PARAM_STATE(missile, 1, self->MissileState);
474 ACTION_PARAM_INT(flags, 2);
475
476 int32_t move,target;
477 int dx,dy,dist = INT_MAX,chance;
478 bool dodge = !(flags & CHF_DONTDODGE);
479 bool pathing = (self->flags & FL_PATHING) ? true : false;
480
481 if (self->dir == nodir)
482 {
483 if (pathing)
484 SelectPathDir (self);
485 else if (dodge)
486 SelectDodgeDir (self);
487 else
488 SelectChaseDir (self);
489
490 self->movecount = pr_chase.RandomOld(false) & 15;
491 }
492 // Movecount is an approximation of Doom's movecount which would keep the
493 // monster moving in some direction for a random amount of time (contrarily
494 // to wolfensteins block based movement). This is simulated since it also
495 // determines when a monster attempts to attack
496 else if(--self->movecount < 0)
497 self->movecount = pr_chase.RandomOld(false) & 15;
498
499 if(!pathing)
500 {
501 bool inMeleeRange = melee ? CheckMeleeRange(self, players[0].mo, self->speed) : false;
502
503 if(!inMeleeRange && missile)
504 {
505 dodge = false;
506 if ((self->IsFast() || self->movecount == 0) && CheckLine(self)) // got a shot at players[0].mo?
507 {
508 self->hidden = false;
509 dx = abs(self->tilex + dirdeltax[self->dir] - players[0].mo->tilex);
510 dy = abs(self->tiley + dirdeltay[self->dir] - players[0].mo->tiley);
511 dist = dx>dy ? dx : dy;
512 // If we only do ranged attacks, be more aggressive
513 if(!melee)
514 {
515 if(self->missilefrequency >= FRACUNIT)
516 dist -= 2;
517 else
518 // For frequencies less than 1.0 scale back the boost
519 // in aggressiveness. Through the magic that is integer
520 // math, this will become 0 at wolfensteins's frequency
521 // This allows us to approximate Doom's aggressiveness
522 // while not tampering the Wolf probability
523 dist -= (2*self->missilefrequency)>>FRACBITS;
524 }
525
526 if(!(flags & CHF_BACKOFF))
527 {
528 if (dist > 0)
529 chance = (208*self->missilefrequency/dist)>>FRACBITS;
530 else
531 chance = 256;
532
533 // If we have a combo attack monster, we want to skip this
534 // check as the monster should try to get melee in.
535 if (dist == 1 && !melee)
536 {
537 target = abs(self->x - players[0].mo->x);
538 if (target < 0x14000l) // < 1.25 tiles or 80 units
539 {
540 target = abs(self->y - players[0].mo->y);
541 if (target < 0x14000l)
542 chance = 256;
543 }
544 }
545 }
546 else
547 chance = (208*self->missilefrequency)>>FRACBITS;
548
549 if ( pr_chase.RandomOld(!!(self->flags & FL_OLDRANDOMCHASE)) < MAX<int>(chance, self->minmissilechance))
550 {
551 self->SetState(missile);
552 return true;
553 }
554 dodge = !(flags & CHF_DONTDODGE);
555 }
556 else
557 self->hidden = true;
558 }
559 else
560 self->hidden = !inMeleeRange;
561 }
562 else
563 {
564 if (!(flags & CHF_NOSIGHTCHECK) && SightPlayer (self, 0, 0, 0, 180))
565 return true;
566 }
567
568 if(self->dir == nodir)
569 return false; // object is blocked in
570
571 self->angle = dirangle[self->dir];
572 move = self->speed;
573
574 do
575 {
576 if (CheckDoorMovement(self))
577 return true;
578
579 if(!pathing)
580 {
581 //
582 // check for melee range
583 //
584 if(melee && CheckMeleeRange(self, players[0].mo, self->speed))
585 {
586 PlaySoundLocActor(self->attacksound, self);
587 self->SetState(melee);
588 return true;
589 }
590 }
591
592 if (move < self->distance)
593 {
594 if(!MoveObj (self,move) && !(flags & CHF_DONTDODGE) && !(self->flags & FL_PATHING))
595 {
596 // Touched the player so turn around!
597 self->dir = dirtype((self->dir+4)%8);
598 self->distance = FRACUNIT-self->distance;
599 }
600 break;
601 }
602
603 //
604 // reached goal tile, so select another one
605 //
606
607 //
608 // fix position to account for round off during moving
609 //
610 self->x = ((int32_t)self->tilex<<TILESHIFT)+TILEGLOBAL/2;
611 self->y = ((int32_t)self->tiley<<TILESHIFT)+TILEGLOBAL/2;
612
613 move -= self->distance;
614
615 dx = abs(self->tilex - players[0].mo->tilex);
616 dy = abs(self->tiley - players[0].mo->tiley);
617 dist = dx>dy ? dx : dy;
618 if (pathing)
619 SelectPathDir (self);
620 else if ((flags & CHF_BACKOFF) && dist < 4)
621 SelectRunDir (self);
622 else if (dodge)
623 SelectDodgeDir (self);
624 else
625 SelectChaseDir (self);
626
627 if (self->dir == nodir)
628 return false; // object is blocked in
629 }
630 while(move);
631
632 if(!(flags & CHF_NOPLAYACTIVE) &&
633 self->activesound != NAME_None && pr_chase.RandomOld(false) < 3)
634 {
635 PlaySoundLocActor(self->activesound, self);
636 }
637 return true;
638 }
639
640 /*
641 =============================================================================
642
643 FIGHT
644
645 =============================================================================
646 */
647
648
649 /*
650 ===============
651 =
652 = A_WolfAttack
653 =
654 = Try to damage the players[0].mo, based on skill level and players[0].mo's speed
655 =
656 ===============
657 */
658
659 static FRandom pr_cabullet("CustomBullet");
ACTION_FUNCTION(A_WolfAttack)660 ACTION_FUNCTION(A_WolfAttack)
661 {
662 enum
663 {
664 WAF_NORANDOM = 1
665 };
666
667 ACTION_PARAM_INT(flags, 0);
668 ACTION_PARAM_STRING(sound, 1);
669 ACTION_PARAM_FIXED(snipe, 2);
670 ACTION_PARAM_INT(maxdamage, 3);
671 ACTION_PARAM_INT(blocksize, 4);
672 ACTION_PARAM_INT(pointblank, 5);
673 ACTION_PARAM_INT(longrange, 6);
674 ACTION_PARAM_DOUBLE(runspeed, 7);
675
676 int dx,dy,dist;
677 int hitchance;
678
679 runspeed *= 37.5;
680
681 A_Face(self, players[0].mo);
682
683 if (CheckLine (self)) // players[0].mo is not behind a wall
684 {
685 dx = abs(self->x - players[0].mo->x);
686 dy = abs(self->y - players[0].mo->y);
687 dist = dx>dy ? dx:dy;
688
689 dist = FixedMul(dist, snipe);
690 dist /= blocksize<<9;
691
692 if (thrustspeed >= runspeed)
693 {
694 if (self->flags&FL_VISABLE)
695 hitchance = 160-dist*16; // players[0].mo can see to dodge
696 else
697 hitchance = 160-dist*8;
698 }
699 else
700 {
701 if (self->flags&FL_VISABLE)
702 hitchance = 256-dist*16; // players[0].mo can see to dodge
703 else
704 hitchance = 256-dist*8;
705 }
706
707 // see if the shot was a hit
708
709 if (pr_cabullet()<hitchance)
710 {
711 int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet()%maxdamage));
712 if (dist>=pointblank)
713 damage >>= 1;
714 if (dist>=longrange)
715 damage >>= 1;
716
717 TakeDamage (damage,self);
718 }
719 }
720
721 if(sound.Len() == 1 && sound[0] == '*')
722 PlaySoundLocActor(self->attacksound, self);
723 else
724 PlaySoundLocActor(sound, self);
725
726 return true;
727 }
728