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