diff --git a/extras/acs/srb2defs.acs b/extras/acs/srb2defs.acs
index 321f1beefcaf547db1a16ef6cf45eb5926eb2c49..b09ac93fd6b77d4f9123d26d166adb819d9650d3 100644
--- a/extras/acs/srb2defs.acs
+++ b/extras/acs/srb2defs.acs
@@ -183,12 +183,69 @@
 #define SECSPAC_FloorMissile           1024      // when a projectile touches the floor of this sector
 #define SECSPAC_CeilingMissile         2048      // when a projectile touches the ceiling of this sector
 
+// Player powers
+
+#define POWER_INVULNERABILITY   0
+#define POWER_SNEAKERS          1
+#define POWER_FLASHING          2
+#define POWER_SHIELD            3
+#define POWER_TAILSFLY          4
+#define POWER_UNDERWATER        5
+#define POWER_SPACETIME         6
+#define POWER_GRAVITYBOOTS      7
+#define POWER_EMERALDS          8
+#define POWER_NIGHTS_SUPERLOOP  9
+#define POWER_NIGHTS_HELPER     10
+#define POWER_NIGHTS_LINKFREEZE 11
+#define POWER_AUTOMATICRING     12
+#define POWER_BOUNCERING        13
+#define POWER_SCATTERRING       14
+#define POWER_GRENADERING       15
+#define POWER_EXPLOSIONRING     16
+#define POWER_RAILRING          17
+
+// Player shields
+
+#define SHIELD_NONE            0
+#define SHIELD_PITY            1
+#define SHIELD_WHIRLWIND       2
+#define SHIELD_ARMAGEDDON      3
+#define SHIELD_PINK            4
+
+#define SHIELD_PROTECTFIRE     0x400
+#define SHIELD_PROTECTWATER    0x800
+#define SHIELD_PROTECTELECTRIC 0x1000
+#define SHIELD_PROTECTSPIKE    0x2000
+
+#define SHIELD_ATTRACT         (SHIELD_PITY | SHIELD_PROTECTELECTRIC)
+#define SHIELD_ELEMENTAL       (SHIELD_PITY | SHIELD_PROTECTFIRE | SHIELD_PROTECTWATER)
+#define SHIELD_FLAMEAURA       (SHIELD_PITY | SHIELD_PROTECTFIRE)
+#define SHIELD_BUBBLEWRAP      (SHIELD_PITY | SHIELD_PROTECTWATER)
+#define SHIELD_THUNDERCOIN     (SHIELD_WHIRLWIND | SHIELD_PROTECTELECTRIC)
+
+#define SHIELD_FORCE           0x100
+#define SHIELD_FIREFLOWER      0x200
+
+#define SHIELD_STACK           SHIELD_FIREFLOWER
+#define SHIELD_NOSTACK         (~SHIELD_STACK)
+
+#define SHIELD_FORCEHP         0xFF
+
 // Teams
 
 #define NO_TEAM                 0
 #define TEAM_RED                1
 #define TEAM_BLUE               2
 
+// Weapons
+
+#define WEAPON_AUTO    0
+#define WEAPON_BOUNCE  1
+#define WEAPON_SCATTER 2
+#define WEAPON_GRENADE 3
+#define WEAPON_EXPLODE 4
+#define WEAPON_RAIL    5
+
 // Bot types
 
 #define BOT_NONE    0
diff --git a/extras/acs/srb2special.acs b/extras/acs/srb2special.acs
index 631c736e1068fb8522904314a789a5802e80eae9..17b64d09afdc7187b8f4ce399f594d7e5eaa463d 100644
--- a/extras/acs/srb2special.acs
+++ b/extras/acs/srb2special.acs
@@ -11,10 +11,13 @@ special
 	-11:SetThingProperty(3),
 	-100:strcmp(2,3),
 	-101:strcasecmp(2,3),
+	-120:PlayerRings(0),
+	-122:PlayerScore(0),
+	-123:PlayerSuper(0),
 	-300:CountEnemies(2),
 	-301:CountPushables(2),
