Ijon 4 years ago
commit
768d4c7790

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+*.pk3
+*.zip
+nocommit/*

+ 1 - 0
CHANGELOG.txt

@@ -0,0 +1 @@
+

+ 1 - 0
README.txt

@@ -0,0 +1 @@
+

+ 433 - 0
pk3/DECORATE.dec

@@ -0,0 +1,433 @@
+const int PICKUP_DISPLAY = 288;
+const int PICKUP_PICKUP  = 289;
+const int PICKUP_DROPPED = 289;
+
+// Arguments for PICKUP_DISPLAY:
+//  - arg0: Item index. Specified through Damage here.
+//  - arg1: Whether the pickup's dropped or not.
+
+
+// Just used for incrementing armor. Has no value on its own.
+actor Pickup_OneArmor: BasicArmorBonus
+{
+    Armor.SavePercent   0
+    Armor.SaveAmount    1
+    Armor.MaxSaveAmount 0x7FFFFFFF
+}
+
+actor Class_KickedToClientside: Inventory
+{
+    Inventory.MaxAmount 1
+    +UNDROPPABLE
+}
+
+actor ClassBasedPickup: CustomInventory
+{
+    +QUIET
+    +AUTOACTIVATE
+    +SYNCHRONIZED
+    Inventory.PickupMessage ""
+    Inventory.MaxAmount 0
+
+    // Can't use either of these in the pickup states because fug u :^))))
+    var int user_index;
+    var int user_dropped;
+
+    Damage 0
+
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        goto DropCheck
+
+      DropCheck:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_DROPPED) != 0, "Dropped")
+        goto Display
+
+      Dropped:
+        goto Display
+
+      Display:
+        TNT1 A 35 ACS_ExecuteAlways(PICKUP_DISPLAY, 0, user_index, user_dropped)
+        wait
+
+      Unknown:
+        TNT1 A -1
+        stop
+
+      PickedUp:
+        // This is just here so that 'stop' actually works.
+        TNT1 A 0 A_ChangeFlag("ROCKETTRAIL", 0)
+        stop
+
+      NotPickedUp:
+        TNT1 A 0
+        fail
+    }
+}
+
+actor BaseTestPickup: ClassBasedPickup
+{
+    States
+    {
+      Clip:
+        CLIP A -1
+        stop
+
+      ClipBox:
+        AMMO A -1
+        stop
+
+      Shell:
+        SHEL A -1
+        stop
+
+      ShellBox:
+        SBOX A -1
+        stop
+
+      Rocket:
+        ROCK A -1
+        stop
+
+      RocketBox:
+        BROK A -1
+        stop
+
+      Cell:
+        CELL A -1
+        stop
+
+      CellPack:
+        CELP A -1
+        stop
+    }
+}
+
+
+actor TestClip: BaseTestPickup replaces Clip
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   0)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:      goto Clip
+      Clipguy:      goto Clip
+      Shellguy:     goto Shell
+      Rocketguy:    goto Rocket
+      Cellguy:      goto Cell
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 0, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestShell: BaseTestPickup replaces Shell
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   2)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:      goto Shell
+      Clipguy:      goto Clip
+      Shellguy:     goto Shell
+      Rocketguy:    goto Rocket
+      Cellguy:      goto Cell
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 2, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestRocket: BaseTestPickup replaces RocketAmmo
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   4)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:      goto Rocket
+      Clipguy:      goto Clip
+      Shellguy:     goto Shell
+      Rocketguy:    goto Rocket
+      Cellguy:      goto Cell
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 4, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestCell: BaseTestPickup replaces Cell
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   6)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:      goto Cell
+      Clipguy:      goto Clip
+      Shellguy:     goto Shell
+      Rocketguy:    goto Rocket
+      Cellguy:      goto Cell
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 6, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestClipBox: BaseTestPickup replaces ClipBox
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   1)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:      goto ClipBox
+      Clipguy:      goto ClipBox
+      Shellguy:     goto ShellBox
+      Rocketguy:    goto RocketBox
+      Cellguy:      goto CellPack
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 1, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestShellBox: BaseTestPickup replaces ShellBox
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   3)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:      goto ShellBox
+      Clipguy:      goto ClipBox
+      Shellguy:     goto ShellBox
+      Rocketguy:    goto RocketBox
+      Cellguy:      goto CellPack
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 3, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestRocketBox: BaseTestPickup replaces RocketBox
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   5)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:      goto RocketBox
+      Clipguy:      goto ClipBox
+      Shellguy:     goto ShellBox
+      Rocketguy:    goto RocketBox
+      Cellguy:      goto CellPack
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 5, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestCellPack: BaseTestPickup replaces CellPack
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   7)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:      goto CellPack
+      Clipguy:      goto ClipBox
+      Shellguy:     goto ShellBox
+      Rocketguy:    goto RocketBox
+      Cellguy:      goto CellPack
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 7, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+
+actor TestStim: ClassBasedPickup replaces Stimpack
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   8)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:
+      Clipguy:
+      Shellguy:
+      Rocketguy:
+      Cellguy:
+        STIM A -1
+        stop
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 8, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestBonus: ClassBasedPickup replaces HealthBonus
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   9)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:
+      Clipguy:
+      Shellguy:
+      Rocketguy:
+      Cellguy:
+        BON1 ABCDCB 6
+        loop
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 9, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestShotgun: ClassBasedPickup replaces Shotgun
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   10)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:
+      Clipguy:
+        SHOT A -1
+        stop
+
+      Shellguy:
+      Rocketguy:
+      Cellguy:
+        SGN2 A -1
+        stop
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 10, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestGreenArmor: ClassBasedPickup replaces GreenArmor
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   11)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:
+      Clipguy:
+      Shellguy:
+      Rocketguy:
+      Cellguy:
+        ARM1 A 6
+        ARM1 B 6 bright
+        loop
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 11, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestBlueArmor: ClassBasedPickup replaces BlueArmor
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   12)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:
+      Clipguy:
+      Shellguy:
+      Rocketguy:
+      Cellguy:
+        ARM2 A 6
+        ARM2 B 6 bright
+        loop
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 12, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}
+
+actor TestArmorBonus: ClassBasedPickup replaces ArmorBonus
+{
+    States
+    {
+      Spawn:
+        TNT1 A 0
+        TNT1 A 0 A_SetUserVar("user_index",   13)
+        TNT1 A 0 A_SetUserVar("user_dropped", 0)
+        goto DropCheck
+
+      Unknown:
+      Clipguy:
+      Shellguy:
+      Rocketguy:
+      Cellguy:
+        BON2 ABCDCD 6
+        loop
+
+      Pickup:
+        TNT1 A 0 A_JumpIf(ACS_ExecuteWithResult(PICKUP_PICKUP, 13, 0) == 1, "PickedUp")
+        goto NotPickedUp
+    }
+}

+ 1 - 0
pk3/LOADACS.txt

@@ -0,0 +1 @@
+pickup

File diff suppressed because it is too large
+ 1367 - 0
pk3/acs/commonFuncs.h


+ 275 - 0
pk3/acs/pickup.c

@@ -0,0 +1,275 @@
+#include "zcommon.acs"
+#library "pickup"
+
+#include "commonFuncs.h"
+
+// This is a library dedicated to making pickups give different items to
+//  different classes. Theoretically, you could use it for just one class,
+//  but why the fuck would you?
+
+
+// What each file corresponds to:
+//
+//
+//      == pickup_const ==
+//
+// This is where constant definitions that don't fit anywhere else lay. Mainly
+//  script numbers.
+//
+//
+//      == pickup_classes ==
+//
+// This stores the data and functions required to distinguish classes from each
+//  other. Notably, class-related constants are here.
+//
+//
+//      == pickup_items ==
+//
+// This is where pickups are actually defined!
+//
+// It's also entirely constant definitions, like pickup_const.
+//
+//
+//      == pickup_items_pickup ==
+// 
+// This is such a dumb, redundant name. But whatever. This stores purely
+//  cosmetic data used on pickup for each item. Pickup messages, flashes,
+//  pickup sounds, the like.
+//
+//
+//      == pickup_items_weapons ==
+//      == pickup_items_health ==
+//      == pickup_items_armor ==
+//      == pickup_items_ammo ==
+//
+// Since these are special pickup types (not just plain Inventory items), they
+//  need their own metadata kept for them.
+//  
+//
+//      == pickup_scripted ==
+//
+// Every constant/array definition for scripting pickups goes in here. Pickup,
+//  display, messages, doesn't matter, it's in here.
+//
+//
+//      == pickup_pickup_defs ==
+//
+// Constant and array definitions for the pickup code.
+//
+//
+//      == pickup_pickup_weapons ==
+//      == pickup_pickup_health ==
+//      == pickup_pickup_armor ==
+//      == pickup_pickup_ammo ==
+//
+// Specialized pickup functions. Only pickup code for the inventory type should
+//  be in these files.
+//
+//
+//      == pickup_pickup ==
+//
+// It's redundant! But it's also where the pickup functions are stored. Unlike
+//  pickup_classes, there's too much stuff in pickup_items to put them in there
+//  without everything becoming too damn crowded.
+//
+//
+//      == pickup_client_defs ==
+//
+// The actual server-to-client slots are defined here. You don't need to go
+//  into pickup_client to do anything if you just want to add slots to this.
+//
+//
+//      == pickup_client ==
+//
+// This is where various functions and scripts for sending data from the server
+//  to the client are found. The system-critical S2C definitions are here too.
+//
+//
+//      == pickup_clientmessage ==
+//
+// When a pickup succeeds, the server sends the client some script calls, giving
+//  the client enough data about the pickup to run a pickup message script. This
+//  is where those scripts are.
+//
+//      
+//      == pickup_display ==
+//
+// This is what the stuff in pickup_client is mainly for. It controls display
+//  of pickups, which can be deferred to other scripts.
+//
+//
+//      == pickup_daemon ==
+// 
+// This is where the OPEN script that sends stuff from the server to the client
+//  is at. It also contains the DISCONNECT script resetting the data when the
+//  client leaves/spectates, and the ENTER script force-sending the presumably
+//  empty data to the client (as a reset button, in case they spec/rejoined).
+//
+//
+//          ====================
+//          ===== FUN FACT =====
+//          ====================
+// 
+// If you take only pickup_const.h, pickup_client.h, and pickup_daemon.h, you'll
+//  have yourself a generic system for sending data from the server to the client
+//  via and for ACS. Handy!
+//
+// Well, so long as you get rid of the script stuff.
+
+#include "pickup_const.h"
+#include "pickup_classes.h"
+#include "pickup_items.h"
+#include "pickup_items_pickup.h"
+#include "pickup_items_weapons.h"
+#include "pickup_items_health.h"
+#include "pickup_items_armor.h"
+#include "pickup_items_ammo.h"
+#include "pickup_scripted.h"
+#include "pickup_pickup_defs.h"
+#include "pickup_pickup_weapons.h"
+#include "pickup_pickup_health.h"
+#include "pickup_pickup_armor.h"
+#include "pickup_pickup_ammo.h"
+#include "pickup_pickup.h"
+#include "pickup_client_defs.h"
+#include "pickup_client.h"
+#include "pickup_clientmessage.h"
+#include "pickup_display.h"
+#include "pickup_daemon.h"
+
+
+// This is such a tiny script, it's a complete waste making a file for it. So
+//  here it is.
+script PICKUP_DROPPED (void)
+{
+    SetResultValue(GetActorProperty(0, APROP_Dropped));
+}
+
+
+// Class number checking script. If this returns true, then the class number
+//  this script is for is considered the class for this player. Otherwise,
+//  it keeps checking class numbers.
+
+script 490 (int tid, int gunType)
+{
+    if (tid != 0) { SetActivator(tid); }
+
+    int ret = false;
+
+    switch (gunType)
+    {
+      case 0: ret = CheckWeapon("Pistol")   || CheckWeapon("Chaingun");     break;
+      case 1: ret = CheckWeapon("Shotgun")  || CheckWeapon("SuperShotgun"); break;
+      case 2: ret = CheckWeapon("RocketLauncher");                          break;
+      case 3: ret = CheckWeapon("BFG9000")  || CheckWeapon("PlasmaRifle");  break;
+    }
+
+    SetResultValue(ret);
+}
+
+
+// Pickup testing script.
+// 
+// If, in PKP_ScriptedPickups, you've specified this to be a pickup script that
+//  returns a new index, you need to do that with SetResultValue, before any
+//  delays if you do any for some reason. Otherwise, you'll want to set values
+//  in PKP_ReturnArray, defined in pickup_pickup_defs.h.
+//
+// Pickup data's in PKP_PickupData, defined in pickup_pickup_defs.h.
+//
+//
+// Relevant definitions:
+//
+// #define PDATA_ITEMNUM   0
+// #define PDATA_CLASSNUM  1
+// #define PDATA_DROPPED   2
+//
+// #define PARRAY_SUCCEEDED        0
+// #define PARRAY_CONSUME          1
+// #define PARRAY_NOCONSUME        2
+// #define PARRAY_DIDSOMETHING     3
+// #define PARRAY_LOWHEALTH        4
+//
+
+script 491 (int arg1, int arg2, int arg3)
+{
+    Log(s:"[PKP] args: (", d:arg1, s:", ", d:arg2, s:", ", d:arg3, s:")");
+    Log(s:"[PKP] item: ", d:PKP_PickupData[PDATA_ITEMNUM],
+           s:", class: ", d:PKP_PickupData[PDATA_CLASSNUM],
+         s:", dropped: ", d:PKP_PickupData[PDATA_DROPPED]);
+
+    SetResultValue(It_Shell);
+}
+
+
+// Pickup message testing script.
+//
+// If, in CMSG_ScriptedMessages, you've specified this to be a message script
+//  that returns a pickup message, you need to do that with SetResultValue,
+//  before any delays if there are any. Otherwise, do whatever the hell you
+//  want, because this message script ends with this script.
+//
+// Message data's in CMSG_MessageData, defined in pickup_clientdata.h. The
+//  constant definitions for it are in pickup_pickup_defs.h, however, because
+//  they're the same as the constants for PKP_MessageData, which is also in
+//  there.
+//
+// 
+// Relevant definitions:
+//
+// #define MDATA_CLASSNUM     0
+// #define MDATA_ITEMNUM      1
+// #define MDATA_DROPPED      2
+// #define MDATA_LOWHEALTH    3
+//
+
+script 492 (int arg1, int arg2, int arg3)
+{
+    int pln = PlayerNumber();
+
+    Log(s:"[MSG] args: (", d:arg1, s:", ", d:arg2, s:", ", d:arg3, s:")");
+    Log(s:"[MSG] dropped: ", d:CMSG_MessageData[pln][MDATA_DROPPED]);
+
+    SetResultValue("my bum");
+}
+
+// Pickup display testing script.
+//
+// Note that you have to handle DPASS_DOCLEANUP somehow. Here, it doesn nothing
+//  since APROP_RenderStyle and APROP_Alpha are already reset by the main
+//  display script when class number changes, but if you're doing any other
+//  changes to the pickup, make sure to undo them here.
+//
+// Pickup data's in DISP_ScriptArgs, defined in pickup_display.h.
+//
+//
+// Relevant definitions:
+//
+// #define DPASS_ITEMNUM       0
+// #define DPASS_CLASSNUM      1
+// #define DPASS_OLDCLASSNUM   2
+// #define DPASS_DROPPED       3
+// #define DPASS_DOCLEANUP     4
+//
+
+script 493 (int arg1, int arg2, int arg3)
+{
+    if (DISP_ScriptArgs[DPASS_DOCLEANUP])
+    {
+        terminate;
+    }
+
+    if (DISP_ScriptArgs[DPASS_CLASSNUM] != DISP_ScriptArgs[DPASS_OLDCLASSNUM])
+    {
+        SetActorState(0, "Scripted");
+    }
+
+    int timerPulse = Timer() % 48;
+
+    if (timerPulse > 24) { timerPulse = 48 - timerPulse; }
+
+    int newAlpha = 0.25 + ((1.0 / 24) * timerPulse);
+
+    SetActorProperty(0, APROP_RenderStyle,  STYLE_Translucent);
+    SetActorProperty(0, APROP_Alpha,        newAlpha);
+}

BIN
pk3/acs/pickup.o


+ 235 - 0
pk3/acs/pickup_classes.h

@@ -0,0 +1,235 @@
+// Class data whoa
+
+// First, how many classes are we talking about here?
+#define CLASSCOUNT_DEFINED  4
+
+// But we're not actually going to use that number a lot, because Cl_Unknown
+//  exists. This is typically what we want.
+#define CLASSCOUNT  (CLASSCOUNT_DEFINED + 1)
+
+
+
+// Define constants to match to classes here. No one wants to deal with
+//
+//      if (Pickup_ClassNumber() == 3)
+//
+// when trying to figure out what the hell you mean.
+//
+// Cl_Unknown should always be defined: it's the class you get when no other
+//  one applies.
+
+#define Cl_Unknown              (-1)
+#define Cl_Test_ClipGuy         0
+#define Cl_Test_ShellGuy        1
+#define Cl_Test_RocketGuy       2
+#define Cl_Test_CellGuy         2
+
+
+
+// This is handy.
+
+int ClassNames[CLASSCOUNT] = 
+{
+    "Unknown",
+    "Clipguy",
+    "Shellguy",
+    "Rocketguy",
+    "Cellguy",
+};
+
+
+
+// This is the default state an item on display will jump to if it isn't
+//  scripted. It's used in pickup_display.h, but it's here to keep relevant
+//  data in the same place.
+
+int DISP_ClassStates[CLASSCOUNT_DEFINED] =
+{
+    "Clipguy",
+    "Shellguy",
+    "Rocketguy",
+    "Cellguy",
+};
+
+
+
+// Now, in the past I've done class differentiation purely through inventory
+//  checks, but that's not always the best way to do it. So I'm giving the
+//  option to use either an inventory check, a class name check, or a script-
+//  based check to determine class. First class found is used.
+//
+// FINDBYINV     finds by inventory item.
+//
+// FINDBYNAME    finds by actor class name.
+//
+// FINDBYSCRIPT  finds by calling a given script with the provided arguments.
+//
+// FINDBYNSCRIPT finds by calling a given *named* script. This doesn't work
+//               Zandronum 2.0, but will in 3.0. If you're using this in Z&,
+//               don't use this.
+
+#define CLASS_FINDBYINV     0
+#define CLASS_FINDBYNAME    1
+#define CLASS_FINDBYSCRIPT  2
+#define CLASS_FINDBYNSCRIPT 3
+
+int ClassCheckMethod[CLASSCOUNT_DEFINED] =
+{
+    CLASS_FINDBYSCRIPT,
+    CLASS_FINDBYSCRIPT,
+    CLASS_FINDBYSCRIPT,
+    CLASS_FINDBYSCRIPT,
+};
+
+
+
+// This is for FINDBYINV. I'd adjust this down as low as possible, just to
+//  speed things up some.
+#define CLASS_INVCHECKCOUNT     1
+
+// And the actual items.
+int ClassCheck_ByInv[CLASSCOUNT_DEFINED][CLASS_INVCHECKCOUNT] =
+{
+    {""},
+    {""},
+    {""},
+    {""},
+};
+
+
+// This is for FINDBYNAME. Again, adjust down as low as possible.
+#define CLASS_NAMECHECKCOUNT    1
+
+// Etc.
+int ClassCheck_ByName[CLASSCOUNT_DEFINED][CLASS_NAMECHECKCOUNT] =
+{
+    {""},
+    {""},
+    {""},
+    {""},
+};
+
+
+// And this is for FINDBYSCRIPT. You only get to call one script, but you can
+//  specify whatever three arguments you want for it. Script economy go!
+//
+// The first argument passed is always the TID to check.
+//
+// You probably can't use the third argument in Zandronum, so be warned.
+
+int ClassCheck_ByScript[CLASSCOUNT_DEFINED][4] =
+{
+    {490, 0, 0, 0},
+    {490, 1, 0, 0},
+    {490, 2, 0, 0},
+    {490, 3, 0, 0},
+};
+
+
+// Same deal, but for FINDBYNSCRIPT. Arguments have to be stored in ByScript,
+//  however. Mix strings and integers in an array and things go bad.
+
+int ClassCheck_ByNamedScript[CLASSCOUNT_DEFINED] =
+{
+    "",
+    "",
+    "",
+    "",
+};
+
+
+
+
+
+
+// ========
+// == FUNCTIONS
+// ========
+
+
+// Used by Pickup_ClassNumber to check inventory items,
+//  because I hate indentation.
+function int Pickup_CheckInv(int tid, int classNum)
+{
+    int i;
+
+    for (i = 0; i < CLASS_INVCHECKCOUNT; i++)
+    {
+        int item = ClassCheck_ByInv[classNum][i];
+        if (StrLen(item) == 0) { continue; }
+
+        if (tid == 0)
+        {
+            if (CheckInventory(item)) { return true; }
+        }
+        else
+        {
+            if (CheckActorInventory(tid, item)) { return true; }
+        }
+    }
+
+    return false;
+}
+
+
+// Used by Pickup_ClassNumber to check class names.
+//  Still hate indentation.
+function int Pickup_CheckName(int tid, int classNum)
+{
+    int i;
+
+    for (i = 0; i < CLASS_NAMECHECKCOUNT; i++)
+    {
+        if (CheckActorClass(tid, ClassCheck_ByName[classNum][i]))
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
+// Function's name is pretty self-explanatory.
+function int Pickup_ClassNumber(int tid)
+{
+    int i;
+    int found = false;
+
+    for (i = 0; i < CLASSCOUNT_DEFINED; i++)
+    {
+        int checkType = ClassCheckMethod[i];
+
+        switch (checkType)
+        {
+          case CLASS_FINDBYINV:
+            found = Pickup_CheckInv(tid, i);
+            break;
+
+          
+          case CLASS_FINDBYNAME:
+            found = Pickup_CheckName(tid, i);
+            break;
+
+          case CLASS_FINDBYSCRIPT:
+            found = ACS_ExecuteWithResult(ClassCheck_ByScript[i][0],
+                                          tid,
+                                          ClassCheck_ByScript[i][1],
+                                          ClassCheck_ByScript[i][2],
+                                          ClassCheck_ByScript[i][3]);
+            break;
+
+          case CLASS_FINDBYNSCRIPT:
+            found = ACS_ExecuteWithResult(ClassCheck_ByNamedScript[i],
+                                          tid,
+                                          ClassCheck_ByScript[i][1],
+                                          ClassCheck_ByScript[i][2],
+                                          ClassCheck_ByScript[i][3]);
+            break;
+        }
+
+        if (found) { return i; }
+    }
+
+    return -1;
+}

+ 251 - 0
pk3/acs/pickup_client.h

@@ -0,0 +1,251 @@
+// Server-to-client definitions here.
+//
+// S2C is for constant definitions, SToC is for array definitions, and Sender is
+// for function definitions. Scripts use the PICKUP prefix like all other scripts.
+//
+// This is mainly to avoid same-name issues.
+
+
+// When the server sends data to the client, it will wait this many tics. If it
+//  hasn't gotten a response from the client indicating it got the right data
+//  by then, it'll resend the data-setting script.
+#define S2C_RESENDTIME  36
+
+
+// This is where the data's stored on the server side. One row for each player,
+//  one column for each data point.
+int SToC_ServerData[PLAYERMAX][S2C_DATACOUNT];
+
+
+// This is for the clients. For Zandronum, it technically could just be a 1D
+//  array, but that makes me feel uneasy about the ZDoom scenario. So you get
+//  a 2D array.
+int SToC_ClientData[PLAYERMAX][S2C_DATACOUNT];
+
+
+// In order to avoid packet loss issues, the clients ping back to the server
+//  when they recieve a data-setting script. We store their responses here.
+//
+// Until the data in SToC_ServerData and here match, the server will re-send
+//  the packet every second, or however long specified in S2C_RESENDTIME.
+int SToC_LastClientResponse[PLAYERMAX][S2C_DATACOUNT];
+
+
+// This is where it stores the time the server last sent a data ping.
+int SToC_LastSendTime[PLAYERMAX][S2C_DATACOUNT];
+
+
+// This is for the clients, to keep track of when we got the last ping from a
+//  server.
+int SToC_LastReceiveTime[PLAYERMAX][S2C_DATACOUNT];
+
+
+// Okay, so one bug I ran into right away: not only does ACS_ExecuteAlways get
+// sent in reverse order, but so does RequestScriptPuke. This won't do.
+//
+// So instead of sending directly, the update functions instead add to this,
+//  and another intermediate function does the sending.
+int SToC_ToSend[PLAYERMAX][S2C_DATACOUNT];
+
+
+
+
+
+// ========
+// == FUNCTION DEFINITIONS
+// ========
+
+
+// Are we in Zandronum?
+// Has the side effect of setting IsZandronum if it's -1 (ie. uninitialized).
+function int Pickup_IsZandronum(void)
+{
+    if (IsZandronum == -1)
+    {
+        SetDBEntry("pickup", "client_iszand", true);
+        IsZandronum = GetDBEntry("pickup", "client_iszand") == 1;
+    }
+    
+    return IsZandronum;
+}
+
+
+
+// Base function for setting server-to-client data.
+function void Pickup_SetData(int pln, int index, int value)
+{
+    int oldval = SToC_ServerData[pln][index];
+    if (oldval == value) { return; }
+
+    SToC_ServerData[pln][index]     = value;
+    SToC_LastSendTime[pln][index]   = -1;
+}
+
+
+
+// Clear all server-to-client data for the specified player number. This is
+//  called on disconnect to make sure no data propagates to the next client
+//  accidentally.
+function void Pickup_ClearData(int pln)
+{
+    int i;
+
+    for (i = 0; i < S2C_DATACOUNT; i++)
+    {
+        Pickup_SetData(pln, i, 0);
+    }
+}
+
+
+
+// Base function to send data from server to client.
+// 
+// Note that it doesn't actually *send* anything. It only adds it to an array,
+//  to be sent by another function.
+//
+// This is to avoid dumb issues with scripts being last-in-first-out when it
+//  comes to executing.
+function void Pickup_SendData(int pln, int index)
+{
+    SToC_ToSend[pln][index] = true;
+}
+
+
+
+// Check if we need to send any data to anyone, and if so, do so.
+function void Pickup_UpdateClients(void)
+{
+    int i, j;
+
+    for (i = 0; i < PLAYERMAX; i++)
+    {
+        if (!PlayerInGame(i)) { continue; }
+
+        for (j = 0; j < S2C_DATACOUNT; j++)
+        {
+            int data_server = SToC_ServerData[i][j];
+            int data_client = SToC_LastClientResponse[i][j];
+
+            if (data_server == data_client) { continue; }
+
+            int lastSend    = SToC_LastSendTime[i][j]; 
+
+            if ((lastSend < 0) || (Timer() - lastSend > S2C_RESENDTIME))
+            {
+                Pickup_SendData(i, j);
+            }
+        }
+    }
+}
+
+
+
+// Force a data send for a certain player number.
+function void Pickup_ForceUpdateClient(int pln)
+{
+    int i;
+
+    for (i = 0; i < S2C_DATACOUNT; i++)
+    {
+        Pickup_SendData(pln, i);
+    }
+}
+
+
+
+// Okay NOW send the crap. This should only ever be run by PICKUP_MAINLOOP.
+function void Pickup_ActuallySend(void)
+{
+    int i, j;
+
+    int useAlways = IsZandronum && ConsolePlayerNumber() == -1;
+
+    for (i = 0; i < PLAYERMAX; i++)
+    {
+        for (j = 0; j < S2C_DATACOUNT; j++)
+        {
+            if (SToC_ToSend[i][j])
+            {
+                int data = SToC_ServerData[i][j];
+
+                // As of Zandronum 2.1.2, negative numbers between -1 and -128
+                //  come to the client as 256 + <val>. This is to hack around that.
+                if (data < 0) { data -= 128; }
+
+                if (useAlways)
+                {
+                    ACS_ExecuteAlways(PICKUP_SENDTOCLIENT, 0, i, j, data);
+                }
+                else
+                {
+                    ACS_ExecuteWithResult(PICKUP_SENDTOCLIENT, i, j, data);
+                }
+
+                SToC_LastSendTime[i][j] = Timer();
+
+                SToC_ToSend[i][j] = false;
+            }
+        }
+    }
+}
+
+
+
+// Ping back from the client to server, to let the server know you got the data
+//  properly. In single player/ZDoom at all, just set the value directly.
+function void Pickup_PingBack(int pln, int index, int value)
+{
+    if (Pickup_IsZandronum() && !IsServer)
+    {
+        // Don't send player number since RequestScriptPuke will always run
+        //  on ConsolePlayer on the server's end
+        //
+        // The server won't go by what the script arguments say for player
+        //  number because one could spoof pings really easily then - it's
+        //  harmless in this case, but annoying
+        RequestScriptPuke(PICKUP_PINGSERVER, index, value, 0);
+    }
+    else
+    {
+        // Eh fuck it, we'll do it ourself
+        SToC_LastClientResponse[pln][index] = value;
+    }
+}
+    
+
+// ========
+// == SCRIPT DEFINITIONS
+// ========
+
+
+// Called by the server to send data. Recieved by the client to get that data.
+script PICKUP_SENDTOCLIENT (int pln, int index, int value) clientside
+{
+    int cpln = ConsolePlayerNumber(); // always 0 in ZDoom
+
+    // As of Zandronum 2.1.2, negative numbers between -1 and -128
+    //  come to the client as 256 + <val>. This is to hack around that.
+    if (value < 0) { value += 128; }
+
+    // Add 1 to this so that 0 means "we never got anything", not "tic 0"
+    SToC_LastReceiveTime[pln][index] = Timer() + 1;
+    SToC_ClientData[pln][index] = value;
+
+    // Don't send pingbacks for data that isn't ours.
+    if (pln == cpln)
+    {
+        Pickup_PingBack(pln, index, value);
+    }
+}
+
+
+// Called by the client to send data back to the server. Recieved by the server
+//  to get that data. This is only called in Zandronum online, unless the player
+//  is puking scripts for some reason.
+script PICKUP_PINGSERVER (int index, int value) net
+{
+    int pln = PlayerNumber();
+    SToC_LastClientResponse[pln][index] = value;
+
+    Log(s:"got ping back from player ", d:pln, s:" (name: ", n:pln+1, s:"\c-) (data: ", d:index, s:" = ", d:value, s:")");
+}

+ 19 - 0
pk3/acs/pickup_client_defs.h

@@ -0,0 +1,19 @@
+// I could've kept this stuff in pickup_client.h, but I'd rather have clearer
+//  delineation between "stuff that's meant to be tinkered with" and "system
+//  stuff that shouldn't be tinkered with".
+
+
+// This defines how many different data points we should sync between the server
+//  and the clients.
+#define S2C_DATACOUNT   6
+
+
+// Constants for identifying which data point we want to modify.
+
+#define S2C_D_CLASSNUM      0
+#define S2C_D_HEALTH        1
+#define S2C_D_CLIP          2
+#define S2C_D_SHELL         3
+#define S2C_D_ROCKET        4
+#define S2C_D_CELL          5
+

+ 237 - 0
pk3/acs/pickup_clientmessage.h

@@ -0,0 +1,237 @@
+// From pickup_pickup.h, the client gets data for a pickup message. This is
+//  where it's processed.
+
+// Data that the server sends to the client for pickup messages arrives here.
+//  Constant definitions for this are in pickup_pickup.h.
+
+int CMSG_MessageData[PLAYERMAX][MDATA_SLOTS];
+
+// This is so we know what's been sent and what hasn't been.
+int CMSG_MessageDefined[PLAYERMAX][MDATA_SLOTS];
+
+
+// ZDoom has this odd behaviour where pickups of the same type picked up in the
+//  same tic only show one message among them... unless there's a pickup in
+//  between, in which case it'll display it again.
+//
+// This replicates that. Entirely scripted pickups ignore it, however. They can
+//  do whatever they want.
+
+#define LASTPICKUP_INDEX    0
+#define LASTPICKUP_TIME     1
+
+int CMSG_LastPickup[PLAYERMAX][2];
+
+
+
+
+// Add newly-received data to CMSG_MessageData, and return true if every data
+//  slot has been filled.
+function int CMSG_AddPickupData(int pln, int index, int arg1, int arg2)
+{
+    // As of Zandronum 2.1.2, Using ACS_ExecuteWithResult with arguments
+    //  between -1 and -128 will pass those numbers as 256 + <val> instead.
+    //  This is obviously not desirable.
+    //
+    // We subtracted 128 from it in Pickup_SendMessage, so we add 128 here.
+    if (arg1 < 0) { arg1 += 128; }
+    if (arg2 < 0) { arg2 += 128; }
+
+    CMSG_MessageData[pln][index]   = arg1;
+    CMSG_MessageData[pln][index+1] = arg2;
+
+    CMSG_MessageDefined[pln][index]   = true;
+    CMSG_MessageDefined[pln][index+1] = true;
+
+    int i;
+
+    for (i = 0; i < MDATA_SLOTS; i++)
+    {
+        if (!CMSG_MessageDefined[pln][i]) { return false; }
+    }
+
+    return true;
+}
+
+
+
+// Clear received data on a certain player number, so a new pickup message can
+//  come through.
+function void CMSG_ClearPickupData(int pln)
+{
+    int i;
+
+    for (i = 0; i < MDATA_SLOTS; i++)
+    {
+        CMSG_MessageData[pln][i]    = 0;
+        CMSG_MessageDefined[pln][i] = false;
+    }
+}
+
+
+
+// It's the same as the other two IsScripted functions. Returns the script index
+//  in CMSG_ScriptedPickups, or -1 if it doesn't exist.
+function int CMSG_IsScripted(int index, int classNum)
+{
+    int i;
+
+    for (i = 0; i < MSG_SCRIPTEDCOUNT; i++)
+    {
+        int checkIndex = CMSG_ScriptedMessages[i][MSG_S_ITEMNUM];
+        int checkClass = CMSG_ScriptedMessages[i][MSG_S_CLASSNUM];
+
+        if (index == checkIndex && classNum == checkClass)
+        {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+
+
+script PICKUP_SHOWMESSAGE (int mdata_index, int data1, int data2) clientside
+{
+    // This is the player number of the person picking up the item.
+    int pln  = PlayerNumber();
+
+    // This is the client's player number. If it doesn't match pln, don't do
+    //  Log messages.
+    int cpln = ConsolePlayerNumber();
+
+	int gotAllData = CMSG_AddPickupData(pln, mdata_index, data1, data2);
+
+    // We're still receiving data, don't do anything yet
+    if (!gotAllData) { terminate; }
+
+    int classNum = CMSG_MessageData[pln][MDATA_CLASSNUM];
+    int itemNum  = CMSG_MessageData[pln][MDATA_ITEMNUM];
+    int dropped  = CMSG_MessageData[pln][MDATA_DROPPED];
+
+    int message = "";
+
+    int snum, arg1, arg2, arg3, onlyString;
+    int scriptIndex = CMSG_IsScripted(itemNum, classNum);
+
+    if (scriptIndex != -1)
+    {
+        snum = CMSG_ScriptedMessages[scriptIndex][MSG_S_SCRIPTNUM];
+        arg1 = CMSG_ScriptedMessages[scriptIndex][MSG_S_ARG1];
+        arg2 = CMSG_ScriptedMessages[scriptIndex][MSG_S_ARG2];
+        arg3 = CMSG_ScriptedMessages[scriptIndex][MSG_S_ARG3];
+
+        // If this is true, we just use the script for getting a message.
+        onlyString = CMSG_ScriptedMessages[scriptIndex][MSG_S_ONLYSTRING];
+
+        // If we're handling this pickup message entirely through scripted
+        //  events, don't bother with the ZDoom pickup message behaviour.
+        //
+        // Note that CMSG_MessageData is still available for use in the message
+        //  script, so we don't need to pass in class number, item number, and
+        //  all that.
+        //
+        // We pass in dropped mainly for consistency. I don't wanna make an
+        //  argument array for pickup scripts.
+        //
+        // If I make one for them anyway, expect to get the
+        //  full three arguments.
+
+        if (!onlyString)
+        {
+            ACS_ExecuteWithResult(snum, arg1, arg2, arg3);
+            CMSG_ClearPickupData(pln);
+            terminate;
+        }
+    }
+
+    // Always the "- 1" trick. If Timer() started at 1, things would be nice.
+    int lastPickup = CMSG_LastPickup[pln][LASTPICKUP_INDEX];
+    int lastTime   = CMSG_LastPickup[pln][LASTPICKUP_TIME]  - 1;
+
+    // Replicate ZDoom pickup message behaviour.
+    if (lastTime == Timer() && lastPickup == itemNum)
+    {
+        terminate;
+    }
+
+    if (scriptIndex != -1)
+    {
+        message = ACS_ExecuteWithResult(snum, arg1, arg2, arg3);
+
+        // If the pickup script doesn't return a message, don't do pickup message behaviour.
+        if (message == 0 || StrLen(message) == 0)
+        {
+            CMSG_ClearPickupData(pln);
+            terminate;
+        }
+    }
+    else if (CMSG_MessageData[pln][MDATA_LOWHEALTH] > 0)
+    {
+        message = PKP_LowHealthMessages[CMSG_MessageData[pln][MDATA_LOWHEALTH] - 1];
+    }
+    else
+    {
+        message = PKP_Messages[itemNum][classNum + 1];
+    }
+
+    // Whatever the message ended up being, check if it's a LANGUAGE lookup.
+
+    // Explicitly *not* one.
+    if (strstr(message, "\\$") == 0)
+    {
+        message = sliceString(message, 2, StrLen(message));
+    }
+    else if (GetChar(message, 0) == '$')
+    {
+        message = StrParam(l:sliceString(message, 1, StrLen(message)));
+    }
+
+
+    // msgColors is in commonFuncs.h.
+    // Since Log() prints in white, we need to fix that.
+    int messageColor = msgColors[GetCVar("msg0color")];
+
+    // Can't have \c- breaking shit.
+    message = strsub(message, "\c-", messageColor);
+
+    if (pln == cpln)
+    {
+        Log(s:messageColor, s:message);
+    }
+
+    // If silent pickup's on, can't let other people hear it.
+
+    int pickupSound = PKP_PickupSounds[itemNum][classNum + 1];
+    int silent      = !!(GetCvar("compat_silentpickup"));
+
+    if (silent)
+    {
+        // Only for the right player.
+        if (pln == cpln) { LocalAmbientSound(pickupSound, 127); }
+    }
+    else
+    {
+        ActivatorSound(pickupSound, 127);
+    }
+
+    // And pickup flash.
+
+    int r = PKP_PickupFlashes[itemNum][classNum+1][PFLASH_RED];
+    int g = PKP_PickupFlashes[itemNum][classNum+1][PFLASH_GREEN];
+    int b = PKP_PickupFlashes[itemNum][classNum+1][PFLASH_BLUE];
+    int a = PKP_PickupFlashes[itemNum][classNum+1][PFLASH_ALPHA];
+    int t = PKP_PickupFlashes[itemNum][classNum+1][PFLASH_TIME];
+
+    FadeRange(r, g, b, a, r, g, b, 0, t);
+
+
+    // This must be at the end, or else for every two numbers sent from the
+    //  server, the client will do another likely malformed pickup message
+    CMSG_ClearPickupData(pln);
+
+    CMSG_LastPickup[pln][LASTPICKUP_INDEX] = itemNum;
+    CMSG_LastPickup[pln][LASTPICKUP_TIME]  = Timer() + 1;
+}
+

+ 38 - 0
pk3/acs/pickup_const.h

@@ -0,0 +1,38 @@
+// Used in pickup_daemon.h
+#define PICKUP_MAINLOOP     281
+#define PICKUP_ENTER        282
+#define PICKUP_DISCONNECT   283
+#define PICKUP_OPEN_CLIENT  284
+#define PICKUP_WEAPONSWITCH 285
+
+// Used in pickup_client.h
+#define PICKUP_SENDTOCLIENT 286
+#define PICKUP_PINGSERVER   287
+
+// Used in pickup_display.h
+#define PICKUP_DISPLAY      288
+
+// Used in pickup_pickup.h
+#define PICKUP_PICKUP       289
+#define PICKUP_SHOWMESSAGE  290
+
+// Used in pickup.c because of how small a script it is
+#define PICKUP_DROPPED      291
+
+
+
+// There's not much we can do to make client-specific display work in ZDoom
+//  multiplayer. But at least we can *try* to not have things break horribly,
+//  at least in single player.
+int IsZandronum = -1;
+
+
+// Also for Zandronum.
+int IsServer;
+
+
+// For weapon pickup.
+// - 0 means "never switch".
+// - 1 means "switch on better".
+// - 2 means "always switch".
+int C2S_WeaponSwitchState[PLAYERMAX];

+ 98 - 0
pk3/acs/pickup_daemon.h

@@ -0,0 +1,98 @@
+// This file holds the main loop responsible for making sure clients get
+//  all the information they should have.
+
+
+script PICKUP_MAINLOOP open
+{
+    // We're just interested in the side effect.
+    Pickup_IsZandronum();
+    IsServer = true;
+
+    while (true)
+    {
+        Pickup_UpdateClients();
+        Pickup_ActuallySend();
+        Delay(1);
+    }
+}
+
+script PICKUP_ENTER enter
+{
+    int pln = PlayerNumber();
+    Pickup_ForceUpdateClient(pln);
+
+    while (true)
+    {
+        Pickup_SetData(pln, S2C_D_HEALTH,       GetActorProperty(0, APROP_Health));
+        Pickup_SetData(pln, S2C_D_CLASSNUM,     Pickup_ClassNumber(0));
+        Pickup_SetData(pln, S2C_D_CLIP,         CheckInventory("Clip"));
+        Pickup_SetData(pln, S2C_D_SHELL,        CheckInventory("Shell"));
+        Pickup_SetData(pln, S2C_D_ROCKET,       CheckInventory("RocketAmmo"));
+        Pickup_SetData(pln, S2C_D_CELL,         CheckInventory("Cell"));
+        Delay(1);
+    }
+}
+
+script PICKUP_DISCONNECT (int pln) disconnect
+{
+    Pickup_ClearData(pln);
+    Pickup_ForceUpdateClient(pln);
+}
+
+
+
+script PICKUP_OPEN_CLIENT open clientside
+{
+    int cpln = ConsolePlayerNumber();
+    int oldPickupState;
+    int pickupState = -1;
+
+    while (true)
+    {
+        int time = Timer();
+        int i;
+
+// Debug shit, comment out whenever
+//*
+        SetHudSize(640, 480, 1);
+        SetFont("CONFONT");
+        for (i = 0; i < S2C_DATACOUNT; i++)
+        {
+            HudMessage(d:i, s:": ", d:SToC_ClientData[cpln][i];
+                HUDMSG_PLAIN, 12000 + (i * 4), CR_WHITE, 560.1, 60.0 + (12.0 * i), 0.25);
+
+            if (SToC_LastReceiveTime[cpln][i] - 1 == time)
+            {
+                HudMessage(s:"Ping!";
+                    HUDMSG_FADEOUT, 12001 + (i * 4), CR_GREEN,     550.2, 60.0 + (12.0 * i), 0.0, 1.0);
+                HudMessage(s:"Ping!";
+                    HUDMSG_PLAIN,   12002 + (i * 4), CR_DARKGRAY, 550.2, 60.0 + (12.0 * i), 1.0);
+            }
+        }
+// */
+        oldPickupState = pickupState;
+
+        if (Pickup_IsZandronum())
+        {
+            pickupState = GetCVar("switchonpickup");
+        }
+        else
+        {
+            pickupState = cond(GetCvar("neverswitchonpickup"), 0, 2);
+        }
+
+        if ((pickupState != oldPickupState) || (time % 350 == 0))
+        {
+            RequestScriptPuke(PICKUP_WEAPONSWITCH, pickupState,0,0);
+        }
+
+        Delay(1);
+    }
+}
+
+
+script PICKUP_WEAPONSWITCH (int pickupState) net
+{
+    int pln = PlayerNumber();
+    C2S_WeaponSwitchState[pln] = pickupState;
+}

