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 = §ors[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 = §ors[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(§ors[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 = §ors[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