1//**************************************************************************
2//**
3//**    ##   ##    ##    ##   ##   ####     ####   ###     ###
4//**    ##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
5//**     ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
6//**     ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
7//**      ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
8//**       #    ##    ##    #      ####     ####   ##       ##
9//**
10//**    $Id: LineSpecialLevelInfo.vc 4140 2010-03-04 23:04:43Z firebrand_kh $
11//**
12//**    Copyright (C) 1999-2006 Jānis Legzdiņš
13//**
14//**    This program is free software; you can redistribute it and/or
15//**  modify it under the terms of the GNU General Public License
16//**  as published by the Free Software Foundation; either version 2
17//**  of the License, or (at your option) any later version.
18//**
19//**    This program is distributed in the hope that it will be useful,
20//**  but WITHOUT ANY WARRANTY; without even the implied warranty of
21//**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22//**  GNU General Public License for more details.
23//**
24//**************************************************************************
25
26class LineSpecialLevelInfo : LevelInfo;
27
28const int
29	STROBEBRIGHT	= 5,
30	FASTDARK		= 15,
31	SLOWDARK		= 35;
32
33//
34// Map things flags
35//
36const int
37	MTF_AMBUSH		= 0x0008,	// Deaf monsters/do not react to sound.
38	MTF_DORMANT		= 0x0010,	// The thing is dormant
39	MTF_GSINGLE		= 0x0100,	// Appearing in game modes
40	MTF_GCOOP		= 0x0200,
41	MTF_GDEATHMATCH	= 0x0400,
42	MTF_SHADOW		= 0x0800,
43	MTF_ALTSHADOW	= 0x1000,
44	MTF_FRIENDLY	= 0x2000,
45	MTF_STANDSTILL	= 0x4000,
46
47	MTF2_FIGHTER	= 0x000010000,	// Thing appearing in player classes
48	MTF2_CLERIC		= 0x000020000,
49	MTF2_MAGE		= 0x000040000;
50
51enum
52{
53	pt_static,
54	pt_explode,
55	pt_explode2,
56	pt_ice_chunk,
57	pt_rail,
58	pt_fountain,
59	pt_spark
60};
61
62//	Flags for SectorDamage
63const int DAMAGE_PLAYERS			= 1;
64const int DAMAGE_NONPLAYERS			= 2;
65const int DAMAGE_IN_AIR				= 4;
66const int DAMAGE_SUBCLASSES_PROTECT	= 8;
67
68const int BODYQUESIZE				= 32;
69const int CORPSEQUEUESIZE			= 64;
70
71const int
72	SECF_SILENT 					= 1,
73	SECF_NOFALLINGDAMAGE			= 2;
74
75name			DefaultDoorSound;
76name			DefaultCeilingSound;
77name			DefaultSilentCeilingSound;
78name			DefaultFloorSound;
79name			DefaultFloorAltSound;
80name			DefaultStairStepSound;
81name			DefaultPlatformSound;
82
83bool			bTeleportNewMapBothSides;
84bool			bCheckStrifeStartSpots;
85bool			bDefaultStopOnCrush;
86
87bool			bPuffSpawned;
88
89int				ExtPlayersBase;
90string			Lock103Message;
91
92EntityEx		CurrentSpeaker;
93EntityEx		CurrentSpeakingTo;
94float			OldSpeakerAngle;
95int				CurrentSpeechIndex;
96bool			ConversationSlideshow;
97
98EntityEx		bodyque[BODYQUESIZE];
99int				BodyQueSize;
100int				bodyqueslot;
101
102// Corpse queue for monsters
103EntityEx		corpseQueue[CORPSEQUEUESIZE];
104int				CorpseQueSize;
105int				corpseQueueSlot;
106
107//==========================================================================
108//
109//  SpawnSpecials
110//
111//==========================================================================
112
113void SpawnSpecials()
114{
115	sector_t *sector;
116	int i;
117
118	if (Level.bLightning)
119	{
120		LightningThinker Lightning = Spawn(LightningThinker);
121		Lightning.Init();
122	}
123
124	//  Init special SECTORs.
125	for (i = 0; i < XLevel.NumSectors; i++)
126	{
127		sector = &XLevel.Sectors[i];
128		if (!sector->special)
129		{
130			continue;
131		}
132		if (sector->special & SECSPEC_SECRET_MASK)
133		{
134			//	Secret sector.
135			TotalSecret++;
136		}
137		switch (sector->special & SECSPEC_BASE_MASK)
138		{
139		case SECSPEC_LightPhased:	//	Phased light
140			//	Hardcoded base, use sector->lightlevel as the index
141			SpawnPhasedLight(sector, 80, -1);
142			sector->special &= ~SECSPEC_BASE_MASK;
143			break;
144		case SECSPEC_LightSequenceStart:	// Phased light sequence start
145			SpawnLightSequence(sector, 1.0);
146			sector->special &= ~SECSPEC_BASE_MASK;
147			break;
148			// Specials 3 & 4 are used by the phased light sequences
149		case SECSPEC_LightFlicker:
150			SpawnLightFlash(sector);
151			sector->special &= ~SECSPEC_BASE_MASK;
152			break;
153		case SECSPEC_LightStrobeFast:
154			SpawnStrobeFlash(sector, FASTDARK, STROBEBRIGHT, 0);
155			sector->special &= ~SECSPEC_BASE_MASK;
156			break;
157		case SECSPEC_LightStrobeSlow:
158			SpawnStrobeFlash(sector, SLOWDARK, STROBEBRIGHT, 0);
159			sector->special &= ~SECSPEC_BASE_MASK;
160			break;
161		case SECSPEC_LightStrobeFastDamage:
162			SpawnStrobeFlash(sector, FASTDARK, STROBEBRIGHT, 0);
163			break;
164		case SECSPEC_LightGlow:
165			SpawnGlowingLight(sector);
166			sector->special &= ~SECSPEC_BASE_MASK;
167			break;
168		case SECSPEC_DoorCloseIn30:
169			SpawnDoorCloseIn30(sector);
170			break;
171		case SECSPEC_LightSyncStrobeSlow:
172			SpawnStrobeFlash(sector, SLOWDARK, STROBEBRIGHT, 1);
173			sector->special &= ~SECSPEC_BASE_MASK;
174			break;
175		case SECSPEC_LightSyncStrobeFast:
176			SpawnStrobeFlash(sector, FASTDARK, STROBEBRIGHT, 1);
177			sector->special &= ~SECSPEC_BASE_MASK;
178			break;
179		case SECSPEC_DoorRaiseIn5Minutes:
180			SpawnDoorRaiseIn5Mins(sector);
181			break;
182		case SECSPEC_LightFireFlicker:
183			SpawnFireFlicker(sector);
184			sector->special &= ~SECSPEC_BASE_MASK;
185			break;
186		case SECSPEC_ScrollEastLavaDamage:
187			SpawnScrollingFloor(sector, -1, 0, 3);
188			break;
189		case SECSPEC_ScrollNorthSlow:
190		case SECSPEC_ScrollNorthMedium:
191		case SECSPEC_ScrollNorthFast:
192			SpawnScrollingFloor(sector, 0, 1, (sector->special &
193				SECSPEC_BASE_MASK) - SECSPEC_ScrollNorthSlow);
194			break;
195		case SECSPEC_ScrollEastSlow:
196		case SECSPEC_ScrollEastMedium:
197		case SECSPEC_ScrollEastFast:
198			SpawnScrollingFloor(sector, -1, 0, (sector->special &
199				SECSPEC_BASE_MASK) - SECSPEC_ScrollEastSlow);
200			break;
201		case SECSPEC_ScrollSouthSlow:
202		case SECSPEC_ScrollSouthMedium:
203		case SECSPEC_ScrollSouthFast:
204			SpawnScrollingFloor(sector, 0, -1, (sector->special &
205				SECSPEC_BASE_MASK) - SECSPEC_ScrollSouthSlow);
206			break;
207		case SECSPEC_ScrollWestSlow:
208		case SECSPEC_ScrollWestMedium:
209		case SECSPEC_ScrollWestFast:
210			SpawnScrollingFloor(sector, 1, 0, (sector->special &
211				SECSPEC_BASE_MASK) - SECSPEC_ScrollWestSlow);
212			break;
213		case SECSPEC_ScrollNorthWestSlow:
214		case SECSPEC_ScrollNorthWestMedium:
215		case SECSPEC_ScrollNorthWestFast:
216			SpawnScrollingFloor(sector, 1, 1, (sector->special &
217				SECSPEC_BASE_MASK) - SECSPEC_ScrollNorthWestSlow);
218			break;
219		case SECSPEC_ScrollNorthEastSlow:
220		case SECSPEC_ScrollNorthEastMedium:
221		case SECSPEC_ScrollNorthEastFast:
222			SpawnScrollingFloor(sector, -1, 1, (sector->special &
223				SECSPEC_BASE_MASK) - SECSPEC_ScrollNorthEastSlow);
224			break;
225		case SECSPEC_ScrollSouthEastSlow:
226		case SECSPEC_ScrollSouthEastMedium:
227		case SECSPEC_ScrollSouthEastFast:
228			SpawnScrollingFloor(sector, -1, -1, (sector->special &
229				SECSPEC_BASE_MASK) - SECSPEC_ScrollSouthEastSlow);
230			break;
231		case SECSPEC_ScrollSouthWestSlow:
232		case SECSPEC_ScrollSouthWestMedium:
233		case SECSPEC_ScrollSouthWestFast:
234			SpawnScrollingFloor(sector, 1, -1, (sector->special &
235				SECSPEC_BASE_MASK) - SECSPEC_ScrollSouthWestSlow);
236			break;
237		case SECSPEC_ScrollEast5:
238		case SECSPEC_ScrollEast10:
239		case SECSPEC_ScrollEast25:
240		case SECSPEC_ScrollEast30:
241		case SECSPEC_ScrollEast35:
242			SpawnScrollingFloor(sector, -1, 0, (sector->special &
243				SECSPEC_BASE_MASK) - SECSPEC_ScrollEast5);
244			break;
245		}
246	}
247
248	//  Init line EFFECTs
249	for (i = 0; i < XLevel.NumLines; i++)
250	{
251		switch (XLevel.Lines[i].special)
252		{
253		case LNSPEC_ScrollTextureLeft:
254			SpawnWallScroller(&XLevel.Lines[i], 1, 0);
255			XLevel.Lines[i].special = 0;
256			break;
257		case LNSPEC_ScrollTextureRight:
258			SpawnWallScroller(&XLevel.Lines[i], -1, 0);
259			XLevel.Lines[i].special = 0;
260			break;
261		case LNSPEC_ScrollTextureUp:
262			SpawnWallScroller(&XLevel.Lines[i], 0, 1);
263			XLevel.Lines[i].special = 0;
264			break;
265		case LNSPEC_ScrollTextureDown:
266			SpawnWallScroller(&XLevel.Lines[i], 0, -1);
267			XLevel.Lines[i].special = 0;
268			break;
269		case LNSPEC_ScrollTextureBoth:
270			SpawnTextureBothScroller(&XLevel.Lines[i]);
271			XLevel.Lines[i].special = 0;
272			break;
273		case LNSPEC_ScrollTextureModel:
274			SpawnScrollTextureModel(&XLevel.Lines[i]);
275			XLevel.Lines[i].special = 0;
276			break;
277		case LNSPEC_ScrollFloor:
278			SpawnScrollFloor(&XLevel.Lines[i]);
279			XLevel.Lines[i].special = 0;
280			break;
281		case LNSPEC_ScrollCeiling:
282			SpawnScrollCeiling(&XLevel.Lines[i]);
283			XLevel.Lines[i].special = 0;
284			break;
285		case LNSPEC_ScrollTextureOffsets:
286			SpawnWallOffsetsScroller(&XLevel.Lines[i]);
287			XLevel.Lines[i].special = 0;
288			break;
289		case LNSPEC_TransferWallLight:
290			SpawnTransferWallLight(&XLevel.Lines[i]);
291			XLevel.Lines[i].special = 0;
292			break;
293		}
294	}
295
296	SpawnPushers();
297}
298
299//==========================================================================
300//
301//  ActivateLine
302//
303//==========================================================================
304
305final bool ActivateLine(line_t* Line, EntityEx A, int Side,
306	int ActivationType)
307{
308	int lineActivation;
309	bool repeat;
310	bool buttonSuccess;
311	bool changeBack;
312
313	if (!CheckActivation(ActivationType, Line, Side, A))
314	{
315		return false;
316	}
317
318	lineActivation = Line->SpacFlags;
319	repeat = Line->flags & ML_REPEAT_SPECIAL;
320	buttonSuccess = ExecuteActionSpecial(Line->special, Line->arg1,
321		Line->arg2, Line->arg3, Line->arg4, Line->arg5, Line, Side, A);
322	changeBack = Line->special == LNSPEC_GlassBreak &&
323		(Line->flags & ML_TWOSIDED) && buttonSuccess;
324	if ((lineActivation & (SPAC_Use | SPAC_Impact | SPAC_UseThrough)) &&
325		buttonSuccess)
326	{
327		byte Quest;
328		ChangeSwitchTexture(Line->sidenum[0], repeat,
329			Line->special == LNSPEC_ExitNormal ||
330			Line->special == LNSPEC_ExitSecret ||
331			Line->special == LNSPEC_TeleportNewMap ||
332			Line->special == LNSPEC_TeleportEndGame
333			? 'switches/exitbutn' : 'switches/normbutn', Quest);
334	}
335	if (changeBack)
336	{
337		XLevel.Sides[Line->sidenum[1]].MidTexture =
338			XLevel.Sides[Line->sidenum[0]].MidTexture;
339	}
340	if (!repeat && buttonSuccess)
341	{
342		// clear the special on non-retriggerable lines
343		Line->special = 0;
344	}
345	return true;
346}
347
348//==========================================================================
349//
350//  CheckActivation
351//
352//==========================================================================
353
354final bool CheckActivation(int activationType, line_t* line, int Side,
355	EntityEx A)
356{
357	if ((line->flags & ML_FIRSTSIDEONLY) && Side == 1)
358	{
359		return false;
360	}
361
362	int lineActivation = line->SpacFlags;
363	if (lineActivation & SPAC_UseThrough)
364	{
365		lineActivation |= SPAC_Use;
366	}
367	else if (line->special == LNSPEC_Teleport &&
368		(lineActivation & SPAC_Cross) && activationType == SPAC_PCross &&
369		A && A.bMissile)
370	{
371		// Let missiles use regular player teleports
372		lineActivation |= SPAC_PCross;
373	}
374	//	BOOM's generalized line types that allow monster use can actually be
375	// activated by anything except projectiles.
376	if (lineActivation & SPAC_AnyCross)
377	{
378		lineActivation |= SPAC_Cross | SPAC_MCross;
379	}
380	if (!(lineActivation & activationType))
381	{
382		if (activationType == SPAC_Use && (lineActivation & SPAC_MUse) &&
383			!A.bIsPlayer && A.bCanUseWalls)
384		{
385			return true;
386		}
387		if (activationType == SPAC_Push && (lineActivation & SPAC_MPush) &&
388			!A.bIsPlayer && A.bActivatePushWall)
389		{
390			return true;
391		}
392		if (activationType != SPAC_MCross || !(lineActivation & SPAC_Cross))
393		{
394			return false;
395		}
396	}
397	if (A && !A.bIsPlayer && !A.bMissile &&
398		!(line->flags & ML_MONSTERSCANACTIVATE) &&
399		(activationType != SPAC_MCross || !(lineActivation & SPAC_MCross)))
400	{
401		//	With lax monster activation monsters can activate several line
402		// specials even when not marked as monster activateable. This is
403		// the default for non-Hexen maps in Hexen format.
404		if (!Level.bLaxMonsterActivation)
405		{
406			return false;
407		}
408
409		if ((activationType == SPAC_Use || activationType == SPAC_Push) &&
410			(line->flags & ML_SECRET))
411		{
412			return false;	// never open secret doors
413		}
414
415		bool noway = true;
416		switch (activationType)
417		{
418		case SPAC_MCross:
419			if (!(lineActivation & SPAC_MCross))
420			{
421				switch (line->special)
422				{
423				case LNSPEC_DoorRaise:
424					if (line->arg2 >= 64)
425					{
426						break;
427					}
428				case LNSPEC_PlatDownWaitUpStay:
429				case LNSPEC_Teleport:
430				case LNSPEC_TeleportNoFog:
431				case LNSPEC_PlatDownWaitUpStayLip:
432				case LNSPEC_TeleportLine:
433					noway = false;
434				}
435			}
436			else
437			{
438				noway = false;
439			}
440			break;
441
442		case SPAC_Use:
443		case SPAC_Push:
444			switch (line->special)
445			{
446			case LNSPEC_DoorRaise:
447				if (line->arg1 == 0 && line->arg2 < 64)
448				{
449					noway = false;
450				}
451				break;
452			case LNSPEC_Teleport:
453			case LNSPEC_TeleportNoFog:
454				noway = false;
455			}
456			break;
457
458		default:
459			noway = false;
460			break;
461		}
462		return !noway;
463	}
464	if (activationType == SPAC_MCross && !(lineActivation & SPAC_MCross) &&
465		!(line->flags & ML_MONSTERSCANACTIVATE))
466	{
467		return false;
468	}
469	return true;
470}
471
472//==========================================================================
473//
474//  ExecuteActionSpecial
475//
476//==========================================================================
477
478int ExecuteActionSpecial(int Special, int Arg1, int Arg2, int Arg3,
479	int Arg4, int Arg5, line_t* Line, int Side, Entity E)
480{
481	EntityEx A = EntityEx(E);
482	int buttonSuccess = false;
483	switch (Special)
484	{
485	case LNSPEC_PolyStartLine:
486		break;
487	case LNSPEC_PolyRotateLeft:
488		buttonSuccess = EV_RotatePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 1,
489			false);
490		break;
491	case LNSPEC_PolyRotateRight:
492		buttonSuccess = EV_RotatePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, -1,
493			false);
494		break;
495	case LNSPEC_PolyMove:
496		buttonSuccess = EV_MovePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5,
497			false, false);
498		break;
499	case LNSPEC_PolyExplicitLine: // Only used in initialization
500		break;
501	case LNSPEC_PolyMoveTimes8:
502		buttonSuccess = EV_MovePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, true,
503			false);
504		break;
505	case LNSPEC_PolyDoorSwing:
506		buttonSuccess = EV_OpenPolyDoor(Line, Arg1, Arg2, Arg3, Arg4, Arg5,
507			PolyobjDoor::PODOOR_SWING);
508		break;
509	case LNSPEC_PolyDoorSlide:
510		buttonSuccess = EV_OpenPolyDoor(Line, Arg1, Arg2, Arg3, Arg4, Arg5,
511			PolyobjDoor::PODOOR_SLIDE);
512		break;
513	case LNSPEC_DoorClose:
514		buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5,
515			VerticalDoor::DOOREV_Close, Line, A);
516		break;
517	case LNSPEC_DoorOpen:
518		buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5,
519			VerticalDoor::DOOREV_Open, Line, A);
520		break;
521	case LNSPEC_DoorRaise:
522		buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5,
523			VerticalDoor::DOOREV_Raise, Line, A);
524		break;
525	case LNSPEC_DoorLockedRaise:
526		if (CheckLock(A, Arg4, true))
527		{
528			buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5,
529				VerticalDoor::DOOREV_RaiseLocked, Line, A);
530		}
531		break;
532	case LNSPEC_DoorAnimated:
533		buttonSuccess = EV_TextureChangeDoor(Arg1, Arg2, Arg3, Arg4, Arg5,
534			Line, A);
535		break;
536	case LNSPEC_Autosave:
537		AutoSave();
538		buttonSuccess = true;
539		break;
540	case LNSPEC_ThingRaise:
541		buttonSuccess = EV_ThingRaise(Arg1, Arg2, Arg3, Arg4, Arg5, A);
542		break;
543	case LNSPEC_StartConversation:
544		buttonSuccess = EV_StartConversation(Arg1, Arg2, Arg3, Arg4, Arg5, A);
545		break;
546	case LNSPEC_ThingStop:
547		buttonSuccess = EV_ThingStop(Arg1, Arg2, Arg3, Arg4, Arg5, A);
548		break;
549	case LNSPEC_FloorLowerByValue:
550		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
551			FloorMover::FLOOREV_LowerByValue, Line);
552		break;
553	case LNSPEC_FloorLowerToLowest:
554		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
555			FloorMover::FLOOREV_LowerToLowest, Line);
556		break;
557	case LNSPEC_FloorLowerToNearest:
558		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
559			FloorMover::FLOOREV_LowerToNearest, Line);
560		break;
561	case LNSPEC_FloorRaiseByValue:
562		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
563			FloorMover::FLOOREV_RaiseByValue, Line);
564		break;
565	case LNSPEC_FloorRaiseToHighest:
566		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
567			FloorMover::FLOOREV_RaiseToHighest, Line);
568		break;
569	case LNSPEC_FloorRaiseToNearest:
570		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
571			FloorMover::FLOOREV_RaiseToNearest, Line);
572		break;
573	case LNSPEC_StairsBuildDownNormal:
574		buttonSuccess = EV_BuildStairs(Arg1, Arg2, Arg3, Arg4, Arg5,
575			StairStepMover::STAIRSEV_DownNormal);
576		break;
577	case LNSPEC_StairsBuildUpNormal:
578		buttonSuccess = EV_BuildStairs(Arg1, Arg2, Arg3, Arg4, Arg5,
579			StairStepMover::STAIRSEV_UpNormal);
580		break;
581	case LNSPEC_FloorRaiseAndCrush:
582		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
583			FloorMover::FLOOREV_RaiseAndCrush, Line);
584		break;
585	case LNSPEC_PillarBuild:	// (no crushing)
586		buttonSuccess = EV_BuildPillar(Arg1, Arg2, Arg3, Arg4, Arg5, false);
587		break;
588	case LNSPEC_PillarOpen:
589		buttonSuccess = EV_OpenPillar(Arg1, Arg2, Arg3, Arg4, Arg5);
590		break;
591	case LNSPEC_StairsBuildDownSync:
592		buttonSuccess = EV_BuildStairs(Arg1, Arg2, Arg3, Arg4, Arg5,
593			StairStepMover::STAIRSEV_DownSync);
594		break;
595	case LNSPEC_StairsBuildUpSync:
596		buttonSuccess = EV_BuildStairs(Arg1, Arg2, Arg3, Arg4, Arg5,
597			StairStepMover::STAIRSEV_UpSync);
598		break;
599	case LNSPEC_ForceField:
600		buttonSuccess = EV_ForceField(Arg1, Arg2, Arg3, Arg4, Arg5, A);
601		break;
602	case LNSPEC_ClearForceField:
603		buttonSuccess = EV_RemoveForceField(Arg1, Arg2, Arg3, Arg4, Arg5);
604		break;
605	case LNSPEC_FloorRaiseByValueTimes8:
606		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
607			FloorMover::FLOOREV_RaiseByValueTimes8, Line);
608		break;
609	case LNSPEC_FloorLowerByValueTimes8:
610		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
611			FloorMover::FLOOREV_LowerByValueTimes8, Line);
612		break;
613	case LNSPEC_FloorMoveToValue:
614		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
615			FloorMover::FLOOREV_MoveToValue, Line);
616		break;
617	case LNSPEC_CeilingWaggle:
618		buttonSuccess = EV_StartCeilingWaggle(Arg1, Arg2, Arg3, Arg4, Arg5);
619		break;
620	case LNSPEC_TeleportZombieChanger:
621		if (Side == 0)
622		{
623			//	Only teleport when crossing the front side of a line
624			buttonSuccess = EV_Teleport(Arg1, Arg2, Arg3, Arg4, Arg5, A, Line,
625				true);
626			A.SetState(A.FindState('Pain'));
627		}
628		break;
629	case LNSPEC_CeilingLowerByValue:
630		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
631			CeilingMover::CEILEV_LowerByValue, Line);
632		break;
633	case LNSPEC_CeilingRaiseByValue:
634		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
635			CeilingMover::CEILEV_RaiseByValue, Line);
636		break;
637	case LNSPEC_CeilingCrushAndRaise:
638		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
639			CeilingMover::CEILEV_CrushAndRaise, Line);
640		break;
641	case LNSPEC_CeilingLowerAndCrush:
642		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
643			CeilingMover::CEILEV_LowerAndCrush, Line);
644		break;
645	case LNSPEC_CeilingCrushStop:
646		buttonSuccess = EV_CeilingCrushStop(Line, Arg1, Arg2, Arg3, Arg4,
647			Arg5);
648		break;
649	case LNSPEC_CeilingCrushRaiseAndStay:
650		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
651			CeilingMover::CEILEV_CrushRaiseAndStay, Line);
652		break;
653	case LNSPEC_FloorCrushStop:
654		buttonSuccess = EV_FloorCrushStop(Arg1, Arg2, Arg3, Arg4, Arg5);
655		break;
656	case LNSPEC_CeilingMoveToValue:
657		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
658			CeilingMover::CEILEV_MoveToValue, Line);
659		break;
660	case LNSPEC_GlassBreak:
661		buttonSuccess = EV_GlassBreak(Arg1, Arg2, Arg3, Arg4, Arg5, Line, A);
662		break;
663	case LNSPEC_ScrollWall:
664		buttonSuccess = EV_ScrollWall(Arg1, Arg2, Arg3, Arg4, Arg5);
665		break;
666	case LNSPEC_LineSetTextureOffset:
667		buttonSuccess = EV_LineSetTextureOffset(Arg1, Arg2, Arg3, Arg4, Arg5);
668		break;
669	case LNSPEC_SectorChangeFlags:
670		buttonSuccess = EV_SectorChangeFlags(Arg1, Arg2, Arg3, Arg4, Arg5);
671		break;
672	case LNSPEC_PlatPerpetualRaise:
673		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
674			Platform::PLATEV_PerpetualRaise, Line);
675		break;
676	case LNSPEC_PlatStop:
677		buttonSuccess = EV_StopPlat(Arg1, Arg2, Arg3, Arg4, Arg5);
678		break;
679	case LNSPEC_PlatDownWaitUpStay:
680		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
681			Platform::PLATEV_DownWaitUpStay, Line);
682		break;
683	case LNSPEC_PlatDownByValueWaitUpStay:
684		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
685			Platform::PLATEV_DownByValueWaitUpStay, Line);
686		break;
687	case LNSPEC_PlatUpWaitDownStay:
688		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
689			Platform::PLATEV_UpWaitDownStay, Line);
690		break;
691	case LNSPEC_PlatUpByValueWaitDownStay:
692		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
693			Platform::PLATEV_UpByValueWaitDownStay, Line);
694		break;
695	case LNSPEC_FloorLowerTimes8Instant:
696		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
697			FloorMover::FLOOREV_LowerTimes8Instant, Line);
698		break;
699	case LNSPEC_FloorRaiseTimes8Instant:
700		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
701			FloorMover::FLOOREV_RaiseTimes8Instant, Line);
702		break;
703	case LNSPEC_FloorMoveToValueTimes8:
704		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
705			FloorMover::FLOOREV_MoveToValueTimes8, Line);
706		break;
707	case LNSPEC_CeilingMoveToValueTimes8:
708		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
709			CeilingMover::CEILEV_MoveToValueTimes8, Line);
710		break;
711	case LNSPEC_Teleport:
712		if (Side == 0)
713		{
714			// Only teleport when crossing the front side of a line
715			buttonSuccess = EV_Teleport(Arg1, Arg2, Arg3, Arg4, Arg5, A, Line,
716				true);
717		}
718		break;
719	case LNSPEC_TeleportNoFog:
720		if (Side == 0)
721		{
722			// Only teleport when crossing the front side of a line
723			buttonSuccess = EV_Teleport(Arg1, Arg2, Arg3, Arg4, Arg5, A, Line,
724				false);
725		}
726		break;
727	case LNSPEC_ThrustThing:
728		if (A && !Side) // Only thrust on side 0
729		{
730			A.Thrust(itof(Arg1) * (90.0 / 64.0), itof(Arg2));
731			buttonSuccess = 1;
732		}
733		break;
734	case LNSPEC_DamageThing:
735		if (A)
736		{
737			if (Arg1)
738			{
739				A.Damage(none, none, Arg1, ModToDamageType(Arg2));
740			}
741			else
742			{
743				// If arg1 is zero, then guarantee a kill
744				A.Damage(none, none, 10000, ModToDamageType(Arg2));
745			}
746		}
747		buttonSuccess = 1;
748		break;
749	case LNSPEC_TeleportNewMap:
750		if (Side == 0 || bTeleportNewMapBothSides)
751		{
752			// Only teleport when crossing the front side of a line
753			// Players must be alive to teleport
754			if (!(A && A.bIsPlayer && A.Player.PlayerState == PST_DEAD))
755			{
756				Completed(Arg1, Arg2, Arg3);
757				buttonSuccess = true;
758			}
759		}
760		break;
761	case LNSPEC_TeleportEndGame:
762		if (Side == 0)
763		{
764			// Only teleport when crossing the front side of a line
765			// Players must be alive to teleport
766			if (!(A && A.bIsPlayer && A.Player.PlayerState == PST_DEAD))
767			{
768				buttonSuccess = true;
769				if (Game.deathmatch)
770				{
771					// Winning in deathmatch just goes back to map 1
772					Completed(1, 0, 0);
773				}
774				else
775				{
776					// Passing -1, -1 to G_Completed() starts the Finale
777					Completed(-1, -1, 0);
778				}
779			}
780		}
781		break;
782	case LNSPEC_TeleportOther:
783		buttonSuccess = EV_TeleportOther(Arg1, Arg2, Arg3, Arg4, Arg5);
784		break;
785	case LNSPEC_TeleportGroup:
786		buttonSuccess = EV_TeleportGroup(Arg1, Arg2, Arg3, Arg4, Arg5, A);
787		break;
788	case LNSPEC_TeleportSector:
789		buttonSuccess = EV_TeleportSector(Arg1, Arg2, Arg3, Arg4, Arg5);
790		break;
791	case LNSPEC_ACSExecute:
792		buttonSuccess = XLevel.StartACS(Arg1, Arg2, Arg3, Arg4, Arg5, A, Line,
793			Side, false, false);
794		break;
795	case LNSPEC_ACSSuspend:
796		buttonSuccess = XLevel.SuspendACS(Arg1, Arg2);
797		break;
798	case LNSPEC_ACSTerminate:
799		buttonSuccess = XLevel.TerminateACS(Arg1, Arg2);
800		break;
801	case LNSPEC_ACSLockedExecute:
802		if (CheckLock(A, Arg5, false))
803		{
804			buttonSuccess = XLevel.StartACS(Arg1, Arg2, Arg3, Arg4, 0, A,
805				Line, Side, false, false);
806		}
807		break;
808	case LNSPEC_ACSExecuteWithResult:
809		buttonSuccess = XLevel.StartACS(Arg1, 0, Arg2, Arg3, Arg4, A, Line,
810			Side, true, true);
811		break;
812	case LNSPEC_ACSLockedExecuteDoor:
813		if (CheckLock(A, Arg5, true))
814		{
815			buttonSuccess = XLevel.StartACS(Arg1, Arg2, Arg3, Arg4, 0, A,
816				Line, Side, false, false);
817		}
818		break;
819	case LNSPEC_PolyRotateLeftOverride:
820		buttonSuccess = EV_RotatePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, 1,
821			true);
822		break;
823	case LNSPEC_PolyRotateRightOverride:
824		buttonSuccess = EV_RotatePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5, -1,
825			true);
826		break;
827	case LNSPEC_PolyMoveOverride:
828		buttonSuccess = EV_MovePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5,
829			false, true);
830		break;
831	case LNSPEC_PolyMoveTimes8Override:
832		buttonSuccess = EV_MovePoly(Line, Arg1, Arg2, Arg3, Arg4, Arg5,
833			true, true);
834		break;
835	case LNSPEC_PillarBuildCrush:
836		buttonSuccess = EV_BuildPillar(Arg1, Arg2, Arg3, Arg4, Arg5, true);
837		break;
838	case LNSPEC_FloorAndCeilingLowerByValue:
839		buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5,
840			Elevator::ELEVEV_Lower, Line);
841		break;
842	case LNSPEC_FloorAndCeilingRaiseByValue:
843		buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5,
844			Elevator::ELEVEV_Raise, Line);
845		break;
846	case LNSPEC_LightForceLightning:
847		buttonSuccess = true;
848		ForceLightning(Arg1);
849		break;
850	case LNSPEC_LightRaiseByValue:
851		buttonSuccess = EV_LightRaiseByValue(Arg1, Arg2, Arg3, Arg4, Arg5);
852		break;
853	case LNSPEC_LightLowerByValue:
854		buttonSuccess = EV_LightLowerByValue(Arg1, Arg2, Arg3, Arg4, Arg5);
855		break;
856	case LNSPEC_LightChangeToValue:
857		buttonSuccess = EV_LightChangeToValue(Arg1, Arg2, Arg3, Arg4, Arg5);
858		break;
859	case LNSPEC_LightFade:
860		buttonSuccess = EV_SpawnLight(Arg1, Arg2, Arg3, Arg4, Arg5,
861			LightEffect::LIGHTEV_Fade);
862		break;
863	case LNSPEC_LightGlow:
864		buttonSuccess = EV_SpawnLight(Arg1, Arg2, Arg3, Arg4, Arg5,
865			LightEffect::LIGHTEV_Glow);
866		break;
867	case LNSPEC_LightFlicker:
868		buttonSuccess = EV_SpawnLight(Arg1, Arg2, Arg3, Arg4, Arg5,
869			LightEffect::LIGHTEV_Flicker);
870		break;
871	case LNSPEC_LightStrobe:
872		buttonSuccess = EV_SpawnLight(Arg1, Arg2, Arg3, Arg4, Arg5,
873			LightEffect::LIGHTEV_Strobe);
874		break;
875	case LNSPEC_LightStop:
876		buttonSuccess = EV_LightStop(Arg1);
877		break;
878	case LNSPEC_ThingDamage:
879		buttonSuccess = EV_ThingDamage(Arg1, Arg2, Arg3, Arg4, Arg5, A);
880		break;
881	case LNSPEC_QuakeTremor:
882		buttonSuccess = A_LocalQuake(Arg1, Arg2, Arg3, Arg4, Arg5);
883		break;
884	case LNSPEC_ThingMove:
885		buttonSuccess = EV_ThingMove(Arg1, Arg2, Arg3, Arg4, Arg5, A);
886		break;
887	case LNSPEC_ThingSetSpecial:
888		buttonSuccess = EV_ThingSetSpecial(Arg1, Arg2, Arg3, Arg4, Arg5, A);
889		break;
890	case LNSPEC_ThrustThingZ:
891		buttonSuccess = EV_ThrustThingZ(Arg1, Arg2, Arg3, Arg4, Arg5, A);
892		break;
893	case LNSPEC_UsePuzzleItem:
894		buttonSuccess = EV_LineSearchForPuzzleItem(Arg1, Arg2, Arg3, Arg4,
895			Arg5, A);
896		break;
897	case LNSPEC_ThingActivate:
898		buttonSuccess = EV_ThingActivate(Arg1, A);
899		break;
900	case LNSPEC_ThingDeactivate:
901		buttonSuccess = EV_ThingDeactivate(Arg1, A);
902		break;
903	case LNSPEC_ThingRemove:
904		buttonSuccess = EV_ThingRemove(Arg1);
905		break;
906	case LNSPEC_ThingDestroy:
907		buttonSuccess = EV_ThingDestroy(Arg1, Arg2);
908		break;
909	case LNSPEC_ThingProjectile:
910		buttonSuccess = EV_ThingProjectile(Arg1, Arg2, Arg3, Arg4, Arg5, 0,
911			0, '', A);
912		break;
913	case LNSPEC_ThingSpawn:
914		buttonSuccess = EV_ThingSpawn(Arg1, Arg2, Arg3, Arg4, Arg5, true,
915			false, A);
916		break;
917	case LNSPEC_ThingProjectileGravity:
918		buttonSuccess = EV_ThingProjectile(Arg1, Arg2, Arg3, Arg4, Arg5, 1,
919			0, '', A);
920		break;
921	case LNSPEC_ThingSpawnNoFog:
922		buttonSuccess = EV_ThingSpawn(Arg1, Arg2, Arg3, Arg4, Arg5, false,
923			false, A);
924		break;
925	case LNSPEC_FloorWaggle:
926		buttonSuccess = EV_StartFloorWaggle(Arg1, Arg2, Arg3, Arg4, Arg5);
927		break;
928	case LNSPEC_ThingSpawnFacing:
929		buttonSuccess = EV_ThingSpawn(Arg1, Arg2, 0, Arg4, Arg5, !Arg3,
930			true, A);
931		break;
932	case LNSPEC_SectorSoundChange:
933		buttonSuccess = EV_SectorSoundChange(Arg1, Arg2, Arg3, Arg4, Arg5);
934		break;
935	case LNSPEC_SectorSetPlaneReflection:
936		buttonSuccess = EV_SectorSetPlaneReflection(Arg1, Arg2, Arg3, Arg4,
937			Arg5);
938		break;
939	case LNSPEC_CeilingGenericCrush2:
940		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
941			CeilingMover::CEILEV_GenericCrush2, Line);
942		break;
943	case LNSPEC_SectorSetCeilingScale2:
944		buttonSuccess = EV_SectorSetCeilingScale2(Arg1, Arg2, Arg3, Arg4,
945			Arg5);
946		break;
947	case LNSPEC_SectorSetFloorScale2:
948		buttonSuccess = EV_SectorSetFloorScale2(Arg1, Arg2, Arg3, Arg4,
949			Arg5);
950		break;
951	case LNSPEC_PlaneUpNearestWaitDownStay:
952		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
953			Platform::PLATEV_UpNearestWaitDownStay, Line);
954		break;
955	case LNSPEC_NoiseAlert:
956		buttonSuccess = EV_NoiseAlert(A, Arg1, Arg2, Arg3, Arg4, Arg5);
957		break;
958	case LNSPEC_SendToCommunicator:
959		buttonSuccess = EV_SendToCommunicator(A, Arg1, Arg2, Arg3, Arg4,
960			Arg5, Side);
961		break;
962	case LNSPEC_ThingProjectileIntercept:
963		buttonSuccess = EV_ThingProjectileIntercept(Arg1, Arg2, Arg3, Arg4,
964			Arg5, A);
965		break;
966	case LNSPEC_ThingChangeTID:
967		buttonSuccess = EV_ThingChangeTID(Arg1, Arg2, Arg3, Arg4, Arg5, A);
968		break;
969	case LNSPEC_ThingHate:
970		buttonSuccess = EV_ThingHate(Arg1, Arg2, Arg3, Arg4, Arg5, A);
971		break;
972	case LNSPEC_ThingProjectileAimed:
973		buttonSuccess = EV_ThingProjectileAimed(Arg1, Arg2, Arg3, Arg4, Arg5,
974			A);
975		break;
976	case LNSPEC_ChangeSkill:
977		LineSpecialGameInfo(Game).SetSkill(Arg1);
978		buttonSuccess = true;
979		break;
980	case LNSPEC_ThingSetTranslation:
981		buttonSuccess = EV_ThingSetTranslation(Arg1, Arg2, Arg3, Arg4, Arg5,
982			A);
983		break;
984	case LNSPEC_LineAlignCeiling:
985		buttonSuccess = EV_LineAlignCeiling(Arg1, Arg2, Arg3, Arg4, Arg5);
986		break;
987	case LNSPEC_LineAlignFloor:
988		buttonSuccess = EV_LineAlignFloor(Arg1, Arg2, Arg3, Arg4, Arg5);
989		break;
990	case LNSPEC_SectorSetRotation:
991		buttonSuccess = EV_SectorSetRotation(Arg1, Arg2, Arg3, Arg4, Arg5);
992		break;
993	case LNSPEC_SectorSetCeilingPanning:
994		buttonSuccess = EV_SectorSetCeilingPanning(Arg1, Arg2, Arg3, Arg4,
995			Arg5);
996		break;
997	case LNSPEC_SectorSetFloorPanning:
998		buttonSuccess = EV_SectorSetFloorPanning(Arg1, Arg2, Arg3, Arg4,
999			Arg5);
1000		break;
1001	case LNSPEC_SectorSetCeilingScale:
1002		buttonSuccess = EV_SectorSetCeilingScale(Arg1, Arg2, Arg3, Arg4,
1003			Arg5);
1004		break;
1005	case LNSPEC_SectorSetFloorScale:
1006		buttonSuccess = EV_SectorSetFloorScale(Arg1, Arg2, Arg3, Arg4,
1007			Arg5);
1008		break;
1009	case LNSPEC_SetPlayerProperty:
1010		buttonSuccess = EV_SetPlayerProperty(Arg1, Arg2, Arg3, Arg4, Arg5, A);
1011		break;
1012	case LNSPEC_CeilingLowerToHighestFloor:
1013		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1014			CeilingMover::CEILEV_LowerToHighestFloor, Line);
1015		break;
1016	case LNSPEC_CeilingLowerInstant:
1017		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1018			CeilingMover::CEILEV_LowerTimes8Instant, Line);
1019		break;
1020	case LNSPEC_CeilingRaiseInstant:
1021		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1022			CeilingMover::CEILEV_RaiseTimes8Instant, Line);
1023		break;
1024	case LNSPEC_CeilingCrushRaiseAndStayA:
1025		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1026			CeilingMover::CEILEV_CrushRaiseAndStayA, Line);
1027		break;
1028	case LNSPEC_CeilingCrushAndRaiseA:
1029		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1030			CeilingMover::CEILEV_CrushAndRaiseA, Line);
1031		break;
1032	case LNSPEC_CeilingCrushAndRaiseSilentA:
1033		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1034			CeilingMover::CEILEV_CrushAndRaiseSilA, Line);
1035		break;
1036	case LNSPEC_CeilingRaiseByValueTimes8:
1037		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1038			CeilingMover::CEILEV_RaiseByValueTimes8, Line);
1039		break;
1040	case LNSPEC_CeilingLowerByValueTimes8:
1041		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1042			CeilingMover::CEILEV_LowerByValueTimes8, Line);
1043		break;
1044	case LNSPEC_FloorGeneric:
1045		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
1046			FloorMover::FLOOREV_Generic, Line);
1047		break;
1048	case LNSPEC_CeilingGeneric:
1049		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1050			CeilingMover::CEILEV_Generic, Line);
1051		break;
1052	case LNSPEC_DoorGeneric:
1053		buttonSuccess = EV_GenericDoor(Arg1, Arg2, Arg3, Arg4, Arg5, Line, A);
1054		break;
1055	case LNSPEC_PlatGeneric:
1056		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
1057			Platform::PLATEV_Generic, Line);
1058		break;
1059	case LNSPEC_StairsGeneric:
1060		buttonSuccess = EV_BuildStairsOld(Arg1, Arg2, Arg3, Arg4, Arg5,
1061			true, Line);
1062		break;
1063	case LNSPEC_CeilingGenericCrush:
1064		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1065			CeilingMover::CEILEV_GenericCrush, Line);
1066		break;
1067	case LNSPEC_PlatDownWaitUpStayLip:
1068		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
1069			Platform::PLATEV_DownWaitUpStayLip, Line);
1070		break;
1071	case LNSPEC_PlatPerpetualRaiseLip:
1072		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
1073			Platform::PLATEV_PerpetualRaiseLip, Line);
1074		break;
1075	case LNSPEC_LineTranslucent:
1076		buttonSuccess = EV_LineTranslucent(Arg1, Arg2, Arg3, Arg4, Arg5);
1077		break;
1078	case LNSPEC_SectorSetColour:
1079		buttonSuccess = EV_SectorSetColour(Arg1, Arg2, Arg3, Arg4, Arg5);
1080		break;
1081	case LNSPEC_SectorSetFade:
1082		buttonSuccess = EV_SectorSetFade(Arg1, Arg2, Arg3, Arg4, Arg5);
1083		break;
1084	case LNSPEC_SectorSetDamage:
1085		buttonSuccess = EV_SectorSetDamage(Arg1, Arg2, Arg3, Arg4, Arg5);
1086		break;
1087	case LNSPEC_TeleportLine:
1088		buttonSuccess = EV_SilentLineTeleport(Line, Side, A, Arg2, Arg3);
1089		break;
1090	case LNSPEC_SectorSetGravity:
1091		buttonSuccess = EV_SectorSetGravity(Arg1, Arg2, Arg3, Arg4, Arg5);
1092		break;
1093	case LNSPEC_StairsBuildUpDoom:
1094		buttonSuccess = EV_BuildStairsOld(Arg1, Arg2, Arg3, Arg4, Arg5,
1095			false, Line);
1096		break;
1097	case LNSPEC_SectorSetWind:
1098		buttonSuccess = AdjustPusher(Arg1, Arg2, Arg3, Arg4, Arg5, Line,
1099			Pusher::PUSHER_Wind);
1100		break;
1101	case LNSPEC_SectorSetFriction:
1102		buttonSuccess = EV_SectorSetFriction(Arg1, Arg2, Arg3, Arg4, Arg5);
1103		break;
1104	case LNSPEC_SectorSetCurrent:
1105		buttonSuccess = AdjustPusher(Arg1, Arg2, Arg3, Arg4, Arg5, Line,
1106			Pusher::PUSHER_Current);
1107		break;
1108	case LNSPEC_ScrollTextureBoth:
1109		buttonSuccess = EV_ScrollTextureBoth(Arg1, Arg2, Arg3, Arg4, Arg5);
1110		break;
1111	case LNSPEC_ScrollFloor:
1112		buttonSuccess = EV_ScrollFloor(Arg1, Arg2, Arg3, Arg4, Arg5);
1113		break;
1114	case LNSPEC_ScrollCeiling:
1115		buttonSuccess = EV_ScrollCeiling(Arg1, Arg2, Arg3, Arg4, Arg5);
1116		break;
1117	case LNSPEC_ACSExecuteAlways:
1118		buttonSuccess = XLevel.StartACS(Arg1, Arg2, Arg3, Arg4, Arg5, A,
1119			Line, Side, true, false);
1120		break;
1121	case LNSPEC_FloorRaiseToNearestChange:
1122		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
1123			FloorMover::FLOOREV_RaiseToNearestChange, Line);
1124		break;
1125	case LNSPEC_ThingSetGoal:
1126		buttonSuccess = EV_ThingSetGoal(Arg1, Arg2, Arg3, Arg4, Arg5);
1127		break;
1128	case LNSPEC_FloorRaiseByValueChangeTex:
1129		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
1130			FloorMover::FLOOREV_RaiseByValueChange2, Line);
1131		break;
1132	case LNSPEC_PlatToggle:
1133		buttonSuccess = EV_DoPlat(Arg1, Arg2, Arg3, Arg4, Arg5,
1134			Platform::PLATEV_Toggle, Line);
1135		break;
1136	case LNSPEC_LightStrobeDoom:
1137		buttonSuccess = EV_StartLightStrobing(Arg1, Arg2, Arg3, Arg4, Arg5);
1138		break;
1139	case LNSPEC_LightMinNeighbor:
1140		buttonSuccess = EV_TurnTagLightsOff(Arg1, Arg2, Arg3, Arg4, Arg5);
1141		break;
1142	case LNSPEC_LightMaxNeighbor:
1143		buttonSuccess = EV_TagLightTurnOn(Arg1, Arg2, Arg3, Arg4, Arg5);
1144		break;
1145	case LNSPEC_FloorTransferTrigger:
1146		buttonSuccess = EV_FloorTransferTrigger(Arg1, Arg2, Arg3, Arg4, Arg5,
1147			Line);
1148		break;
1149	case LNSPEC_FloorTransferNumeric:
1150		buttonSuccess = EV_FloorTransferNumeric(Arg1, Arg2, Arg3, Arg4, Arg5);
1151		break;
1152	case LNSPEC_ChangeCamera:
1153		buttonSuccess = EV_ChangeCamera(Arg1, Arg2, Arg3, Arg4, Arg5, A);
1154		break;
1155	case LNSPEC_FloorRaiseToLowestCeiling:
1156		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
1157			FloorMover::FLOOREV_RaiseToLowestCeiling, Line);
1158		break;
1159	case LNSPEC_FloorRaiseByValueChange:
1160		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
1161			FloorMover::FLOOREV_RaiseByValueChange, Line);
1162		break;
1163	case LNSPEC_FloorRaiseByTexture:
1164		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
1165			FloorMover::FLOOREV_RaiseByTexture, Line);
1166		break;
1167	case LNSPEC_FloorLowerToLowestChange:
1168		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
1169			FloorMover::FLOOREV_LowerToLowestChange, Line);
1170		break;
1171	case LNSPEC_FloorLowerToHighest:
1172		buttonSuccess = EV_DoFloor(Arg1, Arg2, Arg3, Arg4, Arg5,
1173			FloorMover::FLOOREV_LowerToHighest, Line);
1174		break;
1175	case LNSPEC_ExitNormal:
1176		buttonSuccess = true;
1177		ExitLevel(Arg1);
1178		break;
1179	case LNSPEC_ExitSecret:
1180		buttonSuccess = true;
1181		SecretExitLevel(Arg1);
1182		break;
1183	case LNSPEC_ElevatorRaiseToNearest:
1184		buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5,
1185			Elevator::ELEVEV_Up, Line);
1186		break;
1187	case LNSPEC_ElevatorMoveToFloor:
1188		buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5,
1189			Elevator::ELEVEV_Current, Line);
1190		break;
1191	case LNSPEC_ElevatorLowerToNearest:
1192		buttonSuccess = EV_DoElevator(Arg1, Arg2, Arg3, Arg4, Arg5,
1193			Elevator::ELEVEV_Down, Line);
1194		break;
1195	case LNSPEC_HealThing:
1196		buttonSuccess = EV_HealThing(Arg1, Arg2, Arg3, Arg4, Arg5, A);
1197		break;
1198	case LNSPEC_DoorCloseWaitOpen:
1199		buttonSuccess = EV_DoDoor(Arg1, Arg2, Arg3, Arg4, Arg5,
1200			VerticalDoor::DOOREV_CloseWaitOpen, Line, A);
1201		break;
1202	case LNSPEC_FloorDonut:
1203		buttonSuccess = EV_DoDonut(Arg1, Arg2, Arg3, Arg4, Arg5);
1204		break;
1205	case LNSPEC_FloorAndCeilingLowerRaise:
1206		buttonSuccess = EV_DoCeiling(Arg1, Arg3, 0, 0, 0,
1207			CeilingMover::CEILEV_RaiseToHighest, Line);
1208		buttonSuccess |= EV_DoFloor(Arg1, Arg2, 0, 0, 0,
1209			FloorMover::FLOOREV_LowerToLowest, Line);
1210		break;
1211	case LNSPEC_CeilingRaiseToNearest:
1212		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1213			CeilingMover::CEILEV_RaiseToNearest, Line);
1214		break;
1215	case LNSPEC_CeilingLowerToLowest:
1216		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1217			CeilingMover::CEILEV_LowerToLowest, Line);
1218		break;
1219	case LNSPEC_CeilingLowerToFloor:
1220		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1221			CeilingMover::CEILEV_LowerToFloor, Line);
1222		break;
1223	case LNSPEC_CeilingCrushRaiseAndStaySilentA:
1224		buttonSuccess = EV_DoCeiling(Arg1, Arg2, Arg3, Arg4, Arg5,
1225			CeilingMover::CEILEV_CrushRaiseAndStaySilA, Line);
1226		break;
1227
1228	// Line specials only processed during level initialization
1229	// LNSPEC_ScrollTextureLeft:
1230	// LNSPEC_ScrollTextureRight:
1231	// LNSPEC_ScrollTextureUp:
1232	// LNSPEC_ScrollTextureDown:
1233	// LNSPEC_LineSetIdentification:
1234	// LNSPEC_3DFloor:
1235	// LNSPEC_Contents:
1236	// LNSPEC_PlaneAlign:
1237	// LNSPEC_TransferHeights:
1238	// LNSPEC_ScrollTextureModel:
1239	// LNSPEC_ScrollTextureOffsets:
1240	// LNSPEC_PointPushSetForce:
1241
1242	default:
1243		//	Log everything else to know what needs to be implemented.
1244		print("Unknown action special %d(%d, %d, %d, %d, %d)", Special,
1245			Arg1, Arg2, Arg3, Arg4, Arg5);
1246		break;
1247	}
1248	return buttonSuccess;
1249}
1250
1251//==========================================================================
1252//
1253//  StartPlaneWatcher
1254//
1255//==========================================================================
1256
1257final void StartPlaneWatcher(Entity it, line_t* line, int lineSide,
1258	bool ceiling, int tag, int height, int special, int arg0, int arg1,
1259	int arg2, int arg3, int arg4)
1260{
1261	PlaneWatcher	PW;
1262
1263	PW = Spawn(PlaneWatcher);
1264	PW.Start(it, line, lineSide, ceiling, tag, height, special, arg0, arg1,
1265		arg2, arg3, arg4);
1266}
1267
1268//**************************************************************************
1269//
1270//  Doors
1271//
1272//**************************************************************************
1273
1274//==========================================================================
1275//
1276//  EV_DoDoor
1277//
1278//  Move a door up/down
1279//
1280//==========================================================================
1281
1282final int EV_DoDoor(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
1283	int Type, line_t* Line, Entity Thing)
1284{
1285	int				SecNum;
1286	int				RetCode;
1287	sector_t*		Sec;
1288	VerticalDoor	Door;
1289
1290	RetCode = false;
1291	if (!Arg1)
1292	{
1293		if (!Line)
1294			return false;
1295
1296		//	Make sure it's a two-sided line.
1297		if (Line->sidenum[1] < 0)
1298			return false;
1299
1300		// if the sector has an active thinker, use it
1301		Sec = XLevel.Sides[Line->sidenum[1]].Sector;
1302		if (Sec->CeilingData)
1303		{
1304			Door = VerticalDoor(Sec->CeilingData);
1305			if (Door)
1306			{
1307				return Door.ReUse(Type, Line, Thing);
1308			}
1309			return false;
1310		}
1311
1312		// new door thinker
1313		Door = Spawn(VerticalDoor);
1314		Door.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type);
1315		RetCode = true;
1316	}
1317	else
1318	{
1319		for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
1320			SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
1321		{
1322			Sec = &XLevel.Sectors[SecNum];
1323			if (Sec->CeilingData)
1324			{
1325				continue;
1326			}
1327			// Add new door thinker
1328			RetCode = true;
1329			Door = Spawn(VerticalDoor);
1330			Door.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type);
1331		}
1332	}
1333	return RetCode;
1334}
1335
1336//==========================================================================
1337//
1338//  EV_GenericDoor
1339//
1340//	Boom's generic doors.
1341//
1342//==========================================================================
1343
1344final int EV_GenericDoor(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
1345	line_t* Line, Entity Thing)
1346{
1347	int			Tag;
1348	int			LightTag;
1349
1350	//	Check for locked door.
1351	if (Arg5 && !CheckLock(Thing, Arg5, true))
1352	{
1353		return false;
1354	}
1355
1356	//	Check for Boom's local door light special.
1357	if (Arg3 & 128)
1358	{
1359		Tag = 0;
1360		LightTag = Arg1;
1361	}
1362	else
1363	{
1364		Tag = Arg1;
1365		LightTag = 0;
1366	}
1367
1368	switch (Arg3 & 127)
1369	{
1370	case 0:
1371		return EV_DoDoor(Tag, Arg2, Arg4, LightTag, 0,
1372			VerticalDoor::DOOREV_Raise, Line, Thing);
1373	case 1:
1374		return EV_DoDoor(Tag, Arg2, LightTag, 0, 0,
1375			VerticalDoor::DOOREV_Open, Line, Thing);
1376	case 2:
1377		return EV_DoDoor(Tag, Arg2, Arg4, LightTag, 0,
1378			VerticalDoor::DOOREV_CloseWaitOpen, Line, Thing);
1379	case 3:
1380		return EV_DoDoor(Tag, Arg2, LightTag, 0, 0,
1381			VerticalDoor::DOOREV_Close, Line, Thing);
1382	}
1383	return false;
1384}
1385
1386//==========================================================================
1387//
1388//  SpawnDoorCloseIn30
1389//
1390//  Spawn a door that closes after 30 seconds
1391//
1392//==========================================================================
1393
1394final void SpawnDoorCloseIn30(sector_t* sec)
1395{
1396	VerticalDoor	Door;
1397
1398	Door = Spawn(VerticalDoor);
1399	Door.InitCloseIn30(sec);
1400	sec->special = 0;
1401}
1402
1403//==========================================================================
1404//
1405//  SpawnDoorRaiseIn5Mins
1406//
1407//  Spawn a door that opens after 5 minutes
1408//
1409//==========================================================================
1410
1411final void SpawnDoorRaiseIn5Mins(sector_t* sec)
1412{
1413	VerticalDoor Door;
1414
1415	sec->special = 0;
1416	Door = Spawn(VerticalDoor);
1417	Door.Init(sec, 0, 16, 150, 0, 0, VerticalDoor::DOOREV_RaiseIn5Mins);
1418}
1419
1420//==========================================================================
1421//
1422//  EV_TextureChangeDoor
1423//
1424//==========================================================================
1425
1426final int EV_TextureChangeDoor(int Arg1, int Arg2, int Arg3, int Arg4,
1427	int Arg5, line_t* Line, Entity E)
1428{
1429	int					i;
1430	int					SecNum;
1431	int					Rtn;
1432	sector_t*			Sec;
1433	TextureChangeDoor	Door;
1434
1435	Rtn = false;
1436
1437	if (!Arg1)
1438	{
1439		if (!Line || !Line->backsector)
1440		{
1441			return false;
1442		}
1443
1444		// if the sector has an active thinker, use it
1445		if (Line->backsector->CeilingData)
1446		{
1447			if (!E.bIsPlayer)
1448				return false;
1449			Door = TextureChangeDoor(Line->backsector->CeilingData);
1450			if (Door && Door.Direction == 0)
1451			{
1452				return Door.StartClosing();
1453			}
1454			return false;
1455		}
1456
1457		// new door thinker
1458		if (FindAnimDoor(XLevel.Sides[Line->sidenum[0]].TopTexture))
1459		{
1460			Door = Spawn(TextureChangeDoor);
1461			Door.Init(Line->backsector, Arg1, Arg2, Arg3, Arg4, Arg5, Line);
1462			Rtn = true;
1463		}
1464	}
1465	else
1466	{
1467		for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
1468			SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
1469		{
1470			Sec = &XLevel.Sectors[SecNum];
1471			if (Sec->CeilingData)
1472			{
1473				continue;
1474			}
1475			for (i = 0; i < Sec->linecount; i++)
1476			{
1477				Line = Sec->lines[i];
1478				if (!Line->backsector)
1479				{
1480					continue;
1481				}
1482				//	New door thinker
1483				if (FindAnimDoor(XLevel.Sides[Line->sidenum[0]].TopTexture))
1484				{
1485					Rtn = true;
1486					Door = Spawn(TextureChangeDoor);
1487					Door.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Line);
1488					break;
1489				}
1490			}
1491		}
1492	}
1493	return Rtn;
1494}
1495
1496//**************************************************************************
1497//
1498//	Ceilings
1499//
1500//**************************************************************************
1501
1502//==========================================================================
1503//
1504//  EV_DoCeiling
1505//
1506//  Move a ceiling up/down and all around!
1507//
1508//==========================================================================
1509
1510final int EV_DoCeiling(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
1511	int Type, line_t* Line)
1512{
1513	int				SecNum;
1514	int				Rtn;
1515	sector_t*		Sec;
1516	CeilingMover	Ceiling;
1517
1518	Rtn = false;
1519
1520	if (!Arg1)
1521	{
1522		if (!Line || !Line->backsector)
1523			return false;
1524
1525		//  Reactivate in-stasis ceilings...for certain types.
1526		if ((Type == CeilingMover::CEILEV_CrushAndRaiseA ||
1527			Type == CeilingMover::CEILEV_CrushAndRaiseSilA) &&
1528			CeilingMover(Line->backsector->CeilingData))
1529		{
1530			CeilingMover(Line->backsector->CeilingData).ActivateInStasis(0);
1531		}
1532
1533		if (!Line->backsector->CeilingData)
1534		{
1535			// new ceiling thinker
1536			Rtn = true;
1537			Ceiling = Spawn(CeilingMover);
1538			Ceiling.Init(Line->backsector, Arg1, Arg2, Arg3, Arg4, Arg5,
1539				Type, Line);
1540		}
1541	}
1542	else
1543	{
1544		//  Reactivate in-stasis ceilings...for certain types.
1545		if (Type == CeilingMover::CEILEV_CrushAndRaiseA ||
1546			Type == CeilingMover::CEILEV_CrushAndRaiseSilA)
1547		{
1548			foreach AllThinkers(CeilingMover, Ceiling)
1549			{
1550				Ceiling.ActivateInStasis(Arg1);
1551			}
1552		}
1553
1554		for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
1555			SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
1556		{
1557			Sec = &XLevel.Sectors[SecNum];
1558			if (Sec->CeilingData)
1559				continue;
1560
1561			// new ceiling thinker
1562			Rtn = true;
1563			Ceiling = Spawn(CeilingMover);
1564			Ceiling.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type, Line);
1565		}
1566	}
1567	return Rtn;
1568}
1569
1570//==========================================================================
1571//
1572//  EV_CeilingCrushStop
1573//
1574//  Stop a ceiling from crushing!
1575//
1576//==========================================================================
1577
1578final int EV_CeilingCrushStop(line_t* line, int Arg1, int Arg2, int Arg3,
1579	int Arg4, int Arg5)
1580{
1581	int				rtn;
1582	CeilingMover	Ceiling;
1583
1584	rtn = false;
1585	foreach AllThinkers(CeilingMover, Ceiling)
1586	{
1587		if (Ceiling.CrushStop(Arg1))
1588		{
1589			rtn = true;
1590		}
1591	}
1592	return rtn;
1593}
1594
1595//==========================================================================
1596//
1597//	EV_StartCeilingWaggle
1598//
1599//==========================================================================
1600
1601final bool EV_StartCeilingWaggle(int Arg1, int Arg2, int Arg3, int Arg4,
1602	int Arg5)
1603{
1604	int				SectorIndex;
1605	sector_t*		Sector;
1606	CeilingWaggle	Waggle;
1607	bool			RetCode;
1608
1609	RetCode = false;
1610	for (SectorIndex = XLevel.FindSectorFromTag(Arg1, -1); SectorIndex >= 0;
1611		SectorIndex = XLevel.FindSectorFromTag(Arg1, SectorIndex))
1612	{
1613		Sector = &XLevel.Sectors[SectorIndex];
1614		if (Sector->CeilingData)
1615		{
1616			// Already busy with another thinker
1617			continue;
1618		}
1619		RetCode = true;
1620		Waggle = Spawn(CeilingWaggle);
1621		Waggle.Init(Sector, Arg1, Arg2, Arg3, Arg4, Arg5);
1622	}
1623	return RetCode;
1624}
1625
1626//**************************************************************************
1627//
1628//	Floors
1629//
1630//**************************************************************************
1631
1632//==========================================================================
1633//
1634//  EV_DoFloor
1635//
1636//  HANDLE FLOOR TYPES
1637//
1638//==========================================================================
1639
1640final int EV_DoFloor(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
1641	int Type, line_t* Line)
1642{
1643	int			SecNum;
1644	int			Rtn;
1645	sector_t*	Sec;
1646	FloorMover	Floor;
1647
1648	Rtn = false;
1649	if (!Arg1)
1650	{
1651		if (!Line || !Line->backsector)
1652			return false;
1653
1654		if (!Line->backsector->FloorData)
1655		{
1656			// new floor thinker
1657			Rtn = true;
1658			Floor = Spawn(FloorMover);
1659			Floor.Init(Line->backsector, Arg1, Arg2, Arg3, Arg4, Arg5, Type,
1660				Line);
1661		}
1662	}
1663	else
1664	{
1665		for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
1666			SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
1667		{
1668			Sec = &XLevel.Sectors[SecNum];
1669
1670			// ALREADY MOVING?  IF SO, KEEP GOING...
1671			if (Sec->FloorData)
1672				continue;
1673
1674			// new floor thinker
1675			Rtn = true;
1676			Floor = Spawn(FloorMover);
1677			Floor.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type, Line);
1678		}
1679	}
1680	return Rtn;
1681}
1682
1683//==========================================================================
1684//
1685// EV_FloorCrushStop
1686//
1687//==========================================================================
1688
1689final int EV_FloorCrushStop(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
1690{
1691	FloorMover	Floor;
1692	bool		Rtn;
1693
1694	Rtn = false;
1695	foreach AllThinkers(FloorMover, Floor)
1696	{
1697		if (Floor.CrushStop(Arg1))
1698		{
1699			Rtn = true;
1700		}
1701	}
1702	return Rtn;
1703}
1704
1705//==========================================================================
1706//
1707//  EV_DoDonut()
1708//
1709//  Handle donut function: lower pillar, raise surrounding pool, both to
1710// height, texture and type of the sector surrounding the pool.
1711//  Passed the linedef that triggered the donut
1712//  Returns whether a thinker was created
1713//
1714//==========================================================================
1715
1716final int EV_DoDonut(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
1717{
1718	sector_t*	s1;
1719	sector_t*	s2;
1720	sector_t*	s3;
1721	int			secnum;
1722	int			rtn;
1723	int			i;
1724	FloorMover	Floor;
1725
1726	rtn = 0;
1727	// do function on all sectors with same tag as linedef
1728	for (secnum = XLevel.FindSectorFromTag(Arg1, -1); secnum >= 0;
1729		secnum = XLevel.FindSectorFromTag(Arg1, secnum))
1730	{
1731		s1 = &XLevel.Sectors[secnum];	// s1 is pillar's sector
1732
1733		// ALREADY MOVING?  IF SO, KEEP GOING...
1734		if (s1->FloorData)
1735			continue;
1736
1737		s2 = getNextSector(s1->lines[0], s1);	// s2 is pool's sector
1738		rtn = 1;
1739
1740		// find a two sided line around the pool whose other side isn't the pillar
1741		for (i = 0; i < s2->linecount; i++)
1742		{
1743			if ((!s2->lines[i]->flags & ML_TWOSIDED) ||
1744				(s2->lines[i]->backsector == s1))
1745				continue;
1746			s3 = s2->lines[i]->backsector;
1747
1748			//  Spawn rising slime
1749			Floor = Spawn(FloorMover);
1750			Floor.InitDonut(s2, s3, Arg2);
1751
1752			//  Spawn lowering donut-hole
1753			Floor = Spawn(FloorMover);
1754			Floor.InitDonut2(s1, s3, Arg3);
1755			break;
1756		}
1757	}
1758	return rtn;
1759}
1760
1761//==========================================================================
1762//
1763//	EV_StartFloorWaggle
1764//
1765//==========================================================================
1766
1767final bool EV_StartFloorWaggle(int Arg1, int Arg2, int Arg3, int Arg4,
1768	int Arg5)
1769{
1770	int				SectorIndex;
1771	sector_t*		Sector;
1772	FloorWaggle		Waggle;
1773	bool			RetCode;
1774
1775	RetCode = false;
1776	for (SectorIndex = XLevel.FindSectorFromTag(Arg1, -1); SectorIndex >= 0;
1777		SectorIndex = XLevel.FindSectorFromTag(Arg1, SectorIndex))
1778	{
1779		Sector = &XLevel.Sectors[SectorIndex];
1780		if (Sector->FloorData)
1781		{
1782			// Already busy with another thinker
1783			continue;
1784		}
1785		RetCode = true;
1786		Waggle = Spawn(FloorWaggle);
1787		Waggle.Init(Sector, Arg1, Arg2, Arg3, Arg4, Arg5);
1788	}
1789	return RetCode;
1790}
1791
1792//**************************************************************************
1793//
1794//	Stairs
1795//
1796//**************************************************************************
1797
1798//==========================================================================
1799//
1800//	EV_BuildStairsOld
1801//
1802//	Build a staircase!
1803//
1804//==========================================================================
1805
1806final int EV_BuildStairsOld(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
1807	bool Generic, line_t* Line)
1808{
1809	int			SecNum;
1810	float		Height;
1811	int			i;
1812	int			j;
1813	int			Ok;
1814	int			Texture;
1815	int			Rtn;
1816	sector_t*	Sec;
1817	sector_t*	TSec;
1818	FloorMover	Floor;
1819	line_t*		SecLine;
1820	int			Direction;
1821	float		StairSize;
1822	bool		IgnTxt;
1823	int			OldSecNum;
1824
1825	if (!Arg1 && (!Line || !Line->backsector))
1826	{
1827		return false;
1828	}
1829
1830	if (Generic)
1831	{
1832		Direction = Arg4 & 1 ? 1 : -1;
1833		IgnTxt = !!(Arg4 & 2);
1834	}
1835	else
1836	{
1837		Direction = 1;
1838		IgnTxt = false;
1839	}
1840	StairSize = itof(Arg3 * Direction);
1841	Rtn = 0;
1842	for (SecNum = Arg1 ? XLevel.FindSectorFromTag(Arg1, -1) : 1; SecNum >= 0;
1843		SecNum = Arg1 ? XLevel.FindSectorFromTag(Arg1, SecNum) : -1)
1844	{
1845		Sec = Arg1 ? &XLevel.Sectors[SecNum] : Line->backsector;
1846
1847		// ALREADY MOVING?  IF SO, KEEP GOING...
1848		if (Sec->FloorData)
1849		{
1850			continue;
1851		}
1852
1853		// new floor thinker
1854		Rtn = 1;
1855		Height = GetPlanePointZ(&Sec->floor, vector(0.0, 0.0, 0.0)) +
1856			StairSize;
1857		Floor = Spawn(FloorMover);
1858		Floor.InitStair(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Generic, Height);
1859
1860		Texture = Sec->floor.pic;
1861		OldSecNum = SecNum;	//jff 3/4/98 preserve loop index
1862
1863		// Find next sector to raise
1864		// 1. Find 2-sided line with same sector side[0]
1865		// 2. Other side is the next sector to raise
1866		// 3. Unless already moving, or different texture, then stop building
1867		do
1868		{
1869			Ok = false;
1870			for (i = 0; i < Sec->linecount; i++)
1871			{
1872				SecLine = Sec->lines[i];
1873
1874				if (!(SecLine->flags & ML_TWOSIDED))
1875					continue;
1876
1877				TSec = SecLine->frontsector;
1878				if (Sec != TSec)
1879					continue;
1880				TSec = SecLine->backsector;
1881				if (!TSec)
1882					continue;	//jff 5/7/98 if no backside, continue
1883				if (!IgnTxt && TSec->floor.pic != Texture)
1884					continue;
1885
1886				Height += StairSize;
1887				if (TSec->FloorData)
1888					continue;
1889
1890				Sec = TSec;
1891				//	SecNum = TSec - XLevel.Sectors;
1892				for (j = 0; j < XLevel.NumSectors; j++)
1893				{
1894					if (TSec == &XLevel.Sectors[j])
1895					{
1896						SecNum = j;
1897						break;
1898					}
1899				}
1900				Floor = Spawn(FloorMover);
1901				Floor.InitStair(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Generic,
1902					Height);
1903				Ok = true;
1904				break;
1905			}
1906		}
1907		while (Ok);
1908		if (!Level.CompatStairs)
1909		{
1910			SecNum = OldSecNum;	//jff 3/4/98 restore loop index
1911		}
1912	}
1913	return Rtn;
1914}
1915
1916//==========================================================================
1917//
1918//	EV_BuildStairs
1919//
1920//	Build a staircase!
1921//
1922//	StairDirection is either positive or negative, denoting build stairs
1923// up or down.
1924//
1925//==========================================================================
1926
1927final int EV_BuildStairs(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
1928	int StairsType)
1929{
1930	int				SecNum;
1931	sector_t*		Sec;
1932	StairStepMover	StairStep;
1933	StairStepMover	StairQueueHead;
1934
1935	StairQueueHead = none;
1936	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
1937		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
1938	{
1939		Sec = &XLevel.Sectors[SecNum];
1940
1941		// ALREADY MOVING?  IF SO, KEEP GOING...
1942		if (Sec->FloorData)
1943		{
1944			continue;
1945		}
1946
1947		StairStep = Spawn(StairStepMover);
1948		StairStep.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5,
1949			StairsType);
1950		if (StairQueueHead)
1951		{
1952			StairQueueHead.AppendToQueue(StairStep);
1953		}
1954		else
1955		{
1956			StairQueueHead = StairStep;
1957		}
1958		Sec->special &= ~SECSPEC_BASE_MASK;
1959	}
1960	for (StairStep = StairQueueHead; StairStep;
1961		StairStep = StairStep.QueueNext)
1962	{
1963		StairStep.ProcessStairSector();
1964	}
1965	return 1;
1966}
1967
1968//**************************************************************************
1969//
1970//	Platforms
1971//
1972//**************************************************************************
1973
1974//==========================================================================
1975//
1976//  EV_DoPlat
1977//
1978//  Do Platforms.
1979//
1980//==========================================================================
1981
1982final int EV_DoPlat(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
1983	int Type, line_t* Line)
1984{
1985	Platform	Plat;
1986	int			SecNum;
1987	int			Rtn;
1988	sector_t*	Sec;
1989
1990	Rtn = false;
1991
1992	if (!Arg1)
1993	{
1994		if (!Line || !Line->backsector)
1995			return false;
1996
1997		//  Activate all <type> plats that are in stasis.
1998		if ((Type == Platform::PLATEV_PerpetualRaise ||
1999			Type == Platform::PLATEV_PerpetualRaiseLip ||
2000			Type == Platform::PLATEV_Toggle) && Platform(Line->backsector->FloorData))
2001		{
2002			//  Activate in stasis
2003			Platform(Line->backsector->FloorData).ActivateInStasis(Arg1);
2004			if (Type == Platform::PLATEV_Toggle)
2005			{
2006				Rtn = true;
2007			}
2008		}
2009
2010		if (!Line->backsector->FloorData)
2011		{
2012			// Find lowest & highest floors around sector
2013			Rtn = 1;
2014			Plat = Spawn(Platform);
2015			Plat.Init(Line->backsector, Arg1, Arg2, Arg3, Arg4, Arg5, Type);
2016		}
2017	}
2018	else
2019	{
2020		//  Activate all <type> plats that are in stasis.
2021		if (Type == Platform::PLATEV_PerpetualRaise ||
2022			Type == Platform::PLATEV_PerpetualRaiseLip ||
2023			Type == Platform::PLATEV_Toggle)
2024		{
2025			//  Activate in stasis
2026			foreach AllThinkers(Platform, Plat)
2027			{
2028				Plat.ActivateInStasis(Arg1);
2029			}
2030			if (Type == Platform::PLATEV_Toggle)
2031			{
2032				Rtn = true;
2033			}
2034		}
2035
2036		for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2037			SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2038		{
2039			Sec = &XLevel.Sectors[SecNum];
2040			if (Sec->FloorData)
2041				continue;
2042
2043			// Find lowest & highest floors around sector
2044			Rtn = 1;
2045			Plat = Spawn(Platform);
2046			Plat.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type);
2047		}
2048	}
2049	return Rtn;
2050}
2051
2052//==========================================================================
2053//
2054//  EV_StopPlat
2055//
2056//==========================================================================
2057
2058final int EV_StopPlat(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
2059{
2060	Platform	Plat;
2061
2062	foreach AllThinkers(Platform, Plat)
2063	{
2064		Plat.StopPlat(Arg1);
2065	}
2066	return 1;
2067}
2068
2069//**************************************************************************
2070//
2071//  Pillar
2072//
2073//**************************************************************************
2074
2075//==========================================================================
2076//
2077//	EV_BuildPillar
2078//
2079//==========================================================================
2080
2081final int EV_BuildPillar(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
2082	bool Crush)
2083{
2084	int			SecNum;
2085	sector_t*	Sec;
2086	Pillar		pillar;
2087	int			Rtn;
2088
2089	Rtn = false;
2090	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2091		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2092	{
2093		Sec = &XLevel.Sectors[SecNum];
2094		if (Sec->FloorData || Sec->CeilingData)
2095			continue;	// already moving
2096		Rtn = true;
2097		pillar = Spawn(Pillar);
2098		pillar.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Crush);
2099	}
2100	return Rtn;
2101}
2102
2103//==========================================================================
2104//
2105//	EV_OpenPillar
2106//
2107//==========================================================================
2108
2109final int EV_OpenPillar(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
2110{
2111	int			SecNum;
2112	sector_t*	Sec;
2113	Pillar		pillar;
2114	int			Rtn;
2115
2116	Rtn = false;
2117	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2118		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2119	{
2120		Sec = &XLevel.Sectors[SecNum];
2121		if (Sec->FloorData || Sec->CeilingData)
2122			continue;	// already moving
2123		Rtn = true;
2124		pillar = Spawn(Pillar);
2125		pillar.InitOpen(Sec, Arg1, Arg2, Arg3, Arg4, Arg5);
2126	}
2127	return Rtn;
2128}
2129
2130//**************************************************************************
2131//
2132//  Elevator
2133//
2134//**************************************************************************
2135
2136//==========================================================================
2137//
2138//  EV_DoElevator
2139//
2140//==========================================================================
2141
2142final int EV_DoElevator(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
2143	int Type, line_t* Line)
2144{
2145	int			SecNum;
2146	int			Rtn;
2147	sector_t*	Sec;
2148	Elevator	Elev;
2149
2150	if (!Line && (Type == Elevator::ELEVEV_Current))
2151	{
2152		return false;
2153	}
2154
2155	Rtn = false;
2156	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2157		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2158	{
2159		Sec = &XLevel.Sectors[SecNum];
2160
2161		//	Skip if already busy.
2162		if (Sec->FloorData || Sec->CeilingData)
2163			continue;
2164
2165		//	New elevator thinker
2166		Rtn = true;
2167		Elev = Spawn(Elevator);
2168		Elev.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type, Line);
2169	}
2170	return Rtn;
2171}
2172
2173//**************************************************************************
2174//
2175//	Polyobj Event Code
2176//
2177//**************************************************************************
2178
2179//==========================================================================
2180//
2181//  EV_RotatePoly
2182//
2183//==========================================================================
2184
2185final bool EV_RotatePoly(line_t* line, int Arg1, int Arg2, int Arg3,
2186	int Arg4, int Arg5, int direction, bool overRide)
2187{
2188	int mirror;
2189	int polyNum;
2190	PolyobjRotator pe;
2191	polyobj_t *poly;
2192
2193	polyNum = Arg1;
2194	poly = XLevel.GetPolyobj(polyNum);
2195	if (poly)
2196	{
2197		if (poly->SpecialData && !overRide)
2198		{	// poly is already moving
2199			return false;
2200		}
2201	}
2202	else
2203	{
2204		Error("EV_RotatePoly:  Invalid polyobj num");	//: %d\n", polyNum);
2205	}
2206	pe = Spawn(PolyobjRotator);
2207	pe.polyobj = polyNum;
2208	if (Arg3)
2209	{
2210		if (Arg3 == 255)
2211		{
2212			pe.dist = -1.0;
2213		}
2214		else
2215		{
2216			pe.dist = itof(Arg3) * (90.0 / 64.0);	// Angle
2217		}
2218	}
2219	else
2220	{
2221		pe.dist = 360.0;
2222	}
2223	pe.speed = AngleMod180(32.0 * itof(Arg2) * itof(direction) * 90.0 / 64.0 / 8.0);
2224
2225	//THRUST
2226	pe.thrust_force = pe.speed / 32.0 * itof(0x800) / 90.0;
2227
2228	poly->SpecialData = pe;
2229	PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0);
2230
2231	for (mirror = XLevel.GetPolyobjMirror(polyNum); mirror;
2232		mirror = XLevel.GetPolyobjMirror(polyNum))
2233	{
2234		poly = XLevel.GetPolyobj(mirror);
2235		if (!poly)
2236		{
2237			Error("EV_RotatePoly:  Invalid polyobj num");	//: %d\n", polyNum);
2238		}
2239		if (poly && poly->SpecialData && !overRide)
2240		{	// mirroring poly is already in motion
2241			break;
2242		}
2243		pe = Spawn(PolyobjRotator);
2244		poly->SpecialData = pe;
2245		pe.polyobj = mirror;
2246		if (Arg3)
2247		{
2248			if (Arg3 == 255)
2249			{
2250				pe.dist = -1.0;
2251			}
2252			else
2253			{
2254				pe.dist = itof(Arg3) * (90.0 / 64.0);	// Angle
2255			}
2256		}
2257		else
2258		{
2259			pe.dist = 360.0;
2260		}
2261		direction = -direction;
2262		pe.speed = AngleMod180(32.0 * itof(Arg2) * itof(direction) * 90.0 / 64.0 / 8.0);
2263
2264		//THRUST
2265		pe.thrust_force = pe.speed / 32.0 * itof(0x800) / 90.0;
2266
2267		polyNum = mirror;
2268		PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0);
2269	}
2270	return true;
2271}
2272
2273//==========================================================================
2274//
2275// EV_MovePoly
2276//
2277//==========================================================================
2278
2279final bool EV_MovePoly(line_t * line, int Arg1, int Arg2, int Arg3, int Arg4,
2280	int Arg5, bool timesEight, bool overRide)
2281{
2282	int mirror;
2283	int polyNum;
2284	PolyobjMover pe;
2285	polyobj_t *poly;
2286	float an;
2287
2288	polyNum = Arg1;
2289	poly = XLevel.GetPolyobj(polyNum);
2290	if (poly)
2291	{
2292		if (poly->SpecialData && !overRide)
2293		{	// poly is already moving
2294			return false;
2295		}
2296	}
2297	else
2298	{
2299		Error("EV_MovePoly:  Invalid polyobj num");	//: %d\n", polyNum);
2300	}
2301	pe = Spawn(PolyobjMover);
2302	pe.polyobj = polyNum;
2303	if (timesEight)
2304	{
2305		pe.dist = itof(Arg4) * 8.0;
2306	}
2307	else
2308	{
2309		pe.dist = itof(Arg4);	// Distance
2310	}
2311	pe.speed = itof(Arg2) * 4.0;
2312
2313	//THRUST
2314	pe.thrust_force = pe.speed / 8.0;
2315
2316	poly->SpecialData = pe;
2317
2318	an = itof(Arg3) * (90.0 / 64.0);
2319
2320	pe.angle = an;
2321	PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0);
2322
2323	for (mirror = XLevel.GetPolyobjMirror(polyNum); mirror;
2324		mirror = XLevel.GetPolyobjMirror(polyNum))
2325	{
2326		poly = XLevel.GetPolyobj(mirror);
2327		if (poly && poly->SpecialData && !overRide)
2328		{	// mirroring poly is already in motion
2329			break;
2330		}
2331		pe = Spawn(PolyobjMover);
2332		pe.polyobj = mirror;
2333		poly->SpecialData = pe;
2334		if (timesEight)
2335		{
2336			pe.dist = itof(Arg4) * 8.0;
2337		}
2338		else
2339		{
2340			pe.dist = itof(Arg4);	// Distance
2341		}
2342		pe.speed = itof(Arg2) * 4.0;
2343
2344		//THRUST
2345		pe.thrust_force = pe.speed / 8.0;
2346
2347		an = AngleMod360(an + 180.0);	// reverse the angle
2348		pe.angle = an;
2349		polyNum = mirror;
2350		PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0);
2351	}
2352	return true;
2353}
2354
2355//==========================================================================
2356//
2357// EV_OpenPolyDoor
2358//
2359//==========================================================================
2360
2361final bool EV_OpenPolyDoor(line_t * line, int Arg1, int Arg2, int Arg3,
2362	int Arg4, int Arg5, int type)
2363{
2364	int mirror;
2365	int polyNum;
2366	PolyobjDoor pd;
2367	polyobj_t *poly;
2368	float an = 0.0;
2369
2370	polyNum = Arg1;
2371	poly = XLevel.GetPolyobj(polyNum);
2372	if (poly)
2373	{
2374		if (poly->SpecialData)
2375		{	// poly is already moving
2376			return false;
2377		}
2378	}
2379	else
2380	{
2381		Error("EV_OpenPolyDoor:  Invalid polyobj num");	//: %d\n", polyNum);
2382	}
2383	pd = Spawn(PolyobjDoor);
2384	pd.type = type;
2385	pd.polyobj = polyNum;
2386	if (type == PolyobjDoor::PODOOR_SLIDE)
2387	{
2388		pd.waitTime = itof(Arg5) / 35.0;
2389		pd.speed = itof(Arg2) * 4.0;
2390		pd.totalDist = itof(Arg4);	// Distance
2391		pd.dist = pd.totalDist;
2392		an = itof(Arg3) * (90.0 / 64.0);
2393		pd.xSpeed = cos(an);
2394		pd.ySpeed = sin(an);
2395
2396		//THRUST
2397		pd.thrust_force = pd.speed / 8.0;
2398
2399		PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0);
2400	}
2401	else if (type == PolyobjDoor::PODOOR_SWING)
2402	{
2403		pd.waitTime = itof(Arg4) / 35.0;
2404		pd.speed = AngleMod180(4.0 * itof(Arg2) * (90.0 / 64.0));
2405		pd.totalDist = itof(Arg3) * (90.0 / 64.0);
2406		pd.dist = pd.totalDist;
2407
2408		//THRUST
2409		pd.thrust_force = pd.speed * itof(0x1000) / 180.0;
2410
2411		PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0);
2412	}
2413
2414	poly->SpecialData = pd;
2415
2416	for (mirror = XLevel.GetPolyobjMirror(polyNum); mirror;
2417		mirror = XLevel.GetPolyobjMirror(polyNum))
2418	{
2419		poly = XLevel.GetPolyobj(mirror);
2420		if (poly && poly->SpecialData)
2421		{	// mirroring poly is already in motion
2422			break;
2423		}
2424		pd = Spawn(PolyobjDoor);
2425		pd.polyobj = mirror;
2426		pd.type = type;
2427		poly->SpecialData = pd;
2428		if (type == PolyobjDoor::PODOOR_SLIDE)
2429		{
2430			pd.waitTime = itof(Arg5) / 35.0;
2431			pd.speed = itof(Arg2) * 4.0;
2432			pd.totalDist = itof(Arg4);	// Distance
2433			pd.dist = pd.totalDist;
2434			an = AngleMod360(an + 180.0);	// reverse the angle
2435			pd.xSpeed = cos(an);
2436			pd.ySpeed = sin(an);
2437
2438			//THRUST
2439			pd.thrust_force = pd.speed / 8.0;
2440
2441			PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0);
2442		}
2443		else if (type == PolyobjDoor::PODOOR_SWING)
2444		{
2445			pd.waitTime = itof(Arg4) / 35.0;
2446			pd.speed = AngleMod180(4.0 * itof(-Arg2) * (90.0 / 64.0));
2447			pd.totalDist = itof(Arg3) * (90.0 / 64.0);
2448			pd.dist = pd.totalDist;
2449
2450			//THRUST
2451			pd.thrust_force = pd.speed * itof(0x1000) / 180.0;
2452
2453			PolyobjStartSequence(poly, GetSeqTrans(poly->seqType, SEQ_Door), 0);
2454		}
2455		polyNum = mirror;
2456	}
2457	return true;
2458}
2459
2460//**************************************************************************
2461//
2462//  Light specials
2463//
2464//**************************************************************************
2465
2466//==========================================================================
2467//
2468//  SpawnFireFlicker
2469//
2470//==========================================================================
2471
2472final void SpawnFireFlicker(sector_t* sector)
2473{
2474	FireFlicker Flick;
2475
2476	Flick = Spawn(FireFlicker);
2477	Flick.Init(sector);
2478}
2479
2480//==========================================================================
2481//
2482//  SpawnGlowingLight
2483//
2484//  Spawn glowing light
2485//
2486//==========================================================================
2487
2488final void SpawnGlowingLight(sector_t* sector)
2489{
2490	GlowingLight G;
2491
2492	G = Spawn(GlowingLight);
2493	G.Init(sector);
2494}
2495
2496//==========================================================================
2497//
2498//  SpawnLightFlash
2499//
2500//==========================================================================
2501
2502final void SpawnLightFlash(sector_t* sector)
2503{
2504	LightFlash Flash;
2505
2506	Flash = Spawn(LightFlash);
2507	Flash.Init(sector);
2508}
2509
2510//==========================================================================
2511//
2512//  SpawnStrobeFlash
2513//
2514//==========================================================================
2515
2516final void SpawnStrobeFlash(sector_t* sector, int fastOrSlow, int maxtime,
2517	int inSync)
2518{
2519	Strobe Flash;
2520
2521	Flash = Spawn(Strobe);
2522	Flash.Init(sector, fastOrSlow, maxtime, inSync);
2523}
2524
2525//==========================================================================
2526//
2527//	SpawnPhasedLight
2528//
2529//==========================================================================
2530
2531final void SpawnPhasedLight(sector_t * sector, int base, int index)
2532{
2533	PhasedLight Phase;
2534
2535	Phase = Spawn(PhasedLight);
2536	Phase.Init(sector, base, index);
2537}
2538
2539//==========================================================================
2540//
2541//  SpawnLightSequence
2542//
2543//==========================================================================
2544
2545final void SpawnLightSequence(sector_t * sector, float indexStep)
2546{
2547	sector_t *sec;
2548	sector_t *nextSec;
2549	sector_t *tempSec;
2550	int seqSpecial;
2551	int i;
2552	float count;
2553	float index;
2554	float indexDelta;
2555	int base;
2556
2557	seqSpecial = SECSPEC_LightSequence;	// look for Light_Sequence, first
2558	sec = sector;
2559	count = 1.0;
2560	do
2561	{
2562		nextSec = NULL;
2563		//	Make sure that the search doesn't back up.
2564		sec->special = (sec->special & ~SECSPEC_BASE_MASK) |
2565			SECSPEC_LightSequenceStart;
2566		for (i = 0; i < sec->linecount; i++)
2567		{
2568			tempSec = getNextSector(sec->lines[i], sec);
2569			if (!tempSec)
2570			{
2571				continue;
2572			}
2573			if ((tempSec->special & SECSPEC_BASE_MASK) == seqSpecial)
2574			{
2575				if (seqSpecial == SECSPEC_LightSequence)
2576				{
2577					seqSpecial = SECSPEC_LightSequenceAlt;
2578				}
2579				else
2580				{
2581					seqSpecial = SECSPEC_LightSequence;
2582				}
2583				nextSec = tempSec;
2584				count += 1.0;
2585			}
2586		}
2587		sec = nextSec;
2588	}
2589	while (sec);
2590
2591	sec = sector;
2592	count *= indexStep;
2593	index = 0.0;
2594	indexDelta = 64.0 / count;
2595	base = sector->params.lightlevel;
2596	do
2597	{
2598		nextSec = NULL;
2599		if (sec->params.lightlevel)
2600		{
2601			base = sec->params.lightlevel;
2602		}
2603		SpawnPhasedLight(sec, base, ftoi(index));
2604		//	Clear sector special.
2605		sec->special &= ~SECSPEC_BASE_MASK;
2606		index += indexDelta;
2607		for (i = 0; i < sec->linecount; i++)
2608		{
2609			tempSec = getNextSector(sec->lines[i], sec);
2610			if (!tempSec)
2611			{
2612				continue;
2613			}
2614			if ((tempSec->special & SECSPEC_BASE_MASK) ==
2615				SECSPEC_LightSequenceStart)
2616			{
2617				nextSec = tempSec;
2618			}
2619		}
2620		sec = nextSec;
2621	}
2622	while (sec);
2623}
2624
2625//==========================================================================
2626//
2627//  EV_StartLightStrobing
2628//
2629//  Start strobing lights (usually from a trigger)
2630//
2631//==========================================================================
2632
2633final int EV_StartLightStrobing(int Arg1, int Arg2, int Arg3, int Arg4,
2634	int Arg5)
2635{
2636	int			Ret;
2637	int			SecNum;
2638	sector_t*	Sec;
2639	Strobe		Flash;
2640
2641	Ret = false;
2642	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2643		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2644	{
2645		Sec = &XLevel.Sectors[SecNum];
2646		if (Sec->LightingData)
2647			continue;
2648
2649		Ret = true;
2650		Flash = Spawn(Strobe);
2651		Flash.Init(Sec, Arg3, Arg2, Arg4);
2652	}
2653	return Ret;
2654}
2655
2656//==========================================================================
2657//
2658//  EV_TagLightTurnOn
2659//
2660//  Turn line's tag lights on
2661//
2662//==========================================================================
2663
2664final int EV_TagLightTurnOn(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
2665{
2666	int			SecNum;
2667	sector_t*	Sec;
2668	int			j;
2669	sector_t*	TSec;
2670	int			Max;
2671
2672	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2673		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2674	{
2675		Sec = &XLevel.Sectors[SecNum];
2676		Max = 0;
2677		for (j = 0; j < Sec->linecount; j++)
2678		{
2679			TSec = getNextSector(Sec->lines[j], Sec);
2680			if (!TSec)
2681				continue;
2682			if (TSec->params.lightlevel > Max)
2683				Max = TSec->params.lightlevel;
2684		}
2685		Sec->params.lightlevel = Max;
2686	}
2687	return 1;
2688}
2689
2690//==========================================================================
2691//
2692//  EV_TurnTagLightsOff
2693//
2694//  Turn line's tag lights off
2695//
2696//==========================================================================
2697
2698final int EV_TurnTagLightsOff(int Arg1, int Arg2, int Arg3, int Arg4,
2699	int Arg5)
2700{
2701	int			SecNum;
2702	sector_t*	Sec;
2703	int			i;
2704	int			Min;
2705	sector_t*	TSec;
2706
2707	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2708		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2709	{
2710		Sec = &XLevel.Sectors[SecNum];
2711		Min = Sec->params.lightlevel;
2712		for (i = 0; i < Sec->linecount; i++)
2713		{
2714			TSec = getNextSector(Sec->lines[i], Sec);
2715			if (!TSec)
2716				continue;
2717			if (TSec->params.lightlevel < Min)
2718				Min = TSec->params.lightlevel;
2719		}
2720		Sec->params.lightlevel = Min;
2721	}
2722	return 1;
2723}
2724
2725//============================================================================
2726//
2727//  EV_LightRaiseByValue
2728//
2729//============================================================================
2730
2731final bool EV_LightRaiseByValue(int Arg1, int Arg2, int Arg3, int Arg4,
2732	int Arg5)
2733{
2734	sector_t*	Sec;
2735	int			SecNum;
2736	bool		Rtn;
2737
2738	Rtn = false;
2739	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2740		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2741	{
2742		Sec = &XLevel.Sectors[SecNum];
2743		Sec->params.lightlevel += Arg2;
2744		if (Sec->params.lightlevel > 255)
2745		{
2746			Sec->params.lightlevel = 255;
2747		}
2748		Rtn = true;
2749	}
2750	return Rtn;
2751}
2752
2753//============================================================================
2754//
2755//  EV_LightLowerByValue
2756//
2757//============================================================================
2758
2759final bool EV_LightLowerByValue(int Arg1, int Arg2, int Arg3, int Arg4,
2760	int Arg5)
2761{
2762	sector_t*	Sec;
2763	int			SecNum;
2764	bool		Rtn;
2765
2766	Rtn = false;
2767	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2768		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2769	{
2770		Sec = &XLevel.Sectors[SecNum];
2771		Sec->params.lightlevel -= Arg2;
2772		if (Sec->params.lightlevel < 0)
2773		{
2774			Sec->params.lightlevel = 0;
2775		}
2776		Rtn = true;
2777	}
2778	return Rtn;
2779}
2780
2781//============================================================================
2782//
2783//	EV_LightChangeToValue
2784//
2785//============================================================================
2786
2787final bool EV_LightChangeToValue(int Arg1, int Arg2, int Arg3, int Arg4,
2788	int Arg5)
2789{
2790	sector_t*	Sec;
2791	int			SecNum;
2792	bool		Rtn;
2793
2794	Rtn = false;
2795	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2796		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2797	{
2798		Sec = &XLevel.Sectors[SecNum];
2799		Sec->params.lightlevel = Arg2;
2800		if (Sec->params.lightlevel < 0)
2801		{
2802			Sec->params.lightlevel = 0;
2803		}
2804		else if (Sec->params.lightlevel > 255)
2805		{
2806			Sec->params.lightlevel = 255;
2807		}
2808		Rtn = true;
2809	}
2810	return Rtn;
2811}
2812
2813//============================================================================
2814//
2815//  EV_SpawnLight
2816//
2817//============================================================================
2818
2819final bool EV_SpawnLight(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
2820	int Type)
2821{
2822	LightEffect	Light;
2823	sector_t*	Sec;
2824	int			SecNum;
2825	bool		Rtn;
2826
2827	Rtn = false;
2828	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
2829		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
2830	{
2831		Sec = &XLevel.Sectors[SecNum];
2832		Light = Spawn(LightEffect);
2833		Light.Init(Sec, Arg1, Arg2, Arg3, Arg4, Arg5, Type);
2834		Rtn = true;
2835	}
2836	return Rtn;
2837}
2838
2839//==========================================================================
2840//
2841//	EV_LightStop
2842//
2843//==========================================================================
2844
2845final bool EV_LightStop(int Tag)
2846{
2847	Lighting		L;
2848
2849	foreach AllThinkers(Lighting, L)
2850	{
2851		if (L.Sector->tag == Tag)
2852		{
2853			if (L.Sector->LightingData == L)
2854				L.Sector->LightingData = none;
2855			L.Destroy();
2856		}
2857	}
2858	return true;
2859}
2860
2861//==========================================================================
2862//
2863//	SpawnTransferWallLight
2864//
2865//==========================================================================
2866
2867final void SpawnTransferWallLight(line_t* Line)
2868{
2869	WallLightTransfer	Tr;
2870
2871	Tr = Spawn(WallLightTransfer);
2872	Tr.Init(Line->frontsector, Line->arg1, Line->arg2, Line->arg3,
2873		Line->arg4, Line->arg5);
2874}
2875
2876//**************************************************************************
2877//
2878//  Scrollers
2879//
2880//**************************************************************************
2881
2882//==========================================================================
2883//
2884//  SpawnScrollingFloor
2885//
2886//==========================================================================
2887
2888final void SpawnScrollingFloor(sector_t* Sector, int XDir, int YDir,
2889	int Speed)
2890{
2891	Scroller	Scroll;
2892
2893	Scroll = Spawn(Scroller);
2894	Scroll.InitFloor(Sector, XDir, YDir, Speed);
2895}
2896
2897//==========================================================================
2898//
2899//  SpawnWallScroller
2900//
2901//==========================================================================
2902
2903final void SpawnWallScroller(line_t* Line, int XDir, int YDir)
2904{
2905	Scroller	Scroll;
2906
2907	Scroll = Spawn(Scroller);
2908	Scroll.InitWall(Line, XDir, YDir);
2909}
2910
2911//==========================================================================
2912//
2913//  SpawnWallOffsetsScroller
2914//
2915//==========================================================================
2916
2917final void SpawnWallOffsetsScroller(line_t* Line)
2918{
2919	Scroller	Scroll;
2920
2921	Scroll = Spawn(Scroller);
2922	Scroll.InitWallOffsets(Line);
2923}
2924
2925//==========================================================================
2926//
2927//  SpawnTextureBothScroller
2928//
2929//==========================================================================
2930
2931final void SpawnTextureBothScroller(line_t* Line)
2932{
2933	Scroller	Scroll;
2934
2935	Scroll = Spawn(Scroller);
2936	Scroll.InitTextureBoth(Line, itof(Line->arg2 - Line->arg3) / 64.0,
2937		itof(Line->arg5 - Line->arg4) / 64.0, 0, 7);
2938}
2939
2940//==========================================================================
2941//
2942//  SpawnScrollCeiling
2943//
2944//==========================================================================
2945
2946final void SpawnScrollCeiling(line_t* Line)
2947{
2948	Scroller	Scroll;
2949	int			SecNum;
2950
2951	for (SecNum = XLevel.FindSectorFromTag(Line->arg1, -1); SecNum >= 0;
2952		SecNum = XLevel.FindSectorFromTag(Line->arg1, SecNum))
2953	{
2954		Scroll = Spawn(Scroller);
2955		Scroll.InitGen(Scroller::SCROLLEV_Ceiling, Line, SecNum);
2956	}
2957}
2958
2959//==========================================================================
2960//
2961//  SpawnScrollFloor
2962//
2963//==========================================================================
2964
2965final void SpawnScrollFloor(line_t* Line)
2966{
2967	Scroller	Scroll;
2968	int			SecNum;
2969
2970	for (SecNum = XLevel.FindSectorFromTag(Line->arg1, -1); SecNum >= 0;
2971		SecNum = XLevel.FindSectorFromTag(Line->arg1, SecNum))
2972	{
2973		if (Line->arg3 != 1)
2974		{
2975			//	Scroll the floor texture
2976			Scroll = Spawn(Scroller);
2977			Scroll.InitGen(Scroller::SCROLLEV_Floor, Line, SecNum);
2978		}
2979
2980		if (Line->arg3 > 0)
2981		{
2982			//	Carry objects on the floor
2983			Scroll = Spawn(Scroller);
2984			Scroll.InitGen(Scroller::SCROLLEV_Carry, Line, SecNum);
2985		}
2986	}
2987}
2988
2989//==========================================================================
2990//
2991//  SpawnScrollTextureModel
2992//
2993//	Scroll wall according to linedef
2994// (same direction and speed as scrolling floors)
2995//
2996//==========================================================================
2997
2998final void SpawnScrollTextureModel(line_t* Line)
2999{
3000	Scroller	Scroll;
3001	int			Searcher;
3002	line_t*		Other;
3003
3004	Searcher = -1;
3005	for (Other = XLevel.FindLine(Line->arg1, &Searcher); Other;
3006		Other = XLevel.FindLine(Line->arg1, &Searcher))
3007	{
3008		if (Line != Other)
3009		{
3010			Scroll = Spawn(Scroller);
3011			Scroll.InitTextureModel(Other, Line);
3012		}
3013	}
3014}
3015
3016//==========================================================================
3017//
3018//  EV_ScrollTextureBoth
3019//
3020//==========================================================================
3021
3022final bool EV_ScrollTextureBoth(int Arg1, int Arg2, int Arg3, int Arg4,
3023	int Arg5)
3024{
3025	if (!Arg1)
3026	{
3027		//	No lines to adjust.
3028		return false;
3029	}
3030
3031	int WhichSide = 0;
3032	if (Arg1 < 0)
3033	{
3034		WhichSide = 1;
3035		Arg1 = -Arg1;
3036	}
3037
3038	SetWallScroller(Arg1, itof(Arg2 - Arg3) / 64.0, itof(Arg5 - Arg4) / 64.0,
3039		WhichSide, 7);
3040	return true;
3041}
3042
3043//==========================================================================
3044//
3045//	EV_ScrollWall
3046//
3047//==========================================================================
3048
3049final bool EV_ScrollWall(int Arg1, int Arg2, int Arg3, int Arg4,
3050	int Arg5)
3051{
3052	if (!Arg1)
3053	{
3054		//	No lines to adjust.
3055		return false;
3056	}
3057
3058	SetWallScroller(Arg1, itof(Arg2) / itof(0x10000),
3059		itof(Arg3) / itof(0x10000), !!Arg4, Arg5);
3060	return true;
3061}
3062
3063//==========================================================================
3064//
3065//	SetWallScroller
3066//
3067//==========================================================================
3068
3069final void SetWallScroller(int LineId, float XSpeed, float YSpeed,
3070	int WhichSide, int Where)
3071{
3072	Scroller	Scroll;
3073	int			i;
3074
3075	Where &= 7;
3076	if (!Where)
3077	{
3078		return;
3079	}
3080
3081	if (!XSpeed && !YSpeed)
3082	{
3083		//	As a special case with no deltas remove any texture scrolers.
3084		foreach AllThinkers(Scroller, Scroll)
3085		{
3086			if (Scroll.Type != Scroller::SCROLLEV_Side)
3087			{
3088				continue;
3089			}
3090			//	Check if line has a correct tag.
3091			if (Scroll.AffecteeSrcLine->LineTag != LineId)
3092			{
3093				continue;
3094			}
3095			//	Check if it's the correct side.
3096			if (Scroll.AffecteeSrcLine->sidenum[WhichSide] != Scroll.Affectee)
3097			{
3098				continue;
3099			}
3100			//	Check if it's scrolling the same wall parts
3101			if (Scroll.SideParts != Where)
3102			{
3103				continue;
3104			}
3105			//	OK, destroy the thinker.
3106			Scroll.Destroy();
3107		}
3108	}
3109	else
3110	{
3111		array<Scroller>		FoundScrollers;
3112
3113		foreach AllThinkers(Scroller, Scroll)
3114		{
3115			if (Scroll.Type != Scroller::SCROLLEV_Side)
3116			{
3117				continue;
3118			}
3119			//	Check if line has a correct tag.
3120			if (Scroll.AffecteeSrcLine->LineTag != LineId)
3121			{
3122				continue;
3123			}
3124			//	Check if it's the correct side.
3125			if (Scroll.AffecteeSrcLine->sidenum[WhichSide] != Scroll.Affectee)
3126			{
3127				continue;
3128			}
3129			//	Check if it's scrolling the same wall parts
3130			if (Scroll.SideParts != Where)
3131			{
3132				continue;
3133			}
3134			//	Found it.
3135			Scroll.AdjustTextureBoth(XSpeed, YSpeed);
3136			FoundScrollers[FoundScrollers.Num] = Scroll;
3137		}
3138
3139		int			Searcher;
3140		line_t*		Other;
3141
3142		Searcher = -1;
3143		for (Other = XLevel.FindLine(LineId, &Searcher); Other;
3144			Other = XLevel.FindLine(LineId, &Searcher))
3145		{
3146			//	Check if this line already has scroller.
3147			for (i = 0; i < FoundScrollers.Num; i++)
3148			{
3149				if (FoundScrollers[i].Affectee == Other->sidenum[WhichSide])
3150				{
3151					break;
3152				}
3153			}
3154			if (i == FoundScrollers.Num)
3155			{
3156				//	Start a new scroller.
3157				Scroll = Spawn(Scroller);
3158				Scroll.InitTextureBoth(Other, XSpeed, YSpeed, WhichSide,
3159					Where);
3160			}
3161		}
3162	}
3163}
3164
3165//==========================================================================
3166//
3167//  EV_ScrollFloor
3168//
3169//==========================================================================
3170
3171final bool EV_ScrollFloor(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
3172{
3173	//	Set floor scrolling
3174	if (Arg4 == 0 || Arg4 == 2)
3175	{
3176		SetScroller(Scroller::SCROLLEV_Floor, Arg1, Arg2, Arg3);
3177	}
3178	else
3179	{
3180		SetScroller(Scroller::SCROLLEV_Floor, Arg1, 0, 0);
3181	}
3182
3183	//	Set carrying of items.
3184	if (Arg4 > 0)
3185	{
3186		SetScroller(Scroller::SCROLLEV_Carry, Arg1, Arg2, Arg3);
3187	}
3188	else
3189	{
3190		SetScroller(Scroller::SCROLLEV_Carry, Arg1, 0, 0);
3191	}
3192	return true;
3193}
3194
3195//==========================================================================
3196//
3197//  EV_ScrollCeiling
3198//
3199//==========================================================================
3200
3201final bool EV_ScrollCeiling(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
3202{
3203	SetScroller(Scroller::SCROLLEV_Ceiling, Arg1, Arg2, Arg3);
3204	return true;
3205}
3206
3207//==========================================================================
3208//
3209//  SetScroller
3210//
3211//==========================================================================
3212
3213final void SetScroller(int Type, int Arg1, int Arg2, int Arg3)
3214{
3215	//	Adjust existing scrollers. If there is any, it means that all
3216	// tagged sectors have them and there's no need to spawn new ones.
3217	Scroller Sc;
3218	bool Found = false;
3219	foreach AllThinkers(Scroller, Sc)
3220	{
3221		if (Sc.Type == Type && Sc.Sector->tag == Arg1)
3222		{
3223			Sc.SetSpeed(Arg2, Arg3);
3224			Found = true;
3225		}
3226	}
3227
3228	if (Found)
3229	{
3230		return;
3231	}
3232	//	Don't spawn scrollers if both speeds are 0.
3233	if (!Arg2 && !Arg3)
3234	{
3235		return;
3236	}
3237
3238	int			SecNum;
3239
3240	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
3241		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
3242	{
3243		Sc = Spawn(Scroller);
3244		Sc.InitScripted(Type, Arg2, Arg3, SecNum);
3245	}
3246}
3247
3248//**************************************************************************
3249//
3250//  Transfering floor texture and sector special
3251//
3252//**************************************************************************
3253
3254//==========================================================================
3255//
3256//	EV_FloorTransferTrigger
3257//
3258//==========================================================================
3259
3260final bool EV_FloorTransferTrigger(int Arg1, int Arg2, int Arg3, int Arg4,
3261	int Arg5, line_t* Line)
3262{
3263	int			SecNum;
3264	bool		Rtn;
3265	sector_t*	Sec;
3266
3267	if (!Line)
3268	{
3269		return false;
3270	}
3271
3272	Rtn = false;
3273	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
3274		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
3275	{
3276		Sec = &XLevel.Sectors[SecNum];
3277		Rtn = true;
3278
3279		Sec->floor.pic = Line->frontsector->floor.pic;
3280		Sec->special = (Sec->special & SECSPEC_SECRET_MASK) |
3281			(Line->frontsector->special & ~SECSPEC_SECRET_MASK);
3282	}
3283	return Rtn;
3284}
3285
3286//==========================================================================
3287//
3288//	EV_FloorTransferNumeric
3289//
3290//==========================================================================
3291
3292final bool EV_FloorTransferNumeric(int Arg1, int Arg2, int Arg3, int Arg4,
3293	int Arg5)
3294{
3295	int			SecNum;
3296	bool		Rtn;
3297	sector_t*	Sec;
3298	sector_t*	MdlSec;
3299
3300	Rtn = false;
3301	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
3302		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
3303	{
3304		Sec = &XLevel.Sectors[SecNum];
3305		Rtn = true;
3306
3307		MdlSec = FindModelFloorSector(Sec, GetPlanePointZ(&Sec->floor,
3308			Sec->soundorg));
3309		if (MdlSec)
3310		{
3311			Sec->floor.pic = MdlSec->floor.pic;
3312			Sec->special = MdlSec->special;
3313		}
3314	}
3315	return Rtn;
3316}
3317
3318//************************************************************************
3319//
3320//	Changing of sector properties
3321//
3322//************************************************************************
3323
3324//=========================================================================
3325//
3326//	EV_SectorSoundChange
3327//
3328//=========================================================================
3329
3330final bool EV_SectorSoundChange(int Arg1, int Arg2, int Arg3, int Arg4,
3331	int Arg5)
3332{
3333	int secNum;
3334	bool rtn;
3335
3336	if (!Arg1)
3337	{
3338		return false;
3339	}
3340	rtn = false;
3341	for (secNum = XLevel.FindSectorFromTag(Arg1, -1); secNum >= 0;
3342		secNum = XLevel.FindSectorFromTag(Arg1, secNum))
3343	{
3344		XLevel.Sectors[secNum].seqType = Arg2;
3345		rtn = true;
3346	}
3347	return rtn;
3348}
3349
3350//=========================================================================
3351//
3352//	EV_SectorSetColour
3353//
3354//=========================================================================
3355
3356final bool EV_SectorSetColour(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
3357{
3358	int		SecNum;
3359	int		Col;
3360
3361	Col = RGBA(Arg2, Arg3, Arg4, 0);
3362	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
3363		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
3364	{
3365		XLevel.Sectors[SecNum].params.LightColour = Col;
3366	}
3367	return true;
3368}
3369
3370//=========================================================================
3371//
3372//	EV_SectorSetFade
3373//
3374//=========================================================================
3375
3376final bool EV_SectorSetFade(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
3377{
3378	int		SecNum;
3379	int		Fade;
3380
3381	Fade = RGBA(Arg2, Arg3, Arg4, 255);
3382	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
3383		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
3384	{
3385		XLevel.Sectors[SecNum].params.Fade = Fade;
3386	}
3387	return true;
3388}
3389
3390//=========================================================================
3391//
3392//	EV_SectorSetDamage
3393//
3394//=========================================================================
3395
3396final bool EV_SectorSetDamage(int Arg1, int Arg2, int Arg3, int Arg4,
3397	int Arg5)
3398{
3399	int		SecNum;
3400	bool	Rtn;
3401
3402	if (!Arg1)
3403	{
3404		return false;
3405	}
3406	Rtn = false;
3407	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
3408		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
3409	{
3410		XLevel.Sectors[SecNum].Damage = Arg2;
3411		//FIXME Arg3 is MOD
3412		Rtn = true;
3413	}
3414	return Rtn;
3415}
3416
3417//==========================================================================
3418//
3419//  EV_SectorSetGravity
3420//
3421//==========================================================================
3422
3423final bool EV_SectorSetGravity(int Arg1, int Arg2, int Arg3, int Arg4,
3424	int Arg5)
3425{
3426	int		SecNum;
3427	float	SecGrav;
3428	bool	Ret;
3429
3430	if (Arg3 > 99)
3431		Arg3 = 99;
3432
3433	SecGrav = itof(Arg2) + itof(Arg3) * 0.01;
3434	Ret = false;
3435	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
3436		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
3437	{
3438		XLevel.Sectors[SecNum].Gravity = SecGrav;
3439		Ret = true;
3440	}
3441	return Ret;
3442}
3443
3444//==========================================================================
3445//
3446//  SetSectorFriction
3447//
3448//==========================================================================
3449
3450final bool EV_SectorSetFriction(int Arg1, int Arg2, int Arg3, int Arg4,
3451	int Arg5)
3452{
3453	int			s;
3454	int			OldFriction;
3455	int			OldMoveFactor;
3456	float		Friction;
3457	float		MoveFactor;
3458	bool		Ret;
3459
3460	// An amount of 100 should result in a friction of
3461	// ORIG_FRICTION (0xE800)
3462	OldFriction = (0x1EB8 * Arg2) / 0x80 + 0xD001;
3463
3464	// killough 8/28/98: prevent odd situations
3465	if (OldFriction > 0x10000)
3466		OldFriction = 0x10000;
3467	if (OldFriction < 0)
3468		OldFriction = 0;
3469
3470	// The following check might seem odd. At the time of movement,
3471	// the move distance is multiplied by 'friction/0x10000', so a
3472	// higher friction value actually means 'less friction'.
3473
3474	// [RH] Twiddled these values so that momentum on ice (with
3475	//		friction 0xf900) is the same as in Heretic/Hexen.
3476	if (OldFriction >= 0xe800)	// ice
3477//		movefactor = ((0x10092 - friction)*(0x70))/0x158;
3478		OldMoveFactor = ((0x10092 - OldFriction) * 1024) / 4352 + 568;
3479	else
3480		OldMoveFactor = ((OldFriction - 0xDB34) * (0xA)) / 0x80;
3481
3482	// killough 8/28/98: prevent odd situations
3483	if (OldMoveFactor < 32)
3484		OldMoveFactor = 32;
3485
3486	Friction = (1.0 - itof(OldFriction) / itof(0x10000)) * 35.0;
3487	MoveFactor = itof(OldMoveFactor) / itof(0x10000);
3488
3489	Ret = false;
3490	for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0;
3491		s = XLevel.FindSectorFromTag(Arg1, s))
3492	{
3493		// killough 8/28/98:
3494		//
3495		// Instead of spawning thinkers, which are slow and expensive,
3496		// modify the sector's own friction values. Friction should be
3497		// a property of sectors, not objects which reside inside them.
3498		// Original code scanned every object in every friction sector
3499		// on every tic, adjusting its friction, putting unnecessary
3500		// drag on CPU. New code adjusts friction of sector only once
3501		// at level startup, and then uses this friction value.
3502
3503		XLevel.Sectors[s].Friction = Friction;
3504		XLevel.Sectors[s].MoveFactor = MoveFactor;
3505		// When used inside a script, the sectors' friction flags
3506		// can be enabled and disabled at will.
3507		if (OldFriction == 0xe800)
3508		{
3509			XLevel.Sectors[s].special &= ~SECSPEC_FRICTION_MASK;
3510		}
3511		else
3512		{
3513			XLevel.Sectors[s].special |= SECSPEC_FRICTION_MASK;
3514		}
3515		Ret = true;
3516	}
3517	return Ret;
3518}
3519
3520//=========================================================================
3521//
3522//	EV_SectorChangeFlags
3523//
3524//=========================================================================
3525
3526final bool EV_SectorChangeFlags(int Arg1, int Arg2, int Arg3, int Arg4,
3527	int Arg5)
3528{
3529	int		SecNum;
3530	bool	Rtn;
3531
3532	if (!Arg1)
3533	{
3534		return false;
3535	}
3536	Rtn = false;
3537	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
3538		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
3539	{
3540		sector_t* Sec = &XLevel.Sectors[SecNum];
3541		if (Arg3 & SECF_SILENT)
3542		{
3543			Sec->bSilent = false;
3544		}
3545		else if (Arg2 & SECF_SILENT)
3546		{
3547			Sec->bSilent = true;
3548		}
3549		if (Arg3 & SECF_NOFALLINGDAMAGE)
3550		{
3551			Sec->bNoFallingDamage = false;
3552		}
3553		else if (Arg2 & SECF_NOFALLINGDAMAGE)
3554		{
3555			Sec->bNoFallingDamage = true;
3556		}
3557		Rtn = true;
3558	}
3559	return Rtn;
3560}
3561
3562//==========================================================================
3563//
3564//  EV_SectorSetFloorPanning
3565//
3566//==========================================================================
3567
3568final bool EV_SectorSetFloorPanning(int Arg1, int Arg2, int Arg3, int Arg4,
3569	int Arg5)
3570{
3571	int s;
3572	float XOffs = itof(Arg2) + itof(Arg3) / 100.0;
3573	float YOffs = itof(Arg4) + itof(Arg5) / 100.0;
3574	for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0;
3575		s = XLevel.FindSectorFromTag(Arg1, s))
3576	{
3577		XLevel.Sectors[s].floor.xoffs = XOffs;
3578		XLevel.Sectors[s].floor.yoffs = YOffs;
3579		ClampSecPlaneOffsets(&XLevel.Sectors[s].floor);
3580	}
3581	return true;
3582}
3583
3584//==========================================================================
3585//
3586//  EV_SectorSetCeilingPanning
3587//
3588//==========================================================================
3589
3590final bool EV_SectorSetCeilingPanning(int Arg1, int Arg2, int Arg3, int Arg4,
3591	int Arg5)
3592{
3593	int s;
3594	float XOffs = itof(Arg2) + itof(Arg3) / 100.0;
3595	float YOffs = itof(Arg4) + itof(Arg5) / 100.0;
3596	for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0;
3597		s = XLevel.FindSectorFromTag(Arg1, s))
3598	{
3599		XLevel.Sectors[s].ceiling.xoffs = XOffs;
3600		XLevel.Sectors[s].ceiling.yoffs = YOffs;
3601		ClampSecPlaneOffsets(&XLevel.Sectors[s].ceiling);
3602	}
3603	return true;
3604}
3605
3606//==========================================================================
3607//
3608//  EV_SectorSetRotation
3609//
3610//==========================================================================
3611
3612final bool EV_SectorSetRotation(int Arg1, int Arg2, int Arg3, int Arg4,
3613	int Arg5)
3614{
3615	int s;
3616	for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0;
3617		s = XLevel.FindSectorFromTag(Arg1, s))
3618	{
3619		XLevel.Sectors[s].floor.Angle = itof(Arg2);
3620		XLevel.Sectors[s].ceiling.Angle = itof(Arg3);
3621	}
3622	return true;
3623}
3624
3625//==========================================================================
3626//
3627//  EV_SectorSetFloorScale
3628//
3629//==========================================================================
3630
3631final bool EV_SectorSetFloorScale(int Arg1, int Arg2, int Arg3, int Arg4,
3632	int Arg5)
3633{
3634	int s;
3635	float XScale = itof(Arg2) + itof(Arg3) / 100.0;
3636	float YScale = itof(Arg4) + itof(Arg5) / 100.0;
3637	if (XScale)
3638	{
3639		XScale = 1.0 / XScale;
3640	}
3641	if (YScale)
3642	{
3643		YScale = 1.0 / YScale;
3644	}
3645	for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0;
3646		s = XLevel.FindSectorFromTag(Arg1, s))
3647	{
3648		if (XScale)
3649		{
3650			XLevel.Sectors[s].floor.XScale = XScale;
3651		}
3652		if (YScale)
3653		{
3654			XLevel.Sectors[s].floor.YScale = YScale;
3655		}
3656	}
3657	return true;
3658}
3659
3660//==========================================================================
3661//
3662//  EV_SectorSetCeilingScale
3663//
3664//==========================================================================
3665
3666final bool EV_SectorSetCeilingScale(int Arg1, int Arg2, int Arg3, int Arg4,
3667	int Arg5)
3668{
3669	int s;
3670	float XScale = itof(Arg2) + itof(Arg3) / 100.0;
3671	float YScale = itof(Arg4) + itof(Arg5) / 100.0;
3672	if (XScale)
3673	{
3674		XScale = 1.0 / XScale;
3675	}
3676	if (YScale)
3677	{
3678		YScale = 1.0 / YScale;
3679	}
3680	for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0;
3681		s = XLevel.FindSectorFromTag(Arg1, s))
3682	{
3683		if (XScale)
3684		{
3685			XLevel.Sectors[s].ceiling.XScale = XScale;
3686		}
3687		if (YScale)
3688		{
3689			XLevel.Sectors[s].ceiling.YScale = YScale;
3690		}
3691	}
3692	return true;
3693}
3694
3695//==========================================================================
3696//
3697//  EV_SectorSetFloorScale2
3698//
3699//==========================================================================
3700
3701final bool EV_SectorSetFloorScale2(int Arg1, int Arg2, int Arg3, int Arg4,
3702	int Arg5)
3703{
3704	int s;
3705	float XScale = itof(Arg2) / itof(0x10000);
3706	float YScale = itof(Arg3) / itof(0x10000);
3707	if (XScale)
3708	{
3709		XScale = 1.0 / XScale;
3710	}
3711	if (YScale)
3712	{
3713		YScale = 1.0 / YScale;
3714	}
3715	for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0;
3716		s = XLevel.FindSectorFromTag(Arg1, s))
3717	{
3718		if (XScale)
3719		{
3720			XLevel.Sectors[s].floor.XScale = XScale;
3721		}
3722		if (YScale)
3723		{
3724			XLevel.Sectors[s].floor.YScale = YScale;
3725		}
3726	}
3727	return true;
3728}
3729
3730//==========================================================================
3731//
3732//  EV_SectorSetCeilingScale2
3733//
3734//==========================================================================
3735
3736final bool EV_SectorSetCeilingScale2(int Arg1, int Arg2, int Arg3, int Arg4,
3737	int Arg5)
3738{
3739	int s;
3740	float XScale = itof(Arg2) / itof(0x10000);
3741	float YScale = itof(Arg3) / itof(0x10000);
3742	if (XScale)
3743	{
3744		XScale = 1.0 / XScale;
3745	}
3746	if (YScale)
3747	{
3748		YScale = 1.0 / YScale;
3749	}
3750	for (s = XLevel.FindSectorFromTag(Arg1, -1); s >= 0;
3751		s = XLevel.FindSectorFromTag(Arg1, s))
3752	{
3753		if (XScale)
3754		{
3755			XLevel.Sectors[s].ceiling.XScale = XScale;
3756		}
3757		if (YScale)
3758		{
3759			XLevel.Sectors[s].ceiling.YScale = YScale;
3760		}
3761	}
3762	return true;
3763}
3764
3765//==========================================================================
3766//
3767//  EV_LineAlignFloor
3768//
3769//==========================================================================
3770
3771final bool EV_LineAlignFloor(int Arg1, int Arg2, int Arg3, int Arg4,
3772	int Arg5)
3773{
3774	int			Searcher;
3775	line_t*		Line;
3776	bool		Ret;
3777	sector_t*	Sec;
3778
3779	Searcher = -1;
3780	Ret = false;
3781	for (Line = XLevel.FindLine(Arg1, &Searcher); Line;
3782		Line = XLevel.FindLine(Arg1, &Searcher))
3783	{
3784		Sec = Arg2 ? Line->backsector : Line->frontsector;
3785		if (!Sec)
3786		{
3787			continue;
3788		}
3789
3790		TVec v1 = *Line->v1;
3791
3792		float Angle = atan2(Line->v2->y - v1.y, Line->v2->x - v1.x);
3793		float Norm = Angle - 90.0;
3794
3795		float Dist = -(cos(Norm) * v1.x + sin(Norm) * v1.y);
3796
3797		if (Arg2)
3798		{
3799			Angle = AngleMod360(Angle + 180.0);
3800			Dist = -Dist;
3801		}
3802
3803		Sec->floor.BaseAngle = Angle;
3804		while (Dist < 0.0)
3805		{
3806			Dist += 256.0;
3807		}
3808		while (Dist >= 256.0)
3809		{
3810			Dist -= 256.0;
3811		}
3812		Sec->floor.BaseYOffs = Dist;
3813		Ret = true;
3814	}
3815	return Ret;
3816}
3817
3818//==========================================================================
3819//
3820//  EV_LineAlignCeiling
3821//
3822//==========================================================================
3823
3824final bool EV_LineAlignCeiling(int Arg1, int Arg2, int Arg3, int Arg4,
3825	int Arg5)
3826{
3827	int			Searcher;
3828	line_t*		Line;
3829	bool		Ret;
3830	sector_t*	Sec;
3831
3832	Searcher = -1;
3833	Ret = false;
3834	for (Line = XLevel.FindLine(Arg1, &Searcher); Line;
3835		Line = XLevel.FindLine(Arg1, &Searcher))
3836	{
3837		Sec = Arg2 ? Line->backsector : Line->frontsector;
3838		if (!Sec)
3839		{
3840			continue;
3841		}
3842
3843		TVec v1 = *Line->v1;
3844
3845		float Angle = atan2(Line->v2->y - v1.y, Line->v2->x - v1.x);
3846		float Norm = Angle - 90.0;
3847
3848		float Dist = -(cos(Norm) * v1.x + sin(Norm) * v1.y);
3849
3850		if (Arg2)
3851		{
3852			Angle = AngleMod360(Angle + 180.0);
3853			Dist = -Dist;
3854		}
3855
3856		Sec->ceiling.BaseAngle = Angle;
3857		while (Dist < 0.0)
3858		{
3859			Dist += 256.0;
3860		}
3861		while (Dist >= 256.0)
3862		{
3863			Dist -= 256.0;
3864		}
3865		Sec->ceiling.BaseYOffs = Dist;
3866		Ret = true;
3867	}
3868	return Ret;
3869}
3870
3871//==========================================================================
3872//
3873//	EV_LineSetTextureOffset
3874//
3875//==========================================================================
3876
3877final bool EV_LineSetTextureOffset(int Arg1, int Arg2, int Arg3, int Arg4,
3878	int Arg5)
3879{
3880	int			Searcher;
3881	line_t*		Line;
3882
3883	//	Check if side is valid.
3884	if (Arg4 < 0 || Arg4 > 1)
3885	{
3886		return false;
3887	}
3888
3889	int NoChange = 0x7fff0000;
3890
3891	Searcher = -1;
3892	for (Line = XLevel.FindLine(Arg1, &Searcher); Line;
3893		Line = XLevel.FindLine(Arg1, &Searcher))
3894	{
3895		if (Line->sidenum[Arg4] < 0)
3896		{
3897			continue;
3898		}
3899		side_t* Side = &XLevel.Sides[Line->sidenum[Arg4]];
3900
3901		//	X offset
3902		if (Arg2 != NoChange)
3903		{
3904			float Offs = itof(Arg2) / itof(0x10000);
3905			if (!(Arg5 & 8))
3906			{
3907				//	Set
3908				if (Arg5 & 1)
3909				{
3910					Side->TopTextureOffset = Offs;
3911				}
3912				if (Arg5 & 2)
3913				{
3914					Side->MidTextureOffset = Offs;
3915				}
3916				if (Arg5 & 4)
3917				{
3918					Side->BotTextureOffset = Offs;
3919				}
3920			}
3921			else
3922			{
3923				//	Add
3924				if (Arg5 & 1)
3925				{
3926					Side->TopTextureOffset += Offs;
3927				}
3928				if (Arg5 & 2)
3929				{
3930					Side->MidTextureOffset += Offs;
3931				}
3932				if (Arg5 & 4)
3933				{
3934					Side->BotTextureOffset += Offs;
3935				}
3936			}
3937		}
3938
3939		//	Y offset
3940		if (Arg3 != NoChange)
3941		{
3942			float Offs = itof(Arg3) / itof(0x10000);
3943			if (!(Arg5 & 8))
3944			{
3945				//	Set
3946				if (Arg5 & 1)
3947				{
3948					Side->TopRowOffset = Offs;
3949				}
3950				if (Arg5 & 2)
3951				{
3952					Side->MidRowOffset = Offs;
3953				}
3954				if (Arg5 & 4)
3955				{
3956					Side->BotRowOffset = Offs;
3957				}
3958			}
3959			else
3960			{
3961				//	Add
3962				if (Arg5 & 1)
3963				{
3964					Side->TopRowOffset += Offs;
3965				}
3966				if (Arg5 & 2)
3967				{
3968					Side->MidRowOffset += Offs;
3969				}
3970				if (Arg5 & 4)
3971				{
3972					Side->BotRowOffset += Offs;
3973				}
3974			}
3975		}
3976		ClampSideOffsets(Side);
3977	}
3978	return true;
3979}
3980
3981//==========================================================================
3982//
3983//	ClampSideOffsets
3984//
3985//==========================================================================
3986
3987final void ClampSideOffsets(side_t* Side)
3988{
3989	if (Side->TopTextureOffset > 32767.0 || Side->TopTextureOffset < -32768.0)
3990	{
3991		Side->TopTextureOffset = 0.0;
3992	}
3993	if (Side->BotTextureOffset > 32767.0 || Side->BotTextureOffset < -32768.0)
3994	{
3995		Side->BotTextureOffset = 0.0;
3996	}
3997	if (Side->MidTextureOffset > 32767.0 || Side->MidTextureOffset < -32768.0)
3998	{
3999		Side->MidTextureOffset = 0.0;
4000	}
4001	if (Side->TopRowOffset > 32767.0 || Side->TopRowOffset < -32768.0)
4002	{
4003		Side->TopRowOffset = 0.0;
4004	}
4005	if (Side->BotRowOffset > 32767.0 || Side->BotRowOffset < -32768.0)
4006	{
4007		Side->BotRowOffset = 0.0;
4008	}
4009	if (Side->MidRowOffset > 32767.0 || Side->MidRowOffset < -32768.0)
4010	{
4011		Side->MidRowOffset = 0.0;
4012	}
4013}
4014
4015//==========================================================================
4016//
4017//	ClampSecPlaneOffsets
4018//
4019//==========================================================================
4020
4021final void ClampSecPlaneOffsets(sec_plane_t* Plane)
4022{
4023	if (Plane->xoffs > 32767.0 || Plane->xoffs < -32768.0)
4024	{
4025		Plane->xoffs = 0.0;
4026	}
4027	if (Plane->yoffs > 32767.0 || Plane->yoffs < -32768.0)
4028	{
4029		Plane->yoffs = 0.0;
4030	}
4031}
4032
4033//**************************************************************************
4034//
4035//  Thing line specials
4036//
4037//**************************************************************************
4038
4039//==========================================================================
4040//
4041//	EV_ThingProjectile
4042//
4043//==========================================================================
4044
4045final bool EV_ThingProjectile(int Arg1, int Arg2, int Arg3, int Arg4,
4046	int Arg5, bool gravity, int newtid, name TypeName, Entity Activator)
4047{
4048	return DoThingProjectile(Arg1, Arg2, Arg3, Arg4, Arg5, gravity, newtid,
4049		TypeName, Activator, 0, none, false);
4050}
4051
4052//==========================================================================
4053//
4054//	EV_ThingProjectileAimed
4055//
4056//==========================================================================
4057
4058final bool EV_ThingProjectileAimed(int Arg1, int Arg2, int Arg3, int Arg4,
4059	int Arg5, Entity Activator)
4060{
4061	return DoThingProjectile(Arg1, Arg2, 0, Arg3, 0, 0, Arg5, '', Activator,
4062		Arg4, Activator, false);
4063}
4064
4065//==========================================================================
4066//
4067//	EV_ThingProjectileIntercept
4068//
4069//==========================================================================
4070
4071final bool EV_ThingProjectileIntercept(int Arg1, int Arg2, int Arg3, int Arg4,
4072	int Arg5, Entity Activator)
4073{
4074	return DoThingProjectile(Arg1, Arg2, 0, Arg3, 0, 0, Arg5, '', Activator,
4075		Arg4, Activator, true);
4076}
4077
4078//==========================================================================
4079//
4080//	DoThingProjectile
4081//
4082//==========================================================================
4083
4084final bool DoThingProjectile(int tid, int TypeID, int AAngle, int ASpeed,
4085	int AVSpeed, bool gravity, int newtid, name TypeName, Entity Activator,
4086	int DestTID, Entity ForceDest, bool Intercept)
4087{
4088	float		angle;
4089	float		speed;
4090	float		vspeed;
4091	class<EntityEx>	moType;
4092	Entity		A;
4093	EntityEx	newA;
4094	bool		success;
4095
4096	success = false;
4097	if (TypeName)
4098	{
4099		moType = class<EntityEx>(FindClassLowerCase(TypeName));
4100	}
4101	else
4102	{
4103		moType = class<EntityEx>(FindClassFromScriptId(TypeID,
4104			LineSpecialGameInfo(Game).GameFilterFlag));
4105	}
4106	if (!moType)
4107	{
4108		return false;
4109	}
4110	moType = class<EntityEx>(GetClassReplacement(moType));
4111	if (Level.Game.nomonsters && moType.default.bMonster)
4112	{
4113		return false;
4114	}
4115	angle = itof(AAngle) * (360.0 / 256.0);
4116	speed = itof(ASpeed) / 8.0;
4117	vspeed = itof(AVSpeed) / 8.0;
4118	for (A = tid ? FindMobjFromTID(tid, none) : Activator; A;
4119		A = FindMobjFromTID(tid, A))
4120	{
4121		Entity Targ = ForceDest;
4122		if (DestTID)
4123		{
4124			Targ = FindMobjFromTID(DestTID, none);
4125			if (!Targ)
4126			{
4127				continue;
4128			}
4129		}
4130
4131		newA = Spawn(moType, A.Origin);
4132		if (newA.SightSound)
4133		{
4134			newA.PlaySound(newA.SightSound, CHAN_VOICE);
4135		}
4136		newA.Target = EntityEx(A);	// Originator
4137		if (Targ)
4138		{
4139			TVec TargOrg = Targ.Origin + vector(0.0, 0.0, Targ.Height / 2.0);
4140			TVec Delta = TargOrg - newA.Origin;
4141			TVec TargVel = Targ.Velocity;
4142			if (!Targ.bNoGravity && Targ.WaterLevel < 3)
4143			{
4144				// If the target is subject to gravity and not underwater,
4145				// assume that it isn't moving vertically. Thanks to gravity,
4146				// even if we did consider the vertical component of the target's
4147				// velocity, we would still miss more often than not.
4148				TargVel.z = 0.0;
4149			}
4150
4151			if (Intercept && speed > 0.0 && (TargVel.x || TargVel.y || TargVel.z))
4152			{
4153				// Aiming at the target's position some time in the future
4154				// is basically just an application of the law of sines:
4155				//     a/sin(A) = b/sin(B)
4156
4157				float Dist = Length(Delta);
4158				float TargSpeed = Length(TargVel) / 35.0;
4159				float YDotX = DotProduct(-Delta, TargVel) / 35.0;
4160				float a = acos(FClamp(YDotX / TargSpeed / Dist, -1.0, 1.0));
4161				float Multiplier = (Random() - Random()) * 0.1 + 1.1;
4162				float SinB = FClamp(TargSpeed * Multiplier * sin(a) / speed, -1.0, 1.0);
4163
4164				//	Use the cross product of two of the triangle's sides to
4165				// get a rotation vector.
4166				TVec rv = CrossProduct(TargVel, Delta);
4167				//	The vector must be normalized.
4168				rv = Normalise(rv);
4169				//	Rotate around this vector.
4170				TVec AimVec = RotateVectorAroundVector(Delta, rv, asin(SinB));
4171				// And make the projectile follow that vector at the desired speed.
4172				float AimScale = speed / Dist * 35.0;
4173				newA.Velocity = AimVec * AimScale;
4174				newA.Angles.yaw = atan2(newA.Velocity.y, newA.Velocity.x);
4175			}
4176			else
4177			{
4178				newA.Angles.yaw = atan2(Delta.y, Delta.x);
4179				newA.Velocity = Normalise(Delta) * speed * 35.0;
4180			}
4181			if (newA.bSeekerMissile)
4182			{
4183				newA.Tracer = EntityEx(Targ);
4184			}
4185		}
4186		else
4187		{
4188			newA.Angles.yaw = angle;
4189			newA.Velocity.x = speed * cos(angle) * 35.0;
4190			newA.Velocity.y = speed * sin(angle) * 35.0;
4191			newA.Velocity.z = vspeed * 35.0;
4192		}
4193		newA.bDropped = true;	// Don't respawn
4194		if (gravity)
4195		{
4196			newA.bNoGravity = false;
4197			newA.Gravity = 0.125;
4198		}
4199		newA.SetTID(newtid);
4200		if (newA.bMissile)
4201		{
4202			if (newA.CheckMissileSpawn())
4203			{
4204				success = true;
4205			}
4206		}
4207		else if (!newA.TestLocation())
4208		{
4209			//	If it counts as kill, adjust total kills count
4210			if (newA.CountsAsKill())
4211			{
4212				TotalKills--;
4213			}
4214			//	Same for items.
4215			if (newA.bCountItem)
4216			{
4217				TotalItems--;
4218			}
4219			newA.Destroy();
4220		}
4221		else
4222		{
4223			success = true;
4224		}
4225		if (!tid)
4226		{
4227			break;
4228		}
4229	}
4230	return success;
4231}
4232
4233//==========================================================================
4234//
4235//	EV_ThingSpawn
4236//
4237//==========================================================================
4238
4239final bool EV_ThingSpawn(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
4240	bool fog, bool Facing, Entity Activator)
4241{
4242	int				tid;
4243	float			angle;
4244	Entity			A;
4245	EntityEx		newAct;
4246	class<EntityEx>	moType;
4247	bool			success;
4248
4249	success = false;
4250	tid = Arg1;
4251	moType = class<EntityEx>(FindClassFromScriptId(Arg2,
4252		LineSpecialGameInfo(Game).GameFilterFlag));
4253	if (!moType)
4254	{
4255		return false;
4256	}
4257	moType = class<EntityEx>(GetClassReplacement(moType));
4258	if (Level.Game.nomonsters && moType.default.bMonster)
4259	{
4260		return false;
4261	}
4262	angle = itof(Arg3) * 360.0 / 256.0;
4263	for (A = tid ? FindMobjFromTID(tid, none) : Activator; A;
4264		A = FindMobjFromTID(tid, A))
4265	{
4266		newAct = Spawn(moType, A.Origin);
4267		if (newAct.bFloatBob)
4268		{
4269			newAct.Origin.z = A.Origin.z - A.FloorZ;
4270			newAct.SetOrigin2(newAct.Origin);
4271		}
4272		if (newAct.TestLocation() == false)
4273		{
4274			// Didn't fit
4275			//	If it counts as kill, adjust total kills count
4276			if (newAct.CountsAsKill())
4277			{
4278				TotalKills--;
4279			}
4280			//	Same for items.
4281			if (newAct.bCountItem)
4282			{
4283				TotalItems--;
4284			}
4285			newAct.Destroy();
4286		}
4287		else
4288		{
4289			newAct.Angles.yaw = Facing ? A.Angles.yaw : angle;
4290			newAct.SetTID(Arg4);
4291			if (fog == true)
4292			{
4293				Spawn(TeleportFog, A.Origin + vector(0.0, 0.0,
4294					LineSpecialGameInfo(Game).TeleFogHeight));
4295			}
4296			newAct.bDropped = true;	// Don't respawn
4297			if (newAct.bFloatBob)
4298			{
4299				newAct.Special1f = newAct.Origin.z - newAct.FloorZ;
4300			}
4301			success = true;
4302		}
4303		if (!tid)
4304		{
4305			break;
4306		}
4307	}
4308	return success;
4309}
4310
4311//==========================================================================
4312//
4313//  AcsSpawnThing
4314//
4315//==========================================================================
4316
4317final int AcsSpawnThing(name Name, TVec Org, int Tid, float Angle)
4318{
4319	class EClass = FindClassLowerCase(Name);
4320	if (!EClass)
4321	{
4322		print("No such class %n", Name);
4323		return false;
4324	}
4325	class<EntityEx> EntClass = class<EntityEx>(EClass);
4326	if (!EntClass)
4327	{
4328		print("%n is not an actor class", Name);
4329		return false;
4330	}
4331
4332	EntityEx NewAct = Spawn(EntClass, Org);
4333	bool PrevPassMobj = NewAct.bPassMobj;
4334	NewAct.bPassMobj = true;
4335	if (NewAct.TestLocation() == false)
4336	{
4337		// Didn't fit
4338		//	If it counts as kill, subtract total.
4339		if (NewAct.CountsAsKill())
4340		{
4341			Level.TotalKills--;
4342		}
4343		//	The same for items.
4344		if (NewAct.bCountItem)
4345		{
4346			Level.TotalItems--;
4347		}
4348		NewAct.Destroy();
4349		return false;
4350	}
4351	NewAct.bPassMobj = PrevPassMobj;
4352
4353	NewAct.Angles.yaw = Angle;
4354	NewAct.SetTID(Tid);
4355	NewAct.bDropped = true;	// Don't respawn
4356	return true;
4357}
4358
4359//==========================================================================
4360//
4361//  AcsSpawnSpot
4362//
4363//==========================================================================
4364
4365final int AcsSpawnSpot(name Name, int SpotTid, int Tid, float Angle)
4366{
4367	int NumSpawned = 0;
4368	Entity A;
4369	for (A = FindMobjFromTID(SpotTid, none); A;
4370		A = FindMobjFromTID(SpotTid, A))
4371	{
4372		NumSpawned += AcsSpawnThing(Name, A.Origin, Tid, Angle);
4373	}
4374	return NumSpawned;
4375}
4376
4377//==========================================================================
4378//
4379//  AcsSpawnSpotFacing
4380//
4381//==========================================================================
4382
4383final int AcsSpawnSpotFacing(name Name, int SpotTid, int Tid)
4384{
4385	int NumSpawned = 0;
4386	Entity A;
4387	for (A = FindMobjFromTID(SpotTid, none); A;
4388		A = FindMobjFromTID(SpotTid, A))
4389	{
4390		NumSpawned += AcsSpawnThing(Name, A.Origin, Tid, A.Angles.yaw);
4391	}
4392	return NumSpawned;
4393}
4394
4395//==========================================================================
4396//
4397//	EV_ThingActivate
4398//
4399//==========================================================================
4400
4401final bool EV_ThingActivate(int tid, EntityEx Activator)
4402{
4403	Entity	A;
4404	bool	success;
4405
4406	success = false;
4407	for (A = FindMobjFromTID(tid, none); A; A = FindMobjFromTID(tid, A))
4408	{
4409		if (EntityEx(A).Activate(Activator))
4410		{
4411			success = true;
4412		}
4413	}
4414	return success;
4415}
4416
4417//==========================================================================
4418//
4419//	EV_ThingDeactivate
4420//
4421//==========================================================================
4422
4423final bool EV_ThingDeactivate(int tid, EntityEx Activator)
4424{
4425	Entity	A;
4426	bool	success;
4427
4428	success = false;
4429	for (A = FindMobjFromTID(tid, none); A; A = FindMobjFromTID(tid, A))
4430	{
4431		if (EntityEx(A).Deactivate(Activator))
4432		{
4433			success = true;
4434		}
4435	}
4436	return success;
4437}
4438
4439//==========================================================================
4440//
4441//	EV_ThingRemove
4442//
4443//==========================================================================
4444
4445final bool EV_ThingRemove(int tid)
4446{
4447	Entity	A;
4448	bool	success;
4449
4450	success = false;
4451	for (A = FindMobjFromTID(tid, none); A; A = FindMobjFromTID(tid, A))
4452	{
4453		//	Be friendly to level statistics.
4454		if (EntityEx(A).CountsAsKill() && A.Health > 0)
4455		{
4456			TotalKills--;
4457		}
4458		if (EntityEx(A).bCountItem)
4459		{
4460			TotalItems--;
4461		}
4462		A.RemoveThing();
4463		success = true;
4464	}
4465	return success;
4466}
4467
4468//==========================================================================
4469//
4470//	EV_ThingDestroy
4471//
4472//==========================================================================
4473
4474final bool EV_ThingDestroy(int tid, int Extreme)
4475{
4476	EntityEx	A;
4477	bool		success;
4478
4479	success = false;
4480	for (A = EntityEx(FindMobjFromTID(tid, none)); A;
4481		A = EntityEx(FindMobjFromTID(tid, A)))
4482	{
4483		if (A.bShootable)
4484		{
4485			A.Damage(none, none, Extreme ? 1000000 : A.Health, '');
4486			success = true;
4487		}
4488	}
4489	return success;
4490}
4491
4492//===========================================================================
4493//
4494//	EV_HealThing
4495//
4496//===========================================================================
4497
4498final bool EV_HealThing(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
4499	EntityEx A)
4500{
4501	if (A)
4502	{
4503		int Max = Arg2;
4504
4505		if (Max == 0 || !A.bIsPlayer)
4506		{
4507			A.GiveBody(Arg1);
4508			return true;
4509		}
4510		else if (Max == 1)
4511		{
4512			Max = 200;
4513			class<Health> SClass = class<Health>(FindClass('Soulsphere'));
4514			if (SClass)
4515			{
4516				Max = SClass.default.MaxAmount;
4517			}
4518		}
4519
4520		//	If health is already above max, do nothing
4521		if (A.Health < Max)
4522		{
4523			A.Health += Arg1;
4524			if (A.Health > Max && Max > 0)
4525			{
4526				A.Health = Max;
4527			}
4528			A.Player.Health = A.Health;
4529		}
4530	}
4531
4532	return !!A;
4533}
4534
4535//==========================================================================
4536//
4537//	EV_ThingDamage
4538//
4539//==========================================================================
4540
4541final int EV_ThingDamage(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
4542	Entity Activator)
4543{
4544	return DoThingDamage(Arg1, Arg2, ModToDamageType(Arg3), Activator);
4545}
4546
4547//==========================================================================
4548//
4549//	DoThingDamage
4550//
4551//==========================================================================
4552
4553final int DoThingDamage(int Tid, int Amount, name DmgType, Entity Activator)
4554{
4555	Entity	A;
4556	int		NumDamaged;
4557
4558	NumDamaged = 0;
4559	for (A = Tid ? FindMobjFromTID(Tid, none) : Activator; A;
4560		A = FindMobjFromTID(Tid, A))
4561	{
4562		if (EntityEx(A).bShootable)
4563		{
4564			if (Amount > 0)
4565			{
4566				EntityEx(A).Damage(none, EntityEx(Activator), Amount, DmgType);
4567			}
4568			else if (A.Health < A.default.Health)
4569			{
4570				//	Negative amount heals thing.
4571				A.Health -= Amount;
4572				if (A.Health > A.default.Health)
4573				{
4574					A.Health = A.default.Health;
4575				}
4576				if (A.bIsPlayer)
4577				{
4578					A.Player.Health = A.Health;
4579				}
4580			}
4581			NumDamaged++;
4582		}
4583		if (!Tid)
4584		{
4585			break;
4586		}
4587	}
4588	return NumDamaged;
4589}
4590
4591//===========================================================================
4592//
4593//	EV_ThingSetGoal
4594//
4595//===========================================================================
4596
4597final bool EV_ThingSetGoal(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
4598{
4599	Entity Ent;
4600	EntityEx NewGoal = none;
4601	for (Ent = FindMobjFromTID(Arg2, none); Ent;
4602		Ent = FindMobjFromTID(Arg2, Ent))
4603	{
4604		if (!Ent.IsA('PatrolPoint'))
4605		{
4606			continue;
4607		}
4608		NewGoal = EntityEx(Ent);
4609		break;
4610	}
4611
4612	bool Ret = false;
4613	for (Ent = FindMobjFromTID(Arg1, none); Ent;
4614		Ent = FindMobjFromTID(Arg1, Ent))
4615	{
4616		EntityEx E = EntityEx(Ent);
4617		Ret = true;
4618		if (E.bShootable)
4619		{
4620			E.Goal = NewGoal;
4621			E.bChaseGoal = !!Arg4;
4622			if (!E.Target)
4623			{
4624				E.ReactionTime = itof(Arg3);
4625			}
4626		}
4627	}
4628	return Ret;
4629}
4630
4631//===========================================================================
4632//
4633//	EV_ChangeCamera
4634//
4635//===========================================================================
4636
4637final bool EV_ChangeCamera(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
4638	EntityEx A)
4639{
4640	Entity Camera = none;
4641	if (Arg1)
4642	{
4643		Camera = FindMobjFromTID(Arg1, none);
4644	}
4645
4646	if (!A || !A.Player || Arg2)
4647	{
4648		BasePlayer P;
4649		foreach AllActivePlayers(P)
4650		{
4651			if (Camera)
4652			{
4653				P.Camera = Camera;
4654				if (Arg3)
4655				{
4656					PlayerEx(P).bRevertCamera = true;
4657				}
4658			}
4659			else
4660			{
4661				P.Camera = P.MO;
4662				PlayerEx(P).bRevertCamera = false;
4663			}
4664		}
4665	}
4666	else
4667	{
4668		if (Camera)
4669		{
4670			A.Player.Camera = Camera;
4671			if (Arg3)
4672			{
4673				PlayerEx(A.Player).bRevertCamera = true;
4674			}
4675		}
4676		else
4677		{
4678			A.Player.Camera = A;
4679			PlayerEx(A.Player).bRevertCamera = false;
4680		}
4681	}
4682	return true;
4683}
4684
4685//===========================================================================
4686//
4687//	EV_ThingSetTranslation
4688//
4689//===========================================================================
4690
4691final bool EV_ThingSetTranslation(int Arg1, int Arg2, int Arg3, int Arg4,
4692	int Arg5, EntityEx A)
4693{
4694	int Trans;
4695	if (Arg2 == -1 && A)
4696	{
4697		Trans = A.Translation;
4698	}
4699	else if (Arg2 >= 1 && Arg2 < Level::MAX_LEVEL_TRANSLATIONS)
4700	{
4701		Trans = (Entity::TRANSL_Level << Entity::TRANSL_TYPE_SHIFT) +
4702			Arg2 - 1;
4703	}
4704	else
4705	{
4706		Trans = 0;
4707	}
4708
4709	bool Ret = false;
4710	if (Arg1)
4711	{
4712		Entity Ent;
4713		for (Ent = FindMobjFromTID(Arg1, none); Ent;
4714			Ent = FindMobjFromTID(Arg1, Ent))
4715		{
4716			Ret = true;
4717			Ent.Translation = Trans ? Trans : Ent.default.Translation;
4718		}
4719	}
4720	else if (A)
4721	{
4722		Ret = true;
4723		A.Translation = Trans ? Trans : A.default.Translation;
4724	}
4725	return Ret;
4726}
4727
4728//===========================================================================
4729//
4730//	EV_ThingHate
4731//
4732//===========================================================================
4733
4734final bool EV_ThingHate(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
4735	EntityEx A)
4736{
4737	EntityEx Hatee = none;
4738	bool NothingToHate = false;
4739	if (Arg2)
4740	{
4741		for (Hatee = EntityEx(FindMobjFromTID(Arg2, none)); Hatee;
4742			Hatee = EntityEx(FindMobjFromTID(Arg2, Hatee)))
4743		{
4744			if (Hatee.bShootable &&	// can't hate nonshootable things
4745				Hatee.Health > 0 &&	// can't hate dead things
4746				!Hatee.bDormant)	// can't target dormant things
4747			{
4748				break;
4749			}
4750		}
4751		if (!Hatee)
4752		{
4753			// Nothing to hate
4754			NothingToHate = true;
4755		}
4756	}
4757
4758	EntityEx Hater;
4759	if (Arg1 == 0)
4760	{
4761		if (A && A.bIsPlayer)
4762		{
4763			// Players cannot have their attitudes set
4764			return false;
4765		}
4766		else
4767		{
4768			Hater = A;
4769		}
4770	}
4771	else
4772	{
4773		for (Hater = EntityEx(FindMobjFromTID(Arg1, none)); Hater;
4774			Hater = EntityEx(FindMobjFromTID(Arg1, Hater)))
4775		{
4776			if (Hater.Health > 0 && Hater.bShootable)
4777			{
4778				break;
4779			}
4780		}
4781	}
4782
4783	while (Hater)
4784	{
4785		// Can't hate if can't attack.
4786		if (Hater.SeeState)
4787		{
4788			// If hating a group of things, record the TID and NULL
4789			// the target (if its TID doesn't match). A_Look will
4790			// find an appropriate thing to go chase after.
4791			if (Arg3)
4792			{
4793				Hater.TIDToHate = Arg2;
4794				Hater.LastLookActor = none;
4795
4796				// If the TID to hate is 0, then don't forget the target and
4797				// lastenemy fields.
4798				if (Arg2)
4799				{
4800					if (Hater.Target && Hater.Target.TID != Arg2)
4801					{
4802						Hater.Target = none;
4803					}
4804					if (Hater.LastEnemy && Hater.LastEnemy.TID != Arg2)
4805					{
4806						Hater.LastEnemy = none;
4807					}
4808				}
4809			}
4810			// Hate types for Arg3:
4811			//
4812			// 0 - Just hate one specific actor
4813			// 1 - Hate actors with given TID and attack players when shot
4814			// 2 - Same as 1, but will go after enemies without seeing them first
4815			// 3 - Hunt actors with given TID and also players
4816			// 4 - Same as 3, but will go after monsters without seeing them first
4817			// 5 - Hate actors with given TID and ignore player attacks
4818			// 6 - Same as 5, but will go after enemies without seeing them first
4819
4820			// Note here: If you use Thing_Hate (tid, 0, 2), you can make
4821			// a monster go after a player without seeing him first.
4822			Hater.bNoSightCheck = Arg3 == 2 || Arg3 == 4 || Arg3 == 6;
4823			Hater.bHuntPlayers = Arg3 == 3 || Arg3 == 4;
4824			Hater.bNoHatePlayers = Arg3 == 5 || Arg3 == 6;
4825
4826			if (Arg2 == 0)
4827			{
4828				Hatee = A;
4829			}
4830			else if (NothingToHate)
4831			{
4832				Hatee = none;
4833			}
4834			else if (Arg3)
4835			{
4836				do
4837				{
4838					Hatee = EntityEx(FindMobjFromTID(Arg2, Hatee));
4839				}
4840				while (!Hatee ||
4841					Hatee == Hater ||		// can't hate self
4842					!Hatee.bShootable ||	// can't hate nonshootable things
4843					Hatee.Health <= 0 ||	// can't hate dead things
4844					Hatee.bDormant);
4845			}
4846
4847			if (Hatee && Hatee != Hater && (Arg3 == 0 || (Hater.Goal && Hater.Target != Hater.Goal)))
4848			{
4849				if (Hater.Target)
4850				{
4851					Hater.LastEnemy = Hater.Target;
4852				}
4853				Hater.Target = Hatee;
4854				if (!Hater.bDormant)
4855				{
4856					if (Hater.Health > 0)
4857					{
4858						Hater.SetState(Hater.SeeState);
4859					}
4860				}
4861			}
4862		}
4863		if (Arg1)
4864		{
4865			for (Hater = EntityEx(FindMobjFromTID(Arg1, Hater)); Hater;
4866				Hater = EntityEx(FindMobjFromTID(Arg1, Hater)))
4867			{
4868				if (Hater.Health > 0 && Hater.bShootable)
4869				{
4870					break;
4871				}
4872			}
4873		}
4874		else
4875		{
4876			Hater = none;
4877		}
4878	}
4879	return true;
4880}
4881
4882//===========================================================================
4883//
4884//	EV_ThingMove
4885//
4886//===========================================================================
4887
4888final bool EV_ThingMove(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
4889	EntityEx A)
4890{
4891	if (Arg1)
4892	{
4893		A = EntityEx(FindMobjFromTID(Arg1, none));
4894	}
4895
4896	EntityEx Dest = EntityEx(FindMobjFromTID(Arg2, none));
4897
4898	if (A && Dest)
4899	{
4900		TVec OldOrg = A.Origin;
4901		A.SetOrigin(Dest.Origin);
4902		if (A.TestLocation())
4903		{
4904			if (Arg3)
4905			{
4906				Spawn(TeleportFog, OldOrg + vector(0.0, 0.0,
4907					LineSpecialGameInfo(Game).TeleFogHeight));
4908				Spawn(TeleportFog, A.Origin + vector(0.0, 0.0,
4909					LineSpecialGameInfo(Game).TeleFogHeight));
4910			}
4911			return true;
4912		}
4913		else
4914		{
4915			A.SetOrigin(OldOrg);
4916			return false;
4917		}
4918	}
4919	return false;
4920}
4921
4922//===========================================================================
4923//
4924//	EV_ThingSetSpecial
4925//
4926//	It's recomended to use SetThingSpecial ACS command that can set all 5
4927// arguments.
4928//
4929//===========================================================================
4930
4931final bool EV_ThingSetSpecial(int Arg1, int Arg2, int Arg3, int Arg4,
4932	int Arg5, EntityEx A)
4933{
4934	if (Arg1)
4935	{
4936		Entity Ent;
4937		for (Ent = FindMobjFromTID(Arg1, none); Ent;
4938			Ent = FindMobjFromTID(Arg1, Ent))
4939		{
4940			Ent.Special = Arg2;
4941			Ent.Args[0] = Arg3;
4942			Ent.Args[1] = Arg4;
4943			Ent.Args[2] = Arg5;
4944		}
4945	}
4946	else if (A)
4947	{
4948		A.Special = Arg2;
4949		A.Args[0] = Arg3;
4950		A.Args[1] = Arg4;
4951		A.Args[2] = Arg5;
4952	}
4953	return true;
4954}
4955
4956//===========================================================================
4957//
4958//	EV_ThrustThingZ
4959//
4960//===========================================================================
4961
4962final bool EV_ThrustThingZ(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
4963	EntityEx A)
4964{
4965	float Force = itof(Arg2) * 35.0 / 4.0;
4966	if (Arg3)
4967	{
4968		Force = -Force;
4969	}
4970
4971	if (Arg1)
4972	{
4973		Entity Ent;
4974		for (Ent = FindMobjFromTID(Arg1, none); Ent;
4975			Ent = FindMobjFromTID(Arg1, Ent))
4976		{
4977			if (!Arg4)
4978			{
4979				Ent.Velocity.z = Force;
4980			}
4981			else
4982			{
4983				Ent.Velocity.z += Force;
4984			}
4985		}
4986		return true;
4987	}
4988	if (A)
4989	{
4990		if (!Arg4)
4991		{
4992			A.Velocity.z = Force;
4993		}
4994		else
4995		{
4996			A.Velocity.z += Force;
4997		}
4998		return true;
4999	}
5000	return false;
5001}
5002
5003//===========================================================================
5004//
5005//	EV_ThingChangeTID
5006//
5007//===========================================================================
5008
5009final bool EV_ThingChangeTID(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
5010	EntityEx A)
5011{
5012	if (!Arg1)
5013	{
5014		if (A)
5015		{
5016			A.SetTID(Arg2);
5017		}
5018	}
5019	else
5020	{
5021		Entity Ent;
5022		for (Ent = FindMobjFromTID(Arg1, none); Ent;
5023			Ent = FindMobjFromTID(Arg1, Ent))
5024		{
5025			Ent.SetTID(Arg2);
5026		}
5027	}
5028	return true;
5029}
5030
5031//===========================================================================
5032//
5033//	EV_ThingStop
5034//
5035//===========================================================================
5036
5037final bool EV_ThingStop(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
5038	EntityEx A)
5039{
5040	bool Ret = false;
5041	if (!Arg1)
5042	{
5043		if (A)
5044		{
5045			A.Velocity = vector(0.0, 0.0, 0.0);
5046			Ret = true;
5047		}
5048	}
5049	else
5050	{
5051		Entity Ent;
5052		for (Ent = FindMobjFromTID(Arg1, none); Ent;
5053			Ent = FindMobjFromTID(Arg1, Ent))
5054		{
5055			Ent.Velocity = vector(0.0, 0.0, 0.0);
5056			Ret = true;
5057		}
5058	}
5059	return Ret;
5060}
5061
5062//===========================================================================
5063//
5064//	EV_SetPlayerProperty
5065//
5066//===========================================================================
5067
5068final bool EV_SetPlayerProperty(int Arg1, int Arg2, int Arg3, int Arg4,
5069	int Arg5, EntityEx A)
5070{
5071	if (!Arg1)
5072	{
5073		if (!A || !A.bIsPlayer)
5074		{
5075			return false;
5076		}
5077		return A.SetPlayerProperty(Arg3, Arg2);
5078	}
5079	else
5080	{
5081		bool Ret = false;
5082		int i;
5083		for (i = 0; i < MAXPLAYERS; i++)
5084		{
5085			if (!Game.Players[i] || !Game.Players[i].bSpawned)
5086			{
5087				continue;
5088			}
5089			if (EntityEx(Game.Players[i].MO).SetPlayerProperty(Arg3, Arg2))
5090			{
5091				Ret = true;
5092			}
5093		}
5094		return Ret;
5095	}
5096}
5097
5098//===========================================================================
5099//
5100//	EV_ThingRaise
5101//
5102//===========================================================================
5103
5104final bool EV_ThingRaise(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
5105	EntityEx A)
5106{
5107	bool Ret = false;
5108	if (!Arg1)
5109	{
5110		if (A)
5111		{
5112			Ret = DoThingRaise(A);
5113		}
5114	}
5115	else
5116	{
5117		Entity Ent;
5118		for (Ent = FindMobjFromTID(Arg1, none); Ent;
5119			Ent = FindMobjFromTID(Arg1, Ent))
5120		{
5121			if (DoThingRaise(EntityEx(Ent)))
5122			{
5123				Ret = true;
5124			}
5125		}
5126	}
5127	return true;
5128}
5129
5130//===========================================================================
5131//
5132//	DoThingRaise
5133//
5134//===========================================================================
5135
5136final bool DoThingRaise(EntityEx A)
5137{
5138	if (!A.DoThingRaise())
5139	{
5140		return false;
5141	}
5142	return true;
5143}
5144
5145//**************************************************************************
5146//
5147//	Force field
5148//
5149//**************************************************************************
5150
5151//==========================================================================
5152//
5153//  EV_ForceField
5154//
5155//==========================================================================
5156
5157final bool EV_ForceField(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
5158	EntityEx A)
5159{
5160	A.Damage(none, none, 16, '');
5161	A.Velocity.x += 7.8125 * cos(A.Angles.yaw + 180.0) * 35.0;
5162	A.Velocity.y += 7.8125 * sin(A.Angles.yaw + 180.0) * 35.0;
5163	return true;
5164}
5165
5166//==========================================================================
5167//
5168//  EV_RemoveForceField
5169//
5170//==========================================================================
5171
5172final bool EV_RemoveForceField(int Arg1, int Arg2, int Arg3, int Arg4,
5173	int Arg5)
5174{
5175	int			i;
5176	int			secnum;
5177	sector_t*	sec;
5178	line_t*		secline;
5179
5180	for (secnum = XLevel.FindSectorFromTag(Arg1, -1); secnum >= 0;
5181		secnum = XLevel.FindSectorFromTag(Arg1, secnum))
5182	{
5183		sec = &XLevel.Sectors[secnum];
5184		for (i = 0; i < sec->linecount; i++)
5185		{
5186			secline = sec->lines[i];
5187			if (secline->special != LNSPEC_ForceField)
5188				continue;
5189			if (!(secline->flags & ML_TWOSIDED))
5190				continue;
5191
5192			XLevel.Sides[secline->sidenum[0]].MidTexture = 0;
5193			XLevel.Sides[secline->sidenum[1]].MidTexture = 0;
5194			secline->special = 0;
5195			secline->flags &= ~ML_BLOCKING;
5196		}
5197	}
5198	return true;
5199}
5200
5201//**************************************************************************
5202//
5203//  Teleportation
5204//
5205//**************************************************************************
5206
5207//==========================================================================
5208//
5209//	EV_Teleport
5210//
5211//==========================================================================
5212
5213final bool EV_Teleport(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
5214	EntityEx thing, line_t* Line, bool fog)
5215{
5216	int			i;
5217	int			count;
5218	EntityEx	A;
5219	int			SecTag;
5220	int			SecNum;
5221	sector_t*	Sec;
5222	bool		SrcFog;
5223	bool		KeepOrient;
5224	TVec		DstOrg;
5225	float		Angle;
5226	TVec		OldVel;
5227
5228	if (!thing)
5229	{
5230		// Teleport function called with an invalid mobj
5231		return false;
5232	}
5233	if (thing.bNoTeleport)
5234	{
5235		return false;
5236	}
5237
5238	A = none;
5239	if (fog)
5240	{
5241		SecTag = Arg2;
5242		SrcFog = !Arg3;
5243		KeepOrient = false;
5244	}
5245	else
5246	{
5247		SecTag = Arg3;
5248		SrcFog = false;
5249		KeepOrient = !Arg2;
5250	}
5251	if (Arg1)
5252	{
5253		count = 0;
5254		for (A = EntityEx(FindMobjFromTID(Arg1, none)); A;
5255			A = EntityEx(FindMobjFromTID(Arg1, A)))
5256		{
5257			if (SecTag == 0 || A.Sector->tag == SecTag)
5258			{
5259				count++;
5260			}
5261		}
5262		if (count == 0)
5263		{
5264			return false;
5265		}
5266		count = 1 + (P_Random() % count);
5267		A = none;
5268		for (i = 0; i < count; i++)
5269		{
5270			do
5271			{
5272				A = EntityEx(FindMobjFromTID(Arg1, A));
5273			}
5274			while (A && SecTag != 0 && A.Sector->tag != SecTag);
5275		}
5276	}
5277	else if (SecTag)
5278	{
5279		for (SecNum = XLevel.FindSectorFromTag(SecTag, -1); SecNum >= 0;
5280			SecNum = XLevel.FindSectorFromTag(SecTag, SecNum))
5281		{
5282			Sec = &XLevel.Sectors[SecNum];
5283			foreach AllThinkers(EntityEx, A)
5284			{
5285				if (!TeleportDest(A))
5286				{
5287					//	Not a teleportman
5288					continue;
5289				}
5290				if (A.Sector != Sec)
5291				{
5292					//	Wrong sector
5293					continue;
5294				}
5295				break;
5296			}
5297			if (A)
5298			{
5299				break;
5300			}
5301		}
5302	}
5303	if (!A)
5304	{
5305		return false;
5306	}
5307
5308	DstOrg = A.Origin;
5309	// Lee Killough's changes for silent teleporters from BOOM
5310	if (KeepOrient && Line)
5311	{
5312		// Get the angle between the exit thing and source linedef.
5313		// Rotate 180 degrees, so that walking perpendicularly across
5314		// teleporter linedef causes thing to exit in the direction
5315		// indicated by the exit thing.
5316		Angle = atan2(Line->normal.y, Line->normal.x) - A.Angles.yaw + 180.0;
5317
5318		// Momentum of thing crossing teleporter linedef
5319		OldVel = thing.Velocity;
5320	}
5321	if (!TeleportDest2(A))
5322	{
5323		DstOrg.z = EntityEx::ONFLOORZ;
5324	}
5325	if (thing.Teleport(DstOrg, A.Angles.yaw, fog, SrcFog, KeepOrient))
5326	{
5327		// Lee Killough's changes for silent teleporters from BOOM
5328		if (!fog && Line && KeepOrient)
5329		{
5330			// Rotate thing according to difference in angles
5331			thing.Angles.yaw = AngleMod360(thing.Angles.yaw + Angle);
5332
5333			// Rotate thing's momentum to come out of exit just like it entered
5334			thing.Velocity.x = OldVel.x * cos(Angle) - OldVel.y * sin(Angle);
5335			thing.Velocity.y = OldVel.y * cos(Angle) + OldVel.x * sin(Angle);
5336		}
5337		return true;
5338	}
5339	return false;
5340}
5341
5342//==========================================================================
5343//
5344//	EV_TeleportOther
5345//
5346//	Teleport anything matching other_tid to dest_tid
5347//
5348//==========================================================================
5349
5350final bool EV_TeleportOther(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
5351{
5352	bool		Ret;
5353	EntityEx	A;
5354
5355	Ret = false;
5356	if (Arg1 && Arg2)
5357	{
5358		for (A = EntityEx(FindMobjFromTID(Arg1, none)); A;
5359			A = EntityEx(FindMobjFromTID(Arg1, A)))
5360		{
5361			Ret |= EV_Teleport(Arg2, 0, 0, 0, 0, A, NULL, !!Arg3);
5362		}
5363	}
5364	return Ret;
5365}
5366
5367//==========================================================================
5368//
5369//	DoGroupForOne
5370//
5371//==========================================================================
5372
5373final bool DoGroupForOne(EntityEx victim, EntityEx source, EntityEx dest,
5374	bool floorz, bool fog)
5375{
5376	float an = dest.Angles.yaw - source.Angles.yaw;
5377	float offX = victim.Origin.x - source.Origin.x;
5378	float offY = victim.Origin.y - source.Origin.y;
5379	float offAngle = victim.Angles.yaw - source.Angles.yaw;
5380	float newX = offX * cos(an) - offY * sin(an);
5381	float newY = offX * sin(an) + offY * cos(an);
5382	bool res;
5383
5384	res = victim.Teleport(vector(dest.Origin.x + newX,
5385		dest.Origin.y + newY, floorz ? EntityEx::ONFLOORZ : dest.Origin.z +
5386		victim.Origin.z - source.Origin.z), 0.0, fog, fog, !fog);
5387	// P_Teleport only changes angle if fog is true
5388	victim.Angles.yaw = AngleMod360(dest.Angles.yaw + offAngle);
5389
5390	return res;
5391}
5392
5393//==========================================================================
5394//
5395//	EV_TeleportGroup
5396//
5397//	[RH] Teleport a group of actors centred around source_tid so
5398// that they become centred around dest_tid instead.
5399//
5400//==========================================================================
5401
5402final bool EV_TeleportGroup(int group_tid, int source_tid, int dest_tid,
5403	bool moveSource, bool fog, EntityEx victim)
5404{
5405	EntityEx	sourceOrigin;
5406	EntityEx	destOrigin;
5407	bool		didSomething;
5408	bool		floorz;
5409
5410	sourceOrigin = EntityEx(FindMobjFromTID(source_tid, none));
5411	if (!sourceOrigin)
5412	{
5413		//	If there is no source origin, behave like TeleportOther
5414		return EV_TeleportOther(group_tid, dest_tid, fog, 0, 0);
5415	}
5416
5417	destOrigin = none;
5418	do
5419	{
5420		destOrigin = EntityEx(FindMobjFromTID(dest_tid, destOrigin));
5421	}
5422	while (destOrigin && !TeleportDest(destOrigin));
5423	if (!destOrigin)
5424	{
5425		return false;
5426	}
5427
5428	didSomething = false;
5429	floorz = !TeleportDest2(destOrigin);
5430
5431	// Use the passed victim if group_tid is 0
5432	if (group_tid == 0 && victim)
5433	{
5434		didSomething = DoGroupForOne(victim, sourceOrigin, destOrigin,
5435			floorz, fog);
5436	}
5437	else
5438	{
5439		// For each actor with tid matching arg0, move it to the same
5440		// position relative to destOrigin as it is relative to
5441		// sourceOrigin before the teleport.
5442		for (victim = EntityEx(FindMobjFromTID(group_tid, none)); victim;
5443			victim = EntityEx(FindMobjFromTID(group_tid, victim)))
5444		{
5445			didSomething |= DoGroupForOne(victim, sourceOrigin, destOrigin,
5446				floorz, fog);
5447		}
5448	}
5449
5450	if (moveSource && didSomething)
5451	{
5452		didSomething |= sourceOrigin.Teleport(vector(destOrigin.Origin.x,
5453			destOrigin.Origin.y, floorz ? EntityEx::ONFLOORZ : destOrigin.Origin.z),
5454			0.0, false, false, true);
5455		sourceOrigin.Angles.yaw = destOrigin.Angles.yaw;
5456	}
5457
5458	return didSomething;
5459}
5460
5461//==========================================================================
5462//
5463//	EV_TeleportSector
5464//
5465//	[RH] Teleport a group of actors in a sector. Source_tid is used as a
5466// reference point so that they end up in the same position relative to
5467// dest_tid. Group_tid can be used to not teleport all actors in the sector.
5468//
5469//==========================================================================
5470
5471final bool EV_TeleportSector(int tag, int source_tid, int dest_tid, bool fog,
5472	int group_tid)
5473{
5474	EntityEx	sourceOrigin;
5475	EntityEx	destOrigin;
5476	bool		didSomething;
5477	bool		floorz;
5478	int			secnum;
5479	sector_t*	sec;
5480	EntityEx	A;
5481
5482	sourceOrigin = EntityEx(FindMobjFromTID(source_tid, none));
5483	if (!sourceOrigin)
5484	{
5485		return false;
5486	}
5487
5488	destOrigin = none;
5489	do
5490	{
5491		destOrigin = EntityEx(FindMobjFromTID(dest_tid, destOrigin));
5492	}
5493	while (destOrigin && !TeleportDest(destOrigin));
5494	if (!destOrigin)
5495	{
5496		return false;
5497	}
5498
5499	didSomething = false;
5500	floorz = !TeleportDest2(destOrigin);
5501
5502	for (secnum = XLevel.FindSectorFromTag(tag, -1); secnum >= 0;
5503		secnum = XLevel.FindSectorFromTag(tag, secnum))
5504	{
5505		sec = &XLevel.Sectors[secnum];
5506
5507		msecnode_t*Node = sec->TouchingThingList;
5508		while (Node)
5509		{
5510			A = EntityEx(Node->Thing);
5511			msecnode_t*Next = Node->SNext;
5512			// possibly limit actors by group
5513			if (A.Sector == sec && (group_tid == 0 || A.TID == group_tid))
5514			{
5515				didSomething |= DoGroupForOne(A, sourceOrigin, destOrigin,
5516					floorz, fog);
5517			}
5518			Node = Next;
5519		}
5520	}
5521	return didSomething;
5522}
5523
5524//==========================================================================
5525//
5526//	EV_SilentLineTeleport
5527//
5528// Silent linedef-based TELEPORTATION, by Lee Killough
5529// Primarily for rooms-over-rooms etc.
5530// This is the complete player-preserving kind of teleporter.
5531// It has advantages over the teleporter with thing exits.
5532//
5533// [RH] Modified to support different source and destination ids.
5534//
5535//==========================================================================
5536
5537final bool EV_SilentLineTeleport(line_t *line, int side, EntityEx thing,
5538	int id, bool reverse)
5539{
5540	int searcher;
5541	line_t *l;
5542
5543	if (side || thing.bNoTeleport || !line)
5544	{
5545		return false;
5546	}
5547
5548	searcher = -1;
5549	for (l = XLevel.FindLine(id, &searcher); l;
5550		l = XLevel.FindLine(id, &searcher))
5551	{
5552		TVec SrcXAxis;
5553		TVec SrcYAxis;
5554		TVec DstXAxis;
5555		TVec DstYAxis;
5556		TVec newPos;
5557		TVec TempV;
5558		TAVec TempA;
5559		float pos;
5560		float TempX;
5561		float TempY;
5562		float oldZ;
5563
5564		if (l == line || !l->backsector)
5565		{
5566			continue;
5567		}
5568
5569		// Get the thing's position along the source linedef
5570		SrcXAxis = Normalise(*line->v2 - *line->v1);
5571		SrcYAxis = -line->normal;
5572		pos = DotProduct(SrcXAxis, thing.Origin - *line->v1);
5573		oldZ = thing.Origin.z;
5574
5575		// Interpolate position across the exit linedef
5576		if (reverse)
5577		{
5578			DstXAxis = Normalise(*l->v2 - *l->v1);
5579			DstYAxis = -l->normal;
5580			newPos = *l->v1 + pos * DstXAxis;
5581			newPos.z = thing.Origin.z - GetPlanePointZ(
5582				line->frontsector->botregion->floor, thing.Origin) +
5583				GetPlanePointZ(l->frontsector->botregion->floor, newPos);
5584		}
5585		else
5586		{
5587			DstXAxis = Normalise(*l->v1 - *l->v2);
5588			DstYAxis = l->normal;
5589			newPos = *l->v2 + pos * DstXAxis;
5590			newPos.z = thing.Origin.z - GetPlanePointZ(
5591				line->frontsector->botregion->floor, thing.Origin) +
5592				GetPlanePointZ(l->backsector->botregion->floor, newPos);
5593		}
5594
5595		// Attempt to teleport, aborting if blocked
5596		if (!thing.TeleportMove(newPos))
5597		{
5598			return false;
5599		}
5600
5601		// Rotate thing's orientation according to difference in linedef angles
5602		TempV.x = DotProduct(DstXAxis, SrcXAxis);
5603		TempV.y = DotProduct(DstYAxis, SrcXAxis);
5604		TempV.z = 0.0;
5605		VectorAngles(&TempV, &TempA);
5606		thing.Angles.yaw = AngleMod360(thing.Angles.yaw - TempA.yaw);
5607
5608		// Rotate thing's momentum to come out of exit just like it entered
5609		TempX = DotProduct(thing.Velocity, SrcXAxis);
5610		TempY = DotProduct(thing.Velocity, SrcYAxis);
5611		thing.Velocity.x = TempX * DstXAxis.x + TempY * DstYAxis.x;
5612		thing.Velocity.y = TempX * DstXAxis.y + TempY * DstYAxis.y;
5613
5614		// Adjust a player's view, in case there has been a height change
5615		if (thing.bIsPlayer && thing.Player.MO == thing)
5616		{
5617			thing.Player.ViewOrg.z += thing.Origin.z - oldZ;
5618
5619			thing.Player.bFixAngle = true;
5620		}
5621		return true;
5622	}
5623	return false;
5624}
5625
5626//**************************************************************************
5627//
5628//	Noise alert
5629//
5630//**************************************************************************
5631
5632//==========================================================================
5633//
5634//  RecursiveSound
5635//
5636//  Called by NoiseAlert. Recursively traverse adjacent sectors, sound
5637// blocking lines cut off traversal.
5638//
5639//==========================================================================
5640
5641final void RecursiveSound(sector_t* sec, int soundblocks, Entity soundtarget,
5642			bool Splash)
5643{
5644	int			i;
5645	line_t*		check;
5646	sector_t*	other;
5647	Entity		Ent;
5648
5649	// wake up all monsters in this sector
5650	if (sec->validcount == *Game.validcount &&
5651		sec->soundtraversed <= soundblocks + 1)
5652	{
5653		return;	// already flooded
5654	}
5655
5656	sec->validcount = *Game.validcount;
5657	sec->soundtraversed = soundblocks + 1;
5658	sec->SoundTarget = soundtarget;
5659
5660	for (Ent = sec->ThingList; Ent; Ent = Ent.SNext)
5661	{
5662		if (Ent != soundtarget && (!Splash || !EntityEx(Ent).bNoSplashAlert))
5663		{
5664			EntityEx(Ent).LastHeard = EntityEx(soundtarget);
5665		}
5666	}
5667
5668	for (i = 0; i < sec->linecount; i++)
5669	{
5670		check = sec->lines[i];
5671		if (check->sidenum[1] == -1 || !(check->flags & ML_TWOSIDED))
5672		{
5673			continue;
5674		}
5675
5676		// Early out for intra-sector lines
5677		if (check->frontsector == check->backsector)
5678		{
5679			continue;
5680		}
5681
5682		if (!LineOpenings(check, *check->v1))
5683		{
5684			if (!LineOpenings(check, *check->v2))
5685			{
5686				//	Closed door
5687				continue;
5688			}
5689		}
5690
5691		if (check->frontsector == sec)
5692		{
5693			other = check->backsector;
5694		}
5695		else
5696		{
5697			other = check->frontsector;
5698		}
5699
5700		if (check->flags & ML_SOUNDBLOCK)
5701		{
5702			if (!soundblocks)
5703			{
5704				RecursiveSound(other, 1, soundtarget, Splash);
5705			}
5706		}
5707		else
5708		{
5709			RecursiveSound(other, soundblocks, soundtarget, Splash);
5710		}
5711	}
5712}
5713
5714//==========================================================================
5715//
5716//  NoiseAlert
5717//
5718//  If a monster yells at a player, it will alert other monsters to the
5719// player.
5720//
5721//==========================================================================
5722
5723final void NoiseAlert(Entity target, Entity emmiter, optional bool Splash)
5724{
5725	if (!emmiter)
5726	{
5727		return;
5728	}
5729
5730	if (target && target.bIsPlayer && PlayerEx(target.Player).bNoTarget)
5731	{
5732		return;
5733	}
5734
5735	if (!specified_Splash)
5736	{
5737		Splash = false;
5738	}
5739
5740	(*Game.validcount)++;
5741	RecursiveSound(emmiter.Sector, 0, target, Splash);
5742}
5743
5744//==========================================================================
5745//
5746//  EV_NoiseAlert
5747//
5748//==========================================================================
5749
5750final bool EV_NoiseAlert(EntityEx A, int Arg1, int Arg2, int Arg3, int Arg4,
5751	int Arg5)
5752{
5753	EntityEx	ASource;
5754	EntityEx	ATarget;
5755
5756	if (!Arg1)
5757	{
5758		ASource = A;
5759	}
5760	else
5761	{
5762		ASource = EntityEx(FindMobjFromTID(Arg1, none));
5763	}
5764
5765	if (!Arg2)
5766	{
5767		ATarget = A;
5768	}
5769	else
5770	{
5771		ATarget = EntityEx(FindMobjFromTID(Arg2, none));
5772	}
5773
5774	NoiseAlert(ASource, ATarget);
5775	return true;
5776}
5777
5778//==========================================================================
5779//
5780// EV_LineSearchForPuzzleItem
5781//
5782//==========================================================================
5783
5784final bool EV_LineSearchForPuzzleItem(int Arg1, int Arg2, int Arg3, int Arg4,
5785	int Arg5, EntityEx A)
5786{
5787	Inventory Item;
5788
5789	if (!A)
5790		return false;
5791	if (!A.bIsPlayer)
5792		return false;
5793
5794	// Search player's inventory for puzzle items
5795	for (Item = A.Inventory; Item; Item = Item.Inventory)
5796	{
5797		if (!PuzzleItem(Item))
5798			continue;
5799		if (PuzzleItem(Item).PuzzleItemNumber == Arg1)
5800		{
5801			// A puzzle item was found for the line
5802			if (A.UseInventory(Item))
5803			{
5804				return true;
5805			}
5806		}
5807	}
5808	return false;
5809}
5810
5811//**************************************************************************
5812//
5813//	Point pushers and pullers
5814//
5815//**************************************************************************
5816
5817//==========================================================================
5818//
5819//	GetPushThing
5820//
5821//	Returns a pointer to an MT_PUSH or MT_PULL thing, NULL otherwise.
5822//
5823//==========================================================================
5824
5825final EntityEx GetPushThing(int s)
5826{
5827	Entity		thing;
5828	sector_t*	sec;
5829
5830	sec = &XLevel.Sectors[s];
5831	for (thing = sec->ThingList; thing; thing = thing.SNext)
5832	{
5833		if (PointPusher(thing) || PointPuller(thing))
5834		{
5835			return EntityEx(thing);
5836		}
5837	}
5838	return none;
5839}
5840
5841//==========================================================================
5842//
5843//	SpawnPushers
5844//
5845//	Initialise the sectors where pushers are present
5846//
5847//==========================================================================
5848
5849final void SpawnPushers()
5850{
5851	int			i;
5852	line_t*		l;
5853	int			s;
5854	Pusher		P;
5855	EntityEx	thing;
5856
5857	for (i = 0; i < XLevel.NumLines; i++)
5858	{
5859		l = &XLevel.Lines[i];
5860		switch (l->special)
5861		{
5862		case LNSPEC_SectorSetWind: // wind
5863			for (s = XLevel.FindSectorFromTag(l->arg1, -1); s >= 0;
5864				s = XLevel.FindSectorFromTag(l->arg1, s))
5865			{
5866				P = Spawn(Pusher);
5867				P.Init(Pusher::PUSHER_Wind, l->arg4 ? l : NULL, l->arg2,
5868					l->arg3, none, s);
5869			}
5870			break;
5871
5872		case LNSPEC_SectorSetCurrent: // current
5873			for (s = XLevel.FindSectorFromTag(l->arg1, -1); s >= 0;
5874				s = XLevel.FindSectorFromTag(l->arg1, s))
5875			{
5876				P = Spawn(Pusher);
5877				P.Init(Pusher::PUSHER_Current, l->arg4 ? l : NULL, l->arg2,
5878					l->arg3, none, s);
5879			}
5880			break;
5881
5882		case LNSPEC_PointPushSetForce: // push/pull
5883			if (l->arg1)
5884			{
5885				// [RH] Find thing by sector
5886				for (s = XLevel.FindSectorFromTag(l->arg1, -1); s >= 0;
5887					s = XLevel.FindSectorFromTag(l->arg1, s))
5888				{
5889					thing = GetPushThing(s);
5890					if (thing)
5891					{
5892						// No MT_P* means no effect
5893						// [RH] Allow narrowing it down by tid
5894						if (!l->arg2 || l->arg2 == thing.TID)
5895						{
5896							P = Spawn(Pusher);
5897							P.Init(Pusher::PUSHER_Push, l->arg4 ? l : NULL,
5898								l->arg3, 0, thing, s);
5899						}
5900					}
5901				}
5902			}
5903			else
5904			{
5905				// [RH] Find thing by tid
5906				for (thing = EntityEx(FindMobjFromTID(l->arg2, none));
5907					thing;
5908					thing = EntityEx(FindMobjFromTID(l->arg2, thing)))
5909				{
5910					if (PointPuller(thing) || PointPusher(thing))
5911					{
5912						for (s = 0; s < XLevel.NumSectors; s++)
5913							if (&XLevel.Sectors[s] == thing.Sector)
5914								break;
5915						P = Spawn(Pusher);
5916						P.Init(Pusher::PUSHER_Push, l->arg4 ? l : NULL,
5917							l->arg3, 0, thing, s);
5918					}
5919				}
5920			}
5921			break;
5922		}
5923	}
5924}
5925
5926//==========================================================================
5927//
5928//	AdjustPusher
5929//
5930//==========================================================================
5931
5932final bool AdjustPusher(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
5933	line_t* Line, int Type)
5934{
5935	int		tag;
5936	int		magnitude;
5937	int		angle;
5938	Pusher	Collection;
5939	Pusher	P;
5940	int		secnum;
5941
5942	if (Line || Arg4)
5943	{
5944		return false;
5945	}
5946	tag = Arg1;
5947	magnitude = Arg2;
5948	angle = Arg3;
5949	Collection = none;
5950	// Find pushers already attached to the sector, and change their parameters.
5951	foreach AllThinkers(Pusher, P)
5952	{
5953		if (P.CheckForSectorMatch(Type, tag) >= 0)
5954		{
5955			P.ChangeValues(magnitude, angle);
5956			P.AdjustLink = Collection;
5957			Collection = P;
5958		}
5959	}
5960
5961	// Now create pushers for any sectors that don't already have them.
5962	for (secnum = XLevel.FindSectorFromTag(tag, -1); secnum >= 0;
5963		secnum = XLevel.FindSectorFromTag(tag, secnum))
5964	{
5965		for (P = Collection; P; P = P.AdjustLink)
5966		{
5967			if (P.Affectee == secnum)
5968				break;
5969		}
5970		if (!P)
5971		{
5972			P = Spawn(Pusher);
5973			P.Init(Type, NULL, magnitude, angle, none, secnum);
5974		}
5975	}
5976	return true;
5977}
5978
5979//==========================================================================
5980//
5981//	EV_LineTranslucent
5982//
5983//==========================================================================
5984
5985final bool EV_LineTranslucent(int Arg1, int Arg2, int Arg3, int Arg4,
5986	int Arg5)
5987{
5988	int			Searcher;
5989	line_t*		Other;
5990
5991	Searcher = -1;
5992	for (Other = XLevel.FindLine(Arg1, &Searcher); Other;
5993		Other = XLevel.FindLine(Arg1, &Searcher))
5994	{
5995		Other->alpha = itof(Arg2) / 255.0;
5996		if (Arg3 == 0)
5997		{
5998			Other->flags &= ~ML_ADDITIVE;
5999		}
6000		else if (Arg3 == 1)
6001		{
6002			Other->flags |= ML_ADDITIVE;
6003		}
6004		else
6005		{
6006			print("Invalid line translucency type %d", Arg3);
6007		}
6008	}
6009	return true;
6010}
6011
6012//==========================================================================
6013//
6014//  EV_SectorSetPlaneReflection
6015//
6016//==========================================================================
6017
6018int EV_SectorSetPlaneReflection(int Arg1, int Arg2, int Arg3, int Arg4,
6019	int Arg5)
6020{
6021	int			SecNum;
6022
6023	for (SecNum = XLevel.FindSectorFromTag(Arg1, -1); SecNum >= 0;
6024		SecNum = XLevel.FindSectorFromTag(Arg1, SecNum))
6025	{
6026		XLevel.Sectors[SecNum].floor.MirrorAlpha = itof(255 - Arg2) / 255.0;
6027		XLevel.Sectors[SecNum].ceiling.MirrorAlpha = itof(255 - Arg3) / 255.0;
6028	}
6029	return 1;
6030}
6031
6032//==========================================================================
6033//
6034//  EV_GlassBreak
6035//
6036//==========================================================================
6037
6038final bool EV_GlassBreak(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5,
6039	line_t* Line, EntityEx A)
6040{
6041	bool		switched;
6042	byte		Quest1;
6043	byte		Quest2;
6044	float		x;
6045	float		y;
6046	int			i;
6047	EntityEx	glass;
6048	float		an;
6049	float		speed;
6050
6051	if (!Line)
6052	{
6053		return false;
6054	}
6055	if (Line->flags & ML_TWOSIDED)
6056	{
6057		Line->flags &= ~(ML_BLOCKING | ML_BLOCKEVERYTHING);
6058	}
6059	switched = ChangeSwitchTexture(Line->sidenum[0], false,
6060		'switches/normbutn', Quest1);
6061	Line->special = 0;
6062	if (Line->sidenum[1] != -1)
6063	{
6064		if (ChangeSwitchTexture(Line->sidenum[1], false, 'switches/normbutn',
6065			Quest2))
6066		{
6067			switched = true;
6068		}
6069	}
6070	else
6071	{
6072		Quest2 = Quest1;
6073	}
6074	if (switched)
6075	{
6076		if (!Arg1)
6077		{
6078			// Break some glass
6079			x = Line->v1->x + (Line->v2->x - Line->v1->x) / 2.0;
6080			y = Line->v1->y + (Line->v2->y - Line->v1->y) / 2.0;
6081			x += (Line->frontsector->soundorg.x - x) / 5.0;
6082			y += (Line->frontsector->soundorg.y - y) / 5.0;
6083
6084			for (i = 0; i < 7; i++)
6085			{
6086				glass = Spawn(GlassJunk, vector(x, y, EntityEx::ONFLOORZ));
6087				glass.Origin.z += 24.0;
6088				glass.SetState(GetStatePlus(glass.IdleState, P_Random() % glass.Health, true));
6089				an = Random() * 360.0;
6090				glass.Angles.yaw = an;
6091				speed = Random() * 4.0 * 35.0;
6092				glass.Velocity.x = cos(an) * speed;
6093				glass.Velocity.y = sin(an) * speed;
6094				glass.Velocity.z = Random() * 8.0 * 35.0;
6095			}
6096		}
6097		if (Quest1 || Quest2)
6098		{
6099			// Up stats and signal this mission is complete
6100			if (!A)
6101			{
6102				for (i = 0; i < MAXPLAYERS; ++i)
6103				{
6104					if (Game.Players[i] && Game.Players[i].bSpawned)
6105					{
6106						A = EntityEx(Game.Players[i].MO);
6107						break;
6108					}
6109				}
6110			}
6111			if (A && A.bIsPlayer)
6112			{
6113				A.GiveInventoryType(QuestItem29);
6114				A.GiveInventoryType(class<Inventory>(FindClass('UpgradeAccuracy')));
6115				A.GiveInventoryType(class<Inventory>(FindClass('UpgradeStamina')));
6116			}
6117		}
6118	}
6119	//	We already changed the switch texture, so don't make the main code
6120	// switch it back.
6121	return false;
6122}
6123
6124//==========================================================================
6125//
6126//  EV_SendToCommunicator
6127//
6128//==========================================================================
6129
6130final bool EV_SendToCommunicator(EntityEx A, int Arg1, int Arg2, int Arg3,
6131	int Arg4, int Arg5, int Side)
6132{
6133	if (Arg2 && Side)
6134	{
6135		return false;
6136	}
6137
6138	if (A && A.bIsPlayer && A.FindInventory(Communicator))
6139	{
6140		if (!Arg4)
6141		{
6142			PlayerEx(A.Player).SetObjectives(Arg1);
6143		}
6144
6145		PlayerEx(A.Player).ClientVoice(Arg1);
6146
6147		if (Arg3 == 0)
6148		{
6149			A.Player.cprint("Incoming Message");
6150		}
6151		else if (Arg3 == 1)
6152		{
6153			A.Player.cprint("Incoming Message from BlackBird");
6154		}
6155		return true;
6156	}
6157	return false;
6158}
6159
6160//==========================================================================
6161//
6162//  EV_StartConversation
6163//
6164//==========================================================================
6165
6166final bool EV_StartConversation(int Arg1, int Arg2, int Arg3, int Arg4,
6167	int Arg5, EntityEx A)
6168{
6169	EntityEx	Ent;
6170
6171	if (!A || !A.bIsPlayer)
6172	{
6173		return false;
6174	}
6175	for (Ent = EntityEx(FindMobjFromTID(Arg1, none)); Ent;
6176		Ent = EntityEx(FindMobjFromTID(Arg1, Ent)))
6177	{
6178		if (StartConversation(A, Ent))
6179		{
6180			if (Arg2)
6181			{
6182				A.Angles.yaw = atan2(Ent.Origin.y - A.Origin.y,
6183					Ent.Origin.x - A.Origin.x);
6184				A.Player.bFixAngle = true;
6185			}
6186			return true;
6187		}
6188	}
6189	return false;
6190}
6191
6192//===========================================================================
6193// Quake variables
6194//
6195//      Arg1     Intensity on richter scale (2..9)
6196//      Arg2     Duration in tics
6197//      Arg3     Radius for damage, in tile units (64 pixels)
6198//      Arg4     Radius for tremor in tile units (64 pixels)
6199//      Arg5     TID of map thing for focus of quake
6200//
6201//===========================================================================
6202
6203//===========================================================================
6204//
6205//	A_LocalQuake
6206//
6207//===========================================================================
6208
6209final bool A_LocalQuake(int Arg1, int Arg2, int Arg3, int Arg4, int Arg5)
6210{
6211	QuakeFocus focus;
6212	EntityEx target;
6213	int success = false;
6214
6215	// Find all quake foci
6216	for (target = EntityEx(FindMobjFromTID(Arg5, none)); target;
6217		target = EntityEx(FindMobjFromTID(Arg5, target)))
6218	{
6219		focus = Spawn(QuakeFocus, target.Origin);
6220		if (focus)
6221		{
6222			focus.Richters = Arg1;
6223			focus.QuakeDuration = Arg2 >> 1;	// decremented every 2 tics
6224			focus.DamageRadius = itof(Arg3) * 64.0;
6225			focus.TremorRadius = itof(Arg4) * 64.0;
6226			success = true;
6227		}
6228	}
6229
6230	return success;
6231}
6232
6233//==========================================================================
6234//
6235//	ForceLightning
6236//
6237//==========================================================================
6238
6239final void ForceLightning(int Mode)
6240{
6241	LightningThinker Lightning = none;
6242	Thinker Th;
6243	foreach AllThinkers(LightningThinker, Th)
6244	{
6245		Lightning = LightningThinker(Th);
6246		break;
6247	}
6248	if (!Lightning)
6249	{
6250		Lightning = Spawn(LightningThinker);
6251		Lightning.Init();
6252	}
6253	Lightning.ForceLightning(Mode);
6254}
6255
6256//==========================================================================
6257//
6258//	ModToDamageType
6259//
6260//==========================================================================
6261
6262final name ModToDamageType(int Mod)
6263{
6264	switch (Mod)
6265	{
6266	case 9:
6267		return 'BFGSplash';
6268	case 12:
6269		return 'Drowning';
6270	case 13:
6271		return 'Slime';
6272	case 14:
6273		return 'Fire';
6274	case 15:
6275		return 'Crush';
6276	case 16:
6277		return 'Telefrag';
6278	case 17:
6279		return 'Falling';
6280	case 18:
6281		return 'Suicide';
6282	case 20:
6283		return 'Exit';
6284	case 22:
6285		return 'Melee';
6286	case 23:
6287		return 'Railgun';
6288	case 24:
6289		return 'Ice';
6290	case 25:
6291		return 'Disintegrate';
6292	case 26:
6293		return 'Poison';
6294	case 27:
6295		return 'Electric';
6296	case 1000:
6297		return 'Massacre';
6298	default:
6299		return '';
6300	}
6301}
6302
6303//==========================================================================
6304//
6305//  PolyThrustMobj
6306//
6307//==========================================================================
6308
6309final void PolyThrustMobj(Entity Other, TVec thrustDir, polyobj_t* po)
6310{
6311	float force;
6312	PolyobjThinker pe;
6313
6314	if (!EntityEx(Other).bShootable && !Other.bIsPlayer)
6315	{
6316		return;
6317	}
6318
6319	pe = PolyobjThinker(po->SpecialData);
6320	if (pe)
6321	{
6322		force = pe.thrust_force;
6323		if (force < 1.0)
6324		{
6325			force = 1.0;
6326		}
6327		else if (force > 128.0)
6328		{
6329			force = 128.0;
6330		}
6331	}
6332	else
6333	{
6334		force = 1.0;
6335	}
6336
6337	Other.Velocity += force * thrustDir;
6338	if (po->bCrush)
6339	{
6340		TVec testPos;
6341
6342		testPos = Other.Origin + force * thrustDir * Game.frametime;
6343		if (po->bHurtOnTouch || !Other.CheckPosition(testPos))
6344		{
6345			EntityEx(Other).Damage(none, none, 3, 'Crush');
6346		}
6347	}
6348}
6349
6350//==========================================================================
6351//
6352//  PolyBusy
6353//
6354//==========================================================================
6355
6356final bool PolyBusy(int polyobj)
6357{
6358	polyobj_t* poly = XLevel.GetPolyobj(polyobj);
6359	if (!poly || !poly->SpecialData)
6360	{
6361		return false;
6362	}
6363	else
6364	{
6365		return true;
6366	}
6367}
6368
6369//==========================================================================
6370//
6371//	ThingCount
6372//
6373//==========================================================================
6374
6375final int ThingCount(int type, name TypeName, int tid, int SectorTag)
6376{
6377	class<EntityEx>		moType;
6378
6379	if (!(type + tid) && !TypeName)
6380	{
6381		// Nothing to count
6382		return 0;
6383	}
6384	if (TypeName)
6385	{
6386		moType = class<EntityEx>(FindClassLowerCase(TypeName));
6387		if (!moType)
6388		{
6389			return 0;
6390		}
6391	}
6392	else
6393	{
6394		moType = class<EntityEx>(FindClassFromScriptId(type,
6395			LineSpecialGameInfo(Game).GameFilterFlag));
6396	}
6397
6398	//	Count things.
6399	int Count = DoThingCount(moType, tid, SectorTag);
6400	if (moType)
6401	{
6402		//	If this class has a replacement, count number of instances of
6403		// replacement class too.
6404		class<EntityEx> RepType = class<EntityEx>(GetClassReplacement(
6405			moType));
6406		if (RepType != moType)
6407		{
6408			Count += DoThingCount(RepType, tid, SectorTag);
6409		}
6410	}
6411	return Count;
6412}
6413
6414//==========================================================================
6415//
6416//	DoThingCount
6417//
6418//==========================================================================
6419
6420final int DoThingCount(class<EntityEx> moType, int tid, int SectorTag)
6421{
6422	int					count;
6423	EntityEx			Ent;
6424
6425	count = 0;
6426	if (tid)
6427	{
6428		// Count TID things
6429		for (Ent = EntityEx(FindMobjFromTID(tid, none)); Ent != none;
6430			Ent = EntityEx(FindMobjFromTID(tid, Ent)))
6431		{
6432			if (Ent.bMonster && Ent.Health <= 0)
6433			{
6434				// Don't count dead monsters
6435				continue;
6436			}
6437			if (Inventory(Ent) && Ent.Owner)
6438			{
6439				//	Don't count things in somebody's inventory
6440				continue;
6441			}
6442			if (SectorTag != -1 && Ent.Sector->tag != SectorTag)
6443			{
6444				//	Wrong sector tag.
6445				continue;
6446			}
6447			if (!moType || moType == Ent.Class)
6448			{
6449				count++;
6450			}
6451		}
6452	}
6453	else if (moType)
6454	{
6455		// Count only types
6456		foreach AllThinkers(moType, Ent)
6457		{
6458			if (Ent.Class != moType)
6459			{
6460				// Doesn't match
6461				continue;
6462			}
6463			if (Ent.bMonster && Ent.Health <= 0)
6464			{
6465				// Don't count dead monsters
6466				continue;
6467			}
6468			if (Inventory(Ent) && Ent.Owner)
6469			{
6470				//	Don't count things in somebody's inventory
6471				continue;
6472			}
6473			if (SectorTag != -1 && Ent.Sector->tag != SectorTag)
6474			{
6475				//	Wrong sector tag.
6476				continue;
6477			}
6478			count++;
6479		}
6480	}
6481	return count;
6482}
6483
6484//==========================================================================
6485//
6486//  SectorDamage
6487//
6488//==========================================================================
6489
6490final void SectorDamage(int Tag, int Amount, name DamageType,
6491	name ProtectionType, int Flags)
6492{
6493	class<Inventory> ProtectClass = class<Inventory>(FindClassLowerCase(
6494		ProtectionType));
6495
6496	int SecNum = -1;
6497	for (SecNum = XLevel.FindSectorFromTag(Tag, -1); SecNum >= 0;
6498		SecNum = XLevel.FindSectorFromTag(Tag, SecNum))
6499	{
6500		sector_t* Sec = &XLevel.Sectors[SecNum];
6501		Entity Ent;
6502		Entity Next;
6503
6504		for (Ent = Sec->ThingList; Ent; Ent = Next)
6505		{
6506			Next = Ent.SNext;
6507
6508			if (!EntityEx(Ent).bShootable)
6509			{
6510				continue;
6511			}
6512			if (!(Flags & DAMAGE_NONPLAYERS) && !Ent.bIsPlayer)
6513			{
6514				continue;
6515			}
6516			if (!(Flags & DAMAGE_PLAYERS) && Ent.bIsPlayer)
6517			{
6518				continue;
6519			}
6520
6521			if (!(Flags & DAMAGE_IN_AIR) && Ent.Origin.z !=
6522				GetPlanePointZ(&Sec->floor, Ent.Origin) && !Ent.WaterLevel)
6523			{
6524				continue;
6525			}
6526
6527			if (ProtectClass)
6528			{
6529				if (!(Flags & DAMAGE_SUBCLASSES_PROTECT))
6530				{
6531					if (EntityEx(Ent).FindInventory(ProtectClass))
6532					{
6533						continue;
6534					}
6535				}
6536				else
6537				{
6538					Inventory Item;
6539
6540					for (Item = EntityEx(Ent).Inventory; Item; Item = Item.Inventory)
6541					{
6542						if (Item.IsA(ProtectionType))
6543						{
6544							break;
6545						}
6546					}
6547					if (Item)
6548					{
6549						continue;
6550					}
6551				}
6552			}
6553
6554			EntityEx(Ent).Damage(none, none, Amount, DamageType);
6555		}
6556	}
6557}
6558
6559//==========================================================================
6560//
6561//	SpawnMapThing
6562//
6563//	The fields of the mapthing should already be in host byte order.
6564//
6565//==========================================================================
6566
6567final void SpawnMapThing(mthing_t* mthing)
6568{
6569	int				spawnMask;
6570	class<EntityEx>	moClass;
6571
6572	if (mthing->type <= 0)
6573	{
6574		return;
6575	}
6576
6577	// count deathmatch start positions
6578	if (mthing->type == 11)
6579	{
6580		DeathmatchStarts.Num = DeathmatchStarts.Num + 1;
6581		CopyMThing(mthing, &DeathmatchStarts[DeathmatchStarts.Num - 1]);
6582		return;
6583	}
6584
6585	if (bCheckStrifeStartSpots && mthing->type >= 118 && mthing->type <= 127)
6586	{
6587		//	Map start spots, i.e. player starts.
6588		mthing->arg1 = mthing->type - 117;
6589		mthing->type = 1;
6590	}
6591
6592	// Check for player starts 1 to 4
6593	bool IsPlayerStart = false;
6594	if (mthing->type <= 4)
6595	{
6596		IsPlayerStart = true;
6597	}
6598	// Check for player starts 5 to 8
6599	if (mthing->type >= ExtPlayersBase && mthing->type < ExtPlayersBase + 4)
6600	{
6601		//	Change type to range 5-8.
6602		mthing->type = 5 + mthing->type - ExtPlayersBase;
6603		IsPlayerStart = true;
6604	}
6605
6606	if (!IsPlayerStart || bFilterStarts)
6607	{
6608		// Check current game type with spawn flags
6609		if (Game.netgame == false)
6610		{
6611			spawnMask = MTF_GSINGLE;
6612		}
6613		else if (Game.deathmatch)
6614		{
6615			spawnMask = MTF_GDEATHMATCH;
6616		}
6617		else
6618		{
6619			spawnMask = MTF_GCOOP;
6620		}
6621		if (!(mthing->options & spawnMask))
6622		{
6623			return;
6624		}
6625
6626		// Check current skill with spawn flags
6627		if (!(mthing->SkillClassFilter & World.SkillSpawnFilter))
6628		{
6629			return;
6630		}
6631
6632		// Check current character classes with spawn flags
6633		spawnMask = GetPClassSpawnFlags();
6634		if (spawnMask && !(mthing->SkillClassFilter & spawnMask))
6635		{
6636			// Not for current class
6637			return;
6638		}
6639	}
6640
6641	if (IsPlayerStart)
6642	{
6643		// save spots for respawning in network games
6644		PlayerStarts.Num = PlayerStarts.Num + 1;
6645		CopyMThing(mthing, &PlayerStarts[PlayerStarts.Num - 1]);
6646		return;
6647	}
6648
6649	//	Sector's sound sequence types.
6650	if (mthing->type >= 1400 && mthing->type < 1410)
6651	{
6652		XLevel.PointInSector(vector(mthing->x,
6653			mthing->y, 0.0))->seqType = mthing->type - 1400;
6654		return;
6655	}
6656	if (mthing->type == 1411)
6657	{
6658		int			SeqType;
6659
6660		if (mthing->arg1 == 255)
6661			SeqType = -1;
6662		else
6663			SeqType = mthing->arg1;
6664
6665		if (SeqType > 63)
6666		{
6667			print("Sound sequence %d out of range", SeqType);
6668		}
6669		else
6670		{
6671			XLevel.PointInSector(vector(mthing->x,
6672				mthing->y, 0.0))->seqType = SeqType;
6673		}
6674		return;
6675	}
6676
6677	//	Remap old ambient sound types to the generic one.
6678	if (mthing->type >= 14001 && mthing->type <= 14064)
6679	{
6680		mthing->arg1 = mthing->type - 14000;
6681		mthing->type = 14065;
6682	}
6683
6684	// find which type to spawn
6685	moClass = class<EntityEx>(FindClassFromEditorId(mthing->type,
6686		LineSpecialGameInfo(Game).GameFilterFlag));
6687	if (!moClass)
6688	{
6689		// Can't find thing type
6690		dprint("SpawnMapThing: Unknown type %d at (%f, %f)",
6691			mthing->type, mthing->x, mthing->y);
6692		moClass = Unknown;
6693	}
6694	else
6695	{
6696		moClass = class<EntityEx>(GetClassReplacement(moClass));
6697
6698		//	If actor has no sprites, also map it to the unknown thing
6699		if (FindClassState(moClass, 'Spawn') &&
6700			!AreStateSpritesPresent(FindClassState(moClass, 'Spawn')))
6701		{
6702			print("%n at (%f, %f), has no frames", GetClassName(moClass),
6703				mthing->x, mthing->y);
6704			moClass = Unknown;
6705		}
6706	}
6707
6708	if (Level.Game.nomonsters && moClass.default.bMonster)
6709	{
6710		return;
6711	}
6712
6713	// spawn it
6714	Spawn(moClass,,, mthing, false);
6715}
6716
6717//==========================================================================
6718//
6719//  CheckLock
6720//
6721//==========================================================================
6722
6723final bool CheckLock(Entity user, int lock, bool door)
6724{
6725	if (!user.bIsPlayer)
6726	{
6727		return false;
6728	}
6729	if (!lock)
6730	{
6731		return true;
6732	}
6733	LockDef* Lock = GetLockDef(lock);
6734	if (!Lock)
6735	{
6736		if (lock == 103)
6737		{
6738			user.Player.centreprint(Lock103Message);
6739		}
6740		else
6741		{
6742			user.Player.centreprint("That doesn't seem to work");
6743		}
6744		user.PlaySound('misc/keytry', CHAN_VOICE);
6745		return false;
6746	}
6747	if (CheckLockDef(Lock, EntityEx(user)))
6748	{
6749		return true;
6750	}
6751	user.Player.centreprint(door ? Lock->Message : Lock->RemoteMessage);
6752	user.PlaySound(Lock->LockedSound, CHAN_VOICE);
6753	return false;
6754}
6755
6756//==========================================================================
6757//
6758//  GetDefaultDoorSound
6759//
6760//==========================================================================
6761
6762final bool CheckLockDef(LockDef* Lock, EntityEx User)
6763{
6764	int			i;
6765	int			j;
6766	Inventory	Item;
6767
6768	//	Empty lock list means check for any key.
6769	if (!Lock->Locks.Num)
6770	{
6771		for (Item = User.Inventory; Item; Item = Item.Inventory)
6772		{
6773			if (Key(Item))
6774			{
6775				return true;
6776			}
6777		}
6778		return false;
6779	}
6780
6781	for (i = 0; i < Lock->Locks.Num; i++)
6782	{
6783		bool Good = false;
6784		for (j = 0; j < Lock->Locks[i].AnyKeyList.Num; j++)
6785		{
6786			if (User.FindInventory(class<Inventory>(
6787				Lock->Locks[i].AnyKeyList[j])))
6788			{
6789				Good = true;
6790				break;
6791			}
6792		}
6793		if (!Good)
6794		{
6795			return false;
6796		}
6797	}
6798	return true;
6799}
6800
6801//**************************************************************************
6802//
6803//	CONVERSATION STUFF
6804//
6805//**************************************************************************
6806
6807//==========================================================================
6808//
6809//	StartConversation
6810//
6811//==========================================================================
6812
6813bool StartConversation(EntityEx User, EntityEx UseOn)
6814{
6815	int SpeechNum;
6816
6817	if (Game.netgame)
6818	{
6819		return false;
6820	}
6821	if (!User || !User.bIsPlayer || User.Health <= 0)
6822	{
6823		return false;
6824	}
6825	if (!UseOn || UseOn.Health <= 0)
6826	{
6827		return false;
6828	}
6829	if (UseOn.bInCombat)
6830	{
6831		//	This dude is too busy to talk right now.
6832		return false;
6833	}
6834	if (!UseOn.ConversationID)
6835	{
6836		return false;
6837	}
6838	SpeechNum = UseOn.GetSpeech();
6839	if (SpeechNum)
6840	{
6841		CurrentSpeaker = UseOn;
6842		CurrentSpeakingTo = User;
6843		OldSpeakerAngle = UseOn.Angles.yaw;
6844		UseOn.Angles.yaw = atan2(User.Origin.y - UseOn.Origin.y,
6845			User.Origin.x - UseOn.Origin.x);
6846		User.PlaySound('misc/chat', CHAN_VOICE);
6847		StartSpeech(SpeechNum);
6848	}
6849	return true;
6850}
6851
6852//==========================================================================
6853//
6854//	StartSpeech
6855//
6856//==========================================================================
6857
6858final void StartSpeech(int SpeechNum)
6859{
6860	RogueConSpeech *Speech;
6861	bool conJumped;
6862
6863	do
6864	{
6865		conJumped = false;
6866		if (!SpeechNum)
6867		{
6868			StopSpeech();
6869			return;
6870		}
6871		if (SpeechNum < 0)
6872		{
6873			Speech = &XLevel.GenericSpeeches[-SpeechNum - 1];
6874		}
6875		else
6876		{
6877			Speech = &XLevel.LevelSpeeches[SpeechNum - 1];
6878		}
6879		if (Speech->JumpToConv &&
6880			CheckForNeededItem(CurrentSpeakingTo, Speech->CheckItem1, 1) != -2 &&
6881			CheckForNeededItem(CurrentSpeakingTo, Speech->CheckItem2, 1) != -2 &&
6882			CheckForNeededItem(CurrentSpeakingTo, Speech->CheckItem3, 1) != -2)
6883		{
6884			CurrentSpeaker.CurrentSpeech = Speech->JumpToConv;
6885			SpeechNum = CurrentSpeaker.GetSpeech();
6886			conJumped = true;
6887		}
6888	}
6889	while (conJumped);
6890	CurrentSpeechIndex = SpeechNum;
6891
6892	PlayerEx(CurrentSpeakingTo.Player).ClientSpeech(CurrentSpeaker, SpeechNum);
6893}
6894
6895//==========================================================================
6896//
6897//	GetClassFromID
6898//
6899//==========================================================================
6900
6901final class<EntityEx> GetClassFromID(int ID)
6902{
6903	class Cls;
6904	if (ID)
6905	{
6906		foreach (AllClasses(EntityEx, Cls))
6907		{
6908			if (class<EntityEx>(Cls).default.ConversationID == ID)
6909			{
6910				return class<EntityEx>(Cls);
6911			}
6912		}
6913		print("Unknown item %d", ID);
6914	}
6915
6916	return none;
6917}
6918
6919//==========================================================================
6920//
6921//	CheckForNeededItem
6922//
6923//==========================================================================
6924
6925final int CheckForNeededItem(EntityEx A, int ID, int Amount)
6926{
6927	int i;
6928
6929	if (!ID)
6930	{
6931		return -1;
6932	}
6933	//	Get class ID.
6934	class<EntityEx> CID = GetClassFromID(ID);
6935	//	Check inventory items.
6936	if (class<Inventory>(CID) && class<Inventory>(CID).default.bInvBar)
6937	{
6938		Inventory Item = A.FindInventory(class<Inventory>(CID));
6939		return !Item || Item.Amount < Amount ? -2 : 0;
6940	}
6941	//	Check keys.
6942	Inventory Item = A.FindInventory(class<Inventory>(CID));
6943	if (Item)
6944	{
6945		return -1;
6946	}
6947	return -2;
6948}
6949
6950//==========================================================================
6951//
6952//	StopSpeech
6953//
6954//==========================================================================
6955
6956final void StopSpeech()
6957{
6958	CurrentSpeaker.Angles.yaw = OldSpeakerAngle;
6959	CurrentSpeaker = none;
6960	CurrentSpeakingTo = none;
6961	CurrentSpeechIndex = 0;
6962}
6963
6964//==========================================================================
6965//
6966//	ConChoiceImpulse
6967//
6968//==========================================================================
6969
6970final void ConChoiceImpulse(int ChoiceNum)
6971{
6972	RogueConSpeech *Speech;
6973	RogueConChoice *Choice;
6974	int SpeechNum;
6975	int Item1;
6976	int Item2;
6977	int Item3;
6978	class<EntityEx> ItemType;
6979	Inventory Item;
6980	bool GaveItem;
6981
6982	if (ConversationSlideshow)
6983	{
6984		//	Resume conversation after slideshow.
6985		StartSpeech(CurrentSpeaker.GetSpeech());
6986		ConversationSlideshow = false;
6987		return;
6988	}
6989	if (!CurrentSpeaker || !CurrentSpeechIndex)
6990	{
6991		return;
6992	}
6993	if (!ChoiceNum)
6994	{
6995		StopSpeech();
6996		return;
6997	}
6998	if (CurrentSpeechIndex < 0)
6999	{
7000		Speech = &XLevel.GenericSpeeches[-CurrentSpeechIndex - 1];
7001	}
7002	else
7003	{
7004		Speech = &XLevel.LevelSpeeches[CurrentSpeechIndex - 1];
7005	}
7006	Choice = &Speech->Choices[ChoiceNum - 1];
7007	//	Check if player has needed items.
7008	Item1 = CheckForNeededItem(CurrentSpeakingTo, Choice->NeedItem1, Choice->NeedAmount1);
7009	Item2 = CheckForNeededItem(CurrentSpeakingTo, Choice->NeedItem2, Choice->NeedAmount2);
7010	Item3 = CheckForNeededItem(CurrentSpeakingTo, Choice->NeedItem3, Choice->NeedAmount3);
7011	if (Item1 == -2 || Item2 == -2 || Item3 == -2)
7012	{
7013		CurrentSpeakingTo.Player.cprint(Choice->TextNo);
7014		StopSpeech();
7015		return;
7016	}
7017	GaveItem = true;
7018	if (Choice->GiveItem > 0)
7019	{
7020		ItemType = GetClassFromID(Choice->GiveItem);
7021		if (ItemType)
7022		{
7023			Item = Inventory(Spawn(ItemType,,,, false));
7024			//	This shouldn't count for the item statistics
7025			if (Item.bCountItem)
7026			{
7027				Item.bCountItem = false;
7028				Level.TotalItems--;
7029			}
7030			GaveItem = Item.TryPickup(CurrentSpeakingTo);
7031			if (!GaveItem)
7032			{
7033				Item.Destroy();
7034			}
7035		}
7036	}
7037	if (GaveItem)
7038	{
7039		if (Item1 != -1)
7040		{
7041			Item = CurrentSpeakingTo.FindInventory(
7042				class<Inventory>(GetClassFromID(Choice->NeedItem1)));
7043			Item.Amount -= Choice->NeedAmount1;
7044			if (Item.Amount <= 0)
7045			{
7046				Item.Destroy();
7047			}
7048		}
7049		if (Item2 != -1)
7050		{
7051			Item = CurrentSpeakingTo.FindInventory(
7052				class<Inventory>(GetClassFromID(Choice->NeedItem2)));
7053			Item.Amount -= Choice->NeedAmount2;
7054			if (Item.Amount <= 0)
7055			{
7056				Item.Destroy();
7057			}
7058		}
7059		if (Item3 != -1)
7060		{
7061			Item = CurrentSpeakingTo.FindInventory(
7062				class<Inventory>(GetClassFromID(Choice->NeedItem3)));
7063			Item.Amount -= Choice->NeedAmount3;
7064			if (Item.Amount <= 0)
7065			{
7066				Item.Destroy();
7067			}
7068		}
7069	}
7070	if (Choice->Objectives)
7071	{
7072		PlayerEx(CurrentSpeakingTo.Player).SetObjectives(Choice->Objectives);
7073	}
7074	if (!GaveItem)
7075	{
7076		CurrentSpeakingTo.Player.cprint(Choice->TextNo);
7077	}
7078	else if (strcmp(Choice->TextOK, "") && strcmp(Choice->TextOK, "_"))
7079	{
7080		CurrentSpeakingTo.Player.cprint(Choice->TextOK);
7081	}
7082	if (Choice->Next < 0)
7083	{
7084		CurrentSpeaker.CurrentSpeech = -Choice->Next;
7085		if (!ConversationSlideshow)
7086		{
7087			StartSpeech(CurrentSpeaker.GetSpeech());
7088		}
7089	}
7090	else
7091	{
7092		if (Choice->Next)
7093		{
7094			CurrentSpeaker.CurrentSpeech = Choice->Next;
7095		}
7096		StopSpeech();
7097		ConversationSlideshow = false;
7098	}
7099}
7100
7101//==========================================================================
7102//
7103//	AddPlayerCorpse
7104//
7105//==========================================================================
7106
7107final void AddPlayerCorpse(EntityEx Corpse)
7108{
7109	if (bodyqueslot >= BodyQueSize)
7110	{
7111		// Too many player corpses - remove an old one
7112		if (bodyque[bodyqueslot % BodyQueSize])
7113		{
7114			bodyque[bodyqueslot % BodyQueSize].Destroy();
7115		}
7116	}
7117	bodyque[bodyqueslot % BodyQueSize] = Corpse;
7118	Corpse.Translation = XLevel.SetBodyQueueTrans(
7119		bodyqueslot % BodyQueSize, Corpse.Translation);
7120	bodyqueslot++;
7121}
7122
7123//==========================================================================
7124//
7125//  ParticleEffect
7126//
7127//==========================================================================
7128
7129void ParticleEffect(int count, int type1, int type2, TVec origin, float ornd,
7130	TVec velocity, float vrnd, float acceleration, float grav, int colour, float duration, float ramp)
7131{
7132	int i;
7133	particle_t *p;
7134
7135	for (i = 0; i < count; i++)
7136	{
7137		p = Level.NewParticle();
7138		if (!p)
7139			return;
7140
7141		p->die = Level.XLevel.Time + duration * Random();
7142		p->colour = colour;
7143		p->Size = 1.0;
7144		if(ramp)
7145			p->ramp = Random() * ramp;
7146
7147		if(type2)
7148		{
7149			// Choose between the two types
7150			if (i & 1)
7151			{
7152				p->type = type1;
7153			}
7154			else
7155			{
7156				p->type = type2;
7157			}
7158		}
7159		else
7160		{
7161			p->type = type1;
7162		}
7163
7164		// No random origin
7165		if(!ornd)
7166		{
7167			p->org = origin;
7168		}
7169		else
7170		{
7171			p->org.x = origin.x + ((Random() * ornd) - ornd / 2.0);
7172			p->org.y = origin.y + ((Random() * ornd) - ornd / 2.0);
7173			p->org.z = origin.z + ((Random() * ornd) - ornd / 2.0);
7174		}
7175
7176		// No random velocity
7177		if(!vrnd)
7178		{
7179			p->vel = velocity;
7180		}
7181		else
7182		{
7183			p->vel.x = velocity.x * (Random() - vrnd);
7184			p->vel.y = velocity.y * (Random() - vrnd);
7185			p->vel.z = velocity.z * (Random() - vrnd);
7186		}
7187		p->accel.x = acceleration;
7188		p->accel.y = acceleration;
7189		p->accel.z = acceleration;
7190		p->gravity = grav;
7191	}
7192}
7193
7194//==========================================================================
7195//
7196//  UpdateParticle
7197//
7198//==========================================================================
7199
7200void UpdateParticle(particle_t * p, float DeltaTime)
7201{
7202	float time2, time3;
7203	float dvel;
7204	float grav;
7205
7206	time2 = DeltaTime * 10.0;	// 15;
7207	time3 = DeltaTime * 15.0;
7208	dvel = 4.0 * DeltaTime;
7209	grav = DeltaTime * p->gravity /*sv_gravity.value * 0.05*/;
7210	p->vel.z -= grav;
7211
7212	switch (p->type)
7213	{
7214	case pt_static:
7215		p->vel += p->accel * DeltaTime;
7216		break;
7217
7218	case pt_explode:
7219		p->ramp += time2;
7220		if (p->ramp >= 16.0)
7221			p->die = -1.0;
7222		else
7223			p->colour = LineSpecialGameInfo.default.ramp1[ftoi(p->ramp)];
7224		p->vel.x += p->vel.x * dvel;
7225		p->vel.y += p->vel.y * dvel;
7226		p->vel.z += p->vel.z * dvel;
7227		p->vel += p->accel * DeltaTime;
7228		p->vel.z -= grav;
7229		break;
7230
7231	case pt_explode2:
7232		p->ramp += time3;
7233		if (p->ramp >= 16.0)
7234			p->die = -1.0;
7235		else
7236			p->colour = LineSpecialGameInfo.default.ramp2[ftoi(p->ramp)];
7237		p->vel.x -= p->vel.x * DeltaTime;
7238		p->vel.y -= p->vel.y * DeltaTime;
7239		p->vel.z -= p->vel.z * DeltaTime;
7240		p->vel += p->accel * DeltaTime;
7241		p->vel.z -= grav;
7242		break;
7243
7244	case pt_fountain:
7245		p->vel += p->accel * DeltaTime;
7246		p->colour = (p->colour & 0x00ffffff) | (ftoi(
7247			itof(p->colour >> 24) - 255.0 / 51.0 * 35.0 * DeltaTime) << 24);
7248		break;
7249
7250	case pt_spark:
7251		p->vel += p->accel * DeltaTime;
7252		p->colour = (p->colour & 0x00ffffff) | (ftoi(
7253			itof(p->colour >> 24) - 255.0 / 10.0 * 35.0 * DeltaTime) << 24);
7254		break;
7255
7256	case pt_ice_chunk:
7257		p->vel.x -= p->vel.x * DeltaTime;
7258		p->vel.y -= p->vel.y * DeltaTime;
7259		p->vel.z += p->accel.z * DeltaTime;
7260		break;
7261
7262	case pt_rail:
7263		p->vel += p->accel * DeltaTime;
7264		p->colour = (p->colour & 0x00ffffff) | (ftoi(
7265			itof(p->colour >> 24) - 255.0 * DeltaTime) << 24);
7266		break;
7267	}
7268}
7269
7270//==========================================================================
7271//
7272//	AcsFadeRange
7273//
7274//==========================================================================
7275
7276void AcsFadeRange(float BlendR1, float BlendG1, float BlendB1, float BlendA1,
7277	float BlendR2, float BlendG2, float BlendB2, float BlendA2,
7278	float Duration, Entity Activator)
7279{
7280	if (Activator)
7281	{
7282		if (!Activator.bIsPlayer)
7283		{
7284			return;
7285		}
7286		StartFlashFader(BlendR1, BlendG1, BlendB1, BlendA1, BlendR2,
7287			BlendG2, BlendB2, BlendA2, Duration, Activator);
7288	}
7289	else
7290	{
7291		BasePlayer P;
7292		foreach AllActivePlayers(P)
7293		{
7294			StartFlashFader(BlendR1, BlendG1, BlendB1, BlendA1, BlendR2,
7295				BlendG2, BlendB2, BlendA2, Duration, P.MO);
7296		}
7297	}
7298}
7299
7300//==========================================================================
7301//
7302//	StartFlashFader
7303//
7304//==========================================================================
7305
7306final void StartFlashFader(float BlendR1, float BlendG1, float BlendB1,
7307	float BlendA1, float BlendR2, float BlendG2, float BlendB2,
7308	float BlendA2, float Duration, Entity ForWho)
7309{
7310	PlayerEx P = PlayerEx(ForWho.Player);
7311	if (Duration <= 0.0)
7312	{
7313		P.BlendR = BlendR2;
7314		P.BlendG = BlendG2;
7315		P.BlendB = BlendB2;
7316		P.BlendA = BlendA2;
7317	}
7318	else
7319	{
7320		if (BlendA1 < 0.0)
7321		{
7322			if (P.BlendA <= 0.0)
7323			{
7324				BlendR1 = BlendR2;
7325				BlendG1 = BlendG2;
7326				BlendB1 = BlendB2;
7327				BlendA1 = 0.0;
7328			}
7329			else
7330			{
7331				BlendR1 = P.BlendR;
7332				BlendG1 = P.BlendG;
7333				BlendB1 = P.BlendB;
7334				BlendA1 = P.BlendA;
7335			}
7336		}
7337		FlashFader F = Spawn(FlashFader);
7338		F.Init(BlendR1, BlendG1, BlendB1, BlendA1, BlendR2, BlendG2, BlendB2,
7339			BlendA2, Duration, EntityEx(ForWho));
7340	}
7341}
7342
7343//==========================================================================
7344//
7345//	AcsCancelFade
7346//
7347//==========================================================================
7348
7349void AcsCancelFade(Entity Activator)
7350{
7351	Thinker Th;
7352	foreach AllThinkers(FlashFader, Th)
7353	{
7354		if (!Activator || FlashFader(Th).ForWho == Activator)
7355		{
7356			FlashFader(Th).Cancel();
7357		}
7358	}
7359}
7360
7361//==========================================================================
7362//
7363//  P_Massacre
7364//
7365//  Kills all monsters.
7366//
7367//==========================================================================
7368
7369final int P_Massacre()
7370{
7371	int count;
7372	EntityEx mo;
7373
7374	count = 0;
7375	foreach AllThinkers(EntityEx, mo)
7376	{
7377		if (mo.bMonster && mo.Health > 0)
7378		{
7379			mo.bNonShootable = false;
7380			mo.bInvulnerable = false;
7381			mo.bShootable = true;
7382			mo.Damage(none, none, 10000);
7383			count++;
7384		}
7385	}
7386	return count;
7387}
7388
7389//==========================================================================
7390//
7391//	SetMarineWeapon
7392//
7393//==========================================================================
7394
7395final void SetMarineWeapon(int Tid, int Weapon, Entity Activator)
7396{
7397	if (Tid)
7398	{
7399		Entity Ent;
7400		for (Ent = Level.FindMobjFromTID(Tid, none); Ent;
7401			Ent = Level.FindMobjFromTID(Tid, Ent))
7402		{
7403			if (ScriptedMarine(Ent))
7404			{
7405				ScriptedMarine(Ent).SetWeapon(Weapon);
7406			}
7407		}
7408	}
7409	else if (ScriptedMarine(Activator))
7410	{
7411		ScriptedMarine(Activator).SetWeapon(Weapon);
7412	}
7413}
7414
7415//==========================================================================
7416//
7417//	SetMarineSprite
7418//
7419//==========================================================================
7420
7421final void SetMarineSprite(int Tid, name SrcClassName, Entity Activator)
7422{
7423	//	If there's no such class, print message and do nothing.
7424	class TmpClass = FindClass(SrcClassName);
7425	if (!TmpClass)
7426	{
7427		print("Unknown class %n", SrcClassName);
7428		return;
7429	}
7430	//	If it's not a valid actor class, it will set sprite back to default.
7431	class<EntityEx> SrcClass = class<EntityEx>(TmpClass);
7432
7433	if (Tid)
7434	{
7435		Entity Ent;
7436		for (Ent = Level.FindMobjFromTID(Tid, none); Ent;
7437			Ent = Level.FindMobjFromTID(Tid, Ent))
7438		{
7439			if (ScriptedMarine(Ent))
7440			{
7441				ScriptedMarine(Ent).SetSprite(SrcClass);
7442			}
7443		}
7444	}
7445	else if (ScriptedMarine(Activator))
7446	{
7447		ScriptedMarine(Activator).SetSprite(SrcClass);
7448	}
7449}
7450
7451//==========================================================================
7452//
7453//  GetDefaultDoorSound
7454//
7455//==========================================================================
7456
7457name GetDefaultDoorSound(sector_t* Sector)
7458{
7459	return DefaultDoorSound;
7460}
7461
7462//==========================================================================
7463//
7464//  GetClassSpawnFlags
7465//
7466//==========================================================================
7467
7468int GetPClassSpawnFlags()
7469{
7470	return 0;
7471}
7472
7473//==========================================================================
7474//
7475//  GetDehackedItemType
7476//
7477//==========================================================================
7478
7479class<Inventory> GetDehackedItemType(EntityEx Ent)
7480{
7481	return none;
7482}
7483
7484defaultproperties
7485{
7486	ExtPlayersBase = 4001;
7487	Lock103Message = "That doesn't seem to work";
7488	BodyQueSize = BODYQUESIZE;
7489	CorpseQueSize = CORPSEQUEUESIZE;
7490	DefaultDoorSound = 'DoorNormal';
7491	DefaultCeilingSound = 'CeilingNormal';
7492	DefaultSilentCeilingSound = 'CeilingSemiSilent';
7493	DefaultFloorSound = 'Floor';
7494	DefaultFloorAltSound = 'Floor';
7495	DefaultStairStepSound = 'Floor';
7496	DefaultPlatformSound = 'Platform';
7497}
7498