+ 137 - 0
pk3/acs/pickup_display.h

@@ -0,0 +1,137 @@
+// Pickup display stuff.
+
+// This is for display scripts. It gets set right before they get called, so
+//  it's always safe to use.
+
+#define DPASS_SLOTS 5
+
+// Item number for this pickup.
+#define DPASS_ITEMNUM       0
+
+// Previous and current class number, so the script knows if it needs to
+//  SetActorState or not.
+#define DPASS_CLASSNUM      1
+#define DPASS_OLDCLASSNUM   2
+
+// Is the item considered dropped?
+#define DPASS_DROPPED       3
+
+// When a script number changes, we use this to signal to it that it should do
+//  cleanup.
+#define DPASS_DOCLEANUP     4
+
+int DISP_ScriptArgs[DPASS_SLOTS];
+
+
+
+// Copypasta!
+function int Pickup_IsDisplayScripted(int index, int classNum)
+{
+    int i;
+
+    for (i = 0; i < DISP_SCRIPTEDCOUNT; i++)
+    {
+        int checkIndex = DISP_ScriptedDisplays[i][DISP_S_ITEMNUM];
+        int checkClass = DISP_ScriptedDisplays[i][DISP_S_CLASSNUM];
+
+        if (index == checkIndex && classNum == checkClass)
+        {
+            return i;
+        }
+    }
+
+    return -1;
+}
+    
+
+
+
+script PICKUP_DISPLAY (int index, int dropped) clientside
+{
+    int cpln     = ConsolePlayerNumber();
+    // Don't do shit if we're somehow running on the server.
+    if (cpln == -1) { terminate; }
+
+    // Only accept the first one of these. That way, the next fifty billion
+    //  times the server polls the item, nothing happens and our item script
+    //  doesn't break.
+    if (CheckInventory("Class_KickedToClientside")) { terminate; }
+
+    // Intentionally break sync between client and server for display shit.
+    //  This is kinda voodoo magic.
+    GiveInventory("Class_KickedToClientside", 1);
+
+    // Default state to go to.
+    SetActorState(0, "Unknown");
+
+    int oldClassNum;
+    int classNum = -1;
+    int oldScript;
+    int scriptIndex = -1;
+    int snum, arg1, arg2, arg3;
+
+    while (true)
+    {
+        // The script doesn't auto-terminate on pickup, so we need to handle
+        //  that ourselves.
+        if (ClassifyActor(0) & ACTOR_WORLD) { terminate; }
+
+        oldClassNum = classNum;
+        classNum = SToC_ClientData[cpln][S2C_D_CLASSNUM];
+
+        // If class number changes, try to go back to the default display state.
+        if (oldClassNum != classNum)
+        {
+            SetActorProperty(0, APROP_RenderStyle, STYLE_Normal);
+            SetActorProperty(0, APROP_Alpha,       1.0);
+        }
+
+        oldScript = scriptIndex;
+        scriptIndex = Pickup_IsDisplayScripted(index, classNum);
+
+        Disp_ScriptArgs[DPASS_ITEMNUM]      = index;
+        Disp_ScriptArgs[DPASS_CLASSNUM]     = classNum;
+        Disp_ScriptArgs[DPASS_OLDCLASSNUM]  = oldClassNum;
+        Disp_ScriptArgs[DPASS_DROPPED]      = dropped;
+
+        // Let the old script that we're phasing out do cleanup.
+        if ((oldScript != scriptIndex) && (oldScript != -1))
+        {
+            Disp_ScriptArgs[DPASS_DOCLEANUP] = true;
+
+            snum = DISP_ScriptedDisplays[oldScript][DISP_S_SCRIPTNUM];
+            arg1 = DISP_ScriptedDisplays[oldScript][DISP_S_ARG1];
+            arg2 = DISP_ScriptedDisplays[oldScript][DISP_S_ARG2];
+            arg3 = DISP_ScriptedDisplays[oldScript][DISP_S_ARG3];
+            ACS_ExecuteWithResult(snum, arg1, arg2, arg3);
+
+            Disp_ScriptArgs[DPASS_DOCLEANUP] = false;
+        }
+
+        // If we got a script, let it handle everything. If it wants to
+        //  SetActorState every tic, let it. I don't give a fuck!
+        if (scriptIndex != -1)
+        {
+            snum = DISP_ScriptedDisplays[scriptIndex][DISP_S_SCRIPTNUM];
+            arg1 = DISP_ScriptedDisplays[scriptIndex][DISP_S_ARG1];
+            arg2 = DISP_ScriptedDisplays[scriptIndex][DISP_S_ARG2];
+            arg3 = DISP_ScriptedDisplays[scriptIndex][DISP_S_ARG3];
+
+            ACS_ExecuteWithResult(snum, arg1, arg2, arg3);
+        }
+        else if (classNum != oldClassNum)
+        {
+            if (classNum == -1)
+            {
+                SetActorState(0, "Unknown");
+            }
+            else
+            {
+                // DISP_ClassStates is in pickup_items.h.
+                SetActorState(0, DISP_ClassStates[classNum]);
+            }
+        }
+
+        Delay(1);
+    }
+}

