1 /*
2 ** lnspec.cpp
3 **
4 **---------------------------------------------------------------------------
5 ** Copyright 2011 Braden Obrzut
6 ** All rights reserved.
7 **
8 ** Redistribution and use in source and binary forms, with or without
9 ** modification, are permitted provided that the following conditions
10 ** are met:
11 **
12 ** 1. Redistributions of source code must retain the above copyright
13 ** notice, this list of conditions and the following disclaimer.
14 ** 2. Redistributions in binary form must reproduce the above copyright
15 ** notice, this list of conditions and the following disclaimer in the
16 ** documentation and/or other materials provided with the distribution.
17 ** 3. The name of the author may not be used to endorse or promote products
18 ** derived from this software without specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 **---------------------------------------------------------------------------
31 **
32 **
33 */
34
35 #include "doomerrors.h"
36 #include "id_ca.h"
37 #include "id_sd.h"
38 #include "g_conversation.h"
39 #include "lnspec.h"
40 #include "actor.h"
41 #include "sndseq.h"
42 #include "thinker.h"
43 #include "wl_act.h"
44 #include "wl_agent.h"
45 #include "wl_draw.h"
46 #include "wl_game.h"
47 #include "wl_loadsave.h"
48 #include "wl_play.h"
49 #include "g_mapinfo.h"
50 #include "g_shared/a_keys.h"
51 #include "thingdef/thingdef.h"
52 using namespace Specials;
53
54 #define DEFINE_SPECIAL(name,num,argc) \
55 static int LN_##name(MapSpot spot, const int args[], MapTrigger::Side direction, AActor *activator);
56 #define FUNC(name) \
57 static int LN_##name(MapSpot spot, const int args[], MapTrigger::Side direction, AActor *activator)
58
59 DEFINE_SPECIAL(NOP, 0, 0)
60 #include "lnspecials.h"
61 #undef DEFINE_SPECIAL
62
63 LineSpecialFunction lnspecFunctions[NUM_POSSIBLE_SPECIALS] =
64 {
65 LN_NOP,
66
67 #define DEFINE_SPECIAL(name,num,argc) LN_##name,
68 #include "lnspecials.h"
69 #undef DEFINE_SPECIAL
70 };
71
72 #define DEFINE_SPECIAL(name,num,args) { #name, num, args},
73 const struct LineSpecialMeta
74 {
75 const char* const name;
76 const unsigned int num;
77 const unsigned short args;
78 } lnspecMeta[] = {
79 #include "lnspecials.h"
80 { NULL, 0, 0 }
81 };
82
LookupFunctionNum(const char * const function)83 LineSpecials Specials::LookupFunctionNum(const char* const function)
84 {
85 const LineSpecialMeta *func = lnspecMeta;
86 do
87 {
88 if(stricmp(func->name, function) == 0)
89 return static_cast<LineSpecials> (func->num);
90 }
91 while((++func)->name != NULL);
92 return NUM_POSSIBLE_SPECIALS;
93 }
LookupFunction(LineSpecials function)94 LineSpecialFunction Specials::LookupFunction(LineSpecials function)
95 {
96 if(function >= NUM_POSSIBLE_SPECIALS)
97 return LN_NOP;
98
99 return lnspecFunctions[function];
100 }
101
102 ////////////////////////////////////////////////////////////////////////////////
103
FUNC(NOP)104 FUNC(NOP)
105 {
106 return 0;
107 }
108
109 class EVDoor : public Thinker
110 {
111 DECLARE_CLASS(EVDoor, Thinker)
112
113 public:
EVDoor(MapSpot spot,unsigned int speed,int opentics,bool direction,unsigned int style)114 EVDoor(MapSpot spot, unsigned int speed, int opentics, bool direction, unsigned int style) : Thinker(ThinkerList::WORLD),
115 state(Closed), spot(spot), amount(0), opentics(opentics), direction(direction)
116 {
117 if(spot->tile->soundSequence == NAME_None)
118 seqname = gameinfo.DoorSoundSequence;
119 else
120 seqname = spot->tile->soundSequence;
121 sndseq = NULL;
122
123 spot->slideStyle = style;
124 if(spot->slideAmount[direction] == 0 && spot->slideAmount[direction+2] == 0)
125 ChangeState(Opening);
126 else
127 {
128 // This should occur if we have a negative duration door
129 // allow manual closing.
130 amount = spot->slideAmount[direction];
131 ChangeState(Closing);
132 }
133 spot->thinker = this;
134 this->speed = 64*speed;
135 }
136
Destroy()137 void Destroy()
138 {
139 if(sndseq)
140 delete sndseq;
141 if(spot->thinker == this)
142 spot->thinker = NULL;
143 Super::Destroy();
144 }
145
IsClosing() const146 bool IsClosing() const
147 {
148 return state == Closing || state == Closed;
149 }
150
Tick()151 void Tick()
152 {
153 if(sndseq)
154 sndseq->Tick();
155
156 switch(state)
157 {
158 default:
159 break;
160 case Opening:
161 // Connect sound
162 if(amount == 0)
163 {
164 const MapZone *zone1 = spot->GetAdjacent(MapTile::Side(direction))->zone;
165 const MapZone *zone2 = spot->GetAdjacent(MapTile::Side(direction), true)->zone;
166 map->LinkZones(zone1, zone2, true);
167
168 if(map->CheckLink(zone1, players[0].mo->GetZone(), true))
169 sndseq = new SndSeqPlayer(SoundSeq(seqname, SEQ_OpenNormal), spot);
170 }
171
172 if(amount < 0xffff)
173 amount += speed;
174 if(amount >= 0xffff)
175 {
176 amount = 0xffff;
177 if(opentics < 0) // Negative delay means stay open
178 Destroy();
179 else
180 ChangeState(Opened);
181 }
182 spot->slideAmount[direction] = spot->slideAmount[direction+2] = amount;
183 break;
184 case Opened:
185 if(wait == 0)
186 {
187 if(CheckJammed(false))
188 {
189 // Might as well reset the door timer, chances are the actor isn't going any where
190 wait = opentics;
191 break;
192 }
193 ChangeState(Closing);
194 }
195 else
196 --wait;
197 break;
198 case Closing:
199 if(amount > 0)
200 amount -= speed;
201 if(amount <= 0)
202 {
203 amount = 0;
204 Destroy();
205
206 // Disconnect Sound
207 const MapZone *zone1 = spot->GetAdjacent(MapTile::Side(direction))->zone;
208 const MapZone *zone2 = spot->GetAdjacent(MapTile::Side(direction), true)->zone;
209 map->LinkZones(zone1, zone2, false);
210 }
211 spot->slideAmount[direction] = spot->slideAmount[direction+2] = amount;
212 break;
213 }
214 }
215
216 // forceclose is a temporary workaround for the issue where monsters can
217 // jam elevator doors and breaking levels. TODO: Replace with ROTT's
218 // crushing/gibbing behavior.
Reactivate(AActor * activator,bool ismonster,bool forceclose=false)219 bool Reactivate(AActor *activator, bool ismonster, bool forceclose=false)
220 {
221 switch(state)
222 {
223 case Opened:
224 if(ismonster) // Monsters can reset the door
225 {
226 wait = opentics;
227 return false;
228 }
229 default:
230 if(ismonster || CheckJammed(forceclose))
231 break;
232 return ChangeState(Closing);
233 case Closing:
234 return ChangeState(Opening);
235 }
236 return false;
237 }
238
Serialize(FArchive & arc)239 void Serialize(FArchive &arc)
240 {
241 BYTE state = this->state;
242 arc << state;
243 this->state = static_cast<State>(state);
244
245 arc << spot
246 << speed
247 << amount
248 << wait
249 << direction;
250 if(GameSave::SaveVersion > 1410810515)
251 arc << sndseq << seqname << opentics;
252
253 Super::Serialize(arc);
254 }
255
256 private:
257 enum State { Opening, Opened, Closing, Closed };
258
259 // Returns true if the actor isn't blocking the door.
CheckClears(AActor * obj) const260 bool CheckClears(AActor *obj) const
261 {
262 if(direction == 0)
263 {
264 if(spot->GetY() == obj->tiley &&
265 abs((((fixed)spot->GetX()<<FRACBITS)+(FRACUNIT/2))-obj->x) < FRACUNIT/2 + obj->radius)
266 {
267 return false;
268 }
269 }
270 else
271 {
272 if(spot->GetX() == obj->tilex &&
273 abs((((fixed)spot->GetY()<<FRACBITS)+(FRACUNIT/2))-obj->y) < FRACUNIT/2 + obj->radius)
274 {
275 return false;
276 }
277 }
278 return true;
279 }
280
CheckJammed(bool onlysolid) const281 bool CheckJammed(bool onlysolid) const
282 {
283 for(AActor::Iterator iter = AActor::GetIterator();iter.Next();)
284 {
285 if(!CheckClears(iter))
286 {
287 if(!onlysolid || (iter->flags & FL_SOLID))
288 return true;
289 }
290 }
291 return false;
292 }
293
ChangeState(State st)294 bool ChangeState(State st)
295 {
296 if(st == state)
297 return false;
298 state = st;
299
300 switch(state)
301 {
302 default:
303 break;
304 case Opened:
305 wait = opentics;
306 if(sndseq)
307 {
308 delete sndseq;
309 sndseq = NULL;
310 }
311 break;
312 case Closing:
313 if(map->CheckLink(spot->GetAdjacent(MapTile::Side(direction))->zone, players[0].mo->GetZone(), true))
314 sndseq = new SndSeqPlayer(SoundSeq(seqname, SEQ_CloseNormal), spot);
315 break;
316 }
317 return true;
318 }
319
320 State state;
321 MapSpot spot;
322
323 SndSeqPlayer *sndseq;
324 FName seqname;
325
326 unsigned int speed;
327 int amount;
328 int opentics;
329 unsigned int wait;
330 bool direction;
331 };
332 IMPLEMENT_INTERNAL_CLASS(EVDoor)
333
FUNC(Door_Open)334 FUNC(Door_Open)
335 {
336 static const unsigned int DOOR_TYPE_DIRECTION = 0x1;
337
338 if(activator->player)
339 {
340 if(buttonheld[bt_use])
341 return 0;
342 }
343
344 if(activator->player || (activator->flags & FL_REQUIREKEYS))
345 {
346 if(args[3] != 0)
347 {
348 if(!P_CheckKeys(activator, args[3], false))
349 return 0;
350 }
351 }
352
353 if(args[0] == 0)
354 {
355 if(spot->thinker)
356 {
357 if(spot->thinker->IsThinkerType<EVDoor>())
358 {
359 return barrier_cast<EVDoor *>(spot->thinker)->Reactivate(activator, activator && (activator->flags & FL_ISMONSTER));
360 }
361 return 0;
362 }
363
364 new EVDoor(spot, args[1], args[2], args[4]&DOOR_TYPE_DIRECTION, args[4]>>1);
365 }
366 else
367 {
368 bool activated = false;
369 MapSpot door = NULL;
370 while((door = map->GetSpotByTag(args[0], door)))
371 {
372 if(door->thinker)
373 {
374 if(door->thinker->IsThinkerType<EVDoor>())
375 {
376 return barrier_cast<EVDoor *>(door->thinker)->Reactivate(activator, activator && (activator->flags & FL_ISMONSTER));
377 }
378 continue;
379 }
380
381 activated = true;
382 new EVDoor(door, args[1], args[2], args[4]&DOOR_TYPE_DIRECTION, args[4]>>1);
383 }
384 return activated;
385 }
386 return 1;
387 }
388
389 class EVElevator : public Thinker
390 {
391 DECLARE_CLASS(EVElevator, Thinker)
392 public:
EVElevator(AActor * activator,MapSpot spot,MapSpot door,MapSpot next,unsigned int elevTag,unsigned int callSpeed)393 EVElevator(AActor *activator, MapSpot spot, MapSpot door, MapSpot next, unsigned int elevTag, unsigned int callSpeed) :
394 Thinker(ThinkerList::WORLD), activator(activator), sndseq(NULL),
395 spot(spot), door(door), next(next), nextDoor(NULL), elevTag(elevTag),
396 callSpeed(callSpeed)
397 {
398 state = Moving;
399
400 // Try to close the door if it is open.
401 if(door->thinker)
402 {
403 if(door->thinker->IsThinkerType<EVDoor>())
404 {
405 state = ClosingDoor;
406
407 EVDoor *doorThinker = barrier_cast<EVDoor *>(door->thinker);
408 if(!doorThinker->IsClosing())
409 {
410 if(!doorThinker->Reactivate(activator, activator && (activator->flags & FL_ISMONSTER), true))
411 {
412 Destroy();
413 return;
414 }
415 }
416 }
417 else
418 {
419 Destroy();
420 return;
421 }
422 }
423
424 if(state == Moving)
425 StartSoundSequence();
426
427 // Remove elevator position since we're off!
428 map->elevatorPosition[elevTag] = NULL;
429 }
430
Destroy()431 void Destroy()
432 {
433 delete sndseq;
434 Super::Destroy();
435 }
436
Tick()437 void Tick()
438 {
439 if(sndseq)
440 sndseq->Tick();
441
442 switch(state)
443 {
444 default:
445 Destroy();
446 break;
447 case ClosingDoor:
448 if(door->thinker)
449 break;
450
451 state = Moving;
452 StartSoundSequence();
453 // Fall through
454 case Moving:
455 if(--callSpeed == 0)
456 {
457 state = Shaking;
458 callSpeed = 20; // ROTT uses 10 tics @ 35Hz
459 if(sndseq)
460 {
461 sndseq->SetSource(next);
462 sndseq->Stop();
463 }
464
465 map->elevatorPosition[elevTag] = next;
466
467 // Find elevator trigger on destination
468 for(unsigned int i = 0;i < next->triggers.Size();++i)
469 {
470 if(next->triggers[i].action == Elevator_SwitchFloor)
471 {
472 nextDoor = map->GetSpotByTag(next->triggers[i].arg[1], NULL);
473 }
474 }
475 if(!nextDoor)
476 {
477 Printf("Failed to perform elevator teleport.\n");
478 state = Finished;
479 break;
480 }
481
482 if(activator)
483 {
484 /* Teleport the player, trying to rotate according to the
485 * orientation of the detination. ROTT only changed the
486 * location of the player, so this could be an issue. In
487 * that case we just need to remove the angle change and
488 * the fracx/y change since ROTT use elevator things and
489 * not the switch as the reference point.
490 */
491 const int ddeltax = clamp<int>(nextDoor->GetX() - next->GetX(), -1, 1);
492 const int ddeltay = clamp<int>(nextDoor->GetY() - next->GetY(), -1, 1);
493 const int sdeltax = clamp<int>(door->GetX() - spot->GetX(), -1, 1);
494 const int sdeltay = clamp<int>(door->GetY() - spot->GetY(), -1, 1);
495 int relx = spot->GetX()-activator->tilex;
496 int rely = spot->GetY()-activator->tiley;
497 fixed fracx = activator->fracx;
498 fixed fracy = activator->fracy;
499 angle_t angle = 0;
500 if((ddeltax && ddeltax == -sdeltax) || (ddeltay && ddeltay == -sdeltay))
501 {
502 relx *= -1;
503 rely *= -1;
504 angle = ANGLE_180;
505 fracx = (FRACUNIT-fracx);
506 fracy = (FRACUNIT-fracy);
507 }
508 else if(ddeltax == 0 && sdeltax)
509 {
510 int tmp = rely;
511 if(ddeltay < 0)
512 {
513 rely = -relx;
514 relx = tmp;
515 angle = ANGLE_90;
516
517 tmp = (FRACUNIT-fracx);
518 fracx = fracy;
519 fracy = tmp;
520 }
521 else
522 {
523 rely = relx;
524 relx = -tmp;
525 angle = ANGLE_270;
526
527 tmp = fracx;
528 fracx = (FRACUNIT-fracy);
529 fracy = tmp;
530
531 }
532 }
533 else if(ddeltay == 0 && sdeltay)
534 {
535 int tmp = relx;
536 if(ddeltax < 0)
537 {
538 relx = rely;
539 rely = -tmp;
540 angle = ANGLE_90;
541
542 tmp = fracy;
543 fracy = (FRACUNIT-fracx);
544 fracx = tmp;
545 }
546 else
547 {
548 relx = -rely;
549 rely = tmp;
550 angle = ANGLE_270;
551
552 tmp = (FRACUNIT-fracy);
553 fracy = fracx;
554 fracx = tmp;
555 }
556 }
557 activator->x = ((next->GetX() - relx)<<16)|fracx;
558 activator->y = ((next->GetY() - rely)<<16)|fracy;
559 activator->angle += angle;
560 activator->EnterZone(map->GetSpot(activator->tilex, activator->tiley, 0)->zone);
561 }
562 }
563 break;
564 case Shaking:
565 // TODO: Shake the screen
566 if(--callSpeed == 0)
567 {
568 state = Finished;
569
570 // Find the actual door trigger and try to open it.
571 for(unsigned int i = nextDoor->triggers.Size();i-- > 0;)
572 {
573 MapTrigger &trig = nextDoor->triggers[i];
574 if(trig.action == Door_Elevator)
575 {
576 map->ActivateTrigger(trig, MapTrigger::East, activator);
577 break;
578 }
579 }
580 }
581 break;
582 }
583 }
584
Serialize(FArchive & arc)585 void Serialize(FArchive &arc)
586 {
587 if(GameSave::SaveVersion > 1438232816)
588 {
589 BYTE state = this->state;
590 arc << state;
591 this->state = static_cast<State>(state);
592
593 arc << activator
594 << sndseq
595 << spot
596 << door
597 << next
598 << nextDoor
599 << elevTag
600 << callSpeed;
601 }
602
603 Super::Serialize(arc);
604 }
605
606 protected:
StartSoundSequence()607 void StartSoundSequence()
608 {
609 FName seqname;
610 if(spot->tile->soundSequence == NAME_None)
611 seqname = NAME_Platform;
612 else
613 seqname = spot->tile->soundSequence;
614
615 sndseq = new SndSeqPlayer(SoundSeq(seqname, SEQ_OpenNormal), spot);
616 }
617
618 private:
619 enum State { ClosingDoor, Moving, Shaking, Finished };
620
621 TObjPtr<AActor> activator;
622 SndSeqPlayer *sndseq;
623 MapSpot spot;
624 MapSpot door;
625 MapSpot next;
626 MapSpot nextDoor;
627 unsigned int elevTag;
628 unsigned int callSpeed;
629 State state;
630 };
631 IMPLEMENT_INTERNAL_POINTY_CLASS(EVElevator)
DECLARE_POINTER(activator)632 DECLARE_POINTER(activator)
633 END_POINTERS
634
635 // Takes same arugments as Door_Open, but the tag points to the elevator switch.
636 // Will attempt to call the elevator if not in the correct position.
637 FUNC(Door_Elevator)
638 {
639 static const unsigned int DOOR_TYPE_DIRECTION = 0x1;
640
641 // If no activator it's probably the elevator reaching its destination.
642 if(activator)
643 {
644 if(activator->player)
645 {
646 if(buttonheld[bt_use])
647 return 0;
648 }
649
650 if(activator->player || (activator->flags & FL_REQUIREKEYS))
651 {
652 if(args[3] != 0)
653 {
654 if(!P_CheckKeys(activator, args[3], false))
655 return 0;
656 }
657 }
658 }
659
660 if(spot->thinker)
661 {
662 if(spot->thinker->IsThinkerType<EVDoor>())
663 {
664 return barrier_cast<EVDoor *>(spot->thinker)->Reactivate(activator, activator && (activator->flags & FL_ISMONSTER));
665 }
666 return 0;
667 }
668
669 // Now locate the switch and elevator trigger so we can find out if we need
670 // to call to our location.
671 MapSpot swtch = map->GetSpotByTag(args[0], NULL);
672 if(!swtch)
673 {
674 Printf("Door_Elevator: Could not find switch.\n");
675 return 0;
676 }
677 MapTrigger *trig = NULL;
678 for(unsigned int i = 0;i < swtch->triggers.Size();++i)
679 {
680 if(swtch->triggers[i].action == Elevator_SwitchFloor)
681 {
682 trig = &swtch->triggers[i];
683 break;
684 }
685 }
686 if(!trig)
687 {
688 Printf("Door_Elevator: Could not find elevator trigger.\n");
689 return 0;
690 }
691
692 // Call elevator
693 if(map->elevatorPosition[trig->arg[0]] != swtch)
694 {
695 // Check if elevator is active
696 if(map->elevatorPosition[trig->arg[0]] == NULL)
697 return 0;
698
699 new EVElevator(activator, swtch, spot, swtch, trig->arg[0], trig->arg[2]);
700 return 1;
701 }
702
703 new EVDoor(spot, args[1], args[2], args[4]&DOOR_TYPE_DIRECTION, args[4]>>1);
704 return 1;
705 }
706
FUNC(Elevator_SwitchFloor)707 FUNC(Elevator_SwitchFloor)
708 { // elevTag, doorTag, callSpeed, nextTag
709 MapSpot door = map->GetSpotByTag(args[1], NULL);
710 MapSpot next = map->GetSpotByTag(args[3], NULL);
711 if(spot->thinker || !door || !next)
712 return 0;
713
714 EVElevator *elevator = new EVElevator(activator, spot, door, next, args[0], args[2]);
715 if(!(elevator->ObjectFlags & OF_EuthanizeMe))
716 {
717 spot->thinker = elevator;
718 return 1;
719 }
720 return 0;
721 }
722
723 class EVPushwall : public Thinker
724 {
725 DECLARE_CLASS(EVPushwall, Thinker)
726
727 public:
EVPushwall(MapSpot spot,unsigned int speed,MapTrigger::Side direction,unsigned int distance)728 EVPushwall(MapSpot spot, unsigned int speed, MapTrigger::Side direction, unsigned int distance) :
729 Thinker(ThinkerList::WORLD), spot(spot), moveTo(NULL), direction(direction), position(0),
730 speed(speed), distance(distance)
731 {
732 if(spot->tile->soundSequence == NAME_None)
733 seqname = gameinfo.PushwallSoundSequence;
734 else
735 seqname = spot->tile->soundSequence;
736 sndseq = NULL;
737
738 if(distance == 0) // ROTT style pushwall, move until blocked
739 distance = 0xFFFF;
740
741 spot->thinker = this;
742 spot->pushDirection = MapTile::Side(direction);
743 }
744
CheckSpotFree(MapSpot spot)745 static bool CheckSpotFree(MapSpot spot)
746 {
747 if(spot->tile)
748 {
749 // Hit a wall so we're done.
750 return false;
751 }
752
753 // Check for any blocking actors
754 AActor::Iterator iter = AActor::GetIterator();
755 int movex = spot->GetX();
756 int movey = spot->GetY();
757 while(iter.Next())
758 {
759 AActor *actor = iter;
760 if((actor->flags&FL_ISMONSTER) || actor->player)
761 {
762 if(actor->tilex+dirdeltax[actor->dir] == movex &&
763 actor->tiley+dirdeltay[actor->dir] == movey)
764 {
765 // Blocked!
766 return false;
767 }
768 }
769 }
770 return true;
771 }
772
Destroy()773 void Destroy()
774 {
775 if(sndseq)
776 delete sndseq;
777 if(spot->thinker == this)
778 spot->thinker = NULL;
779 Super::Destroy();
780 }
781
Tick()782 void Tick()
783 {
784 if(position == 0)
785 sndseq = new SndSeqPlayer(SoundSeq(seqname, SEQ_OpenNormal), spot);
786
787 if(sndseq)
788 sndseq->Tick();
789
790 // Setup the next tile to get ready to accept this tile.
791 if(moveTo == NULL)
792 {
793 moveTo = spot->GetAdjacent(MapTile::Side(direction));
794
795 if(moveTo == NULL)
796 {
797 Destroy();
798 return;
799 // Maybe in the future this can be a flag or something
800 #if 0
801 FString error;
802 error.Format("\"I'm free!\" -Pushwall @ (%d, %d)", spot->GetX(), spot->GetY());
803 throw CRecoverableError(error);
804 #endif
805 }
806
807 if(!CheckSpotFree(moveTo))
808 {
809 Destroy();
810 return;
811 }
812
813 moveTo->SetTile(spot->tile);
814 moveTo->pushReceptor = spot;
815 moveTo->pushDirection = spot->pushDirection;
816
817 // Try to get a sound zone.
818 if(spot->zone == NULL)
819 spot->zone = moveTo->zone;
820 }
821
822 // Move the tile a bit.
823 if((position += speed) > 1024)
824 {
825 position -= 1024;
826 spot->pushAmount = 0;
827 spot->SetTile(NULL);
828 spot->thinker = NULL;
829 moveTo->pushReceptor = NULL;
830 moveTo->thinker = this;
831
832 // Transfer amflags
833 moveTo->amFlags |= spot->amFlags;
834
835 spot = moveTo;
836 moveTo = NULL;
837 }
838 else
839 spot->pushAmount = position/16;
840
841 if(!moveTo)
842 {
843 if(--distance == 0)
844 Destroy();
845 }
846 }
847
Serialize(FArchive & arc)848 void Serialize(FArchive &arc)
849 {
850 arc << spot
851 << moveTo
852 << direction
853 << position
854 << speed
855 << distance;
856
857 if(GameSave::SaveVersion > 1410810515)
858 arc << sndseq << seqname;
859
860 Super::Serialize(arc);
861 }
862
863 private:
864
865 MapSpot spot, moveTo;
866
867 SndSeqPlayer *sndseq;
868 FName seqname;
869
870 unsigned short direction;
871 unsigned int position;
872 unsigned int speed;
873 unsigned int distance;
874 };
875 IMPLEMENT_INTERNAL_CLASS(EVPushwall)
876
FUNC(Pushwall_Move)877 FUNC(Pushwall_Move)
878 {
879 static const unsigned int PUSHWALL_DIR_DIAGONAL = 0x1;
880 static const unsigned int PUSHWALL_DIR_ABSOLUTE = 0x8;
881
882 if(args[2] & PUSHWALL_DIR_DIAGONAL)
883 throw CRecoverableError("Diagonal pushwalls not yet supported!");
884
885 bool absolute = !!(args[2]&PUSHWALL_DIR_ABSOLUTE);
886 MapTrigger::Side dir = absolute ? MapTrigger::Side((args[2]>>1)&0x3) : MapTrigger::Side((direction + 1 + (args[2]>>1))&0x3);
887
888 if(args[0] == 0)
889 {
890 if(spot->thinker || !spot->tile || spot->GetAdjacent(MapTile::Side(dir))->tile)
891 {
892 return 0;
893 }
894
895 new EVPushwall(spot, args[1], dir, args[3]);
896 }
897 else
898 {
899 bool activated = false;
900 MapSpot pwall = NULL;
901 while((pwall = map->GetSpotByTag(args[0], pwall)))
902 {
903 if(pwall->thinker || !pwall->tile || pwall->GetAdjacent(MapTile::Side(dir))->tile)
904 {
905 continue;
906 }
907
908 activated = true;
909 new EVPushwall(pwall, args[1], dir, args[3]);
910 }
911 return activated;
912 }
913 return 1;
914 }
915
FUNC(Exit_Normal)916 FUNC(Exit_Normal)
917 {
918 if(buttonheld[bt_use])
919 return 0;
920 buttonheld[bt_use] = true;
921
922 playstate = ex_completed;
923 SD_WaitSoundDone();
924 return 1;
925 }
926
FUNC(Exit_Secret)927 FUNC(Exit_Secret)
928 {
929 if(buttonheld[bt_use])
930 return 0;
931 buttonheld[bt_use] = true;
932
933 playstate = ex_secretlevel;
934 SD_WaitSoundDone();
935 return 1;
936 }
937
FUNC(Exit_Victory)938 FUNC(Exit_Victory)
939 {
940 if(buttonheld[bt_use])
941 return 0;
942 buttonheld[bt_use] = true;
943
944 playstate = ex_victorious;
945 SD_WaitSoundDone();
946 return 1;
947 }
948
FUNC(Teleport_NewMap)949 FUNC(Teleport_NewMap)
950 {
951 if(buttonheld[bt_use])
952 return 0;
953 buttonheld[bt_use] = true;
954
955 playstate = ex_newmap;
956 NewMap.newmap = args[0];
957 NewMap.flags = args[2];
958 return 1;
959 }
960
961 class EVVictorySpin : public Thinker
962 {
963 DECLARE_CLASS(EVVictorySpin, Thinker)
964 HAS_OBJECT_POINTERS
965 public:
966 // Note that we trigger slightly a half unit before wolf3d did
EVVictorySpin(AActor * activator,MapTrigger::Side direction)967 EVVictorySpin(AActor *activator, MapTrigger::Side direction) : Thinker(ThinkerList::VICTORY),
968 doturn(true), dist(6*FRACUNIT + FRACUNIT/2), activator(activator)
969 {
970 gamestate.victoryflag = true;
971 players[0].SetPSprite(NULL, player_t::ps_weapon);
972
973 runner = AActor::Spawn(ClassDef::FindClass("BJRun"), activator->x, activator->y, 0, SPAWN_AllowReplacement);
974 runner->flags |= FL_PATHING;
975 runner->angle = ((direction+2)%4)*ANGLE_90;
976 runner->dir = static_cast<dirtype>(runner->angle/ANGLE_45);
977 runner->SetPriority(ThinkerList::VICTORY);
978
979 activator->SetPriority(ThinkerList::VICTORY);
980 }
981
Serialize(FArchive & arc)982 void Serialize(FArchive &arc)
983 {
984 arc << doturn
985 << dist
986 << activator
987 << runner;
988
989 Super::Serialize(arc);
990 }
991
Tick()992 void Tick()
993 {
994 if(doturn)
995 {
996 angle_t oldangle = activator->angle;
997 A_Face(activator, runner, 3*ANGLE_1);
998 if(activator->angle == oldangle)
999 doturn = false;
1000 }
1001
1002 // Keep the runner in line with the player (A_Chase will try to tile align)
1003 switch(runner->dir)
1004 {
1005 case north:
1006 case south:
1007 runner->x = activator->x;
1008 break;
1009 case east:
1010 case west:
1011 runner->y = activator->y;
1012 break;
1013 default: break;
1014 }
1015
1016 if(dist > 0)
1017 {
1018 static const unsigned int speed = 4096;
1019
1020 if(dist <= speed)
1021 {
1022 activator->x += FixedMul(dist, finecosine[runner->angle>>ANGLETOFINESHIFT]);
1023 activator->y -= FixedMul(dist, finesine[runner->angle>>ANGLETOFINESHIFT]);
1024 dist = 0;
1025 }
1026 else
1027 {
1028 activator->x += FixedMul(speed, finecosine[runner->angle>>ANGLETOFINESHIFT]);
1029 activator->y -= FixedMul(speed, finesine[runner->angle>>ANGLETOFINESHIFT]);
1030 dist -= speed;
1031 }
1032 }
1033 else
1034 {
1035 // This is the gross part, we must do a "collision" check.
1036 // If it passes we're done here, but we must first make the
1037 // runner a projectile and then make sure it doesn't try to
1038 // explode on us.
1039 fixed radius = runner->radius + activator->radius + runner->speed;
1040 if(abs(activator->x - runner->x) <= radius &&
1041 abs(activator->y - runner->y) <= radius)
1042 {
1043 runner->Die();
1044 runner->velx = FixedMul(runner->runspeed, finecosine[runner->angle>>ANGLETOFINESHIFT]);
1045 runner->vely = -FixedMul(runner->runspeed, finesine[runner->angle>>ANGLETOFINESHIFT]);
1046 runner->flags |= FL_MISSILE;
1047 runner->radius = 1;
1048 Destroy();
1049 }
1050 }
1051 }
1052
1053 bool doturn;
1054 unsigned int dist;
1055 TObjPtr<AActor> activator;
1056 TObjPtr<AActor> runner;
1057 };
1058 IMPLEMENT_INTERNAL_POINTY_CLASS(EVVictorySpin)
DECLARE_POINTER(activator)1059 DECLARE_POINTER(activator)
1060 DECLARE_POINTER(runner)
1061 END_POINTERS
1062 FUNC(Exit_VictorySpin)
1063 {
1064 if(buttonheld[bt_use])
1065 return 0;
1066 buttonheld[bt_use] = true;
1067
1068 new EVVictorySpin(activator, direction);
1069 return 1;
1070 }
1071
1072 // Executes all triggers on a particular map spot
1073 // Useful for making events happen in binary format maps
FUNC(Trigger_Execute)1074 FUNC(Trigger_Execute)
1075 {
1076 if(!map->IsValidTileCoordinate(args[0], args[1], args[2]))
1077 return 0;
1078
1079 MapSpot target = map->GetSpot(args[0], args[1], args[2]);
1080
1081 bool activated = false;
1082 for(unsigned int i = target->triggers.Size();i-- > 0;)
1083 {
1084 if(map->ActivateTrigger(target->triggers[i], MapTrigger::East, activator))
1085 activated = true;
1086 }
1087
1088 return activated;
1089 }
1090
FUNC(StartConversation)1091 FUNC(StartConversation)
1092 {
1093 if(args[0] != 0)
1094 I_Error("TIDs are not yet supported.");
1095
1096 AActor *talker = activator;
1097
1098 if(args[1] == 1) // Face talker
1099 {
1100 A_Face(activator, talker);
1101 }
1102
1103 Dialog::StartConversation(talker);
1104
1105 return 1;
1106 }
1107