1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id:$
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 //
8 // This source is available for distribution and/or modification
9 // only under the terms of the DOOM Source Code License as
10 // published by id Software. All rights reserved.
11 //
12 // The source is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
15 // for more details.
16 //
17 // $Log:$
18 //
19 // DESCRIPTION:
20 // Implements special effects:
21 // Texture animation, height or lighting changes
22 // according to adjacent sectors, respective
23 // utility functions, etc.
24 // Line Tag handling. Line and Sector triggers.
25 // Implements donut linedef triggers
26 // Initializes and implements BOOM linedef triggers for
27 // Scrollers/Conveyors
28 // Friction
29 // Wind/Current
30 //
31 //-----------------------------------------------------------------------------
32
33
34 #include <stdlib.h>
35
36 #include "templates.h"
37 #include "doomdef.h"
38 #include "doomstat.h"
39 #include "d_event.h"
40 #include "gstrings.h"
41
42 #include "i_system.h"
43 #include "m_argv.h"
44 #include "m_random.h"
45 #include "m_bbox.h"
46 #include "w_wad.h"
47
48 #include "p_local.h"
49 #include "p_lnspec.h"
50 #include "p_terrain.h"
51 #include "p_acs.h"
52 #include "p_3dmidtex.h"
53
54 #include "g_game.h"
55
56 #include "s_sound.h"
57 #include "sc_man.h"
58 #include "gi.h"
59 #include "statnums.h"
60 #include "g_level.h"
61 #include "v_font.h"
62 #include "a_sharedglobal.h"
63 #include "farchive.h"
64 #include "a_keys.h"
65 #include "c_dispatch.h"
66 #include "r_sky.h"
67
68 // State.
69 #include "r_state.h"
70
71 #include "c_console.h"
72
73 #include "r_data/r_interpolate.h"
74
75 static FRandom pr_playerinspecialsector ("PlayerInSpecialSector");
76
77 EXTERN_CVAR(Bool, cl_predict_specials)
78
79 IMPLEMENT_POINTY_CLASS (DScroller)
80 DECLARE_POINTER (m_Interpolations[0])
81 DECLARE_POINTER (m_Interpolations[1])
82 DECLARE_POINTER (m_Interpolations[2])
83 END_POINTERS
84
85 IMPLEMENT_POINTY_CLASS (DPusher)
86 DECLARE_POINTER (m_Source)
87 END_POINTERS
88
89 inline FArchive &operator<< (FArchive &arc, DScroller::EScrollType &type)
90 {
91 BYTE val = (BYTE)type;
92 arc << val;
93 type = (DScroller::EScrollType)val;
94 return arc;
95 }
96
DScroller()97 DScroller::DScroller ()
98 {
99 }
100
Serialize(FArchive & arc)101 void DScroller::Serialize (FArchive &arc)
102 {
103 Super::Serialize (arc);
104 arc << m_Type
105 << m_dx << m_dy
106 << m_Affectee
107 << m_Control
108 << m_LastHeight
109 << m_vdx << m_vdy
110 << m_Accel
111 << m_Parts
112 << m_Interpolations[0]
113 << m_Interpolations[1]
114 << m_Interpolations[2];
115 }
116
DPusher()117 DPusher::DPusher ()
118 {
119 }
120
operator <<(FArchive & arc,DPusher::EPusher & type)121 inline FArchive &operator<< (FArchive &arc, DPusher::EPusher &type)
122 {
123 BYTE val = (BYTE)type;
124 arc << val;
125 type = (DPusher::EPusher)val;
126 return arc;
127 }
128
Serialize(FArchive & arc)129 void DPusher::Serialize (FArchive &arc)
130 {
131 Super::Serialize (arc);
132 arc << m_Type
133 << m_Source
134 << m_Xmag
135 << m_Ymag
136 << m_Magnitude
137 << m_Radius
138 << m_X
139 << m_Y
140 << m_Affectee;
141 }
142
143 // killough 3/7/98: Initialize generalized scrolling
144 static void P_SpawnScrollers();
145 static void P_SpawnFriction (); // phares 3/16/98
146 static void P_SpawnPushers (); // phares 3/20/98
147
148
149 // [RH] Check dmflags for noexit and respond accordingly
CheckIfExitIsGood(AActor * self,level_info_t * info)150 bool CheckIfExitIsGood (AActor *self, level_info_t *info)
151 {
152 cluster_info_t *cluster;
153
154 // The world can always exit itself.
155 if (self == NULL)
156 return true;
157
158 // We must kill all monsters to exit the level.
159 if ((dmflags2 & DF2_KILL_MONSTERS) && level.killed_monsters != level.total_monsters)
160 return false;
161
162 // Is this a deathmatch game and we're not allowed to exit?
163 if ((deathmatch || alwaysapplydmflags) && (dmflags & DF_NO_EXIT))
164 {
165 P_DamageMobj (self, self, self, TELEFRAG_DAMAGE, NAME_Exit);
166 return false;
167 }
168 // Is this a singleplayer game and the next map is part of the same hub and we're dead?
169 if (self->health <= 0 &&
170 !multiplayer &&
171 info != NULL &&
172 info->cluster == level.cluster &&
173 (cluster = FindClusterInfo(level.cluster)) != NULL &&
174 cluster->flags & CLUSTER_HUB)
175 {
176 return false;
177 }
178 if (deathmatch && gameaction != ga_completed)
179 {
180 Printf ("%s exited the level.\n", self->player->userinfo.GetName());
181 }
182 return true;
183 }
184
185
186 //
187 // UTILITIES
188 //
189
190 //============================================================================
191 //
192 // P_ActivateLine
193 //
194 //============================================================================
195
P_ActivateLine(line_t * line,AActor * mo,int side,int activationType)196 bool P_ActivateLine (line_t *line, AActor *mo, int side, int activationType)
197 {
198 int lineActivation;
199 INTBOOL repeat;
200 INTBOOL buttonSuccess;
201 BYTE special;
202
203 if (!P_TestActivateLine (line, mo, side, activationType))
204 {
205 return false;
206 }
207 bool remote = (line->special != 7 && line->special != 8 && (line->special < 11 || line->special > 14));
208 if (line->locknumber > 0 && !P_CheckKeys (mo, line->locknumber, remote)) return false;
209 lineActivation = line->activation;
210 repeat = line->flags & ML_REPEAT_SPECIAL;
211 buttonSuccess = false;
212 buttonSuccess = P_ExecuteSpecial(line->special,
213 line, mo, side == 1, line->args[0],
214 line->args[1], line->args[2],
215 line->args[3], line->args[4]);
216
217 special = line->special;
218 if (!repeat && buttonSuccess)
219 { // clear the special on non-retriggerable lines
220 line->special = 0;
221 }
222
223 if (buttonSuccess)
224 {
225 if (activationType == SPAC_Use || activationType == SPAC_Impact || activationType == SPAC_Push)
226 {
227 P_ChangeSwitchTexture (line->sidedef[0], repeat, special);
228 }
229 }
230 // some old WADs use this method to create walls that change the texture when shot.
231 else if (activationType == SPAC_Impact && // only for shootable triggers
232 (level.flags2 & LEVEL2_DUMMYSWITCHES) && // this is only a compatibility setting for an old hack!
233 !repeat && // only non-repeatable triggers
234 (special<Generic_Floor || special>Generic_Crusher) && // not for Boom's generalized linedefs
235 special && // not for lines without a special
236 tagManager.LineHasID(line, line->args[0]) && // Safety check: exclude edited UDMF linedefs or ones that don't map the tag to args[0]
237 line->args[0] && // only if there's a tag (which is stored in the first arg)
238 P_FindFirstSectorFromTag (line->args[0]) == -1) // only if no sector is tagged to this linedef
239 {
240 P_ChangeSwitchTexture (line->sidedef[0], repeat, special);
241 line->special = 0;
242 }
243 // end of changed code
244 if (developer && buttonSuccess)
245 {
246 Printf ("Line special %d activated on line %i\n", special, int(line - lines));
247 }
248 return true;
249 }
250
251 //============================================================================
252 //
253 // P_TestActivateLine
254 //
255 //============================================================================
256
P_TestActivateLine(line_t * line,AActor * mo,int side,int activationType)257 bool P_TestActivateLine (line_t *line, AActor *mo, int side, int activationType)
258 {
259 int lineActivation = line->activation;
260
261 if (line->flags & ML_FIRSTSIDEONLY && side == 1)
262 {
263 return false;
264 }
265
266 if (lineActivation & SPAC_UseThrough)
267 {
268 lineActivation |= SPAC_Use;
269 }
270 else if (line->special == Teleport &&
271 (lineActivation & SPAC_Cross) &&
272 activationType == SPAC_PCross &&
273 mo != NULL &&
274 mo->flags & MF_MISSILE)
275 { // Let missiles use regular player teleports
276 lineActivation |= SPAC_PCross;
277 }
278 // BOOM's generalized line types that allow monster use can actually be
279 // activated by anything except projectiles.
280 if (lineActivation & SPAC_AnyCross)
281 {
282 lineActivation |= SPAC_Cross|SPAC_MCross;
283 }
284 if (activationType == SPAC_Use || activationType == SPAC_UseBack)
285 {
286 if (!P_CheckSwitchRange(mo, line, side))
287 {
288 return false;
289 }
290 }
291
292 if (activationType == SPAC_Use && (lineActivation & SPAC_MUse) && !mo->player && mo->flags4 & MF4_CANUSEWALLS)
293 {
294 return true;
295 }
296 if (activationType == SPAC_Push && (lineActivation & SPAC_MPush) && !mo->player && mo->flags2 & MF2_PUSHWALL)
297 {
298 return true;
299 }
300 if ((lineActivation & activationType) == 0)
301 {
302 if (activationType != SPAC_MCross || lineActivation != SPAC_Cross)
303 {
304 return false;
305 }
306 }
307 if (activationType == SPAC_AnyCross && (lineActivation & activationType))
308 {
309 return true;
310 }
311 if (mo && !mo->player &&
312 !(mo->flags & MF_MISSILE) &&
313 !(line->flags & ML_MONSTERSCANACTIVATE) &&
314 (activationType != SPAC_MCross || (!(lineActivation & SPAC_MCross))))
315 {
316 // [RH] monsters' ability to activate this line depends on its type
317 // In Hexen, only MCROSS lines could be activated by monsters. With
318 // lax activation checks, monsters can also activate certain lines
319 // even without them being marked as monster activate-able. This is
320 // the default for non-Hexen maps in Hexen format.
321 if (!(level.flags2 & LEVEL2_LAXMONSTERACTIVATION))
322 {
323 return false;
324 }
325 if ((activationType == SPAC_Use || activationType == SPAC_Push)
326 && (line->flags & ML_SECRET))
327 return false; // never open secret doors
328
329 bool noway = true;
330
331 switch (activationType)
332 {
333 case SPAC_Use:
334 case SPAC_Push:
335 switch (line->special)
336 {
337 case Door_Raise:
338 if (line->args[0] == 0 && line->args[1] < 64)
339 noway = false;
340 break;
341 case Teleport:
342 case Teleport_NoFog:
343 noway = false;
344 }
345 break;
346
347 case SPAC_MCross:
348 if (!(lineActivation & SPAC_MCross))
349 {
350 switch (line->special)
351 {
352 case Door_Raise:
353 if (line->args[1] >= 64)
354 {
355 break;
356 }
357 case Teleport:
358 case Teleport_NoFog:
359 case Teleport_Line:
360 case Plat_DownWaitUpStayLip:
361 case Plat_DownWaitUpStay:
362 noway = false;
363 }
364 }
365 else noway = false;
366 break;
367
368 default:
369 noway = false;
370 }
371 return !noway;
372 }
373 if (activationType == SPAC_MCross && !(lineActivation & SPAC_MCross) &&
374 !(line->flags & ML_MONSTERSCANACTIVATE))
375 {
376 return false;
377 }
378 return true;
379 }
380
381 //============================================================================
382 //
383 // P_PredictLine
384 //
385 //============================================================================
386
P_PredictLine(line_t * line,AActor * mo,int side,int activationType)387 bool P_PredictLine(line_t *line, AActor *mo, int side, int activationType)
388 {
389 int lineActivation;
390 INTBOOL buttonSuccess;
391 BYTE special;
392
393 // Only predict a very specifc section of specials
394 if (line->special != Teleport_Line &&
395 line->special != Teleport)
396 {
397 return false;
398 }
399
400 if (!P_TestActivateLine(line, mo, side, activationType) || !cl_predict_specials)
401 {
402 return false;
403 }
404
405 if (line->locknumber > 0) return false;
406 lineActivation = line->activation;
407 buttonSuccess = false;
408 buttonSuccess = P_ExecuteSpecial(line->special,
409 line, mo, side == 1, line->args[0],
410 line->args[1], line->args[2],
411 line->args[3], line->args[4]);
412
413 special = line->special;
414
415 // end of changed code
416 if (developer && buttonSuccess)
417 {
418 Printf("Line special %d predicted on line %i\n", special, int(line - lines));
419 }
420 return true;
421 }
422
423 //
424 // P_PlayerInSpecialSector
425 // Called every tic frame
426 // that the player origin is in a special sector
427 //
P_PlayerInSpecialSector(player_t * player,sector_t * sector)428 void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
429 {
430 if (sector == NULL)
431 {
432 // Falling, not all the way down yet?
433 sector = player->mo->Sector;
434 if (player->mo->Z() != sector->floorplane.ZatPoint(player->mo)
435 && !player->mo->waterlevel)
436 {
437 return;
438 }
439 }
440
441 // Has hit ground.
442 AInventory *ironfeet;
443
444 // [RH] Apply any customizable damage
445 if (sector->damageamount > 0)
446 {
447 // Allow subclasses. Better would be to implement it as armor and let that reduce
448 // the damage as part of the normal damage procedure. Unfortunately, I don't have
449 // different damage types yet, so that's not happening for now.
450 for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
451 {
452 if (ironfeet->IsKindOf (RUNTIME_CLASS(APowerIronFeet)))
453 break;
454 }
455
456 if (sector->Flags & SECF_ENDGODMODE) player->cheats &= ~CF_GODMODE;
457 if ((ironfeet == NULL || pr_playerinspecialsector() < sector->leakydamage))
458 {
459 if (sector->Flags & SECF_HAZARD)
460 {
461 player->hazardcount += sector->damageamount;
462 player->hazardtype = sector->damagetype;
463 player->hazardinterval = sector->damageinterval;
464 }
465 else if (level.time % sector->damageinterval == 0)
466 {
467 if (!(player->cheats & (CF_GODMODE|CF_GODMODE2))) P_DamageMobj(player->mo, NULL, NULL, sector->damageamount, sector->damagetype);
468 if ((sector->Flags & SECF_ENDLEVEL) && player->health <= 10 && (!deathmatch || !(dmflags & DF_NO_EXIT)))
469 {
470 G_ExitLevel(0, false);
471 }
472 if (sector->Flags & SECF_DMGTERRAINFX)
473 {
474 P_HitWater(player->mo, sector, INT_MIN, INT_MIN, INT_MIN, false, true, true);
475 }
476 }
477 }
478 }
479 else if (sector->damageamount < 0)
480 {
481 if (level.time % sector->damageinterval == 0)
482 {
483 P_GiveBody(player->mo, -sector->damageamount, 100);
484 }
485 }
486
487 if (sector->isSecret())
488 {
489 sector->ClearSecret();
490 P_GiveSecret(player->mo, true, true, int(sector - sectors));
491 }
492 }
493
494 //============================================================================
495 //
496 // P_SectorDamage
497 //
498 //============================================================================
499
DoSectorDamage(AActor * actor,sector_t * sec,int amount,FName type,const PClass * protectClass,int flags)500 static void DoSectorDamage(AActor *actor, sector_t *sec, int amount, FName type, const PClass *protectClass, int flags)
501 {
502 if (!(actor->flags & MF_SHOOTABLE))
503 return;
504
505 if (!(flags & DAMAGE_NONPLAYERS) && actor->player == NULL)
506 return;
507
508 if (!(flags & DAMAGE_PLAYERS) && actor->player != NULL)
509 return;
510
511 if (!(flags & DAMAGE_IN_AIR) && actor->Z() != sec->floorplane.ZatPoint(actor) && !actor->waterlevel)
512 return;
513
514 if (protectClass != NULL)
515 {
516 if (actor->FindInventory(protectClass, !!(flags & DAMAGE_SUBCLASSES_PROTECT)))
517 return;
518 }
519
520 P_DamageMobj (actor, NULL, NULL, amount, type);
521 }
522
P_SectorDamage(int tag,int amount,FName type,const PClass * protectClass,int flags)523 void P_SectorDamage(int tag, int amount, FName type, const PClass *protectClass, int flags)
524 {
525 FSectorTagIterator itr(tag);
526 int secnum;
527 while ((secnum = itr.Next()) >= 0)
528 {
529 AActor *actor, *next;
530 sector_t *sec = §ors[secnum];
531
532 // Do for actors in this sector.
533 for (actor = sec->thinglist; actor != NULL; actor = next)
534 {
535 next = actor->snext;
536 DoSectorDamage(actor, sec, amount, type, protectClass, flags);
537 }
538 // If this is a 3D floor control sector, also do for anything in/on
539 // those 3D floors.
540 for (unsigned i = 0; i < sec->e->XFloor.attached.Size(); ++i)
541 {
542 sector_t *sec2 = sec->e->XFloor.attached[i];
543
544 for (actor = sec2->thinglist; actor != NULL; actor = next)
545 {
546 next = actor->snext;
547 // Only affect actors touching the 3D floor
548 fixed_t z1 = sec->floorplane.ZatPoint(actor);
549 fixed_t z2 = sec->ceilingplane.ZatPoint(actor);
550 if (z2 < z1)
551 {
552 // Account for Vavoom-style 3D floors
553 fixed_t zz = z1;
554 z1 = z2;
555 z2 = zz;
556 }
557 if (actor->Z() + actor->height > z1)
558 {
559 // If DAMAGE_IN_AIR is used, anything not beneath the 3D floor will be
560 // damaged (so, anything touching it or above it). Other 3D floors between
561 // the actor and this one will not stop this effect.
562 if ((flags & DAMAGE_IN_AIR) || actor->Z() <= z2)
563 {
564 // Here we pass the DAMAGE_IN_AIR flag to disable the floor check, since it
565 // only works with the real sector's floor. We did the appropriate height checks
566 // for 3D floors already.
567 DoSectorDamage(actor, NULL, amount, type, protectClass, flags | DAMAGE_IN_AIR);
568 }
569 }
570 }
571 }
572 }
573 }
574
575 //============================================================================
576 //
577 // P_GiveSecret
578 //
579 //============================================================================
580
581 CVAR(Bool, showsecretsector, false, 0)
CVAR(Bool,cl_showsecretmessage,true,CVAR_ARCHIVE)582 CVAR(Bool, cl_showsecretmessage, true, CVAR_ARCHIVE)
583
584 void P_GiveSecret(AActor *actor, bool printmessage, bool playsound, int sectornum)
585 {
586 if (actor != NULL)
587 {
588 if (actor->player != NULL)
589 {
590 actor->player->secretcount++;
591 }
592 if (cl_showsecretmessage && actor->CheckLocalView(consoleplayer))
593 {
594 if (printmessage)
595 {
596 if (!showsecretsector || sectornum < 0) C_MidPrint(SmallFont, GStrings["SECRETMESSAGE"]);
597 else
598 {
599 FString s = GStrings["SECRETMESSAGE"];
600 s.AppendFormat(" (Sector %d)", sectornum);
601 C_MidPrint(SmallFont, s);
602 }
603 }
604 if (playsound) S_Sound (CHAN_AUTO | CHAN_UI, "misc/secret", 1, ATTN_NORM);
605 }
606 }
607 level.found_secrets++;
608 }
609
610 //============================================================================
611 //
612 // P_PlayerOnSpecialFlat
613 //
614 //============================================================================
615
P_PlayerOnSpecialFlat(player_t * player,int floorType)616 void P_PlayerOnSpecialFlat (player_t *player, int floorType)
617 {
618 if (Terrains[floorType].DamageAmount &&
619 !(level.time & Terrains[floorType].DamageTimeMask))
620 {
621 AInventory *ironfeet = NULL;
622
623 if (Terrains[floorType].AllowProtection)
624 {
625 for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
626 {
627 if (ironfeet->IsKindOf (RUNTIME_CLASS(APowerIronFeet)))
628 break;
629 }
630 }
631
632 int damage = 0;
633 if (ironfeet == NULL)
634 {
635 damage = P_DamageMobj (player->mo, NULL, NULL, Terrains[floorType].DamageAmount,
636 Terrains[floorType].DamageMOD);
637 }
638 if (damage > 0 && Terrains[floorType].Splash != -1)
639 {
640 S_Sound (player->mo, CHAN_AUTO,
641 Splashes[Terrains[floorType].Splash].NormalSplashSound, 1,
642 ATTN_IDLE);
643 }
644 }
645 }
646
647
648
649 //
650 // P_UpdateSpecials
651 // Animate planes, scroll walls, etc.
652 //
EXTERN_CVAR(Float,timelimit)653 EXTERN_CVAR (Float, timelimit)
654
655 void P_UpdateSpecials ()
656 {
657 // LEVEL TIMER
658 if (deathmatch && timelimit)
659 {
660 if (level.maptime >= (int)(timelimit * TICRATE * 60))
661 {
662 Printf ("%s\n", GStrings("TXT_TIMELIMIT"));
663 G_ExitLevel(0, false);
664 }
665 }
666 }
667
668
669
670 //
671 // SPECIAL SPAWNING
672 //
673
674 CUSTOM_CVAR (Bool, forcewater, false, CVAR_ARCHIVE|CVAR_SERVERINFO)
675 {
676 if (gamestate == GS_LEVEL)
677 {
678 int i;
679
680 for (i = 0; i < numsectors; i++)
681 {
682 sector_t *hsec = sectors[i].GetHeightSec();
683 if (hsec &&
684 !(sectors[i].heightsec->MoreFlags & SECF_UNDERWATER))
685 {
686 if (self)
687 {
688 hsec->MoreFlags |= SECF_FORCEDUNDERWATER;
689 }
690 else
691 {
692 hsec->MoreFlags &= ~SECF_FORCEDUNDERWATER;
693 }
694 }
695 }
696 }
697 }
698
699 class DLightTransfer : public DThinker
700 {
DECLARE_CLASS(DLightTransfer,DThinker)701 DECLARE_CLASS (DLightTransfer, DThinker)
702
703 DLightTransfer() {}
704 public:
705 DLightTransfer (sector_t *srcSec, int target, bool copyFloor);
706 void Serialize (FArchive &arc);
707 void Tick ();
708
709 protected:
710 static void DoTransfer (int level, int target, bool floor);
711
712 sector_t *Source;
713 int TargetTag;
714 bool CopyFloor;
715 short LastLight;
716 };
717
IMPLEMENT_CLASS(DLightTransfer)718 IMPLEMENT_CLASS (DLightTransfer)
719
720 void DLightTransfer::Serialize (FArchive &arc)
721 {
722 Super::Serialize (arc);
723 if (SaveVersion < 3223)
724 {
725 BYTE bytelight;
726 arc << bytelight;
727 LastLight = bytelight;
728 }
729 else
730 {
731 arc << LastLight;
732 }
733 arc << Source << TargetTag << CopyFloor;
734 }
735
DLightTransfer(sector_t * srcSec,int target,bool copyFloor)736 DLightTransfer::DLightTransfer (sector_t *srcSec, int target, bool copyFloor)
737 {
738 int secnum;
739
740 Source = srcSec;
741 TargetTag = target;
742 CopyFloor = copyFloor;
743 DoTransfer (LastLight = srcSec->lightlevel, target, copyFloor);
744
745 if (copyFloor)
746 {
747 FSectorTagIterator itr(target);
748 while ((secnum = itr.Next()) >= 0)
749 sectors[secnum].ChangeFlags(sector_t::floor, 0, PLANEF_ABSLIGHTING);
750 }
751 else
752 {
753 FSectorTagIterator itr(target);
754 while ((secnum = itr.Next()) >= 0)
755 sectors[secnum].ChangeFlags(sector_t::ceiling, 0, PLANEF_ABSLIGHTING);
756 }
757 ChangeStatNum (STAT_LIGHTTRANSFER);
758 }
759
Tick()760 void DLightTransfer::Tick ()
761 {
762 int light = Source->lightlevel;
763
764 if (light != LastLight)
765 {
766 LastLight = light;
767 DoTransfer (light, TargetTag, CopyFloor);
768 }
769 }
770
DoTransfer(int level,int target,bool floor)771 void DLightTransfer::DoTransfer (int level, int target, bool floor)
772 {
773 int secnum;
774
775 if (floor)
776 {
777 FSectorTagIterator itr(target);
778 while ((secnum = itr.Next()) >= 0)
779 sectors[secnum].SetPlaneLight(sector_t::floor, level);
780 }
781 else
782 {
783 FSectorTagIterator itr(target);
784 while ((secnum = itr.Next()) >= 0)
785 sectors[secnum].SetPlaneLight(sector_t::ceiling, level);
786 }
787 }
788
789
790 class DWallLightTransfer : public DThinker
791 {
792 enum
793 {
794 WLF_SIDE1=1,
795 WLF_SIDE2=2,
796 WLF_NOFAKECONTRAST=4
797 };
798
DECLARE_CLASS(DWallLightTransfer,DThinker)799 DECLARE_CLASS (DWallLightTransfer, DThinker)
800 DWallLightTransfer() {}
801 public:
802 DWallLightTransfer (sector_t *srcSec, int target, BYTE flags);
803 void Serialize (FArchive &arc);
804 void Tick ();
805
806 protected:
807 static void DoTransfer (short level, int target, BYTE flags);
808
809 sector_t *Source;
810 int TargetID;
811 short LastLight;
812 BYTE Flags;
813 };
814
IMPLEMENT_CLASS(DWallLightTransfer)815 IMPLEMENT_CLASS (DWallLightTransfer)
816
817 void DWallLightTransfer::Serialize (FArchive &arc)
818 {
819 Super::Serialize (arc);
820 if (SaveVersion < 3223)
821 {
822 BYTE bytelight;
823 arc << bytelight;
824 LastLight = bytelight;
825 }
826 else
827 {
828 arc << LastLight;
829 }
830 arc << Source << TargetID << Flags;
831 }
832
DWallLightTransfer(sector_t * srcSec,int target,BYTE flags)833 DWallLightTransfer::DWallLightTransfer (sector_t *srcSec, int target, BYTE flags)
834 {
835 int linenum;
836 int wallflags;
837
838 Source = srcSec;
839 TargetID = target;
840 Flags = flags;
841 DoTransfer (LastLight = srcSec->GetLightLevel(), target, Flags);
842
843 if (!(flags & WLF_NOFAKECONTRAST))
844 {
845 wallflags = WALLF_ABSLIGHTING;
846 }
847 else
848 {
849 wallflags = WALLF_ABSLIGHTING | WALLF_NOFAKECONTRAST;
850 }
851
852 FLineIdIterator itr(target);
853 while ((linenum = itr.Next()) >= 0)
854 {
855 if (flags & WLF_SIDE1 && lines[linenum].sidedef[0] != NULL)
856 {
857 lines[linenum].sidedef[0]->Flags |= wallflags;
858 }
859
860 if (flags & WLF_SIDE2 && lines[linenum].sidedef[1] != NULL)
861 {
862 lines[linenum].sidedef[1]->Flags |= wallflags;
863 }
864 }
865 ChangeStatNum(STAT_LIGHTTRANSFER);
866 }
867
Tick()868 void DWallLightTransfer::Tick ()
869 {
870 short light = sector_t::ClampLight(Source->lightlevel);
871
872 if (light != LastLight)
873 {
874 LastLight = light;
875 DoTransfer (light, TargetID, Flags);
876 }
877 }
878
DoTransfer(short lightlevel,int target,BYTE flags)879 void DWallLightTransfer::DoTransfer (short lightlevel, int target, BYTE flags)
880 {
881 int linenum;
882
883 FLineIdIterator itr(target);
884 while ((linenum = itr.Next()) >= 0)
885 {
886 line_t *line = &lines[linenum];
887
888 if (flags & WLF_SIDE1 && line->sidedef[0] != NULL)
889 {
890 line->sidedef[0]->SetLight(lightlevel);
891 }
892
893 if (flags & WLF_SIDE2 && line->sidedef[1] != NULL)
894 {
895 line->sidedef[1]->SetLight(lightlevel);
896 }
897 }
898 }
899
900 //-----------------------------------------------------------------------------
901 //
902 // Portals
903 //
904 //-----------------------------------------------------------------------------
905
906 //---------------------------------------------------------------------------
907 // Upper stacks go in the top sector. Lower stacks go in the bottom sector.
908
SetupFloorPortal(AStackPoint * point)909 static void SetupFloorPortal (AStackPoint *point)
910 {
911 NActorIterator it (NAME_LowerStackLookOnly, point->tid);
912 sector_t *Sector = point->Sector;
913 Sector->SkyBoxes[sector_t::floor] = static_cast<ASkyViewpoint*>(it.Next());
914 if (Sector->SkyBoxes[sector_t::floor] != NULL && Sector->SkyBoxes[sector_t::floor]->bAlways)
915 {
916 Sector->SkyBoxes[sector_t::floor]->Mate = point;
917 if (Sector->GetAlpha(sector_t::floor) == OPAQUE)
918 Sector->SetAlpha(sector_t::floor, Scale (point->args[0], OPAQUE, 255));
919 }
920 }
921
SetupCeilingPortal(AStackPoint * point)922 static void SetupCeilingPortal (AStackPoint *point)
923 {
924 NActorIterator it (NAME_UpperStackLookOnly, point->tid);
925 sector_t *Sector = point->Sector;
926 Sector->SkyBoxes[sector_t::ceiling] = static_cast<ASkyViewpoint*>(it.Next());
927 if (Sector->SkyBoxes[sector_t::ceiling] != NULL && Sector->SkyBoxes[sector_t::ceiling]->bAlways)
928 {
929 Sector->SkyBoxes[sector_t::ceiling]->Mate = point;
930 if (Sector->GetAlpha(sector_t::ceiling) == OPAQUE)
931 Sector->SetAlpha(sector_t::ceiling, Scale (point->args[0], OPAQUE, 255));
932 }
933 }
934
P_SetupPortals()935 void P_SetupPortals()
936 {
937 TThinkerIterator<AStackPoint> it;
938 AStackPoint *pt;
939 TArray<AStackPoint *> points;
940
941 while ((pt = it.Next()))
942 {
943 FName nm = pt->GetClass()->TypeName;
944 if (nm == NAME_UpperStackLookOnly)
945 {
946 SetupFloorPortal(pt);
947 }
948 else if (nm == NAME_LowerStackLookOnly)
949 {
950 SetupCeilingPortal(pt);
951 }
952 pt->special1 = 0;
953 points.Push(pt);
954 }
955 }
956
SetPortal(sector_t * sector,int plane,ASkyViewpoint * portal,fixed_t alpha)957 static void SetPortal(sector_t *sector, int plane, ASkyViewpoint *portal, fixed_t alpha)
958 {
959 // plane: 0=floor, 1=ceiling, 2=both
960 if (plane > 0)
961 {
962 if (sector->SkyBoxes[sector_t::ceiling] == NULL || !sector->SkyBoxes[sector_t::ceiling]->bAlways)
963 {
964 sector->SkyBoxes[sector_t::ceiling] = portal;
965 if (sector->GetAlpha(sector_t::ceiling) == OPAQUE)
966 sector->SetAlpha(sector_t::ceiling, alpha);
967
968 if (!portal->bAlways) sector->SetTexture(sector_t::ceiling, skyflatnum);
969 }
970 }
971 if (plane == 2 || plane == 0)
972 {
973 if (sector->SkyBoxes[sector_t::floor] == NULL || !sector->SkyBoxes[sector_t::floor]->bAlways)
974 {
975 sector->SkyBoxes[sector_t::floor] = portal;
976 }
977 if (sector->GetAlpha(sector_t::floor) == OPAQUE)
978 sector->SetAlpha(sector_t::floor, alpha);
979
980 if (!portal->bAlways) sector->SetTexture(sector_t::floor, skyflatnum);
981 }
982 }
983
CopyPortal(int sectortag,int plane,ASkyViewpoint * origin,fixed_t alpha,bool tolines)984 static void CopyPortal(int sectortag, int plane, ASkyViewpoint *origin, fixed_t alpha, bool tolines)
985 {
986 int s;
987 FSectorTagIterator itr(sectortag);
988 while ((s = itr.Next()) >= 0)
989 {
990 SetPortal(§ors[s], plane, origin, alpha);
991 }
992
993 for (int j=0;j<numlines;j++)
994 {
995 // Check if this portal needs to be copied to other sectors
996 // This must be done here to ensure that it gets done only after the portal is set up
997 if (lines[j].special == Sector_SetPortal &&
998 lines[j].args[1] == 1 &&
999 (lines[j].args[2] == plane || lines[j].args[2] == 3) &&
1000 lines[j].args[3] == sectortag)
1001 {
1002 if (lines[j].args[0] == 0)
1003 {
1004 SetPortal(lines[j].frontsector, plane, origin, alpha);
1005 }
1006 else
1007 {
1008 FSectorTagIterator itr(lines[j].args[0]);
1009 while ((s = itr.Next()) >= 0)
1010 {
1011 SetPortal(§ors[s], plane, origin, alpha);
1012 }
1013 }
1014 }
1015 }
1016 }
1017
P_SpawnPortal(line_t * line,int sectortag,int plane,int alpha)1018 void P_SpawnPortal(line_t *line, int sectortag, int plane, int alpha)
1019 {
1020 for (int i=0;i<numlines;i++)
1021 {
1022 // We must look for the reference line with a linear search unless we want to waste the line ID for it
1023 // which is not a good idea.
1024 if (lines[i].special == Sector_SetPortal &&
1025 lines[i].args[0] == sectortag &&
1026 lines[i].args[1] == 0 &&
1027 lines[i].args[2] == plane &&
1028 lines[i].args[3] == 1)
1029 {
1030 // beware of overflows.
1031 fixed_t x1 = fixed_t((SQWORD(line->v1->x) + SQWORD(line->v2->x)) >> 1);
1032 fixed_t y1 = fixed_t((SQWORD(line->v1->y) + SQWORD(line->v2->y)) >> 1);
1033 fixed_t x2 = fixed_t((SQWORD(lines[i].v1->x) + SQWORD(lines[i].v2->x)) >> 1);
1034 fixed_t y2 = fixed_t((SQWORD(lines[i].v1->y) + SQWORD(lines[i].v2->y)) >> 1);
1035 fixed_t alpha = Scale (lines[i].args[4], OPAQUE, 255);
1036
1037 AStackPoint *anchor = Spawn<AStackPoint>(x1, y1, 0, NO_REPLACE);
1038 AStackPoint *reference = Spawn<AStackPoint>(x2, y2, 0, NO_REPLACE);
1039
1040 reference->Mate = anchor;
1041 anchor->Mate = reference;
1042
1043 // This is so that the renderer can distinguish these portals from
1044 // the ones spawned with the '*StackLookOnly' things.
1045 reference->flags |= MF_JUSTATTACKED;
1046 anchor->flags |= MF_JUSTATTACKED;
1047
1048 CopyPortal(sectortag, plane, reference, alpha, false);
1049 return;
1050 }
1051 }
1052 }
1053
1054 // This searches the viewpoint's sector
1055 // for a skybox line special, gets its tag and transfers the skybox to all tagged sectors.
P_SpawnSkybox(ASkyViewpoint * origin)1056 void P_SpawnSkybox(ASkyViewpoint *origin)
1057 {
1058 sector_t *Sector = origin->Sector;
1059 if (Sector == NULL)
1060 {
1061 Printf("Sector not initialized for SkyCamCompat\n");
1062 origin->Sector = Sector = P_PointInSector(origin->X(), origin->Y());
1063 }
1064 if (Sector)
1065 {
1066 line_t * refline = NULL;
1067 for (short i = 0; i < Sector->linecount; i++)
1068 {
1069 refline = Sector->lines[i];
1070 if (refline->special == Sector_SetPortal && refline->args[1] == 2)
1071 {
1072 // We found the setup linedef for this skybox, so let's use it for our init.
1073 CopyPortal(refline->args[0], refline->args[2], origin, 0, true);
1074 return;
1075 }
1076 }
1077 }
1078 }
1079
1080
1081
1082 //
1083 // P_SetSectorDamage
1084 //
1085 // Sets damage properties for one sector. Allows combination of original specials with explicit use of the damage properties
1086 //
1087
P_SetupSectorDamage(sector_t * sector,int damage,int interval,int leakchance,FName type,int flags)1088 static void P_SetupSectorDamage(sector_t *sector, int damage, int interval, int leakchance, FName type, int flags)
1089 {
1090 // Only set if damage is not yet initialized. This ensures that UDMF takes precedence over sector specials.
1091 if (sector->damageamount == 0)
1092 {
1093 sector->damageamount = damage;
1094 sector->damageinterval = MAX(1, interval);
1095 sector->leakydamage = leakchance;
1096 sector->damagetype = type;
1097 sector->Flags = (sector->Flags & ~SECF_DAMAGEFLAGS) | (flags & SECF_DAMAGEFLAGS);
1098 }
1099 }
1100
1101 //
1102 // P_InitSectorSpecial
1103 //
1104 // Sets up everything derived from 'sector->special' for one sector
1105 // ('fromload' is necessary to allow conversion upon savegame load.)
1106 //
1107
P_InitSectorSpecial(sector_t * sector,int special,bool nothinkers)1108 void P_InitSectorSpecial(sector_t *sector, int special, bool nothinkers)
1109 {
1110 // [RH] All secret sectors are marked with a BOOM-ish bitfield
1111 if (sector->special & SECRET_MASK)
1112 {
1113 sector->Flags |= SECF_SECRET | SECF_WASSECRET;
1114 level.total_secrets++;
1115 }
1116 if (sector->special & FRICTION_MASK)
1117 {
1118 sector->Flags |= SECF_FRICTION;
1119 }
1120 if (sector->special & PUSH_MASK)
1121 {
1122 sector->Flags |= SECF_PUSH;
1123 }
1124 if ((sector->special & DAMAGE_MASK) == 0x100)
1125 {
1126 P_SetupSectorDamage(sector, 5, 32, 0, NAME_Fire, 0);
1127 }
1128 else if ((sector->special & DAMAGE_MASK) == 0x200)
1129 {
1130 P_SetupSectorDamage(sector, 10, 32, 0, NAME_Slime, 0);
1131 }
1132 else if ((sector->special & DAMAGE_MASK) == 0x300)
1133 {
1134 P_SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0);
1135 }
1136 sector->special &= 0xff;
1137
1138 // [RH] Normal DOOM special or BOOM specialized?
1139 bool keepspecial = false;
1140 switch (sector->special)
1141 {
1142 case Light_Phased:
1143 if (!nothinkers) new DPhased (sector, 48, 63 - (sector->lightlevel & 63));
1144 break;
1145
1146 // [RH] Hexen-like phased lighting
1147 case LightSequenceStart:
1148 if (!nothinkers) new DPhased (sector);
1149 break;
1150
1151 case dLight_Flicker:
1152 if (!nothinkers) new DLightFlash (sector);
1153 break;
1154
1155 case dLight_StrobeFast:
1156 if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, FASTDARK, false);
1157 break;
1158
1159 case dLight_StrobeSlow:
1160 if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, SLOWDARK, false);
1161 break;
1162
1163 case dLight_Strobe_Hurt:
1164 if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, FASTDARK, false);
1165 P_SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0);
1166 break;
1167
1168 case dDamage_Hellslime:
1169 P_SetupSectorDamage(sector, 10, 32, 0, NAME_Slime, 0);
1170 break;
1171
1172 case dDamage_Nukage:
1173 P_SetupSectorDamage(sector, 5, 32, 0, NAME_Slime, 0);
1174 break;
1175
1176 case dLight_Glow:
1177 if (!nothinkers) new DGlow (sector);
1178 break;
1179
1180 case dSector_DoorCloseIn30:
1181 P_SpawnDoorCloseIn30 (sector);
1182 break;
1183
1184 case dDamage_End:
1185 P_SetupSectorDamage(sector, 20, 32, 256, NAME_None, SECF_ENDGODMODE|SECF_ENDLEVEL);
1186 break;
1187
1188 case dLight_StrobeSlowSync:
1189 if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, SLOWDARK, true);
1190 break;
1191
1192 case dLight_StrobeFastSync:
1193 if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, FASTDARK, true);
1194 break;
1195
1196 case dSector_DoorRaiseIn5Mins:
1197 P_SpawnDoorRaiseIn5Mins (sector);
1198 break;
1199
1200 case dFriction_Low:
1201 sector->friction = FRICTION_LOW;
1202 sector->movefactor = 0x269;
1203 sector->Flags |= SECF_FRICTION;
1204 break;
1205
1206 case dDamage_SuperHellslime:
1207 P_SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0);
1208 break;
1209
1210 case dLight_FireFlicker:
1211 if (!nothinkers) new DFireFlicker (sector);
1212 break;
1213
1214 case dDamage_LavaWimpy:
1215 P_SetupSectorDamage(sector, 5, 32, 256, NAME_Fire, SECF_DMGTERRAINFX);
1216 break;
1217
1218 case dDamage_LavaHefty:
1219 P_SetupSectorDamage(sector, 8, 32, 256, NAME_Fire, SECF_DMGTERRAINFX);
1220 break;
1221
1222 case dScroll_EastLavaDamage:
1223 P_SetupSectorDamage(sector, 5, 32, 256, NAME_Fire, SECF_DMGTERRAINFX);
1224 if (!nothinkers)
1225 {
1226 new DStrobe(sector, STROBEBRIGHT, FASTDARK, false);
1227 new DScroller(DScroller::sc_floor, -((FRACUNIT / 2) << 3),
1228 0, -1, int(sector - sectors), 0);
1229 }
1230 keepspecial = true;
1231 break;
1232
1233 case hDamage_Sludge:
1234 P_SetupSectorDamage(sector, 4, 32, 0, NAME_Slime, 0);
1235 break;
1236
1237 case sLight_Strobe_Hurt:
1238 P_SetupSectorDamage(sector, 5, 32, 0, NAME_Slime, 0);
1239 if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, FASTDARK, false);
1240 break;
1241
1242 case sDamage_Hellslime:
1243 P_SetupSectorDamage(sector, 2, 32, 0, NAME_Slime, SECF_HAZARD);
1244 break;
1245
1246 case Damage_InstantDeath:
1247 // Strife's instant death sector
1248 P_SetupSectorDamage(sector, TELEFRAG_DAMAGE, 1, 256, NAME_InstantDeath, 0);
1249 break;
1250
1251 case sDamage_SuperHellslime:
1252 P_SetupSectorDamage(sector, 4, 32, 0, NAME_Slime, SECF_HAZARD);
1253 break;
1254
1255 case Sector_Hidden:
1256 sector->MoreFlags |= SECF_HIDDEN;
1257 break;
1258
1259 case Sector_Heal:
1260 // CoD's healing sector
1261 P_SetupSectorDamage(sector, -1, 32, 0, NAME_None, 0);
1262 break;
1263
1264 case Sky2:
1265 sector->sky = PL_SKYFLAT;
1266 break;
1267
1268 default:
1269 if (sector->special >= Scroll_North_Slow &&
1270 sector->special <= Scroll_SouthWest_Fast)
1271 { // Hexen scroll special
1272 static const signed char hexenScrollies[24][2] =
1273 {
1274 { 0, 1 }, { 0, 2 }, { 0, 4 },
1275 { -1, 0 }, { -2, 0 }, { -4, 0 },
1276 { 0, -1 }, { 0, -2 }, { 0, -4 },
1277 { 1, 0 }, { 2, 0 }, { 4, 0 },
1278 { 1, 1 }, { 2, 2 }, { 4, 4 },
1279 { -1, 1 }, { -2, 2 }, { -4, 4 },
1280 { -1, -1 }, { -2, -2 }, { -4, -4 },
1281 { 1, -1 }, { 2, -2 }, { 4, -4 }
1282 };
1283
1284
1285 int i = sector->special - Scroll_North_Slow;
1286 fixed_t dx = hexenScrollies[i][0] * (FRACUNIT/2);
1287 fixed_t dy = hexenScrollies[i][1] * (FRACUNIT/2);
1288 if (!nothinkers) new DScroller (DScroller::sc_floor, dx, dy, -1, int(sector-sectors), 0);
1289 }
1290 else if (sector->special >= Carry_East5 &&
1291 sector->special <= Carry_East35)
1292 { // Heretic scroll special
1293 // Only east scrollers also scroll the texture
1294 if (!nothinkers) new DScroller (DScroller::sc_floor,
1295 (-FRACUNIT/2)<<(sector->special - Carry_East5),
1296 0, -1, int(sector-sectors), 0);
1297 }
1298 keepspecial = true;
1299 break;
1300 }
1301 if (!keepspecial) sector->special = 0;
1302 }
1303
1304 //
1305 // P_SpawnSpecials
1306 //
1307 // After the map has been loaded, scan for specials that spawn thinkers
1308 //
1309
P_SpawnSpecials(void)1310 void P_SpawnSpecials (void)
1311 {
1312 sector_t *sector;
1313 int i;
1314
1315 P_SetupPortals();
1316
1317 // Init special SECTORs.
1318 sector = sectors;
1319 for (i = 0; i < numsectors; i++, sector++)
1320 {
1321 if (sector->special == 0)
1322 continue;
1323
1324 P_InitSectorSpecial(sector, sector->special, false);
1325 }
1326
1327 // Init other misc stuff
1328
1329 P_SpawnScrollers(); // killough 3/7/98: Add generalized scrollers
1330 P_SpawnFriction(); // phares 3/12/98: New friction model using linedefs
1331 P_SpawnPushers(); // phares 3/20/98: New pusher model using linedefs
1332
1333 TThinkerIterator<ASkyCamCompat> it2;
1334 ASkyCamCompat *pt2;
1335 while ((pt2 = it2.Next()))
1336 {
1337 P_SpawnSkybox(pt2);
1338 }
1339
1340 for (i = 0; i < numlines; i++)
1341 {
1342 switch (lines[i].special)
1343 {
1344 int s;
1345 sector_t *sec;
1346
1347 // killough 3/7/98:
1348 // support for drawn heights coming from different sector
1349 case Transfer_Heights:
1350 {
1351 sec = lines[i].frontsector;
1352 if (lines[i].args[1] & 2)
1353 {
1354 sec->MoreFlags |= SECF_FAKEFLOORONLY;
1355 }
1356 if (lines[i].args[1] & 4)
1357 {
1358 sec->MoreFlags |= SECF_CLIPFAKEPLANES;
1359 }
1360 if (lines[i].args[1] & 8)
1361 {
1362 sec->MoreFlags |= SECF_UNDERWATER;
1363 }
1364 else if (forcewater)
1365 {
1366 sec->MoreFlags |= SECF_FORCEDUNDERWATER;
1367 }
1368 if (lines[i].args[1] & 16)
1369 {
1370 sec->MoreFlags |= SECF_IGNOREHEIGHTSEC;
1371 }
1372 if (lines[i].args[1] & 32)
1373 {
1374 sec->MoreFlags |= SECF_NOFAKELIGHT;
1375 }
1376 FSectorTagIterator itr(lines[i].args[0]);
1377 while ((s = itr.Next()) >= 0)
1378 {
1379 sectors[s].heightsec = sec;
1380 sec->e->FakeFloor.Sectors.Push(§ors[s]);
1381 sectors[s].AdjustFloorClip();
1382 }
1383 break;
1384 }
1385
1386 // killough 3/16/98: Add support for setting
1387 // floor lighting independently (e.g. lava)
1388 case Transfer_FloorLight:
1389 new DLightTransfer (lines[i].frontsector, lines[i].args[0], true);
1390 break;
1391
1392 // killough 4/11/98: Add support for setting
1393 // ceiling lighting independently
1394 case Transfer_CeilingLight:
1395 new DLightTransfer (lines[i].frontsector, lines[i].args[0], false);
1396 break;
1397
1398 // [Graf Zahl] Add support for setting lighting
1399 // per wall independently
1400 case Transfer_WallLight:
1401 new DWallLightTransfer (lines[i].frontsector, lines[i].args[0], lines[i].args[1]);
1402 break;
1403
1404 case Sector_Attach3dMidtex:
1405 P_Attach3dMidtexLinesToSector(lines[i].frontsector, lines[i].args[0], lines[i].args[1], !!lines[i].args[2]);
1406 break;
1407
1408 case Sector_SetLink:
1409 if (lines[i].args[0] == 0)
1410 {
1411 P_AddSectorLinks(lines[i].frontsector, lines[i].args[1], lines[i].args[2], lines[i].args[3]);
1412 }
1413 break;
1414
1415 case Sector_SetPortal:
1416 // arg 0 = sector tag
1417 // arg 1 = type
1418 // - 0: normal (handled here)
1419 // - 1: copy (handled by the portal they copy)
1420 // - 2: EE-style skybox (handled by the camera object)
1421 // other values reserved for later use
1422 // arg 2 = 0:floor, 1:ceiling, 2:both
1423 // arg 3 = 0: anchor, 1: reference line
1424 // arg 4 = for the anchor only: alpha
1425 if (lines[i].args[1] == 0 && lines[i].args[3] == 0)
1426 {
1427 P_SpawnPortal(&lines[i], lines[i].args[0], lines[i].args[2], lines[i].args[4]);
1428 }
1429 break;
1430
1431 // [RH] ZDoom Static_Init settings
1432 case Static_Init:
1433 switch (lines[i].args[1])
1434 {
1435 case Init_Gravity:
1436 {
1437 float grav = ((float)P_AproxDistance (lines[i].dx, lines[i].dy)) / (FRACUNIT * 100.0f);
1438 FSectorTagIterator itr(lines[i].args[0]);
1439 while ((s = itr.Next()) >= 0)
1440 sectors[s].gravity = grav;
1441 }
1442 break;
1443
1444 //case Init_Color:
1445 // handled in P_LoadSideDefs2()
1446
1447 case Init_Damage:
1448 {
1449 int damage = P_AproxDistance (lines[i].dx, lines[i].dy) >> FRACBITS;
1450 FSectorTagIterator itr(lines[i].args[0]);
1451 while ((s = itr.Next()) >= 0)
1452 {
1453 sector_t *sec = §ors[s];
1454 sec->damageamount = damage;
1455 sec->damagetype = NAME_None;
1456 if (sec->damageamount < 20)
1457 {
1458 sec->leakydamage = 0;
1459 sec->damageinterval = 32;
1460 }
1461 else if (sec->damageamount < 50)
1462 {
1463 sec->leakydamage = 5;
1464 sec->damageinterval = 32;
1465 }
1466 else
1467 {
1468 sec->leakydamage = 256;
1469 sec->damageinterval = 1;
1470 }
1471 }
1472 }
1473 break;
1474
1475 case Init_SectorLink:
1476 if (lines[i].args[3] == 0)
1477 P_AddSectorLinksByID(lines[i].frontsector, lines[i].args[0], lines[i].args[2]);
1478 break;
1479
1480 // killough 10/98:
1481 //
1482 // Support for sky textures being transferred from sidedefs.
1483 // Allows scrolling and other effects (but if scrolling is
1484 // used, then the same sector tag needs to be used for the
1485 // sky sector, the sky-transfer linedef, and the scroll-effect
1486 // linedef). Still requires user to use F_SKY1 for the floor
1487 // or ceiling texture, to distinguish floor and ceiling sky.
1488
1489 case Init_TransferSky:
1490 {
1491 FSectorTagIterator itr(lines[i].args[0]);
1492 while ((s = itr.Next()) >= 0)
1493 sectors[s].sky = (i + 1) | PL_SKYFLAT;
1494 break;
1495 }
1496 }
1497 break;
1498 }
1499 }
1500 // [RH] Start running any open scripts on this map
1501 FBehavior::StaticStartTypedScripts (SCRIPT_Open, NULL, false);
1502 }
1503
1504 // killough 2/28/98:
1505 //
1506 // This function, with the help of r_plane.c and r_bsp.c, supports generalized
1507 // scrolling floors and walls, with optional mobj-carrying properties, e.g.
1508 // conveyor belts, rivers, etc. A linedef with a special type affects all
1509 // tagged sectors the same way, by creating scrolling and/or object-carrying
1510 // properties. Multiple linedefs may be used on the same sector and are
1511 // cumulative, although the special case of scrolling a floor and carrying
1512 // things on it, requires only one linedef. The linedef's direction determines
1513 // the scrolling direction, and the linedef's length determines the scrolling
1514 // speed. This was designed so that an edge around the sector could be used to
1515 // control the direction of the sector's scrolling, which is usually what is
1516 // desired.
1517 //
1518 // Process the active scrollers.
1519 //
1520 // This is the main scrolling code
1521 // killough 3/7/98
1522
1523 // [RH] Compensate for rotated sector textures by rotating the scrolling
1524 // in the opposite direction.
RotationComp(const sector_t * sec,int which,fixed_t dx,fixed_t dy,fixed_t & tdx,fixed_t & tdy)1525 static void RotationComp(const sector_t *sec, int which, fixed_t dx, fixed_t dy, fixed_t &tdx, fixed_t &tdy)
1526 {
1527 angle_t an = sec->GetAngle(which);
1528 if (an == 0)
1529 {
1530 tdx = dx;
1531 tdy = dy;
1532 }
1533 else
1534 {
1535 an = an >> ANGLETOFINESHIFT;
1536 fixed_t ca = -finecosine[an];
1537 fixed_t sa = -finesine[an];
1538 tdx = DMulScale16(dx, ca, -dy, sa);
1539 tdy = DMulScale16(dy, ca, dx, sa);
1540 }
1541 }
1542
Tick()1543 void DScroller::Tick ()
1544 {
1545 fixed_t dx = m_dx, dy = m_dy, tdx, tdy;
1546
1547 if (m_Control != -1)
1548 { // compute scroll amounts based on a sector's height changes
1549 fixed_t height = sectors[m_Control].CenterFloor () +
1550 sectors[m_Control].CenterCeiling ();
1551 fixed_t delta = height - m_LastHeight;
1552 m_LastHeight = height;
1553 dx = FixedMul(dx, delta);
1554 dy = FixedMul(dy, delta);
1555 }
1556
1557 // killough 3/14/98: Add acceleration
1558 if (m_Accel)
1559 {
1560 m_vdx = dx += m_vdx;
1561 m_vdy = dy += m_vdy;
1562 }
1563
1564 if (!(dx | dy)) // no-op if both (x,y) offsets are 0
1565 return;
1566
1567 switch (m_Type)
1568 {
1569 case sc_side: // killough 3/7/98: Scroll wall texture
1570 if (m_Parts & scw_top)
1571 {
1572 sides[m_Affectee].AddTextureXOffset(side_t::top, dx);
1573 sides[m_Affectee].AddTextureYOffset(side_t::top, dy);
1574 }
1575 if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
1576 !(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
1577 {
1578 sides[m_Affectee].AddTextureXOffset(side_t::mid, dx);
1579 sides[m_Affectee].AddTextureYOffset(side_t::mid, dy);
1580 }
1581 if (m_Parts & scw_bottom)
1582 {
1583 sides[m_Affectee].AddTextureXOffset(side_t::bottom, dx);
1584 sides[m_Affectee].AddTextureYOffset(side_t::bottom, dy);
1585 }
1586 break;
1587
1588 case sc_floor: // killough 3/7/98: Scroll floor texture
1589 RotationComp(§ors[m_Affectee], sector_t::floor, dx, dy, tdx, tdy);
1590 sectors[m_Affectee].AddXOffset(sector_t::floor, tdx);
1591 sectors[m_Affectee].AddYOffset(sector_t::floor, tdy);
1592 break;
1593
1594 case sc_ceiling: // killough 3/7/98: Scroll ceiling texture
1595 RotationComp(§ors[m_Affectee], sector_t::ceiling, dx, dy, tdx, tdy);
1596 sectors[m_Affectee].AddXOffset(sector_t::ceiling, tdx);
1597 sectors[m_Affectee].AddYOffset(sector_t::ceiling, tdy);
1598 break;
1599
1600 // [RH] Don't actually carry anything here. That happens later.
1601 case sc_carry:
1602 level.Scrolls[m_Affectee].ScrollX += dx;
1603 level.Scrolls[m_Affectee].ScrollY += dy;
1604 break;
1605
1606 case sc_carry_ceiling: // to be added later
1607 break;
1608 }
1609 }
1610
1611 //
1612 // Add_Scroller()
1613 //
1614 // Add a generalized scroller to the thinker list.
1615 //
1616 // type: the enumerated type of scrolling: floor, ceiling, floor carrier,
1617 // wall, floor carrier & scroller
1618 //
1619 // (dx,dy): the direction and speed of the scrolling or its acceleration
1620 //
1621 // control: the sector whose heights control this scroller's effect
1622 // remotely, or -1 if no control sector
1623 //
1624 // affectee: the index of the affected object (sector or sidedef)
1625 //
1626 // accel: non-zero if this is an accelerative effect
1627 //
1628
DScroller(EScrollType type,fixed_t dx,fixed_t dy,int control,int affectee,int accel,int scrollpos)1629 DScroller::DScroller (EScrollType type, fixed_t dx, fixed_t dy,
1630 int control, int affectee, int accel, int scrollpos)
1631 : DThinker (STAT_SCROLLER)
1632 {
1633 m_Type = type;
1634 m_dx = dx;
1635 m_dy = dy;
1636 m_Accel = accel;
1637 m_Parts = scrollpos;
1638 m_vdx = m_vdy = 0;
1639 if ((m_Control = control) != -1)
1640 m_LastHeight =
1641 sectors[control].CenterFloor () + sectors[control].CenterCeiling ();
1642 m_Affectee = affectee;
1643 m_Interpolations[0] = m_Interpolations[1] = m_Interpolations[2] = NULL;
1644
1645 switch (type)
1646 {
1647 case sc_carry:
1648 level.AddScroller (this, affectee);
1649 break;
1650
1651 case sc_side:
1652 sides[affectee].Flags |= WALLF_NOAUTODECALS;
1653 if (m_Parts & scw_top)
1654 {
1655 m_Interpolations[0] = sides[m_Affectee].SetInterpolation(side_t::top);
1656 }
1657 if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
1658 !(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
1659 {
1660 m_Interpolations[1] = sides[m_Affectee].SetInterpolation(side_t::mid);
1661 }
1662 if (m_Parts & scw_bottom)
1663 {
1664 m_Interpolations[2] = sides[m_Affectee].SetInterpolation(side_t::bottom);
1665 }
1666 break;
1667
1668 case sc_floor:
1669 m_Interpolations[0] = sectors[affectee].SetInterpolation(sector_t::FloorScroll, false);
1670 break;
1671
1672 case sc_ceiling:
1673 m_Interpolations[0] = sectors[affectee].SetInterpolation(sector_t::CeilingScroll, false);
1674 break;
1675
1676 default:
1677 break;
1678 }
1679 }
1680
Destroy()1681 void DScroller::Destroy ()
1682 {
1683 for(int i=0;i<3;i++)
1684 {
1685 if (m_Interpolations[i] != NULL)
1686 {
1687 m_Interpolations[i]->DelRef();
1688 m_Interpolations[i] = NULL;
1689 }
1690 }
1691 Super::Destroy();
1692 }
1693
1694 // Adds wall scroller. Scroll amount is rotated with respect to wall's
1695 // linedef first, so that scrolling towards the wall in a perpendicular
1696 // direction is translated into vertical motion, while scrolling along
1697 // the wall in a parallel direction is translated into horizontal motion.
1698 //
1699 // killough 5/25/98: cleaned up arithmetic to avoid drift due to roundoff
1700
DScroller(fixed_t dx,fixed_t dy,const line_t * l,int control,int accel,int scrollpos)1701 DScroller::DScroller (fixed_t dx, fixed_t dy, const line_t *l,
1702 int control, int accel, int scrollpos)
1703 : DThinker (STAT_SCROLLER)
1704 {
1705 fixed_t x = abs(l->dx), y = abs(l->dy), d;
1706 if (y > x)
1707 d = x, x = y, y = d;
1708 d = FixedDiv (x, finesine[(tantoangle[FixedDiv(y,x) >> DBITS] + ANG90)
1709 >> ANGLETOFINESHIFT]);
1710 x = -FixedDiv (FixedMul(dy, l->dy) + FixedMul(dx, l->dx), d);
1711 y = -FixedDiv (FixedMul(dx, l->dy) - FixedMul(dy, l->dx), d);
1712
1713 m_Type = sc_side;
1714 m_dx = x;
1715 m_dy = y;
1716 m_vdx = m_vdy = 0;
1717 m_Accel = accel;
1718 m_Parts = scrollpos;
1719 if ((m_Control = control) != -1)
1720 m_LastHeight = sectors[control].CenterFloor() + sectors[control].CenterCeiling();
1721 m_Affectee = int(l->sidedef[0] - sides);
1722 sides[m_Affectee].Flags |= WALLF_NOAUTODECALS;
1723 m_Interpolations[0] = m_Interpolations[1] = m_Interpolations[2] = NULL;
1724
1725 if (m_Parts & scw_top)
1726 {
1727 m_Interpolations[0] = sides[m_Affectee].SetInterpolation(side_t::top);
1728 }
1729 if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
1730 !(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
1731 {
1732 m_Interpolations[1] = sides[m_Affectee].SetInterpolation(side_t::mid);
1733 }
1734 if (m_Parts & scw_bottom)
1735 {
1736 m_Interpolations[2] = sides[m_Affectee].SetInterpolation(side_t::bottom);
1737 }
1738 }
1739
1740 // Amount (dx,dy) vector linedef is shifted right to get scroll amount
1741 #define SCROLL_SHIFT 5
1742 #define SCROLLTYPE(i) (((i) <= 0) || ((i) & ~7) ? 7 : (i))
1743
1744 // Initialize the scrollers
P_SpawnScrollers(void)1745 static void P_SpawnScrollers(void)
1746 {
1747 int i;
1748 line_t *l = lines;
1749 TArray<int> copyscrollers;
1750
1751 for (i = 0; i < numlines; i++)
1752 {
1753 if (lines[i].special == Sector_CopyScroller)
1754 {
1755 // don't allow copying the scroller if the sector has the same tag as it would just duplicate it.
1756 if (!tagManager.SectorHasTag(lines[i].frontsector, lines[i].args[0]))
1757 {
1758 copyscrollers.Push(i);
1759 }
1760 lines[i].special = 0;
1761 }
1762 }
1763
1764 for (i = 0; i < numlines; i++, l++)
1765 {
1766 fixed_t dx; // direction and speed of scrolling
1767 fixed_t dy;
1768 int control = -1, accel = 0; // no control sector or acceleration
1769 int special = l->special;
1770
1771 // Check for undefined parameters that are non-zero and output messages for them.
1772 // We don't report for specials we don't understand.
1773 if (special != 0)
1774 {
1775 int max = LineSpecialsInfo[special] != NULL ? LineSpecialsInfo[special]->map_args : countof(l->args);
1776 for (unsigned arg = max; arg < countof(l->args); ++arg)
1777 {
1778 if (l->args[arg] != 0)
1779 {
1780 Printf("Line %d (type %d:%s), arg %u is %d (should be 0)\n",
1781 i, special, LineSpecialsInfo[special]->name, arg+1, l->args[arg]);
1782 }
1783 }
1784 }
1785
1786 // killough 3/7/98: Types 245-249 are same as 250-254 except that the
1787 // first side's sector's heights cause scrolling when they change, and
1788 // this linedef controls the direction and speed of the scrolling. The
1789 // most complicated linedef since donuts, but powerful :)
1790 //
1791 // killough 3/15/98: Add acceleration. Types 214-218 are the same but
1792 // are accelerative.
1793
1794 // [RH] Assume that it's a scroller and zero the line's special.
1795 l->special = 0;
1796
1797 dx = dy = 0; // Shut up, GCC
1798
1799 if (special == Scroll_Ceiling ||
1800 special == Scroll_Floor ||
1801 special == Scroll_Texture_Model)
1802 {
1803 if (l->args[1] & 3)
1804 {
1805 // if 1, then displacement
1806 // if 2, then accelerative (also if 3)
1807 control = int(l->sidedef[0]->sector - sectors);
1808 if (l->args[1] & 2)
1809 accel = 1;
1810 }
1811 if (special == Scroll_Texture_Model ||
1812 l->args[1] & 4)
1813 {
1814 // The line housing the special controls the
1815 // direction and speed of scrolling.
1816 dx = l->dx >> SCROLL_SHIFT;
1817 dy = l->dy >> SCROLL_SHIFT;
1818 }
1819 else
1820 {
1821 // The speed and direction are parameters to the special.
1822 dx = (l->args[3] - 128) * (FRACUNIT / 32);
1823 dy = (l->args[4] - 128) * (FRACUNIT / 32);
1824 }
1825 }
1826
1827 switch (special)
1828 {
1829 register int s;
1830
1831 case Scroll_Ceiling:
1832 {
1833 FSectorTagIterator itr(l->args[0]);
1834 while ((s = itr.Next()) >= 0)
1835 {
1836 new DScroller(DScroller::sc_ceiling, -dx, dy, control, s, accel);
1837 }
1838 for (unsigned j = 0; j < copyscrollers.Size(); j++)
1839 {
1840 line_t *line = &lines[copyscrollers[j]];
1841
1842 if (line->args[0] == l->args[0] && (line->args[1] & 1))
1843 {
1844 new DScroller(DScroller::sc_ceiling, -dx, dy, control, int(line->frontsector - sectors), accel);
1845 }
1846 }
1847 break;
1848 }
1849
1850 case Scroll_Floor:
1851 if (l->args[2] != 1)
1852 { // scroll the floor texture
1853 FSectorTagIterator itr(l->args[0]);
1854 while ((s = itr.Next()) >= 0)
1855 {
1856 new DScroller (DScroller::sc_floor, -dx, dy, control, s, accel);
1857 }
1858 for(unsigned j = 0;j < copyscrollers.Size(); j++)
1859 {
1860 line_t *line = &lines[copyscrollers[j]];
1861
1862 if (line->args[0] == l->args[0] && (line->args[1] & 2))
1863 {
1864 new DScroller (DScroller::sc_floor, -dx, dy, control, int(line->frontsector-sectors), accel);
1865 }
1866 }
1867 }
1868
1869 if (l->args[2] > 0)
1870 { // carry objects on the floor
1871 FSectorTagIterator itr(l->args[0]);
1872 while ((s = itr.Next()) >= 0)
1873 {
1874 new DScroller (DScroller::sc_carry, dx, dy, control, s, accel);
1875 }
1876 for(unsigned j = 0;j < copyscrollers.Size(); j++)
1877 {
1878 line_t *line = &lines[copyscrollers[j]];
1879
1880 if (line->args[0] == l->args[0] && (line->args[1] & 4))
1881 {
1882 new DScroller (DScroller::sc_carry, dx, dy, control, int(line->frontsector-sectors), accel);
1883 }
1884 }
1885 }
1886 break;
1887
1888 // killough 3/1/98: scroll wall according to linedef
1889 // (same direction and speed as scrolling floors)
1890 case Scroll_Texture_Model:
1891 {
1892 FLineIdIterator itr(l->args[0]);
1893 while ((s = itr.Next()) >= 0)
1894 {
1895 if (s != i)
1896 new DScroller(dx, dy, lines + s, control, accel);
1897 }
1898 break;
1899 }
1900
1901 case Scroll_Texture_Offsets:
1902 // killough 3/2/98: scroll according to sidedef offsets
1903 s = int(lines[i].sidedef[0] - sides);
1904 new DScroller (DScroller::sc_side, -sides[s].GetTextureXOffset(side_t::mid),
1905 sides[s].GetTextureYOffset(side_t::mid), -1, s, accel, SCROLLTYPE(l->args[0]));
1906 break;
1907
1908 case Scroll_Texture_Left:
1909 l->special = special; // Restore the special, for compat_useblocking's benefit.
1910 s = int(lines[i].sidedef[0] - sides);
1911 new DScroller (DScroller::sc_side, l->args[0] * (FRACUNIT/64), 0,
1912 -1, s, accel, SCROLLTYPE(l->args[1]));
1913 break;
1914
1915 case Scroll_Texture_Right:
1916 l->special = special;
1917 s = int(lines[i].sidedef[0] - sides);
1918 new DScroller (DScroller::sc_side, l->args[0] * (-FRACUNIT/64), 0,
1919 -1, s, accel, SCROLLTYPE(l->args[1]));
1920 break;
1921
1922 case Scroll_Texture_Up:
1923 l->special = special;
1924 s = int(lines[i].sidedef[0] - sides);
1925 new DScroller (DScroller::sc_side, 0, l->args[0] * (FRACUNIT/64),
1926 -1, s, accel, SCROLLTYPE(l->args[1]));
1927 break;
1928
1929 case Scroll_Texture_Down:
1930 l->special = special;
1931 s = int(lines[i].sidedef[0] - sides);
1932 new DScroller (DScroller::sc_side, 0, l->args[0] * (-FRACUNIT/64),
1933 -1, s, accel, SCROLLTYPE(l->args[1]));
1934 break;
1935
1936 case Scroll_Texture_Both:
1937 s = int(lines[i].sidedef[0] - sides);
1938 if (l->args[0] == 0) {
1939 dx = (l->args[1] - l->args[2]) * (FRACUNIT/64);
1940 dy = (l->args[4] - l->args[3]) * (FRACUNIT/64);
1941 new DScroller (DScroller::sc_side, dx, dy, -1, s, accel);
1942 }
1943 break;
1944
1945 default:
1946 // [RH] It wasn't a scroller after all, so restore the special.
1947 l->special = special;
1948 break;
1949 }
1950 }
1951 }
1952
1953 // killough 3/7/98 -- end generalized scroll effects
1954
1955 ////////////////////////////////////////////////////////////////////////////
1956 //
1957 // FRICTION EFFECTS
1958 //
1959 // phares 3/12/98: Start of friction effects
1960
1961 // As the player moves, friction is applied by decreasing the x and y
1962 // velocity values on each tic. By varying the percentage of decrease,
1963 // we can simulate muddy or icy conditions. In mud, the player slows
1964 // down faster. In ice, the player slows down more slowly.
1965 //
1966 // The amount of friction change is controlled by the length of a linedef
1967 // with type 223. A length < 100 gives you mud. A length > 100 gives you ice.
1968 //
1969 // Also, each sector where these effects are to take place is given a
1970 // new special type _______. Changing the type value at runtime allows
1971 // these effects to be turned on or off.
1972 //
1973 // Sector boundaries present problems. The player should experience these
1974 // friction changes only when his feet are touching the sector floor. At
1975 // sector boundaries where floor height changes, the player can find
1976 // himself still 'in' one sector, but with his feet at the floor level
1977 // of the next sector (steps up or down). To handle this, Thinkers are used
1978 // in icy/muddy sectors. These thinkers examine each object that is touching
1979 // their sectors, looking for players whose feet are at the same level as
1980 // their floors. Players satisfying this condition are given new friction
1981 // values that are applied by the player movement code later.
1982
1983 //
1984 // killough 8/28/98:
1985 //
1986 // Completely redid code, which did not need thinkers, and which put a heavy
1987 // drag on CPU. Friction is now a property of sectors, NOT objects inside
1988 // them. All objects, not just players, are affected by it, if they touch
1989 // the sector's floor. Code simpler and faster, only calling on friction
1990 // calculations when an object needs friction considered, instead of doing
1991 // friction calculations on every sector during every tic.
1992 //
1993 // Although this -might- ruin Boom demo sync involving friction, it's the only
1994 // way, short of code explosion, to fix the original design bug. Fixing the
1995 // design bug in Boom's original friction code, while maintaining demo sync
1996 // under every conceivable circumstance, would double or triple code size, and
1997 // would require maintenance of buggy legacy code which is only useful for old
1998 // demos. Doom demos, which are more important IMO, are not affected by this
1999 // change.
2000 //
2001 // [RH] On the other hand, since I've given up on trying to maintain demo
2002 // sync between versions, these considerations aren't a big deal to me.
2003 //
2004 /////////////////////////////
2005 //
2006 // Initialize the sectors where friction is increased or decreased
2007
P_SpawnFriction(void)2008 static void P_SpawnFriction(void)
2009 {
2010 int i;
2011 line_t *l = lines;
2012
2013 for (i = 0 ; i < numlines ; i++,l++)
2014 {
2015 if (l->special == Sector_SetFriction)
2016 {
2017 int length;
2018
2019 if (l->args[1])
2020 { // [RH] Allow setting friction amount from parameter
2021 length = l->args[1] <= 200 ? l->args[1] : 200;
2022 }
2023 else
2024 {
2025 length = P_AproxDistance(l->dx,l->dy)>>FRACBITS;
2026 }
2027
2028 P_SetSectorFriction (l->args[0], length, false);
2029 l->special = 0;
2030 }
2031 }
2032 }
2033
P_SetSectorFriction(int tag,int amount,bool alterFlag)2034 void P_SetSectorFriction (int tag, int amount, bool alterFlag)
2035 {
2036 int s;
2037 fixed_t friction, movefactor;
2038
2039 // An amount of 100 should result in a friction of
2040 // ORIG_FRICTION (0xE800)
2041 friction = (0x1EB8*amount)/0x80 + 0xD001;
2042
2043 // killough 8/28/98: prevent odd situations
2044 friction = clamp(friction, 0, FRACUNIT);
2045
2046 // The following check might seem odd. At the time of movement,
2047 // the move distance is multiplied by 'friction/0x10000', so a
2048 // higher friction value actually means 'less friction'.
2049 movefactor = FrictionToMoveFactor(friction);
2050
2051 FSectorTagIterator itr(tag);
2052 while ((s = itr.Next()) >= 0)
2053 {
2054 // killough 8/28/98:
2055 //
2056 // Instead of spawning thinkers, which are slow and expensive,
2057 // modify the sector's own friction values. Friction should be
2058 // a property of sectors, not objects which reside inside them.
2059 // Original code scanned every object in every friction sector
2060 // on every tic, adjusting its friction, putting unnecessary
2061 // drag on CPU. New code adjusts friction of sector only once
2062 // at level startup, and then uses this friction value.
2063
2064 sectors[s].friction = friction;
2065 sectors[s].movefactor = movefactor;
2066 if (alterFlag)
2067 {
2068 // When used inside a script, the sectors' friction flags
2069 // can be enabled and disabled at will.
2070 if (friction == ORIG_FRICTION)
2071 {
2072 sectors[s].Flags &= ~SECF_FRICTION;
2073 }
2074 else
2075 {
2076 sectors[s].Flags |= SECF_FRICTION;
2077 }
2078 }
2079 }
2080 }
2081
2082 //
2083 // phares 3/12/98: End of friction effects
2084 //
2085 ////////////////////////////////////////////////////////////////////////////
2086
2087 ////////////////////////////////////////////////////////////////////////////
2088 //
2089 // PUSH/PULL EFFECT
2090 //
2091 // phares 3/20/98: Start of push/pull effects
2092 //
2093 // This is where push/pull effects are applied to objects in the sectors.
2094 //
2095 // There are four kinds of push effects
2096 //
2097 // 1) Pushing Away
2098 //
2099 // Pushes you away from a point source defined by the location of an
2100 // MT_PUSH Thing. The force decreases linearly with distance from the
2101 // source. This force crosses sector boundaries and is felt w/in a circle
2102 // whose center is at the MT_PUSH. The force is felt only if the point
2103 // MT_PUSH can see the target object.
2104 //
2105 // 2) Pulling toward
2106 //
2107 // Same as Pushing Away except you're pulled toward an MT_PULL point
2108 // source. This force crosses sector boundaries and is felt w/in a circle
2109 // whose center is at the MT_PULL. The force is felt only if the point
2110 // MT_PULL can see the target object.
2111 //
2112 // 3) Wind
2113 //
2114 // Pushes you in a constant direction. Full force above ground, half
2115 // force on the ground, nothing if you're below it (water).
2116 //
2117 // 4) Current
2118 //
2119 // Pushes you in a constant direction. No force above ground, full
2120 // force if on the ground or below it (water).
2121 //
2122 // The magnitude of the force is controlled by the length of a controlling
2123 // linedef. The force vector for types 3 & 4 is determined by the angle
2124 // of the linedef, and is constant.
2125 //
2126 // For each sector where these effects occur, the sector special type has
2127 // to have the PUSH_MASK bit set. If this bit is turned off by a switch
2128 // at run-time, the effect will not occur. The controlling sector for
2129 // types 1 & 2 is the sector containing the MT_PUSH/MT_PULL Thing.
2130
2131
2132 #define PUSH_FACTOR 7
2133
2134 /////////////////////////////
2135 //
2136 // Add a push thinker to the thinker list
2137
DPusher(DPusher::EPusher type,line_t * l,int magnitude,int angle,AActor * source,int affectee)2138 DPusher::DPusher (DPusher::EPusher type, line_t *l, int magnitude, int angle,
2139 AActor *source, int affectee)
2140 {
2141 m_Source = source;
2142 m_Type = type;
2143 if (l)
2144 {
2145 m_Xmag = l->dx>>FRACBITS;
2146 m_Ymag = l->dy>>FRACBITS;
2147 m_Magnitude = P_AproxDistance (m_Xmag, m_Ymag);
2148 }
2149 else
2150 { // [RH] Allow setting magnitude and angle with parameters
2151 ChangeValues (magnitude, angle);
2152 }
2153 if (source) // point source exist?
2154 {
2155 m_Radius = (m_Magnitude) << (FRACBITS+1); // where force goes to zero
2156 m_X = m_Source->X();
2157 m_Y = m_Source->Y();
2158 }
2159 m_Affectee = affectee;
2160 }
2161
CheckForSectorMatch(EPusher type,int tag)2162 int DPusher::CheckForSectorMatch (EPusher type, int tag)
2163 {
2164 if (m_Type == type && tagManager.SectorHasTag(m_Affectee, tag))
2165 return m_Affectee;
2166 else
2167 return -1;
2168 }
2169
2170
2171 /////////////////////////////
2172 //
2173 // T_Pusher looks for all objects that are inside the radius of
2174 // the effect.
2175 //
Tick()2176 void DPusher::Tick ()
2177 {
2178 sector_t *sec;
2179 AActor *thing;
2180 msecnode_t *node;
2181 int xspeed,yspeed;
2182 int ht;
2183
2184 if (!var_pushers)
2185 return;
2186
2187 sec = sectors + m_Affectee;
2188
2189 // Be sure the special sector type is still turned on. If so, proceed.
2190 // Else, bail out; the sector type has been changed on us.
2191
2192 if (!(sec->Flags & SECF_PUSH))
2193 return;
2194
2195 // For constant pushers (wind/current) there are 3 situations:
2196 //
2197 // 1) Affected Thing is above the floor.
2198 //
2199 // Apply the full force if wind, no force if current.
2200 //
2201 // 2) Affected Thing is on the ground.
2202 //
2203 // Apply half force if wind, full force if current.
2204 //
2205 // 3) Affected Thing is below the ground (underwater effect).
2206 //
2207 // Apply no force if wind, full force if current.
2208 //
2209 // Apply the effect to clipped players only for now.
2210 //
2211 // In Phase II, you can apply these effects to Things other than players.
2212 // [RH] No Phase II, but it works with anything having MF2_WINDTHRUST now.
2213
2214 if (m_Type == p_push)
2215 {
2216 // Seek out all pushable things within the force radius of this
2217 // point pusher. Crosses sectors, so use blockmap.
2218
2219 FBlockThingsIterator it(FBoundingBox(m_X, m_Y, m_Radius));
2220 AActor *thing;
2221
2222 while ((thing = it.Next()))
2223 {
2224 // Normal ZDoom is based only on the WINDTHRUST flag, with the noclip cheat as an exemption.
2225 bool pusharound = ((thing->flags2 & MF2_WINDTHRUST) && !(thing->flags & MF_NOCLIP));
2226
2227 // MBF allows any sentient or shootable thing to be affected, but players with a fly cheat aren't.
2228 if (compatflags & COMPATF_MBFMONSTERMOVE)
2229 {
2230 pusharound = ((pusharound || (thing->IsSentient()) || (thing->flags & MF_SHOOTABLE)) // Add categories here
2231 && (!(thing->player && (thing->flags & (MF_NOGRAVITY))))); // Exclude flying players here
2232 }
2233
2234 if ((pusharound) )
2235 {
2236 int sx = m_X;
2237 int sy = m_Y;
2238 int dist = thing->AproxDistance (sx, sy);
2239 int speed = (m_Magnitude - ((dist>>FRACBITS)>>1))<<(FRACBITS-PUSH_FACTOR-1);
2240
2241 // If speed <= 0, you're outside the effective radius. You also have
2242 // to be able to see the push/pull source point.
2243
2244 if ((speed > 0) && (P_CheckSight (thing, m_Source, SF_IGNOREVISIBILITY)))
2245 {
2246 angle_t pushangle = thing->AngleTo(sx, sy);
2247 if (m_Source->GetClass()->TypeName == NAME_PointPusher)
2248 pushangle += ANG180; // away
2249 pushangle >>= ANGLETOFINESHIFT;
2250 thing->velx += FixedMul (speed, finecosine[pushangle]);
2251 thing->vely += FixedMul (speed, finesine[pushangle]);
2252 }
2253 }
2254 }
2255 return;
2256 }
2257
2258 // constant pushers p_wind and p_current
2259
2260 node = sec->touching_thinglist; // things touching this sector
2261 for ( ; node ; node = node->m_snext)
2262 {
2263 thing = node->m_thing;
2264 if (!(thing->flags2 & MF2_WINDTHRUST) || (thing->flags & MF_NOCLIP))
2265 continue;
2266
2267 sector_t *hsec = sec->GetHeightSec();
2268 if (m_Type == p_wind)
2269 {
2270 if (hsec == NULL)
2271 { // NOT special water sector
2272 if (thing->Z() > thing->floorz) // above ground
2273 {
2274 xspeed = m_Xmag; // full force
2275 yspeed = m_Ymag;
2276 }
2277 else // on ground
2278 {
2279 xspeed = (m_Xmag)>>1; // half force
2280 yspeed = (m_Ymag)>>1;
2281 }
2282 }
2283 else // special water sector
2284 {
2285 ht = hsec->floorplane.ZatPoint(thing);
2286 if (thing->Z() > ht) // above ground
2287 {
2288 xspeed = m_Xmag; // full force
2289 yspeed = m_Ymag;
2290 }
2291 else if (thing->player->viewz < ht) // underwater
2292 {
2293 xspeed = yspeed = 0; // no force
2294 }
2295 else // wading in water
2296 {
2297 xspeed = (m_Xmag)>>1; // half force
2298 yspeed = (m_Ymag)>>1;
2299 }
2300 }
2301 }
2302 else // p_current
2303 {
2304 const secplane_t *floor;
2305
2306 if (hsec == NULL)
2307 { // NOT special water sector
2308 floor = &sec->floorplane;
2309 }
2310 else
2311 { // special water sector
2312 floor = &hsec->floorplane;
2313 }
2314 if (thing->Z() > floor->ZatPoint(thing))
2315 { // above ground
2316 xspeed = yspeed = 0; // no force
2317 }
2318 else
2319 { // on ground/underwater
2320 xspeed = m_Xmag; // full force
2321 yspeed = m_Ymag;
2322 }
2323 }
2324 thing->velx += xspeed<<(FRACBITS-PUSH_FACTOR);
2325 thing->vely += yspeed<<(FRACBITS-PUSH_FACTOR);
2326 }
2327 }
2328
2329 /////////////////////////////
2330 //
2331 // P_GetPushThing() returns a pointer to an MT_PUSH or MT_PULL thing,
2332 // NULL otherwise.
2333
P_GetPushThing(int s)2334 AActor *P_GetPushThing (int s)
2335 {
2336 AActor* thing;
2337 sector_t* sec;
2338
2339 sec = sectors + s;
2340 thing = sec->thinglist;
2341
2342 while (thing &&
2343 thing->GetClass()->TypeName != NAME_PointPusher &&
2344 thing->GetClass()->TypeName != NAME_PointPuller)
2345 {
2346 thing = thing->snext;
2347 }
2348 return thing;
2349 }
2350
2351 /////////////////////////////
2352 //
2353 // Initialize the sectors where pushers are present
2354 //
2355
P_SpawnPushers()2356 static void P_SpawnPushers ()
2357 {
2358 int i;
2359 line_t *l = lines;
2360 register int s;
2361
2362 for (i = 0; i < numlines; i++, l++)
2363 {
2364 switch (l->special)
2365 {
2366 case Sector_SetWind: // wind
2367 {
2368 FSectorTagIterator itr(l->args[0]);
2369 while ((s = itr.Next()) >= 0)
2370 new DPusher(DPusher::p_wind, l->args[3] ? l : NULL, l->args[1], l->args[2], NULL, s);
2371 l->special = 0;
2372 break;
2373 }
2374
2375 case Sector_SetCurrent: // current
2376 {
2377 FSectorTagIterator itr(l->args[0]);
2378 while ((s = itr.Next()) >= 0)
2379 new DPusher(DPusher::p_current, l->args[3] ? l : NULL, l->args[1], l->args[2], NULL, s);
2380 l->special = 0;
2381 break;
2382 }
2383
2384 case PointPush_SetForce: // push/pull
2385 if (l->args[0]) { // [RH] Find thing by sector
2386 FSectorTagIterator itr(l->args[0]);
2387 while ((s = itr.Next()) >= 0)
2388 {
2389 AActor *thing = P_GetPushThing (s);
2390 if (thing) { // No MT_P* means no effect
2391 // [RH] Allow narrowing it down by tid
2392 if (!l->args[1] || l->args[1] == thing->tid)
2393 new DPusher (DPusher::p_push, l->args[3] ? l : NULL, l->args[2],
2394 0, thing, s);
2395 }
2396 }
2397 } else { // [RH] Find thing by tid
2398 AActor *thing;
2399 FActorIterator iterator (l->args[1]);
2400
2401 while ( (thing = iterator.Next ()) )
2402 {
2403 if (thing->GetClass()->TypeName == NAME_PointPusher ||
2404 thing->GetClass()->TypeName == NAME_PointPuller)
2405 {
2406 new DPusher (DPusher::p_push, l->args[3] ? l : NULL, l->args[2],
2407 0, thing, int(thing->Sector - sectors));
2408 }
2409 }
2410 }
2411 l->special = 0;
2412 break;
2413 }
2414 }
2415 }
2416
2417 //
2418 // phares 3/20/98: End of Pusher effects
2419 //
2420 ////////////////////////////////////////////////////////////////////////////
2421
AdjustFloorClip() const2422 void sector_t::AdjustFloorClip () const
2423 {
2424 msecnode_t *node;
2425
2426 for (node = touching_thinglist; node; node = node->m_snext)
2427 {
2428 if (node->m_thing->flags2 & MF2_FLOORCLIP)
2429 {
2430 node->m_thing->AdjustFloorClip();
2431 }
2432 }
2433 }
2434