+ 355 - 0
pk3/acs/pickup_items.h

@@ -0,0 +1,355 @@
+// Defining pickups themselves is handled here.
+
+
+// This is, quite simply, the amount of pickup types that this ACS is equipped
+//  to handle.
+#define PICKUPCOUNT     14
+
+
+// How many items can you get from each pickup?
+#define PK_RECEIVECOUNT 1
+
+
+// Define item constants here. Because "item 0" doesn't mean shit.
+#define It_Clip         0
+#define It_ClipBox      1
+#define It_Shell        2
+#define It_ShellBox     3
+#define It_Rocket       4
+#define It_RocketBox    5
+#define It_Cell         6
+#define It_CellPack     7
+#define It_Stim         8
+#define It_HealthBonus  9
+#define It_Shotgun      10
+#define It_GreenArmor   11
+#define It_BlueArmor    12
+#define It_ArmorBonus   13
+
+
+// Here, you have the items that each pickup will give you. Each pickup can give
+//  as many as PK_RECEIVECOUNT items.
+//
+// Note that index 0 is not for class 0 in pickup_classes.h, but actually for
+//  Cl_Unknown.
+//
+// And we group by pickup and not by class because it's more useful contextually
+//  to see "oh, this is the place where everyone gets health bonuses" than "oh,
+//  he gets a soulsphere up there and a stimpack up there so this is the health
+//  bonus index I guess... or is it megasphere?".
+//
+// Talking from experience.
+
+int PKP_ReceiveItems[PICKUPCOUNT][CLASSCOUNT][PK_RECEIVECOUNT] =
+{
+    {
+        {"Clip"},
+        {"Clip"},
+        {"Shell"},
+        {"RocketAmmo"},
+        {"Cell"},
+    },
+    {
+        {"Clip"},
+        {"Clip"},
+        {"Shell"},
+        {"RocketAmmo"},
+        {"Cell"},
+    },
+    {
+        {"Shell"},
+        {"Clip"},
+        {"Shell"},
+        {"RocketAmmo"},
+        {"Cell"},
+    },
+    {
+        {"Shell"},
+        {"Clip"},
+        {"Shell"},
+        {"RocketAmmo"},
+        {"Cell"},
+    },
+    {
+        {"RocketAmmo"},
+        {"Clip"},
+        {"Shell"},
+        {"RocketAmmo"},
+        {"Cell"},
+    },
+    {
+        {"RocketAmmo"},
+        {"Clip"},
+        {"Shell"},
+        {"RocketAmmo"},
+        {"Cell"},
+    },
+    {
+        {"Cell"},
+        {"Clip"},
+        {"Shell"},
+        {"RocketAmmo"},
+        {"Cell"},
+    },
+    {
+        {"Cell"},
+        {"Clip"},
+        {"Shell"},
+        {"RocketAmmo"},
+        {"Cell"},
+    },
+    {
+        {"BaseHealth"},
+        {"BaseHealth"},
+        {"BaseHealth"},
+        {"BaseHealth"},
+        {"BaseHealth"},
+    },
+    {
+        {"DakkaHealthBonus"},
+        {"DakkaHealthBonus"},
+        {"DakkaHealthBonus"},
+        {"DakkaHealthBonus"},
+        {"DakkaHealthBonus"},
+    },
+    {
+        {"Shotgun"},
+        {"Shotgun"},
+        {"SuperShotgun"},
+        {"SuperShotgun"},
+        {"SuperShotgun"},
+    },
+    {
+        {"GreenArmor"},
+        {"GreenArmor"},
+        {"GreenArmor"},
+        {"GreenArmor"},
+        {"GreenArmor"},
+    },
+    {
+        {"BlueArmor"},
+        {"BlueArmor"},
+        {"BlueArmor"},
+        {"BlueArmor"},
+        {"BlueArmor"},
+    },
+    {
+        {"ArmorBonus"},
+        {"ArmorBonus"},
+        {"ArmorBonus"},
+        {"ArmorBonus"},
+        {"ArmorBonus"},
+    },
+};
+
+
+
+// How many of those items are we going to get?
+int PKP_ReceiveCount[PICKUPCOUNT][CLASSCOUNT][PK_RECEIVECOUNT] =
+{
+    {
+        {10},
+        {10},
+        {4},
+        {1},
+        {20},
+    },
+    {
+        {50},
+        {50},
+        {20},
+        {5},
+        {100},
+    },
+    {
+        {4},
+        {10},
+        {4},
+        {1},
+        {20},
+    },
+    {
+        {20},
+        {50},
+        {20},
+        {5},
+        {100},
+    },
+    {
+        {1},
+        {10},
+        {4},
+        {1},
+        {20},
+    },
+    {
+        {5},
+        {50},
+        {20},
+        {5},
+        {100},
+    },
+    {
+        {20},
+        {10},
+        {4},
+        {1},
+        {20},
+    },
+    {
+        {100},
+        {50},
+        {20},
+        {5},
+        {100},
+    },
+    {
+        {10},
+        {10},
+        {10},
+        {10},
+        {10},
+    },
+    {
+        {1},
+        {1},
+        {1},
+        {1},
+        {1},
+    },
+    {
+        {1},
+        {1},
+        {1},
+        {1},
+        {1},
+    },
+    {
+        {1},
+        {1},
+        {1},
+        {1},
+        {1},
+    },
+    {
+        {1},
+        {1},
+        {1},
+        {1},
+        {1},
+    },
+    {
+        {1},
+        {1},
+        {1},
+        {1},
+        {1},
+    },
+};
+
+
+
+// And should this item *always* be picked up?
+// This not only forces a pickup message, but also forces consumption
+//  of the pickup. PARRAY_NOCONSUME will still block consumption, however.
+int PKP_AlwaysPickup[PICKUPCOUNT][CLASSCOUNT] =
+{
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        true,
+        true,
+        true,
+        true,
+        true,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        false,
+        false,
+        false,
+        false,
+        false,
+    },
+    {
+        true,
+        true,
+        true,
+        true,
+        true,
+    },
+};