-	// -302:SkinAvailable(1),
-	-303:HasUnlockable(1),
+	-302:HasUnlockable(1),
+	-303:SkinUnlocked(1),
 	-304:PlayerSkin(0),
 	-305:PlayerEmeralds(0),
 	-306:PlayerBot(0),
@@ -34,12 +37,18 @@ special
 	// -324:Thing_Spawn(1), // NOTE: would it be better to implement Spawn? (https://zdoom.org/wiki/Spawn)
 	-325:Thing_TrackAngle(4,6),
 	-326:Thing_StopTrackingAngle(1),
-	// -327:GiveShield(1,2),
-	// -328:GivePowerUp(1,2),
+	-327:CheckPowerUp(1),
+	-328:GivePowerUp(1,2),
+	-329:CheckAmmo(1),
+	-330:GiveAmmo(2),
+	-331:TakeAmmo(2),
+	-332:DoSuperTransformation(0,1),
+	-333:DoSuperDetransformation(0),
 	-500:CameraWait(1),
 	-503:SetLineRenderStyle(3),
 	-504:MapWarp(2),
 	-505:AddBot(1, 4),
+	-506:RemoveBot(1),
 	-507:ExitLevel(0,1),
 	-508:MusicPlay(1,2),
 	-509:MusicStopAll(0,1),
diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp
index 1e4303b530a38924ddc977704feb47a0a3431e0b..132066bd4313add272fa283c843d564949a8ee61 100644
--- a/src/acs/call-funcs.cpp
+++ b/src/acs/call-funcs.cpp
@@ -169,7 +169,7 @@ static bool ACS_GetSpriteFromString(const char *word, spritenum_t *type)
 
 	for (int i = 0; i < NUMSPRITES; i++)
 	{
-		if (fastncmp(word, sprnames[i], 4))
+		if (strcmp(word, sprnames[i]) == 0)
 		{
 			*type = static_cast<spritenum_t>(i);
 			return true;
@@ -1269,6 +1269,30 @@ bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM:
 	return false;
 }
 
+/*--------------------------------------------------
+	bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Returns if the activating player is super.
+--------------------------------------------------*/
+bool CallFunc_PlayerSuper(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	auto info = &static_cast<Thread *>(thread)->info;
+	bool super = false;
+
+	(void)argV;
+	(void)argC;
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		super = (info->mo->player->powers[pw_super] != 0);
+	}
+
+	thread->dataStk.push(super);
+	return false;
+}
+
 /*--------------------------------------------------
 	bool CallFunc_PlayerNumber(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
 
@@ -1501,6 +1525,33 @@ bool CallFunc_HasUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSV
 	return false;
 }
 
+/*--------------------------------------------------
+	bool CallFunc_SkinUnlocked(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Checks if a certain skin has been unlocked.
+--------------------------------------------------*/
+bool CallFunc_SkinUnlocked(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	bool unlocked = false;
+	auto info = &static_cast<Thread *>(thread)->info;
+
+	(void)argC;
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		INT32 skinNum;
+		if (ACS_GetSkinFromString(thread->scopeMap->getString( argV[0] )->str, &skinNum) == true)
+		{
+			unlocked = R_SkinUsable(info->mo->player - players, skinNum);
+		}
+	}
+
+	thread->dataStk.push(unlocked);
+	return false;
+}
+
 /*--------------------------------------------------
 	bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
 
@@ -2074,6 +2125,38 @@ bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word
 	return false;
 }
 
+/*--------------------------------------------------
+	bool CallFunc_RemoveBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Removes a bot.
+--------------------------------------------------*/
+bool CallFunc_RemoveBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	(void)argC;
+
+	int playernum = argV[0];
+
+	if (playernum < 0 || playernum >= MAXPLAYERS)
+	{
+		CONS_Alert(CONS_WARNING, "RemoveBot player %d out of range (expected 0 - %d).\n", playernum, MAXPLAYERS-1);
+	}
+	else if (playeringame[playernum])
+	{
+		if (players[playernum].bot == BOT_NONE)
+		{
+			CONS_Alert(CONS_WARNING, "RemoveBot cannot remove human\n");
+		}
+		else
+		{
+			players[playernum].removing = true;
+		}
+	}
+
+	thread->dataStk.push(0);
+
+	return false;
+}
+
 /*--------------------------------------------------
 	bool CallFunc_ExitLevel(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
 
@@ -3157,9 +3240,7 @@ bool CallFunc_GetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, A
 #define PROP_SPR(x, y) \
 	case x: \
 	{ \
-		char crunched[5] = {0}; \
-		strncpy(crunched, sprnames[ mobj->y ], 4); \
-		value = static_cast<INT32>( ~env->getString( crunched )->idx ); \
+		value = static_cast<INT32>( ~env->getString( sprnames[ mobj->y ] )->idx ); \
 		break; \
 	}
 
@@ -3560,6 +3641,442 @@ bool CallFunc_SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, A
 	return false;
 }
 
+enum
+{
+	POWER_INVULNERABILITY,
+	POWER_SNEAKERS,
+	POWER_FLASHING,
+	POWER_SHIELD,
+	POWER_TAILSFLY,
+	POWER_UNDERWATER,
+	POWER_SPACETIME,
+	POWER_GRAVITYBOOTS,
+	POWER_EMERALDS,
+	POWER_NIGHTS_SUPERLOOP,
+	POWER_NIGHTS_HELPER,
+	POWER_NIGHTS_LINKFREEZE,
+	POWER_AUTOMATICRING,
+	POWER_BOUNCERING,
+	POWER_SCATTERRING,
+	POWER_GRENADERING,
+	POWER_EXPLOSIONRING,
+	POWER_RAILRING,
+	POWER__MAX
+};
+
+/*--------------------------------------------------
+	bool CallFunc_CheckPowerUp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Checks the amount of the activator's given power-up.
+--------------------------------------------------*/
+bool CallFunc_CheckPowerUp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	(void)argC;
+
+	INT32 value = 0;
+
+	auto info = &static_cast<Thread *>(thread)->info;
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		INT32 property = argV[0];
+
+		player_t *player = info->mo->player;
+
+#define POWER(x, y) \
+	case x: \
+	{ \
+		value = player->powers[y]; \
+		break; \
+	}
+
+#define WEAPON(x, y) \
+	case x: \
+	{ \
+		value = (player->ringweapons & y) != 0; \
+		break; \
+	}
+
+		switch (property)
+		{
+			POWER(POWER_INVULNERABILITY, pw_invulnerability)
+			POWER(POWER_SNEAKERS, pw_sneakers)
+			POWER(POWER_FLASHING, pw_flashing)
+			POWER(POWER_SHIELD, pw_shield)
+			POWER(POWER_TAILSFLY, pw_tailsfly)
+			POWER(POWER_UNDERWATER, pw_underwater)
+			POWER(POWER_SPACETIME, pw_spacetime)
+			POWER(POWER_GRAVITYBOOTS, pw_gravityboots)
+			POWER(POWER_EMERALDS, pw_emeralds)
+			POWER(POWER_NIGHTS_SUPERLOOP, pw_nights_superloop)
+			POWER(POWER_NIGHTS_HELPER, pw_nights_helper)
+			POWER(POWER_NIGHTS_LINKFREEZE, pw_nights_linkfreeze)
+			WEAPON(POWER_AUTOMATICRING, WEP_AUTO)
+			WEAPON(POWER_BOUNCERING, WEP_BOUNCE)
+			WEAPON(POWER_SCATTERRING, WEP_SCATTER)
+			WEAPON(POWER_GRENADERING, WEP_GRENADE)
+			WEAPON(POWER_EXPLOSIONRING, WEP_EXPLODE)
+			WEAPON(POWER_RAILRING, WEP_RAIL)
+			default:
+			{
+				CONS_Alert(CONS_WARNING, "CheckPowerUp type %d out of range (expected 0 - %d).\n", property, POWER__MAX-1);
+				break;
+			}
+		}
+
+#undef POWER
+#undef WEAPON
+	}
+
+	thread->dataStk.push(value);
+
+	return false;
+}
+
+/*--------------------------------------------------
+	static void ACS_SetPowerUp(player_t *player, INT32 property, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Helper for CallFunc_GivePowerUp.
+--------------------------------------------------*/
+static void ACS_SetPowerUp(player_t *player, INT32 property, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+#define POWER(x, y, z) \
+	case x: \
+	{ \
+		if (argC >= 2) \
+			player->powers[y] = argV[1]; \
+		else \
+			player->powers[y] = z; \
+		break; \
+	}
+
+#define WEAPON(x, y) \
+	case x: \
+	{ \
+		if (argC >= 2 && (argV[1] > 0)) \
+			player->ringweapons |= y; \
+		else \
+			player->ringweapons &= ~y; \
+		break; \
+	}
+
+	switch (property)
+	{
+		POWER(POWER_INVULNERABILITY, pw_invulnerability, invulntics)
+		POWER(POWER_SNEAKERS, pw_sneakers, sneakertics)
+		POWER(POWER_FLASHING, pw_flashing, flashingtics)
+		POWER(POWER_SHIELD, pw_shield, 0)
+		POWER(POWER_TAILSFLY, pw_tailsfly, tailsflytics)
+		POWER(POWER_UNDERWATER, pw_underwater, underwatertics)
+		POWER(POWER_SPACETIME, pw_spacetime, spacetimetics)
+		POWER(POWER_GRAVITYBOOTS, pw_gravityboots, 20*TICRATE)
+		POWER(POWER_EMERALDS, pw_emeralds, 0)
+		POWER(POWER_NIGHTS_SUPERLOOP, pw_nights_superloop, 0)
+		POWER(POWER_NIGHTS_HELPER, pw_nights_helper, 0)
+		POWER(POWER_NIGHTS_LINKFREEZE, pw_nights_linkfreeze, 0)
+		WEAPON(POWER_AUTOMATICRING, WEP_AUTO)
+		WEAPON(POWER_BOUNCERING, WEP_BOUNCE)
+		WEAPON(POWER_SCATTERRING, WEP_SCATTER)
+		WEAPON(POWER_GRENADERING, WEP_GRENADE)
+		WEAPON(POWER_EXPLOSIONRING, WEP_EXPLODE)
+		WEAPON(POWER_RAILRING, WEP_RAIL)
+		default:
+		{
+			CONS_Alert(CONS_WARNING, "GivePowerUp type %d out of range (expected 0 - %d).\n", property, POWER__MAX-1);
+			break;
+		}
+	}
+
+	// Give the player a shield
+	if (property == POWER_SHIELD && player->powers[pw_shield] != SH_NONE)
+	{
+		P_SpawnShieldOrb(player);
+	}
+
+#undef POWER
+#undef AMMO
+}
+
+/*--------------------------------------------------
+	bool CallFunc_GivePowerUp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Awards a power-up to the activator.
+		Like GiveInventory, if this is called with no activator, this awards the power-up to all players.
+--------------------------------------------------*/
+bool CallFunc_GivePowerUp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	auto info = &static_cast<Thread *>(thread)->info;
+
+	INT32 property = argV[0];
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		ACS_SetPowerUp(info->mo->player, property, argV, argC);
+	}
+	else
+	{
+		for (UINT8 i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i])
+				ACS_SetPowerUp(&players[i], property, argV, argC);
+		}
+	}
+
+	thread->dataStk.push(0);
+
+	return false;
+}
+
+enum
+{
+	WEAPON_AUTO,
+	WEAPON_BOUNCE,
+	WEAPON_SCATTER,
+	WEAPON_GRENADE,
+	WEAPON_EXPLODE,
+	WEAPON_RAIL,
+	WEAPON__MAX
+};
+
+/*--------------------------------------------------
+	bool CallFunc_CheckAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Checks the ammo of the activator's weapon.
+--------------------------------------------------*/
+bool CallFunc_CheckAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	(void)argC;
+
+	INT32 value = 0;
+
+	auto info = &static_cast<Thread *>(thread)->info;
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		INT32 weapon = argV[0];
+
+		player_t *player = info->mo->player;
+
+#define AMMO(x, y) \
+	case x: \
+	{ \
+		value = player->powers[y]; \
+		break; \
+	}
+
+		switch (weapon)
+		{
+			AMMO(WEAPON_AUTO, pw_automaticring)
+			AMMO(WEAPON_BOUNCE, pw_bouncering)
+			AMMO(WEAPON_SCATTER, pw_scatterring)
+			AMMO(WEAPON_GRENADE, pw_grenadering)
+			AMMO(WEAPON_EXPLODE, pw_explosionring)
+			AMMO(WEAPON_RAIL, pw_railring)
+			default:
+			{
+				CONS_Alert(CONS_WARNING, "GiveAmmo weapon %d out of range (expected 0 - %d).\n", weapon, WEAPON__MAX-1);
+				break;
+			}
+		}
+
+#undef AMMO
+	}
+
+	thread->dataStk.push(value);
+
+	return false;
+}
+
+/*--------------------------------------------------
+	static void ACS_GiveAmmo(player_t *player, INT32 weapon, INT32 value)
+
+		Helper for CallFunc_GiveAmmo/CallFunc_TakeAmmo.
+--------------------------------------------------*/
+static bool ACS_GiveAmmo(player_t *player, INT32 weapon, INT32 value)
+{
+#define AMMO(x, y) \
+	case x: \
+	{ \
+		player->powers[y] += value; \
+		return true; \
+	}
+
+	switch (weapon)
+	{
+		AMMO(WEAPON_AUTO, pw_automaticring)
+		AMMO(WEAPON_BOUNCE, pw_bouncering)
+		AMMO(WEAPON_SCATTER, pw_scatterring)
+		AMMO(WEAPON_GRENADE, pw_grenadering)
+		AMMO(WEAPON_EXPLODE, pw_explosionring)
+		AMMO(WEAPON_RAIL, pw_railring)
+	}
+
+#undef AMMO
+
+	return false;
+}
+
+/*--------------------------------------------------
+	bool CallFunc_GiveAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Gives ammo to the activator.
+		Like GiveInventory, if this is called with no activator, this awards ammo to all players.
+--------------------------------------------------*/
+bool CallFunc_GiveAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	auto info = &static_cast<Thread *>(thread)->info;
+
+	INT32 weapon = argV[0];
+	INT32 value = argC >= 2 ? (argV[1]) : 0;
+
+	bool fail = false;
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		fail = !ACS_GiveAmmo(info->mo->player, weapon, value);
+	}
+	else
+	{
+		for (UINT8 i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i])
+			{
+				if (!ACS_GiveAmmo(&players[i], weapon, value))
+				{
+					fail = true;
+					break;
+				}
+			}
+		}
+	}
+
+	if (fail)
+	{
+		CONS_Alert(CONS_WARNING, "GiveAmmo weapon %d out of range (expected 0 - %d).\n", weapon, WEAPON__MAX-1);
+	}
+
+	thread->dataStk.push(0);
+
+	return false;
+}
+
+/*--------------------------------------------------
+	bool CallFunc_TakeAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Takes ammo from the activator.
+		Like TakeInventory, if this is called with no activator, this takes ammo from all players.
+--------------------------------------------------*/
+bool CallFunc_TakeAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	auto info = &static_cast<Thread *>(thread)->info;
+
+	INT32 weapon = argV[0];
+	INT32 value = argC >= 2 ? (argV[1]) : 0;
+
+	bool fail = false;
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		fail = !ACS_GiveAmmo(info->mo->player, weapon, -value);
+	}
+	else
+	{
+		for (UINT8 i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i])
+			{
+				if (!ACS_GiveAmmo(&players[i], weapon, -value))
+				{
+					fail = true;
+					break;
+				}
+			}
+		}
+	}
+
+	if (fail)
+	{
+		CONS_Alert(CONS_WARNING, "TakeAmmo weapon %d out of range (expected 0 - %d).\n", weapon, WEAPON__MAX-1);
+	}
+
+	thread->dataStk.push(0);
+
+	return false;
+}
+
+/*--------------------------------------------------
+	bool CallFunc_DoSuperTransformation(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Turns the activator super.
+--------------------------------------------------*/
+bool CallFunc_DoSuperTransformation(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	auto info = &static_cast<Thread *>(thread)->info;
+
+	bool giverings = argC >= 2 ? (argV[1] != 0) : false;
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		P_DoSuperTransformation(info->mo->player, giverings);
+	}
+	else
+	{
+		for (UINT8 i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i])
+				P_DoSuperTransformation(&players[i], giverings);
+		}
+	}
+
+	thread->dataStk.push(0);
+
+	return false;
+}
+
+/*--------------------------------------------------
+	bool CallFunc_DoSuperTransformation(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+
+		Detransforms the activator if super.
+--------------------------------------------------*/
+bool CallFunc_DoSuperDetransformation(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
+{
+	(void)argV;
+	(void)argC;
+
+	auto info = &static_cast<Thread *>(thread)->info;
+
+	if ((info != NULL)
+		&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
+		&& (info->mo->player != NULL))
+	{
+		P_DoSuperDetransformation(info->mo->player);
+	}
+	else
+	{
+		for (UINT8 i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i])
+				P_DoSuperDetransformation(&players[i]);
+		}
+	}
+
+	thread->dataStk.push(0);
+
+	return false;
+}
+
 /*--------------------------------------------------
 	static angle_t ACS_GetAngle(int angle)
 
@@ -3742,7 +4259,7 @@ bool CallFunc_OppositeColor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSV
 
 	if (color < 1 || color >= numskincolors)
 	{
-		CONS_Alert(CONS_WARNING, "OppositeColor: out of range\n");
+		CONS_Alert(CONS_WARNING, "OppositeColor color %d out of range (expected 1 - %d).\n", color, numskincolors-1);
 	}
 	else
 	{
diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp
index 5d29182572177a0fbf7ac70b391f79f7f47a2d5f..7fe0269b8fe2d0f11cbc3ce39d4e8105576648d8 100644
--- a/src/acs/call-funcs.hpp
+++ b/src/acs/call-funcs.hpp
@@ -63,6 +63,7 @@ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM
 bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+bool CallFunc_PlayerSuper(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_PlayerNumber(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_ActivatorTID(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_EndLog(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
@@ -73,6 +74,7 @@ bool CallFunc_strcasecmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::
 bool CallFunc_CountEnemies(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_CountPushables(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_HasUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+bool CallFunc_SkinUnlocked(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_PlayerExiting(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
@@ -96,6 +98,7 @@ bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV,
 
 bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+bool CallFunc_RemoveBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_ExitLevel(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_MusicPlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_MusicStopAll(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
@@ -111,6 +114,16 @@ bool CallFunc_SetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV,
 bool CallFunc_GetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 
+bool CallFunc_CheckPowerUp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+bool CallFunc_GivePowerUp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+
+bool CallFunc_CheckAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+bool CallFunc_GiveAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+bool CallFunc_TakeAmmo(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+
+bool CallFunc_DoSuperTransformation(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+bool CallFunc_DoSuperDetransformation(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
+
 bool CallFunc_Sin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_Cos(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
 bool CallFunc_Tan(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp
index 626ad00c269d8f4bb07600493fa78fe77101c57e..f9c850c45b4e79e9d3844657751ddd043b4cba8a 100644
--- a/src/acs/environment.cpp
+++ b/src/acs/environment.cpp
@@ -96,9 +96,6 @@ Environment::Environment()
 	// Skulltag p-codes begin here
 	addCodeDataACS0(118, {"",        0, addCallFunc(CallFunc_IsNetworkGame)});
 	addCodeDataACS0(119, {"",        0, addCallFunc(CallFunc_PlayerTeam)});
-	addCodeDataACS0(120, {"",        0, addCallFunc(CallFunc_PlayerRings)});
-
-	addCodeDataACS0(122, {"",        0, addCallFunc(CallFunc_PlayerScore)});
 
 	// 136 to 137: Implemented by ACSVM
 
@@ -160,10 +157,14 @@ Environment::Environment()
 	addFuncDataACS0( 100, addCallFunc(CallFunc_strcmp));
 	addFuncDataACS0( 101, addCallFunc(CallFunc_strcasecmp));
 
+	addFuncDataACS0( 120, addCallFunc(CallFunc_PlayerRings));
+	addFuncDataACS0( 122, addCallFunc(CallFunc_PlayerScore));
+	addFuncDataACS0( 123, addCallFunc(CallFunc_PlayerSuper));
+
 	addFuncDataACS0( 300, addCallFunc(CallFunc_CountEnemies));
 	addFuncDataACS0( 301, addCallFunc(CallFunc_CountPushables));
-	// addFuncDataACS0( 302, addCallFunc(CallFunc_SkinAvailable));
-	addFuncDataACS0( 303, addCallFunc(CallFunc_HasUnlockable));
+	addFuncDataACS0( 302, addCallFunc(CallFunc_HasUnlockable));
+	addFuncDataACS0( 303, addCallFunc(CallFunc_SkinUnlocked));
 	addFuncDataACS0( 304, addCallFunc(CallFunc_PlayerSkin));
 	addFuncDataACS0( 305, addCallFunc(CallFunc_PlayerEmeralds));
 	addFuncDataACS0( 306, addCallFunc(CallFunc_PlayerBot));
@@ -183,13 +184,19 @@ Environment::Environment()
 	// addFuncDataACS0( 324, addCallFunc(CallFunc_SpawnObject));
 	addFuncDataACS0( 325, addCallFunc(CallFunc_TrackObjectAngle));
 	addFuncDataACS0( 326, addCallFunc(CallFunc_StopTrackingObjectAngle));
-	// addFuncDataACS0( 327, addCallFunc(CallFunc_GiveShield));
-	// addFuncDataACS0( 328, addCallFunc(CallFunc_GivePowerUp));
+	addFuncDataACS0( 327, addCallFunc(CallFunc_CheckPowerUp));
+	addFuncDataACS0( 328, addCallFunc(CallFunc_GivePowerUp));
+	addFuncDataACS0( 329, addCallFunc(CallFunc_CheckAmmo));
+	addFuncDataACS0( 330, addCallFunc(CallFunc_GiveAmmo));
+	addFuncDataACS0( 331, addCallFunc(CallFunc_TakeAmmo));
+	addFuncDataACS0( 332, addCallFunc(CallFunc_DoSuperTransformation));
+	addFuncDataACS0( 333, addCallFunc(CallFunc_DoSuperDetransformation));
 
 	addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait));
 	addFuncDataACS0( 503, addCallFunc(CallFunc_SetLineRenderStyle));
 	addFuncDataACS0( 504, addCallFunc(CallFunc_MapWarp));
 	addFuncDataACS0( 505, addCallFunc(CallFunc_AddBot));
+	addFuncDataACS0( 506, addCallFunc(CallFunc_RemoveBot));
 	addFuncDataACS0( 507, addCallFunc(CallFunc_ExitLevel));
 	addFuncDataACS0( 508, addCallFunc(CallFunc_MusicPlay));
 	addFuncDataACS0( 509, addCallFunc(CallFunc_MusicStopAll));