1 /*
2 ** p_acs.cpp
3 ** General BEHAVIOR management and ACS execution environment
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2012 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 ** This code at one time made lots of little-endian assumptions.
34 ** I think it should be fine on big-endian machines now, but I have no
35 ** real way to test it.
36 */
37 
38 #include <assert.h>
39 
40 #include "templates.h"
41 #include "doomdef.h"
42 #include "p_local.h"
43 #include "p_spec.h"
44 #include "g_level.h"
45 #include "s_sound.h"
46 #include "p_acs.h"
47 #include "p_saveg.h"
48 #include "p_lnspec.h"
49 #include "p_enemy.h"
50 #include "m_random.h"
51 #include "doomstat.h"
52 #include "c_console.h"
53 #include "c_dispatch.h"
54 #include "s_sndseq.h"
55 #include "i_system.h"
56 #include "i_movie.h"
57 #include "sbar.h"
58 #include "m_swap.h"
59 #include "a_sharedglobal.h"
60 #include "a_doomglobal.h"
61 #include "a_strifeglobal.h"
62 #include "v_video.h"
63 #include "w_wad.h"
64 #include "r_sky.h"
65 #include "gstrings.h"
66 #include "gi.h"
67 #include "sc_man.h"
68 #include "c_bind.h"
69 #include "info.h"
70 #include "r_data/r_translate.h"
71 #include "cmdlib.h"
72 #include "m_png.h"
73 #include "p_setup.h"
74 #include "po_man.h"
75 #include "actorptrselect.h"
76 #include "farchive.h"
77 #include "decallib.h"
78 #include "p_terrain.h"
79 #include "version.h"
80 #include "p_effect.h"
81 
82 #include "g_shared/a_pickups.h"
83 
84 extern FILE *Logfile;
85 
86 FRandom pr_acs ("ACS");
87 
88 // I imagine this much stack space is probably overkill, but it could
89 // potentially get used with recursive functions.
90 #define STACK_SIZE 4096
91 
92 #define CLAMPCOLOR(c)		(EColorRange)((unsigned)(c) >= NUM_TEXT_COLORS ? CR_UNTRANSLATED : (c))
93 #define LANGREGIONMASK		MAKE_ID(0,0,0xff,0xff)
94 
95 // HUD message flags
96 #define HUDMSG_LOG					(0x80000000)
97 #define HUDMSG_COLORSTRING			(0x40000000)
98 #define HUDMSG_ADDBLEND				(0x20000000)
99 #define HUDMSG_ALPHA				(0x10000000)
100 #define HUDMSG_NOWRAP				(0x08000000)
101 
102 // HUD message layers; these are not flags
103 #define HUDMSG_LAYER_SHIFT			12
104 #define HUDMSG_LAYER_MASK			(0x0000F000)
105 // See HUDMSGLayer enumerations in sbar.h
106 
107 // HUD message visibility flags
108 #define HUDMSG_VISIBILITY_SHIFT		16
109 #define HUDMSG_VISIBILITY_MASK		(0x00070000)
110 // See HUDMSG visibility enumerations in sbar.h
111 
112 // Flags for ReplaceTextures
113 #define NOT_BOTTOM			1
114 #define NOT_MIDDLE			2
115 #define NOT_TOP				4
116 #define NOT_FLOOR			8
117 #define NOT_CEILING			16
118 
119 // LineAttack flags
120 #define FHF_NORANDOMPUFFZ	1
121 #define FHF_NOIMPACTDECAL	2
122 
123 // SpawnDecal flags
124 #define SDF_ABSANGLE		1
125 #define SDF_PERMANENT		2
126 
127 // GetArmorInfo
128 enum
129 {
130 	ARMORINFO_CLASSNAME,
131 	ARMORINFO_SAVEAMOUNT,
132 	ARMORINFO_SAVEPERCENT,
133 	ARMORINFO_MAXABSORB,
134 	ARMORINFO_MAXFULLABSORB,
135 	ARMORINFO_ACTUALSAVEAMOUNT,
136 };
137 
138 // PickActor
139 // [JP] I've renamed these flags to something else to avoid confusion with the other PAF_ flags
140 enum
141 {
142 //	PAF_FORCETID,
143 //	PAF_RETURNTID
144 	PICKAF_FORCETID = 1,
145 	PICKAF_RETURNTID = 2,
146 };
147 
148 struct CallReturn
149 {
CallReturnCallReturn150 	CallReturn(int pc, ScriptFunction *func, FBehavior *module, SDWORD *locals, ACSLocalArrays *arrays, bool discard, unsigned int runaway)
151 		: ReturnFunction(func),
152 		  ReturnModule(module),
153 		  ReturnLocals(locals),
154 		  ReturnArrays(arrays),
155 		  ReturnAddress(pc),
156 		  bDiscardResult(discard),
157 		  EntryInstrCount(runaway)
158 	{}
159 
160 	ScriptFunction *ReturnFunction;
161 	FBehavior *ReturnModule;
162 	SDWORD *ReturnLocals;
163 	ACSLocalArrays *ReturnArrays;
164 	int ReturnAddress;
165 	int bDiscardResult;
166 	unsigned int EntryInstrCount;
167 };
168 
169 static DLevelScript *P_GetScriptGoing (AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module,
170 	const int *args, int argcount, int flags);
171 
172 
173 struct FBehavior::ArrayInfo
174 {
175 	DWORD ArraySize;
176 	SDWORD *Elements;
177 };
178 
179 TArray<FBehavior *> FBehavior::StaticModules;
180 TArray<FString> ACS_StringBuilderStack;
181 
182 #define STRINGBUILDER_START(Builder) if (Builder.IsNotEmpty() || ACS_StringBuilderStack.Size()) { ACS_StringBuilderStack.Push(Builder); Builder = ""; }
183 #define STRINGBUILDER_FINISH(Builder) if (!ACS_StringBuilderStack.Pop(Builder)) { Builder = ""; }
184 
185 //============================================================================
186 //
187 // uallong
188 //
189 // Read a possibly unaligned four-byte little endian integer from memory.
190 //
191 //============================================================================
192 
193 #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__)
uallong(const int & foo)194 inline int uallong(const int &foo)
195 {
196 	return foo;
197 }
198 #else
uallong(const int & foo)199 inline int uallong(const int &foo)
200 {
201 	const unsigned char *bar = (const unsigned char *)&foo;
202 	return bar[0] | (bar[1] << 8) | (bar[2] << 16) | (bar[3] << 24);
203 }
204 #endif
205 
206 //============================================================================
207 //
208 // Global and world variables
209 //
210 //============================================================================
211 
212 // ACS variables with world scope
213 SDWORD ACS_WorldVars[NUM_WORLDVARS];
214 FWorldGlobalArray ACS_WorldArrays[NUM_WORLDVARS];
215 
216 // ACS variables with global scope
217 SDWORD ACS_GlobalVars[NUM_GLOBALVARS];
218 FWorldGlobalArray ACS_GlobalArrays[NUM_GLOBALVARS];
219 
220 //----------------------------------------------------------------------------
221 //
222 // ACS stack manager
223 //
224 // This is needed so that the garbage collector has access to all active
225 // script stacks
226 //
227 //----------------------------------------------------------------------------
228 
229 struct FACSStack
230 {
231 	SDWORD buffer[STACK_SIZE];
232 	int sp;
233 	FACSStack *next;
234 	FACSStack *prev;
235 	static FACSStack *head;
236 
237 	FACSStack();
238 	~FACSStack();
239 };
240 
241 FACSStack *FACSStack::head;
242 
FACSStack()243 FACSStack::FACSStack()
244 {
245 	sp = 0;
246 	next = head;
247 	prev = NULL;
248 	head = this;
249 }
250 
~FACSStack()251 FACSStack::~FACSStack()
252 {
253 	if (next != NULL) next->prev = prev;
254 	if (prev == NULL)
255 	{
256 		head = next;
257 	}
258 	else
259 	{
260 		prev->next = next;
261 	}
262 }
263 
264 //----------------------------------------------------------------------------
265 //
266 // Global ACS strings (Formerly known as On the fly strings)
267 //
268 // This special string table is part of the global state. Programmatically
269 // generated strings (e.g. those returned by strparam) are stored here.
270 // PCD_TAGSTRING also now stores strings in this table instead of simply
271 // tagging strings with their library ID.
272 //
273 // Identical strings map to identical string identifiers.
274 //
275 // When the string table needs to grow to hold more strings, a garbage
276 // collection is first attempted to see if more room can be made to store
277 // strings without growing. A string is considered in use if any value
278 // in any of these variable blocks contains a valid ID in the global string
279 // table:
280 //   * The active area of the ACS stack
281 //   * All running scripts' local variables
282 //   * All map variables
283 //   * All world variables
284 //   * All global variables
285 // It's not important whether or not they are really used as strings, only
286 // that they might be. A string is also considered in use if its lock count
287 // is non-zero, even if none of the above variable blocks referenced it.
288 //
289 // To keep track of local and map variables for nonresident maps in a hub,
290 // when a map's state is archived, all strings found in its local and map
291 // variables are locked. When a map is revisited in a hub, all strings found
292 // in its local and map variables are unlocked. Locking and unlocking are
293 // cumulative operations.
294 //
295 // What this all means is that:
296 //   * Strings returned by strparam last indefinitely. No longer do they
297 //     disappear at the end of the tic they were generated.
298 //   * You can pass library strings around freely without having to worry
299 //     about always having the same libraries loaded in the same order on
300 //     every map that needs to use those strings.
301 //
302 //----------------------------------------------------------------------------
303 
304 ACSStringPool GlobalACSStrings;
305 
ACSStringPool()306 ACSStringPool::ACSStringPool()
307 {
308 	memset(PoolBuckets, 0xFF, sizeof(PoolBuckets));
309 	FirstFreeEntry = 0;
310 }
311 
312 //============================================================================
313 //
314 // ACSStringPool :: Clear
315 //
316 // Remove all strings from the pool.
317 //
318 //============================================================================
319 
Clear()320 void ACSStringPool::Clear()
321 {
322 	Pool.Clear();
323 	memset(PoolBuckets, 0xFF, sizeof(PoolBuckets));
324 	FirstFreeEntry = 0;
325 }
326 
327 //============================================================================
328 //
329 // ACSStringPool :: AddString
330 //
331 // Returns a valid string identifier (including library ID) or -1 if we ran
332 // out of room. Identical strings will return identical values.
333 //
334 //============================================================================
335 
AddString(const char * str)336 int ACSStringPool::AddString(const char *str)
337 {
338 	size_t len = strlen(str);
339 	unsigned int h = SuperFastHash(str, len);
340 	unsigned int bucketnum = h % NUM_BUCKETS;
341 	int i = FindString(str, len, h, bucketnum);
342 	if (i >= 0)
343 	{
344 		return i | STRPOOL_LIBRARYID_OR;
345 	}
346 	FString fstr(str);
347 	return InsertString(fstr, h, bucketnum);
348 }
349 
AddString(FString & str)350 int ACSStringPool::AddString(FString &str)
351 {
352 	unsigned int h = SuperFastHash(str.GetChars(), str.Len());
353 	unsigned int bucketnum = h % NUM_BUCKETS;
354 	int i = FindString(str, str.Len(), h, bucketnum);
355 	if (i >= 0)
356 	{
357 		return i | STRPOOL_LIBRARYID_OR;
358 	}
359 	return InsertString(str, h, bucketnum);
360 }
361 
362 //============================================================================
363 //
364 // ACSStringPool :: GetString
365 //
366 //============================================================================
367 
GetString(int strnum)368 const char *ACSStringPool::GetString(int strnum)
369 {
370 	assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR);
371 	strnum &= ~LIBRARYID_MASK;
372 	if ((unsigned)strnum < Pool.Size() && Pool[strnum].Next != FREE_ENTRY)
373 	{
374 		return Pool[strnum].Str;
375 	}
376 	return NULL;
377 }
378 
379 //============================================================================
380 //
381 // ACSStringPool :: LockString
382 //
383 // Prevents this string from being purged.
384 //
385 //============================================================================
386 
LockString(int strnum)387 void ACSStringPool::LockString(int strnum)
388 {
389 	assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR);
390 	strnum &= ~LIBRARYID_MASK;
391 	assert((unsigned)strnum < Pool.Size());
392 	Pool[strnum].LockCount++;
393 }
394 
395 //============================================================================
396 //
397 // ACSStringPool :: UnlockString
398 //
399 // When equally mated with LockString, allows this string to be purged.
400 //
401 //============================================================================
402 
UnlockString(int strnum)403 void ACSStringPool::UnlockString(int strnum)
404 {
405 	assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR);
406 	strnum &= ~LIBRARYID_MASK;
407 	assert((unsigned)strnum < Pool.Size());
408 	assert(Pool[strnum].LockCount > 0);
409 	Pool[strnum].LockCount--;
410 }
411 
412 //============================================================================
413 //
414 // ACSStringPool :: MarkString
415 //
416 // Prevent this string from being purged during the next call to PurgeStrings.
417 // This does not carry over to subsequent calls of PurgeStrings.
418 //
419 //============================================================================
420 
MarkString(int strnum)421 void ACSStringPool::MarkString(int strnum)
422 {
423 	assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR);
424 	strnum &= ~LIBRARYID_MASK;
425 	assert((unsigned)strnum < Pool.Size());
426 	Pool[strnum].LockCount |= 0x80000000;
427 }
428 
429 //============================================================================
430 //
431 // ACSStringPool :: LockStringArray
432 //
433 // Prevents several strings from being purged. Entries not in this pool will
434 // be silently ignored. The idea here is to pass this function a block of
435 // ACS variables. Everything that looks like it might be a string in the pool
436 // is locked, even if it's not actually used as such. It's better to keep
437 // more strings than we need than to throw away ones we do need.
438 //
439 //============================================================================
440 
LockStringArray(const int * strnum,unsigned int count)441 void ACSStringPool::LockStringArray(const int *strnum, unsigned int count)
442 {
443 	for (unsigned int i = 0; i < count; ++i)
444 	{
445 		int num = strnum[i];
446 		if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR)
447 		{
448 			num &= ~LIBRARYID_MASK;
449 			if ((unsigned)num < Pool.Size())
450 			{
451 				Pool[num].LockCount++;
452 			}
453 		}
454 	}
455 }
456 
457 //============================================================================
458 //
459 // ACSStringPool :: UnlockStringArray
460 //
461 // Reverse of LockStringArray.
462 //
463 //============================================================================
464 
UnlockStringArray(const int * strnum,unsigned int count)465 void ACSStringPool::UnlockStringArray(const int *strnum, unsigned int count)
466 {
467 	for (unsigned int i = 0; i < count; ++i)
468 	{
469 		int num = strnum[i];
470 		if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR)
471 		{
472 			num &= ~LIBRARYID_MASK;
473 			if ((unsigned)num < Pool.Size())
474 			{
475 				assert(Pool[num].LockCount > 0);
476 				Pool[num].LockCount--;
477 			}
478 		}
479 	}
480 }
481 
482 //============================================================================
483 //
484 // ACSStringPool :: MarkStringArray
485 //
486 // Array version of MarkString.
487 //
488 //============================================================================
489 
MarkStringArray(const int * strnum,unsigned int count)490 void ACSStringPool::MarkStringArray(const int *strnum, unsigned int count)
491 {
492 	for (unsigned int i = 0; i < count; ++i)
493 	{
494 		int num = strnum[i];
495 		if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR)
496 		{
497 			num &= ~LIBRARYID_MASK;
498 			if ((unsigned)num < Pool.Size())
499 			{
500 				Pool[num].LockCount |= 0x80000000;
501 			}
502 		}
503 	}
504 }
505 
506 //============================================================================
507 //
508 // ACSStringPool :: MarkStringMap
509 //
510 // World/global variables version of MarkString.
511 //
512 //============================================================================
513 
MarkStringMap(const FWorldGlobalArray & aray)514 void ACSStringPool::MarkStringMap(const FWorldGlobalArray &aray)
515 {
516 	FWorldGlobalArray::ConstIterator it(aray);
517 	FWorldGlobalArray::ConstPair *pair;
518 
519 	while (it.NextPair(pair))
520 	{
521 		int num = pair->Value;
522 		if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR)
523 		{
524 			num &= ~LIBRARYID_MASK;
525 			if ((unsigned)num < Pool.Size())
526 			{
527 				Pool[num].LockCount |= 0x80000000;
528 			}
529 		}
530 	}
531 }
532 
533 //============================================================================
534 //
535 // ACSStringPool :: UnlockAll
536 //
537 // Resets every entry's lock count to 0. Used when doing a partial reset of
538 // ACS state such as travelling to a new hub.
539 //
540 //============================================================================
541 
UnlockAll()542 void ACSStringPool::UnlockAll()
543 {
544 	for (unsigned int i = 0; i < Pool.Size(); ++i)
545 	{
546 		Pool[i].LockCount = 0;
547 	}
548 }
549 
550 //============================================================================
551 //
552 // ACSStringPool :: PurgeStrings
553 //
554 // Remove all unlocked strings from the pool.
555 //
556 //============================================================================
557 
PurgeStrings()558 void ACSStringPool::PurgeStrings()
559 {
560 	// Clear the hash buckets. We'll rebuild them as we decide what strings
561 	// to keep and which to toss.
562 	memset(PoolBuckets, 0xFF, sizeof(PoolBuckets));
563 	size_t usedcount = 0, freedcount = 0;
564 	for (unsigned int i = 0; i < Pool.Size(); ++i)
565 	{
566 		PoolEntry *entry = &Pool[i];
567 		if (entry->Next != FREE_ENTRY)
568 		{
569 			if (entry->LockCount == 0)
570 			{
571 				freedcount++;
572 				// Mark this entry as free.
573 				entry->Next = FREE_ENTRY;
574 				if (i < FirstFreeEntry)
575 				{
576 					FirstFreeEntry = i;
577 				}
578 				// And free the string.
579 				entry->Str = "";
580 			}
581 			else
582 			{
583 				usedcount++;
584 				// Rehash this entry.
585 				unsigned int h = entry->Hash % NUM_BUCKETS;
586 				entry->Next = PoolBuckets[h];
587 				PoolBuckets[h] = i;
588 				// Remove MarkString's mark.
589 				entry->LockCount &= 0x7FFFFFFF;
590 			}
591 		}
592 	}
593 }
594 
595 //============================================================================
596 //
597 // ACSStringPool :: FindString
598 //
599 // Finds a string in the pool. Does not include the library ID in the returned
600 // value. Returns -1 if the string does not exist in the pool.
601 //
602 //============================================================================
603 
FindString(const char * str,size_t len,unsigned int h,unsigned int bucketnum)604 int ACSStringPool::FindString(const char *str, size_t len, unsigned int h, unsigned int bucketnum)
605 {
606 	unsigned int i = PoolBuckets[bucketnum];
607 	while (i != NO_ENTRY)
608 	{
609 		PoolEntry *entry = &Pool[i];
610 		assert(entry->Next != FREE_ENTRY);
611 		if (entry->Hash == h && entry->Str.Len() == len &&
612 			memcmp(entry->Str.GetChars(), str, len) == 0)
613 		{
614 			return i;
615 		}
616 		i = entry->Next;
617 	}
618 	return -1;
619 }
620 
621 //============================================================================
622 //
623 // ACSStringPool :: InsertString
624 //
625 // Inserts a new string into the pool.
626 //
627 //============================================================================
628 
InsertString(FString & str,unsigned int h,unsigned int bucketnum)629 int ACSStringPool::InsertString(FString &str, unsigned int h, unsigned int bucketnum)
630 {
631 	unsigned int index = FirstFreeEntry;
632 	if (index >= MIN_GC_SIZE && index == Pool.Max())
633 	{ // We will need to grow the array. Try a garbage collection first.
634 		P_CollectACSGlobalStrings();
635 		index = FirstFreeEntry;
636 	}
637 	if (FirstFreeEntry >= STRPOOL_LIBRARYID_OR)
638 	{ // If we go any higher, we'll collide with the library ID marker.
639 		return -1;
640 	}
641 	if (index == Pool.Size())
642 	{ // There were no free entries; make a new one.
643 		Pool.Reserve(1);
644 		FirstFreeEntry++;
645 	}
646 	else
647 	{ // Scan for the next free entry
648 		FindFirstFreeEntry(FirstFreeEntry + 1);
649 	}
650 	PoolEntry *entry = &Pool[index];
651 	entry->Str = str;
652 	entry->Hash = h;
653 	entry->Next = PoolBuckets[bucketnum];
654 	entry->LockCount = 0;
655 	PoolBuckets[bucketnum] = index;
656 	return index | STRPOOL_LIBRARYID_OR;
657 }
658 
659 //============================================================================
660 //
661 // ACSStringPool :: FindFirstFreeEntry
662 //
663 // Finds the first free entry, starting at base.
664 //
665 //============================================================================
666 
FindFirstFreeEntry(unsigned base)667 void ACSStringPool::FindFirstFreeEntry(unsigned base)
668 {
669 	while (base < Pool.Size() && Pool[base].Next != FREE_ENTRY)
670 	{
671 		base++;
672 	}
673 	FirstFreeEntry = base;
674 }
675 
676 //============================================================================
677 //
678 // ACSStringPool :: ReadStrings
679 //
680 // Reads strings from a PNG chunk.
681 //
682 //============================================================================
683 
ReadStrings(PNGHandle * png,DWORD id)684 void ACSStringPool::ReadStrings(PNGHandle *png, DWORD id)
685 {
686 	Clear();
687 
688 	size_t len = M_FindPNGChunk(png, id);
689 	if (len != 0)
690 	{
691 		FPNGChunkArchive arc(png->File->GetFile(), id, len);
692 		int32 i, j, poolsize;
693 		unsigned int h, bucketnum;
694 		char *str = NULL;
695 
696 		arc << poolsize;
697 
698 		Pool.Resize(poolsize);
699 		i = 0;
700 		j = arc.ReadCount();
701 		while (j >= 0)
702 		{
703 			// Mark skipped entries as free
704 			for (; i < j; ++i)
705 			{
706 				Pool[i].Next = FREE_ENTRY;
707 				Pool[i].LockCount = 0;
708 			}
709 			arc << str;
710 			h = SuperFastHash(str, strlen(str));
711 			bucketnum = h % NUM_BUCKETS;
712 			Pool[i].Str = str;
713 			Pool[i].Hash = h;
714 			Pool[i].LockCount = arc.ReadCount();
715 			Pool[i].Next = PoolBuckets[bucketnum];
716 			PoolBuckets[bucketnum] = i;
717 			i++;
718 			j = arc.ReadCount();
719 		}
720 		if (str != NULL)
721 		{
722 			delete[] str;
723 		}
724 		FindFirstFreeEntry(0);
725 	}
726 }
727 
728 //============================================================================
729 //
730 // ACSStringPool :: WriteStrings
731 //
732 // Writes strings to a PNG chunk.
733 //
734 //============================================================================
735 
WriteStrings(FILE * file,DWORD id) const736 void ACSStringPool::WriteStrings(FILE *file, DWORD id) const
737 {
738 	int32 i, poolsize = (int32)Pool.Size();
739 
740 	if (poolsize == 0)
741 	{ // No need to write if we don't have anything.
742 		return;
743 	}
744 	FPNGChunkArchive arc(file, id);
745 
746 	arc << poolsize;
747 	for (i = 0; i < poolsize; ++i)
748 	{
749 		PoolEntry *entry = &Pool[i];
750 		if (entry->Next != FREE_ENTRY)
751 		{
752 			arc.WriteCount(i);
753 			arc.WriteString(entry->Str);
754 			arc.WriteCount(entry->LockCount);
755 		}
756 	}
757 	arc.WriteCount(-1);
758 }
759 
760 //============================================================================
761 //
762 // ACSStringPool :: Dump
763 //
764 // Lists all strings in the pool.
765 //
766 //============================================================================
767 
Dump() const768 void ACSStringPool::Dump() const
769 {
770 	for (unsigned int i = 0; i < Pool.Size(); ++i)
771 	{
772 		if (Pool[i].Next != FREE_ENTRY)
773 		{
774 			Printf("%4u. (%2d) \"%s\"\n", i, Pool[i].LockCount, Pool[i].Str.GetChars());
775 		}
776 	}
777 	Printf("First free %u\n", FirstFreeEntry);
778 }
779 
780 //============================================================================
781 //
782 // P_MarkWorldVarStrings
783 //
784 //============================================================================
785 
P_MarkWorldVarStrings()786 void P_MarkWorldVarStrings()
787 {
788 	GlobalACSStrings.MarkStringArray(ACS_WorldVars, countof(ACS_WorldVars));
789 	for (size_t i = 0; i < countof(ACS_WorldArrays); ++i)
790 	{
791 		GlobalACSStrings.MarkStringMap(ACS_WorldArrays[i]);
792 	}
793 }
794 
795 //============================================================================
796 //
797 // P_MarkGlobalVarStrings
798 //
799 //============================================================================
800 
P_MarkGlobalVarStrings()801 void P_MarkGlobalVarStrings()
802 {
803 	GlobalACSStrings.MarkStringArray(ACS_GlobalVars, countof(ACS_GlobalVars));
804 	for (size_t i = 0; i < countof(ACS_GlobalArrays); ++i)
805 	{
806 		GlobalACSStrings.MarkStringMap(ACS_GlobalArrays[i]);
807 	}
808 }
809 
810 //============================================================================
811 //
812 // P_CollectACSGlobalStrings
813 //
814 // Garbage collect ACS global strings.
815 //
816 //============================================================================
817 
P_CollectACSGlobalStrings()818 void P_CollectACSGlobalStrings()
819 {
820 	for (FACSStack *stack = FACSStack::head; stack != NULL; stack = stack->next)
821 	{
822 		GlobalACSStrings.MarkStringArray(stack->buffer, stack->sp);
823 	}
824 	FBehavior::StaticMarkLevelVarStrings();
825 	P_MarkWorldVarStrings();
826 	P_MarkGlobalVarStrings();
827 	GlobalACSStrings.PurgeStrings();
828 }
829 
830 #ifdef _DEBUG
CCMD(acsgc)831 CCMD(acsgc)
832 {
833 	P_CollectACSGlobalStrings();
834 }
CCMD(globstr)835 CCMD(globstr)
836 {
837 	GlobalACSStrings.Dump();
838 }
839 #endif
840 
841 //============================================================================
842 //
843 // ScriptPresentation
844 //
845 // Returns a presentable version of the script number.
846 //
847 //============================================================================
848 
ScriptPresentation(int script)849 static FString ScriptPresentation(int script)
850 {
851 	FString out = "script ";
852 
853 	if (script < 0)
854 	{
855 		FName scrname = FName(ENamedName(-script));
856 		if (scrname.IsValidName())
857 		{
858 			out << '"' << scrname.GetChars() << '"';
859 			return out;
860 		}
861 	}
862 	out.AppendFormat("%d", script);
863 	return out;
864 }
865 
866 //============================================================================
867 //
868 // P_ClearACSVars
869 //
870 //============================================================================
871 
P_ClearACSVars(bool alsoglobal)872 void P_ClearACSVars(bool alsoglobal)
873 {
874 	int i;
875 
876 	memset (ACS_WorldVars, 0, sizeof(ACS_WorldVars));
877 	for (i = 0; i < NUM_WORLDVARS; ++i)
878 	{
879 		ACS_WorldArrays[i].Clear ();
880 	}
881 	if (alsoglobal)
882 	{
883 		memset (ACS_GlobalVars, 0, sizeof(ACS_GlobalVars));
884 		for (i = 0; i < NUM_GLOBALVARS; ++i)
885 		{
886 			ACS_GlobalArrays[i].Clear ();
887 		}
888 		// Since we cleared all ACS variables, we know nothing refers to them
889 		// anymore.
890 		GlobalACSStrings.Clear();
891 	}
892 	else
893 	{
894 		// Purge any strings that aren't referenced by global variables, since
895 		// they're the only possible references left.
896 		P_MarkGlobalVarStrings();
897 		GlobalACSStrings.PurgeStrings();
898 	}
899 }
900 
901 //============================================================================
902 //
903 // WriteVars
904 //
905 //============================================================================
906 
WriteVars(FILE * file,SDWORD * vars,size_t count,DWORD id)907 static void WriteVars (FILE *file, SDWORD *vars, size_t count, DWORD id)
908 {
909 	size_t i, j;
910 
911 	for (i = 0; i < count; ++i)
912 	{
913 		if (vars[i] != 0)
914 			break;
915 	}
916 	if (i < count)
917 	{
918 		// Find last non-zero var. Anything beyond the last stored variable
919 		// will be zeroed at load time.
920 		for (j = count-1; j > i; --j)
921 		{
922 			if (vars[j] != 0)
923 				break;
924 		}
925 		FPNGChunkArchive arc (file, id);
926 		for (i = 0; i <= j; ++i)
927 		{
928 			DWORD var = vars[i];
929 			arc << var;
930 		}
931 	}
932 }
933 
934 //============================================================================
935 //
936 //
937 //
938 //============================================================================
939 
ReadVars(PNGHandle * png,SDWORD * vars,size_t count,DWORD id)940 static void ReadVars (PNGHandle *png, SDWORD *vars, size_t count, DWORD id)
941 {
942 	size_t len = M_FindPNGChunk (png, id);
943 	size_t used = 0;
944 
945 	if (len != 0)
946 	{
947 		DWORD var;
948 		size_t i;
949 		FPNGChunkArchive arc (png->File->GetFile(), id, len);
950 		used = len / 4;
951 
952 		for (i = 0; i < used; ++i)
953 		{
954 			arc << var;
955 			vars[i] = var;
956 		}
957 		png->File->ResetFilePtr();
958 	}
959 	if (used < count)
960 	{
961 		memset (&vars[used], 0, (count-used)*4);
962 	}
963 }
964 
965 //============================================================================
966 //
967 //
968 //
969 //============================================================================
970 
WriteArrayVars(FILE * file,FWorldGlobalArray * vars,unsigned int count,DWORD id)971 static void WriteArrayVars (FILE *file, FWorldGlobalArray *vars, unsigned int count, DWORD id)
972 {
973 	unsigned int i, j;
974 
975 	// Find the first non-empty array.
976 	for (i = 0; i < count; ++i)
977 	{
978 		if (vars[i].CountUsed() != 0)
979 			break;
980 	}
981 	if (i < count)
982 	{
983 		// Find last non-empty array. Anything beyond the last stored array
984 		// will be emptied at load time.
985 		for (j = count-1; j > i; --j)
986 		{
987 			if (vars[j].CountUsed() != 0)
988 				break;
989 		}
990 		FPNGChunkArchive arc (file, id);
991 		arc.WriteCount (i);
992 		arc.WriteCount (j);
993 		for (; i <= j; ++i)
994 		{
995 			arc.WriteCount (vars[i].CountUsed());
996 
997 			FWorldGlobalArray::ConstIterator it(vars[i]);
998 			const FWorldGlobalArray::Pair *pair;
999 
1000 			while (it.NextPair (pair))
1001 			{
1002 				arc.WriteCount (pair->Key);
1003 				arc.WriteCount (pair->Value);
1004 			}
1005 		}
1006 	}
1007 }
1008 
1009 //============================================================================
1010 //
1011 //
1012 //
1013 //============================================================================
1014 
ReadArrayVars(PNGHandle * png,FWorldGlobalArray * vars,size_t count,DWORD id)1015 static void ReadArrayVars (PNGHandle *png, FWorldGlobalArray *vars, size_t count, DWORD id)
1016 {
1017 	size_t len = M_FindPNGChunk (png, id);
1018 	unsigned int i, k;
1019 
1020 	for (i = 0; i < count; ++i)
1021 	{
1022 		vars[i].Clear ();
1023 	}
1024 
1025 	if (len != 0)
1026 	{
1027 		DWORD max, size;
1028 		FPNGChunkArchive arc (png->File->GetFile(), id, len);
1029 
1030 		i = arc.ReadCount ();
1031 		max = arc.ReadCount ();
1032 
1033 		for (; i <= max; ++i)
1034 		{
1035 			size = arc.ReadCount ();
1036 			for (k = 0; k < size; ++k)
1037 			{
1038 				SDWORD key, val;
1039 				key = arc.ReadCount();
1040 
1041 				val = arc.ReadCount();
1042 				vars[i].Insert (key, val);
1043 			}
1044 		}
1045 		png->File->ResetFilePtr();
1046 	}
1047 }
1048 
1049 //============================================================================
1050 //
1051 //
1052 //
1053 //============================================================================
1054 
P_ReadACSVars(PNGHandle * png)1055 void P_ReadACSVars(PNGHandle *png)
1056 {
1057 	ReadVars (png, ACS_WorldVars, NUM_WORLDVARS, MAKE_ID('w','v','A','r'));
1058 	ReadVars (png, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r'));
1059 	ReadArrayVars (png, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r'));
1060 	ReadArrayVars (png, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r'));
1061 	GlobalACSStrings.ReadStrings(png, MAKE_ID('a','s','T','r'));
1062 }
1063 
1064 //============================================================================
1065 //
1066 //
1067 //
1068 //============================================================================
1069 
P_WriteACSVars(FILE * stdfile)1070 void P_WriteACSVars(FILE *stdfile)
1071 {
1072 	WriteVars (stdfile, ACS_WorldVars, NUM_WORLDVARS, MAKE_ID('w','v','A','r'));
1073 	WriteVars (stdfile, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r'));
1074 	WriteArrayVars (stdfile, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r'));
1075 	WriteArrayVars (stdfile, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r'));
1076 	GlobalACSStrings.WriteStrings(stdfile, MAKE_ID('a','s','T','r'));
1077 }
1078 
1079 //---- Inventory functions --------------------------------------//
1080 //
1081 
1082 //============================================================================
1083 //
1084 // ClearInventory
1085 //
1086 // Clears the inventory for one or more actors.
1087 //
1088 //============================================================================
1089 
ClearInventory(AActor * activator)1090 static void ClearInventory (AActor *activator)
1091 {
1092 	if (activator == NULL)
1093 	{
1094 		for (int i = 0; i < MAXPLAYERS; ++i)
1095 		{
1096 			if (playeringame[i])
1097 				players[i].mo->ClearInventory();
1098 		}
1099 	}
1100 	else
1101 	{
1102 		activator->ClearInventory();
1103 	}
1104 }
1105 
1106 //============================================================================
1107 //
1108 // DoGiveInv
1109 //
1110 // Gives an item to a single actor.
1111 //
1112 //============================================================================
1113 
DoGiveInv(AActor * actor,const PClass * info,int amount)1114 static void DoGiveInv (AActor *actor, const PClass *info, int amount)
1115 {
1116 	AWeapon *savedPendingWeap = actor->player != NULL
1117 		? actor->player->PendingWeapon : NULL;
1118 	bool hadweap = actor->player != NULL ? actor->player->ReadyWeapon != NULL : true;
1119 
1120 	AInventory *item = static_cast<AInventory *>(Spawn (info, 0,0,0, NO_REPLACE));
1121 
1122 	// This shouldn't count for the item statistics!
1123 	item->ClearCounters();
1124 	if (info->IsDescendantOf (RUNTIME_CLASS(ABasicArmorPickup)))
1125 	{
1126 		static_cast<ABasicArmorPickup*>(item)->SaveAmount *= amount;
1127 	}
1128 	else if (info->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus)))
1129 	{
1130 		static_cast<ABasicArmorBonus*>(item)->SaveAmount *= amount;
1131 	}
1132 	else
1133 	{
1134 		item->Amount = amount;
1135 	}
1136 	if (!item->CallTryPickup (actor))
1137 	{
1138 		item->Destroy ();
1139 	}
1140 	// If the item was a weapon, don't bring it up automatically
1141 	// unless the player was not already using a weapon.
1142 	if (savedPendingWeap != NULL && hadweap && actor->player != NULL)
1143 	{
1144 		actor->player->PendingWeapon = savedPendingWeap;
1145 	}
1146 }
1147 
1148 //============================================================================
1149 //
1150 // GiveInventory
1151 //
1152 // Gives an item to one or more actors.
1153 //
1154 //============================================================================
1155 
GiveInventory(AActor * activator,const char * type,int amount)1156 static void GiveInventory (AActor *activator, const char *type, int amount)
1157 {
1158 	const PClass *info;
1159 
1160 	if (amount <= 0 || type == NULL)
1161 	{
1162 		return;
1163 	}
1164 	if (stricmp (type, "Armor") == 0)
1165 	{
1166 		type = "BasicArmorPickup";
1167 	}
1168 	info = PClass::FindClass (type);
1169 	if (info == NULL)
1170 	{
1171 		Printf ("ACS: I don't know what %s is.\n", type);
1172 	}
1173 	else if (!info->IsDescendantOf (RUNTIME_CLASS(AInventory)))
1174 	{
1175 		Printf ("ACS: %s is not an inventory item.\n", type);
1176 	}
1177 	else if (activator == NULL)
1178 	{
1179 		for (int i = 0; i < MAXPLAYERS; ++i)
1180 		{
1181 			if (playeringame[i])
1182 				DoGiveInv (players[i].mo, info, amount);
1183 		}
1184 	}
1185 	else
1186 	{
1187 		DoGiveInv (activator, info, amount);
1188 	}
1189 }
1190 
1191 //============================================================================
1192 //
1193 // TakeInventory
1194 //
1195 // Takes an item from one or more actors.
1196 //
1197 //============================================================================
1198 
TakeInventory(AActor * activator,const char * type,int amount)1199 static void TakeInventory (AActor *activator, const char *type, int amount)
1200 {
1201 	const PClass *info;
1202 
1203 	if (type == NULL)
1204 	{
1205 		return;
1206 	}
1207 	if (strcmp (type, "Armor") == 0)
1208 	{
1209 		type = "BasicArmor";
1210 	}
1211 	if (amount <= 0)
1212 	{
1213 		return;
1214 	}
1215 	info = PClass::FindClass (type);
1216 	if (info == NULL)
1217 	{
1218 		return;
1219 	}
1220 	if (activator == NULL)
1221 	{
1222 		for (int i = 0; i < MAXPLAYERS; ++i)
1223 		{
1224 			if (playeringame[i])
1225 				players[i].mo->TakeInventory(info, amount);
1226 		}
1227 	}
1228 	else
1229 	{
1230 		activator->TakeInventory(info, amount);
1231 	}
1232 }
1233 
1234 //============================================================================
1235 //
1236 // DoUseInv
1237 //
1238 // Makes a single actor use an inventory item
1239 //
1240 //============================================================================
1241 
DoUseInv(AActor * actor,const PClass * info)1242 static bool DoUseInv (AActor *actor, const PClass *info)
1243 {
1244 	AInventory *item = actor->FindInventory (info);
1245 	if (item != NULL)
1246 	{
1247 		if (actor->player == NULL)
1248 		{
1249 			return actor->UseInventory(item);
1250 		}
1251 		else
1252 		{
1253 			int cheats;
1254 			bool res;
1255 
1256 			// Bypass CF_TOTALLYFROZEN
1257 			cheats = actor->player->cheats;
1258 			actor->player->cheats &= ~CF_TOTALLYFROZEN;
1259 			res = actor->UseInventory(item);
1260 			actor->player->cheats |= (cheats & CF_TOTALLYFROZEN);
1261 			return res;
1262 		}
1263 	}
1264 	return false;
1265 }
1266 
1267 //============================================================================
1268 //
1269 // UseInventory
1270 //
1271 // makes one or more actors use an inventory item.
1272 //
1273 //============================================================================
1274 
UseInventory(AActor * activator,const char * type)1275 static int UseInventory (AActor *activator, const char *type)
1276 {
1277 	const PClass *info;
1278 	int ret = 0;
1279 
1280 	if (type == NULL)
1281 	{
1282 		return 0;
1283 	}
1284 	info = PClass::FindClass (type);
1285 	if (info == NULL)
1286 	{
1287 		return 0;
1288 	}
1289 	if (activator == NULL)
1290 	{
1291 		for (int i = 0; i < MAXPLAYERS; ++i)
1292 		{
1293 			if (playeringame[i])
1294 				ret += DoUseInv (players[i].mo, info);
1295 		}
1296 	}
1297 	else
1298 	{
1299 		ret = DoUseInv (activator, info);
1300 	}
1301 	return ret;
1302 }
1303 
1304 //============================================================================
1305 //
1306 // CheckInventory
1307 //
1308 // Returns how much of a particular item an actor has.
1309 //
1310 //============================================================================
1311 
CheckInventory(AActor * activator,const char * type,bool max)1312 static int CheckInventory (AActor *activator, const char *type, bool max)
1313 {
1314 	if (activator == NULL || type == NULL)
1315 		return 0;
1316 
1317 	if (stricmp (type, "Armor") == 0)
1318 	{
1319 		type = "BasicArmor";
1320 	}
1321 	else if (stricmp (type, "Health") == 0)
1322 	{
1323 		if (max)
1324 		{
1325 			if (activator->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
1326 				return static_cast<APlayerPawn *>(activator)->MaxHealth;
1327 			else
1328 				return activator->SpawnHealth();
1329 		}
1330 		return activator->health;
1331 	}
1332 
1333 	const PClass *info = PClass::FindClass (type);
1334 	AInventory *item = activator->FindInventory (info);
1335 
1336 	if (max)
1337 	{
1338 		if (item)
1339 			return item->MaxAmount;
1340 		else
1341 			return ((AInventory *)GetDefaultByType (info))->MaxAmount;
1342 	}
1343 	return item ? item->Amount : 0;
1344 }
1345 
1346 //---- Plane watchers ----//
1347 
1348 class DPlaneWatcher : public DThinker
1349 {
1350 	DECLARE_CLASS (DPlaneWatcher, DThinker)
1351 	HAS_OBJECT_POINTERS
1352 public:
1353 	DPlaneWatcher (AActor *it, line_t *line, int lineSide, bool ceiling,
1354 		int tag, int height, int special,
1355 		int arg0, int arg1, int arg2, int arg3, int arg4);
1356 	void Tick ();
1357 	void Serialize (FArchive &arc);
1358 private:
1359 	sector_t *Sector;
1360 	fixed_t WatchD, LastD;
1361 	int Special, Arg0, Arg1, Arg2, Arg3, Arg4;
1362 	TObjPtr<AActor> Activator;
1363 	line_t *Line;
1364 	bool LineSide;
1365 	bool bCeiling;
1366 
DPlaneWatcher()1367 	DPlaneWatcher() {}
1368 };
1369 
1370 IMPLEMENT_POINTY_CLASS (DPlaneWatcher)
DECLARE_POINTER(Activator)1371  DECLARE_POINTER (Activator)
1372 END_POINTERS
1373 
1374 DPlaneWatcher::DPlaneWatcher (AActor *it, line_t *line, int lineSide, bool ceiling,
1375 	int tag, int height, int special,
1376 	int arg0, int arg1, int arg2, int arg3, int arg4)
1377 	: Special (special), Arg0 (arg0), Arg1 (arg1), Arg2 (arg2), Arg3 (arg3), Arg4 (arg4),
1378 	  Activator (it), Line (line), LineSide (!!lineSide), bCeiling (ceiling)
1379 {
1380 	int secnum;
1381 
1382 	secnum = P_FindFirstSectorFromTag (tag);
1383 	if (secnum >= 0)
1384 	{
1385 		secplane_t plane;
1386 
1387 		Sector = &sectors[secnum];
1388 		if (bCeiling)
1389 		{
1390 			plane = Sector->ceilingplane;
1391 		}
1392 		else
1393 		{
1394 			plane = Sector->floorplane;
1395 		}
1396 		LastD = plane.d;
1397 		plane.ChangeHeight (height << FRACBITS);
1398 		WatchD = plane.d;
1399 	}
1400 	else
1401 	{
1402 		Sector = NULL;
1403 		WatchD = LastD = 0;
1404 	}
1405 }
1406 
Serialize(FArchive & arc)1407 void DPlaneWatcher::Serialize (FArchive &arc)
1408 {
1409 	Super::Serialize (arc);
1410 
1411 	arc << Special << Arg0 << Arg1 << Arg2 << Arg3 << Arg4
1412 		<< Sector << bCeiling << WatchD << LastD << Activator
1413 		<< Line << LineSide << bCeiling;
1414 }
1415 
Tick()1416 void DPlaneWatcher::Tick ()
1417 {
1418 	if (Sector == NULL)
1419 	{
1420 		Destroy ();
1421 		return;
1422 	}
1423 
1424 	fixed_t newd;
1425 
1426 	if (bCeiling)
1427 	{
1428 		newd = Sector->ceilingplane.d;
1429 	}
1430 	else
1431 	{
1432 		newd = Sector->floorplane.d;
1433 	}
1434 
1435 	if ((LastD < WatchD && newd >= WatchD) ||
1436 		(LastD > WatchD && newd <= WatchD))
1437 	{
1438 		P_ExecuteSpecial(Special, Line, Activator, LineSide, Arg0, Arg1, Arg2, Arg3, Arg4);
1439 		Destroy ();
1440 	}
1441 
1442 }
1443 
1444 //---- ACS lump manager ----//
1445 
1446 // Load user-specified default modules. This must be called after the level's
1447 // own behavior is loaded (if it has one).
StaticLoadDefaultModules()1448 void FBehavior::StaticLoadDefaultModules ()
1449 {
1450 	// Scan each LOADACS lump and load the specified modules in order
1451 	int lump, lastlump = 0;
1452 
1453 	while ((lump = Wads.FindLump ("LOADACS", &lastlump)) != -1)
1454 	{
1455 		FScanner sc(lump);
1456 		while (sc.GetString())
1457 		{
1458 			int acslump = Wads.CheckNumForName (sc.String, ns_acslibrary);
1459 			if (acslump >= 0)
1460 			{
1461 				StaticLoadModule (acslump);
1462 			}
1463 			else
1464 			{
1465 				Printf (TEXTCOLOR_RED "Could not find autoloaded ACS library %s\n", sc.String);
1466 			}
1467 		}
1468 	}
1469 }
1470 
StaticLoadModule(int lumpnum,FileReader * fr,int len)1471 FBehavior *FBehavior::StaticLoadModule (int lumpnum, FileReader *fr, int len)
1472 {
1473 	if (lumpnum == -1 && fr == NULL) return NULL;
1474 
1475 	for (unsigned int i = 0; i < StaticModules.Size(); ++i)
1476 	{
1477 		if (StaticModules[i]->LumpNum == lumpnum)
1478 		{
1479 			return StaticModules[i];
1480 		}
1481 	}
1482 
1483 	FBehavior * behavior = new FBehavior ();
1484 	if (behavior->Init(lumpnum, fr, len))
1485 	{
1486 		return behavior;
1487 	}
1488 	else
1489 	{
1490 		delete behavior;
1491 		Printf(TEXTCOLOR_RED "%s: invalid ACS module\n", Wads.GetLumpFullName(lumpnum));
1492 		return NULL;
1493 	}
1494 }
1495 
StaticCheckAllGood()1496 bool FBehavior::StaticCheckAllGood ()
1497 {
1498 	for (unsigned int i = 0; i < StaticModules.Size(); ++i)
1499 	{
1500 		if (!StaticModules[i]->IsGood())
1501 		{
1502 			return false;
1503 		}
1504 	}
1505 	return true;
1506 }
1507 
StaticUnloadModules()1508 void FBehavior::StaticUnloadModules ()
1509 {
1510 	for (unsigned int i = StaticModules.Size(); i-- > 0; )
1511 	{
1512 		delete StaticModules[i];
1513 	}
1514 	StaticModules.Clear ();
1515 }
1516 
StaticGetModule(int lib)1517 FBehavior *FBehavior::StaticGetModule (int lib)
1518 {
1519 	if ((size_t)lib >= StaticModules.Size())
1520 	{
1521 		return NULL;
1522 	}
1523 	return StaticModules[lib];
1524 }
1525 
StaticMarkLevelVarStrings()1526 void FBehavior::StaticMarkLevelVarStrings()
1527 {
1528 	// Mark map variables.
1529 	for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum)
1530 	{
1531 		StaticModules[modnum]->MarkMapVarStrings();
1532 	}
1533 	// Mark running scripts' local variables.
1534 	if (DACSThinker::ActiveThinker != NULL)
1535 	{
1536 		for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext())
1537 		{
1538 			script->MarkLocalVarStrings();
1539 		}
1540 	}
1541 }
1542 
StaticLockLevelVarStrings()1543 void FBehavior::StaticLockLevelVarStrings()
1544 {
1545 	// Lock map variables.
1546 	for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum)
1547 	{
1548 		StaticModules[modnum]->LockMapVarStrings();
1549 	}
1550 	// Lock running scripts' local variables.
1551 	if (DACSThinker::ActiveThinker != NULL)
1552 	{
1553 		for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext())
1554 		{
1555 			script->LockLocalVarStrings();
1556 		}
1557 	}
1558 }
1559 
StaticUnlockLevelVarStrings()1560 void FBehavior::StaticUnlockLevelVarStrings()
1561 {
1562 	// Unlock map variables.
1563 	for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum)
1564 	{
1565 		StaticModules[modnum]->UnlockMapVarStrings();
1566 	}
1567 	// Unlock running scripts' local variables.
1568 	if (DACSThinker::ActiveThinker != NULL)
1569 	{
1570 		for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext())
1571 		{
1572 			script->UnlockLocalVarStrings();
1573 		}
1574 	}
1575 }
1576 
MarkMapVarStrings() const1577 void FBehavior::MarkMapVarStrings() const
1578 {
1579 	GlobalACSStrings.MarkStringArray(MapVarStore, NUM_MAPVARS);
1580 	for (int i = 0; i < NumArrays; ++i)
1581 	{
1582 		GlobalACSStrings.MarkStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize);
1583 	}
1584 }
1585 
LockMapVarStrings() const1586 void FBehavior::LockMapVarStrings() const
1587 {
1588 	GlobalACSStrings.LockStringArray(MapVarStore, NUM_MAPVARS);
1589 	for (int i = 0; i < NumArrays; ++i)
1590 	{
1591 		GlobalACSStrings.LockStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize);
1592 	}
1593 }
1594 
UnlockMapVarStrings() const1595 void FBehavior::UnlockMapVarStrings() const
1596 {
1597 	GlobalACSStrings.UnlockStringArray(MapVarStore, NUM_MAPVARS);
1598 	for (int i = 0; i < NumArrays; ++i)
1599 	{
1600 		GlobalACSStrings.UnlockStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize);
1601 	}
1602 }
1603 
StaticSerializeModuleStates(FArchive & arc)1604 void FBehavior::StaticSerializeModuleStates (FArchive &arc)
1605 {
1606 	DWORD modnum;
1607 
1608 	modnum = StaticModules.Size();
1609 	arc << modnum;
1610 
1611 	if (modnum != StaticModules.Size())
1612 	{
1613 		I_Error("Level was saved with a different number of ACS modules. (Have %d, save has %d)", StaticModules.Size(), modnum);
1614 	}
1615 
1616 	for (modnum = 0; modnum < StaticModules.Size(); ++modnum)
1617 	{
1618 		FBehavior *module = StaticModules[modnum];
1619 		int ModSize = module->GetDataSize();
1620 
1621 		if (arc.IsStoring())
1622 		{
1623 			arc.WriteString (module->ModuleName);
1624 			if (SaveVersion >= 4516) arc << ModSize;
1625 		}
1626 		else
1627 		{
1628 			char *modname = NULL;
1629 			arc << modname;
1630 			if (SaveVersion >= 4516) arc << ModSize;
1631 			if (stricmp (modname, module->ModuleName) != 0)
1632 			{
1633 				delete[] modname;
1634 				I_Error("Level was saved with a different set or order of ACS modules. (Have %s, save has %s)", module->ModuleName, modname);
1635 			}
1636 			else if (ModSize != module->GetDataSize())
1637 			{
1638 				delete[] modname;
1639 				I_Error("ACS module %s has changed from what was saved. (Have %d bytes, save has %d bytes)", module->ModuleName, module->GetDataSize(), ModSize);
1640 			}
1641 			delete[] modname;
1642 		}
1643 		module->SerializeVars (arc);
1644 	}
1645 }
1646 
SerializeVars(FArchive & arc)1647 void FBehavior::SerializeVars (FArchive &arc)
1648 {
1649 	SerializeVarSet (arc, MapVarStore, NUM_MAPVARS);
1650 	for (int i = 0; i < NumArrays; ++i)
1651 	{
1652 		SerializeVarSet (arc, ArrayStore[i].Elements, ArrayStore[i].ArraySize);
1653 	}
1654 }
1655 
SerializeVarSet(FArchive & arc,SDWORD * vars,int max)1656 void FBehavior::SerializeVarSet (FArchive &arc, SDWORD *vars, int max)
1657 {
1658 	SDWORD arcval;
1659 	SDWORD first, last;
1660 
1661 	if (arc.IsStoring ())
1662 	{
1663 		// Find first non-zero variable
1664 		for (first = 0; first < max; ++first)
1665 		{
1666 			if (vars[first] != 0)
1667 			{
1668 				break;
1669 			}
1670 		}
1671 
1672 		// Find last non-zero variable
1673 		for (last = max - 1; last >= first; --last)
1674 		{
1675 			if (vars[last] != 0)
1676 			{
1677 				break;
1678 			}
1679 		}
1680 
1681 		if (last < first)
1682 		{ // no non-zero variables
1683 			arcval = 0;
1684 			arc << arcval;
1685 			return;
1686 		}
1687 
1688 		arcval = last - first + 1;
1689 		arc << arcval;
1690 		arcval = first;
1691 		arc << arcval;
1692 
1693 		while (first <= last)
1694 		{
1695 			arc << vars[first];
1696 			++first;
1697 		}
1698 	}
1699 	else
1700 	{
1701 		SDWORD truelast;
1702 
1703 		memset (vars, 0, max*sizeof(*vars));
1704 
1705 		arc << last;
1706 		if (last == 0)
1707 		{
1708 			return;
1709 		}
1710 		arc << first;
1711 		last += first;
1712 		truelast = last;
1713 
1714 		if (last > max)
1715 		{
1716 			last = max;
1717 		}
1718 
1719 		while (first < last)
1720 		{
1721 			arc << vars[first];
1722 			++first;
1723 		}
1724 		while (first < truelast)
1725 		{
1726 			arc << arcval;
1727 			++first;
1728 		}
1729 	}
1730 }
1731 
ParseLocalArrayChunk(void * chunk,ACSLocalArrays * arrays,int offset)1732 static int ParseLocalArrayChunk(void *chunk, ACSLocalArrays *arrays, int offset)
1733 {
1734 	unsigned count = (LittleShort(static_cast<unsigned short>(((unsigned *)chunk)[1]) - 2)) / 4;
1735 	int *sizes = (int *)((BYTE *)chunk + 10);
1736 	arrays->Count = count;
1737 	if (count > 0)
1738 	{
1739 		ACSLocalArrayInfo *info = new ACSLocalArrayInfo[count];
1740 		arrays->Info = info;
1741 		for (unsigned i = 0; i < count; ++i)
1742 		{
1743 			info[i].Size = LittleLong(sizes[i]);
1744 			info[i].Offset = offset;
1745 			offset += info[i].Size;
1746 		}
1747 	}
1748 	// Return the new local variable size, with space for the arrays
1749 	return offset;
1750 }
1751 
FBehavior()1752 FBehavior::FBehavior()
1753 {
1754 	NumScripts = 0;
1755 	NumFunctions = 0;
1756 	NumArrays = 0;
1757 	NumTotalArrays = 0;
1758 	Scripts = NULL;
1759 	Functions = NULL;
1760 	Arrays = NULL;
1761 	ArrayStore = NULL;
1762 	Chunks = NULL;
1763 	Data = NULL;
1764 	Format = ACS_Unknown;
1765 	LumpNum = -1;
1766 	memset (MapVarStore, 0, sizeof(MapVarStore));
1767 	ModuleName[0] = 0;
1768 	FunctionProfileData = NULL;
1769 
1770 }
1771 
1772 
Init(int lumpnum,FileReader * fr,int len)1773 bool FBehavior::Init(int lumpnum, FileReader * fr, int len)
1774 {
1775 	BYTE *object;
1776 	int i;
1777 
1778 	LumpNum = lumpnum;
1779 
1780 	// Now that everything is set up, record this module as being among the loaded modules.
1781 	// We need to do this before resolving any imports, because an import might (indirectly)
1782 	// need to resolve exports in this module. The only things that can be exported are
1783 	// functions and map variables, which must already be present if they're exported, so
1784 	// this is okay.
1785 
1786 	// This must be done first for 2 reasons:
1787 	// 1. If not, corrupt modules cause memory leaks
1788 	// 2. Corrupt modules won't be reported when a level is being loaded if this function quits before
1789 	//    adding it to the list.
1790 
1791 	if (fr == NULL) len = Wads.LumpLength (lumpnum);
1792 
1793 
1794 
1795 	// Any behaviors smaller than 32 bytes cannot possibly contain anything useful.
1796 	// (16 bytes for a completely empty behavior + 12 bytes for one script header
1797 	//  + 4 bytes for PCD_TERMINATE for an old-style object. A new-style object
1798 	// has 24 bytes if it is completely empty. An empty SPTR chunk adds 8 bytes.)
1799 	if (len < 32)
1800 	{
1801 		return false;
1802 	}
1803 
1804 	object = new BYTE[len];
1805 	if (fr == NULL)
1806 	{
1807 		Wads.ReadLump (lumpnum, object);
1808 	}
1809 	else
1810 	{
1811 		fr->Read (object, len);
1812 	}
1813 
1814 	if (object[0] != 'A' || object[1] != 'C' || object[2] != 'S')
1815 	{
1816 		delete[] object;
1817 		return false;
1818 	}
1819 
1820 	switch (object[3])
1821 	{
1822 	case 0:
1823 		Format = ACS_Old;
1824 		break;
1825 	case 'E':
1826 		Format = ACS_Enhanced;
1827 		break;
1828 	case 'e':
1829 		Format = ACS_LittleEnhanced;
1830 		break;
1831 	default:
1832 		delete[] object;
1833 		return false;
1834 	}
1835     LibraryID = StaticModules.Push (this) << LIBRARYID_SHIFT;
1836 
1837 	if (fr == NULL)
1838 	{
1839 		Wads.GetLumpName (ModuleName, lumpnum);
1840 		ModuleName[8] = 0;
1841 	}
1842 	else
1843 	{
1844 		strcpy(ModuleName, "BEHAVIOR");
1845 	}
1846 
1847 	Data = object;
1848 	DataSize = len;
1849 
1850 	if (Format == ACS_Old)
1851 	{
1852 		DWORD dirofs = LittleLong(((DWORD *)object)[1]);
1853 		DWORD pretag = ((DWORD *)(object + dirofs))[-1];
1854 
1855 		Chunks = object + len;
1856 		// Check for redesigned ACSE/ACSe
1857 		if (dirofs >= 6*4 &&
1858 			(pretag == MAKE_ID('A','C','S','e') ||
1859 			 pretag == MAKE_ID('A','C','S','E')))
1860 		{
1861 			Format = (pretag == MAKE_ID('A','C','S','e')) ? ACS_LittleEnhanced : ACS_Enhanced;
1862 			Chunks = object + LittleLong(((DWORD *)(object + dirofs))[-2]);
1863 			// Forget about the compatibility cruft at the end of the lump
1864 			DataSize = LittleLong(((DWORD *)object)[1]) - 8;
1865 		}
1866 	}
1867 	else
1868 	{
1869 		Chunks = object + LittleLong(((DWORD *)object)[1]);
1870 	}
1871 
1872 	LoadScriptsDirectory ();
1873 
1874 	if (Format == ACS_Old)
1875 	{
1876 		StringTable = LittleLong(((DWORD *)Data)[1]);
1877 		StringTable += LittleLong(((DWORD *)(Data + StringTable))[0]) * 12 + 4;
1878 		UnescapeStringTable(Data + StringTable, Data, false);
1879 	}
1880 	else
1881 	{
1882 		UnencryptStrings ();
1883 		BYTE *strings = FindChunk (MAKE_ID('S','T','R','L'));
1884 		if (strings != NULL)
1885 		{
1886 			StringTable = DWORD(strings - Data + 8);
1887 			UnescapeStringTable(strings + 8, NULL, true);
1888 		}
1889 		else
1890 		{
1891 			StringTable = 0;
1892 		}
1893 	}
1894 
1895 	if (Format == ACS_Old)
1896 	{
1897 		// Do initialization for old-style behavior lumps
1898 		for (i = 0; i < NUM_MAPVARS; ++i)
1899 		{
1900 			MapVars[i] = &MapVarStore[i];
1901 		}
1902 		//LibraryID = StaticModules.Push (this) << LIBRARYID_SHIFT;
1903 	}
1904 	else
1905 	{
1906 		DWORD *chunk;
1907 
1908 		// Load functions
1909 		BYTE *funcs;
1910 		Functions = NULL;
1911 		funcs = FindChunk (MAKE_ID('F','U','N','C'));
1912 		if (funcs != NULL)
1913 		{
1914 			NumFunctions = LittleLong(((DWORD *)funcs)[1]) / 8;
1915 			funcs += 8;
1916 			FunctionProfileData = new ACSProfileInfo[NumFunctions];
1917 			Functions = new ScriptFunction[NumFunctions];
1918 			for (i = 0; i < NumFunctions; ++i)
1919 			{
1920 				ScriptFunctionInFile *funcf = &((ScriptFunctionInFile *)funcs)[i];
1921 				ScriptFunction *funcm = &Functions[i];
1922 				funcm->ArgCount = funcf->ArgCount;
1923 				funcm->HasReturnValue = funcf->HasReturnValue;
1924 				funcm->ImportNum = funcf->ImportNum;
1925 				funcm->LocalCount = funcf->LocalCount;
1926 				funcm->Address = LittleLong(funcf->Address);
1927 			}
1928 		}
1929 
1930 		// Load local arrays for functions
1931 		if (NumFunctions > 0)
1932 		{
1933 			for (chunk = (DWORD *)FindChunk(MAKE_ID('F','A','R','Y')); chunk != NULL; chunk = (DWORD *)NextChunk((BYTE *)chunk))
1934 			{
1935 				int size = LittleLong(chunk[1]);
1936 				if (size >= 6)
1937 				{
1938 					unsigned int func_num = LittleShort(((WORD *)chunk)[4]);
1939 					if (func_num < (unsigned int)NumFunctions)
1940 					{
1941 						ScriptFunction *func = &Functions[func_num];
1942 						// Unlike scripts, functions do not include their arg count in their local count.
1943 						func->LocalCount = ParseLocalArrayChunk(chunk, &func->LocalArrays, func->LocalCount + func->ArgCount) - func->ArgCount;
1944 					}
1945 				}
1946 			}
1947 		}
1948 
1949 		// Load JUMP points
1950 		chunk = (DWORD *)FindChunk (MAKE_ID('J','U','M','P'));
1951 		if (chunk != NULL)
1952 		{
1953 			for (i = 0;i < (int)LittleLong(chunk[1]);i += 4)
1954 				JumpPoints.Push(LittleLong(chunk[2 + i/4]));
1955 		}
1956 
1957 		// Initialize this object's map variables
1958 		memset (MapVarStore, 0, sizeof(MapVarStore));
1959 		chunk = (DWORD *)FindChunk (MAKE_ID('M','I','N','I'));
1960 		while (chunk != NULL)
1961 		{
1962 			int numvars = LittleLong(chunk[1])/4 - 1;
1963 			int firstvar = LittleLong(chunk[2]);
1964 			for (i = 0; i < numvars; ++i)
1965 			{
1966 				MapVarStore[i+firstvar] = LittleLong(chunk[3+i]);
1967 			}
1968 			chunk = (DWORD *)NextChunk ((BYTE *)chunk);
1969 		}
1970 
1971 		// Initialize this object's map variable pointers to defaults. They can be changed
1972 		// later once the imported modules are loaded.
1973 		for (i = 0; i < NUM_MAPVARS; ++i)
1974 		{
1975 			MapVars[i] = &MapVarStore[i];
1976 		}
1977 
1978 		// Create arrays for this module
1979 		chunk = (DWORD *)FindChunk (MAKE_ID('A','R','A','Y'));
1980 		if (chunk != NULL)
1981 		{
1982 			NumArrays = LittleLong(chunk[1])/8;
1983 			ArrayStore = new ArrayInfo[NumArrays];
1984 			memset (ArrayStore, 0, sizeof(*Arrays)*NumArrays);
1985 			for (i = 0; i < NumArrays; ++i)
1986 			{
1987 				MapVarStore[LittleLong(chunk[2+i*2])] = i;
1988 				ArrayStore[i].ArraySize = LittleLong(chunk[3+i*2]);
1989 				ArrayStore[i].Elements = new SDWORD[ArrayStore[i].ArraySize];
1990 				memset(ArrayStore[i].Elements, 0, ArrayStore[i].ArraySize*sizeof(DWORD));
1991 			}
1992 		}
1993 
1994 		// Initialize arrays for this module
1995 		chunk = (DWORD *)FindChunk (MAKE_ID('A','I','N','I'));
1996 		while (chunk != NULL)
1997 		{
1998 			int arraynum = MapVarStore[LittleLong(chunk[2])];
1999 			if ((unsigned)arraynum < (unsigned)NumArrays)
2000 			{
2001 				// Use unsigned iterator here to avoid issue with GCC 4.9/5.x
2002 				// optimizer. Might be some undefined behavior in this code,
2003 				// but I don't know what it is.
2004 				unsigned int initsize = MIN<unsigned int> (ArrayStore[arraynum].ArraySize, (LittleLong(chunk[1])-4)/4);
2005 				SDWORD *elems = ArrayStore[arraynum].Elements;
2006 				for (unsigned int j = 0; j < initsize; ++j)
2007 				{
2008 					elems[j] = LittleLong(chunk[3+j]);
2009 				}
2010 			}
2011 			chunk = (DWORD *)NextChunk((BYTE *)chunk);
2012 		}
2013 
2014 		// Start setting up array pointers
2015 		NumTotalArrays = NumArrays;
2016 		chunk = (DWORD *)FindChunk (MAKE_ID('A','I','M','P'));
2017 		if (chunk != NULL)
2018 		{
2019 			NumTotalArrays += LittleLong(chunk[2]);
2020 		}
2021 		if (NumTotalArrays != 0)
2022 		{
2023 			Arrays = new ArrayInfo *[NumTotalArrays];
2024 			for (i = 0; i < NumArrays; ++i)
2025 			{
2026 				Arrays[i] = &ArrayStore[i];
2027 			}
2028 		}
2029 
2030 		// Tag the library ID to any map variables that are initialized with strings
2031 		if (LibraryID != 0)
2032 		{
2033 			chunk = (DWORD *)FindChunk (MAKE_ID('M','S','T','R'));
2034 			if (chunk != NULL)
2035 			{
2036 				for (DWORD i = 0; i < LittleLong(chunk[1])/4; ++i)
2037 				{
2038 					const char *str = LookupString(MapVarStore[LittleLong(chunk[i+2])]);
2039 					if (str != NULL)
2040 					{
2041 						MapVarStore[LittleLong(chunk[i+2])] = GlobalACSStrings.AddString(str);
2042 					}
2043 				}
2044 			}
2045 
2046 			chunk = (DWORD *)FindChunk (MAKE_ID('A','S','T','R'));
2047 			if (chunk != NULL)
2048 			{
2049 				for (DWORD i = 0; i < LittleLong(chunk[1])/4; ++i)
2050 				{
2051 					int arraynum = MapVarStore[LittleLong(chunk[i+2])];
2052 					if ((unsigned)arraynum < (unsigned)NumArrays)
2053 					{
2054 						SDWORD *elems = ArrayStore[arraynum].Elements;
2055 						for (int j = ArrayStore[arraynum].ArraySize; j > 0; --j, ++elems)
2056 						{
2057 //							*elems |= LibraryID;
2058 							const char *str = LookupString(*elems);
2059 							if (str != NULL)
2060 							{
2061 								*elems = GlobalACSStrings.AddString(str);
2062 							}
2063 						}
2064 					}
2065 				}
2066 			}
2067 
2068 			// [BL] Newer version of ASTR for structure aware compilers although we only have one array per chunk
2069 			chunk = (DWORD *)FindChunk (MAKE_ID('A','T','A','G'));
2070 			while (chunk != NULL)
2071 			{
2072 				const BYTE* chunkData = (const BYTE*)(chunk + 2);
2073 				// First byte is version, it should be 0
2074 				if(*chunkData++ == 0)
2075 				{
2076 					int arraynum = MapVarStore[uallong(LittleLong(*(const int*)(chunkData)))];
2077 					chunkData += 4;
2078 					if ((unsigned)arraynum < (unsigned)NumArrays)
2079 					{
2080 						SDWORD *elems = ArrayStore[arraynum].Elements;
2081 						// Ending zeros may be left out.
2082 						for (int j = MIN(LittleLong(chunk[1])-5, ArrayStore[arraynum].ArraySize); j > 0; --j, ++elems, ++chunkData)
2083 						{
2084 							// For ATAG, a value of 0 = Integer, 1 = String, 2 = FunctionPtr
2085 							// Our implementation uses the same tags for both String and FunctionPtr
2086 							if (*chunkData == 2)
2087 							{
2088 								*elems |= LibraryID;
2089 							}
2090 							else if (*chunkData == 1)
2091 							{
2092 								const char *str = LookupString(*elems);
2093 								if (str != NULL)
2094 								{
2095 									*elems = GlobalACSStrings.AddString(str);
2096 								}
2097 							}
2098 						}
2099 					}
2100 				}
2101 
2102 				chunk = (DWORD *)NextChunk ((BYTE *)chunk);
2103 			}
2104 		}
2105 
2106 		// Load required libraries.
2107 		if (NULL != (chunk = (DWORD *)FindChunk (MAKE_ID('L','O','A','D'))))
2108 		{
2109 			const char *const parse = (char *)&chunk[2];
2110 			DWORD i;
2111 
2112 			for (i = 0; i < LittleLong(chunk[1]); )
2113 			{
2114 				if (parse[i])
2115 				{
2116 					FBehavior *module = NULL;
2117 					int lump = Wads.CheckNumForName (&parse[i], ns_acslibrary);
2118 					if (lump < 0)
2119 					{
2120 						Printf (TEXTCOLOR_RED "Could not find ACS library %s.\n", &parse[i]);
2121 					}
2122 					else
2123 					{
2124 						module = StaticLoadModule (lump);
2125 					}
2126 					if (module != NULL) Imports.Push (module);
2127 					do {;} while (parse[++i]);
2128 				}
2129 				++i;
2130 			}
2131 
2132 			// Go through each imported module in order and resolve all imported functions
2133 			// and map variables.
2134 			for (i = 0; i < Imports.Size(); ++i)
2135 			{
2136 				FBehavior *lib = Imports[i];
2137 				int j;
2138 
2139 				if (lib == NULL)
2140 					continue;
2141 
2142 				// Resolve functions
2143 				chunk = (DWORD *)FindChunk(MAKE_ID('F','N','A','M'));
2144 				for (j = 0; j < NumFunctions; ++j)
2145 				{
2146 					ScriptFunction *func = &((ScriptFunction *)Functions)[j];
2147 					if (func->Address == 0 && func->ImportNum == 0)
2148 					{
2149 						int libfunc = lib->FindFunctionName ((char *)(chunk + 2) + LittleLong(chunk[3+j]));
2150 						if (libfunc >= 0)
2151 						{
2152 							ScriptFunction *realfunc = &((ScriptFunction *)lib->Functions)[libfunc];
2153 							// Make sure that the library really defines this function. It might simply
2154 							// be importing it itself.
2155 							if (realfunc->Address != 0 && realfunc->ImportNum == 0)
2156 							{
2157 								func->Address = libfunc;
2158 								func->ImportNum = i+1;
2159 								if (realfunc->ArgCount != func->ArgCount)
2160 								{
2161 									Printf (TEXTCOLOR_ORANGE "Function %s in %s has %d arguments. %s expects it to have %d.\n",
2162 										(char *)(chunk + 2) + LittleLong(chunk[3+j]), lib->ModuleName, realfunc->ArgCount,
2163 										ModuleName, func->ArgCount);
2164 									Format = ACS_Unknown;
2165 								}
2166 								// The next two properties do not affect code compatibility, so it is
2167 								// okay for them to be different in the imported module than they are
2168 								// in this one, as long as we make sure to use the real values.
2169 								func->LocalCount = LittleLong(realfunc->LocalCount);
2170 								func->HasReturnValue = realfunc->HasReturnValue;
2171 							}
2172 						}
2173 					}
2174 				}
2175 
2176 				// Resolve map variables
2177 				chunk = (DWORD *)FindChunk(MAKE_ID('M','I','M','P'));
2178 				if (chunk != NULL)
2179 				{
2180 					char *parse = (char *)&chunk[2];
2181 					for (DWORD j = 0; j < LittleLong(chunk[1]); )
2182 					{
2183 						DWORD varNum = LittleLong(*(DWORD *)&parse[j]);
2184 						j += 4;
2185 						int impNum = lib->FindMapVarName (&parse[j]);
2186 						if (impNum >= 0)
2187 						{
2188 							MapVars[varNum] = &lib->MapVarStore[impNum];
2189 						}
2190 						do {;} while (parse[++j]);
2191 						++j;
2192 					}
2193 				}
2194 
2195 				// Resolve arrays
2196 				if (NumTotalArrays > NumArrays)
2197 				{
2198 					chunk = (DWORD *)FindChunk(MAKE_ID('A','I','M','P'));
2199 					char *parse = (char *)&chunk[3];
2200 					for (DWORD j = 0; j < LittleLong(chunk[2]); ++j)
2201 					{
2202 						DWORD varNum = LittleLong(*(DWORD *)parse);
2203 						parse += 4;
2204 						DWORD expectedSize = LittleLong(*(DWORD *)parse);
2205 						parse += 4;
2206 						int impNum = lib->FindMapArray (parse);
2207 						if (impNum >= 0)
2208 						{
2209 							Arrays[NumArrays + j] = &lib->ArrayStore[impNum];
2210 							MapVarStore[varNum] = NumArrays + j;
2211 							if (lib->ArrayStore[impNum].ArraySize != expectedSize)
2212 							{
2213 								Format = ACS_Unknown;
2214 								Printf (TEXTCOLOR_ORANGE "The array %s in %s has %u elements, but %s expects it to only have %u.\n",
2215 									parse, lib->ModuleName, lib->ArrayStore[impNum].ArraySize,
2216 									ModuleName, expectedSize);
2217 							}
2218 						}
2219 						do {;} while (*++parse);
2220 						++parse;
2221 					}
2222 				}
2223 			}
2224 		}
2225 	}
2226 
2227 	DPrintf ("Loaded %d scripts, %d functions\n", NumScripts, NumFunctions);
2228 	return true;
2229 }
2230 
~FBehavior()2231 FBehavior::~FBehavior ()
2232 {
2233 	if (Scripts != NULL)
2234 	{
2235 		delete[] Scripts;
2236 		Scripts = NULL;
2237 	}
2238 	if (Arrays != NULL)
2239 	{
2240 		delete[] Arrays;
2241 		Arrays = NULL;
2242 	}
2243 	if (ArrayStore != NULL)
2244 	{
2245 		for (int i = 0; i < NumArrays; ++i)
2246 		{
2247 			if (ArrayStore[i].Elements != NULL)
2248 			{
2249 				delete[] ArrayStore[i].Elements;
2250 				ArrayStore[i].Elements = NULL;
2251 			}
2252 		}
2253 		delete[] ArrayStore;
2254 		ArrayStore = NULL;
2255 	}
2256 	if (Functions != NULL)
2257 	{
2258 		delete[] Functions;
2259 		Functions = NULL;
2260 	}
2261 	if (FunctionProfileData != NULL)
2262 	{
2263 		delete[] FunctionProfileData;
2264 		FunctionProfileData = NULL;
2265 	}
2266 	if (Data != NULL)
2267 	{
2268 		delete[] Data;
2269 		Data = NULL;
2270 	}
2271 }
2272 
LoadScriptsDirectory()2273 void FBehavior::LoadScriptsDirectory ()
2274 {
2275 	union
2276 	{
2277 		BYTE *b;
2278 		DWORD *dw;
2279 		WORD *w;
2280 		SWORD *sw;
2281 		ScriptPtr2 *po;		// Old
2282 		ScriptPtr1 *pi;		// Intermediate
2283 		ScriptPtr3 *pe;		// LittleEnhanced
2284 	} scripts;
2285 	int i, max;
2286 
2287 	NumScripts = 0;
2288 	Scripts = NULL;
2289 
2290 	// Load the main script directory
2291 	switch (Format)
2292 	{
2293 	case ACS_Old:
2294 		scripts.dw = (DWORD *)(Data + LittleLong(((DWORD *)Data)[1]));
2295 		NumScripts = LittleLong(scripts.dw[0]);
2296 		if (NumScripts != 0)
2297 		{
2298 			scripts.dw++;
2299 
2300 			Scripts = new ScriptPtr[NumScripts];
2301 
2302 			for (i = 0; i < NumScripts; ++i)
2303 			{
2304 				ScriptPtr2 *ptr1 = &scripts.po[i];
2305 				ScriptPtr  *ptr2 = &Scripts[i];
2306 
2307 				ptr2->Number = LittleLong(ptr1->Number) % 1000;
2308 				ptr2->Type = LittleLong(ptr1->Number) / 1000;
2309 				ptr2->ArgCount = LittleLong(ptr1->ArgCount);
2310 				ptr2->Address = LittleLong(ptr1->Address);
2311 			}
2312 		}
2313 		break;
2314 
2315 	case ACS_Enhanced:
2316 	case ACS_LittleEnhanced:
2317 		scripts.b = FindChunk (MAKE_ID('S','P','T','R'));
2318 		if (scripts.b == NULL)
2319 		{
2320 			// There are no scripts!
2321 		}
2322 		else if (*(DWORD *)Data != MAKE_ID('A','C','S',0))
2323 		{
2324 			NumScripts = LittleLong(scripts.dw[1]) / 12;
2325 			Scripts = new ScriptPtr[NumScripts];
2326 			scripts.dw += 2;
2327 
2328 			for (i = 0; i < NumScripts; ++i)
2329 			{
2330 				ScriptPtr1 *ptr1 = &scripts.pi[i];
2331 				ScriptPtr  *ptr2 = &Scripts[i];
2332 
2333 				ptr2->Number = LittleShort(ptr1->Number);
2334 				ptr2->Type = BYTE(LittleShort(ptr1->Type));
2335 				ptr2->ArgCount = LittleLong(ptr1->ArgCount);
2336 				ptr2->Address = LittleLong(ptr1->Address);
2337 			}
2338 		}
2339 		else
2340 		{
2341 			NumScripts = LittleLong(scripts.dw[1]) / 8;
2342 			Scripts = new ScriptPtr[NumScripts];
2343 			scripts.dw += 2;
2344 
2345 			for (i = 0; i < NumScripts; ++i)
2346 			{
2347 				ScriptPtr3 *ptr1 = &scripts.pe[i];
2348 				ScriptPtr  *ptr2 = &Scripts[i];
2349 
2350 				ptr2->Number = LittleShort(ptr1->Number);
2351 				ptr2->Type = ptr1->Type;
2352 				ptr2->ArgCount = ptr1->ArgCount;
2353 				ptr2->Address = LittleLong(ptr1->Address);
2354 			}
2355 		}
2356 		break;
2357 
2358 	default:
2359 		break;
2360 	}
2361 
2362 // [EP] Clang 3.5.0 optimizer miscompiles this function and causes random
2363 // crashes in the program. This is fixed in 3.5.1 onwards.
2364 #if defined(__clang__) && __clang_major__ == 3 && __clang_minor__ == 5 && __clang_patchlevel__ == 0
2365 	asm("" : "+g" (NumScripts));
2366 #endif
2367 	for (i = 0; i < NumScripts; ++i)
2368 	{
2369 		Scripts[i].Flags = 0;
2370 		Scripts[i].VarCount = LOCAL_SIZE;
2371 	}
2372 
2373 	// Sort scripts, so we can use a binary search to find them
2374 	if (NumScripts > 1)
2375 	{
2376 		qsort (Scripts, NumScripts, sizeof(ScriptPtr), SortScripts);
2377 		// Check for duplicates because ACC originally did not enforce
2378 		// script number uniqueness across different script types. We
2379 		// only need to do this for old format lumps, because the ACCs
2380 		// that produce new format lumps won't let you do this.
2381 		if (Format == ACS_Old)
2382 		{
2383 			for (i = 0; i < NumScripts - 1; ++i)
2384 			{
2385 				if (Scripts[i].Number == Scripts[i+1].Number)
2386 				{
2387 					Printf(TEXTCOLOR_ORANGE "%s appears more than once.\n",
2388 						ScriptPresentation(Scripts[i].Number).GetChars());
2389 					// Make the closed version the first one.
2390 					if (Scripts[i+1].Type == SCRIPT_Closed)
2391 					{
2392 						swapvalues(Scripts[i], Scripts[i+1]);
2393 					}
2394 				}
2395 			}
2396 		}
2397 	}
2398 
2399 	if (Format == ACS_Old)
2400 		return;
2401 
2402 	// Load script flags
2403 	scripts.b = FindChunk (MAKE_ID('S','F','L','G'));
2404 	if (scripts.dw != NULL)
2405 	{
2406 		max = LittleLong(scripts.dw[1]) / 4;
2407 		scripts.dw += 2;
2408 		for (i = max; i > 0; --i, scripts.w += 2)
2409 		{
2410 			ScriptPtr *ptr = const_cast<ScriptPtr *>(FindScript (LittleShort(scripts.sw[0])));
2411 			if (ptr != NULL)
2412 			{
2413 				ptr->Flags = LittleShort(scripts.w[1]);
2414 			}
2415 		}
2416 	}
2417 
2418 	// Load script var counts. (Only recorded for scripts that use more than LOCAL_SIZE variables.)
2419 	scripts.b = FindChunk (MAKE_ID('S','V','C','T'));
2420 	if (scripts.dw != NULL)
2421 	{
2422 		max = LittleLong(scripts.dw[1]) / 4;
2423 		scripts.dw += 2;
2424 		for (i = max; i > 0; --i, scripts.w += 2)
2425 		{
2426 			ScriptPtr *ptr = const_cast<ScriptPtr *>(FindScript (LittleShort(scripts.sw[0])));
2427 			if (ptr != NULL)
2428 			{
2429 				ptr->VarCount = LittleShort(scripts.w[1]);
2430 			}
2431 		}
2432 	}
2433 
2434 	// Load script array sizes. (One chunk per script that uses arrays.)
2435 	for (scripts.b = FindChunk(MAKE_ID('S','A','R','Y')); scripts.dw != NULL; scripts.b = NextChunk(scripts.b))
2436 	{
2437 		int size = LittleLong(scripts.dw[1]);
2438 		if (size >= 6)
2439 		{
2440 			int script_num = LittleShort(scripts.sw[4]);
2441 			ScriptPtr *ptr = const_cast<ScriptPtr *>(FindScript(script_num));
2442 			if (ptr != NULL)
2443 			{
2444 				ptr->VarCount = ParseLocalArrayChunk(scripts.b, &ptr->LocalArrays, ptr->VarCount);
2445 			}
2446 		}
2447 	}
2448 
2449 	// Load script names (if any)
2450 	scripts.b = FindChunk(MAKE_ID('S','N','A','M'));
2451 	if (scripts.dw != NULL)
2452 	{
2453 		UnescapeStringTable(scripts.b + 8, NULL, false);
2454 		for (i = 0; i < NumScripts; ++i)
2455 		{
2456 			// ACC stores script names as an index into the SNAM chunk, with the first index as
2457 			// -1 and counting down from there. We convert this from an index into SNAM into
2458 			// a negative index into the global name table.
2459 			if (Scripts[i].Number < 0)
2460 			{
2461 				const char *str = (const char *)(scripts.b + 8 + scripts.dw[3 + (-Scripts[i].Number - 1)]);
2462 				FName name(str);
2463 				Scripts[i].Number = -name;
2464 			}
2465 		}
2466 		// We need to resort scripts, because the new numbers for named scripts likely
2467 		// do not match the order they were originally in.
2468 		qsort (Scripts, NumScripts, sizeof(ScriptPtr), SortScripts);
2469 	}
2470 }
2471 
SortScripts(const void * a,const void * b)2472 int STACK_ARGS FBehavior::SortScripts (const void *a, const void *b)
2473 {
2474 	ScriptPtr *ptr1 = (ScriptPtr *)a;
2475 	ScriptPtr *ptr2 = (ScriptPtr *)b;
2476 	return ptr1->Number - ptr2->Number;
2477 }
2478 
2479 //============================================================================
2480 //
2481 // FBehavior :: UnencryptStrings
2482 //
2483 // Descrambles strings in a STRE chunk to transform it into a STRL chunk.
2484 //
2485 //============================================================================
2486 
UnencryptStrings()2487 void FBehavior::UnencryptStrings ()
2488 {
2489 	DWORD *prevchunk = NULL;
2490 	DWORD *chunk = (DWORD *)FindChunk(MAKE_ID('S','T','R','E'));
2491 	while (chunk != NULL)
2492 	{
2493 		for (DWORD strnum = 0; strnum < LittleLong(chunk[3]); ++strnum)
2494 		{
2495 			int ofs = LittleLong(chunk[5+strnum]);
2496 			BYTE *data = (BYTE *)chunk + ofs + 8, last;
2497 			int p = (BYTE)(ofs*157135);
2498 			int i = 0;
2499 			do
2500 			{
2501 				last = (data[i] ^= (BYTE)(p+(i>>1)));
2502 				++i;
2503 			} while (last != 0);
2504 		}
2505 		prevchunk = chunk;
2506 		chunk = (DWORD *)NextChunk ((BYTE *)chunk);
2507 		*prevchunk = MAKE_ID('S','T','R','L');
2508 	}
2509 	if (prevchunk != NULL)
2510 	{
2511 		*prevchunk = MAKE_ID('S','T','R','L');
2512 	}
2513 }
2514 
2515 //============================================================================
2516 //
2517 // FBehavior :: UnescapeStringTable
2518 //
2519 // Processes escape sequences for every string in a string table.
2520 // Chunkstart points to the string table. Datastart points to the base address
2521 // for offsets in the string table; if NULL, it will use chunkstart. If
2522 // has_padding is true, then this is a STRL chunk with four bytes of padding
2523 // on either side of the string count.
2524 //
2525 //============================================================================
2526 
UnescapeStringTable(BYTE * chunkstart,BYTE * datastart,bool has_padding)2527 void FBehavior::UnescapeStringTable(BYTE *chunkstart, BYTE *datastart, bool has_padding)
2528 {
2529 	assert(chunkstart != NULL);
2530 
2531 	DWORD *chunk = (DWORD *)chunkstart;
2532 
2533 	if (datastart == NULL)
2534 	{
2535 		datastart = chunkstart;
2536 	}
2537 	if (!has_padding)
2538 	{
2539 		chunk[0] = LittleLong(chunk[0]);
2540 		for (DWORD strnum = 0; strnum < chunk[0]; ++strnum)
2541 		{
2542 			int ofs = LittleLong(chunk[1 + strnum]);	// Byte swap offset, if needed.
2543 			chunk[1 + strnum] = ofs;
2544 			strbin((char *)datastart + ofs);
2545 		}
2546 	}
2547 	else
2548 	{
2549 		chunk[1] = LittleLong(chunk[1]);
2550 		for (DWORD strnum = 0; strnum < chunk[1]; ++strnum)
2551 		{
2552 			int ofs = LittleLong(chunk[3 + strnum]);	// Byte swap offset, if needed.
2553 			chunk[3 + strnum] = ofs;
2554 			strbin((char *)datastart + ofs);
2555 		}
2556 	}
2557 }
2558 
2559 //============================================================================
2560 //
2561 // FBehavior :: IsGood
2562 //
2563 //============================================================================
2564 
IsGood()2565 bool FBehavior::IsGood ()
2566 {
2567 	bool bad;
2568 	int i;
2569 
2570 	// Check that the data format was understood
2571 	if (Format == ACS_Unknown)
2572 	{
2573 		return false;
2574 	}
2575 
2576 	// Check that all functions are resolved
2577 	bad = false;
2578 	for (i = 0; i < NumFunctions; ++i)
2579 	{
2580 		ScriptFunction *funcdef = (ScriptFunction *)Functions + i;
2581 		if (funcdef->Address == 0 && funcdef->ImportNum == 0)
2582 		{
2583 			DWORD *chunk = (DWORD *)FindChunk (MAKE_ID('F','N','A','M'));
2584 			Printf (TEXTCOLOR_RED "Could not find ACS function %s for use in %s.\n",
2585 				(char *)(chunk + 2) + chunk[3+i], ModuleName);
2586 			bad = true;
2587 		}
2588 	}
2589 
2590 	// Check that all imported modules were loaded
2591 	for (i = Imports.Size() - 1; i >= 0; --i)
2592 	{
2593 		if (Imports[i] == NULL)
2594 		{
2595 			Printf (TEXTCOLOR_RED "Not all the libraries used by %s could be found.\n", ModuleName);
2596 			return false;
2597 		}
2598 	}
2599 
2600 	return !bad;
2601 }
2602 
FindScript(int script) const2603 const ScriptPtr *FBehavior::FindScript (int script) const
2604 {
2605 	const ScriptPtr *ptr = BinarySearch<ScriptPtr, int>
2606 		((ScriptPtr *)Scripts, NumScripts, &ScriptPtr::Number, script);
2607 
2608 	// If the preceding script has the same number, return it instead.
2609 	// See the note by the script sorting above for why.
2610 	if (ptr > Scripts)
2611 	{
2612 		if (ptr[-1].Number == script)
2613 		{
2614 			ptr--;
2615 		}
2616 	}
2617 	return ptr;
2618 }
2619 
StaticFindScript(int script,FBehavior * & module)2620 const ScriptPtr *FBehavior::StaticFindScript (int script, FBehavior *&module)
2621 {
2622 	for (DWORD i = 0; i < StaticModules.Size(); ++i)
2623 	{
2624 		const ScriptPtr *code = StaticModules[i]->FindScript (script);
2625 		if (code != NULL)
2626 		{
2627 			module = StaticModules[i];
2628 			return code;
2629 		}
2630 	}
2631 	return NULL;
2632 }
2633 
GetFunction(int funcnum,FBehavior * & module) const2634 ScriptFunction *FBehavior::GetFunction (int funcnum, FBehavior *&module) const
2635 {
2636 	if ((unsigned)funcnum >= (unsigned)NumFunctions)
2637 	{
2638 		return NULL;
2639 	}
2640 	ScriptFunction *funcdef = (ScriptFunction *)Functions + funcnum;
2641 	if (funcdef->ImportNum)
2642 	{
2643 		return Imports[funcdef->ImportNum - 1]->GetFunction (funcdef->Address, module);
2644 	}
2645 	// Should I just un-const this function instead of using a const_cast?
2646 	module = const_cast<FBehavior *>(this);
2647 	return funcdef;
2648 }
2649 
FindFunctionName(const char * funcname) const2650 int FBehavior::FindFunctionName (const char *funcname) const
2651 {
2652 	return FindStringInChunk ((DWORD *)FindChunk (MAKE_ID('F','N','A','M')), funcname);
2653 }
2654 
FindMapVarName(const char * varname) const2655 int FBehavior::FindMapVarName (const char *varname) const
2656 {
2657 	return FindStringInChunk ((DWORD *)FindChunk (MAKE_ID('M','E','X','P')), varname);
2658 }
2659 
FindMapArray(const char * arrayname) const2660 int FBehavior::FindMapArray (const char *arrayname) const
2661 {
2662 	int var = FindMapVarName (arrayname);
2663 	if (var >= 0)
2664 	{
2665 		return MapVarStore[var];
2666 	}
2667 	return -1;
2668 }
2669 
FindStringInChunk(DWORD * names,const char * varname) const2670 int FBehavior::FindStringInChunk (DWORD *names, const char *varname) const
2671 {
2672 	if (names != NULL)
2673 	{
2674 		DWORD i;
2675 
2676 		for (i = 0; i < LittleLong(names[2]); ++i)
2677 		{
2678 			if (stricmp (varname, (char *)(names + 2) + LittleLong(names[3+i])) == 0)
2679 			{
2680 				return (int)i;
2681 			}
2682 		}
2683 	}
2684 	return -1;
2685 }
2686 
GetArrayVal(int arraynum,int index) const2687 int FBehavior::GetArrayVal (int arraynum, int index) const
2688 {
2689 	if ((unsigned)arraynum >= (unsigned)NumTotalArrays)
2690 		return 0;
2691 	const ArrayInfo *array = Arrays[arraynum];
2692 	if ((unsigned)index >= (unsigned)array->ArraySize)
2693 		return 0;
2694 	return array->Elements[index];
2695 }
2696 
SetArrayVal(int arraynum,int index,int value)2697 void FBehavior::SetArrayVal (int arraynum, int index, int value)
2698 {
2699 	if ((unsigned)arraynum >= (unsigned)NumTotalArrays)
2700 		return;
2701 	const ArrayInfo *array = Arrays[arraynum];
2702 	if ((unsigned)index >= (unsigned)array->ArraySize)
2703 		return;
2704 	array->Elements[index] = value;
2705 }
2706 
CopyStringToArray(int arraynum,int index,int maxLength,const char * string)2707 inline bool FBehavior::CopyStringToArray(int arraynum, int index, int maxLength, const char *string)
2708 {
2709 	 // false if the operation was incomplete or unsuccessful
2710 
2711 	if ((unsigned)arraynum >= (unsigned)NumTotalArrays || index < 0)
2712 		return false;
2713 	const ArrayInfo *array = Arrays[arraynum];
2714 
2715 	if ((signed)array->ArraySize - index < maxLength) maxLength = (signed)array->ArraySize - index;
2716 
2717 	while (maxLength-- > 0)
2718 	{
2719 		array->Elements[index++] = *string;
2720 		if (!(*string)) return true; // written terminating 0
2721 		string++;
2722 	}
2723 	return !(*string); // return true if only terminating 0 was not written
2724 }
2725 
FindChunk(DWORD id) const2726 BYTE *FBehavior::FindChunk (DWORD id) const
2727 {
2728 	BYTE *chunk = Chunks;
2729 
2730 	while (chunk != NULL && chunk < Data + DataSize)
2731 	{
2732 		if (((DWORD *)chunk)[0] == id)
2733 		{
2734 			return chunk;
2735 		}
2736 		chunk += LittleLong(((DWORD *)chunk)[1]) + 8;
2737 	}
2738 	return NULL;
2739 }
2740 
NextChunk(BYTE * chunk) const2741 BYTE *FBehavior::NextChunk (BYTE *chunk) const
2742 {
2743 	DWORD id = *(DWORD *)chunk;
2744 	chunk += LittleLong(((DWORD *)chunk)[1]) + 8;
2745 	while (chunk != NULL && chunk < Data + DataSize)
2746 	{
2747 		if (((DWORD *)chunk)[0] == id)
2748 		{
2749 			return chunk;
2750 		}
2751 		chunk += LittleLong(((DWORD *)chunk)[1]) + 8;
2752 	}
2753 	return NULL;
2754 }
2755 
StaticLookupString(DWORD index)2756 const char *FBehavior::StaticLookupString (DWORD index)
2757 {
2758 	DWORD lib = index >> LIBRARYID_SHIFT;
2759 
2760 	if (lib == STRPOOL_LIBRARYID)
2761 	{
2762 		return GlobalACSStrings.GetString(index);
2763 	}
2764 	if (lib >= (DWORD)StaticModules.Size())
2765 	{
2766 		return NULL;
2767 	}
2768 	return StaticModules[lib]->LookupString (index & 0xffff);
2769 }
2770 
LookupString(DWORD index) const2771 const char *FBehavior::LookupString (DWORD index) const
2772 {
2773 	if (StringTable == 0)
2774 	{
2775 		return NULL;
2776 	}
2777 	if (Format == ACS_Old)
2778 	{
2779 		DWORD *list = (DWORD *)(Data + StringTable);
2780 
2781 		if (index >= list[0])
2782 			return NULL;	// Out of range for this list;
2783 		return (const char *)(Data + list[1+index]);
2784 	}
2785 	else
2786 	{
2787 		DWORD *list = (DWORD *)(Data + StringTable);
2788 
2789 		if (index >= list[1])
2790 			return NULL;	// Out of range for this list
2791 		return (const char *)(Data + StringTable + list[3+index]);
2792 	}
2793 }
2794 
StaticStartTypedScripts(WORD type,AActor * activator,bool always,int arg1,bool runNow)2795 void FBehavior::StaticStartTypedScripts (WORD type, AActor *activator, bool always, int arg1, bool runNow)
2796 {
2797 	static const char *const TypeNames[] =
2798 	{
2799 		"Closed",
2800 		"Open",
2801 		"Respawn",
2802 		"Death",
2803 		"Enter",
2804 		"Pickup",
2805 		"BlueReturn",
2806 		"RedReturn",
2807 		"WhiteReturn",
2808 		"Unknown", "Unknown", "Unknown",
2809 		"Lightning",
2810 		"Unloading",
2811 		"Disconnect",
2812 		"Return"
2813 	};
2814 	DPrintf("Starting all scripts of type %d (%s)\n", type,
2815 		type < countof(TypeNames) ? TypeNames[type] : TypeNames[SCRIPT_Lightning - 1]);
2816 	for (unsigned int i = 0; i < StaticModules.Size(); ++i)
2817 	{
2818 		StaticModules[i]->StartTypedScripts (type, activator, always, arg1, runNow);
2819 	}
2820 }
2821 
StartTypedScripts(WORD type,AActor * activator,bool always,int arg1,bool runNow)2822 void FBehavior::StartTypedScripts (WORD type, AActor *activator, bool always, int arg1, bool runNow)
2823 {
2824 	const ScriptPtr *ptr;
2825 	int i;
2826 
2827 	for (i = 0; i < NumScripts; ++i)
2828 	{
2829 		ptr = &Scripts[i];
2830 		if (ptr->Type == type)
2831 		{
2832 			DLevelScript *runningScript = P_GetScriptGoing (activator, NULL, ptr->Number,
2833 				ptr, this, &arg1, 1, always ? ACS_ALWAYS : 0);
2834 			if (runNow)
2835 			{
2836 				runningScript->RunScript ();
2837 			}
2838 		}
2839 	}
2840 }
2841 
2842 // FBehavior :: StaticStopMyScripts
2843 //
2844 // Stops any scripts started by the specified actor. Used by the net code
2845 // when a player disconnects. Should this be used in general whenever an
2846 // actor is destroyed?
2847 
StaticStopMyScripts(AActor * actor)2848 void FBehavior::StaticStopMyScripts (AActor *actor)
2849 {
2850 	DACSThinker *controller = DACSThinker::ActiveThinker;
2851 
2852 	if (controller != NULL)
2853 	{
2854 		controller->StopScriptsFor (actor);
2855 	}
2856 }
2857 
2858 //==========================================================================
2859 //
2860 // P_SerializeACSScriptNumber
2861 //
2862 // Serializes a script number. If it's negative, it's really a name, so
2863 // that will get serialized after it.
2864 //
2865 //==========================================================================
2866 
P_SerializeACSScriptNumber(FArchive & arc,int & scriptnum,bool was2byte)2867 void P_SerializeACSScriptNumber(FArchive &arc, int &scriptnum, bool was2byte)
2868 {
2869 	if (SaveVersion < 3359)
2870 	{
2871 		if (was2byte)
2872 		{
2873 			WORD oldver;
2874 			arc << oldver;
2875 			scriptnum = oldver;
2876 		}
2877 		else
2878 		{
2879 			arc << scriptnum;
2880 		}
2881 	}
2882 	else
2883 	{
2884 		arc << scriptnum;
2885 		// If the script number is negative, then it's really a name.
2886 		// So read/store the name after it.
2887 		if (scriptnum < 0)
2888 		{
2889 			if (arc.IsStoring())
2890 			{
2891 				arc.WriteName(FName(ENamedName(-scriptnum)).GetChars());
2892 			}
2893 			else
2894 			{
2895 				const char *nam = arc.ReadName();
2896 				scriptnum = -FName(nam);
2897 			}
2898 		}
2899 	}
2900 }
2901 
2902 //---- The ACS Interpreter ----//
2903 
2904 IMPLEMENT_POINTY_CLASS (DACSThinker)
2905  DECLARE_POINTER(LastScript)
2906  DECLARE_POINTER(Scripts)
2907 END_POINTERS
2908 
2909 TObjPtr<DACSThinker> DACSThinker::ActiveThinker;
2910 
DACSThinker()2911 DACSThinker::DACSThinker ()
2912 : DThinker(STAT_SCRIPTS)
2913 {
2914 	if (ActiveThinker)
2915 	{
2916 		I_Error ("Only one ACSThinker is allowed to exist at a time.\nCheck your code.");
2917 	}
2918 	else
2919 	{
2920 		ActiveThinker = this;
2921 		Scripts = NULL;
2922 		LastScript = NULL;
2923 		RunningScripts.Clear();
2924 	}
2925 }
2926 
~DACSThinker()2927 DACSThinker::~DACSThinker ()
2928 {
2929 	Scripts = NULL;
2930 	ActiveThinker = NULL;
2931 }
2932 
Serialize(FArchive & arc)2933 void DACSThinker::Serialize (FArchive &arc)
2934 {
2935 	int scriptnum;
2936 	int scriptcount = 0;
2937 
2938 	Super::Serialize (arc);
2939 	if (SaveVersion < 4515)
2940 		arc << Scripts << LastScript;
2941 	else
2942 	{
2943 		if (arc.IsStoring())
2944 		{
2945 			DLevelScript *script;
2946 			script = Scripts;
2947 			while (script)
2948 			{
2949 				scriptcount++;
2950 
2951 				// We want to store this list backwards, so we can't loose the last pointer
2952 				if (script->next == NULL)
2953 					break;
2954 				script = script->next;
2955 			}
2956 			arc << scriptcount;
2957 
2958 			while (script)
2959 			{
2960 				arc << script;
2961 				script = script->prev;
2962 			}
2963 		}
2964 		else
2965 		{
2966 			// We are running through this list backwards, so the next entry is the last processed
2967 			DLevelScript *next = NULL;
2968 			arc << scriptcount;
2969 			Scripts = NULL;
2970 			LastScript = NULL;
2971 			for (int i = 0; i < scriptcount; i++)
2972 			{
2973 				arc << Scripts;
2974 
2975 				Scripts->next = next;
2976 				Scripts->prev = NULL;
2977 				if (next != NULL)
2978 					next->prev = Scripts;
2979 
2980 				next = Scripts;
2981 
2982 				if (i == 0)
2983 					LastScript = Scripts;
2984 			}
2985 		}
2986 	}
2987 	if (arc.IsStoring ())
2988 	{
2989 		ScriptMap::Iterator it(RunningScripts);
2990 		ScriptMap::Pair *pair;
2991 
2992 		while (it.NextPair(pair))
2993 		{
2994 			assert(pair->Value != NULL);
2995 			arc << pair->Value;
2996 			scriptnum = pair->Key;
2997 			P_SerializeACSScriptNumber(arc, scriptnum, true);
2998 		}
2999 		DLevelScript *nilptr = NULL;
3000 		arc << nilptr;
3001 	}
3002 	else // Loading
3003 	{
3004 		DLevelScript *script = NULL;
3005 		RunningScripts.Clear();
3006 
3007 		arc << script;
3008 		while (script)
3009 		{
3010 			P_SerializeACSScriptNumber(arc, scriptnum, true);
3011 			RunningScripts[scriptnum] = script;
3012 			arc << script;
3013 		}
3014 	}
3015 }
3016 
Tick()3017 void DACSThinker::Tick ()
3018 {
3019 	DLevelScript *script = Scripts;
3020 
3021 	while (script)
3022 	{
3023 		DLevelScript *next = script->next;
3024 		script->RunScript ();
3025 		script = next;
3026 	}
3027 
3028 //	GlobalACSStrings.Clear();
3029 
3030 	if (ACS_StringBuilderStack.Size())
3031 	{
3032 		int size = ACS_StringBuilderStack.Size();
3033 		ACS_StringBuilderStack.Clear();
3034 		I_Error("Error: %d garbage entries on ACS string builder stack.", size);
3035 	}
3036 }
3037 
StopScriptsFor(AActor * actor)3038 void DACSThinker::StopScriptsFor (AActor *actor)
3039 {
3040 	DLevelScript *script = Scripts;
3041 
3042 	while (script != NULL)
3043 	{
3044 		DLevelScript *next = script->next;
3045 		if (script->activator == actor)
3046 		{
3047 			script->SetState (DLevelScript::SCRIPT_PleaseRemove);
3048 		}
3049 		script = next;
3050 	}
3051 }
3052 
3053 IMPLEMENT_POINTY_CLASS (DLevelScript)
3054  DECLARE_POINTER(next)
3055  DECLARE_POINTER(prev)
3056  DECLARE_POINTER(activator)
3057 END_POINTERS
3058 
3059 inline FArchive &operator<< (FArchive &arc, DLevelScript::EScriptState &state)
3060 {
3061 	BYTE val = (BYTE)state;
3062 	arc << val;
3063 	state = (DLevelScript::EScriptState)val;
3064 	return arc;
3065 }
3066 
Serialize(FArchive & arc)3067 void DLevelScript::Serialize (FArchive &arc)
3068 {
3069 	DWORD i;
3070 
3071 	Super::Serialize (arc);
3072 	if (SaveVersion < 4515)
3073 		arc << next << prev;
3074 
3075 	P_SerializeACSScriptNumber(arc, script, false);
3076 
3077 	arc	<< state
3078 		<< statedata
3079 		<< activator
3080 		<< activationline
3081 		<< backSide
3082 		<< numlocalvars;
3083 
3084 	if (arc.IsLoading())
3085 	{
3086 		localvars = new SDWORD[numlocalvars];
3087 	}
3088 	for (i = 0; i < (DWORD)numlocalvars; i++)
3089 	{
3090 		arc << localvars[i];
3091 	}
3092 
3093 	if (arc.IsStoring ())
3094 	{
3095 		WORD lib = activeBehavior->GetLibraryID() >> LIBRARYID_SHIFT;
3096 		arc << lib;
3097 		i = activeBehavior->PC2Ofs (pc);
3098 		arc << i;
3099 	}
3100 	else
3101 	{
3102 		WORD lib;
3103 		arc << lib << i;
3104 		activeBehavior = FBehavior::StaticGetModule (lib);
3105 		pc = activeBehavior->Ofs2PC (i);
3106 	}
3107 
3108 	arc << activefont
3109 		<< hudwidth << hudheight;
3110 	if (SaveVersion >= 3960)
3111 	{
3112 		arc << ClipRectLeft << ClipRectTop << ClipRectWidth << ClipRectHeight
3113 			<< WrapWidth;
3114 	}
3115 	else
3116 	{
3117 		ClipRectLeft = ClipRectTop = ClipRectWidth = ClipRectHeight = WrapWidth = 0;
3118 	}
3119 	if (SaveVersion >= 4058)
3120 	{
3121 		arc << InModuleScriptNumber;
3122 	}
3123 	else
3124 	{ // Don't worry about locating profiling info for old saves.
3125 		InModuleScriptNumber = -1;
3126 	}
3127 }
3128 
DLevelScript()3129 DLevelScript::DLevelScript ()
3130 {
3131 	next = prev = NULL;
3132 	if (DACSThinker::ActiveThinker == NULL)
3133 		new DACSThinker;
3134 	activefont = SmallFont;
3135 	localvars = NULL;
3136 }
3137 
~DLevelScript()3138 DLevelScript::~DLevelScript ()
3139 {
3140 	if (localvars != NULL)
3141 		delete[] localvars;
3142 	localvars = NULL;
3143 }
3144 
Unlink()3145 void DLevelScript::Unlink ()
3146 {
3147 	DACSThinker *controller = DACSThinker::ActiveThinker;
3148 
3149 	if (controller->LastScript == this)
3150 	{
3151 		controller->LastScript = prev;
3152 		GC::WriteBarrier(controller, prev);
3153 	}
3154 	if (controller->Scripts == this)
3155 	{
3156 		controller->Scripts = next;
3157 		GC::WriteBarrier(controller, next);
3158 	}
3159 	if (prev)
3160 	{
3161 		prev->next = next;
3162 		GC::WriteBarrier(prev, next);
3163 	}
3164 	if (next)
3165 	{
3166 		next->prev = prev;
3167 		GC::WriteBarrier(next, prev);
3168 	}
3169 }
3170 
Link()3171 void DLevelScript::Link ()
3172 {
3173 	DACSThinker *controller = DACSThinker::ActiveThinker;
3174 
3175 	next = controller->Scripts;
3176 	GC::WriteBarrier(this, next);
3177 	if (controller->Scripts)
3178 	{
3179 		controller->Scripts->prev = this;
3180 		GC::WriteBarrier(controller->Scripts, this);
3181 	}
3182 	prev = NULL;
3183 	controller->Scripts = this;
3184 	GC::WriteBarrier(controller, this);
3185 	if (controller->LastScript == NULL)
3186 	{
3187 		controller->LastScript = this;
3188 	}
3189 }
3190 
PutLast()3191 void DLevelScript::PutLast ()
3192 {
3193 	DACSThinker *controller = DACSThinker::ActiveThinker;
3194 
3195 	if (controller->LastScript == this)
3196 		return;
3197 
3198 	Unlink ();
3199 	if (controller->Scripts == NULL)
3200 	{
3201 		Link ();
3202 	}
3203 	else
3204 	{
3205 		if (controller->LastScript)
3206 			controller->LastScript->next = this;
3207 		prev = controller->LastScript;
3208 		next = NULL;
3209 		controller->LastScript = this;
3210 	}
3211 }
3212 
PutFirst()3213 void DLevelScript::PutFirst ()
3214 {
3215 	DACSThinker *controller = DACSThinker::ActiveThinker;
3216 
3217 	if (controller->Scripts == this)
3218 		return;
3219 
3220 	Unlink ();
3221 	Link ();
3222 }
3223 
Random(int min,int max)3224 int DLevelScript::Random (int min, int max)
3225 {
3226 	if (max < min)
3227 	{
3228 		swapvalues (max, min);
3229 	}
3230 
3231 	return min + pr_acs(max - min + 1);
3232 }
3233 
ThingCount(int type,int stringid,int tid,int tag)3234 int DLevelScript::ThingCount (int type, int stringid, int tid, int tag)
3235 {
3236 	AActor *actor;
3237 	const PClass *kind;
3238 	int count = 0;
3239 	bool replacemented = false;
3240 
3241 	if (type > 0)
3242 	{
3243 		kind = P_GetSpawnableType(type);
3244 		if (kind == NULL)
3245 			return 0;
3246 	}
3247 	else if (stringid >= 0)
3248 	{
3249 		const char *type_name = FBehavior::StaticLookupString (stringid);
3250 		if (type_name == NULL)
3251 			return 0;
3252 
3253 		kind = PClass::FindClass (type_name);
3254 		if (kind == NULL || kind->ActorInfo == NULL)
3255 			return 0;
3256 
3257 	}
3258 	else
3259 	{
3260 		kind = NULL;
3261 	}
3262 
3263 do_count:
3264 	if (tid)
3265 	{
3266 		FActorIterator iterator (tid);
3267 		while ( (actor = iterator.Next ()) )
3268 		{
3269 			if (actor->health > 0 &&
3270 				(kind == NULL || actor->IsA (kind)))
3271 			{
3272 				if (tag == -1 || tagManager.SectorHasTag(actor->Sector, tag))
3273 				{
3274 					// Don't count items in somebody's inventory
3275 					if (!actor->IsKindOf (RUNTIME_CLASS(AInventory)) ||
3276 						static_cast<AInventory *>(actor)->Owner == NULL)
3277 					{
3278 						count++;
3279 					}
3280 				}
3281 			}
3282 		}
3283 	}
3284 	else
3285 	{
3286 		TThinkerIterator<AActor> iterator;
3287 		while ( (actor = iterator.Next ()) )
3288 		{
3289 			if (actor->health > 0 &&
3290 				(kind == NULL || actor->IsA (kind)))
3291 			{
3292 				if (tag == -1 || tagManager.SectorHasTag(actor->Sector, tag))
3293 				{
3294 					// Don't count items in somebody's inventory
3295 					if (!actor->IsKindOf (RUNTIME_CLASS(AInventory)) ||
3296 						static_cast<AInventory *>(actor)->Owner == NULL)
3297 					{
3298 						count++;
3299 					}
3300 				}
3301 			}
3302 		}
3303 	}
3304 	if (!replacemented && kind != NULL)
3305 	{
3306 		// Again, with decorate replacements
3307 		replacemented = true;
3308 		PClass *newkind = kind->GetReplacement();
3309 		if (newkind != kind)
3310 		{
3311 			kind = newkind;
3312 			goto do_count;
3313 		}
3314 	}
3315 	return count;
3316 }
3317 
ChangeFlat(int tag,int name,bool floorOrCeiling)3318 void DLevelScript::ChangeFlat (int tag, int name, bool floorOrCeiling)
3319 {
3320 	FTextureID flat;
3321 	int secnum = -1;
3322 	const char *flatname = FBehavior::StaticLookupString (name);
3323 
3324 	if (flatname == NULL)
3325 		return;
3326 
3327 	flat = TexMan.GetTexture (flatname, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable);
3328 
3329 	FSectorTagIterator it(tag);
3330 	while ((secnum = it.Next()) >= 0)
3331 	{
3332 		int pos = floorOrCeiling? sector_t::ceiling : sector_t::floor;
3333 		sectors[secnum].SetTexture(pos, flat);
3334 	}
3335 }
3336 
CountPlayers()3337 int DLevelScript::CountPlayers ()
3338 {
3339 	int count = 0, i;
3340 
3341 	for (i = 0; i < MAXPLAYERS; i++)
3342 		if (playeringame[i])
3343 			count++;
3344 
3345 	return count;
3346 }
3347 
SetLineTexture(int lineid,int side,int position,int name)3348 void DLevelScript::SetLineTexture (int lineid, int side, int position, int name)
3349 {
3350 	FTextureID texture;
3351 	int linenum = -1;
3352 	const char *texname = FBehavior::StaticLookupString (name);
3353 
3354 	if (texname == NULL)
3355 		return;
3356 
3357 	side = !!side;
3358 
3359 	texture = TexMan.GetTexture (texname, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable);
3360 
3361 	FLineIdIterator itr(lineid);
3362 	while ((linenum = itr.Next()) >= 0)
3363 	{
3364 		side_t *sidedef;
3365 
3366 		sidedef = lines[linenum].sidedef[side];
3367 		if (sidedef == NULL)
3368 			continue;
3369 
3370 		switch (position)
3371 		{
3372 		case TEXTURE_TOP:
3373 			sidedef->SetTexture(side_t::top, texture);
3374 			break;
3375 		case TEXTURE_MIDDLE:
3376 			sidedef->SetTexture(side_t::mid, texture);
3377 			break;
3378 		case TEXTURE_BOTTOM:
3379 			sidedef->SetTexture(side_t::bottom, texture);
3380 			break;
3381 		default:
3382 			break;
3383 		}
3384 
3385 	}
3386 }
3387 
ReplaceTextures(int fromnamei,int tonamei,int flags)3388 void DLevelScript::ReplaceTextures (int fromnamei, int tonamei, int flags)
3389 {
3390 	const char *fromname = FBehavior::StaticLookupString (fromnamei);
3391 	const char *toname = FBehavior::StaticLookupString (tonamei);
3392 	FTextureID picnum1, picnum2;
3393 
3394 	if (fromname == NULL)
3395 		return;
3396 
3397 	if ((flags ^ (NOT_BOTTOM | NOT_MIDDLE | NOT_TOP)) != 0)
3398 	{
3399 		picnum1 = TexMan.GetTexture (fromname, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable);
3400 		picnum2 = TexMan.GetTexture (toname, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable);
3401 
3402 		for (int i = 0; i < numsides; ++i)
3403 		{
3404 			side_t *wal = &sides[i];
3405 
3406 			for(int j=0;j<3;j++)
3407 			{
3408 				static BYTE bits[]={NOT_TOP, NOT_MIDDLE, NOT_BOTTOM};
3409 				if (!(flags & bits[j]) && wal->GetTexture(j) == picnum1)
3410 				{
3411 					wal->SetTexture(j, picnum2);
3412 				}
3413 			}
3414 		}
3415 	}
3416 	if ((flags ^ (NOT_FLOOR | NOT_CEILING)) != 0)
3417 	{
3418 		picnum1 = TexMan.GetTexture (fromname, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable);
3419 		picnum2 = TexMan.GetTexture (toname, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable);
3420 
3421 		for (int i = 0; i < numsectors; ++i)
3422 		{
3423 			sector_t *sec = &sectors[i];
3424 
3425 			if (!(flags & NOT_FLOOR) && sec->GetTexture(sector_t::floor) == picnum1)
3426 				sec->SetTexture(sector_t::floor, picnum2);
3427 			if (!(flags & NOT_CEILING) && sec->GetTexture(sector_t::ceiling) == picnum1)
3428 				sec->SetTexture(sector_t::ceiling, picnum2);
3429 		}
3430 	}
3431 }
3432 
DoSpawn(int type,fixed_t x,fixed_t y,fixed_t z,int tid,int angle,bool force)3433 int DLevelScript::DoSpawn (int type, fixed_t x, fixed_t y, fixed_t z, int tid, int angle, bool force)
3434 {
3435 	const PClass *info = PClass::FindClass (FBehavior::StaticLookupString (type));
3436 	AActor *actor = NULL;
3437 	int spawncount = 0;
3438 
3439 	if (info != NULL)
3440 	{
3441 		info = info->GetReplacement ();
3442 
3443 		if ((GetDefaultByType (info)->flags3 & MF3_ISMONSTER) &&
3444 			((dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS)))
3445 		{
3446 			return 0;
3447 		}
3448 
3449 		actor = Spawn (info, x, y, z, ALLOW_REPLACE);
3450 		if (actor != NULL)
3451 		{
3452 			ActorFlags2 oldFlags2 = actor->flags2;
3453 			actor->flags2 |= MF2_PASSMOBJ;
3454 			if (force || P_TestMobjLocation (actor))
3455 			{
3456 				actor->angle = angle << 24;
3457 				actor->tid = tid;
3458 				actor->AddToHash ();
3459 				if (actor->flags & MF_SPECIAL)
3460 					actor->flags |= MF_DROPPED;  // Don't respawn
3461 				actor->flags2 = oldFlags2;
3462 				spawncount++;
3463 			}
3464 			else
3465 			{
3466 				// If this is a monster, subtract it from the total monster
3467 				// count, because it already added to it during spawning.
3468 				actor->ClearCounters();
3469 				actor->Destroy ();
3470 				actor = NULL;
3471 			}
3472 		}
3473 	}
3474 	return spawncount;
3475 }
3476 
DoSpawnSpot(int type,int spot,int tid,int angle,bool force)3477 int DLevelScript::DoSpawnSpot (int type, int spot, int tid, int angle, bool force)
3478 {
3479 	int spawned = 0;
3480 
3481 	if (spot != 0)
3482 	{
3483 		FActorIterator iterator (spot);
3484 		AActor *aspot;
3485 
3486 		while ( (aspot = iterator.Next ()) )
3487 		{
3488 			spawned += DoSpawn (type, aspot->X(), aspot->Y(), aspot->Z(), tid, angle, force);
3489 		}
3490 	}
3491 	else if (activator != NULL)
3492 	{
3493 			spawned += DoSpawn (type, activator->X(), activator->Y(), activator->Z(), tid, angle, force);
3494 	}
3495 	return spawned;
3496 }
3497 
DoSpawnSpotFacing(int type,int spot,int tid,bool force)3498 int DLevelScript::DoSpawnSpotFacing (int type, int spot, int tid, bool force)
3499 {
3500 	int spawned = 0;
3501 
3502 	if (spot != 0)
3503 	{
3504 		FActorIterator iterator (spot);
3505 		AActor *aspot;
3506 
3507 		while ( (aspot = iterator.Next ()) )
3508 		{
3509 			spawned += DoSpawn (type, aspot->X(), aspot->Y(), aspot->Z(), tid, aspot->angle >> 24, force);
3510 		}
3511 	}
3512 	else if (activator != NULL)
3513 	{
3514 			spawned += DoSpawn (type, activator->X(), activator->Y(), activator->Z(), tid, activator->angle >> 24, force);
3515 	}
3516 	return spawned;
3517 }
3518 
DoFadeTo(int r,int g,int b,int a,fixed_t time)3519 void DLevelScript::DoFadeTo (int r, int g, int b, int a, fixed_t time)
3520 {
3521 	DoFadeRange (0, 0, 0, -1, clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), clamp(a, 0, FRACUNIT), time);
3522 }
3523 
DoFadeRange(int r1,int g1,int b1,int a1,int r2,int g2,int b2,int a2,fixed_t time)3524 void DLevelScript::DoFadeRange (int r1, int g1, int b1, int a1,
3525 								int r2, int g2, int b2, int a2, fixed_t time)
3526 {
3527 	player_t *viewer;
3528 	float ftime = (float)time / 65536.f;
3529 	bool fadingFrom = a1 >= 0;
3530 	float fr1 = 0, fg1 = 0, fb1 = 0, fa1 = 0;
3531 	float fr2, fg2, fb2, fa2;
3532 	int i;
3533 
3534 	fr2 = (float)r2 / 255.f;
3535 	fg2 = (float)g2 / 255.f;
3536 	fb2 = (float)b2 / 255.f;
3537 	fa2 = (float)a2 / 65536.f;
3538 
3539 	if (fadingFrom)
3540 	{
3541 		fr1 = (float)r1 / 255.f;
3542 		fg1 = (float)g1 / 255.f;
3543 		fb1 = (float)b1 / 255.f;
3544 		fa1 = (float)a1 / 65536.f;
3545 	}
3546 
3547 	if (activator != NULL)
3548 	{
3549 		viewer = activator->player;
3550 		if (viewer == NULL)
3551 			return;
3552 		i = MAXPLAYERS;
3553 		goto showme;
3554 	}
3555 	else
3556 	{
3557 		for (i = 0; i < MAXPLAYERS; ++i)
3558 		{
3559 			if (playeringame[i])
3560 			{
3561 				viewer = &players[i];
3562 showme:
3563 				if (ftime <= 0.f)
3564 				{
3565 					viewer->BlendR = fr2;
3566 					viewer->BlendG = fg2;
3567 					viewer->BlendB = fb2;
3568 					viewer->BlendA = fa2;
3569 				}
3570 				else
3571 				{
3572 					if (!fadingFrom)
3573 					{
3574 						if (viewer->BlendA <= 0.f)
3575 						{
3576 							fr1 = fr2;
3577 							fg1 = fg2;
3578 							fb1 = fb2;
3579 							fa1 = 0.f;
3580 						}
3581 						else
3582 						{
3583 							fr1 = viewer->BlendR;
3584 							fg1 = viewer->BlendG;
3585 							fb1 = viewer->BlendB;
3586 							fa1 = viewer->BlendA;
3587 						}
3588 					}
3589 					new DFlashFader (fr1, fg1, fb1, fa1, fr2, fg2, fb2, fa2, ftime, viewer->mo);
3590 				}
3591 			}
3592 		}
3593 	}
3594 }
3595 
DoSetFont(int fontnum)3596 void DLevelScript::DoSetFont (int fontnum)
3597 {
3598 	const char *fontname = FBehavior::StaticLookupString (fontnum);
3599 	activefont = V_GetFont (fontname);
3600 	if (activefont == NULL)
3601 	{
3602 		activefont = SmallFont;
3603 	}
3604 }
3605 
DoSetMaster(AActor * self,AActor * master)3606 int DoSetMaster (AActor *self, AActor *master)
3607 {
3608     AActor *defs;
3609     if (self->flags3&MF3_ISMONSTER)
3610     {
3611         if (master)
3612         {
3613             if (master->flags3&MF3_ISMONSTER)
3614             {
3615                 self->FriendPlayer = 0;
3616                 self->master = master;
3617                 level.total_monsters -= self->CountsAsKill();
3618                 self->flags = (self->flags & ~MF_FRIENDLY) | (master->flags & MF_FRIENDLY);
3619                 level.total_monsters += self->CountsAsKill();
3620                 // Don't attack your new master
3621                 if (self->target == self->master) self->target = NULL;
3622                 if (self->lastenemy == self->master) self->lastenemy = NULL;
3623                 if (self->LastHeard == self->master) self->LastHeard = NULL;
3624                 return 1;
3625             }
3626             else if (master->player)
3627             {
3628                 // [KS] Be friendly to this player
3629                 self->master = NULL;
3630                 level.total_monsters -= self->CountsAsKill();
3631                 self->flags|=MF_FRIENDLY;
3632                 self->SetFriendPlayer(master->player);
3633 
3634                 AActor * attacker=master->player->attacker;
3635                 if (attacker)
3636                 {
3637                     if (!(attacker->flags&MF_FRIENDLY) ||
3638                         (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=self->FriendPlayer))
3639                     {
3640                         self->LastHeard = self->target = attacker;
3641                     }
3642                 }
3643                 // And stop attacking him if necessary.
3644                 if (self->target == master) self->target = NULL;
3645                 if (self->lastenemy == master) self->lastenemy = NULL;
3646                 if (self->LastHeard == master) self->LastHeard = NULL;
3647                 return 1;
3648             }
3649         }
3650         else
3651         {
3652             self->master = NULL;
3653             self->FriendPlayer = 0;
3654             // Go back to whatever friendliness we usually have...
3655             defs = self->GetDefault();
3656             level.total_monsters -= self->CountsAsKill();
3657             self->flags = (self->flags & ~MF_FRIENDLY) | (defs->flags & MF_FRIENDLY);
3658             level.total_monsters += self->CountsAsKill();
3659             // ...And re-side with our friends.
3660             if (self->target && !self->IsHostile (self->target)) self->target = NULL;
3661             if (self->lastenemy && !self->IsHostile (self->lastenemy)) self->lastenemy = NULL;
3662             if (self->LastHeard && !self->IsHostile (self->LastHeard)) self->LastHeard = NULL;
3663             return 1;
3664         }
3665     }
3666     return 0;
3667 }
3668 
DoGetMasterTID(AActor * self)3669 int DoGetMasterTID (AActor *self)
3670 {
3671 	if (self->master) return self->master->tid;
3672 	else if (self->FriendPlayer)
3673 	{
3674 		player_t *player = &players[(self->FriendPlayer)-1];
3675 		return player->mo->tid;
3676 	}
3677 	else return 0;
3678 }
3679 
SingleActorFromTID(int tid,AActor * defactor)3680 AActor *SingleActorFromTID (int tid, AActor *defactor)
3681 {
3682 	if (tid == 0)
3683 	{
3684 		return defactor;
3685 	}
3686 	else
3687 	{
3688 		FActorIterator iterator (tid);
3689 		return iterator.Next();
3690 	}
3691 }
3692 
3693 enum
3694 {
3695 	APROP_Health		= 0,
3696 	APROP_Speed			= 1,
3697 	APROP_Damage		= 2,
3698 	APROP_Alpha			= 3,
3699 	APROP_RenderStyle	= 4,
3700 	APROP_SeeSound		= 5,	// Sounds can only be set, not gotten
3701 	APROP_AttackSound	= 6,
3702 	APROP_PainSound		= 7,
3703 	APROP_DeathSound	= 8,
3704 	APROP_ActiveSound	= 9,
3705 	APROP_Ambush		= 10,
3706 	APROP_Invulnerable	= 11,
3707 	APROP_JumpZ			= 12,	// [GRB]
3708 	APROP_ChaseGoal		= 13,
3709 	APROP_Frightened	= 14,
3710 	APROP_Gravity		= 15,
3711 	APROP_Friendly		= 16,
3712 	APROP_SpawnHealth   = 17,
3713 	APROP_Dropped		= 18,
3714 	APROP_Notarget		= 19,
3715 	APROP_Species		= 20,
3716 	APROP_NameTag		= 21,
3717 	APROP_Score			= 22,
3718 	APROP_Notrigger		= 23,
3719 	APROP_DamageFactor	= 24,
3720 	APROP_MasterTID     = 25,
3721 	APROP_TargetTID		= 26,
3722 	APROP_TracerTID		= 27,
3723 	APROP_WaterLevel	= 28,
3724 	APROP_ScaleX        = 29,
3725 	APROP_ScaleY        = 30,
3726 	APROP_Dormant		= 31,
3727 	APROP_Mass			= 32,
3728 	APROP_Accuracy      = 33,
3729 	APROP_Stamina       = 34,
3730 	APROP_Height		= 35,
3731 	APROP_Radius		= 36,
3732 	APROP_ReactionTime  = 37,
3733 	APROP_MeleeRange	= 38,
3734 	APROP_ViewHeight	= 39,
3735 	APROP_AttackZOffset	= 40,
3736 	APROP_StencilColor	= 41,
3737 	APROP_Friction		= 42,
3738 	APROP_DamageMultiplier=43,
3739 };
3740 
3741 // These are needed for ACS's APROP_RenderStyle
3742 static const int LegacyRenderStyleIndices[] =
3743 {
3744 	0,	// STYLE_None,
3745 	1,  // STYLE_Normal,
3746 	2,  // STYLE_Fuzzy,
3747 	3,	// STYLE_SoulTrans,
3748 	4,	// STYLE_OptFuzzy,
3749 	5,	// STYLE_Stencil,
3750 	64,	// STYLE_Translucent
3751 	65,	// STYLE_Add,
3752 	66,	// STYLE_Shaded,
3753 	67,	// STYLE_TranslucentStencil,
3754 	68,	// STYLE_Shadow,
3755 	69,	// STYLE_Subtract,
3756 	6,	// STYLE_AddStencil
3757 	7,	// STYLE_AddShaded
3758 	-1
3759 };
3760 
SetActorProperty(int tid,int property,int value)3761 void DLevelScript::SetActorProperty (int tid, int property, int value)
3762 {
3763 	if (tid == 0)
3764 	{
3765 		DoSetActorProperty (activator, property, value);
3766 	}
3767 	else
3768 	{
3769 		AActor *actor;
3770 		FActorIterator iterator (tid);
3771 
3772 		while ((actor = iterator.Next()) != NULL)
3773 		{
3774 			DoSetActorProperty (actor, property, value);
3775 		}
3776 	}
3777 }
3778 
DoSetActorProperty(AActor * actor,int property,int value)3779 void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value)
3780 {
3781 	if (actor == NULL)
3782 	{
3783 		return;
3784 	}
3785 	switch (property)
3786 	{
3787 	case APROP_Health:
3788 		// Don't alter the health of dead things.
3789 		if (actor->health <= 0 || (actor->player != NULL && actor->player->playerstate == PST_DEAD))
3790 		{
3791 			break;
3792 		}
3793 		actor->health = value;
3794 		if (actor->player != NULL)
3795 		{
3796 			actor->player->health = value;
3797 		}
3798 		// If the health is set to a non-positive value, properly kill the actor.
3799 		if (value <= 0)
3800 		{
3801 			actor->Die(activator, activator);
3802 		}
3803 		break;
3804 
3805 	case APROP_Speed:
3806 		actor->Speed = value;
3807 		break;
3808 
3809 	case APROP_Damage:
3810 		actor->Damage = value;
3811 		break;
3812 
3813 	case APROP_Alpha:
3814 		actor->alpha = value;
3815 		break;
3816 
3817 	case APROP_RenderStyle:
3818 		for(int i=0; LegacyRenderStyleIndices[i] >= 0; i++)
3819 		{
3820 			if (LegacyRenderStyleIndices[i] == value)
3821 			{
3822 				actor->RenderStyle = ERenderStyle(i);
3823 				break;
3824 			}
3825 		}
3826 		break;
3827 
3828 	case APROP_Ambush:
3829 		if (value) actor->flags |= MF_AMBUSH; else actor->flags &= ~MF_AMBUSH;
3830 		break;
3831 
3832 	case APROP_Dropped:
3833 		if (value) actor->flags |= MF_DROPPED; else actor->flags &= ~MF_DROPPED;
3834 		break;
3835 
3836 	case APROP_Invulnerable:
3837 		if (value) actor->flags2 |= MF2_INVULNERABLE; else actor->flags2 &= ~MF2_INVULNERABLE;
3838 		break;
3839 
3840 	case APROP_Notarget:
3841 		if (value) actor->flags3 |= MF3_NOTARGET; else actor->flags3 &= ~MF3_NOTARGET;
3842 		break;
3843 
3844 	case APROP_Notrigger:
3845 		if (value) actor->flags6 |= MF6_NOTRIGGER; else actor->flags6 &= ~MF6_NOTRIGGER;
3846 		break;
3847 
3848 	case APROP_JumpZ:
3849 		if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
3850 			static_cast<APlayerPawn *>(actor)->JumpZ = value;
3851 		break; 	// [GRB]
3852 
3853 	case APROP_ChaseGoal:
3854 		if (value)
3855 			actor->flags5 |= MF5_CHASEGOAL;
3856 		else
3857 			actor->flags5 &= ~MF5_CHASEGOAL;
3858 		break;
3859 
3860 	case APROP_Frightened:
3861 		if (value)
3862 			actor->flags4 |= MF4_FRIGHTENED;
3863 		else
3864 			actor->flags4 &= ~MF4_FRIGHTENED;
3865 		break;
3866 
3867 	case APROP_Friendly:
3868 		if (actor->CountsAsKill()) level.total_monsters--;
3869 		if (value)
3870 		{
3871 			actor->flags |= MF_FRIENDLY;
3872 		}
3873 		else
3874 		{
3875 			actor->flags &= ~MF_FRIENDLY;
3876 		}
3877 		if (actor->CountsAsKill()) level.total_monsters++;
3878 		break;
3879 
3880 
3881 	case APROP_SpawnHealth:
3882 		if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
3883 		{
3884 			static_cast<APlayerPawn *>(actor)->MaxHealth = value;
3885 		}
3886 		break;
3887 
3888 	case APROP_Gravity:
3889 		actor->gravity = value;
3890 		break;
3891 
3892 	case APROP_SeeSound:
3893 		actor->SeeSound = FBehavior::StaticLookupString(value);
3894 		break;
3895 
3896 	case APROP_AttackSound:
3897 		actor->AttackSound = FBehavior::StaticLookupString(value);
3898 		break;
3899 
3900 	case APROP_PainSound:
3901 		actor->PainSound = FBehavior::StaticLookupString(value);
3902 		break;
3903 
3904 	case APROP_DeathSound:
3905 		actor->DeathSound = FBehavior::StaticLookupString(value);
3906 		break;
3907 
3908 	case APROP_ActiveSound:
3909 		actor->ActiveSound = FBehavior::StaticLookupString(value);
3910 		break;
3911 
3912 	case APROP_Species:
3913 		actor->Species = FBehavior::StaticLookupString(value);
3914 		break;
3915 
3916 	case APROP_Score:
3917 		actor->Score = value;
3918 		break;
3919 
3920 	case APROP_NameTag:
3921 		actor->SetTag(FBehavior::StaticLookupString(value));
3922 		break;
3923 
3924 	case APROP_DamageFactor:
3925 		actor->DamageFactor = value;
3926 		break;
3927 
3928 	case APROP_DamageMultiplier:
3929 		actor->DamageMultiply = value;
3930 		break;
3931 
3932 	case APROP_MasterTID:
3933 		AActor *other;
3934 		other = SingleActorFromTID (value, NULL);
3935 		DoSetMaster (actor, other);
3936 		break;
3937 
3938 	case APROP_ScaleX:
3939 		actor->scaleX = value;
3940 		break;
3941 
3942 	case APROP_ScaleY:
3943 		actor->scaleY = value;
3944 		break;
3945 
3946 	case APROP_Mass:
3947 		actor->Mass = value;
3948 		break;
3949 
3950 	case APROP_Accuracy:
3951 		actor->accuracy = value;
3952 		break;
3953 
3954 	case APROP_Stamina:
3955 		actor->stamina = value;
3956 		break;
3957 
3958 	case APROP_ReactionTime:
3959 		actor->reactiontime = value;
3960 		break;
3961 
3962 	case APROP_MeleeRange:
3963 		actor->meleerange = value;
3964 		break;
3965 
3966 	case APROP_ViewHeight:
3967 		if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
3968 		{
3969 			static_cast<APlayerPawn *>(actor)->ViewHeight = value;
3970 			if (actor->player != NULL)
3971 			{
3972 				actor->player->viewheight = value;
3973 			}
3974 		}
3975 		break;
3976 
3977 	case APROP_AttackZOffset:
3978 		if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
3979 			static_cast<APlayerPawn *>(actor)->AttackZOffset = value;
3980 		break;
3981 
3982 	case APROP_StencilColor:
3983 		actor->SetShade(value);
3984 		break;
3985 
3986 	case APROP_Friction:
3987 		actor->Friction = value;
3988 
3989 	default:
3990 		// do nothing.
3991 		break;
3992 	}
3993 }
3994 
GetActorProperty(int tid,int property)3995 int DLevelScript::GetActorProperty (int tid, int property)
3996 {
3997 	AActor *actor = SingleActorFromTID (tid, activator);
3998 
3999 	if (actor == NULL)
4000 	{
4001 		return 0;
4002 	}
4003 	switch (property)
4004 	{
4005 	case APROP_Health:		return actor->health;
4006 	case APROP_Speed:		return actor->Speed;
4007 	case APROP_Damage:		return actor->Damage;	// Should this call GetMissileDamage() instead?
4008 	case APROP_DamageFactor:return actor->DamageFactor;
4009 	case APROP_DamageMultiplier: return actor->DamageMultiply;
4010 	case APROP_Alpha:		return actor->alpha;
4011 	case APROP_RenderStyle:	for (int style = STYLE_None; style < STYLE_Count; ++style)
4012 							{ // Check for a legacy render style that matches.
4013 								if (LegacyRenderStyles[style] == actor->RenderStyle)
4014 								{
4015 									return LegacyRenderStyleIndices[style];
4016 								}
4017 							}
4018 							// The current render style isn't expressable as a legacy style,
4019 							// so pretends it's normal.
4020 							return STYLE_Normal;
4021 	case APROP_Gravity:		return actor->gravity;
4022 	case APROP_Invulnerable:return !!(actor->flags2 & MF2_INVULNERABLE);
4023 	case APROP_Ambush:		return !!(actor->flags & MF_AMBUSH);
4024 	case APROP_Dropped:		return !!(actor->flags & MF_DROPPED);
4025 	case APROP_ChaseGoal:	return !!(actor->flags5 & MF5_CHASEGOAL);
4026 	case APROP_Frightened:	return !!(actor->flags4 & MF4_FRIGHTENED);
4027 	case APROP_Friendly:	return !!(actor->flags & MF_FRIENDLY);
4028 	case APROP_Notarget:	return !!(actor->flags3 & MF3_NOTARGET);
4029 	case APROP_Notrigger:	return !!(actor->flags6 & MF6_NOTRIGGER);
4030 	case APROP_Dormant:		return !!(actor->flags2 & MF2_DORMANT);
4031 	case APROP_SpawnHealth: if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
4032 							{
4033 								return static_cast<APlayerPawn *>(actor)->MaxHealth;
4034 							}
4035 							else
4036 							{
4037 								return actor->SpawnHealth();
4038 							}
4039 
4040 	case APROP_JumpZ:		if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
4041 							{
4042 								return static_cast<APlayerPawn *>(actor)->JumpZ;	// [GRB]
4043 							}
4044 							else
4045 							{
4046 								return 0;
4047 							}
4048 	case APROP_Score:		return actor->Score;
4049 	case APROP_MasterTID:	return DoGetMasterTID (actor);
4050 	case APROP_TargetTID:	return (actor->target != NULL)? actor->target->tid : 0;
4051 	case APROP_TracerTID:	return (actor->tracer != NULL)? actor->tracer->tid : 0;
4052 	case APROP_WaterLevel:	return actor->waterlevel;
4053 	case APROP_ScaleX: 		return actor->scaleX;
4054 	case APROP_ScaleY: 		return actor->scaleY;
4055 	case APROP_Mass: 		return actor->Mass;
4056 	case APROP_Accuracy:    return actor->accuracy;
4057 	case APROP_Stamina:     return actor->stamina;
4058 	case APROP_Height:		return actor->height;
4059 	case APROP_Radius:		return actor->radius;
4060 	case APROP_ReactionTime:return actor->reactiontime;
4061 	case APROP_MeleeRange:	return actor->meleerange;
4062 	case APROP_ViewHeight:	if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
4063 							{
4064 								return static_cast<APlayerPawn *>(actor)->ViewHeight;
4065 							}
4066 							else
4067 							{
4068 								return 0;
4069 							}
4070 	case APROP_AttackZOffset:
4071 							if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn)))
4072 							{
4073 								return static_cast<APlayerPawn *>(actor)->AttackZOffset;
4074 							}
4075 							else
4076 							{
4077 								return 0;
4078 							}
4079 
4080 	case APROP_SeeSound:	return GlobalACSStrings.AddString(actor->SeeSound);
4081 	case APROP_AttackSound:	return GlobalACSStrings.AddString(actor->AttackSound);
4082 	case APROP_PainSound:	return GlobalACSStrings.AddString(actor->PainSound);
4083 	case APROP_DeathSound:	return GlobalACSStrings.AddString(actor->DeathSound);
4084 	case APROP_ActiveSound:	return GlobalACSStrings.AddString(actor->ActiveSound);
4085 	case APROP_Species:		return GlobalACSStrings.AddString(actor->GetSpecies());
4086 	case APROP_NameTag:		return GlobalACSStrings.AddString(actor->GetTag());
4087 	case APROP_StencilColor:return actor->fillcolor;
4088 	case APROP_Friction:	return actor->Friction;
4089 
4090 	default:				return 0;
4091 	}
4092 }
4093 
4094 
4095 
CheckActorProperty(int tid,int property,int value)4096 int DLevelScript::CheckActorProperty (int tid, int property, int value)
4097 {
4098 	AActor *actor = SingleActorFromTID (tid, activator);
4099 	const char *string = NULL;
4100 	if (actor == NULL)
4101 	{
4102 		return 0;
4103 	}
4104 	switch (property)
4105 	{
4106 		// Default
4107 		default:				return 0;
4108 
4109 		// Straightforward integer values:
4110 		case APROP_Health:
4111 		case APROP_Speed:
4112 		case APROP_Damage:
4113 		case APROP_DamageFactor:
4114 		case APROP_Alpha:
4115 		case APROP_RenderStyle:
4116 		case APROP_Gravity:
4117 		case APROP_SpawnHealth:
4118 		case APROP_JumpZ:
4119 		case APROP_Score:
4120 		case APROP_MasterTID:
4121 		case APROP_TargetTID:
4122 		case APROP_TracerTID:
4123 		case APROP_WaterLevel:
4124 		case APROP_ScaleX:
4125 		case APROP_ScaleY:
4126 		case APROP_Mass:
4127 		case APROP_Accuracy:
4128 		case APROP_Stamina:
4129 		case APROP_Height:
4130 		case APROP_Radius:
4131 		case APROP_ReactionTime:
4132 		case APROP_MeleeRange:
4133 		case APROP_ViewHeight:
4134 		case APROP_AttackZOffset:
4135 		case APROP_StencilColor:
4136 			return (GetActorProperty(tid, property) == value);
4137 
4138 		// Boolean values need to compare to a binary version of value
4139 		case APROP_Ambush:
4140 		case APROP_Invulnerable:
4141 		case APROP_Dropped:
4142 		case APROP_ChaseGoal:
4143 		case APROP_Frightened:
4144 		case APROP_Friendly:
4145 		case APROP_Notarget:
4146 		case APROP_Notrigger:
4147 		case APROP_Dormant:
4148 			return (GetActorProperty(tid, property) == (!!value));
4149 
4150 		// Strings are covered by GetActorProperty, but they're fairly
4151 		// heavy-duty, so make the check here.
4152 		case APROP_SeeSound:	string = actor->SeeSound; break;
4153 		case APROP_AttackSound:	string = actor->AttackSound; break;
4154 		case APROP_PainSound:	string = actor->PainSound; break;
4155 		case APROP_DeathSound:	string = actor->DeathSound; break;
4156 		case APROP_ActiveSound:	string = actor->ActiveSound; break;
4157 		case APROP_Species:		string = actor->GetSpecies(); break;
4158 		case APROP_NameTag:		string = actor->GetTag(); break;
4159 	}
4160 	if (string == NULL) string = "";
4161 	return (!stricmp(string, FBehavior::StaticLookupString(value)));
4162 }
4163 
DoCheckActorTexture(int tid,AActor * activator,int string,bool floor)4164 bool DLevelScript::DoCheckActorTexture(int tid, AActor *activator, int string, bool floor)
4165 {
4166 	AActor *actor = SingleActorFromTID(tid, activator);
4167 	if (actor == NULL)
4168 	{
4169 		return 0;
4170 	}
4171 	FTexture *tex = TexMan.FindTexture(FBehavior::StaticLookupString(string), FTexture::TEX_Flat,
4172 			FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny|FTextureManager::TEXMAN_DontCreate);
4173 
4174 	if (tex == NULL)
4175 	{ // If the texture we want to check against doesn't exist, then
4176 	  // they're obviously not the same.
4177 		return 0;
4178 	}
4179 	int i, numff;
4180 	FTextureID secpic;
4181 	sector_t *sec = actor->Sector;
4182 	numff = sec->e->XFloor.ffloors.Size();
4183 
4184 	if (floor)
4185 	{
4186 		// Looking through planes from top to bottom
4187 		for (i = 0; i < numff; ++i)
4188 		{
4189 			F3DFloor *ff = sec->e->XFloor.ffloors[i];
4190 
4191 			if ((ff->flags & (FF_EXISTS | FF_SOLID)) == (FF_EXISTS | FF_SOLID) &&
4192 				actor->Z() >= ff->top.plane->ZatPoint(actor))
4193 			{ // This floor is beneath our feet.
4194 				secpic = *ff->top.texture;
4195 				break;
4196 			}
4197 		}
4198 		if (i == numff)
4199 		{ // Use sector's floor
4200 			secpic = sec->GetTexture(sector_t::floor);
4201 		}
4202 	}
4203 	else
4204 	{
4205 		fixed_t z = actor->Top();
4206 		// Looking through planes from bottom to top
4207 		for (i = numff-1; i >= 0; --i)
4208 		{
4209 			F3DFloor *ff = sec->e->XFloor.ffloors[i];
4210 
4211 			if ((ff->flags & (FF_EXISTS | FF_SOLID)) == (FF_EXISTS | FF_SOLID) &&
4212 				z <= ff->bottom.plane->ZatPoint(actor))
4213 			{ // This floor is above our eyes.
4214 				secpic = *ff->bottom.texture;
4215 				break;
4216 			}
4217 		}
4218 		if (i < 0)
4219 		{ // Use sector's ceiling
4220 			secpic = sec->GetTexture(sector_t::ceiling);
4221 		}
4222 	}
4223 	return tex == TexMan[secpic];
4224 }
4225 
4226 enum
4227 {
4228 	// These are the original inputs sent by the player.
4229 	INPUT_OLDBUTTONS,
4230 	INPUT_BUTTONS,
4231 	INPUT_PITCH,
4232 	INPUT_YAW,
4233 	INPUT_ROLL,
4234 	INPUT_FORWARDMOVE,
4235 	INPUT_SIDEMOVE,
4236 	INPUT_UPMOVE,
4237 
4238 	// These are the inputs, as modified by P_PlayerThink().
4239 	// Most of the time, these will match the original inputs, but
4240 	// they can be different if a player is frozen or using a
4241 	// chainsaw.
4242 	MODINPUT_OLDBUTTONS,
4243 	MODINPUT_BUTTONS,
4244 	MODINPUT_PITCH,
4245 	MODINPUT_YAW,
4246 	MODINPUT_ROLL,
4247 	MODINPUT_FORWARDMOVE,
4248 	MODINPUT_SIDEMOVE,
4249 	MODINPUT_UPMOVE
4250 };
4251 
GetPlayerInput(int playernum,int inputnum)4252 int DLevelScript::GetPlayerInput(int playernum, int inputnum)
4253 {
4254 	player_t *p;
4255 
4256 	if (playernum < 0)
4257 	{
4258 		if (activator == NULL)
4259 		{
4260 			return 0;
4261 		}
4262 		p = activator->player;
4263 	}
4264 	else if (playernum >= MAXPLAYERS || !playeringame[playernum])
4265 	{
4266 		return 0;
4267 	}
4268 	else
4269 	{
4270 		p = &players[playernum];
4271 	}
4272 	if (p == NULL)
4273 	{
4274 		return 0;
4275 	}
4276 
4277 	switch (inputnum)
4278 	{
4279 	case INPUT_OLDBUTTONS:		return p->original_oldbuttons;		break;
4280 	case INPUT_BUTTONS:			return p->original_cmd.buttons;		break;
4281 	case INPUT_PITCH:			return p->original_cmd.pitch;		break;
4282 	case INPUT_YAW:				return p->original_cmd.yaw;			break;
4283 	case INPUT_ROLL:			return p->original_cmd.roll;		break;
4284 	case INPUT_FORWARDMOVE:		return p->original_cmd.forwardmove;	break;
4285 	case INPUT_SIDEMOVE:		return p->original_cmd.sidemove;	break;
4286 	case INPUT_UPMOVE:			return p->original_cmd.upmove;		break;
4287 
4288 	case MODINPUT_OLDBUTTONS:	return p->oldbuttons;				break;
4289 	case MODINPUT_BUTTONS:		return p->cmd.ucmd.buttons;			break;
4290 	case MODINPUT_PITCH:		return p->cmd.ucmd.pitch;			break;
4291 	case MODINPUT_YAW:			return p->cmd.ucmd.yaw;				break;
4292 	case MODINPUT_ROLL:			return p->cmd.ucmd.roll;			break;
4293 	case MODINPUT_FORWARDMOVE:	return p->cmd.ucmd.forwardmove;		break;
4294 	case MODINPUT_SIDEMOVE:		return p->cmd.ucmd.sidemove;		break;
4295 	case MODINPUT_UPMOVE:		return p->cmd.ucmd.upmove;			break;
4296 
4297 	default:					return 0;							break;
4298 	}
4299 }
4300 
4301 enum
4302 {
4303 	ACTOR_NONE				= 0x00000000,
4304 	ACTOR_WORLD				= 0x00000001,
4305 	ACTOR_PLAYER			= 0x00000002,
4306 	ACTOR_BOT				= 0x00000004,
4307 	ACTOR_VOODOODOLL		= 0x00000008,
4308 	ACTOR_MONSTER			= 0x00000010,
4309 	ACTOR_ALIVE				= 0x00000020,
4310 	ACTOR_DEAD				= 0x00000040,
4311 	ACTOR_MISSILE			= 0x00000080,
4312 	ACTOR_GENERIC			= 0x00000100
4313 };
4314 
DoClassifyActor(int tid)4315 int DLevelScript::DoClassifyActor(int tid)
4316 {
4317 	AActor *actor;
4318 	int classify;
4319 
4320 	if (tid == 0)
4321 	{
4322 		actor = activator;
4323 		if (actor == NULL)
4324 		{
4325 			return ACTOR_WORLD;
4326 		}
4327 	}
4328 	else
4329 	{
4330 		FActorIterator it(tid);
4331 		actor = it.Next();
4332 	}
4333 	if (actor == NULL)
4334 	{
4335 		return ACTOR_NONE;
4336 	}
4337 
4338 	classify = 0;
4339 	if (actor->player != NULL)
4340 	{
4341 		classify |= ACTOR_PLAYER;
4342 		if (actor->player->playerstate == PST_DEAD)
4343 		{
4344 			classify |= ACTOR_DEAD;
4345 		}
4346 		else
4347 		{
4348 			classify |= ACTOR_ALIVE;
4349 		}
4350 		if (actor->player->mo != actor)
4351 		{
4352 			classify |= ACTOR_VOODOODOLL;
4353 		}
4354 		if (actor->player->Bot != NULL)
4355 		{
4356 			classify |= ACTOR_BOT;
4357 		}
4358 	}
4359 	else if (actor->flags3 & MF3_ISMONSTER)
4360 	{
4361 		classify |= ACTOR_MONSTER;
4362 		if (actor->health <= 0)
4363 		{
4364 			classify |= ACTOR_DEAD;
4365 		}
4366 		else
4367 		{
4368 			classify |= ACTOR_ALIVE;
4369 		}
4370 	}
4371 	else if (actor->flags & MF_MISSILE)
4372 	{
4373 		classify |= ACTOR_MISSILE;
4374 	}
4375 	else
4376 	{
4377 		classify |= ACTOR_GENERIC;
4378 	}
4379 	return classify;
4380 }
4381 
4382 enum
4383 {
4384 	SOUND_See,
4385 	SOUND_Attack,
4386 	SOUND_Pain,
4387 	SOUND_Death,
4388 	SOUND_Active,
4389 	SOUND_Use,
4390 	SOUND_Bounce,
4391 	SOUND_WallBounce,
4392 	SOUND_CrushPain,
4393 	SOUND_Howl,
4394 };
4395 
GetActorSound(const AActor * actor,int soundtype)4396 static FSoundID GetActorSound(const AActor *actor, int soundtype)
4397 {
4398 	switch (soundtype)
4399 	{
4400 	case SOUND_See:			return actor->SeeSound;
4401 	case SOUND_Attack:		return actor->AttackSound;
4402 	case SOUND_Pain:		return actor->PainSound;
4403 	case SOUND_Death:		return actor->DeathSound;
4404 	case SOUND_Active:		return actor->ActiveSound;
4405 	case SOUND_Use:			return actor->UseSound;
4406 	case SOUND_Bounce:		return actor->BounceSound;
4407 	case SOUND_WallBounce:	return actor->WallBounceSound;
4408 	case SOUND_CrushPain:	return actor->CrushPainSound;
4409 	case SOUND_Howl:		return actor->GetClass()->Meta.GetMetaInt(AMETA_HowlSound);
4410 	default:				return 0;
4411 	}
4412 }
4413 
4414 enum EACSFunctions
4415 {
4416 	ACSF_GetLineUDMFInt=1,
4417 	ACSF_GetLineUDMFFixed,
4418 	ACSF_GetThingUDMFInt,
4419 	ACSF_GetThingUDMFFixed,
4420 	ACSF_GetSectorUDMFInt,
4421 	ACSF_GetSectorUDMFFixed,
4422 	ACSF_GetSideUDMFInt,
4423 	ACSF_GetSideUDMFFixed,
4424 	ACSF_GetActorVelX,
4425 	ACSF_GetActorVelY,
4426 	ACSF_GetActorVelZ,
4427 	ACSF_SetActivator,
4428 	ACSF_SetActivatorToTarget,
4429 	ACSF_GetActorViewHeight,
4430 	ACSF_GetChar,
4431 	ACSF_GetAirSupply,
4432 	ACSF_SetAirSupply,
4433 	ACSF_SetSkyScrollSpeed,
4434 	ACSF_GetArmorType,
4435 	ACSF_SpawnSpotForced,
4436 	ACSF_SpawnSpotFacingForced,
4437 	ACSF_CheckActorProperty,
4438     ACSF_SetActorVelocity,
4439 	ACSF_SetUserVariable,
4440 	ACSF_GetUserVariable,
4441 	ACSF_Radius_Quake2,
4442 	ACSF_CheckActorClass,
4443 	ACSF_SetUserArray,
4444 	ACSF_GetUserArray,
4445 	ACSF_SoundSequenceOnActor,
4446 	ACSF_SoundSequenceOnSector,
4447 	ACSF_SoundSequenceOnPolyobj,
4448 	ACSF_GetPolyobjX,
4449 	ACSF_GetPolyobjY,
4450     ACSF_CheckSight,
4451 	ACSF_SpawnForced,
4452 	ACSF_AnnouncerSound,	// Skulltag
4453 	ACSF_SetPointer,
4454 	ACSF_ACS_NamedExecute,
4455 	ACSF_ACS_NamedSuspend,
4456 	ACSF_ACS_NamedTerminate,
4457 	ACSF_ACS_NamedLockedExecute,
4458 	ACSF_ACS_NamedLockedExecuteDoor,
4459 	ACSF_ACS_NamedExecuteWithResult,
4460 	ACSF_ACS_NamedExecuteAlways,
4461 	ACSF_UniqueTID,
4462 	ACSF_IsTIDUsed,
4463 	ACSF_Sqrt,
4464 	ACSF_FixedSqrt,
4465 	ACSF_VectorLength,
4466 	ACSF_SetHUDClipRect,
4467 	ACSF_SetHUDWrapWidth,
4468 	ACSF_SetCVar,
4469 	ACSF_GetUserCVar,
4470 	ACSF_SetUserCVar,
4471 	ACSF_GetCVarString,
4472 	ACSF_SetCVarString,
4473 	ACSF_GetUserCVarString,
4474 	ACSF_SetUserCVarString,
4475 	ACSF_LineAttack,
4476 	ACSF_PlaySound,
4477 	ACSF_StopSound,
4478 	ACSF_strcmp,
4479 	ACSF_stricmp,
4480 	ACSF_StrLeft,
4481 	ACSF_StrRight,
4482 	ACSF_StrMid,
4483 	ACSF_GetActorClass,
4484 	ACSF_GetWeapon,
4485 	ACSF_SoundVolume,
4486 	ACSF_PlayActorSound,
4487 	ACSF_SpawnDecal,
4488 	ACSF_CheckFont,
4489 	ACSF_DropItem,
4490 	ACSF_CheckFlag,
4491 	ACSF_SetLineActivation,
4492 	ACSF_GetLineActivation,
4493 	ACSF_GetActorPowerupTics,
4494 	ACSF_ChangeActorAngle,
4495 	ACSF_ChangeActorPitch,		// 80
4496 	ACSF_GetArmorInfo,
4497 	ACSF_DropInventory,
4498 	ACSF_PickActor,
4499 	ACSF_IsPointerEqual,
4500 	ACSF_CanRaiseActor,
4501 	ACSF_SetActorTeleFog,		// 86
4502 	ACSF_SwapActorTeleFog,
4503 	ACSF_SetActorRoll,
4504 	ACSF_ChangeActorRoll,
4505 	ACSF_GetActorRoll,
4506 	ACSF_QuakeEx,
4507 	ACSF_Warp,					// 92
4508 	ACSF_GetMaxInventory,
4509 	ACSF_SetSectorDamage,
4510 	ACSF_SetSectorTerrain,
4511 	ACSF_SpawnParticle,
4512 
4513 	/* Zandronum's - these must be skipped when we reach 99!
4514 	-100:ResetMap(0),
4515 	-101 : PlayerIsSpectator(1),
4516 	-102 : ConsolePlayerNumber(0),
4517 	-103 : GetTeamProperty(2),
4518 	-104 : GetPlayerLivesLeft(1),
4519 	-105 : SetPlayerLivesLeft(2),
4520 	-106 : KickFromGame(2),
4521 	*/
4522 
4523 	// ZDaemon
4524 	ACSF_GetTeamScore = 19620,	// (int team)
4525 	ACSF_SetTeamScore,			// (int team, int value)
4526 };
4527 
SideFromID(int id,int side)4528 int DLevelScript::SideFromID(int id, int side)
4529 {
4530 	if (side != 0 && side != 1) return -1;
4531 
4532 	if (id == 0)
4533 	{
4534 		if (activationline == NULL) return -1;
4535 		if (activationline->sidedef[side] == NULL) return -1;
4536 		return activationline->sidedef[side]->Index;
4537 	}
4538 	else
4539 	{
4540 		int line = P_FindFirstLineFromID(id);
4541 		if (line == -1) return -1;
4542 		if (lines[line].sidedef[side] == NULL) return -1;
4543 		return lines[line].sidedef[side]->Index;
4544 	}
4545 }
4546 
LineFromID(int id)4547 int DLevelScript::LineFromID(int id)
4548 {
4549 	if (id == 0)
4550 	{
4551 		if (activationline == NULL) return -1;
4552 		return int(activationline - lines);
4553 	}
4554 	else
4555 	{
4556 		return P_FindFirstLineFromID(id);
4557 	}
4558 }
4559 
SetUserVariable(AActor * self,FName varname,int index,int value)4560 static void SetUserVariable(AActor *self, FName varname, int index, int value)
4561 {
4562 	PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true);
4563 	int max;
4564 	PSymbolVariable *var;
4565 
4566 	if (sym == NULL || sym->SymbolType != SYM_Variable ||
4567 		!(var = static_cast<PSymbolVariable *>(sym))->bUserVar)
4568 	{
4569 		return;
4570 	}
4571 	if (var->ValueType.Type == VAL_Int)
4572 	{
4573 		max = 1;
4574 	}
4575 	else if (var->ValueType.Type == VAL_Array && var->ValueType.BaseType == VAL_Int)
4576 	{
4577 		max = var->ValueType.size;
4578 	}
4579 	else
4580 	{
4581 		return;
4582 	}
4583 	// Set the value of the specified user variable.
4584 	if (index >= 0 && index < max)
4585 	{
4586 		((int *)(reinterpret_cast<BYTE *>(self) + var->offset))[index] = value;
4587 	}
4588 }
4589 
GetUserVariable(AActor * self,FName varname,int index)4590 static int GetUserVariable(AActor *self, FName varname, int index)
4591 {
4592 	PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true);
4593 	int max;
4594 	PSymbolVariable *var;
4595 
4596 	if (sym == NULL || sym->SymbolType != SYM_Variable ||
4597 		!(var = static_cast<PSymbolVariable *>(sym))->bUserVar)
4598 	{
4599 		return 0;
4600 	}
4601 	if (var->ValueType.Type == VAL_Int)
4602 	{
4603 		max = 1;
4604 	}
4605 	else if (var->ValueType.Type == VAL_Array && var->ValueType.BaseType == VAL_Int)
4606 	{
4607 		max = var->ValueType.size;
4608 	}
4609 	else
4610 	{
4611 		return 0;
4612 	}
4613 	// Get the value of the specified user variable.
4614 	if (index >= 0 && index < max)
4615 	{
4616 		return ((int *)(reinterpret_cast<BYTE *>(self) + var->offset))[index];
4617 	}
4618 	return 0;
4619 }
4620 
4621 // Converts fixed- to floating-point as required.
DoSetCVar(FBaseCVar * cvar,int value,bool is_string,bool force=false)4622 static void DoSetCVar(FBaseCVar *cvar, int value, bool is_string, bool force=false)
4623 {
4624 	UCVarValue val;
4625 	ECVarType type;
4626 
4627 	// For serverinfo variables, only the arbitrator should set it.
4628 	// The actual change to this cvar will not show up until it's
4629 	// been replicated to all peers.
4630 	if ((cvar->GetFlags() & CVAR_SERVERINFO) && consoleplayer != Net_Arbitrator)
4631 	{
4632 		return;
4633 	}
4634 	if (is_string)
4635 	{
4636 		val.String = FBehavior::StaticLookupString(value);
4637 		type = CVAR_String;
4638 	}
4639 	else if (cvar->GetRealType() == CVAR_Float)
4640 	{
4641 		val.Float = FIXED2FLOAT(value);
4642 		type = CVAR_Float;
4643 	}
4644 	else
4645 	{
4646 		val.Int = value;
4647 		type = CVAR_Int;
4648 	}
4649 	if (force)
4650 	{
4651 		cvar->ForceSet(val, type, true);
4652 	}
4653 	else
4654 	{
4655 		cvar->SetGenericRep(val, type);
4656 	}
4657 }
4658 
4659 // Converts floating- to fixed-point as required.
DoGetCVar(FBaseCVar * cvar,bool is_string)4660 static int DoGetCVar(FBaseCVar *cvar, bool is_string)
4661 {
4662 	UCVarValue val;
4663 
4664 	if (is_string)
4665 	{
4666 		val = cvar->GetGenericRep(CVAR_String);
4667 		return GlobalACSStrings.AddString(val.String);
4668 	}
4669 	else if (cvar->GetRealType() == CVAR_Float)
4670 	{
4671 		val = cvar->GetGenericRep(CVAR_Float);
4672 		return FLOAT2FIXED(val.Float);
4673 	}
4674 	else
4675 	{
4676 		val = cvar->GetGenericRep(CVAR_Int);
4677 		return val.Int;
4678 	}
4679 }
4680 
GetUserCVar(int playernum,const char * cvarname,bool is_string)4681 static int GetUserCVar(int playernum, const char *cvarname, bool is_string)
4682 {
4683 	if ((unsigned)playernum >= MAXPLAYERS || !playeringame[playernum])
4684 	{
4685 		return 0;
4686 	}
4687 	FBaseCVar **cvar_p = players[playernum].userinfo.CheckKey(FName(cvarname, true));
4688 	FBaseCVar *cvar;
4689 	if (cvar_p == NULL || (cvar = *cvar_p) == NULL || (cvar->GetFlags() & CVAR_IGNORE))
4690 	{
4691 		return 0;
4692 	}
4693 	return DoGetCVar(cvar, is_string);
4694 }
4695 
GetCVar(AActor * activator,const char * cvarname,bool is_string)4696 static int GetCVar(AActor *activator, const char *cvarname, bool is_string)
4697 {
4698 	FBaseCVar *cvar = FindCVar(cvarname, NULL);
4699 	// Either the cvar doesn't exist, or it's for a mod that isn't loaded, so return 0.
4700 	if (cvar == NULL || (cvar->GetFlags() & CVAR_IGNORE))
4701 	{
4702 		return 0;
4703 	}
4704 	else
4705 	{
4706 		// For userinfo cvars, redirect to GetUserCVar
4707 		if (cvar->GetFlags() & CVAR_USERINFO)
4708 		{
4709 			if (activator == NULL || activator->player == NULL)
4710 			{
4711 				return 0;
4712 			}
4713 			return GetUserCVar(int(activator->player - players), cvarname, is_string);
4714 		}
4715 		return DoGetCVar(cvar, is_string);
4716 	}
4717 }
4718 
SetUserCVar(int playernum,const char * cvarname,int value,bool is_string)4719 static int SetUserCVar(int playernum, const char *cvarname, int value, bool is_string)
4720 {
4721 	if ((unsigned)playernum >= MAXPLAYERS || !playeringame[playernum])
4722 	{
4723 		return 0;
4724 	}
4725 	FBaseCVar **cvar_p = players[playernum].userinfo.CheckKey(FName(cvarname, true));
4726 	FBaseCVar *cvar;
4727 	// Only mod-created cvars may be set.
4728 	if (cvar_p == NULL || (cvar = *cvar_p) == NULL || (cvar->GetFlags() & CVAR_IGNORE) || !(cvar->GetFlags() & CVAR_MOD))
4729 	{
4730 		return 0;
4731 	}
4732 	DoSetCVar(cvar, value, is_string);
4733 
4734 	// If we are this player, then also reflect this change in the local version of this cvar.
4735 	if (playernum == consoleplayer)
4736 	{
4737 		FBaseCVar *cvar = FindCVar(cvarname, NULL);
4738 		// If we can find it in the userinfo, then we should also be able to find it in the normal cvar list,
4739 		// but check just to be safe.
4740 		if (cvar != NULL)
4741 		{
4742 			DoSetCVar(cvar, value, is_string, true);
4743 		}
4744 	}
4745 
4746 	return 1;
4747 }
4748 
SetCVar(AActor * activator,const char * cvarname,int value,bool is_string)4749 static int SetCVar(AActor *activator, const char *cvarname, int value, bool is_string)
4750 {
4751 	FBaseCVar *cvar = FindCVar(cvarname, NULL);
4752 	// Only mod-created cvars may be set.
4753 	if (cvar == NULL || (cvar->GetFlags() & (CVAR_IGNORE|CVAR_NOSET)) || !(cvar->GetFlags() & CVAR_MOD))
4754 	{
4755 		return 0;
4756 	}
4757 	// For userinfo cvars, redirect to SetUserCVar
4758 	if (cvar->GetFlags() & CVAR_USERINFO)
4759 	{
4760 		if (activator == NULL || activator->player == NULL)
4761 		{
4762 			return 0;
4763 		}
4764 		return SetUserCVar(int(activator->player - players), cvarname, value, is_string);
4765 	}
4766 	DoSetCVar(cvar, value, is_string);
4767 	return 1;
4768 }
4769 
DoSpawnDecal(AActor * actor,const FDecalTemplate * tpl,int flags,angle_t angle,fixed_t zofs,fixed_t distance)4770 static bool DoSpawnDecal(AActor *actor, const FDecalTemplate *tpl, int flags, angle_t angle, fixed_t zofs, fixed_t distance)
4771 {
4772 	if (!(flags & SDF_ABSANGLE))
4773 	{
4774 		angle += actor->angle;
4775 	}
4776 	return NULL != ShootDecal(tpl, actor, actor->Sector, actor->X(), actor->Y(),
4777 		actor->Z() + (actor->height>>1) - actor->floorclip + actor->GetBobOffset() + zofs,
4778 		angle, distance, !!(flags & SDF_PERMANENT));
4779 }
4780 
SetActorAngle(AActor * activator,int tid,int angle,bool interpolate)4781 static void SetActorAngle(AActor *activator, int tid, int angle, bool interpolate)
4782 {
4783 	if (tid == 0)
4784 	{
4785 		if (activator != NULL)
4786 		{
4787 			activator->SetAngle(angle << 16, interpolate);
4788 		}
4789 	}
4790 	else
4791 	{
4792 		FActorIterator iterator(tid);
4793 		AActor *actor;
4794 
4795 		while ((actor = iterator.Next()))
4796 		{
4797 			actor->SetAngle(angle << 16, interpolate);
4798 		}
4799 	}
4800 }
4801 
SetActorPitch(AActor * activator,int tid,int angle,bool interpolate)4802 static void SetActorPitch(AActor *activator, int tid, int angle, bool interpolate)
4803 {
4804 	if (tid == 0)
4805 	{
4806 		if (activator != NULL)
4807 		{
4808 			activator->SetPitch(angle << 16, interpolate);
4809 		}
4810 	}
4811 	else
4812 	{
4813 		FActorIterator iterator(tid);
4814 		AActor *actor;
4815 
4816 		while ((actor = iterator.Next()))
4817 		{
4818 			actor->SetPitch(angle << 16, interpolate);
4819 		}
4820 	}
4821 }
4822 
SetActorRoll(AActor * activator,int tid,int angle,bool interpolate)4823 static void SetActorRoll(AActor *activator, int tid, int angle, bool interpolate)
4824 {
4825 	if (tid == 0)
4826 	{
4827 		if (activator != NULL)
4828 		{
4829 			activator->SetRoll(angle << 16, interpolate);
4830 		}
4831 	}
4832 	else
4833 	{
4834 		FActorIterator iterator(tid);
4835 		AActor *actor;
4836 
4837 		while ((actor = iterator.Next()))
4838 		{
4839 			actor->SetRoll(angle << 16, interpolate);
4840 		}
4841 	}
4842 }
4843 
SetActorTeleFog(AActor * activator,int tid,FString telefogsrc,FString telefogdest)4844 static void SetActorTeleFog(AActor *activator, int tid, FString telefogsrc, FString telefogdest)
4845 {
4846 	// Set the actor's telefog to the specified actor. Handle "" as "don't
4847 	// change" since "None" should work just fine for disabling the fog (given
4848 	// that it will resolve to NAME_None which is not a valid actor name).
4849 	if (tid == 0)
4850 	{
4851 		if (activator != NULL)
4852 		{
4853 			if (telefogsrc.IsNotEmpty())
4854 				activator->TeleFogSourceType = PClass::FindClass(telefogsrc);
4855 			if (telefogdest.IsNotEmpty())
4856 				activator->TeleFogDestType = PClass::FindClass(telefogdest);
4857 		}
4858 	}
4859 	else
4860 	{
4861 		FActorIterator iterator(tid);
4862 		AActor *actor;
4863 
4864 		const PClass *src = PClass::FindClass(telefogsrc);
4865 		const PClass * dest = PClass::FindClass(telefogdest);
4866 		while ((actor = iterator.Next()))
4867 		{
4868 			if (telefogsrc.IsNotEmpty())
4869 				actor->TeleFogSourceType = src;
4870 			if (telefogdest.IsNotEmpty())
4871 				actor->TeleFogDestType = dest;
4872 		}
4873 	}
4874 }
4875 
SwapActorTeleFog(AActor * activator,int tid)4876 static int SwapActorTeleFog(AActor *activator, int tid)
4877 {
4878 	int count = 0;
4879 	if (tid == 0)
4880 	{
4881 		if ((activator == NULL) || (activator->TeleFogSourceType == activator->TeleFogDestType))
4882 			return 0; //Does nothing if they're the same.
4883 
4884 		swapvalues (activator->TeleFogSourceType, activator->TeleFogDestType);
4885 		return 1;
4886 	}
4887 	else
4888 	{
4889 		FActorIterator iterator(tid);
4890 		AActor *actor;
4891 
4892 		while ((actor = iterator.Next()))
4893 		{
4894 			if (actor->TeleFogSourceType == actor->TeleFogDestType)
4895 				continue; //They're the same. Save the effort.
4896 
4897 			swapvalues (actor->TeleFogSourceType, actor->TeleFogDestType);
4898 			count++;
4899 		}
4900 	}
4901 	return count;
4902 }
4903 
4904 
4905 
CallFunction(int argCount,int funcIndex,SDWORD * args)4906 int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args)
4907 {
4908 	AActor *actor;
4909 	switch(funcIndex)
4910 	{
4911 		case ACSF_GetLineUDMFInt:
4912 			return GetUDMFInt(UDMF_Line, LineFromID(args[0]), FBehavior::StaticLookupString(args[1]));
4913 
4914 		case ACSF_GetLineUDMFFixed:
4915 			return GetUDMFFixed(UDMF_Line, LineFromID(args[0]), FBehavior::StaticLookupString(args[1]));
4916 
4917 		case ACSF_GetThingUDMFInt:
4918 		case ACSF_GetThingUDMFFixed:
4919 			return 0;	// Not implemented yet
4920 
4921 		case ACSF_GetSectorUDMFInt:
4922 			return GetUDMFInt(UDMF_Sector, P_FindFirstSectorFromTag(args[0]), FBehavior::StaticLookupString(args[1]));
4923 
4924 		case ACSF_GetSectorUDMFFixed:
4925 			return GetUDMFFixed(UDMF_Sector, P_FindFirstSectorFromTag(args[0]), FBehavior::StaticLookupString(args[1]));
4926 
4927 		case ACSF_GetSideUDMFInt:
4928 			return GetUDMFInt(UDMF_Side, SideFromID(args[0], args[1]), FBehavior::StaticLookupString(args[2]));
4929 
4930 		case ACSF_GetSideUDMFFixed:
4931 			return GetUDMFFixed(UDMF_Side, SideFromID(args[0], args[1]), FBehavior::StaticLookupString(args[2]));
4932 
4933 		case ACSF_GetActorVelX:
4934 			actor = SingleActorFromTID(args[0], activator);
4935 			return actor != NULL? actor->velx : 0;
4936 
4937 		case ACSF_GetActorVelY:
4938 			actor = SingleActorFromTID(args[0], activator);
4939 			return actor != NULL? actor->vely : 0;
4940 
4941 		case ACSF_GetActorVelZ:
4942 			actor = SingleActorFromTID(args[0], activator);
4943 			return actor != NULL? actor->velz : 0;
4944 
4945 		case ACSF_SetPointer:
4946 			if (activator)
4947 			{
4948 				AActor *ptr = SingleActorFromTID(args[1], activator);
4949 				if (argCount > 2)
4950 				{
4951 					ptr = COPY_AAPTR(ptr, args[2]);
4952 				}
4953 				if (ptr == activator) ptr = NULL;
4954 				ASSIGN_AAPTR(activator, args[0], ptr, (argCount > 3) ? args[3] : 0);
4955 				return ptr != NULL;
4956 			}
4957 			return 0;
4958 
4959 		case ACSF_SetActivator:
4960 			if (argCount > 1 && args[1] != AAPTR_DEFAULT) // condition (x != AAPTR_DEFAULT) is essentially condition (x).
4961 			{
4962 				activator = COPY_AAPTR(SingleActorFromTID(args[0], activator), args[1]);
4963 			}
4964 			else
4965 			{
4966 				activator = SingleActorFromTID(args[0], NULL);
4967 			}
4968 			return activator != NULL;
4969 
4970 		case ACSF_SetActivatorToTarget:
4971 			// [KS] I revised this a little bit
4972 			actor = SingleActorFromTID(args[0], activator);
4973 			if (actor != NULL)
4974 			{
4975 				if (actor->player != NULL && actor->player->playerstate == PST_LIVE)
4976 				{
4977 					P_BulletSlope(actor, &actor);
4978 				}
4979 				else
4980 				{
4981 					actor = actor->target;
4982 				}
4983 				if (actor != NULL) // [FDARI] moved this (actor != NULL)-branch inside the other, so that it is only tried when it can be true
4984 				{
4985 					activator = actor;
4986 					return 1;
4987 				}
4988 			}
4989 			return 0;
4990 
4991 		case ACSF_GetActorViewHeight:
4992 			actor = SingleActorFromTID(args[0], activator);
4993 			if (actor != NULL)
4994 			{
4995 				if (actor->player != NULL)
4996 				{
4997 					return actor->player->mo->ViewHeight + actor->player->crouchviewdelta;
4998 				}
4999 				else
5000 				{
5001 					return actor->GetClass()->Meta.GetMetaFixed(AMETA_CameraHeight, actor->height/2);
5002 				}
5003 			}
5004 			else return 0;
5005 
5006 		case ACSF_GetChar:
5007 		{
5008 			const char *p = FBehavior::StaticLookupString(args[0]);
5009 			if (p != NULL && args[1] >= 0 && args[1] < int(strlen(p)))
5010 			{
5011 				return p[args[1]];
5012 			}
5013 			else
5014 			{
5015 				return 0;
5016 			}
5017 		}
5018 
5019 		case ACSF_GetAirSupply:
5020 		{
5021 			if (args[0] < 0 || args[0] >= MAXPLAYERS || !playeringame[args[0]])
5022 			{
5023 				return 0;
5024 			}
5025 			else
5026 			{
5027 				return players[args[0]].air_finished - level.time;
5028 			}
5029 		}
5030 
5031 		case ACSF_SetAirSupply:
5032 		{
5033 			if (args[0] < 0 || args[0] >= MAXPLAYERS || !playeringame[args[0]])
5034 			{
5035 				return 0;
5036 			}
5037 			else
5038 			{
5039 				players[args[0]].air_finished = args[1] + level.time;
5040 				return 1;
5041 			}
5042 		}
5043 
5044 		case ACSF_SetSkyScrollSpeed:
5045 		{
5046 			if (args[0] == 1) level.skyspeed1 = FIXED2FLOAT(args[1]);
5047 			else if (args[0] == 2) level.skyspeed2 = FIXED2FLOAT(args[1]);
5048 			return 1;
5049 		}
5050 
5051 		case ACSF_GetArmorType:
5052 		{
5053 			if (args[1] < 0 || args[1] >= MAXPLAYERS || !playeringame[args[1]])
5054 			{
5055 				return 0;
5056 			}
5057 			else
5058 			{
5059 				FName p(FBehavior::StaticLookupString(args[0]));
5060 				ABasicArmor * armor = (ABasicArmor *) players[args[1]].mo->FindInventory(NAME_BasicArmor);
5061 				if (armor && armor->ArmorType == p) return armor->Amount;
5062 			}
5063 			return 0;
5064 		}
5065 
5066 		case ACSF_GetArmorInfo:
5067 		{
5068 			if (activator == NULL || activator->player == NULL) return 0;
5069 
5070 			ABasicArmor * equippedarmor = (ABasicArmor *) activator->FindInventory(NAME_BasicArmor);
5071 
5072 			if (equippedarmor && equippedarmor->Amount != 0)
5073 			{
5074 				switch(args[0])
5075 				{
5076 					case ARMORINFO_CLASSNAME:
5077 						return GlobalACSStrings.AddString(equippedarmor->ArmorType.GetChars());
5078 
5079 					case ARMORINFO_SAVEAMOUNT:
5080 						return equippedarmor->MaxAmount;
5081 
5082 					case ARMORINFO_SAVEPERCENT:
5083 						return equippedarmor->SavePercent;
5084 
5085 					case ARMORINFO_MAXABSORB:
5086 						return equippedarmor->MaxAbsorb;
5087 
5088 					case ARMORINFO_MAXFULLABSORB:
5089 						return equippedarmor->MaxFullAbsorb;
5090 
5091 					case ARMORINFO_ACTUALSAVEAMOUNT:
5092 						return equippedarmor->ActualSaveAmount;
5093 
5094 					default:
5095 						return 0;
5096 				}
5097 			}
5098 			return args[0] == ARMORINFO_CLASSNAME ? GlobalACSStrings.AddString("None") : 0;
5099 		}
5100 
5101 		case ACSF_SpawnSpotForced:
5102 			return DoSpawnSpot(args[0], args[1], args[2], args[3], true);
5103 
5104 		case ACSF_SpawnSpotFacingForced:
5105 			return DoSpawnSpotFacing(args[0], args[1], args[2], true);
5106 
5107 		case ACSF_CheckActorProperty:
5108 			return (CheckActorProperty(args[0], args[1], args[2]));
5109 
5110         case ACSF_SetActorVelocity:
5111             if (args[0] == 0)
5112             {
5113 				P_Thing_SetVelocity(activator, args[1], args[2], args[3], !!args[4], !!args[5]);
5114             }
5115             else
5116             {
5117                 TActorIterator<AActor> iterator (args[0]);
5118 
5119                 while ( (actor = iterator.Next ()) )
5120                 {
5121 					P_Thing_SetVelocity(actor, args[1], args[2], args[3], !!args[4], !!args[5]);
5122                 }
5123             }
5124 			return 0;
5125 
5126 		case ACSF_SetUserVariable:
5127 		{
5128 			int cnt = 0;
5129 			FName varname(FBehavior::StaticLookupString(args[1]), true);
5130 			if (varname != NAME_None)
5131 			{
5132 				if (args[0] == 0)
5133 				{
5134 					if (activator != NULL)
5135 					{
5136 						SetUserVariable(activator, varname, 0, args[2]);
5137 					}
5138 					cnt++;
5139 				}
5140 				else
5141 				{
5142 					TActorIterator<AActor> iterator(args[0]);
5143 
5144 					while ( (actor = iterator.Next()) )
5145 					{
5146 						SetUserVariable(actor, varname, 0, args[2]);
5147 						cnt++;
5148 					}
5149 				}
5150 			}
5151 			return cnt;
5152 		}
5153 
5154 		case ACSF_GetUserVariable:
5155 		{
5156 			FName varname(FBehavior::StaticLookupString(args[1]), true);
5157 			if (varname != NAME_None)
5158 			{
5159 				AActor *a = SingleActorFromTID(args[0], activator);
5160 				return a != NULL ? GetUserVariable(a, varname, 0) : 0;
5161 			}
5162 			return 0;
5163 		}
5164 
5165 		case ACSF_SetUserArray:
5166 		{
5167 			int cnt = 0;
5168 			FName varname(FBehavior::StaticLookupString(args[1]), true);
5169 			if (varname != NAME_None)
5170 			{
5171 				if (args[0] == 0)
5172 				{
5173 					if (activator != NULL)
5174 					{
5175 						SetUserVariable(activator, varname, args[2], args[3]);
5176 					}
5177 					cnt++;
5178 				}
5179 				else
5180 				{
5181 					TActorIterator<AActor> iterator(args[0]);
5182 
5183 					while ( (actor = iterator.Next()) )
5184 					{
5185 						SetUserVariable(actor, varname, args[2], args[3]);
5186 						cnt++;
5187 					}
5188 				}
5189 			}
5190 			return cnt;
5191 		}
5192 
5193 		case ACSF_GetUserArray:
5194 		{
5195 			FName varname(FBehavior::StaticLookupString(args[1]), true);
5196 			if (varname != NAME_None)
5197 			{
5198 				AActor *a = SingleActorFromTID(args[0], activator);
5199 				return a != NULL ? GetUserVariable(a, varname, args[2]) : 0;
5200 			}
5201 			return 0;
5202 		}
5203 
5204 		case ACSF_Radius_Quake2:
5205 			P_StartQuake(activator, args[0], args[1], args[2], args[3], args[4], FBehavior::StaticLookupString(args[5]));
5206 			break;
5207 
5208 		case ACSF_CheckActorClass:
5209 		{
5210 			AActor *a = SingleActorFromTID(args[0], activator);
5211 			return a == NULL ? false : a->GetClass()->TypeName == FName(FBehavior::StaticLookupString(args[1]));
5212 		}
5213 
5214 		case ACSF_GetActorClass:
5215 		{
5216 			AActor *a = SingleActorFromTID(args[0], activator);
5217 			return GlobalACSStrings.AddString(a == NULL ? "None" : a->GetClass()->TypeName.GetChars());
5218 		}
5219 
5220 		case ACSF_SoundSequenceOnActor:
5221 			{
5222 				const char *seqname = FBehavior::StaticLookupString(args[1]);
5223 				if (seqname != NULL)
5224 				{
5225 					if (args[0] == 0)
5226 					{
5227 						if (activator != NULL)
5228 						{
5229 							SN_StartSequence(activator, seqname, 0);
5230 						}
5231 					}
5232 					else
5233 					{
5234 						FActorIterator it(args[0]);
5235 						AActor *actor;
5236 
5237 						while ( (actor = it.Next()) )
5238 						{
5239 							SN_StartSequence(actor, seqname, 0);
5240 						}
5241 					}
5242 				}
5243 			}
5244 			break;
5245 
5246 		case ACSF_SoundSequenceOnSector:
5247 			{
5248 				const char *seqname = FBehavior::StaticLookupString(args[1]);
5249 				int space = args[2] < CHAN_FLOOR || args[2] > CHAN_INTERIOR ? CHAN_FULLHEIGHT : args[2];
5250 				if (seqname != NULL)
5251 				{
5252 					FSectorTagIterator it(args[0]);
5253 					int s;
5254 					while ((s = it.Next()) >= 0)
5255 					{
5256 						SN_StartSequence(&sectors[s], args[2], seqname, 0);
5257 					}
5258 				}
5259 			}
5260 			break;
5261 
5262 		case ACSF_SoundSequenceOnPolyobj:
5263 			{
5264 				const char *seqname = FBehavior::StaticLookupString(args[1]);
5265 				if (seqname != NULL)
5266 				{
5267 					FPolyObj *poly = PO_GetPolyobj(args[0]);
5268 					if (poly != NULL)
5269 					{
5270 						SN_StartSequence(poly, seqname, 0);
5271 					}
5272 				}
5273 			}
5274 			break;
5275 
5276 		case ACSF_GetPolyobjX:
5277 			{
5278 				FPolyObj *poly = PO_GetPolyobj(args[0]);
5279 				if (poly != NULL)
5280 				{
5281 					return poly->StartSpot.x;
5282 				}
5283 			}
5284 			return FIXED_MAX;
5285 
5286 		case ACSF_GetPolyobjY:
5287 			{
5288 				FPolyObj *poly = PO_GetPolyobj(args[0]);
5289 				if (poly != NULL)
5290 				{
5291 					return poly->StartSpot.y;
5292 				}
5293 			}
5294 			return FIXED_MAX;
5295 
5296         case ACSF_CheckSight:
5297         {
5298 			AActor *source;
5299 			AActor *dest;
5300 
5301 			int flags = SF_IGNOREVISIBILITY;
5302 
5303 			if (args[2] & 1) flags |= SF_IGNOREWATERBOUNDARY;
5304 			if (args[2] & 2) flags |= SF_SEEPASTBLOCKEVERYTHING | SF_SEEPASTSHOOTABLELINES;
5305 
5306 			if (args[0] == 0)
5307 			{
5308 				source = (AActor *) activator;
5309 
5310 				if (args[1] == 0) return 1; // [KS] I'm sure the activator can see itself.
5311 
5312 				TActorIterator<AActor> dstiter (args[1]);
5313 
5314 				while ( (dest = dstiter.Next ()) )
5315 				{
5316 					if (P_CheckSight(source, dest, flags)) return 1;
5317 				}
5318 			}
5319 			else
5320 			{
5321 				TActorIterator<AActor> srciter (args[0]);
5322 
5323 				while ( (source = srciter.Next ()) )
5324 				{
5325 					if (args[1] != 0)
5326 					{
5327 						TActorIterator<AActor> dstiter (args[1]);
5328 						while ( (dest = dstiter.Next ()) )
5329 						{
5330 							if (P_CheckSight(source, dest, flags)) return 1;
5331 						}
5332 					}
5333 					else
5334 					{
5335 						if (P_CheckSight(source, activator, flags)) return 1;
5336 					}
5337 				}
5338 			}
5339             return 0;
5340         }
5341 
5342 		case ACSF_SpawnForced:
5343 			return DoSpawn(args[0], args[1], args[2], args[3], args[4], args[5], true);
5344 
5345 		case ACSF_ACS_NamedExecute:
5346 		case ACSF_ACS_NamedSuspend:
5347 		case ACSF_ACS_NamedTerminate:
5348 		case ACSF_ACS_NamedLockedExecute:
5349 		case ACSF_ACS_NamedLockedExecuteDoor:
5350 		case ACSF_ACS_NamedExecuteWithResult:
5351 		case ACSF_ACS_NamedExecuteAlways:
5352 			{
5353 				int scriptnum = -FName(FBehavior::StaticLookupString(args[0]));
5354 				int arg1 = argCount > 1 ? args[1] : 0;
5355 				int arg2 = argCount > 2 ? args[2] : 0;
5356 				int arg3 = argCount > 3 ? args[3] : 0;
5357 				int arg4 = argCount > 4 ? args[4] : 0;
5358 				return P_ExecuteSpecial(NamedACSToNormalACS[funcIndex - ACSF_ACS_NamedExecute],
5359 					activationline, activator, backSide,
5360 					scriptnum, arg1, arg2, arg3, arg4);
5361 			}
5362 			break;
5363 
5364 		case ACSF_UniqueTID:
5365 			return P_FindUniqueTID(argCount > 0 ? args[0] : 0, (argCount > 1 && args[1] >= 0) ? args[1] : 0);
5366 
5367 		case ACSF_IsTIDUsed:
5368 			return P_IsTIDUsed(args[0]);
5369 
5370 		case ACSF_Sqrt:
5371 			return xs_FloorToInt(sqrt(double(args[0])));
5372 
5373 		case ACSF_FixedSqrt:
5374 			return FLOAT2FIXED(sqrt(FIXED2DBL(args[0])));
5375 
5376 		case ACSF_VectorLength:
5377 			return FLOAT2FIXED(TVector2<double>(FIXED2DBL(args[0]), FIXED2DBL(args[1])).Length());
5378 
5379 		case ACSF_SetHUDClipRect:
5380 			ClipRectLeft = argCount > 0 ? args[0] : 0;
5381 			ClipRectTop = argCount > 1 ? args[1] : 0;
5382 			ClipRectWidth = argCount > 2 ? args[2] : 0;
5383 			ClipRectHeight = argCount > 3 ? args[3] : 0;
5384 			WrapWidth = argCount > 4 ? args[4] : 0;
5385 			HandleAspect = argCount > 5 ? !!args[5] : true;
5386 			break;
5387 
5388 		case ACSF_SetHUDWrapWidth:
5389 			WrapWidth = argCount > 0 ? args[0] : 0;
5390 			break;
5391 
5392 		case ACSF_GetCVarString:
5393 			if (argCount == 1)
5394 			{
5395 				return GetCVar(activator, FBehavior::StaticLookupString(args[0]), true);
5396 			}
5397 			break;
5398 
5399 		case ACSF_SetCVar:
5400 			if (argCount == 2)
5401 			{
5402 				return SetCVar(activator, FBehavior::StaticLookupString(args[0]), args[1], false);
5403 			}
5404 			break;
5405 
5406 		case ACSF_SetCVarString:
5407 			if (argCount == 2)
5408 			{
5409 				return SetCVar(activator, FBehavior::StaticLookupString(args[0]), args[1], true);
5410 			}
5411 			break;
5412 
5413 		case ACSF_GetUserCVar:
5414 			if (argCount == 2)
5415 			{
5416 				return GetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), false);
5417 			}
5418 			break;
5419 
5420 		case ACSF_GetUserCVarString:
5421 			if (argCount == 2)
5422 			{
5423 				return GetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), true);
5424 			}
5425 			break;
5426 
5427 		case ACSF_SetUserCVar:
5428 			if (argCount == 3)
5429 			{
5430 				return SetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), args[2], false);
5431 			}
5432 			break;
5433 
5434 		case ACSF_SetUserCVarString:
5435 			if (argCount == 3)
5436 			{
5437 				return SetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), args[2], true);
5438 			}
5439 			break;
5440 
5441 		//[RC] A bullet firing function for ACS. Thanks to DavidPH.
5442 		case ACSF_LineAttack:
5443 			{
5444 				fixed_t	angle		= args[1] << FRACBITS;
5445 				fixed_t	pitch		= args[2] << FRACBITS;
5446 				int	damage			= args[3];
5447 				FName pufftype		= argCount > 4 && args[4]? FName(FBehavior::StaticLookupString(args[4])) : NAME_BulletPuff;
5448 				FName damagetype	= argCount > 5 && args[5]? FName(FBehavior::StaticLookupString(args[5])) : NAME_None;
5449 				fixed_t	range		= argCount > 6 && args[6]? args[6] : MISSILERANGE;
5450 				int flags			= argCount > 7 && args[7]? args[7] : 0;
5451 				int pufftid			= argCount > 8 && args[8]? args[8] : 0;
5452 
5453 				int fhflags = 0;
5454 				if (flags & FHF_NORANDOMPUFFZ) fhflags |= LAF_NORANDOMPUFFZ;
5455 				if (flags & FHF_NOIMPACTDECAL) fhflags |= LAF_NOIMPACTDECAL;
5456 
5457 				if (args[0] == 0)
5458 				{
5459 					AActor *puff = P_LineAttack(activator, angle, range, pitch, damage, damagetype, pufftype, fhflags);
5460 					if (puff != NULL && pufftid != 0)
5461 					{
5462 						puff->tid = pufftid;
5463 						puff->AddToHash();
5464 					}
5465 				}
5466 				else
5467 				{
5468 					AActor *source;
5469 					FActorIterator it(args[0]);
5470 
5471 					while ((source = it.Next()) != NULL)
5472 					{
5473 						AActor *puff = P_LineAttack(source, angle, range, pitch, damage, damagetype, pufftype, fhflags);
5474 						if (puff != NULL && pufftid != 0)
5475 						{
5476 							puff->tid = pufftid;
5477 							puff->AddToHash();
5478 						}
5479 					}
5480 				}
5481 			}
5482 			break;
5483 
5484 		case ACSF_PlaySound:
5485 		case ACSF_PlayActorSound:
5486 			// PlaySound(tid, "SoundName", channel, volume, looping, attenuation)
5487 			{
5488 				FSoundID sid;
5489 
5490 				if (funcIndex == ACSF_PlaySound)
5491 				{
5492 					const char *lookup = FBehavior::StaticLookupString(args[1]);
5493 					if (lookup != NULL)
5494 					{
5495 						sid = lookup;
5496 					}
5497 				}
5498 				if (sid != 0 || funcIndex == ACSF_PlayActorSound)
5499 				{
5500 					FActorIterator it(args[0]);
5501 					AActor *spot;
5502 
5503 					int chan = argCount > 2 ? args[2] : CHAN_BODY;
5504 					float vol = argCount > 3 ? FIXED2FLOAT(args[3]) : 1.f;
5505 					INTBOOL looping = argCount > 4 ? args[4] : false;
5506 					float atten = argCount > 5 ? FIXED2FLOAT(args[5]) : ATTN_NORM;
5507 
5508 					if (args[0] == 0)
5509 					{
5510 						spot = activator;
5511 						goto doplaysound;
5512 					}
5513 					while ((spot = it.Next()) != NULL)
5514 					{
5515 doplaysound:			if (funcIndex == ACSF_PlayActorSound)
5516 						{
5517 							sid = GetActorSound(spot, args[1]);
5518 						}
5519 						if (sid != 0)
5520 						{
5521 							if (!looping)
5522 							{
5523 								S_Sound(spot, chan, sid, vol, atten);
5524 							}
5525 							else if (!S_IsActorPlayingSomething(spot, chan & 7, sid))
5526 							{
5527 								S_Sound(spot, chan | CHAN_LOOP, sid, vol, atten);
5528 							}
5529 						}
5530 					}
5531 				}
5532 			}
5533 			break;
5534 
5535 		case ACSF_StopSound:
5536 			{
5537 				int chan = argCount > 1 ? args[1] : CHAN_BODY;
5538 
5539 				if (args[0] == 0)
5540 				{
5541 					S_StopSound(activator, chan);
5542 				}
5543 				else
5544 				{
5545 					FActorIterator it(args[0]);
5546 					AActor *spot;
5547 
5548 					while ((spot = it.Next()) != NULL)
5549 					{
5550 						S_StopSound(spot, chan);
5551 					}
5552 				}
5553 			}
5554 			break;
5555 
5556 		case ACSF_SoundVolume:
5557 			// SoundVolume(int tid, int channel, fixed volume)
5558 			{
5559 				int chan = args[1];
5560 				float volume = FIXED2FLOAT(args[2]);
5561 
5562 				if (args[0] == 0)
5563 				{
5564 					S_ChangeSoundVolume(activator, chan, volume);
5565 				}
5566 				else
5567 				{
5568 					FActorIterator it(args[0]);
5569 					AActor *spot;
5570 
5571 					while ((spot = it.Next()) != NULL)
5572 					{
5573 						S_ChangeSoundVolume(spot, chan, volume);
5574 					}
5575 				}
5576 			}
5577 			break;
5578 
5579 		case ACSF_strcmp:
5580 		case ACSF_stricmp:
5581 			if (argCount >= 2)
5582 			{
5583 				const char *a, *b;
5584 				a = FBehavior::StaticLookupString(args[0]);
5585 				b = FBehavior::StaticLookupString(args[1]);
5586 
5587 				// Don't crash on invalid strings.
5588 				if (a == NULL) a = "";
5589 				if (b == NULL) b = "";
5590 
5591 				if (argCount > 2)
5592 				{
5593 					int n = args[2];
5594 					return (funcIndex == ACSF_strcmp) ? strncmp(a, b, n) : strnicmp(a, b, n);
5595 				}
5596 				else
5597 				{
5598 					return (funcIndex == ACSF_strcmp) ? strcmp(a, b) : stricmp(a, b);
5599 				}
5600 			}
5601 			break;
5602 
5603 		case ACSF_StrLeft:
5604 		case ACSF_StrRight:
5605 			if (argCount >= 2)
5606 			{
5607 				const char *oldstr = FBehavior::StaticLookupString(args[0]);
5608 				if (oldstr == NULL || *oldstr == '\0')
5609 				{
5610 					return GlobalACSStrings.AddString("");
5611 				}
5612 				size_t oldlen = strlen(oldstr);
5613 				size_t newlen = args[1];
5614 
5615 				if (oldlen < newlen)
5616 				{
5617 					newlen = oldlen;
5618 				}
5619 				FString newstr(funcIndex == ACSF_StrLeft ? oldstr : oldstr + oldlen - newlen, newlen);
5620 				return GlobalACSStrings.AddString(newstr);
5621 			}
5622 			break;
5623 
5624 		case ACSF_StrMid:
5625 			if (argCount >= 3)
5626 			{
5627 				const char *oldstr = FBehavior::StaticLookupString(args[0]);
5628 				if (oldstr == NULL || *oldstr == '\0')
5629 				{
5630 					return GlobalACSStrings.AddString("");
5631 				}
5632 				size_t oldlen = strlen(oldstr);
5633 				size_t pos = args[1];
5634 				size_t newlen = args[2];
5635 
5636 				if (pos >= oldlen)
5637 				{
5638 					return GlobalACSStrings.AddString("");
5639 				}
5640 				if (pos + newlen > oldlen || pos + newlen < pos)
5641 				{
5642 					newlen = oldlen - pos;
5643 				}
5644 				return GlobalACSStrings.AddString(FString(oldstr + pos, newlen));
5645 			}
5646 			break;
5647 
5648 		case ACSF_GetWeapon:
5649             if (activator == NULL || activator->player == NULL || // Non-players do not have weapons
5650                 activator->player->ReadyWeapon == NULL)
5651             {
5652                 return GlobalACSStrings.AddString("None");
5653             }
5654             else
5655             {
5656 				return GlobalACSStrings.AddString(activator->player->ReadyWeapon->GetClass()->TypeName.GetChars());
5657             }
5658 
5659 		case ACSF_SpawnDecal:
5660 			// int SpawnDecal(int tid, str decalname, int flags, fixed angle, int zoffset, int distance)
5661 			// Returns number of decals spawned (not including spreading)
5662 			{
5663 				int count = 0;
5664 				const FDecalTemplate *tpl = DecalLibrary.GetDecalByName(FBehavior::StaticLookupString(args[1]));
5665 				if (tpl != NULL)
5666 				{
5667 					int flags = (argCount > 2) ? args[2] : 0;
5668 					angle_t angle = (argCount > 3) ? (args[3] << FRACBITS) : 0;
5669 					fixed_t zoffset = (argCount > 4) ? (args[4] << FRACBITS) : 0;
5670 					fixed_t distance = (argCount > 5) ? (args[5] << FRACBITS) : 64*FRACUNIT;
5671 
5672 					if (args[0] == 0)
5673 					{
5674 						if (activator != NULL)
5675 						{
5676 							count += DoSpawnDecal(activator, tpl, flags, angle, zoffset, distance);
5677 						}
5678 					}
5679 					else
5680 					{
5681 						FActorIterator it(args[0]);
5682 						AActor *actor;
5683 
5684 						while ((actor = it.Next()) != NULL)
5685 						{
5686 							count += DoSpawnDecal(actor, tpl, flags, angle, zoffset, distance);
5687 						}
5688 					}
5689 				}
5690 				return count;
5691 			}
5692 			break;
5693 
5694 		case ACSF_CheckFont:
5695 			// bool CheckFont(str fontname)
5696 			return V_GetFont(FBehavior::StaticLookupString(args[0])) != NULL;
5697 
5698 		case ACSF_DropItem:
5699 		{
5700 			const char *type = FBehavior::StaticLookupString(args[1]);
5701 			int amount = argCount >= 3? args[2] : -1;
5702 			int chance = argCount >= 4? args[3] : 256;
5703 			const PClass *cls = PClass::FindClass(type);
5704 			int cnt = 0;
5705 			if (cls != NULL)
5706 			{
5707 				if (args[0] == 0)
5708 				{
5709 					if (activator != NULL)
5710 					{
5711 						P_DropItem(activator, cls, amount, chance);
5712 						cnt++;
5713 					}
5714 				}
5715 				else
5716 				{
5717 					FActorIterator it(args[0]);
5718 					AActor *actor;
5719 
5720 					while ((actor = it.Next()) != NULL)
5721 					{
5722 						P_DropItem(actor, cls, amount, chance);
5723 						cnt++;
5724 					}
5725 				}
5726 				return cnt;
5727 			}
5728 			break;
5729 		}
5730 
5731 		case ACSF_DropInventory:
5732 		{
5733 			const char *type = FBehavior::StaticLookupString(args[1]);
5734 			AInventory *inv;
5735 
5736 			if (type != NULL)
5737 			{
5738 				if (args[0] == 0)
5739 				{
5740 					if (activator != NULL)
5741 					{
5742 						inv = activator->FindInventory(type);
5743 						if (inv)
5744 						{
5745 							activator->DropInventory(inv);
5746 						}
5747 					}
5748 				}
5749 				else
5750 				{
5751 					FActorIterator it(args[0]);
5752 					AActor *actor;
5753 
5754 					while ((actor = it.Next()) != NULL)
5755 					{
5756 						inv = actor->FindInventory(type);
5757 						if (inv)
5758 						{
5759 							actor->DropInventory(inv);
5760 						}
5761 					}
5762 				}
5763 			}
5764 		break;
5765 		}
5766 
5767 		case ACSF_CheckFlag:
5768 		{
5769 			AActor *actor = SingleActorFromTID(args[0], activator);
5770 			if (actor != NULL)
5771 			{
5772 				return !!CheckActorFlag(actor, FBehavior::StaticLookupString(args[1]));
5773 			}
5774 			break;
5775 		}
5776 
5777 		case ACSF_QuakeEx:
5778 		{
5779 			return P_StartQuakeXYZ(activator, args[0], args[1], args[2], args[3], args[4], args[5], args[6], FBehavior::StaticLookupString(args[7]),
5780 				argCount > 8 ? args[8] : 0,
5781 				argCount > 9 ? FIXED2DBL(args[9]) : 1.0,
5782 				argCount > 10 ? FIXED2DBL(args[10]) : 1.0,
5783 				argCount > 11 ? FIXED2DBL(args[11]) : 1.0 );
5784 		}
5785 
5786 		case ACSF_SetLineActivation:
5787 			if (argCount >= 2)
5788 			{
5789 				int line;
5790 				FLineIdIterator itr(args[0]);
5791 				while ((line = itr.Next()) >= 0)
5792 				{
5793 					lines[line].activation = args[1];
5794 				}
5795 			}
5796 			break;
5797 
5798 		case ACSF_GetLineActivation:
5799 			if (argCount > 0)
5800 			{
5801 				int line = P_FindFirstLineFromID(args[0]);
5802 				return line >= 0 ? lines[line].activation : 0;
5803 			}
5804 			break;
5805 
5806 		case ACSF_GetActorPowerupTics:
5807 			if (argCount >= 2)
5808 			{
5809 				const PClass *powerupclass = PClass::FindClass(FBehavior::StaticLookupString(args[1]));
5810 				if (powerupclass == NULL || !RUNTIME_CLASS(APowerup)->IsAncestorOf(powerupclass))
5811 				{
5812 					Printf("'%s' is not a type of Powerup.\n", FBehavior::StaticLookupString(args[1]));
5813 					return 0;
5814 				}
5815 
5816 				AActor *actor = SingleActorFromTID(args[0], activator);
5817 				if (actor != NULL)
5818 				{
5819 					APowerup* powerup = (APowerup*)actor->FindInventory(powerupclass);
5820 					if (powerup != NULL)
5821 						return powerup->EffectTics;
5822 				}
5823 				return 0;
5824 			}
5825 			break;
5826 
5827 		case ACSF_ChangeActorAngle:
5828 			if (argCount >= 2)
5829 			{
5830 				SetActorAngle(activator, args[0], args[1], argCount > 2 ? !!args[2] : false);
5831 			}
5832 			break;
5833 
5834 		case ACSF_ChangeActorPitch:
5835 			if (argCount >= 2)
5836 			{
5837 				SetActorPitch(activator, args[0], args[1], argCount > 2 ? !!args[2] : false);
5838 			}
5839 			break;
5840 		case ACSF_SetActorTeleFog:
5841 			if (argCount >= 3)
5842 			{
5843 				SetActorTeleFog(activator, args[0], FBehavior::StaticLookupString(args[1]), FBehavior::StaticLookupString(args[2]));
5844 			}
5845 			break;
5846 		case ACSF_SwapActorTeleFog:
5847 			if (argCount >= 1)
5848 			{
5849 				return SwapActorTeleFog(activator, args[0]);
5850 			}
5851 			break;
5852 		case ACSF_PickActor:
5853 			if (argCount >= 5)
5854 			{
5855 				actor = SingleActorFromTID(args[0], activator);
5856 				if (actor == NULL)
5857 				{
5858 					return 0;
5859 				}
5860 
5861 				ActorFlags actorMask = MF_SHOOTABLE;
5862 				if (argCount >= 6) {
5863 					actorMask = ActorFlags::FromInt(args[5]);
5864 				}
5865 
5866 				DWORD wallMask = ML_BLOCKEVERYTHING | ML_BLOCKHITSCAN;
5867 				if (argCount >= 7) {
5868 					wallMask = args[6];
5869 				}
5870 
5871 				int flags = 0;
5872 				if (argCount >= 8)
5873 				{
5874 					flags = args[7];
5875 				}
5876 
5877 				AActor* pickedActor = P_LinePickActor(actor, args[1] << 16, args[3], args[2] << 16, actorMask, wallMask);
5878 				if (pickedActor == NULL) {
5879 					return 0;
5880 				}
5881 
5882 				if (!(flags & PICKAF_FORCETID) && (args[4] == 0) && (pickedActor->tid == 0))
5883 					return 0;
5884 
5885 				if ((pickedActor->tid == 0) || (flags & PICKAF_FORCETID))
5886 				{
5887 					pickedActor->RemoveFromHash();
5888 					pickedActor->tid = args[4];
5889 					pickedActor->AddToHash();
5890 				}
5891 				if (flags & PICKAF_RETURNTID)
5892 				{
5893 					return pickedActor->tid;
5894 				}
5895 				return 1;
5896 			}
5897 			break;
5898 
5899 		case ACSF_IsPointerEqual:
5900 			{
5901 				int tid1 = 0, tid2 = 0;
5902 				switch (argCount)
5903 				{
5904 				case 4: tid2 = args[3];
5905 				case 3: tid1 = args[2];
5906 				}
5907 
5908 				actor = SingleActorFromTID(tid1, activator);
5909 				AActor * actor2 = tid2 == tid1 ? actor : SingleActorFromTID(tid2, activator);
5910 
5911 				return COPY_AAPTR(actor, args[0]) == COPY_AAPTR(actor2, args[1]);
5912 			}
5913 			break;
5914 
5915 		case ACSF_CanRaiseActor:
5916 			if (argCount >= 1) {
5917 				if (args[0] == 0) {
5918 					actor = SingleActorFromTID(args[0], activator);
5919 					if (actor != NULL) {
5920 						return P_Thing_CanRaise(actor);
5921 					}
5922 				}
5923 
5924 				FActorIterator iterator(args[0]);
5925 				bool canraiseall = true;
5926 				while ((actor = iterator.Next()))
5927 				{
5928 					canraiseall = P_Thing_CanRaise(actor) & canraiseall;
5929 				}
5930 
5931 				return canraiseall;
5932 			}
5933 			break;
5934 
5935 		// [Nash] Actor roll functions. Let's roll!
5936 		case ACSF_SetActorRoll:
5937 			actor = SingleActorFromTID(args[0], activator);
5938 			if (actor != NULL)
5939 			{
5940 				actor->SetRoll(args[1] << 16, false);
5941 			}
5942 			return 0;
5943 
5944 		case ACSF_ChangeActorRoll:
5945 			if (argCount >= 2)
5946 			{
5947 				SetActorRoll(activator, args[0], args[1], argCount > 2 ? !!args[2] : false);
5948 			}
5949 			break;
5950 
5951 		case ACSF_GetActorRoll:
5952 			actor = SingleActorFromTID(args[0], activator);
5953 			return actor != NULL? actor->roll >> 16 : 0;
5954 
5955 		// [ZK] A_Warp in ACS
5956 		case ACSF_Warp:
5957 		{
5958 			int tid_dest = args[0];
5959 			fixed_t xofs = args[1];
5960 			fixed_t yofs = args[2];
5961 			fixed_t zofs = args[3];
5962 			angle_t angle = args[4];
5963 			int flags = args[5];
5964 			const char *statename = argCount > 6 ? FBehavior::StaticLookupString(args[6]) : "";
5965 			bool exact = argCount > 7 ? !!args[7] : false;
5966 			fixed_t heightoffset = argCount > 8 ? args[8] : 0;
5967 			fixed_t radiusoffset = argCount > 9 ? args[9] : 0;
5968 			fixed_t pitch = argCount > 10 ? args[10] : 0;
5969 
5970 			FState *state = argCount > 6 ? activator->GetClass()->ActorInfo->FindStateByString(statename, exact) : 0;
5971 
5972 			AActor *reference;
5973 			if((flags & WARPF_USEPTR) && tid_dest != AAPTR_DEFAULT)
5974 			{
5975 				reference = COPY_AAPTR(activator, tid_dest);
5976 			}
5977 			else
5978 			{
5979 				reference = SingleActorFromTID(tid_dest, activator);
5980 			}
5981 
5982 			// If there is no actor to warp to, fail.
5983 			if (!reference)
5984 				return false;
5985 
5986 			if (P_Thing_Warp(activator, reference, xofs, yofs, zofs, angle, flags, heightoffset, radiusoffset, pitch))
5987 			{
5988 				if (state && argCount > 6)
5989 				{
5990 					activator->SetState(state);
5991 				}
5992 				return true;
5993 			}
5994 			return false;
5995 		}
5996 		case ACSF_GetMaxInventory:
5997 			actor = SingleActorFromTID(args[0], activator);
5998 			if (actor != NULL)
5999 			{
6000 				return CheckInventory(actor, FBehavior::StaticLookupString(args[1]), true);
6001 			}
6002 			break;
6003 
6004 		case ACSF_SetSectorDamage:
6005 			if (argCount >= 2)
6006 			{
6007 				FSectorTagIterator it(args[0]);
6008 				int s;
6009 				while ((s = it.Next()) >= 0)
6010 				{
6011 					sector_t *sec = &sectors[s];
6012 
6013 					sec->damageamount = args[1];
6014 					sec->damagetype = argCount >= 3 ? FName(FBehavior::StaticLookupString(args[2])) : FName(NAME_None);
6015 					sec->damageinterval = argCount >= 4 ? clamp(args[3], 1, INT_MAX) : 32;
6016 					sec->leakydamage = argCount >= 5 ? args[4] : 0;
6017 				}
6018 			}
6019 			break;
6020 
6021 		case ACSF_SetSectorTerrain:
6022 			if (argCount >= 3)
6023 			{
6024 				if (args[1] == sector_t::floor || args[1] == sector_t::ceiling)
6025 				{
6026 					int terrain = P_FindTerrain(FBehavior::StaticLookupString(args[2]));
6027 					FSectorTagIterator it(args[0]);
6028 					int s;
6029 					while ((s = it.Next()) >= 0)
6030 					{
6031 						sectors[s].terrainnum[args[1]] = terrain;
6032 					}
6033 				}
6034 			}
6035 			break;
6036 
6037 		case ACSF_SpawnParticle:
6038 		{
6039 			PalEntry color = args[0];
6040 			bool fullbright = argCount > 1 ? !!args[1] : false;
6041 			int lifetime = argCount > 2 ? args[2] : 35;
6042 			int size = argCount > 3 ? args[3] : 1;
6043 			fixed_t x = argCount > 4 ? args[4] : 0;
6044 			fixed_t y = argCount > 5 ? args[5] : 0;
6045 			fixed_t z = argCount > 6 ? args[6] : 0;
6046 			fixed_t xvel = argCount > 7 ? args[7] : 0;
6047 			fixed_t yvel = argCount > 8 ? args[8] : 0;
6048 			fixed_t zvel = argCount > 9 ? args[9] : 0;
6049 			fixed_t accelx = argCount > 10 ? args[10] : 0;
6050 			fixed_t accely = argCount > 11 ? args[11] : 0;
6051 			fixed_t accelz = argCount > 12 ? args[12] : 0;
6052 			int startalpha = argCount > 13 ? args[13] : 0xFF; // Byte trans
6053 			int fadestep = argCount > 14 ? args[14] : -1;
6054 
6055 			startalpha = clamp<int>(startalpha, 0, 255); // Clamp to byte
6056 			lifetime = clamp<int>(lifetime, 0, 255); // Clamp to byte
6057 			fadestep = clamp<int>(fadestep, -1, 255); // Clamp to byte inc. -1 (indicating automatic)
6058 			size = clamp<int>(size, 0, 65535); // Clamp to word
6059 
6060 			if (lifetime != 0)
6061 				P_SpawnParticle(x, y, z, xvel, yvel, zvel, color, fullbright, startalpha, lifetime, size, fadestep, accelx, accely, accelz);
6062 		}
6063 		break;
6064 
6065 		default:
6066 			break;
6067 	}
6068 
6069 
6070 	return 0;
6071 }
6072 
6073 enum
6074 {
6075 	PRINTNAME_LEVELNAME		= -1,
6076 	PRINTNAME_LEVEL			= -2,
6077 	PRINTNAME_SKILL			= -3,
6078 };
6079 
6080 
6081 #define NEXTWORD	(LittleLong(*pc++))
6082 #define NEXTBYTE	(fmt==ACS_LittleEnhanced?getbyte(pc):NEXTWORD)
6083 #define NEXTSHORT	(fmt==ACS_LittleEnhanced?getshort(pc):NEXTWORD)
6084 #define STACK(a)	(Stack[sp - (a)])
6085 #define PushToStack(a)	(Stack[sp++] = (a))
6086 // Direct instructions that take strings need to have the tag applied.
6087 #define TAGSTR(a)	(a|activeBehavior->GetLibraryID())
6088 
getbyte(int * & pc)6089 inline int getbyte (int *&pc)
6090 {
6091 	int res = *(BYTE *)pc;
6092 	pc = (int *)((BYTE *)pc+1);
6093 	return res;
6094 }
6095 
getshort(int * & pc)6096 inline int getshort (int *&pc)
6097 {
6098 	int res = LittleShort( *(SWORD *)pc);
6099 	pc = (int *)((BYTE *)pc+2);
6100 	return res;
6101 }
6102 
CharArrayParms(int & capacity,int & offset,int & a,int * Stack,int & sp,bool ranged)6103 static bool CharArrayParms(int &capacity, int &offset, int &a, int *Stack, int &sp, bool ranged)
6104 {
6105 	if (ranged)
6106 	{
6107 		capacity = STACK(1);
6108 		offset = STACK(2);
6109 		if (capacity < 1 || offset < 0)
6110 		{
6111 			sp -= 4;
6112 			return false;
6113 		}
6114 		sp -= 2;
6115 	}
6116 	else
6117 	{
6118 		capacity = INT_MAX;
6119 		offset = 0;
6120 	}
6121 	a = STACK(1);
6122 	offset += STACK(2);
6123 	sp -= 2;
6124 	return true;
6125 }
6126 
RunScript()6127 int DLevelScript::RunScript ()
6128 {
6129 	DACSThinker *controller = DACSThinker::ActiveThinker;
6130 	SDWORD *locals = localvars;
6131 	ACSLocalArrays noarrays;
6132 	ACSLocalArrays *localarrays = &noarrays;
6133 	ScriptFunction *activeFunction = NULL;
6134 	FRemapTable *translation = 0;
6135 	int resultValue = 1;
6136 
6137 	if (InModuleScriptNumber >= 0)
6138 	{
6139 		ScriptPtr *ptr = activeBehavior->GetScriptPtr(InModuleScriptNumber);
6140 		assert(ptr != NULL);
6141 		if (ptr != NULL)
6142 		{
6143 			localarrays = &ptr->LocalArrays;
6144 		}
6145 	}
6146 
6147 	// Hexen truncates all special arguments to bytes (only when using an old MAPINFO and old ACS format
6148 	const int specialargmask = ((level.flags2 & LEVEL2_HEXENHACK) && activeBehavior->GetFormat() == ACS_Old) ? 255 : ~0;
6149 
6150 	switch (state)
6151 	{
6152 	case SCRIPT_Delayed:
6153 		// Decrement the delay counter and enter state running
6154 		// if it hits 0
6155 		if (--statedata == 0)
6156 			state = SCRIPT_Running;
6157 		break;
6158 
6159 	case SCRIPT_TagWait:
6160 		// Wait for tagged sector(s) to go inactive, then enter
6161 		// state running
6162 	{
6163 		int secnum;
6164 		FSectorTagIterator it(statedata);
6165 		while ((secnum = it.Next()) >= 0)
6166 		{
6167 			if (sectors[secnum].floordata || sectors[secnum].ceilingdata)
6168 				return resultValue;
6169 		}
6170 
6171 		// If we got here, none of the tagged sectors were busy
6172 		state = SCRIPT_Running;
6173 	}
6174 	break;
6175 
6176 	case SCRIPT_PolyWait:
6177 		// Wait for polyobj(s) to stop moving, then enter state running
6178 		if (!PO_Busy (statedata))
6179 		{
6180 			state = SCRIPT_Running;
6181 		}
6182 		break;
6183 
6184 	case SCRIPT_ScriptWaitPre:
6185 		// Wait for a script to start running, then enter state scriptwait
6186 		if (controller->RunningScripts.CheckKey(statedata) != NULL)
6187 			state = SCRIPT_ScriptWait;
6188 		break;
6189 
6190 	case SCRIPT_ScriptWait:
6191 		// Wait for a script to stop running, then enter state running
6192 		if (controller->RunningScripts.CheckKey(statedata) != NULL)
6193 			return resultValue;
6194 
6195 		state = SCRIPT_Running;
6196 		PutFirst ();
6197 		break;
6198 
6199 	default:
6200 		break;
6201 	}
6202 
6203 	FACSStack stackobj;
6204 	SDWORD *Stack = stackobj.buffer;
6205 	int &sp = stackobj.sp;
6206 
6207 	int *pc = this->pc;
6208 	ACSFormat fmt = activeBehavior->GetFormat();
6209 	FBehavior* const savedActiveBehavior = activeBehavior;
6210 	unsigned int runaway = 0;	// used to prevent infinite loops
6211 	int pcd;
6212 	FString work;
6213 	const char *lookup;
6214 	int optstart = -1;
6215 	int temp;
6216 
6217 	while (state == SCRIPT_Running)
6218 	{
6219 		if (++runaway > 2000000)
6220 		{
6221 			Printf ("Runaway %s terminated\n", ScriptPresentation(script).GetChars());
6222 			state = SCRIPT_PleaseRemove;
6223 			break;
6224 		}
6225 
6226 		if (fmt == ACS_LittleEnhanced)
6227 		{
6228 			pcd = getbyte(pc);
6229 			if (pcd >= 256-16)
6230 			{
6231 				pcd = (256-16) + ((pcd - (256-16)) << 8) + getbyte(pc);
6232 			}
6233 		}
6234 		else
6235 		{
6236 			pcd = NEXTWORD;
6237 		}
6238 
6239 		switch (pcd)
6240 		{
6241 		default:
6242 			Printf ("Unknown P-Code %d in %s\n", pcd, ScriptPresentation(script).GetChars());
6243 			activeBehavior = savedActiveBehavior;
6244 			// fall through
6245 		case PCD_TERMINATE:
6246 			DPrintf ("%s finished\n", ScriptPresentation(script).GetChars());
6247 			state = SCRIPT_PleaseRemove;
6248 			break;
6249 
6250 		case PCD_NOP:
6251 			break;
6252 
6253 		case PCD_SUSPEND:
6254 			state = SCRIPT_Suspended;
6255 			break;
6256 
6257 		case PCD_TAGSTRING:
6258 			//Stack[sp-1] |= activeBehavior->GetLibraryID();
6259 			Stack[sp-1] = GlobalACSStrings.AddString(activeBehavior->LookupString(Stack[sp-1]));
6260 			break;
6261 
6262 		case PCD_PUSHNUMBER:
6263 			PushToStack (uallong(pc[0]));
6264 			pc++;
6265 			break;
6266 
6267 		case PCD_PUSHBYTE:
6268 			PushToStack (*(BYTE *)pc);
6269 			pc = (int *)((BYTE *)pc + 1);
6270 			break;
6271 
6272 		case PCD_PUSH2BYTES:
6273 			Stack[sp] = ((BYTE *)pc)[0];
6274 			Stack[sp+1] = ((BYTE *)pc)[1];
6275 			sp += 2;
6276 			pc = (int *)((BYTE *)pc + 2);
6277 			break;
6278 
6279 		case PCD_PUSH3BYTES:
6280 			Stack[sp] = ((BYTE *)pc)[0];
6281 			Stack[sp+1] = ((BYTE *)pc)[1];
6282 			Stack[sp+2] = ((BYTE *)pc)[2];
6283 			sp += 3;
6284 			pc = (int *)((BYTE *)pc + 3);
6285 			break;
6286 
6287 		case PCD_PUSH4BYTES:
6288 			Stack[sp] = ((BYTE *)pc)[0];
6289 			Stack[sp+1] = ((BYTE *)pc)[1];
6290 			Stack[sp+2] = ((BYTE *)pc)[2];
6291 			Stack[sp+3] = ((BYTE *)pc)[3];
6292 			sp += 4;
6293 			pc = (int *)((BYTE *)pc + 4);
6294 			break;
6295 
6296 		case PCD_PUSH5BYTES:
6297 			Stack[sp] = ((BYTE *)pc)[0];
6298 			Stack[sp+1] = ((BYTE *)pc)[1];
6299 			Stack[sp+2] = ((BYTE *)pc)[2];
6300 			Stack[sp+3] = ((BYTE *)pc)[3];
6301 			Stack[sp+4] = ((BYTE *)pc)[4];
6302 			sp += 5;
6303 			pc = (int *)((BYTE *)pc + 5);
6304 			break;
6305 
6306 		case PCD_PUSHBYTES:
6307 			temp = *(BYTE *)pc;
6308 			pc = (int *)((BYTE *)pc + temp + 1);
6309 			for (temp = -temp; temp; temp++)
6310 			{
6311 				PushToStack (*((BYTE *)pc + temp));
6312 			}
6313 			break;
6314 
6315 		case PCD_DUP:
6316 			Stack[sp] = Stack[sp-1];
6317 			sp++;
6318 			break;
6319 
6320 		case PCD_SWAP:
6321 			swapvalues(Stack[sp-2], Stack[sp-1]);
6322 			break;
6323 
6324 		case PCD_LSPEC1:
6325 			P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide,
6326 									STACK(1) & specialargmask, 0, 0, 0, 0);
6327 			sp -= 1;
6328 			break;
6329 
6330 		case PCD_LSPEC2:
6331 			P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide,
6332 									STACK(2) & specialargmask,
6333 									STACK(1) & specialargmask, 0, 0, 0);
6334 			sp -= 2;
6335 			break;
6336 
6337 		case PCD_LSPEC3:
6338 			P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide,
6339 									STACK(3) & specialargmask,
6340 									STACK(2) & specialargmask,
6341 									STACK(1) & specialargmask, 0, 0);
6342 			sp -= 3;
6343 			break;
6344 
6345 		case PCD_LSPEC4:
6346 			P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide,
6347 									STACK(4) & specialargmask,
6348 									STACK(3) & specialargmask,
6349 									STACK(2) & specialargmask,
6350 									STACK(1) & specialargmask, 0);
6351 			sp -= 4;
6352 			break;
6353 
6354 		case PCD_LSPEC5:
6355 			P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide,
6356 									STACK(5) & specialargmask,
6357 									STACK(4) & specialargmask,
6358 									STACK(3) & specialargmask,
6359 									STACK(2) & specialargmask,
6360 									STACK(1) & specialargmask);
6361 			sp -= 5;
6362 			break;
6363 
6364 		case PCD_LSPEC5RESULT:
6365 			STACK(5) = P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide,
6366 									STACK(5) & specialargmask,
6367 									STACK(4) & specialargmask,
6368 									STACK(3) & specialargmask,
6369 									STACK(2) & specialargmask,
6370 									STACK(1) & specialargmask);
6371 			sp -= 4;
6372 			break;
6373 
6374 		case PCD_LSPEC1DIRECT:
6375 			temp = NEXTBYTE;
6376 			P_ExecuteSpecial(temp, activationline, activator, backSide,
6377 								uallong(pc[0]) & specialargmask ,0, 0, 0, 0);
6378 			pc += 1;
6379 			break;
6380 
6381 		case PCD_LSPEC2DIRECT:
6382 			temp = NEXTBYTE;
6383 			P_ExecuteSpecial(temp, activationline, activator, backSide,
6384 								uallong(pc[0]) & specialargmask,
6385 								uallong(pc[1]) & specialargmask, 0, 0, 0);
6386 			pc += 2;
6387 			break;
6388 
6389 		case PCD_LSPEC3DIRECT:
6390 			temp = NEXTBYTE;
6391 			P_ExecuteSpecial(temp, activationline, activator, backSide,
6392 								uallong(pc[0]) & specialargmask,
6393 								uallong(pc[1]) & specialargmask,
6394 								uallong(pc[2]) & specialargmask, 0, 0);
6395 			pc += 3;
6396 			break;
6397 
6398 		case PCD_LSPEC4DIRECT:
6399 			temp = NEXTBYTE;
6400 			P_ExecuteSpecial(temp, activationline, activator, backSide,
6401 								uallong(pc[0]) & specialargmask,
6402 								uallong(pc[1]) & specialargmask,
6403 								uallong(pc[2]) & specialargmask,
6404 								uallong(pc[3]) & specialargmask, 0);
6405 			pc += 4;
6406 			break;
6407 
6408 		case PCD_LSPEC5DIRECT:
6409 			temp = NEXTBYTE;
6410 			P_ExecuteSpecial(temp, activationline, activator, backSide,
6411 								uallong(pc[0]) & specialargmask,
6412 								uallong(pc[1]) & specialargmask,
6413 								uallong(pc[2]) & specialargmask,
6414 								uallong(pc[3]) & specialargmask,
6415 								uallong(pc[4]) & specialargmask);
6416 			pc += 5;
6417 			break;
6418 
6419 		// Parameters for PCD_LSPEC?DIRECTB are by definition bytes so never need and-ing.
6420 		case PCD_LSPEC1DIRECTB:
6421 			P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide,
6422 				((BYTE *)pc)[1], 0, 0, 0, 0);
6423 			pc = (int *)((BYTE *)pc + 2);
6424 			break;
6425 
6426 		case PCD_LSPEC2DIRECTB:
6427 			P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide,
6428 				((BYTE *)pc)[1], ((BYTE *)pc)[2], 0, 0, 0);
6429 			pc = (int *)((BYTE *)pc + 3);
6430 			break;
6431 
6432 		case PCD_LSPEC3DIRECTB:
6433 			P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide,
6434 				((BYTE *)pc)[1], ((BYTE *)pc)[2], ((BYTE *)pc)[3], 0, 0);
6435 			pc = (int *)((BYTE *)pc + 4);
6436 			break;
6437 
6438 		case PCD_LSPEC4DIRECTB:
6439 			P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide,
6440 				((BYTE *)pc)[1], ((BYTE *)pc)[2], ((BYTE *)pc)[3],
6441 				((BYTE *)pc)[4], 0);
6442 			pc = (int *)((BYTE *)pc + 5);
6443 			break;
6444 
6445 		case PCD_LSPEC5DIRECTB:
6446 			P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide,
6447 				((BYTE *)pc)[1], ((BYTE *)pc)[2], ((BYTE *)pc)[3],
6448 				((BYTE *)pc)[4], ((BYTE *)pc)[5]);
6449 			pc = (int *)((BYTE *)pc + 6);
6450 			break;
6451 
6452 		case PCD_CALLFUNC:
6453 			{
6454 				int argCount = NEXTBYTE;
6455 				int funcIndex = NEXTSHORT;
6456 
6457 				int retval = CallFunction(argCount, funcIndex, &STACK(argCount));
6458 				sp -= argCount-1;
6459 				STACK(1) = retval;
6460 			}
6461 			break;
6462 
6463 		case PCD_PUSHFUNCTION:
6464 		{
6465 			int funcnum = NEXTBYTE;
6466 			// Not technically a string, but since we use the same tagging mechanism
6467 			PushToStack(TAGSTR(funcnum));
6468 			break;
6469 		}
6470 		case PCD_CALL:
6471 		case PCD_CALLDISCARD:
6472 		case PCD_CALLSTACK:
6473 			{
6474 				int funcnum;
6475 				int i;
6476 				ScriptFunction *func;
6477 				FBehavior *module;
6478 				SDWORD *mylocals;
6479 
6480 				if(pcd == PCD_CALLSTACK)
6481 				{
6482 					funcnum = STACK(1);
6483 					module = FBehavior::StaticGetModule(funcnum>>LIBRARYID_SHIFT);
6484 					--sp;
6485 
6486 					funcnum &= 0xFFFF; // Clear out tag
6487 				}
6488 				else
6489 				{
6490 					module = activeBehavior;
6491 					funcnum = NEXTBYTE;
6492 				}
6493 				func = module->GetFunction (funcnum, module);
6494 
6495 				if (func == NULL)
6496 				{
6497 					Printf ("Function %d in %s out of range\n", funcnum, ScriptPresentation(script).GetChars());
6498 					state = SCRIPT_PleaseRemove;
6499 					break;
6500 				}
6501 				if (sp + func->LocalCount + 64 > STACK_SIZE)
6502 				{ // 64 is the margin for the function's working space
6503 					Printf ("Out of stack space in %s\n", ScriptPresentation(script).GetChars());
6504 					state = SCRIPT_PleaseRemove;
6505 					break;
6506 				}
6507 				mylocals = locals;
6508 				// The function's first argument is also its first local variable.
6509 				locals = &Stack[sp - func->ArgCount];
6510 				// Make space on the stack for any other variables the function uses.
6511 				for (i = 0; i < func->LocalCount; ++i)
6512 				{
6513 					Stack[sp+i] = 0;
6514 				}
6515 				sp += i;
6516 				::new(&Stack[sp]) CallReturn(activeBehavior->PC2Ofs(pc), activeFunction,
6517 					activeBehavior, mylocals, localarrays, pcd == PCD_CALLDISCARD, runaway);
6518 				sp += (sizeof(CallReturn) + sizeof(int) - 1) / sizeof(int);
6519 				pc = module->Ofs2PC (func->Address);
6520 				localarrays = &func->LocalArrays;
6521 				activeFunction = func;
6522 				activeBehavior = module;
6523 				fmt = module->GetFormat();
6524 			}
6525 			break;
6526 
6527 		case PCD_RETURNVOID:
6528 		case PCD_RETURNVAL:
6529 			{
6530 				int value;
6531 				union
6532 				{
6533 					SDWORD *retsp;
6534 					CallReturn *ret;
6535 				};
6536 
6537 				if (pcd == PCD_RETURNVAL)
6538 				{
6539 					value = Stack[--sp];
6540 				}
6541 				else
6542 				{
6543 					value = 0;
6544 				}
6545 				sp -= sizeof(CallReturn)/sizeof(int);
6546 				retsp = &Stack[sp];
6547 				activeBehavior->GetFunctionProfileData(activeFunction)->AddRun(runaway - ret->EntryInstrCount);
6548 				sp = int(locals - Stack);
6549 				pc = ret->ReturnModule->Ofs2PC(ret->ReturnAddress);
6550 				activeFunction = ret->ReturnFunction;
6551 				activeBehavior = ret->ReturnModule;
6552 				fmt = activeBehavior->GetFormat();
6553 				locals = ret->ReturnLocals;
6554 				localarrays = ret->ReturnArrays;
6555 				if (!ret->bDiscardResult)
6556 				{
6557 					Stack[sp++] = value;
6558 				}
6559 				ret->~CallReturn();
6560 			}
6561 			break;
6562 
6563 		case PCD_ADD:
6564 			STACK(2) = STACK(2) + STACK(1);
6565 			sp--;
6566 			break;
6567 
6568 		case PCD_SUBTRACT:
6569 			STACK(2) = STACK(2) - STACK(1);
6570 			sp--;
6571 			break;
6572 
6573 		case PCD_MULTIPLY:
6574 			STACK(2) = STACK(2) * STACK(1);
6575 			sp--;
6576 			break;
6577 
6578 		case PCD_DIVIDE:
6579 			if (STACK(1) == 0)
6580 			{
6581 				state = SCRIPT_DivideBy0;
6582 			}
6583 			else
6584 			{
6585 				STACK(2) = STACK(2) / STACK(1);
6586 				sp--;
6587 			}
6588 			break;
6589 
6590 		case PCD_MODULUS:
6591 			if (STACK(1) == 0)
6592 			{
6593 				state = SCRIPT_ModulusBy0;
6594 			}
6595 			else
6596 			{
6597 				STACK(2) = STACK(2) % STACK(1);
6598 				sp--;
6599 			}
6600 			break;
6601 
6602 		case PCD_EQ:
6603 			STACK(2) = (STACK(2) == STACK(1));
6604 			sp--;
6605 			break;
6606 
6607 		case PCD_NE:
6608 			STACK(2) = (STACK(2) != STACK(1));
6609 			sp--;
6610 			break;
6611 
6612 		case PCD_LT:
6613 			STACK(2) = (STACK(2) < STACK(1));
6614 			sp--;
6615 			break;
6616 
6617 		case PCD_GT:
6618 			STACK(2) = (STACK(2) > STACK(1));
6619 			sp--;
6620 			break;
6621 
6622 		case PCD_LE:
6623 			STACK(2) = (STACK(2) <= STACK(1));
6624 			sp--;
6625 			break;
6626 
6627 		case PCD_GE:
6628 			STACK(2) = (STACK(2) >= STACK(1));
6629 			sp--;
6630 			break;
6631 
6632 		case PCD_ASSIGNSCRIPTVAR:
6633 			locals[NEXTBYTE] = STACK(1);
6634 			sp--;
6635 			break;
6636 
6637 
6638 		case PCD_ASSIGNMAPVAR:
6639 			*(activeBehavior->MapVars[NEXTBYTE]) = STACK(1);
6640 			sp--;
6641 			break;
6642 
6643 		case PCD_ASSIGNWORLDVAR:
6644 			ACS_WorldVars[NEXTBYTE] = STACK(1);
6645 			sp--;
6646 			break;
6647 
6648 		case PCD_ASSIGNGLOBALVAR:
6649 			ACS_GlobalVars[NEXTBYTE] = STACK(1);
6650 			sp--;
6651 			break;
6652 
6653 		case PCD_ASSIGNSCRIPTARRAY:
6654 			localarrays->Set(locals, NEXTBYTE, STACK(2), STACK(1));
6655 			sp -= 2;
6656 			break;
6657 
6658 		case PCD_ASSIGNMAPARRAY:
6659 			activeBehavior->SetArrayVal (*(activeBehavior->MapVars[NEXTBYTE]), STACK(2), STACK(1));
6660 			sp -= 2;
6661 			break;
6662 
6663 		case PCD_ASSIGNWORLDARRAY:
6664 			ACS_WorldArrays[NEXTBYTE][STACK(2)] = STACK(1);
6665 			sp -= 2;
6666 			break;
6667 
6668 		case PCD_ASSIGNGLOBALARRAY:
6669 			ACS_GlobalArrays[NEXTBYTE][STACK(2)] = STACK(1);
6670 			sp -= 2;
6671 			break;
6672 
6673 		case PCD_PUSHSCRIPTVAR:
6674 			PushToStack (locals[NEXTBYTE]);
6675 			break;
6676 
6677 		case PCD_PUSHMAPVAR:
6678 			PushToStack (*(activeBehavior->MapVars[NEXTBYTE]));
6679 			break;
6680 
6681 		case PCD_PUSHWORLDVAR:
6682 			PushToStack (ACS_WorldVars[NEXTBYTE]);
6683 			break;
6684 
6685 		case PCD_PUSHGLOBALVAR:
6686 			PushToStack (ACS_GlobalVars[NEXTBYTE]);
6687 			break;
6688 
6689 		case PCD_PUSHSCRIPTARRAY:
6690 			STACK(1) = localarrays->Get(locals, NEXTBYTE, STACK(1));
6691 			break;
6692 
6693 		case PCD_PUSHMAPARRAY:
6694 			STACK(1) = activeBehavior->GetArrayVal (*(activeBehavior->MapVars[NEXTBYTE]), STACK(1));
6695 			break;
6696 
6697 		case PCD_PUSHWORLDARRAY:
6698 			STACK(1) = ACS_WorldArrays[NEXTBYTE][STACK(1)];
6699 			break;
6700 
6701 		case PCD_PUSHGLOBALARRAY:
6702 			STACK(1) = ACS_GlobalArrays[NEXTBYTE][STACK(1)];
6703 			break;
6704 
6705 		case PCD_ADDSCRIPTVAR:
6706 			locals[NEXTBYTE] += STACK(1);
6707 			sp--;
6708 			break;
6709 
6710 		case PCD_ADDMAPVAR:
6711 			*(activeBehavior->MapVars[NEXTBYTE]) += STACK(1);
6712 			sp--;
6713 			break;
6714 
6715 		case PCD_ADDWORLDVAR:
6716 			ACS_WorldVars[NEXTBYTE] += STACK(1);
6717 			sp--;
6718 			break;
6719 
6720 		case PCD_ADDGLOBALVAR:
6721 			ACS_GlobalVars[NEXTBYTE] += STACK(1);
6722 			sp--;
6723 			break;
6724 
6725 		case PCD_ADDSCRIPTARRAY:
6726 			{
6727 				int a = NEXTBYTE, i = STACK(2);
6728 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) + STACK(1));
6729 				sp -= 2;
6730 			}
6731 			break;
6732 
6733 		case PCD_ADDMAPARRAY:
6734 			{
6735 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
6736 				int i = STACK(2);
6737 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) + STACK(1));
6738 				sp -= 2;
6739 			}
6740 			break;
6741 
6742 		case PCD_ADDWORLDARRAY:
6743 			{
6744 				int a = NEXTBYTE;
6745 				ACS_WorldArrays[a][STACK(2)] += STACK(1);
6746 				sp -= 2;
6747 			}
6748 			break;
6749 
6750 		case PCD_ADDGLOBALARRAY:
6751 			{
6752 				int a = NEXTBYTE;
6753 				ACS_GlobalArrays[a][STACK(2)] += STACK(1);
6754 				sp -= 2;
6755 			}
6756 			break;
6757 
6758 		case PCD_SUBSCRIPTVAR:
6759 			locals[NEXTBYTE] -= STACK(1);
6760 			sp--;
6761 			break;
6762 
6763 		case PCD_SUBMAPVAR:
6764 			*(activeBehavior->MapVars[NEXTBYTE]) -= STACK(1);
6765 			sp--;
6766 			break;
6767 
6768 		case PCD_SUBWORLDVAR:
6769 			ACS_WorldVars[NEXTBYTE] -= STACK(1);
6770 			sp--;
6771 			break;
6772 
6773 		case PCD_SUBGLOBALVAR:
6774 			ACS_GlobalVars[NEXTBYTE] -= STACK(1);
6775 			sp--;
6776 			break;
6777 
6778 		case PCD_SUBSCRIPTARRAY:
6779 			{
6780 				int a = NEXTBYTE, i = STACK(2);
6781 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) - STACK(1));
6782 				sp -= 2;
6783 			}
6784 			break;
6785 
6786 		case PCD_SUBMAPARRAY:
6787 			{
6788 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
6789 				int i = STACK(2);
6790 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) - STACK(1));
6791 				sp -= 2;
6792 			}
6793 			break;
6794 
6795 		case PCD_SUBWORLDARRAY:
6796 			{
6797 				int a = NEXTBYTE;
6798 				ACS_WorldArrays[a][STACK(2)] -= STACK(1);
6799 				sp -= 2;
6800 			}
6801 			break;
6802 
6803 		case PCD_SUBGLOBALARRAY:
6804 			{
6805 				int a = NEXTBYTE;
6806 				ACS_GlobalArrays[a][STACK(2)] -= STACK(1);
6807 				sp -= 2;
6808 			}
6809 			break;
6810 
6811 		case PCD_MULSCRIPTVAR:
6812 			locals[NEXTBYTE] *= STACK(1);
6813 			sp--;
6814 			break;
6815 
6816 		case PCD_MULMAPVAR:
6817 			*(activeBehavior->MapVars[NEXTBYTE]) *= STACK(1);
6818 			sp--;
6819 			break;
6820 
6821 		case PCD_MULWORLDVAR:
6822 			ACS_WorldVars[NEXTBYTE] *= STACK(1);
6823 			sp--;
6824 			break;
6825 
6826 		case PCD_MULGLOBALVAR:
6827 			ACS_GlobalVars[NEXTBYTE] *= STACK(1);
6828 			sp--;
6829 			break;
6830 
6831 		case PCD_MULSCRIPTARRAY:
6832 			{
6833 				int a = NEXTBYTE, i = STACK(2);
6834 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) * STACK(1));
6835 				sp -= 2;
6836 			}
6837 			break;
6838 
6839 		case PCD_MULMAPARRAY:
6840 			{
6841 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
6842 				int i = STACK(2);
6843 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) * STACK(1));
6844 				sp -= 2;
6845 			}
6846 			break;
6847 
6848 		case PCD_MULWORLDARRAY:
6849 			{
6850 				int a = NEXTBYTE;
6851 				ACS_WorldArrays[a][STACK(2)] *= STACK(1);
6852 				sp -= 2;
6853 			}
6854 			break;
6855 
6856 		case PCD_MULGLOBALARRAY:
6857 			{
6858 				int a = NEXTBYTE;
6859 				ACS_GlobalArrays[a][STACK(2)] *= STACK(1);
6860 				sp -= 2;
6861 			}
6862 			break;
6863 
6864 		case PCD_DIVSCRIPTVAR:
6865 			if (STACK(1) == 0)
6866 			{
6867 				state = SCRIPT_DivideBy0;
6868 			}
6869 			else
6870 			{
6871 				locals[NEXTBYTE] /= STACK(1);
6872 				sp--;
6873 			}
6874 			break;
6875 
6876 		case PCD_DIVMAPVAR:
6877 			if (STACK(1) == 0)
6878 			{
6879 				state = SCRIPT_DivideBy0;
6880 			}
6881 			else
6882 			{
6883 				*(activeBehavior->MapVars[NEXTBYTE]) /= STACK(1);
6884 				sp--;
6885 			}
6886 			break;
6887 
6888 		case PCD_DIVWORLDVAR:
6889 			if (STACK(1) == 0)
6890 			{
6891 				state = SCRIPT_DivideBy0;
6892 			}
6893 			else
6894 			{
6895 				ACS_WorldVars[NEXTBYTE] /= STACK(1);
6896 				sp--;
6897 			}
6898 			break;
6899 
6900 		case PCD_DIVGLOBALVAR:
6901 			if (STACK(1) == 0)
6902 			{
6903 				state = SCRIPT_DivideBy0;
6904 			}
6905 			else
6906 			{
6907 				ACS_GlobalVars[NEXTBYTE] /= STACK(1);
6908 				sp--;
6909 			}
6910 			break;
6911 
6912 		case PCD_DIVSCRIPTARRAY:
6913 			if (STACK(1) == 0)
6914 			{
6915 				state = SCRIPT_DivideBy0;
6916 			}
6917 			else
6918 			{
6919 				int a = NEXTBYTE, i = STACK(2);
6920 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) / STACK(1));
6921 				sp -= 2;
6922 			}
6923 			break;
6924 
6925 		case PCD_DIVMAPARRAY:
6926 			if (STACK(1) == 0)
6927 			{
6928 				state = SCRIPT_DivideBy0;
6929 			}
6930 			else
6931 			{
6932 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
6933 				int i = STACK(2);
6934 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) / STACK(1));
6935 				sp -= 2;
6936 			}
6937 			break;
6938 
6939 		case PCD_DIVWORLDARRAY:
6940 			if (STACK(1) == 0)
6941 			{
6942 				state = SCRIPT_DivideBy0;
6943 			}
6944 			else
6945 			{
6946 				int a = NEXTBYTE;
6947 				ACS_WorldArrays[a][STACK(2)] /= STACK(1);
6948 				sp -= 2;
6949 			}
6950 			break;
6951 
6952 		case PCD_DIVGLOBALARRAY:
6953 			if (STACK(1) == 0)
6954 			{
6955 				state = SCRIPT_DivideBy0;
6956 			}
6957 			else
6958 			{
6959 				int a = NEXTBYTE;
6960 				ACS_GlobalArrays[a][STACK(2)] /= STACK(1);
6961 				sp -= 2;
6962 			}
6963 			break;
6964 
6965 		case PCD_MODSCRIPTVAR:
6966 			if (STACK(1) == 0)
6967 			{
6968 				state = SCRIPT_ModulusBy0;
6969 			}
6970 			else
6971 			{
6972 				locals[NEXTBYTE] %= STACK(1);
6973 				sp--;
6974 			}
6975 			break;
6976 
6977 		case PCD_MODMAPVAR:
6978 			if (STACK(1) == 0)
6979 			{
6980 				state = SCRIPT_ModulusBy0;
6981 			}
6982 			else
6983 			{
6984 				*(activeBehavior->MapVars[NEXTBYTE]) %= STACK(1);
6985 				sp--;
6986 			}
6987 			break;
6988 
6989 		case PCD_MODWORLDVAR:
6990 			if (STACK(1) == 0)
6991 			{
6992 				state = SCRIPT_ModulusBy0;
6993 			}
6994 			else
6995 			{
6996 				ACS_WorldVars[NEXTBYTE] %= STACK(1);
6997 				sp--;
6998 			}
6999 			break;
7000 
7001 		case PCD_MODGLOBALVAR:
7002 			if (STACK(1) == 0)
7003 			{
7004 				state = SCRIPT_ModulusBy0;
7005 			}
7006 			else
7007 			{
7008 				ACS_GlobalVars[NEXTBYTE] %= STACK(1);
7009 				sp--;
7010 			}
7011 			break;
7012 
7013 		case PCD_MODSCRIPTARRAY:
7014 			if (STACK(1) == 0)
7015 			{
7016 				state = SCRIPT_ModulusBy0;
7017 			}
7018 			else
7019 			{
7020 				int a = NEXTBYTE, i = STACK(2);
7021 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) % STACK(1));
7022 				sp -= 2;
7023 			}
7024 			break;
7025 
7026 		case PCD_MODMAPARRAY:
7027 			if (STACK(1) == 0)
7028 			{
7029 				state = SCRIPT_ModulusBy0;
7030 			}
7031 			else
7032 			{
7033 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
7034 				int i = STACK(2);
7035 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) % STACK(1));
7036 				sp -= 2;
7037 			}
7038 			break;
7039 
7040 		case PCD_MODWORLDARRAY:
7041 			if (STACK(1) == 0)
7042 			{
7043 				state = SCRIPT_ModulusBy0;
7044 			}
7045 			else
7046 			{
7047 				int a = NEXTBYTE;
7048 				ACS_WorldArrays[a][STACK(2)] %= STACK(1);
7049 				sp -= 2;
7050 			}
7051 			break;
7052 
7053 		case PCD_MODGLOBALARRAY:
7054 			if (STACK(1) == 0)
7055 			{
7056 				state = SCRIPT_ModulusBy0;
7057 			}
7058 			else
7059 			{
7060 				int a = NEXTBYTE;
7061 				ACS_GlobalArrays[a][STACK(2)] %= STACK(1);
7062 				sp -= 2;
7063 			}
7064 			break;
7065 
7066 		//[MW] start
7067 		case PCD_ANDSCRIPTVAR:
7068 			locals[NEXTBYTE] &= STACK(1);
7069 			sp--;
7070 			break;
7071 
7072 		case PCD_ANDMAPVAR:
7073 			*(activeBehavior->MapVars[NEXTBYTE]) &= STACK(1);
7074 			sp--;
7075 			break;
7076 
7077 		case PCD_ANDWORLDVAR:
7078 			ACS_WorldVars[NEXTBYTE] &= STACK(1);
7079 			sp--;
7080 			break;
7081 
7082 		case PCD_ANDGLOBALVAR:
7083 			ACS_GlobalVars[NEXTBYTE] &= STACK(1);
7084 			sp--;
7085 			break;
7086 
7087 		case PCD_ANDSCRIPTARRAY:
7088 			{
7089 				int a = NEXTBYTE, i = STACK(2);
7090 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) & STACK(1));
7091 				sp -= 2;
7092 			}
7093 			break;
7094 
7095 		case PCD_ANDMAPARRAY:
7096 			{
7097 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
7098 				int i = STACK(2);
7099 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) & STACK(1));
7100 				sp -= 2;
7101 			}
7102 			break;
7103 
7104 		case PCD_ANDWORLDARRAY:
7105 			{
7106 				int a = NEXTBYTE;
7107 				ACS_WorldArrays[a][STACK(2)] &= STACK(1);
7108 				sp -= 2;
7109 			}
7110 			break;
7111 
7112 		case PCD_ANDGLOBALARRAY:
7113 			{
7114 				int a = NEXTBYTE;
7115 				ACS_GlobalArrays[a][STACK(2)] &= STACK(1);
7116 				sp -= 2;
7117 			}
7118 			break;
7119 
7120 		case PCD_EORSCRIPTVAR:
7121 			locals[NEXTBYTE] ^= STACK(1);
7122 			sp--;
7123 			break;
7124 
7125 		case PCD_EORMAPVAR:
7126 			*(activeBehavior->MapVars[NEXTBYTE]) ^= STACK(1);
7127 			sp--;
7128 			break;
7129 
7130 		case PCD_EORWORLDVAR:
7131 			ACS_WorldVars[NEXTBYTE] ^= STACK(1);
7132 			sp--;
7133 			break;
7134 
7135 		case PCD_EORGLOBALVAR:
7136 			ACS_GlobalVars[NEXTBYTE] ^= STACK(1);
7137 			sp--;
7138 			break;
7139 
7140 		case PCD_EORSCRIPTARRAY:
7141 			{
7142 				int a = NEXTBYTE, i = STACK(2);
7143 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) ^ STACK(1));
7144 				sp -= 2;
7145 			}
7146 			break;
7147 
7148 		case PCD_EORMAPARRAY:
7149 			{
7150 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
7151 				int i = STACK(2);
7152 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) ^ STACK(1));
7153 				sp -= 2;
7154 			}
7155 			break;
7156 
7157 		case PCD_EORWORLDARRAY:
7158 			{
7159 				int a = NEXTBYTE;
7160 				ACS_WorldArrays[a][STACK(2)] ^= STACK(1);
7161 				sp -= 2;
7162 			}
7163 			break;
7164 
7165 		case PCD_EORGLOBALARRAY:
7166 			{
7167 				int a = NEXTBYTE;
7168 				ACS_GlobalArrays[a][STACK(2)] ^= STACK(1);
7169 				sp -= 2;
7170 			}
7171 			break;
7172 
7173 		case PCD_ORSCRIPTVAR:
7174 			locals[NEXTBYTE] |= STACK(1);
7175 			sp--;
7176 			break;
7177 
7178 		case PCD_ORMAPVAR:
7179 			*(activeBehavior->MapVars[NEXTBYTE]) |= STACK(1);
7180 			sp--;
7181 			break;
7182 
7183 		case PCD_ORWORLDVAR:
7184 			ACS_WorldVars[NEXTBYTE] |= STACK(1);
7185 			sp--;
7186 			break;
7187 
7188 		case PCD_ORGLOBALVAR:
7189 			ACS_GlobalVars[NEXTBYTE] |= STACK(1);
7190 			sp--;
7191 			break;
7192 
7193 		case PCD_ORSCRIPTARRAY:
7194 			{
7195 				int a = NEXTBYTE, i = STACK(2);
7196 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) | STACK(1));
7197 				sp -= 2;
7198 			}
7199 			break;
7200 
7201 		case PCD_ORMAPARRAY:
7202 			{
7203 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
7204 				int i = STACK(2);
7205 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) | STACK(1));
7206 				sp -= 2;
7207 			}
7208 			break;
7209 
7210 		case PCD_ORWORLDARRAY:
7211 			{
7212 				int a = NEXTBYTE;
7213 				ACS_WorldArrays[a][STACK(2)] |= STACK(1);
7214 				sp -= 2;
7215 			}
7216 			break;
7217 
7218 		case PCD_ORGLOBALARRAY:
7219 			{
7220 				int a = NEXTBYTE;
7221 				int i = STACK(2);
7222 				ACS_GlobalArrays[a][STACK(2)] |= STACK(1);
7223 				sp -= 2;
7224 			}
7225 			break;
7226 
7227 		case PCD_LSSCRIPTVAR:
7228 			locals[NEXTBYTE] <<= STACK(1);
7229 			sp--;
7230 			break;
7231 
7232 		case PCD_LSMAPVAR:
7233 			*(activeBehavior->MapVars[NEXTBYTE]) <<= STACK(1);
7234 			sp--;
7235 			break;
7236 
7237 		case PCD_LSWORLDVAR:
7238 			ACS_WorldVars[NEXTBYTE] <<= STACK(1);
7239 			sp--;
7240 			break;
7241 
7242 		case PCD_LSGLOBALVAR:
7243 			ACS_GlobalVars[NEXTBYTE] <<= STACK(1);
7244 			sp--;
7245 			break;
7246 
7247 		case PCD_LSSCRIPTARRAY:
7248 			{
7249 				int a = NEXTBYTE, i = STACK(2);
7250 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) << STACK(1));
7251 				sp -= 2;
7252 			}
7253 			break;
7254 
7255 		case PCD_LSMAPARRAY:
7256 			{
7257 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
7258 				int i = STACK(2);
7259 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) << STACK(1));
7260 				sp -= 2;
7261 			}
7262 			break;
7263 
7264 		case PCD_LSWORLDARRAY:
7265 			{
7266 				int a = NEXTBYTE;
7267 				ACS_WorldArrays[a][STACK(2)] <<= STACK(1);
7268 				sp -= 2;
7269 			}
7270 			break;
7271 
7272 		case PCD_LSGLOBALARRAY:
7273 			{
7274 				int a = NEXTBYTE;
7275 				ACS_GlobalArrays[a][STACK(2)] <<= STACK(1);
7276 				sp -= 2;
7277 			}
7278 			break;
7279 
7280 		case PCD_RSSCRIPTVAR:
7281 			locals[NEXTBYTE] >>= STACK(1);
7282 			sp--;
7283 			break;
7284 
7285 		case PCD_RSMAPVAR:
7286 			*(activeBehavior->MapVars[NEXTBYTE]) >>= STACK(1);
7287 			sp--;
7288 			break;
7289 
7290 		case PCD_RSWORLDVAR:
7291 			ACS_WorldVars[NEXTBYTE] >>= STACK(1);
7292 			sp--;
7293 			break;
7294 
7295 		case PCD_RSGLOBALVAR:
7296 			ACS_GlobalVars[NEXTBYTE] >>= STACK(1);
7297 			sp--;
7298 			break;
7299 
7300 		case PCD_RSSCRIPTARRAY:
7301 			{
7302 				int a = NEXTBYTE, i = STACK(2);
7303 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) >> STACK(1));
7304 				sp -= 2;
7305 			}
7306 			break;
7307 
7308 		case PCD_RSMAPARRAY:
7309 			{
7310 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
7311 				int i = STACK(2);
7312 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) >> STACK(1));
7313 				sp -= 2;
7314 			}
7315 			break;
7316 
7317 		case PCD_RSWORLDARRAY:
7318 			{
7319 				int a = NEXTBYTE;
7320 				ACS_WorldArrays[a][STACK(2)] >>= STACK(1);
7321 				sp -= 2;
7322 			}
7323 			break;
7324 
7325 		case PCD_RSGLOBALARRAY:
7326 			{
7327 				int a = NEXTBYTE;
7328 				ACS_GlobalArrays[a][STACK(2)] >>= STACK(1);
7329 				sp -= 2;
7330 			}
7331 			break;
7332 		//[MW] end
7333 
7334 		case PCD_INCSCRIPTVAR:
7335 			++locals[NEXTBYTE];
7336 			break;
7337 
7338 		case PCD_INCMAPVAR:
7339 			*(activeBehavior->MapVars[NEXTBYTE]) += 1;
7340 			break;
7341 
7342 		case PCD_INCWORLDVAR:
7343 			++ACS_WorldVars[NEXTBYTE];
7344 			break;
7345 
7346 		case PCD_INCGLOBALVAR:
7347 			++ACS_GlobalVars[NEXTBYTE];
7348 			break;
7349 
7350 		case PCD_INCSCRIPTARRAY:
7351 			{
7352 				int a = NEXTBYTE, i = STACK(1);
7353 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) + 1);
7354 				sp--;
7355 			}
7356 			break;
7357 
7358 		case PCD_INCMAPARRAY:
7359 			{
7360 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
7361 				int i = STACK(1);
7362 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) + 1);
7363 				sp--;
7364 			}
7365 			break;
7366 
7367 		case PCD_INCWORLDARRAY:
7368 			{
7369 				int a = NEXTBYTE;
7370 				ACS_WorldArrays[a][STACK(1)] += 1;
7371 				sp--;
7372 			}
7373 			break;
7374 
7375 		case PCD_INCGLOBALARRAY:
7376 			{
7377 				int a = NEXTBYTE;
7378 				ACS_GlobalArrays[a][STACK(1)] += 1;
7379 				sp--;
7380 			}
7381 			break;
7382 
7383 		case PCD_DECSCRIPTVAR:
7384 			--locals[NEXTBYTE];
7385 			break;
7386 
7387 		case PCD_DECMAPVAR:
7388 			*(activeBehavior->MapVars[NEXTBYTE]) -= 1;
7389 			break;
7390 
7391 		case PCD_DECWORLDVAR:
7392 			--ACS_WorldVars[NEXTBYTE];
7393 			break;
7394 
7395 		case PCD_DECGLOBALVAR:
7396 			--ACS_GlobalVars[NEXTBYTE];
7397 			break;
7398 
7399 		case PCD_DECSCRIPTARRAY:
7400 			{
7401 				int a = NEXTBYTE, i = STACK(1);
7402 				localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) - 1);
7403 				sp--;
7404 			}
7405 			break;
7406 
7407 		case PCD_DECMAPARRAY:
7408 			{
7409 				int a = *(activeBehavior->MapVars[NEXTBYTE]);
7410 				int i = STACK(1);
7411 				activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) - 1);
7412 				sp--;
7413 			}
7414 			break;
7415 
7416 		case PCD_DECWORLDARRAY:
7417 			{
7418 				int a = NEXTBYTE;
7419 				ACS_WorldArrays[a][STACK(1)] -= 1;
7420 				sp--;
7421 			}
7422 			break;
7423 
7424 		case PCD_DECGLOBALARRAY:
7425 			{
7426 				int a = NEXTBYTE;
7427 				int i = STACK(1);
7428 				ACS_GlobalArrays[a][STACK(1)] -= 1;
7429 				sp--;
7430 			}
7431 			break;
7432 
7433 		case PCD_GOTO:
7434 			pc = activeBehavior->Ofs2PC (LittleLong(*pc));
7435 			break;
7436 
7437 		case PCD_GOTOSTACK:
7438 			pc = activeBehavior->Jump2PC (STACK(1));
7439 			sp--;
7440 			break;
7441 
7442 		case PCD_IFGOTO:
7443 			if (STACK(1))
7444 				pc = activeBehavior->Ofs2PC (LittleLong(*pc));
7445 			else
7446 				pc++;
7447 			sp--;
7448 			break;
7449 
7450 		case PCD_SETRESULTVALUE:
7451 			resultValue = STACK(1);
7452 		case PCD_DROP: //fall through.
7453 			sp--;
7454 			break;
7455 
7456 		case PCD_DELAY:
7457 			statedata = STACK(1) + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen);
7458 			if (statedata > 0)
7459 			{
7460 				state = SCRIPT_Delayed;
7461 			}
7462 			sp--;
7463 			break;
7464 
7465 		case PCD_DELAYDIRECT:
7466 			statedata = uallong(pc[0]) + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen);
7467 			pc++;
7468 			if (statedata > 0)
7469 			{
7470 				state = SCRIPT_Delayed;
7471 			}
7472 			break;
7473 
7474 		case PCD_DELAYDIRECTB:
7475 			statedata = *(BYTE *)pc + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen);
7476 			if (statedata > 0)
7477 			{
7478 				state = SCRIPT_Delayed;
7479 			}
7480 			pc = (int *)((BYTE *)pc + 1);
7481 			break;
7482 
7483 		case PCD_RANDOM:
7484 			STACK(2) = Random (STACK(2), STACK(1));
7485 			sp--;
7486 			break;
7487 
7488 		case PCD_RANDOMDIRECT:
7489 			PushToStack (Random (uallong(pc[0]), uallong(pc[1])));
7490 			pc += 2;
7491 			break;
7492 
7493 		case PCD_RANDOMDIRECTB:
7494 			PushToStack (Random (((BYTE *)pc)[0], ((BYTE *)pc)[1]));
7495 			pc = (int *)((BYTE *)pc + 2);
7496 			break;
7497 
7498 		case PCD_THINGCOUNT:
7499 			STACK(2) = ThingCount (STACK(2), -1, STACK(1), -1);
7500 			sp--;
7501 			break;
7502 
7503 		case PCD_THINGCOUNTDIRECT:
7504 			PushToStack (ThingCount (uallong(pc[0]), -1, uallong(pc[1]), -1));
7505 			pc += 2;
7506 			break;
7507 
7508 		case PCD_THINGCOUNTNAME:
7509 			STACK(2) = ThingCount (-1, STACK(2), STACK(1), -1);
7510 			sp--;
7511 			break;
7512 
7513 		case PCD_THINGCOUNTNAMESECTOR:
7514 			STACK(3) = ThingCount (-1, STACK(3), STACK(2), STACK(1));
7515 			sp -= 2;
7516 			break;
7517 
7518 		case PCD_THINGCOUNTSECTOR:
7519 			STACK(3) = ThingCount (STACK(3), -1, STACK(2), STACK(1));
7520 			sp -= 2;
7521 			break;
7522 
7523 		case PCD_TAGWAIT:
7524 			state = SCRIPT_TagWait;
7525 			statedata = STACK(1);
7526 			sp--;
7527 			break;
7528 
7529 		case PCD_TAGWAITDIRECT:
7530 			state = SCRIPT_TagWait;
7531 			statedata = uallong(pc[0]);
7532 			pc++;
7533 			break;
7534 
7535 		case PCD_POLYWAIT:
7536 			state = SCRIPT_PolyWait;
7537 			statedata = STACK(1);
7538 			sp--;
7539 			break;
7540 
7541 		case PCD_POLYWAITDIRECT:
7542 			state = SCRIPT_PolyWait;
7543 			statedata = uallong(pc[0]);
7544 			pc++;
7545 			break;
7546 
7547 		case PCD_CHANGEFLOOR:
7548 			ChangeFlat (STACK(2), STACK(1), 0);
7549 			sp -= 2;
7550 			break;
7551 
7552 		case PCD_CHANGEFLOORDIRECT:
7553 			ChangeFlat (uallong(pc[0]), TAGSTR(uallong(pc[1])), 0);
7554 			pc += 2;
7555 			break;
7556 
7557 		case PCD_CHANGECEILING:
7558 			ChangeFlat (STACK(2), STACK(1), 1);
7559 			sp -= 2;
7560 			break;
7561 
7562 		case PCD_CHANGECEILINGDIRECT:
7563 			ChangeFlat (uallong(pc[0]), TAGSTR(uallong(pc[1])), 1);
7564 			pc += 2;
7565 			break;
7566 
7567 		case PCD_RESTART:
7568 			{
7569 				const ScriptPtr *scriptp;
7570 
7571 				scriptp = activeBehavior->FindScript (script);
7572 				pc = activeBehavior->GetScriptAddress (scriptp);
7573 			}
7574 			break;
7575 
7576 		case PCD_ANDLOGICAL:
7577 			STACK(2) = (STACK(2) && STACK(1));
7578 			sp--;
7579 			break;
7580 
7581 		case PCD_ORLOGICAL:
7582 			STACK(2) = (STACK(2) || STACK(1));
7583 			sp--;
7584 			break;
7585 
7586 		case PCD_ANDBITWISE:
7587 			STACK(2) = (STACK(2) & STACK(1));
7588 			sp--;
7589 			break;
7590 
7591 		case PCD_ORBITWISE:
7592 			STACK(2) = (STACK(2) | STACK(1));
7593 			sp--;
7594 			break;
7595 
7596 		case PCD_EORBITWISE:
7597 			STACK(2) = (STACK(2) ^ STACK(1));
7598 			sp--;
7599 			break;
7600 
7601 		case PCD_NEGATELOGICAL:
7602 			STACK(1) = !STACK(1);
7603 			break;
7604 
7605 
7606 
7607 
7608 		case PCD_NEGATEBINARY:
7609 			STACK(1) = ~STACK(1);
7610 			break;
7611 
7612 		case PCD_LSHIFT:
7613 			STACK(2) = (STACK(2) << STACK(1));
7614 			sp--;
7615 			break;
7616 
7617 		case PCD_RSHIFT:
7618 			STACK(2) = (STACK(2) >> STACK(1));
7619 			sp--;
7620 			break;
7621 
7622 		case PCD_UNARYMINUS:
7623 			STACK(1) = -STACK(1);
7624 			break;
7625 
7626 		case PCD_IFNOTGOTO:
7627 			if (!STACK(1))
7628 				pc = activeBehavior->Ofs2PC (LittleLong(*pc));
7629 			else
7630 				pc++;
7631 			sp--;
7632 			break;
7633 
7634 		case PCD_LINESIDE:
7635 			PushToStack (backSide);
7636 			break;
7637 
7638 		case PCD_SCRIPTWAIT:
7639 			statedata = STACK(1);
7640 			sp--;
7641 scriptwait:
7642 			if (controller->RunningScripts.CheckKey(statedata) != NULL)
7643 				state = SCRIPT_ScriptWait;
7644 			else
7645 				state = SCRIPT_ScriptWaitPre;
7646 			PutLast ();
7647 			break;
7648 
7649 		case PCD_SCRIPTWAITDIRECT:
7650 			statedata = uallong(pc[0]);
7651 			pc++;
7652 			goto scriptwait;
7653 
7654 		case PCD_SCRIPTWAITNAMED:
7655 			statedata = -FName(FBehavior::StaticLookupString(STACK(1)));
7656 			sp--;
7657 			goto scriptwait;
7658 
7659 		case PCD_CLEARLINESPECIAL:
7660 			if (activationline != NULL)
7661 			{
7662 				activationline->special = 0;
7663 				DPrintf("Cleared line special on line %d\n", (int)(activationline - lines));
7664 			}
7665 			break;
7666 
7667 		case PCD_CASEGOTO:
7668 			if (STACK(1) == uallong(pc[0]))
7669 			{
7670 				pc = activeBehavior->Ofs2PC (uallong(pc[1]));
7671 				sp--;
7672 			}
7673 			else
7674 			{
7675 				pc += 2;
7676 			}
7677 			break;
7678 
7679 		case PCD_CASEGOTOSORTED:
7680 			// The count and jump table are 4-byte aligned
7681 			pc = (int *)(((size_t)pc + 3) & ~3);
7682 			{
7683 				int numcases = uallong(pc[0]); pc++;
7684 				int min = 0, max = numcases-1;
7685 				while (min <= max)
7686 				{
7687 					int mid = (min + max) / 2;
7688 					SDWORD caseval = LittleLong(pc[mid*2]);
7689 					if (caseval == STACK(1))
7690 					{
7691 						pc = activeBehavior->Ofs2PC (LittleLong(pc[mid*2+1]));
7692 						sp--;
7693 						break;
7694 					}
7695 					else if (caseval < STACK(1))
7696 					{
7697 						min = mid + 1;
7698 					}
7699 					else
7700 					{
7701 						max = mid - 1;
7702 					}
7703 				}
7704 				if (min > max)
7705 				{
7706 					// The case was not found, so go to the next instruction.
7707 					pc += numcases * 2;
7708 				}
7709 			}
7710 			break;
7711 
7712 		case PCD_BEGINPRINT:
7713 			STRINGBUILDER_START(work);
7714 			break;
7715 
7716 		case PCD_PRINTSTRING:
7717 		case PCD_PRINTLOCALIZED:
7718 			lookup = FBehavior::StaticLookupString (STACK(1));
7719 			if (pcd == PCD_PRINTLOCALIZED)
7720 			{
7721 				lookup = GStrings(lookup);
7722 			}
7723 			if (lookup != NULL)
7724 			{
7725 				work += lookup;
7726 			}
7727 			--sp;
7728 			break;
7729 
7730 		case PCD_PRINTNUMBER:
7731 			work.AppendFormat ("%d", STACK(1));
7732 			--sp;
7733 			break;
7734 
7735 		case PCD_PRINTBINARY:
7736 			IGNORE_FORMAT_PRE
7737 			work.AppendFormat ("%B", STACK(1));
7738 			IGNORE_FORMAT_POST
7739 			--sp;
7740 			break;
7741 
7742 		case PCD_PRINTHEX:
7743 			work.AppendFormat ("%X", STACK(1));
7744 			--sp;
7745 			break;
7746 
7747 		case PCD_PRINTCHARACTER:
7748 			work += (char)STACK(1);
7749 			--sp;
7750 			break;
7751 
7752 		case PCD_PRINTFIXED:
7753 			work.AppendFormat ("%g", FIXED2FLOAT(STACK(1)));
7754 			--sp;
7755 			break;
7756 
7757 		// [BC] Print activator's name
7758 		// [RH] Fancied up a bit
7759 		case PCD_PRINTNAME:
7760 			{
7761 				player_t *player = NULL;
7762 
7763 				if (STACK(1) < 0)
7764 				{
7765 					switch (STACK(1))
7766 					{
7767 					case PRINTNAME_LEVELNAME:
7768 						work += level.LevelName;
7769 						break;
7770 
7771 					case PRINTNAME_LEVEL:
7772 						work += level.MapName;
7773 						break;
7774 
7775 					case PRINTNAME_SKILL:
7776 						work += G_SkillName();
7777 						break;
7778 
7779 					default:
7780 						work += ' ';
7781 						break;
7782 					}
7783 					sp--;
7784 					break;
7785 
7786 				}
7787 				else if (STACK(1) == 0 || (unsigned)STACK(1) > MAXPLAYERS)
7788 				{
7789 					if (activator)
7790 					{
7791 						player = activator->player;
7792 					}
7793 				}
7794 				else if (playeringame[STACK(1)-1])
7795 				{
7796 					player = &players[STACK(1)-1];
7797 				}
7798 				else
7799 				{
7800 					work.AppendFormat ("Player %d", STACK(1));
7801 					sp--;
7802 					break;
7803 				}
7804 				if (player)
7805 				{
7806 					work += player->userinfo.GetName();
7807 				}
7808 				else if (activator)
7809 				{
7810 					work += activator->GetTag();
7811 				}
7812 				else
7813 				{
7814 					work += ' ';
7815 				}
7816 				sp--;
7817 			}
7818 			break;
7819 
7820 		// Print script character array
7821 		case PCD_PRINTSCRIPTCHARARRAY:
7822 		case PCD_PRINTSCRIPTCHRANGE:
7823 			{
7824 				int capacity, offset, a, c;
7825 				if (CharArrayParms(capacity, offset, a, Stack, sp, pcd == PCD_PRINTSCRIPTCHRANGE))
7826 				{
7827 					while (capacity-- && (c = localarrays->Get(locals, a, offset)) != '\0')
7828 					{
7829 						work += (char)c;
7830 						offset++;
7831 					}
7832 				}
7833 			}
7834 			break;
7835 
7836 		// [JB] Print map character array
7837 		case PCD_PRINTMAPCHARARRAY:
7838 		case PCD_PRINTMAPCHRANGE:
7839 			{
7840 				int capacity, offset, a, c;
7841 				if (CharArrayParms(capacity, offset, a, Stack, sp, pcd == PCD_PRINTMAPCHRANGE))
7842 				{
7843 					while (capacity-- && (c = activeBehavior->GetArrayVal (a, offset)) != '\0')
7844 					{
7845 						work += (char)c;
7846 						offset++;
7847 					}
7848 				}
7849 			}
7850 			break;
7851 
7852 		// [JB] Print world character array
7853 		case PCD_PRINTWORLDCHARARRAY:
7854 		case PCD_PRINTWORLDCHRANGE:
7855 			{
7856 				int capacity, offset, a, c;
7857 				if (CharArrayParms(capacity, offset, a, Stack, sp, pcd == PCD_PRINTWORLDCHRANGE))
7858 				{
7859 					while (capacity-- && (c = ACS_WorldArrays[a][offset]) != '\0')
7860 					{
7861 						work += (char)c;
7862 						offset++;
7863 					}
7864 				}
7865 			}
7866 			break;
7867 
7868 		// [JB] Print global character array
7869 		case PCD_PRINTGLOBALCHARARRAY:
7870 		case PCD_PRINTGLOBALCHRANGE:
7871 			{
7872 				int capacity, offset, a, c;
7873 				if (CharArrayParms(capacity, offset, a, Stack, sp, pcd == PCD_PRINTGLOBALCHRANGE))
7874 				{
7875 					while (capacity-- && (c = ACS_GlobalArrays[a][offset]) != '\0')
7876 					{
7877 						work += (char)c;
7878 						offset++;
7879 					}
7880 				}
7881 			}
7882 			break;
7883 
7884 		// [GRB] Print key name(s) for a command
7885 		case PCD_PRINTBIND:
7886 			lookup = FBehavior::StaticLookupString (STACK(1));
7887 			if (lookup != NULL)
7888 			{
7889 				int key1 = 0, key2 = 0;
7890 
7891 				Bindings.GetKeysForCommand ((char *)lookup, &key1, &key2);
7892 
7893 				if (key2)
7894 					work << KeyNames[key1] << " or " << KeyNames[key2];
7895 				else if (key1)
7896 					work << KeyNames[key1];
7897 				else
7898 					work << "??? (" << (char *)lookup << ')';
7899 			}
7900 			--sp;
7901 			break;
7902 
7903 		case PCD_ENDPRINT:
7904 		case PCD_ENDPRINTBOLD:
7905 		case PCD_MOREHUDMESSAGE:
7906 		case PCD_ENDLOG:
7907 			if (pcd == PCD_ENDLOG)
7908 			{
7909 				Printf ("%s\n", work.GetChars());
7910 				STRINGBUILDER_FINISH(work);
7911 			}
7912 			else if (pcd != PCD_MOREHUDMESSAGE)
7913 			{
7914 				AActor *screen = activator;
7915 				// If a missile is the activator, make the thing that
7916 				// launched the missile the target of the print command.
7917 				if (screen != NULL &&
7918 					screen->player == NULL &&
7919 					(screen->flags & MF_MISSILE) &&
7920 					screen->target != NULL)
7921 				{
7922 					screen = screen->target;
7923 				}
7924 				if (pcd == PCD_ENDPRINTBOLD || screen == NULL ||
7925 					screen->CheckLocalView (consoleplayer))
7926 				{
7927 					C_MidPrint (activefont, work);
7928 				}
7929 				STRINGBUILDER_FINISH(work);
7930 			}
7931 			else
7932 			{
7933 				optstart = -1;
7934 			}
7935 			break;
7936 
7937 		case PCD_OPTHUDMESSAGE:
7938 			optstart = sp;
7939 			break;
7940 
7941 		case PCD_ENDHUDMESSAGE:
7942 		case PCD_ENDHUDMESSAGEBOLD:
7943 			if (optstart == -1)
7944 			{
7945 				optstart = sp;
7946 			}
7947 			{
7948 				AActor *screen = activator;
7949 				if (screen != NULL &&
7950 					screen->player == NULL &&
7951 					(screen->flags & MF_MISSILE) &&
7952 					screen->target != NULL)
7953 				{
7954 					screen = screen->target;
7955 				}
7956 				if (pcd == PCD_ENDHUDMESSAGEBOLD || screen == NULL ||
7957 					players[consoleplayer].mo == screen)
7958 				{
7959 					int type = Stack[optstart-6];
7960 					int id = Stack[optstart-5];
7961 					EColorRange color;
7962 					float x = FIXED2FLOAT(Stack[optstart-3]);
7963 					float y = FIXED2FLOAT(Stack[optstart-2]);
7964 					float holdTime = FIXED2FLOAT(Stack[optstart-1]);
7965 					fixed_t alpha;
7966 					DHUDMessage *msg;
7967 
7968 					if (type & HUDMSG_COLORSTRING)
7969 					{
7970 						color = V_FindFontColor(FBehavior::StaticLookupString(Stack[optstart-4]));
7971 					}
7972 					else
7973 					{
7974 						color = CLAMPCOLOR(Stack[optstart-4]);
7975 					}
7976 
7977 					switch (type & 0xFF)
7978 					{
7979 					default:	// normal
7980 						alpha = (optstart < sp) ? Stack[optstart] : FRACUNIT;
7981 						msg = new DHUDMessage (activefont, work, x, y, hudwidth, hudheight, color, holdTime);
7982 						break;
7983 					case 1:		// fade out
7984 						{
7985 							float fadeTime = (optstart < sp) ? FIXED2FLOAT(Stack[optstart]) : 0.5f;
7986 							alpha = (optstart < sp-1) ? Stack[optstart+1] : FRACUNIT;
7987 							msg = new DHUDMessageFadeOut (activefont, work, x, y, hudwidth, hudheight, color, holdTime, fadeTime);
7988 						}
7989 						break;
7990 					case 2:		// type on, then fade out
7991 						{
7992 							float typeTime = (optstart < sp) ? FIXED2FLOAT(Stack[optstart]) : 0.05f;
7993 							float fadeTime = (optstart < sp-1) ? FIXED2FLOAT(Stack[optstart+1]) : 0.5f;
7994 							alpha = (optstart < sp-2) ? Stack[optstart+2] : FRACUNIT;
7995 							msg = new DHUDMessageTypeOnFadeOut (activefont, work, x, y, hudwidth, hudheight, color, typeTime, holdTime, fadeTime);
7996 						}
7997 						break;
7998 					case 3:		// fade in, then fade out
7999 						{
8000 							float inTime = (optstart < sp) ? FIXED2FLOAT(Stack[optstart]) : 0.5f;
8001 							float outTime = (optstart < sp-1) ? FIXED2FLOAT(Stack[optstart+1]) : 0.5f;
8002 							alpha = (optstart < sp-2) ? Stack[optstart+2] : FRACUNIT;
8003 							msg = new DHUDMessageFadeInOut (activefont, work, x, y, hudwidth, hudheight, color, holdTime, inTime, outTime);
8004 						}
8005 						break;
8006 					}
8007 					msg->SetClipRect(ClipRectLeft, ClipRectTop, ClipRectWidth, ClipRectHeight, HandleAspect);
8008 					if (WrapWidth != 0)
8009 					{
8010 						msg->SetWrapWidth(WrapWidth);
8011 					}
8012 					msg->SetVisibility((type & HUDMSG_VISIBILITY_MASK) >> HUDMSG_VISIBILITY_SHIFT);
8013 					if (type & HUDMSG_NOWRAP)
8014 					{
8015 						msg->SetNoWrap(true);
8016 					}
8017 					if (type & HUDMSG_ALPHA)
8018 					{
8019 						msg->SetAlpha(alpha);
8020 					}
8021 					if (type & HUDMSG_ADDBLEND)
8022 					{
8023 						msg->SetRenderStyle(STYLE_Add);
8024 					}
8025 					StatusBar->AttachMessage (msg, id ? 0xff000000|id : 0,
8026 						(type & HUDMSG_LAYER_MASK) >> HUDMSG_LAYER_SHIFT);
8027 					if (type & HUDMSG_LOG)
8028 					{
8029 						static const char bar[] = TEXTCOLOR_ORANGE "\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
8030 					"\36\36\36\36\36\36\36\36\36\36\36\36\37" TEXTCOLOR_NORMAL "\n";
8031 						static const char logbar[] = "\n<------------------------------->\n";
8032 						char consolecolor[3];
8033 
8034 						consolecolor[0] = '\x1c';
8035 						consolecolor[1] = color >= CR_BRICK && color <= CR_YELLOW ? color + 'A' : '-';
8036 						consolecolor[2] = '\0';
8037 						AddToConsole (-1, bar);
8038 						AddToConsole (-1, consolecolor);
8039 						AddToConsole (-1, work);
8040 						AddToConsole (-1, bar);
8041 					}
8042 				}
8043 			}
8044 			STRINGBUILDER_FINISH(work);
8045 			sp = optstart-6;
8046 			break;
8047 
8048 		case PCD_SETFONT:
8049 			DoSetFont (STACK(1));
8050 			sp--;
8051 			break;
8052 
8053 		case PCD_SETFONTDIRECT:
8054 			DoSetFont (TAGSTR(uallong(pc[0])));
8055 			pc++;
8056 			break;
8057 
8058 		case PCD_PLAYERCOUNT:
8059 			PushToStack (CountPlayers ());
8060 			break;
8061 
8062 		case PCD_GAMETYPE:
8063 			if (gamestate == GS_TITLELEVEL)
8064 				PushToStack (GAME_TITLE_MAP);
8065 			else if (deathmatch)
8066 				PushToStack (GAME_NET_DEATHMATCH);
8067 			else if (multiplayer)
8068 				PushToStack (GAME_NET_COOPERATIVE);
8069 			else
8070 				PushToStack (GAME_SINGLE_PLAYER);
8071 			break;
8072 
8073 		case PCD_GAMESKILL:
8074 			PushToStack (G_SkillProperty(SKILLP_ACSReturn));
8075 			break;
8076 
8077 // [BC] Start ST PCD's
8078 		case PCD_PLAYERHEALTH:
8079 			if (activator)
8080 				PushToStack (activator->health);
8081 			else
8082 				PushToStack (0);
8083 			break;
8084 
8085 		case PCD_PLAYERARMORPOINTS:
8086 			if (activator)
8087 			{
8088 				ABasicArmor *armor = activator->FindInventory<ABasicArmor>();
8089 				PushToStack (armor ? armor->Amount : 0);
8090 			}
8091 			else
8092 			{
8093 				PushToStack (0);
8094 			}
8095 			break;
8096 
8097 		case PCD_PLAYERFRAGS:
8098 			if (activator && activator->player)
8099 				PushToStack (activator->player->fragcount);
8100 			else
8101 				PushToStack (0);
8102 			break;
8103 
8104 		case PCD_MUSICCHANGE:
8105 			lookup = FBehavior::StaticLookupString (STACK(2));
8106 			if (lookup != NULL)
8107 			{
8108 				S_ChangeMusic (lookup, STACK(1));
8109 			}
8110 			sp -= 2;
8111 			break;
8112 
8113 		case PCD_SINGLEPLAYER:
8114 			PushToStack (!netgame);
8115 			break;
8116 // [BC] End ST PCD's
8117 
8118 		case PCD_TIMER:
8119 			PushToStack (level.time);
8120 			break;
8121 
8122 		case PCD_SECTORSOUND:
8123 			lookup = FBehavior::StaticLookupString (STACK(2));
8124 			if (lookup != NULL)
8125 			{
8126 				if (activationline)
8127 				{
8128 					S_Sound (
8129 						activationline->frontsector,
8130 						CHAN_AUTO,	// Not CHAN_AREA, because that'd probably break existing scripts.
8131 						lookup,
8132 						(float)(STACK(1)) / 127.f,
8133 						ATTN_NORM);
8134 				}
8135 				else
8136 				{
8137 					S_Sound (
8138 						CHAN_AUTO,
8139 						lookup,
8140 						(float)(STACK(1)) / 127.f,
8141 						ATTN_NORM);
8142 				}
8143 			}
8144 			sp -= 2;
8145 			break;
8146 
8147 		case PCD_AMBIENTSOUND:
8148 			lookup = FBehavior::StaticLookupString (STACK(2));
8149 			if (lookup != NULL)
8150 			{
8151 				S_Sound (CHAN_AUTO,
8152 						 lookup,
8153 						 (float)(STACK(1)) / 127.f, ATTN_NONE);
8154 			}
8155 			sp -= 2;
8156 			break;
8157 
8158 		case PCD_LOCALAMBIENTSOUND:
8159 			lookup = FBehavior::StaticLookupString (STACK(2));
8160 			if (lookup != NULL && activator->CheckLocalView (consoleplayer))
8161 			{
8162 				S_Sound (CHAN_AUTO,
8163 						 lookup,
8164 						 (float)(STACK(1)) / 127.f, ATTN_NONE);
8165 			}
8166 			sp -= 2;
8167 			break;
8168 
8169 		case PCD_ACTIVATORSOUND:
8170 			lookup = FBehavior::StaticLookupString (STACK(2));
8171 			if (lookup != NULL)
8172 			{
8173 				if (activator != NULL)
8174 				{
8175 					S_Sound (activator, CHAN_AUTO,
8176 							 lookup,
8177 							 (float)(STACK(1)) / 127.f, ATTN_NORM);
8178 				}
8179 				else
8180 				{
8181 					S_Sound (CHAN_AUTO,
8182 							 lookup,
8183 							 (float)(STACK(1)) / 127.f, ATTN_NONE);
8184 				}
8185 			}
8186 			sp -= 2;
8187 			break;
8188 
8189 		case PCD_SOUNDSEQUENCE:
8190 			lookup = FBehavior::StaticLookupString (STACK(1));
8191 			if (lookup != NULL)
8192 			{
8193 				if (activationline != NULL)
8194 				{
8195 					SN_StartSequence (activationline->frontsector, CHAN_FULLHEIGHT, lookup, 0);
8196 				}
8197 			}
8198 			sp--;
8199 			break;
8200 
8201 		case PCD_SETLINETEXTURE:
8202 			SetLineTexture (STACK(4), STACK(3), STACK(2), STACK(1));
8203 			sp -= 4;
8204 			break;
8205 
8206 		case PCD_REPLACETEXTURES:
8207 			ReplaceTextures (STACK(3), STACK(2), STACK(1));
8208 			sp -= 3;
8209 			break;
8210 
8211 		case PCD_SETLINEBLOCKING:
8212 			{
8213 				int line;
8214 
8215 				FLineIdIterator itr(STACK(2));
8216 				while ((line = itr.Next()) >= 0)
8217 				{
8218 					switch (STACK(1))
8219 					{
8220 					case BLOCK_NOTHING:
8221 						lines[line].flags &= ~(ML_BLOCKING|ML_BLOCKEVERYTHING|ML_RAILING|ML_BLOCK_PLAYERS);
8222 						break;
8223 					case BLOCK_CREATURES:
8224 					default:
8225 						lines[line].flags &= ~(ML_BLOCKEVERYTHING|ML_RAILING|ML_BLOCK_PLAYERS);
8226 						lines[line].flags |= ML_BLOCKING;
8227 						break;
8228 					case BLOCK_EVERYTHING:
8229 						lines[line].flags &= ~(ML_RAILING|ML_BLOCK_PLAYERS);
8230 						lines[line].flags |= ML_BLOCKING|ML_BLOCKEVERYTHING;
8231 						break;
8232 					case BLOCK_RAILING:
8233 						lines[line].flags &= ~(ML_BLOCKEVERYTHING|ML_BLOCK_PLAYERS);
8234 						lines[line].flags |= ML_RAILING|ML_BLOCKING;
8235 						break;
8236 					case BLOCK_PLAYERS:
8237 						lines[line].flags &= ~(ML_BLOCKEVERYTHING|ML_BLOCKING|ML_RAILING);
8238 						lines[line].flags |= ML_BLOCK_PLAYERS;
8239 						break;
8240 					}
8241 				}
8242 
8243 				sp -= 2;
8244 			}
8245 			break;
8246 
8247 		case PCD_SETLINEMONSTERBLOCKING:
8248 			{
8249 				int line;
8250 
8251 				FLineIdIterator itr(STACK(2));
8252 				while ((line = itr.Next()) >= 0)
8253 				{
8254 					if (STACK(1))
8255 						lines[line].flags |= ML_BLOCKMONSTERS;
8256 					else
8257 						lines[line].flags &= ~ML_BLOCKMONSTERS;
8258 				}
8259 
8260 				sp -= 2;
8261 			}
8262 			break;
8263 
8264 		case PCD_SETLINESPECIAL:
8265 			{
8266 				int linenum = -1;
8267 				int specnum = STACK(6);
8268 				int arg0 = STACK(5);
8269 
8270 				// Convert named ACS "specials" into real specials.
8271 				if (specnum >= -ACSF_ACS_NamedExecuteAlways && specnum <= -ACSF_ACS_NamedExecute)
8272 				{
8273 					specnum = NamedACSToNormalACS[-specnum - ACSF_ACS_NamedExecute];
8274 					arg0 = -FName(FBehavior::StaticLookupString(arg0));
8275 				}
8276 
8277 				FLineIdIterator itr(STACK(7));
8278 				while ((linenum = itr.Next()) >= 0)
8279 				{
8280 					line_t *line = &lines[linenum];
8281 					line->special = specnum;
8282 					line->args[0] = arg0;
8283 					line->args[1] = STACK(4);
8284 					line->args[2] = STACK(3);
8285 					line->args[3] = STACK(2);
8286 					line->args[4] = STACK(1);
8287 					DPrintf("Set special on line %d (id %d) to %d(%d,%d,%d,%d,%d)\n",
8288 						linenum, STACK(7), specnum, arg0, STACK(4), STACK(3), STACK(2), STACK(1));
8289 				}
8290 				sp -= 7;
8291 			}
8292 			break;
8293 
8294 		case PCD_SETTHINGSPECIAL:
8295 			{
8296 				int specnum = STACK(6);
8297 				int arg0 = STACK(5);
8298 
8299 				// Convert named ACS "specials" into real specials.
8300 				if (specnum >= -ACSF_ACS_NamedExecuteAlways && specnum <= -ACSF_ACS_NamedExecute)
8301 				{
8302 					specnum = NamedACSToNormalACS[-specnum - ACSF_ACS_NamedExecute];
8303 					arg0 = -FName(FBehavior::StaticLookupString(arg0));
8304 				}
8305 
8306 				if (STACK(7) != 0)
8307 				{
8308 					FActorIterator iterator (STACK(7));
8309 					AActor *actor;
8310 
8311 					while ( (actor = iterator.Next ()) )
8312 					{
8313 						actor->special = specnum;
8314 						actor->args[0] = arg0;
8315 						actor->args[1] = STACK(4);
8316 						actor->args[2] = STACK(3);
8317 						actor->args[3] = STACK(2);
8318 						actor->args[4] = STACK(1);
8319 					}
8320 				}
8321 				else if (activator != NULL)
8322 				{
8323 					activator->special = specnum;
8324 					activator->args[0] = arg0;
8325 					activator->args[1] = STACK(4);
8326 					activator->args[2] = STACK(3);
8327 					activator->args[3] = STACK(2);
8328 					activator->args[4] = STACK(1);
8329 				}
8330 				sp -= 7;
8331 			}
8332 			break;
8333 
8334 		case PCD_THINGSOUND:
8335 			lookup = FBehavior::StaticLookupString (STACK(2));
8336 			if (lookup != NULL)
8337 			{
8338 				FActorIterator iterator (STACK(3));
8339 				AActor *spot;
8340 
8341 				while ( (spot = iterator.Next ()) )
8342 				{
8343 					S_Sound (spot, CHAN_AUTO,
8344 							 lookup,
8345 							 (float)(STACK(1))/127.f, ATTN_NORM);
8346 				}
8347 			}
8348 			sp -= 3;
8349 			break;
8350 
8351 		case PCD_FIXEDMUL:
8352 			STACK(2) = FixedMul (STACK(2), STACK(1));
8353 			sp--;
8354 			break;
8355 
8356 		case PCD_FIXEDDIV:
8357 			STACK(2) = FixedDiv (STACK(2), STACK(1));
8358 			sp--;
8359 			break;
8360 
8361 		case PCD_SETGRAVITY:
8362 			level.gravity = (float)STACK(1) / 65536.f;
8363 			sp--;
8364 			break;
8365 
8366 		case PCD_SETGRAVITYDIRECT:
8367 			level.gravity = (float)uallong(pc[0]) / 65536.f;
8368 			pc++;
8369 			break;
8370 
8371 		case PCD_SETAIRCONTROL:
8372 			level.aircontrol = STACK(1);
8373 			sp--;
8374 			G_AirControlChanged ();
8375 			break;
8376 
8377 		case PCD_SETAIRCONTROLDIRECT:
8378 			level.aircontrol = uallong(pc[0]);
8379 			pc++;
8380 			G_AirControlChanged ();
8381 			break;
8382 
8383 		case PCD_SPAWN:
8384 			STACK(6) = DoSpawn (STACK(6), STACK(5), STACK(4), STACK(3), STACK(2), STACK(1), false);
8385 			sp -= 5;
8386 			break;
8387 
8388 		case PCD_SPAWNDIRECT:
8389 			PushToStack (DoSpawn (TAGSTR(uallong(pc[0])), uallong(pc[1]), uallong(pc[2]), uallong(pc[3]), uallong(pc[4]), uallong(pc[5]), false));
8390 			pc += 6;
8391 			break;
8392 
8393 		case PCD_SPAWNSPOT:
8394 			STACK(4) = DoSpawnSpot (STACK(4), STACK(3), STACK(2), STACK(1), false);
8395 			sp -= 3;
8396 			break;
8397 
8398 		case PCD_SPAWNSPOTDIRECT:
8399 			PushToStack (DoSpawnSpot (TAGSTR(uallong(pc[0])), uallong(pc[1]), uallong(pc[2]), uallong(pc[3]), false));
8400 			pc += 4;
8401 			break;
8402 
8403 		case PCD_SPAWNSPOTFACING:
8404 			STACK(3) = DoSpawnSpotFacing (STACK(3), STACK(2), STACK(1), false);
8405 			sp -= 2;
8406 			break;
8407 
8408 		case PCD_CLEARINVENTORY:
8409 			ClearInventory (activator);
8410 			break;
8411 
8412 		case PCD_CLEARACTORINVENTORY:
8413 			if (STACK(1) == 0)
8414 			{
8415 				ClearInventory(NULL);
8416 			}
8417 			else
8418 			{
8419 				FActorIterator it(STACK(1));
8420 				AActor *actor;
8421 				for (actor = it.Next(); actor != NULL; actor = it.Next())
8422 				{
8423 					ClearInventory(actor);
8424 				}
8425 			}
8426 			sp--;
8427 			break;
8428 
8429 		case PCD_GIVEINVENTORY:
8430 			GiveInventory (activator, FBehavior::StaticLookupString (STACK(2)), STACK(1));
8431 			sp -= 2;
8432 			break;
8433 
8434 		case PCD_GIVEACTORINVENTORY:
8435 			{
8436 				const char *type = FBehavior::StaticLookupString(STACK(2));
8437 				if (STACK(3) == 0)
8438 				{
8439 					GiveInventory(NULL, FBehavior::StaticLookupString(STACK(2)), STACK(1));
8440 				}
8441 				else
8442 				{
8443 					FActorIterator it(STACK(3));
8444 					AActor *actor;
8445 					for (actor = it.Next(); actor != NULL; actor = it.Next())
8446 					{
8447 						GiveInventory(actor, type, STACK(1));
8448 					}
8449 				}
8450 				sp -= 3;
8451 			}
8452 			break;
8453 
8454 		case PCD_GIVEINVENTORYDIRECT:
8455 			GiveInventory (activator, FBehavior::StaticLookupString (TAGSTR(uallong(pc[0]))), uallong(pc[1]));
8456 			pc += 2;
8457 			break;
8458 
8459 		case PCD_TAKEINVENTORY:
8460 			TakeInventory (activator, FBehavior::StaticLookupString (STACK(2)), STACK(1));
8461 			sp -= 2;
8462 			break;
8463 
8464 		case PCD_TAKEACTORINVENTORY:
8465 			{
8466 				const char *type = FBehavior::StaticLookupString(STACK(2));
8467 				if (STACK(3) == 0)
8468 				{
8469 					TakeInventory(NULL, type, STACK(1));
8470 				}
8471 				else
8472 				{
8473 					FActorIterator it(STACK(3));
8474 					AActor *actor;
8475 					for (actor = it.Next(); actor != NULL; actor = it.Next())
8476 					{
8477 						TakeInventory(actor, type, STACK(1));
8478 					}
8479 				}
8480 				sp -= 3;
8481 			}
8482 			break;
8483 
8484 		case PCD_TAKEINVENTORYDIRECT:
8485 			TakeInventory (activator, FBehavior::StaticLookupString (TAGSTR(uallong(pc[0]))), uallong(pc[1]));
8486 			pc += 2;
8487 			break;
8488 
8489 		case PCD_CHECKINVENTORY:
8490 			STACK(1) = CheckInventory (activator, FBehavior::StaticLookupString (STACK(1)), false);
8491 			break;
8492 
8493 		case PCD_CHECKACTORINVENTORY:
8494 			STACK(2) = CheckInventory (SingleActorFromTID(STACK(2), NULL),
8495 										FBehavior::StaticLookupString (STACK(1)), false);
8496 			sp--;
8497 			break;
8498 
8499 		case PCD_CHECKINVENTORYDIRECT:
8500 			PushToStack (CheckInventory (activator, FBehavior::StaticLookupString (TAGSTR(uallong(pc[0]))), false));
8501 			pc += 1;
8502 			break;
8503 
8504 		case PCD_USEINVENTORY:
8505 			STACK(1) = UseInventory (activator, FBehavior::StaticLookupString (STACK(1)));
8506 			break;
8507 
8508 		case PCD_USEACTORINVENTORY:
8509 			{
8510 				int ret = 0;
8511 				const char *type = FBehavior::StaticLookupString(STACK(1));
8512 				if (STACK(2) == 0)
8513 				{
8514 					ret = UseInventory(NULL, type);
8515 				}
8516 				else
8517 				{
8518 					FActorIterator it(STACK(2));
8519 					AActor *actor;
8520 					for (actor = it.Next(); actor != NULL; actor = it.Next())
8521 					{
8522 						ret += UseInventory(actor, type);
8523 					}
8524 				}
8525 				STACK(2) = ret;
8526 				sp--;
8527 			}
8528 			break;
8529 
8530 		case PCD_GETSIGILPIECES:
8531 			{
8532 				ASigil *sigil;
8533 
8534 				if (activator == NULL || (sigil = activator->FindInventory<ASigil>()) == NULL)
8535 				{
8536 					PushToStack (0);
8537 				}
8538 				else
8539 				{
8540 					PushToStack (sigil->NumPieces);
8541 				}
8542 			}
8543 			break;
8544 
8545 		case PCD_GETAMMOCAPACITY:
8546 			if (activator != NULL)
8547 			{
8548 				const PClass *type = PClass::FindClass (FBehavior::StaticLookupString (STACK(1)));
8549 				AInventory *item;
8550 
8551 				if (type != NULL && type->ParentClass == RUNTIME_CLASS(AAmmo))
8552 				{
8553 					item = activator->FindInventory (type);
8554 					if (item != NULL)
8555 					{
8556 						STACK(1) = item->MaxAmount;
8557 					}
8558 					else
8559 					{
8560 						STACK(1) = ((AInventory *)GetDefaultByType (type))->MaxAmount;
8561 					}
8562 				}
8563 				else
8564 				{
8565 					STACK(1) = 0;
8566 				}
8567 			}
8568 			else
8569 			{
8570 				STACK(1) = 0;
8571 			}
8572 			break;
8573 
8574 		case PCD_SETAMMOCAPACITY:
8575 			if (activator != NULL)
8576 			{
8577 				const PClass *type = PClass::FindClass (FBehavior::StaticLookupString (STACK(2)));
8578 				AInventory *item;
8579 
8580 				if (type != NULL && type->ParentClass == RUNTIME_CLASS(AAmmo))
8581 				{
8582 					item = activator->FindInventory (type);
8583 					if (item != NULL)
8584 					{
8585 						item->MaxAmount = STACK(1);
8586 					}
8587 					else
8588 					{
8589 						item = activator->GiveInventoryType (type);
8590 						item->MaxAmount = STACK(1);
8591 						item->Amount = 0;
8592 					}
8593 				}
8594 			}
8595 			sp -= 2;
8596 			break;
8597 
8598 		case PCD_SETMUSIC:
8599 			S_ChangeMusic (FBehavior::StaticLookupString (STACK(3)), STACK(2));
8600 			sp -= 3;
8601 			break;
8602 
8603 		case PCD_SETMUSICDIRECT:
8604 			S_ChangeMusic (FBehavior::StaticLookupString (TAGSTR(uallong(pc[0]))), uallong(pc[1]));
8605 			pc += 3;
8606 			break;
8607 
8608 		case PCD_LOCALSETMUSIC:
8609 			if (activator == players[consoleplayer].mo)
8610 			{
8611 				S_ChangeMusic (FBehavior::StaticLookupString (STACK(3)), STACK(2));
8612 			}
8613 			sp -= 3;
8614 			break;
8615 
8616 		case PCD_LOCALSETMUSICDIRECT:
8617 			if (activator == players[consoleplayer].mo)
8618 			{
8619 				S_ChangeMusic (FBehavior::StaticLookupString (TAGSTR(uallong(pc[0]))), uallong(pc[1]));
8620 			}
8621 			pc += 3;
8622 			break;
8623 
8624 		case PCD_FADETO:
8625 			DoFadeTo (STACK(5), STACK(4), STACK(3), STACK(2), STACK(1));
8626 			sp -= 5;
8627 			break;
8628 
8629 		case PCD_FADERANGE:
8630 			DoFadeRange (STACK(9), STACK(8), STACK(7), STACK(6),
8631 						 STACK(5), STACK(4), STACK(3), STACK(2), STACK(1));
8632 			sp -= 9;
8633 			break;
8634 
8635 		case PCD_CANCELFADE:
8636 			{
8637 				TThinkerIterator<DFlashFader> iterator;
8638 				DFlashFader *fader;
8639 
8640 				while ( (fader = iterator.Next()) )
8641 				{
8642 					if (activator == NULL || fader->WhoFor() == activator)
8643 					{
8644 						fader->Cancel ();
8645 					}
8646 				}
8647 			}
8648 			break;
8649 
8650 		case PCD_PLAYMOVIE:
8651 			STACK(1) = I_PlayMovie (FBehavior::StaticLookupString (STACK(1)));
8652 			break;
8653 
8654 		case PCD_SETACTORPOSITION:
8655 			{
8656 				bool result = false;
8657 				AActor *actor = SingleActorFromTID (STACK(5), activator);
8658 				if (actor != NULL)
8659 					result = P_MoveThing(actor, STACK(4), STACK(3), STACK(2), !!STACK(1));
8660 				sp -= 4;
8661 				STACK(1) = result;
8662 			}
8663 			break;
8664 
8665 		case PCD_GETACTORX:
8666 		case PCD_GETACTORY:
8667 		case PCD_GETACTORZ:
8668 			{
8669 				AActor *actor = SingleActorFromTID(STACK(1), activator);
8670 				if (actor == NULL)
8671 				{
8672 					STACK(1) = 0;
8673 				}
8674 				else if (pcd == PCD_GETACTORZ)
8675 				{
8676 					STACK(1) = actor->Z() + actor->GetBobOffset();
8677 				}
8678 				else
8679 				{
8680 					STACK(1) = pcd == PCD_GETACTORX ? actor->X() : pcd == PCD_GETACTORY ? actor->Y() : actor->Z();
8681 				}
8682 			}
8683 			break;
8684 
8685 		case PCD_GETACTORFLOORZ:
8686 			{
8687 				AActor *actor = SingleActorFromTID(STACK(1), activator);
8688 				STACK(1) = actor == NULL ? 0 : actor->floorz;
8689 			}
8690 			break;
8691 
8692 		case PCD_GETACTORCEILINGZ:
8693 			{
8694 				AActor *actor = SingleActorFromTID(STACK(1), activator);
8695 				STACK(1) = actor == NULL ? 0 : actor->ceilingz;
8696 			}
8697 			break;
8698 
8699 		case PCD_GETACTORANGLE:
8700 			{
8701 				AActor *actor = SingleActorFromTID(STACK(1), activator);
8702 				STACK(1) = actor == NULL ? 0 : actor->angle >> 16;
8703 			}
8704 			break;
8705 
8706 		case PCD_GETACTORPITCH:
8707 			{
8708 				AActor *actor = SingleActorFromTID(STACK(1), activator);
8709 				STACK(1) = actor == NULL ? 0 : actor->pitch >> 16;
8710 			}
8711 			break;
8712 
8713 		case PCD_GETLINEROWOFFSET:
8714 			if (activationline != NULL)
8715 			{
8716 				PushToStack (activationline->sidedef[0]->GetTextureYOffset(side_t::mid) >> FRACBITS);
8717 			}
8718 			else
8719 			{
8720 				PushToStack (0);
8721 			}
8722 			break;
8723 
8724 		case PCD_GETSECTORFLOORZ:
8725 		case PCD_GETSECTORCEILINGZ:
8726 			// Arguments are (tag, x, y). If you don't use slopes, then (x, y) don't
8727 			// really matter and can be left as (0, 0) if you like.
8728 			// [Dusk] If tag = 0, then this returns the z height at whatever sector
8729 			// is in x, y.
8730 			{
8731 				int tag = STACK(3);
8732 				int secnum;
8733 				fixed_t x = STACK(2) << FRACBITS;
8734 				fixed_t y = STACK(1) << FRACBITS;
8735 				fixed_t z = 0;
8736 
8737 				if (tag != 0)
8738 					secnum = P_FindFirstSectorFromTag (tag);
8739 				else
8740 					secnum = int(P_PointInSector (x, y) - sectors);
8741 
8742 				if (secnum >= 0)
8743 				{
8744 					if (pcd == PCD_GETSECTORFLOORZ)
8745 					{
8746 						z = sectors[secnum].floorplane.ZatPoint (x, y);
8747 					}
8748 					else
8749 					{
8750 						z = sectors[secnum].ceilingplane.ZatPoint (x, y);
8751 					}
8752 				}
8753 				sp -= 2;
8754 				STACK(1) = z;
8755 			}
8756 			break;
8757 
8758 		case PCD_GETSECTORLIGHTLEVEL:
8759 			{
8760 				int secnum = P_FindFirstSectorFromTag (STACK(1));
8761 				int z = -1;
8762 
8763 				if (secnum >= 0)
8764 				{
8765 					z = sectors[secnum].lightlevel;
8766 				}
8767 				STACK(1) = z;
8768 			}
8769 			break;
8770 
8771 		case PCD_SETFLOORTRIGGER:
8772 			new DPlaneWatcher (activator, activationline, backSide, false, STACK(8),
8773 				STACK(7), STACK(6), STACK(5), STACK(4), STACK(3), STACK(2), STACK(1));
8774 			sp -= 8;
8775 			break;
8776 
8777 		case PCD_SETCEILINGTRIGGER:
8778 			new DPlaneWatcher (activator, activationline, backSide, true, STACK(8),
8779 				STACK(7), STACK(6), STACK(5), STACK(4), STACK(3), STACK(2), STACK(1));
8780 			sp -= 8;
8781 			break;
8782 
8783 		case PCD_STARTTRANSLATION:
8784 			{
8785 				int i = STACK(1);
8786 				sp--;
8787 				if (i >= 1 && i <= MAX_ACS_TRANSLATIONS)
8788 				{
8789 					translation = translationtables[TRANSLATION_LevelScripted].GetVal(i - 1);
8790 					if (translation == NULL)
8791 					{
8792 						translation = new FRemapTable;
8793 						translationtables[TRANSLATION_LevelScripted].SetVal(i - 1, translation);
8794 					}
8795 					translation->MakeIdentity();
8796 				}
8797 			}
8798 			break;
8799 
8800 		case PCD_TRANSLATIONRANGE1:
8801 			{ // translation using palette shifting
8802 				int start = STACK(4);
8803 				int end = STACK(3);
8804 				int pal1 = STACK(2);
8805 				int pal2 = STACK(1);
8806 				sp -= 4;
8807 
8808 				if (translation != NULL)
8809 					translation->AddIndexRange(start, end, pal1, pal2);
8810 			}
8811 			break;
8812 
8813 		case PCD_TRANSLATIONRANGE2:
8814 			{ // translation using RGB values
8815 			  // (would HSV be a good idea too?)
8816 				int start = STACK(8);
8817 				int end = STACK(7);
8818 				int r1 = STACK(6);
8819 				int g1 = STACK(5);
8820 				int b1 = STACK(4);
8821 				int r2 = STACK(3);
8822 				int g2 = STACK(2);
8823 				int b2 = STACK(1);
8824 				sp -= 8;
8825 
8826 				if (translation != NULL)
8827 					translation->AddColorRange(start, end, r1, g1, b1, r2, g2, b2);
8828 			}
8829 			break;
8830 
8831 		case PCD_TRANSLATIONRANGE3:
8832 			{ // translation using desaturation
8833 				int start = STACK(8);
8834 				int end = STACK(7);
8835 				fixed_t r1 = STACK(6);
8836 				fixed_t g1 = STACK(5);
8837 				fixed_t b1 = STACK(4);
8838 				fixed_t r2 = STACK(3);
8839 				fixed_t g2 = STACK(2);
8840 				fixed_t b2 = STACK(1);
8841 				sp -= 8;
8842 
8843 				if (translation != NULL)
8844 					translation->AddDesaturation(start, end,
8845 						FIXED2DBL(r1), FIXED2DBL(g1), FIXED2DBL(b1),
8846 						FIXED2DBL(r2), FIXED2DBL(g2), FIXED2DBL(b2));
8847 			}
8848 			break;
8849 
8850 		case PCD_ENDTRANSLATION:
8851 			// This might be useful for hardware rendering, but
8852 			// for software it is superfluous.
8853 			translation->UpdateNative();
8854 			translation = NULL;
8855 			break;
8856 
8857 		case PCD_SIN:
8858 			STACK(1) = finesine[angle_t(STACK(1)<<16)>>ANGLETOFINESHIFT];
8859 			break;
8860 
8861 		case PCD_COS:
8862 			STACK(1) = finecosine[angle_t(STACK(1)<<16)>>ANGLETOFINESHIFT];
8863 			break;
8864 
8865 		case PCD_VECTORANGLE:
8866 			STACK(2) = R_PointToAngle2 (0, 0, STACK(2), STACK(1)) >> 16;
8867 			sp--;
8868 			break;
8869 
8870         case PCD_CHECKWEAPON:
8871             if (activator == NULL || activator->player == NULL || // Non-players do not have weapons
8872                 activator->player->ReadyWeapon == NULL)
8873             {
8874                 STACK(1) = 0;
8875             }
8876             else
8877             {
8878 				STACK(1) = activator->player->ReadyWeapon->GetClass()->TypeName == FName(FBehavior::StaticLookupString (STACK(1)), true);
8879             }
8880             break;
8881 
8882 		case PCD_SETWEAPON:
8883 			if (activator == NULL || activator->player == NULL)
8884 			{
8885 				STACK(1) = 0;
8886 			}
8887 			else
8888 			{
8889 				AInventory *item = activator->FindInventory (PClass::FindClass (
8890 					FBehavior::StaticLookupString (STACK(1))));
8891 
8892 				if (item == NULL || !item->IsKindOf (RUNTIME_CLASS(AWeapon)))
8893 				{
8894 					STACK(1) = 0;
8895 				}
8896 				else if (activator->player->ReadyWeapon == item)
8897 				{
8898 					// The weapon is already selected, so setweapon succeeds by default,
8899 					// but make sure the player isn't switching away from it.
8900 					activator->player->PendingWeapon = WP_NOCHANGE;
8901 					STACK(1) = 1;
8902 				}
8903 				else
8904 				{
8905 					AWeapon *weap = static_cast<AWeapon *> (item);
8906 
8907 					if (weap->CheckAmmo (AWeapon::EitherFire, false))
8908 					{
8909 						// There's enough ammo, so switch to it.
8910 						STACK(1) = 1;
8911 						activator->player->PendingWeapon = weap;
8912 					}
8913 					else
8914 					{
8915 						STACK(1) = 0;
8916 					}
8917 				}
8918 			}
8919 			break;
8920 
8921 		case PCD_SETMARINEWEAPON:
8922 			if (STACK(2) != 0)
8923 			{
8924 				AScriptedMarine *marine;
8925 				TActorIterator<AScriptedMarine> iterator (STACK(2));
8926 
8927 				while ((marine = iterator.Next()) != NULL)
8928 				{
8929 					marine->SetWeapon ((AScriptedMarine::EMarineWeapon)STACK(1));
8930 				}
8931 			}
8932 			else
8933 			{
8934 				if (activator != NULL && activator->IsKindOf (RUNTIME_CLASS(AScriptedMarine)))
8935 				{
8936 					barrier_cast<AScriptedMarine *>(activator)->SetWeapon (
8937 						(AScriptedMarine::EMarineWeapon)STACK(1));
8938 				}
8939 			}
8940 			sp -= 2;
8941 			break;
8942 
8943 		case PCD_SETMARINESPRITE:
8944 			{
8945 				const PClass *type = PClass::FindClass (FBehavior::StaticLookupString (STACK(1)));
8946 
8947 				if (type != NULL)
8948 				{
8949 					if (STACK(2) != 0)
8950 					{
8951 						AScriptedMarine *marine;
8952 						TActorIterator<AScriptedMarine> iterator (STACK(2));
8953 
8954 						while ((marine = iterator.Next()) != NULL)
8955 						{
8956 							marine->SetSprite (type);
8957 						}
8958 					}
8959 					else
8960 					{
8961 						if (activator != NULL && activator->IsKindOf (RUNTIME_CLASS(AScriptedMarine)))
8962 						{
8963 							barrier_cast<AScriptedMarine *>(activator)->SetSprite (type);
8964 						}
8965 					}
8966 				}
8967 				else
8968 				{
8969 					Printf ("Unknown actor type: %s\n", FBehavior::StaticLookupString (STACK(1)));
8970 				}
8971 			}
8972 			sp -= 2;
8973 			break;
8974 
8975 		case PCD_SETACTORPROPERTY:
8976 			SetActorProperty (STACK(3), STACK(2), STACK(1));
8977 			sp -= 3;
8978 			break;
8979 
8980 		case PCD_GETACTORPROPERTY:
8981 			STACK(2) = GetActorProperty (STACK(2), STACK(1));
8982 			sp -= 1;
8983 			break;
8984 
8985 		case PCD_GETPLAYERINPUT:
8986 			STACK(2) = GetPlayerInput (STACK(2), STACK(1));
8987 			sp -= 1;
8988 			break;
8989 
8990 		case PCD_PLAYERNUMBER:
8991 			if (activator == NULL || activator->player == NULL)
8992 			{
8993 				PushToStack (-1);
8994 			}
8995 			else
8996 			{
8997 				PushToStack (int(activator->player - players));
8998 			}
8999 			break;
9000 
9001 		case PCD_PLAYERINGAME:
9002 			if (STACK(1) < 0 || STACK(1) >= MAXPLAYERS)
9003 			{
9004 				STACK(1) = false;
9005 			}
9006 			else
9007 			{
9008 				STACK(1) = playeringame[STACK(1)];
9009 			}
9010 			break;
9011 
9012 		case PCD_PLAYERISBOT:
9013 			if (STACK(1) < 0 || STACK(1) >= MAXPLAYERS || !playeringame[STACK(1)])
9014 			{
9015 				STACK(1) = false;
9016 			}
9017 			else
9018 			{
9019 				STACK(1) = (players[STACK(1)].Bot != NULL);
9020 			}
9021 			break;
9022 
9023 		case PCD_ACTIVATORTID:
9024 			if (activator == NULL)
9025 			{
9026 				PushToStack (0);
9027 			}
9028 			else
9029 			{
9030 				PushToStack (activator->tid);
9031 			}
9032 			break;
9033 
9034 		case PCD_GETSCREENWIDTH:
9035 			PushToStack (SCREENWIDTH);
9036 			break;
9037 
9038 		case PCD_GETSCREENHEIGHT:
9039 			PushToStack (SCREENHEIGHT);
9040 			break;
9041 
9042 		case PCD_THING_PROJECTILE2:
9043 			// Like Thing_Projectile(Gravity) specials, but you can give the
9044 			// projectile a TID.
9045 			// Thing_Projectile2 (tid, type, angle, speed, vspeed, gravity, newtid);
9046 			P_Thing_Projectile (STACK(7), activator, STACK(6), NULL, ((angle_t)(STACK(5)<<24)),
9047 				STACK(4)<<(FRACBITS-3), STACK(3)<<(FRACBITS-3), 0, NULL, STACK(2), STACK(1), false);
9048 			sp -= 7;
9049 			break;
9050 
9051 		case PCD_SPAWNPROJECTILE:
9052 			// Same, but takes an actor name instead of a spawn ID.
9053 			P_Thing_Projectile (STACK(7), activator, 0, FBehavior::StaticLookupString (STACK(6)), ((angle_t)(STACK(5)<<24)),
9054 				STACK(4)<<(FRACBITS-3), STACK(3)<<(FRACBITS-3), 0, NULL, STACK(2), STACK(1), false);
9055 			sp -= 7;
9056 			break;
9057 
9058 		case PCD_STRLEN:
9059 			{
9060 				const char *str = FBehavior::StaticLookupString(STACK(1));
9061 				if (str != NULL)
9062 				{
9063 					STACK(1) = SDWORD(strlen(str));
9064 					break;
9065 				}
9066 
9067 				static bool StrlenInvalidPrintedAlready = false;
9068 				if (!StrlenInvalidPrintedAlready)
9069 				{
9070 					Printf(PRINT_BOLD, "Warning: ACS function strlen called with invalid string argument.\n");
9071 					StrlenInvalidPrintedAlready = true;
9072 				}
9073 				STACK(1) = 0;
9074 			}
9075 			break;
9076 
9077 		case PCD_GETCVAR:
9078 			STACK(1) = GetCVar(activator, FBehavior::StaticLookupString(STACK(1)), false);
9079 			break;
9080 
9081 		case PCD_SETHUDSIZE:
9082 			hudwidth = abs (STACK(3));
9083 			hudheight = abs (STACK(2));
9084 			if (STACK(1) != 0)
9085 			{ // Negative height means to cover the status bar
9086 				hudheight = -hudheight;
9087 			}
9088 			sp -= 3;
9089 			break;
9090 
9091 		case PCD_GETLEVELINFO:
9092 			switch (STACK(1))
9093 			{
9094 			case LEVELINFO_PAR_TIME:		STACK(1) = level.partime;			break;
9095 			case LEVELINFO_SUCK_TIME:		STACK(1) = level.sucktime;			break;
9096 			case LEVELINFO_CLUSTERNUM:		STACK(1) = level.cluster;			break;
9097 			case LEVELINFO_LEVELNUM:		STACK(1) = level.levelnum;			break;
9098 			case LEVELINFO_TOTAL_SECRETS:	STACK(1) = level.total_secrets;		break;
9099 			case LEVELINFO_FOUND_SECRETS:	STACK(1) = level.found_secrets;		break;
9100 			case LEVELINFO_TOTAL_ITEMS:		STACK(1) = level.total_items;		break;
9101 			case LEVELINFO_FOUND_ITEMS:		STACK(1) = level.found_items;		break;
9102 			case LEVELINFO_TOTAL_MONSTERS:	STACK(1) = level.total_monsters;	break;
9103 			case LEVELINFO_KILLED_MONSTERS:	STACK(1) = level.killed_monsters;	break;
9104 			default:						STACK(1) = 0;						break;
9105 			}
9106 			break;
9107 
9108 		case PCD_CHANGESKY:
9109 			{
9110 				const char *sky1name, *sky2name;
9111 
9112 				sky1name = FBehavior::StaticLookupString (STACK(2));
9113 				sky2name = FBehavior::StaticLookupString (STACK(1));
9114 				if (sky1name[0] != 0)
9115 				{
9116 					sky1texture = level.skytexture1 = TexMan.GetTexture (sky1name, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_ReturnFirst);
9117 				}
9118 				if (sky2name[0] != 0)
9119 				{
9120 					sky2texture = level.skytexture2 = TexMan.GetTexture (sky2name, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_ReturnFirst);
9121 				}
9122 				R_InitSkyMap ();
9123 				sp -= 2;
9124 			}
9125 			break;
9126 
9127 		case PCD_SETCAMERATOTEXTURE:
9128 			{
9129 				const char *picname = FBehavior::StaticLookupString (STACK(2));
9130 				AActor *camera;
9131 
9132 				if (STACK(3) == 0)
9133 				{
9134 					camera = activator;
9135 				}
9136 				else
9137 				{
9138 					FActorIterator it (STACK(3));
9139 					camera = it.Next ();
9140 				}
9141 
9142 				if (camera != NULL)
9143 				{
9144 					FTextureID picnum = TexMan.CheckForTexture (picname, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable);
9145 					if (!picnum.Exists())
9146 					{
9147 						Printf ("SetCameraToTexture: %s is not a texture\n", picname);
9148 					}
9149 					else
9150 					{
9151 						FCanvasTextureInfo::Add (camera, picnum, STACK(1));
9152 					}
9153 				}
9154 				sp -= 3;
9155 			}
9156 			break;
9157 
9158 		case PCD_SETACTORANGLE:		// [GRB]
9159 			SetActorAngle(activator, STACK(2), STACK(1), false);
9160 			sp -= 2;
9161 			break;
9162 
9163 		case PCD_SETACTORPITCH:
9164 			SetActorPitch(activator, STACK(2), STACK(1), false);
9165 			sp -= 2;
9166 			break;
9167 
9168 		case PCD_SETACTORSTATE:
9169 			{
9170 				const char *statename = FBehavior::StaticLookupString (STACK(2));
9171 				FState *state;
9172 
9173 				if (STACK(3) == 0)
9174 				{
9175 					if (activator != NULL)
9176 					{
9177 						state = activator->GetClass()->ActorInfo->FindStateByString (statename, !!STACK(1));
9178 						if (state != NULL)
9179 						{
9180 							activator->SetState (state);
9181 							STACK(3) = 1;
9182 						}
9183 						else
9184 						{
9185 							STACK(3) = 0;
9186 						}
9187 					}
9188 				}
9189 				else
9190 				{
9191 					FActorIterator iterator (STACK(3));
9192 					AActor *actor;
9193 					int count = 0;
9194 
9195 					while ( (actor = iterator.Next ()) )
9196 					{
9197 						state = actor->GetClass()->ActorInfo->FindStateByString (statename, !!STACK(1));
9198 						if (state != NULL)
9199 						{
9200 							actor->SetState (state);
9201 							count++;
9202 						}
9203 					}
9204 					STACK(3) = count;
9205 				}
9206 				sp -= 2;
9207 			}
9208 			break;
9209 
9210 		case PCD_PLAYERCLASS:		// [GRB]
9211 			if (STACK(1) < 0 || STACK(1) >= MAXPLAYERS || !playeringame[STACK(1)])
9212 			{
9213 				STACK(1) = -1;
9214 			}
9215 			else
9216 			{
9217 				STACK(1) = players[STACK(1)].CurrentPlayerClass;
9218 			}
9219 			break;
9220 
9221 		case PCD_GETPLAYERINFO:		// [GRB]
9222 			if (STACK(2) < 0 || STACK(2) >= MAXPLAYERS || !playeringame[STACK(2)])
9223 			{
9224 				STACK(2) = -1;
9225 			}
9226 			else
9227 			{
9228 				player_t *pl = &players[STACK(2)];
9229 				userinfo_t *userinfo = &pl->userinfo;
9230 				switch (STACK(1))
9231 				{
9232 				case PLAYERINFO_TEAM:			STACK(2) = userinfo->GetTeam(); break;
9233 				case PLAYERINFO_AIMDIST:		STACK(2) = userinfo->GetAimDist(); break;
9234 				case PLAYERINFO_COLOR:			STACK(2) = userinfo->GetColor(); break;
9235 				case PLAYERINFO_GENDER:			STACK(2) = userinfo->GetGender(); break;
9236 				case PLAYERINFO_NEVERSWITCH:	STACK(2) = userinfo->GetNeverSwitch(); break;
9237 				case PLAYERINFO_MOVEBOB:		STACK(2) = userinfo->GetMoveBob(); break;
9238 				case PLAYERINFO_STILLBOB:		STACK(2) = userinfo->GetStillBob(); break;
9239 				case PLAYERINFO_PLAYERCLASS:	STACK(2) = userinfo->GetPlayerClassNum(); break;
9240 				case PLAYERINFO_DESIREDFOV:		STACK(2) = (int)pl->DesiredFOV; break;
9241 				case PLAYERINFO_FOV:			STACK(2) = (int)pl->FOV; break;
9242 				default:						STACK(2) = 0; break;
9243 				}
9244 			}
9245 			sp -= 1;
9246 			break;
9247 
9248 		case PCD_CHANGELEVEL:
9249 			{
9250 				G_ChangeLevel(FBehavior::StaticLookupString(STACK(4)), STACK(3), STACK(2), STACK(1));
9251 				sp -= 4;
9252 			}
9253 			break;
9254 
9255 		case PCD_SECTORDAMAGE:
9256 			{
9257 				int tag = STACK(5);
9258 				int amount = STACK(4);
9259 				FName type = FBehavior::StaticLookupString(STACK(3));
9260 				FName protection = FName (FBehavior::StaticLookupString(STACK(2)), true);
9261 				const PClass *protectClass = PClass::FindClass (protection);
9262 				int flags = STACK(1);
9263 				sp -= 5;
9264 
9265 				P_SectorDamage(tag, amount, type, protectClass, flags);
9266 			}
9267 			break;
9268 
9269 		case PCD_THINGDAMAGE2:
9270 			STACK(3) = P_Thing_Damage (STACK(3), activator, STACK(2), FName(FBehavior::StaticLookupString(STACK(1))));
9271 			sp -= 2;
9272 			break;
9273 
9274 		case PCD_CHECKACTORCEILINGTEXTURE:
9275 			STACK(2) = DoCheckActorTexture(STACK(2), activator, STACK(1), false);
9276 			sp--;
9277 			break;
9278 
9279 		case PCD_CHECKACTORFLOORTEXTURE:
9280 			STACK(2) = DoCheckActorTexture(STACK(2), activator, STACK(1), true);
9281 			sp--;
9282 			break;
9283 
9284 		case PCD_GETACTORLIGHTLEVEL:
9285 		{
9286 			AActor *actor = SingleActorFromTID(STACK(1), activator);
9287 			if (actor != NULL)
9288 			{
9289 				STACK(1) = actor->Sector->lightlevel;
9290 			}
9291 			else STACK(1) = 0;
9292 			break;
9293 		}
9294 
9295 		case PCD_SETMUGSHOTSTATE:
9296 			StatusBar->SetMugShotState(FBehavior::StaticLookupString(STACK(1)));
9297 			sp--;
9298 			break;
9299 
9300 		case PCD_CHECKPLAYERCAMERA:
9301 			{
9302 				int playernum = STACK(1);
9303 
9304 				if (playernum < 0 || playernum >= MAXPLAYERS || !playeringame[playernum] || players[playernum].camera == NULL || players[playernum].camera->player != NULL)
9305 				{
9306 					STACK(1) = -1;
9307 				}
9308 				else
9309 				{
9310 					STACK(1) = players[playernum].camera->tid;
9311 				}
9312 			}
9313 			break;
9314 
9315 		case PCD_CLASSIFYACTOR:
9316 			STACK(1) = DoClassifyActor(STACK(1));
9317 			break;
9318 
9319 		case PCD_MORPHACTOR:
9320 			{
9321 				int tag = STACK(7);
9322 				FName playerclass_name = FBehavior::StaticLookupString(STACK(6));
9323 				const PClass *playerclass = PClass::FindClass (playerclass_name);
9324 				FName monsterclass_name = FBehavior::StaticLookupString(STACK(5));
9325 				const PClass *monsterclass = PClass::FindClass (monsterclass_name);
9326 				int duration = STACK(4);
9327 				int style = STACK(3);
9328 				FName morphflash_name = FBehavior::StaticLookupString(STACK(2));
9329 				const PClass *morphflash = PClass::FindClass (morphflash_name);
9330 				FName unmorphflash_name = FBehavior::StaticLookupString(STACK(1));
9331 				const PClass *unmorphflash = PClass::FindClass (unmorphflash_name);
9332 				int changes = 0;
9333 
9334 				if (tag == 0)
9335 				{
9336 					if (activator != NULL && activator->player)
9337 					{
9338 						changes += P_MorphPlayer(activator->player, activator->player, playerclass, duration, style, morphflash, unmorphflash);
9339 					}
9340 					else
9341 					{
9342 						changes += P_MorphMonster(activator, monsterclass, duration, style, morphflash, unmorphflash);
9343 					}
9344 				}
9345 				else
9346 				{
9347 					FActorIterator iterator (tag);
9348 					AActor *actor;
9349 
9350 					while ( (actor = iterator.Next ()) )
9351 					{
9352 						if (actor->player)
9353 						{
9354 							changes += P_MorphPlayer(activator == NULL ? NULL : activator->player,
9355 								actor->player, playerclass, duration, style, morphflash, unmorphflash);
9356 						}
9357 						else
9358 						{
9359 							changes += P_MorphMonster(actor, monsterclass, duration, style, morphflash, unmorphflash);
9360 						}
9361 					}
9362 				}
9363 
9364 				STACK(7) = changes;
9365 				sp -= 6;
9366 			}
9367 			break;
9368 
9369 		case PCD_UNMORPHACTOR:
9370 			{
9371 				int tag = STACK(2);
9372 				bool force = !!STACK(1);
9373 				int changes = 0;
9374 
9375 				if (tag == 0)
9376 				{
9377 					if (activator->player)
9378 					{
9379 						if (P_UndoPlayerMorph(activator->player, activator->player, force))
9380 						{
9381 							changes++;
9382 						}
9383 					}
9384 					else
9385 					{
9386 						if (activator->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster)))
9387 						{
9388 							AMorphedMonster *morphed_actor = barrier_cast<AMorphedMonster *>(activator);
9389 							if (P_UndoMonsterMorph(morphed_actor, force))
9390 							{
9391 								changes++;
9392 							}
9393 						}
9394 					}
9395 				}
9396 				else
9397 				{
9398 					FActorIterator iterator (tag);
9399 					AActor *actor;
9400 
9401 					while ( (actor = iterator.Next ()) )
9402 					{
9403 						if (actor->player)
9404 						{
9405 							if (P_UndoPlayerMorph(activator->player, actor->player, force))
9406 							{
9407 								changes++;
9408 							}
9409 						}
9410 						else
9411 						{
9412 							if (actor->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster)))
9413 							{
9414 								AMorphedMonster *morphed_actor = static_cast<AMorphedMonster *>(actor);
9415 								if (P_UndoMonsterMorph(morphed_actor, force))
9416 								{
9417 									changes++;
9418 								}
9419 							}
9420 						}
9421 					}
9422 				}
9423 
9424 				STACK(2) = changes;
9425 				sp -= 1;
9426 			}
9427 			break;
9428 
9429 		case PCD_SAVESTRING:
9430 			// Saves the string
9431 			{
9432 				const int str = GlobalACSStrings.AddString(work);
9433 				PushToStack(str);
9434 				STRINGBUILDER_FINISH(work);
9435 			}
9436 			break;
9437 
9438 		case PCD_STRCPYTOSCRIPTCHRANGE:
9439 		case PCD_STRCPYTOMAPCHRANGE:
9440 		case PCD_STRCPYTOWORLDCHRANGE:
9441 		case PCD_STRCPYTOGLOBALCHRANGE:
9442 			// source: stringid(2); stringoffset(1)
9443 			// destination: capacity (3); stringoffset(4); arrayid (5); offset(6)
9444 
9445 			{
9446 				int index = STACK(4);
9447 				int capacity = STACK(3);
9448 
9449 				if (index < 0 || STACK(1) < 0)
9450 				{
9451 					// no writable destination, or negative offset to source string
9452 					sp -= 5;
9453 					Stack[sp-1] = 0; // false
9454 					break;
9455 				}
9456 
9457 				index += STACK(6);
9458 
9459 				lookup = FBehavior::StaticLookupString (STACK(2));
9460 
9461 				if (!lookup) {
9462 					// no data, operation complete
9463 	STRCPYTORANGECOMPLETE:
9464 					sp -= 5;
9465 					Stack[sp-1] = 1; // true
9466 					break;
9467 				}
9468 
9469 				for (int i = 0; i < STACK(1); i++)
9470 				{
9471 					if (! (*(lookup++)))
9472 					{
9473 						// no data, operation complete
9474 						goto STRCPYTORANGECOMPLETE;
9475 					}
9476 				}
9477 
9478 				switch (pcd)
9479 				{
9480 				case PCD_STRCPYTOSCRIPTCHRANGE:
9481 					{
9482 						int a = STACK(5);
9483 
9484 						while (capacity-- > 0)
9485 						{
9486 							localarrays->Set(locals, a, index++, *lookup);
9487 							if (! (*(lookup++))) goto STRCPYTORANGECOMPLETE; // complete with terminating 0
9488 						}
9489 
9490 						Stack[sp-6] = !(*lookup); // true/success if only terminating 0 was not copied
9491 					}
9492 					break;
9493 				case PCD_STRCPYTOMAPCHRANGE:
9494 					{
9495 						int a = STACK(5);
9496 						if (a < NUM_MAPVARS && a > 0 &&
9497 							activeBehavior->MapVars[a])
9498 						{
9499 							Stack[sp-6] = activeBehavior->CopyStringToArray(*(activeBehavior->MapVars[a]), index, capacity, lookup);
9500 						}
9501 					}
9502 					break;
9503 				case PCD_STRCPYTOWORLDCHRANGE:
9504 					{
9505 						int a = STACK(5);
9506 
9507 						while (capacity-- > 0)
9508 						{
9509 							ACS_WorldArrays[a][index++] = *lookup;
9510 							if (! (*(lookup++))) goto STRCPYTORANGECOMPLETE; // complete with terminating 0
9511 						}
9512 
9513 						Stack[sp-6] = !(*lookup); // true/success if only terminating 0 was not copied
9514 					}
9515 					break;
9516 				case PCD_STRCPYTOGLOBALCHRANGE:
9517 					{
9518 						int a = STACK(5);
9519 
9520 						while (capacity-- > 0)
9521 						{
9522 							ACS_GlobalArrays[a][index++] = *lookup;
9523 							if (! (*(lookup++))) goto STRCPYTORANGECOMPLETE; // complete with terminating 0
9524 						}
9525 
9526 						Stack[sp-6] = !(*lookup); // true/success if only terminating 0 was not copied
9527 					}
9528 					break;
9529 				}
9530 				sp -= 5;
9531 			}
9532 			break;
9533 
9534 		case PCD_CONSOLECOMMAND:
9535 			Printf (TEXTCOLOR_RED GAMENAME " doesn't support execution of console commands from scripts\n");
9536 			sp -= 3;
9537 			break;
9538  		}
9539  	}
9540 
9541 	if (runaway != 0 && InModuleScriptNumber >= 0)
9542 	{
9543 		activeBehavior->GetScriptPtr(InModuleScriptNumber)->ProfileData.AddRun(runaway);
9544 	}
9545 
9546 	if (state == SCRIPT_DivideBy0)
9547 	{
9548 		Printf ("Divide by zero in %s\n", ScriptPresentation(script).GetChars());
9549 		state = SCRIPT_PleaseRemove;
9550 	}
9551 	else if (state == SCRIPT_ModulusBy0)
9552 	{
9553 		Printf ("Modulus by zero in %s\n", ScriptPresentation(script).GetChars());
9554 		state = SCRIPT_PleaseRemove;
9555 	}
9556 	if (state == SCRIPT_PleaseRemove)
9557 	{
9558 		Unlink ();
9559 		DLevelScript **running;
9560 		if ((running = controller->RunningScripts.CheckKey(script)) != NULL &&
9561 			*running == this)
9562 		{
9563 			controller->RunningScripts.Remove(script);
9564 		}
9565 	}
9566 	else
9567 	{
9568 		this->pc = pc;
9569 		assert (sp == 0);
9570 	}
9571 	return resultValue;
9572 }
9573 
9574 #undef PushtoStack
9575 
P_GetScriptGoing(AActor * who,line_t * where,int num,const ScriptPtr * code,FBehavior * module,const int * args,int argcount,int flags)9576 static DLevelScript *P_GetScriptGoing (AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module,
9577 	const int *args, int argcount, int flags)
9578 {
9579 	DACSThinker *controller = DACSThinker::ActiveThinker;
9580 	DLevelScript **running;
9581 
9582 	if (controller && !(flags & ACS_ALWAYS) && (running = controller->RunningScripts.CheckKey(num)) != NULL)
9583 	{
9584 		if ((*running)->GetState() == DLevelScript::SCRIPT_Suspended)
9585 		{
9586 			(*running)->SetState(DLevelScript::SCRIPT_Running);
9587 			return *running;
9588 		}
9589 		return NULL;
9590 	}
9591 
9592 	return new DLevelScript (who, where, num, code, module, args, argcount, flags);
9593 }
9594 
DLevelScript(AActor * who,line_t * where,int num,const ScriptPtr * code,FBehavior * module,const int * args,int argcount,int flags)9595 DLevelScript::DLevelScript (AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module,
9596 	const int *args, int argcount, int flags)
9597 	: activeBehavior (module)
9598 {
9599 	if (DACSThinker::ActiveThinker == NULL)
9600 		new DACSThinker;
9601 
9602 	script = num;
9603 	assert(code->VarCount >= code->ArgCount);
9604 	numlocalvars = code->VarCount;
9605 	localvars = new SDWORD[code->VarCount];
9606 	memset(localvars, 0, code->VarCount * sizeof(SDWORD));
9607 	for (int i = 0; i < MIN<int>(argcount, code->ArgCount); ++i)
9608 	{
9609 		localvars[i] = args[i];
9610 	}
9611 	pc = module->GetScriptAddress(code);
9612 	InModuleScriptNumber = module->GetScriptIndex(code);
9613 	activator = who;
9614 	activationline = where;
9615 	backSide = flags & ACS_BACKSIDE;
9616 	activefont = SmallFont;
9617 	hudwidth = hudheight = 0;
9618 	ClipRectLeft = ClipRectTop = ClipRectWidth = ClipRectHeight = WrapWidth = 0;
9619 	HandleAspect = true;
9620 	state = SCRIPT_Running;
9621 
9622 	// Hexen waited one second before executing any open scripts. I didn't realize
9623 	// this when I wrote my ACS implementation. Now that I know, it's still best to
9624 	// run them right away because there are several map properties that can't be
9625 	// set in an editor. If an open script sets them, it looks dumb if a second
9626 	// goes by while they're in their default state.
9627 
9628 	if (!(flags & ACS_ALWAYS))
9629 		DACSThinker::ActiveThinker->RunningScripts[num] = this;
9630 
9631 	Link();
9632 
9633 	if (level.flags2 & LEVEL2_HEXENHACK)
9634 	{
9635 		PutLast();
9636 	}
9637 
9638 	DPrintf("%s started.\n", ScriptPresentation(num).GetChars());
9639 }
9640 
SetScriptState(int script,DLevelScript::EScriptState state)9641 static void SetScriptState (int script, DLevelScript::EScriptState state)
9642 {
9643 	DACSThinker *controller = DACSThinker::ActiveThinker;
9644 	DLevelScript **running;
9645 
9646 	if (controller != NULL && (running = controller->RunningScripts.CheckKey(script)) != NULL)
9647 	{
9648 		(*running)->SetState (state);
9649 	}
9650 }
9651 
P_DoDeferedScripts()9652 void P_DoDeferedScripts ()
9653 {
9654 	acsdefered_t *def;
9655 	const ScriptPtr *scriptdata;
9656 	FBehavior *module;
9657 
9658 	// Handle defered scripts in this step, too
9659 	def = level.info->defered;
9660 	while (def)
9661 	{
9662 		acsdefered_t *next = def->next;
9663 		switch (def->type)
9664 		{
9665 		case acsdefered_t::defexecute:
9666 		case acsdefered_t::defexealways:
9667 			scriptdata = FBehavior::StaticFindScript (def->script, module);
9668 			if (scriptdata)
9669 			{
9670 				P_GetScriptGoing ((unsigned)def->playernum < MAXPLAYERS &&
9671 					playeringame[def->playernum] ? players[def->playernum].mo : NULL,
9672 					NULL, def->script,
9673 					scriptdata, module,
9674 					def->args, 3,
9675 					def->type == acsdefered_t::defexealways ? ACS_ALWAYS : 0);
9676 			}
9677 			else
9678 			{
9679 				Printf ("P_DoDeferredScripts: Unknown %s\n", ScriptPresentation(def->script).GetChars());
9680 			}
9681 			break;
9682 
9683 		case acsdefered_t::defsuspend:
9684 			SetScriptState (def->script, DLevelScript::SCRIPT_Suspended);
9685 			DPrintf ("Deferred suspend of %s\n", ScriptPresentation(def->script).GetChars());
9686 			break;
9687 
9688 		case acsdefered_t::defterminate:
9689 			SetScriptState (def->script, DLevelScript::SCRIPT_PleaseRemove);
9690 			DPrintf ("Deferred terminate of %s\n", ScriptPresentation(def->script).GetChars());
9691 			break;
9692 		}
9693 		delete def;
9694 		def = next;
9695 	}
9696 	level.info->defered = NULL;
9697 }
9698 
addDefered(level_info_t * i,acsdefered_t::EType type,int script,const int * args,int argcount,AActor * who)9699 static void addDefered (level_info_t *i, acsdefered_t::EType type, int script, const int *args, int argcount, AActor *who)
9700 {
9701 	if (i)
9702 	{
9703 		acsdefered_t *def = new acsdefered_t;
9704 		int j;
9705 
9706 		def->next = i->defered;
9707 		def->type = type;
9708 		def->script = script;
9709 		for (j = 0; (size_t)j < countof(def->args) && j < argcount; ++j)
9710 		{
9711 			def->args[j] = args[j];
9712 		}
9713 		while ((size_t)j < countof(def->args))
9714 		{
9715 			def->args[j++] = 0;
9716 		}
9717 		if (who != NULL && who->player != NULL)
9718 		{
9719 			def->playernum = int(who->player - players);
9720 		}
9721 		else
9722 		{
9723 			def->playernum = -1;
9724 		}
9725 		i->defered = def;
9726 		DPrintf ("%s on map %s deferred\n", ScriptPresentation(script).GetChars(), i->MapName.GetChars());
9727 	}
9728 }
9729 
EXTERN_CVAR(Bool,sv_cheats)9730 EXTERN_CVAR (Bool, sv_cheats)
9731 
9732 int P_StartScript (AActor *who, line_t *where, int script, const char *map, const int *args, int argcount, int flags)
9733 {
9734 	if (map == NULL || 0 == strnicmp (level.MapName, map, 8))
9735 	{
9736 		FBehavior *module = NULL;
9737 		const ScriptPtr *scriptdata;
9738 
9739 		if ((scriptdata = FBehavior::StaticFindScript (script, module)) != NULL)
9740 		{
9741 			if ((flags & ACS_NET) && netgame && !sv_cheats)
9742 			{
9743 				// If playing multiplayer and cheats are disallowed, check to
9744 				// make sure only net scripts are run.
9745 				if (!(scriptdata->Flags & SCRIPTF_Net))
9746 				{
9747 					Printf(PRINT_BOLD, "%s tried to puke %s (\n",
9748 						who->player->userinfo.GetName(), ScriptPresentation(script).GetChars());
9749 					for (int i = 0; i < argcount; ++i)
9750 					{
9751 						Printf(PRINT_BOLD, "%d%s", args[i], i == argcount-1 ? "" : ", ");
9752 					}
9753 					Printf(PRINT_BOLD, ")\n");
9754 					return false;
9755 				}
9756 			}
9757 			DLevelScript *runningScript = P_GetScriptGoing (who, where, script,
9758 				scriptdata, module, args, argcount, flags);
9759 			if (runningScript != NULL)
9760 			{
9761 				if (flags & ACS_WANTRESULT)
9762 				{
9763 					return runningScript->RunScript();
9764 				}
9765 				return true;
9766 			}
9767 			return false;
9768 		}
9769 		else
9770 		{
9771 			if (!(flags & ACS_NET) || (who && who->player == &players[consoleplayer]))
9772 			{
9773 				Printf("P_StartScript: Unknown %s\n", ScriptPresentation(script).GetChars());
9774 			}
9775 		}
9776 	}
9777 	else
9778 	{
9779 		addDefered (FindLevelInfo (map),
9780 					(flags & ACS_ALWAYS) ? acsdefered_t::defexealways : acsdefered_t::defexecute,
9781 					script, args, argcount, who);
9782 		return true;
9783 	}
9784 	return false;
9785 }
9786 
P_SuspendScript(int script,const char * map)9787 void P_SuspendScript (int script, const char *map)
9788 {
9789 	if (strnicmp (level.MapName, map, 8))
9790 		addDefered (FindLevelInfo (map), acsdefered_t::defsuspend, script, NULL, 0, NULL);
9791 	else
9792 		SetScriptState (script, DLevelScript::SCRIPT_Suspended);
9793 }
9794 
P_TerminateScript(int script,const char * map)9795 void P_TerminateScript (int script, const char *map)
9796 {
9797 	if (strnicmp (level.MapName, map, 8))
9798 		addDefered (FindLevelInfo (map), acsdefered_t::defterminate, script, NULL, 0, NULL);
9799 	else
9800 		SetScriptState (script, DLevelScript::SCRIPT_PleaseRemove);
9801 }
9802 
operator <<(FArchive & arc,acsdefered_t * & defertop)9803 FArchive &operator<< (FArchive &arc, acsdefered_t *&defertop)
9804 {
9805 	BYTE more;
9806 
9807 	if (arc.IsStoring ())
9808 	{
9809 		acsdefered_t *defer = defertop;
9810 		more = 1;
9811 		while (defer)
9812 		{
9813 			BYTE type;
9814 			arc << more;
9815 			type = (BYTE)defer->type;
9816 			arc << type;
9817 			P_SerializeACSScriptNumber(arc, defer->script, false);
9818 			arc << defer->playernum << defer->args[0] << defer->args[1] << defer->args[2];
9819 			defer = defer->next;
9820 		}
9821 		more = 0;
9822 		arc << more;
9823 	}
9824 	else
9825 	{
9826 		acsdefered_t **defer = &defertop;
9827 
9828 		arc << more;
9829 		while (more)
9830 		{
9831 			*defer = new acsdefered_t;
9832 			arc << more;
9833 			(*defer)->type = (acsdefered_t::EType)more;
9834 			P_SerializeACSScriptNumber(arc, (*defer)->script, false);
9835 			arc << (*defer)->playernum << (*defer)->args[0] << (*defer)->args[1] << (*defer)->args[2];
9836 			defer = &((*defer)->next);
9837 			arc << more;
9838 		}
9839 		*defer = NULL;
9840 	}
9841 	return arc;
9842 }
9843 
CCMD(scriptstat)9844 CCMD (scriptstat)
9845 {
9846 	if (DACSThinker::ActiveThinker == NULL)
9847 	{
9848 		Printf ("No scripts are running.\n");
9849 	}
9850 	else
9851 	{
9852 		DACSThinker::ActiveThinker->DumpScriptStatus ();
9853 	}
9854 }
9855 
DumpScriptStatus()9856 void DACSThinker::DumpScriptStatus ()
9857 {
9858 	static const char *stateNames[] =
9859 	{
9860 		"Running",
9861 		"Suspended",
9862 		"Delayed",
9863 		"TagWait",
9864 		"PolyWait",
9865 		"ScriptWaitPre",
9866 		"ScriptWait",
9867 		"PleaseRemove"
9868 	};
9869 	DLevelScript *script = Scripts;
9870 
9871 	while (script != NULL)
9872 	{
9873 		Printf("%s: %s\n", ScriptPresentation(script->script).GetChars(), stateNames[script->state]);
9874 		script = script->next;
9875 	}
9876 }
9877 
9878 // Profiling support --------------------------------------------------------
9879 
ACSProfileInfo()9880 ACSProfileInfo::ACSProfileInfo()
9881 {
9882 	Reset();
9883 }
9884 
Reset()9885 void ACSProfileInfo::Reset()
9886 {
9887 	TotalInstr = 0;
9888 	NumRuns = 0;
9889 	MinInstrPerRun = UINT_MAX;
9890 	MaxInstrPerRun = 0;
9891 }
9892 
AddRun(unsigned int num_instr)9893 void ACSProfileInfo::AddRun(unsigned int num_instr)
9894 {
9895 	TotalInstr += num_instr;
9896 	NumRuns++;
9897 	if (num_instr < MinInstrPerRun)
9898 	{
9899 		MinInstrPerRun = num_instr;
9900 	}
9901 	if (num_instr > MaxInstrPerRun)
9902 	{
9903 		MaxInstrPerRun = num_instr;
9904 	}
9905 }
9906 
ArrangeScriptProfiles(TArray<ProfileCollector> & profiles)9907 void ArrangeScriptProfiles(TArray<ProfileCollector> &profiles)
9908 {
9909 	for (unsigned int mod_num = 0; mod_num < FBehavior::StaticModules.Size(); ++mod_num)
9910 	{
9911 		FBehavior *module = FBehavior::StaticModules[mod_num];
9912 		ProfileCollector prof;
9913 		prof.Module = module;
9914 		for (int i = 0; i < module->NumScripts; ++i)
9915 		{
9916 			prof.Index = i;
9917 			prof.ProfileData = &module->Scripts[i].ProfileData;
9918 			profiles.Push(prof);
9919 		}
9920 	}
9921 }
9922 
ArrangeFunctionProfiles(TArray<ProfileCollector> & profiles)9923 void ArrangeFunctionProfiles(TArray<ProfileCollector> &profiles)
9924 {
9925 	for (unsigned int mod_num = 0; mod_num < FBehavior::StaticModules.Size(); ++mod_num)
9926 	{
9927 		FBehavior *module = FBehavior::StaticModules[mod_num];
9928 		ProfileCollector prof;
9929 		prof.Module = module;
9930 		for (int i = 0; i < module->NumFunctions; ++i)
9931 		{
9932 			ScriptFunction *func = (ScriptFunction *)module->Functions + i;
9933 			if (func->ImportNum == 0)
9934 			{
9935 				prof.Index = i;
9936 				prof.ProfileData = module->FunctionProfileData + i;
9937 				profiles.Push(prof);
9938 			}
9939 		}
9940 	}
9941 }
9942 
ClearProfiles(TArray<ProfileCollector> & profiles)9943 void ClearProfiles(TArray<ProfileCollector> &profiles)
9944 {
9945 	for (unsigned int i = 0; i < profiles.Size(); ++i)
9946 	{
9947 		profiles[i].ProfileData->Reset();
9948 	}
9949 }
9950 
sort_by_total_instr(const void * a_,const void * b_)9951 static int STACK_ARGS sort_by_total_instr(const void *a_, const void *b_)
9952 {
9953 	const ProfileCollector *a = (const ProfileCollector *)a_;
9954 	const ProfileCollector *b = (const ProfileCollector *)b_;
9955 
9956 	assert(a != NULL && a->ProfileData != NULL);
9957 	assert(b != NULL && b->ProfileData != NULL);
9958 	return (int)(b->ProfileData->TotalInstr - a->ProfileData->TotalInstr);
9959 }
9960 
sort_by_min(const void * a_,const void * b_)9961 static int STACK_ARGS sort_by_min(const void *a_, const void *b_)
9962 {
9963 	const ProfileCollector *a = (const ProfileCollector *)a_;
9964 	const ProfileCollector *b = (const ProfileCollector *)b_;
9965 
9966 	return b->ProfileData->MinInstrPerRun - a->ProfileData->MinInstrPerRun;
9967 }
9968 
sort_by_max(const void * a_,const void * b_)9969 static int STACK_ARGS sort_by_max(const void *a_, const void *b_)
9970 {
9971 	const ProfileCollector *a = (const ProfileCollector *)a_;
9972 	const ProfileCollector *b = (const ProfileCollector *)b_;
9973 
9974 	return b->ProfileData->MaxInstrPerRun - a->ProfileData->MaxInstrPerRun;
9975 }
9976 
sort_by_avg(const void * a_,const void * b_)9977 static int STACK_ARGS sort_by_avg(const void *a_, const void *b_)
9978 {
9979 	const ProfileCollector *a = (const ProfileCollector *)a_;
9980 	const ProfileCollector *b = (const ProfileCollector *)b_;
9981 
9982 	int a_avg = a->ProfileData->NumRuns == 0 ? 0 : int(a->ProfileData->TotalInstr / a->ProfileData->NumRuns);
9983 	int b_avg = b->ProfileData->NumRuns == 0 ? 0 : int(b->ProfileData->TotalInstr / b->ProfileData->NumRuns);
9984 	return b_avg - a_avg;
9985 }
9986 
sort_by_runs(const void * a_,const void * b_)9987 static int STACK_ARGS sort_by_runs(const void *a_, const void *b_)
9988 {
9989 	const ProfileCollector *a = (const ProfileCollector *)a_;
9990 	const ProfileCollector *b = (const ProfileCollector *)b_;
9991 
9992 	return b->ProfileData->NumRuns - a->ProfileData->NumRuns;
9993 }
9994 
ShowProfileData(TArray<ProfileCollector> & profiles,long ilimit,int (STACK_ARGS * sorter)(const void *,const void *),bool functions)9995 static void ShowProfileData(TArray<ProfileCollector> &profiles, long ilimit,
9996 	int (STACK_ARGS *sorter)(const void *, const void *), bool functions)
9997 {
9998 	static const char *const typelabels[2] = { "script", "function" };
9999 
10000 	if (profiles.Size() == 0)
10001 	{
10002 		return;
10003 	}
10004 
10005 	unsigned int limit;
10006 	char modname[13];
10007 	char scriptname[21];
10008 
10009 	qsort(&profiles[0], profiles.Size(), sizeof(ProfileCollector), sorter);
10010 
10011 	if (ilimit > 0)
10012 	{
10013 		Printf(TEXTCOLOR_ORANGE "Top %ld %ss:\n", ilimit, typelabels[functions]);
10014 		limit = (unsigned int)ilimit;
10015 	}
10016 	else
10017 	{
10018 		Printf(TEXTCOLOR_ORANGE "All %ss:\n", typelabels[functions]);
10019 		limit = UINT_MAX;
10020 	}
10021 
10022 	Printf(TEXTCOLOR_YELLOW "Module       %-20s      Total    Runs     Avg     Min     Max\n", typelabels[functions]);
10023 	Printf(TEXTCOLOR_YELLOW "------------ -------------------- ---------- ------- ------- ------- -------\n");
10024 	for (unsigned int i = 0; i < limit && i < profiles.Size(); ++i)
10025 	{
10026 		ProfileCollector *prof = &profiles[i];
10027 		if (prof->ProfileData->NumRuns == 0)
10028 		{ // Don't list ones that haven't run.
10029 			continue;
10030 		}
10031 
10032 		// Module name
10033 		mysnprintf(modname, sizeof(modname), "%s", prof->Module->GetModuleName());
10034 
10035 		// Script/function name
10036 		if (functions)
10037 		{
10038 			DWORD *fnames = (DWORD *)prof->Module->FindChunk(MAKE_ID('F','N','A','M'));
10039 			if (prof->Index >= 0 && prof->Index < (int)LittleLong(fnames[2]))
10040 			{
10041 				mysnprintf(scriptname, sizeof(scriptname), "%s",
10042 					(char *)(fnames + 2) + LittleLong(fnames[3+prof->Index]));
10043 			}
10044 			else
10045 			{
10046 				mysnprintf(scriptname, sizeof(scriptname), "Function %d", prof->Index);
10047 			}
10048 		}
10049 		else
10050 		{
10051 			mysnprintf(scriptname, sizeof(scriptname), "%s",
10052 				ScriptPresentation(prof->Module->GetScriptPtr(prof->Index)->Number).GetChars() + 7);
10053 		}
10054 		Printf("%-12s %-20s%11llu%8u%8u%8u%8u\n",
10055 			modname, scriptname,
10056 			prof->ProfileData->TotalInstr,
10057 			prof->ProfileData->NumRuns,
10058 			unsigned(prof->ProfileData->TotalInstr / prof->ProfileData->NumRuns),
10059 			prof->ProfileData->MinInstrPerRun,
10060 			prof->ProfileData->MaxInstrPerRun
10061 			);
10062 	}
10063 }
10064 
CCMD(acsprofile)10065 CCMD(acsprofile)
10066 {
10067 	static int (STACK_ARGS *sort_funcs[])(const void*, const void *) =
10068 	{
10069 		sort_by_total_instr,
10070 		sort_by_min,
10071 		sort_by_max,
10072 		sort_by_avg,
10073 		sort_by_runs
10074 	};
10075 	static const char *sort_names[] = { "total", "min", "max", "avg", "runs" };
10076 	static const BYTE sort_match_len[] = {   1,     2,     2,     1,      1 };
10077 
10078 	TArray<ProfileCollector> ScriptProfiles, FuncProfiles;
10079 	long limit = 10;
10080 	int (STACK_ARGS *sorter)(const void *, const void *) = sort_by_total_instr;
10081 
10082 	assert(countof(sort_names) == countof(sort_match_len));
10083 
10084 	ArrangeScriptProfiles(ScriptProfiles);
10085 	ArrangeFunctionProfiles(FuncProfiles);
10086 
10087 	if (argv.argc() > 1)
10088 	{
10089 		// `acsprofile clear` will zero all profiling information collected so far.
10090 		if (stricmp(argv[1], "clear") == 0)
10091 		{
10092 			ClearProfiles(ScriptProfiles);
10093 			ClearProfiles(FuncProfiles);
10094 			return;
10095 		}
10096 		for (int i = 1; i < argv.argc(); ++i)
10097 		{
10098 			// If it's a number, set the display limit.
10099 			char *endptr;
10100 			long num = strtol(argv[i], &endptr, 0);
10101 			if (endptr != argv[i])
10102 			{
10103 				limit = num;
10104 				continue;
10105 			}
10106 			// If it's a name, set the sort method. We accept partial matches for
10107 			// options that are shorter than the sort name.
10108 			size_t optlen = strlen(argv[i]);
10109 			unsigned int j;
10110 			for (j = 0; j < countof(sort_names); ++j)
10111 			{
10112 				if (optlen < sort_match_len[j] || optlen > strlen(sort_names[j]))
10113 				{ // Too short or long to match.
10114 					continue;
10115 				}
10116 				if (strnicmp(argv[i], sort_names[j], optlen) == 0)
10117 				{
10118 					sorter = sort_funcs[j];
10119 					break;
10120 				}
10121 			}
10122 			if (j == countof(sort_names))
10123 			{
10124 				Printf("Unknown option '%s'\n", argv[i]);
10125 				Printf("acsprofile clear : Reset profiling information\n");
10126 				Printf("acsprofile [total|min|max|avg|runs] [<limit>]\n");
10127 				return;
10128 			}
10129 		}
10130 	}
10131 
10132 	ShowProfileData(ScriptProfiles, limit, sorter, false);
10133 	ShowProfileData(FuncProfiles, limit, sorter, true);
10134 }
10135