+ 33 - 0
pk3/acs/pickup_items_ammo.h

@@ -0,0 +1,33 @@
+#define AMMOCOUNT   19
+
+int PKP_KnownAmmo[AMMOCOUNT] = 
+{
+    // DOOM
+    "Clip",
+    "Shell",
+    "RocketAmmo",
+    "Cell",
+
+    // HERETIC
+    "GoldWandAmmo",
+    "BlasterAmmo",
+    "CrossbowAmmo",
+    "PhoenixRodAmmo",
+    "SkullRodAmmo",
+    "MaceAmmo",
+
+    // HEXEN
+    "Mana1",
+    "Mana2",
+
+    // STRIFE
+    "ClipOfBullets",
+    "ElectricBolts",
+    "PoisonBolts",
+    "MiniMissiles",
+    "HEGrenadeRounds",
+    "PhosphorusGrenadeRounds",
+    "EnergyPod",
+};
+
+// Yeah, that's... basically it.

+ 99 - 0
pk3/acs/pickup_items_armor.h

@@ -0,0 +1,99 @@
+#define ARMORCOUNT 9
+
+int PKP_KnownArmors[ARMORCOUNT] =
+{
+    "ArmorBonus",
+    "GreenArmor",
+    "BlueArmor",
+    "BlueArmorForMegasphere",
+    "SilverShield",
+    "EnchantedShield",
+    "LeatherArmor",
+    "MetalArmor",
+    "RedArmor",
+};
+
+// See AMODE_* for comparison modes.
+#define ARM_COMPAREMODE     0
+
+// Replace current armor, or add to it? See ATYPE_*.
+#define ARM_TYPE            1
+
+// How much armor do we get?
+#define ARM_PICKUPPOINTS    2
+
+// How much armor *can* we get?
+#define ARM_MAXPOINTS       3
+
+// How much does it protect the wearer?
+#define ARM_PROTECTION      4
+
+// When determining the worth of this armor, what method should we use?
+//
+// Note: The comparison method is taken from the armor being given, with one
+//  exception: if the player's current armor type isn't in PKP_KnownArmors,
+//  we always use AMODE_POINTS because we don't have enough info for anything
+//  else.
+
+// Just use how many armor points you currently have.
+#define AMODE_POINTS        0
+
+// If you take just enough damage to lose all your armor, how much health would
+//  be protected? Formula's <armor points> * <protection ratio>. For your
+//  current armor, the amount of armor points you have is used; for the new
+//  armor, the amount of armor points you'd end up with is used.
+//
+// Examples:
+// - Green armor has a protection value of 33.
+// - Blue armor has a protection value of 100.
+// - Enchanted shield has a protection value of 150.
+#define AMODE_PROTECTION    1
+
+// Try to replace current armor if getting the new armor would be better.
+#define ATYPE_REPLACE   0
+
+// Add to current armor if there is any.
+#define ATYPE_BONUS     1
+
+// Replicate the Quake 2 armor pickup behaviour. It's as such:
+//
+// Take the armor protection ratios of both the armor you have now, and the
+//  armor you intend to grab. Use the better of the two.
+//
+// Now with the better armor ratio, you recalculate both armors' point values
+//  as such:
+//
+// <new armor points> = <old armor points> * (<armor protect ratio> / <best protect ratio>)
+//
+// Add the adjusted points together, and that's your new armor value.
+//
+// So in Quake 2, assuming you had 50 jacket armor (the max) and grabbed a
+//  set of combat armor:
+//
+// Jacket armor (current): 30% protection, 50 armor.
+// Combat armor (pickup):  60% protection, 50 armor.
+//
+// Best armor ratio: 60%.
+//
+// Jacket armor points, adjusted: 50 * (30% / 60%) = 25
+// Combat armor points, adjusted: 50 * (60% / 60%) = 50
+//
+// End armor result: Combat armor (60% protection) at 75 points
+//
+// If the armor you currently have is unknown, just do ATYPE_REPLACE instead.
+#define ATYPE_QUAKE2    2
+
+
+int PKP_ArmorData[ARMORCOUNT][5] =
+{
+    {AMODE_POINTS,  ATYPE_BONUS,    1,      200,    0.33335},
+    {AMODE_POINTS,  ATYPE_REPLACE,  100,    100,    0.33335},
+    {AMODE_POINTS,  ATYPE_REPLACE,  200,    200,    0.5},
+    {AMODE_POINTS,  ATYPE_REPLACE,  200,    200,    0.5},
+    {AMODE_POINTS,  ATYPE_REPLACE,  100,    100,    0.5},
+    {AMODE_POINTS,  ATYPE_REPLACE,  200,    200,    0.75},
+    {AMODE_POINTS,  ATYPE_REPLACE,  100,    100,    0.33335},
+    {AMODE_POINTS,  ATYPE_REPLACE,  200,    200,    0.5},
+    {AMODE_POINTS,  ATYPE_REPLACE,  200,    200,    0.66666},
+};
+

+ 180 - 0
pk3/acs/pickup_items_health.h

@@ -0,0 +1,180 @@
+// Get healed.
+// Everything related to defining health pickups is in here. You *must* add
+//  any health pickup stuff in here, or the pickup system won't know how to
+//  handle it.
+
+#define HEALTHCOUNT 6
+
+// NOTE: These health items don't have to actually exist!
+//  So long as they're defined here, the pickup system will know what
+//  to do with them.
+
+int PKP_HealthTypes[HEALTHCOUNT] =
+{
+    "BaseHealth",
+    "HealthBonus",
+    "Medikit",
+    "DakkaHealthBonus",
+    "DakkaSoulsphere",
+    "DakkaMegasphere",
+};
+
+// Now we define how each health pickup actually works. How much health they
+//  give, at what health ranges they give them, how much health is low health,
+//  so on and so forth.
+
+// Now, since player classes can have any number of health values, there's a
+//  number of ways to specify both what health ranges we should work with,
+//  and how we should heal.
+
+// "This point does not actually exist."
+#define HSCALE_BLANK 0
+
+// Don't do anything to the given value.
+#define HSCALE_NONE  1
+
+// Add the player's max health to the given value.
+#define HSCALE_ADD   2
+
+// Multiply the given value with the player's max health.
+#define HSCALE_MULT  3
+
+
+
+// These values are for scaling healing numbers.
+
+// Heal just the specified amount of health.
+#define HTYPE_CONSTANT  0
+
+// Multiply the given healing value with the player's max health to get the
+//  actual healing value.
+#define HTYPE_PERCENT   1
+
+
+
+// Healing points define ranges in which the player will get healed for a
+//  certain amount. From a given healing points, its healing value will be
+//  used until the next highest healing point is reached, at which point its
+//  healing will be used.
+//
+// So for example:
+//
+//  {HSCALE_NONE,   300,    HTYPE_CONSTANT,     1}, // oh shit this is
+//  {HSCALE_NONE,   200,    HTYPE_CONSTANT,     2}, // basically dakka's
+//  {HSCALE_NONE,   100,    HTYPE_CONSTANT,     3}, // health bonus values
+//  {HSCALE_NONE,    50,    HTYPE_CONSTANT,     4}, // oh noooooooooooooo
+//
+//  From   0 to  50 HP, you get healed for 4 HP.
+//  From  50 to 100 HP, you get healed for 3 HP.
+//  From 100 to 200 HP, you get healed for 2 HP.
+//  From 200 to 300 HP, you get healed for 1 HP.
+//
+//  Above 300 HP, you don't get healed.
+
+// How many healing points do we want to work with?
+#define HPOINT_COUNT    4
+
+// This defines the point at which its healing applies. 
+#define HPOINT_BASE         1
+
+// This defines how to modify that base value.
+//  Goes by the HSCALE_* constants.
+#define HPOINT_BASESCALAR   0
+
+// This defines how much to heal by...
+#define HPOINT_HEAL         3   
+
+// and this defines how to modify that.
+//  Goes by the HTYPE_* constants.
+#define HPOINT_HEALSCALAR   2
+
+int PKP_HealingPoints[HEALTHCOUNT][HPOINT_COUNT][4] =
+{
+    // Base health (up to class max health)
+    {
+        {HSCALE_ADD,    0,      HTYPE_CONSTANT,     1},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+    },
+
+    // Health bonus (up to class max health +100)
+    {
+        {HSCALE_ADD,    100,    HTYPE_CONSTANT,     1},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+    },
+
+    // Medikit (up to class max health, below 25 gives low message)
+    {
+        {HSCALE_ADD,    0,      HTYPE_CONSTANT,     1},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+    },
+
+    // Dakka health bonus (4hp to 50, 3hp to 100, 2hp to 200, 1hp to 300)
+    {
+        {HSCALE_MULT,   3.0,    HTYPE_CONSTANT,     1},
+        {HSCALE_MULT,   2.0,    HTYPE_CONSTANT,     2},
+        {HSCALE_MULT,   1.0,    HTYPE_CONSTANT,     3},
+        {HSCALE_MULT,   0.5,    HTYPE_CONSTANT,     4},
+    },
+
+    // Dakka soulsphere (2hp to 200, 1hp to 300)
+    {
+        {HSCALE_MULT,   3.0,    HTYPE_CONSTANT,     1},
+        {HSCALE_MULT,   2.0,    HTYPE_CONSTANT,     2},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+    },
+
+    // Dakka megasphere (1hp to 300)
+    {
+        {HSCALE_MULT,   3.0,    HTYPE_CONSTANT,     1},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+        {HSCALE_BLANK,  0,      0,                  0},
+    },
+};
+
+
+
+// For any health item that defines low health messages, this is where you want
+//  to be. The low health value uses the same scaling system as the base health
+//  values in HealingPoints.
+
+// Base healing value.
+#define HLOW_BASE       0
+
+// Scalar, using HSCALE_* constants.
+#define HLOW_SCALAR     1
+
+int PKP_LowHealthValues[HEALTHCOUNT][2] =
+{
+    {0,  HSCALE_BLANK},
+    {0,  HSCALE_BLANK},
+    {25, HSCALE_NONE},
+    {0,  HSCALE_BLANK},
+    {0,  HSCALE_BLANK},
+    {0,  HSCALE_BLANK},
+};
+
+
+
+// Low health messages override any message that would normally be displayed.
+//  In case of conflicts, last one evaluated takes precedence.
+//
+// If picking up health gets you blank messages occasionally, this is
+//  probably why.
+
+int PKP_LowHealthMessages[HEALTHCOUNT] =
+{
+    "",
+    "",
+    "$GOTMEDINEED",
+    "",
+    "",
+    "",
+};

+ 330 - 0
pk3/acs/pickup_items_pickup.h

@@ -0,0 +1,330 @@
+// Pickup messages. Get some.
+//
+// I'm tempted to add pickup_cl_moremessages to this, but no.
+//
+// You want that, you can add it yourself. 
+// I'll try to make that as easy as possible.
+
+int PKP_Messages[PICKUPCOUNT][CLASSCOUNT] = 
+{
+    {
+        "Picked up a clip.",
+        "Picked up a clip.",
+        "Picked up 4 shotgun shells.",
+        "Picked up a rocket.",
+        "Picked up an energy cell.",
+    },
+    {
+        "Picked up a box of bullets.",
+        "Picked up a box of bullets.",
+        "Picked up a box of shotgun shells.",
+        "Picked up a box of rockets.",
+        "Picked up an energy cell pack.",
+    },
+    {
+        "Picked up 4 shotgun shells.",
+        "Picked up a clip.",
+        "Picked up 4 shotgun shells.",
+        "Picked up a rocket.",
+        "Picked up an energy cell.",
+    },
+    {
+        "Picked up a box of shotgun shells.",
+        "Picked up a box of bullets.",
+        "Picked up a box of shotgun shells.",
+        "Picked up a box of rockets.",
+        "Picked up an energy cell pack.",
+    },
+    {
+        "Picked up a rocket.",
+        "Picked up a clip.",
+        "Picked up 4 shotgun shells.",
+        "Picked up a rocket.",
+        "Picked up an energy cell.",
+    },
+    {
+        "Picked up a box of rockets.",
+        "Picked up a box of bullets.",
+        "Picked up a box of shotgun shells.",
+        "Picked up a box of rockets.",
+        "Picked up an energy cell pack.",
+    },
+    {
+        "Picked up an energy cell.",
+        "Picked up a clip.",
+        "Picked up 4 shotgun shells.",
+        "Picked up a rocket.",
+        "Picked up an energy cell.",
+    },
+    {
+        "Picked up an energy cell pack.",
+        "Picked up a box of bullets.",
+        "Picked up a box of shotgun shells.",
+        "Picked up a box of rockets.",
+        "Picked up an energy cell pack.",
+    },
+    {
+        "$GOTSTIM",
+        "$GOTSTIM",
+        "$GOTSTIM",
+        "$GOTSTIM",
+        "$GOTSTIM",
+    },
+    {
+        "$GOTHTHBONUS",
+        "$GOTHTHBONUS",
+        "$GOTHTHBONUS",
+        "$GOTHTHBONUS",
+        "$GOTHTHBONUS",
+    },
+    {
+        "$GOTSHOTGUN",
+        "$GOTSHOTGUN",
+        "$GOTSHOTGUN2",
+        "$GOTSHOTGUN2",
+        "$GOTSHOTGUN2",
+    },
+    {
+        "$GOTARMOR",
+        "$GOTARMOR",
+        "$GOTARMOR",
+        "$GOTARMOR",
+        "$GOTARMOR",
+    },
+    {
+        "$GOTMEGA",
+        "$GOTMEGA",
+        "$GOTMEGA",
+        "$GOTMEGA",
+        "$GOTMEGA",
+    },
+    {
+        "$GOTARMBONUS",
+        "$GOTARMBONUS",
+        "$GOTARMBONUS",
+        "$GOTARMBONUS",
+        "$GOTARMBONUS",
+    },
+};
+
+
+
+// What pickup flash should the item use? This is only used if the pickup
+//  message isn't entirely scripted. Array entries are in the order:
+//
+// {red [0-255], green [0-255], blue [0-255], alpha [0-1.0], duration [seconds, fixed]}
+//
+// Normal pickup flash is {255, 255, 64, 0.1, 0.25}.
+
+#define PFLASH_RED      0
+#define PFLASH_GREEN    1
+#define PFLASH_BLUE     2
+#define PFLASH_ALPHA    3
+#define PFLASH_TIME     4
+
+int PKP_PickupFlashes[PICKUPCOUNT][CLASSCOUNT][5] =
+{
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+    {
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+        {255, 255, 64, 0.1, 0.25},
+    },
+};
+
+
+
+// Pickup sound? Not much to say here.
+int PKP_PickupSounds[PICKUPCOUNT][CLASSCOUNT] =
+{
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/w_pkup",
+        "misc/w_pkup",
+        "misc/w_pkup",
+        "misc/w_pkup",
+        "misc/w_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+    {
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+        "misc/i_pkup",
+    },
+};

+ 133 - 0
pk3/acs/pickup_items_weapons.h

@@ -0,0 +1,133 @@
+#define WEAPONCOUNT    48
+#define REPLCOUNT   3
+
+#define WEP_NAME    0
+#define WEP_AMMO1   1
+#define WEP_AMMO2   2
+
+
+// This should be fairly self-explanatory.
+
+int PKP_KnownGuns[WEAPONCOUNT][3] =
+{
+//  Name                        Ammo 1                      Ammo 2
+//
+    // DOOM                                                 
+    {"Fist",                    "",                         ""},
+    {"Chainsaw",                "",                         ""},
+    {"Pistol",                  "Clip",                     ""},
+    {"Shotgun",                 "Shell",                    ""},
+    {"SuperShotgun",            "Shell",                    ""},
+    {"Chaingun",                "Clip",                     ""},
+    {"RocketLauncher",          "RocketAmmo",               ""},
+    {"PlasmaRifle",             "Cell",                     ""},
+    {"BFG9000",                 "Cell",                     ""},
+
+    // CHEX QUEST                                           
+    {"Bootspoon",               "",                         ""},
+    {"SuperBootspork",          "",                         ""},
+    {"MiniZorcher",             "Clip",                     ""},
+    {"LargeZorcher",            "Shell",                    ""},
+    {"SuperLargeZorcher",       "Shell",                    ""},
+    {"RapidZorcher",            "Clip",                     ""},
+    {"ZorchPropulsor",          "RocketAmmo",               ""},
+    {"PhasingZorcher",          "Cell",                     ""},
+    {"LAZDevice",               "Cell",                     ""},
+
+    // HERETIC                                              
+    {"Staff",                   "",                         ""},
+    {"Gauntlets",               "",                         ""},
+    {"GoldWand",                "GoldWandAmmo",             ""},
+    {"Crossbow",                "CrossbowAmmo",             ""},
+    {"Blaster",                 "BlasterAmmo",              ""},
+    {"PhoenixRod",              "PhoenixRodAmmo",           ""},
+    {"SkullRod",                "SkullRodAmmo",             ""},
+    {"Mace",                    "MaceAmmo",                 ""},
+
+    // HEXEN
+    {"FWeapFist",               "",                         ""},
+    {"CWeapMace",               "",                         ""},
+    {"MWeapWand",               "",                         ""},
+    {"FWeapAxe",                "Mana1",                    ""},
+    {"CWeapStaff",              "Mana1",                    ""},
+    {"MWeapFrost",              "Mana1",                    ""},
+    {"FWeapHammer",             "Mana2",                    ""},
+    {"CWeapFlame",              "Mana2",                    ""},
+    {"MWeapLightning",          "Mana2",                    ""},
+    {"FWeapQuietus",            "Mana1",                    "Mana2"},
+    {"CWeapWraithverge",        "Mana1",                    "Mana2"},
+    {"MWeapBloodscourge",       "Mana1",                    "Mana2"},
+
+    // STRIFE                                               
+    {"PunchDagger",             "",                         ""},
+    {"StrifeCrossbow",          "ElectricBolts",            ""},
+    {"StrifeCrossbow2",         "PoisonBolts",              ""},
+    {"AssaultGun",              "ClipOfBullets",            ""},
+    {"MiniMissileLauncher",     "MiniMissiles",             ""},
+    {"StrifeGrenadeLauncher",   "HEGrenadeRounds",          ""},
+    {"StrifeGrenadeLauncher2",  "PhosphorusGrenadeRounds",  ""},
+    {"Flamethrower",            "EnergyPod",                ""},
+    {"Mauler",                  "EnergyPod",                ""},
+    {"Mauler2",                 "EnergyPod",                ""},
+};
+
+
+
+// Copy whatever the Weapon.SelectionOrder for the weapon is into here.
+
+int PKP_WeaponPriorities[WEAPONCOUNT] =
+{
+    3700,   // Fist
+    2200,   // Chainsaw
+    1900,   // Pistol
+    1300,   // Shotgun
+    400,    // SuperShotgun
+    700,    // Chaingun
+    2500,   // RocketLauncher
+    100,    // PlasmaRifle
+    2800,   // BFG9000
+
+    3700,   // Bootspoon
+    2200,   // SuperBootspork
+    1900,   // MiniZorcher
+    1300,   // LargeZorcher
+    400,    // SuperLargeZorcher
+    700,    // RapidZorcher
+    2500,   // ZorchPropulsor
+    100,    // PhasingZorcher
+    2800,   // LAZDevice
+
+    3800,   // Staff
+    2300,   // Gauntlets
+    2000,   // GoldWand
+    800,    // Crossbow
+    500,    // Blaster
+    2600,   // PhoenixRod
+    200,    // SkullRod
+    1400,   // Mace
+
+    3400,   // FWeapFist
+    3500,   // CWeapMace
+    3600,   // MWeapWand
+    1500,   // FWeapAxe
+    1600,   // CWeapStaff
+    1700,   // MWeapFrost
+    900,    // FWeapHammer
+    1000,   // CWeapFlame
+    1100,   // MWeapLightning
+    2900,   // FWeapQuietus
+    3000,   // CWeapWraithverge
+    3100,   // MWeapBloodscourge
+
+    3900,   // PunchDagger
+    1200,   // StrifeCrossbow
+    2700,   // StrifeCrossbow2
+    600,    // AssaultGun
+    1800,   // MiniMissileLauncher
+    2400,   // StrifeGrenadeLauncher
+    3200,   // StrifeGrenadeLauncher2
+    2100,   // Flamethrower
+    300,    // Mauler
+    3300,   // Mauler2
+};
+

+ 271 - 0
pk3/acs/pickup_pickup.h

@@ -0,0 +1,271 @@
+// Lovely name, isn't it? This is where picking up shit actually happens.
+
+
+
+// This is here because in Dakka (which had a similar pickup system to this),
+//  when you run over a stack of 500 chainguns and didn't pick up any of them,
+//  things started lagging really fucking badly because it was running the
+//  pickup script 500 times a tic, for every tic you were moving over them.
+//
+// With this, if the pickup failed to do something, any future pickups on that
+//  slot for that player on that tic will just fail instantly, to same a damn
+//  lot of processor cycles.
+
+int PKP_RefusePickups[PLAYERMAX][PICKUPCOUNT];
+
+
+
+
+function int Pickup_IsScripted(int index, int classNum)
+{
+    int i;
+
+    for (i = 0; i < PK_SCRIPTEDCOUNT; i++)
+    {
+        int checkIndex = PKP_ScriptedPickups[i][PK_S_ITEMNUM];
+        int checkClass = PKP_ScriptedPickups[i][PK_S_CLASSNUM];
+
+        if (index == checkIndex && classNum == checkClass)
+        {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+
+
+function void Pickup_ItemPickup(int item, int count, int dropped)
+{
+    int oldcount = CheckInventory(item);
+    GiveInventory(item, count);
+    int newcount = CheckInventory(item);
+
+    if (newcount > oldcount)
+    {
+        PKP_ReturnArray[PARRAY_SUCCEEDED]   = true;
+        PKP_ReturnArray[PARRAY_CONSUME]     = true;
+    }
+}
+
+
+
+// We don't pass arguments to this because they're supplied through
+//  PKP_MessageData.
+function void Pickup_SendMessage(void)
+{
+    int i, arg1, arg2;
+
+    for (i = 0; i < MDATA_SLOTS; i += 2)
+    {
+        arg1 = PKP_MessageData[i];
+
+        if (i + 1 < MDATA_SLOTS) { arg2 = PKP_MessageData[i+1]; }
+        else { arg2 = 0; }
+
+        // As of Zandronum 2.1.2, Using ACS_ExecuteWithResult with arguments
+        //  between -1 and -128 will pass those numbers as 256 + <val> instead.
+        //  This is obviously not desirable.
+
+        if (arg1 < 0) { arg1 -= 128; }
+        if (arg2 < 0) { arg2 -= 128; }
+
+        if (IsZandronum && ConsolePlayerNumber() == -1)
+        {
+            ACS_ExecuteAlways(PICKUP_SHOWMESSAGE, 0, i, arg1, arg2);
+        }
+        else
+        {
+            // I don't like latency.
+            ACS_ExecuteWithResult(PICKUP_SHOWMESSAGE, i, arg1, arg2);
+        }
+    }
+}
+
+
+
+function void Pickup_HandlePickups(int index, int classNum, int dropped)
+{
+    int i;
+
+    for (i = 0; i < PK_RECEIVECOUNT; i++)
+    {
+        int item  = PKP_ReceiveItems[index][classNum+1][i];
+        int count = PKP_ReceiveCount[index][classNum+1][i];
+
+        int healIndex = Heal_HealthIndex(item);
+
+        if (healIndex != -1)
+        {
+            Heal_PickupHealth(healIndex, count);
+            continue;
+        }
+
+        int wepIndex = Weapon_WeaponIndex(item);
+
+        if (wepIndex != -1)