From 6d9a8bfaa5f39e7e1cf79dd76113b8b15cd90d66 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <coelckers@users.noreply.github.com>
Date: Sun, 27 Nov 2022 23:36:39 +0100
Subject: [PATCH] - scriptified the pool balls.

---
 source/common/scripting/vm/vm.h               |   3 +-
 source/core/vmexports.cpp                     |  51 ++++++-
 source/games/duke/src/actors.cpp              |  95 ------------
 source/games/duke/src/actors_d.cpp            |   8 +-
 source/games/duke/src/actors_r.cpp            |   4 -
 source/games/duke/src/funct.h                 |   1 -
 source/games/duke/src/sectors_d.cpp           |  24 ---
 source/games/duke/src/sectors_r.cpp           |  12 +-
 source/games/duke/src/spawn_d.cpp             |  17 +--
 source/games/duke/src/spawn_r.cpp             |  15 +-
 source/games/duke/src/vmexports.cpp           |  12 ++
 .../static/filter/dukelike/engine/engine.def  |   4 +
 wadsrc/static/filter/dukelike/sndinfo.txt     |   1 +
 wadsrc/static/zscript.txt                     |   1 +
 wadsrc/static/zscript/coreactor.zs            |  17 +--
 .../zscript/games/duke/actors/queball.zs      | 138 ++++++++++++++++++
 wadsrc/static/zscript/games/duke/dukeactor.zs |   2 +-
 wadsrc/static/zscript/games/duke/dukegame.zs  |  44 ++++++
 wadsrc/static/zscript/razebase.zs             |  16 ++
 19 files changed, 274 insertions(+), 191 deletions(-)
 create mode 100644 wadsrc/static/zscript/games/duke/actors/queball.zs

diff --git a/source/common/scripting/vm/vm.h b/source/common/scripting/vm/vm.h
index ef089eba5..4870f0048 100644
--- a/source/common/scripting/vm/vm.h
+++ b/source/common/scripting/vm/vm.h
@@ -632,7 +632,8 @@ struct DirectNativeDesc
 	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); }
 	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13); }
 	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13), TP(14)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13), VP(14); }
-	#undef TP
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13), TP(14), TP(15)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13), VP(14), VP(15); }
+#undef TP
 	#undef VP
 
 	template<typename T> void ValidateType() { static_assert(native_is_valid<T>::value, "Argument type is not valid as a direct native parameter or return type"); }
diff --git a/source/core/vmexports.cpp b/source/core/vmexports.cpp
index aae52b791..13031e979 100644
--- a/source/core/vmexports.cpp
+++ b/source/core/vmexports.cpp
@@ -54,6 +54,30 @@ DEFINE_ACTION_FUNCTION_NATIVE(_Raze, updatesector, Raze_updatesector)
 	ACTION_RETURN_POINTER(Raze_updatesector(x, y, s, dist));
 }
 
+DEFINE_ACTION_FUNCTION(_Raze, clipmove)
+{
+	PARAM_PROLOGUE;
+	PARAM_FLOAT(x);
+	PARAM_FLOAT(y);
+	PARAM_FLOAT(z);
+	PARAM_POINTER_NOT_NULL(s, sectortype);
+	PARAM_FLOAT(mx);
+	PARAM_FLOAT(my);
+	PARAM_FLOAT(wdist);
+	PARAM_FLOAT(cdist);
+	PARAM_FLOAT(fdist);
+	PARAM_UINT(cliptype);
+	PARAM_POINTER_NOT_NULL(coll, CollisionBase);
+	PARAM_INT(cmtn);
+	DVector3 rpos(x, y, z);
+	clipmove(rpos, &s, DVector2(mx, my), wdist, cdist, fdist, cliptype, *coll, cmtn);
+	if (numret > 0) ret[0].SetPointer(s);
+	if (numret > 1) ret[1].SetVector(rpos);
+	return min(numret, 2);
+}
+
+
+
 DEFINE_ACTION_FUNCTION_NATIVE(_Raze, SoundEnabled, SoundEnabled)
 {
 	ACTION_RETURN_INT(SoundEnabled());
@@ -795,6 +819,19 @@ DEFINE_ACTION_FUNCTION_NATIVE(DCoreActor, absangle, absangleDbl)	// should this
 	ACTION_RETURN_FLOAT(absangle(DAngle::fromDeg(a1), DAngle::fromDeg(a2)).Degrees());
 }
 
+static double Normalize180(double angle)
+{
+	return DAngle::fromDeg(angle).Normalized180().Degrees();
+}
+
+DEFINE_ACTION_FUNCTION_NATIVE(DCoreActor, Normalize180, Normalize180)
+{
+	PARAM_PROLOGUE;
+	PARAM_ANGLE(angle);
+	ACTION_RETURN_FLOAT(angle.Normalized180().Degrees());
+}
+
+
 
 DEFINE_FIELD_X(Collision, CollisionBase, type)
 DEFINE_FIELD_X(Collision, CollisionBase, exbits)
@@ -805,7 +842,7 @@ walltype* collision_getwall(CollisionBase* coll)
 	else return nullptr;
 }
 
-DEFINE_ACTION_FUNCTION_NATIVE(_Collision, hitwall, collision_getwall)
+DEFINE_ACTION_FUNCTION_NATIVE(_CollisionData, hitwall, collision_getwall)
 {
 	PARAM_SELF_STRUCT_PROLOGUE(CollisionBase);
 	ACTION_RETURN_POINTER(collision_getwall(self));
@@ -817,7 +854,7 @@ sectortype* collision_getsector(CollisionBase* coll)
 	else return nullptr;
 }
 
-DEFINE_ACTION_FUNCTION_NATIVE(_Collision, hitsector, collision_getsector)
+DEFINE_ACTION_FUNCTION_NATIVE(_CollisionData, hitsector, collision_getsector)
 {
 	PARAM_SELF_STRUCT_PROLOGUE(CollisionBase);
 	ACTION_RETURN_POINTER(collision_getsector(self));
@@ -829,7 +866,7 @@ DCoreActor* collision_getactor(CollisionBase* coll)
 	else return nullptr;
 }
 
-DEFINE_ACTION_FUNCTION_NATIVE(_Collision, hitactor, collision_getactor)
+DEFINE_ACTION_FUNCTION_NATIVE(_CollisionData, hitactor, collision_getactor)
 {
 	PARAM_SELF_STRUCT_PROLOGUE(CollisionBase);
 	ACTION_RETURN_POINTER(collision_getactor(self));
@@ -843,7 +880,7 @@ void collision_setwall(CollisionBase* coll, walltype * w)
 	coll->hitWall = w;
 }
 
-DEFINE_ACTION_FUNCTION_NATIVE(_Collision, setwall, collision_setwall)
+DEFINE_ACTION_FUNCTION_NATIVE(_CollisionData, setwall, collision_setwall)
 {
 	PARAM_SELF_STRUCT_PROLOGUE(CollisionBase);
 	PARAM_POINTER(p, walltype);
@@ -857,7 +894,7 @@ void collision_setsector(CollisionBase* coll, sectortype* s)
 	coll->hitSector = s;
 }
 
-DEFINE_ACTION_FUNCTION_NATIVE(_Collision, setsector, collision_setsector)
+DEFINE_ACTION_FUNCTION_NATIVE(_CollisionData, setsector, collision_setsector)
 {
 	PARAM_SELF_STRUCT_PROLOGUE(CollisionBase);
 	PARAM_POINTER(p, sectortype);
@@ -871,7 +908,7 @@ void collision_setactor(CollisionBase* coll, DCoreActor* a)
 	coll->hitActor = a;
 }
 
-DEFINE_ACTION_FUNCTION_NATIVE(_Collision, setactor, collision_setactor)
+DEFINE_ACTION_FUNCTION_NATIVE(_CollisionData, setactor, collision_setactor)
 {
 	PARAM_SELF_STRUCT_PROLOGUE(CollisionBase);
 	PARAM_POINTER(p, DCoreActor);
@@ -885,7 +922,7 @@ void collision_setvoid(CollisionBase* coll)
 	coll->hitActor = nullptr;
 }
 
-DEFINE_ACTION_FUNCTION_NATIVE(_Collision, setvoid, collision_setvoid)
+DEFINE_ACTION_FUNCTION_NATIVE(_CollisionData, setvoid, collision_setvoid)
 {
 	PARAM_SELF_STRUCT_PROLOGUE(CollisionBase);
 	collision_setvoid(self);
diff --git a/source/games/duke/src/actors.cpp b/source/games/duke/src/actors.cpp
index 77b757e92..e16744ce0 100644
--- a/source/games/duke/src/actors.cpp
+++ b/source/games/duke/src/actors.cpp
@@ -681,101 +681,6 @@ void rpgexplode(DDukeActor *actor, int hit, const DVector3 &pos, int EXPLOSION2,
 //
 //---------------------------------------------------------------------------
 
-bool queball(DDukeActor *actor, int pocket, int queball, int stripeball)
-{
-	if(actor->vel.X != 0)
-	{
-		DukeStatIterator it(STAT_DEFAULT);
-		while (auto aa = it.Next())
-		{
-			double dist = (aa->spr.pos.XY() - actor->spr.pos.XY()).Length();
-			if (aa->spr.picnum == pocket && dist < 3.25)
-			{
-				actor->Destroy();
-				return false;
-			}
-		}
-
-		Collision coll;
-		auto sect = actor->sector();
-		auto pos = actor->spr.pos;
-		auto move = actor->spr.Angles.Yaw.ToVector() * actor->vel.X * 0.5;
-		int j = clipmove(pos, &sect, move, 1.5, 4., 4., CLIPMASK1, coll);
-		actor->spr.pos = pos;;
-		actor->setsector(sect);
-
-		if (j == kHitWall)
-		{
-			auto ang = coll.hitWall->delta().Angle();
-			actor->spr.Angles.Yaw = ang * 2 - actor->spr.Angles.Yaw;
-		}
-		else if (j == kHitSprite)
-		{
-			fi.checkhitsprite(actor, coll.actor());
-		}
-
-		actor->vel.X -= 1/16.;
-		if(actor->vel.X < 0) actor->vel.X = 0;
-		if (actor->spr.picnum == stripeball)
-		{
-			actor->spr.cstat = CSTAT_SPRITE_BLOCK_ALL;
-			actor->spr.cstat |= (CSTAT_SPRITE_XFLIP | CSTAT_SPRITE_YFLIP) & ESpriteFlags::FromInt(int(actor->vel.X * 16.)); // special hack edition...
-		}
-	}
-	else
-	{
-		double x;
-		int p = findplayer(actor, &x);
-
-		if (x < 99.75)
-		{
-			//						if(actor->spr.pal == 12)
-			{
-				auto delta = absangle(ps[p].GetActor()->spr.Angles.Yaw, (actor->spr.pos.XY() - ps[p].GetActor()->spr.pos.XY()).Angle());
-				if (delta < DAngle22_5 / 2 && PlayerInput(p, SB_OPEN))
-					if (ps[p].toggle_key_flag == 1)
-					{
-						DukeStatIterator it(STAT_ACTOR);
-						DDukeActor *act2;
-						while ((act2 = it.Next()))
-						{
-							if (act2->spr.picnum == queball || act2->spr.picnum == stripeball)
-							{
-								delta = absangle(ps[p].GetActor()->spr.Angles.Yaw, (act2->spr.pos.XY() - ps[p].GetActor()->spr.pos.XY()).Angle());
-								if (delta < DAngle22_5 / 2)
-								{
-									double l;
-									findplayer(act2, &l);
-									if (x > l) break;
-								}
-							}
-						}
-						if (act2 == nullptr)
-						{
-							if (actor->spr.pal == 12)
-								actor->vel.X = 10.25;
-							else actor->vel.X = 8.75;
-							actor->spr.Angles.Yaw = ps[p].GetActor()->spr.Angles.Yaw;
-							ps[p].toggle_key_flag = 2;
-						}
-					}
-			}
-		}
-		if (x < 32 && actor->sector() == ps[p].cursector)
-		{
-			actor->spr.Angles.Yaw = (actor->spr.pos.XY() - ps[p].GetActor()->spr.pos.XY()).Angle();
-			actor->vel.X = 3;
-		}
-	}
-	return true;
-}
-
-//---------------------------------------------------------------------------
-//
-// 
-//
-//---------------------------------------------------------------------------
-
 void forcesphere(DDukeActor* actor, int forcesphere)
 {
 	auto sectp = actor->sector();
diff --git a/source/games/duke/src/actors_d.cpp b/source/games/duke/src/actors_d.cpp
index 8932dd8c5..fbd066ac3 100644
--- a/source/games/duke/src/actors_d.cpp
+++ b/source/games/duke/src/actors_d.cpp
@@ -2218,6 +2218,10 @@ void moveactors_d(void)
 	DukeStatIterator it(STAT_ACTOR);
 	while (auto act = it.Next())
 	{
+		if (act->spr.picnum == QUEBALL)
+		{
+			int a = 0;
+		}
 		auto sectp = act->sector();
 
 		if (act->spr.scale.X == 0 || sectp == nullptr || actorflag(act, SFLAG2_DIENOW))
@@ -2305,10 +2309,6 @@ void moveactors_d(void)
 				spawn(act, EXPLOSION2);
 			ssp(act, CLIPMASK0);
 			break;
-		case QUEBALL:
-		case STRIPEBALL:
-			if (!queball(act, POCKET, QUEBALL, STRIPEBALL)) continue;
-			break;
 		case FORCESPHERE:
 			forcesphere(act, FORCESPHERE);
 			continue;
diff --git a/source/games/duke/src/actors_r.cpp b/source/games/duke/src/actors_r.cpp
index 9d738954e..4d22b16ca 100644
--- a/source/games/duke/src/actors_r.cpp
+++ b/source/games/duke/src/actors_r.cpp
@@ -2065,10 +2065,6 @@ void moveactors_r(void)
 				break;
 			}
 
-			case QUEBALL:
-			case STRIPEBALL:
-				if (!queball(act, POCKET, QUEBALL, STRIPEBALL)) continue;
-				break;
 			case FORCESPHERE:
 				forcesphere(act, FORCESPHERE);
 				continue;
diff --git a/source/games/duke/src/funct.h b/source/games/duke/src/funct.h
index bd6ddd5c1..c98f30eb2 100644
--- a/source/games/duke/src/funct.h
+++ b/source/games/duke/src/funct.h
@@ -41,7 +41,6 @@ void bounce(DDukeActor* i);
 void rpgexplode(DDukeActor* i, int j, const DVector3& pos, int EXPLOSION2, int EXPLOSIONBOT2, int newextra, int playsound);
 void lotsofstuff(DDukeActor* s, int n, int spawntype);
 bool respawnmarker(DDukeActor* i, int yellow, int green);
-bool queball(DDukeActor* i, int pocket, int queball, int stripeball);
 void forcesphere(DDukeActor* i, int forcesphere);
 void recon(DDukeActor* i, int explosion, int firelaser, int attacksnd, int painsnd, int roamsnd, int shift, int (*getspawn)(DDukeActor* i));
 void ooz(DDukeActor* i);
diff --git a/source/games/duke/src/sectors_d.cpp b/source/games/duke/src/sectors_d.cpp
index 5a5c568e2..813721835 100644
--- a/source/games/duke/src/sectors_d.cpp
+++ b/source/games/duke/src/sectors_d.cpp
@@ -1077,30 +1077,6 @@ void checkhitsprite_d(DDukeActor* targ, DDukeActor* proj)
 		spawn(targ, SMALLSMOKE);
 		targ->Destroy();
 		break;
-	case QUEBALL:
-	case STRIPEBALL:
-		if (proj->spr.picnum == QUEBALL || proj->spr.picnum == STRIPEBALL)
-		{
-			proj->vel.X = targ->vel.X * 0.75;
-			proj->spr.Angles.Yaw -= targ->spr.Angles.Yaw.Normalized180() * 2 + DAngle180;
-			targ->spr.Angles.Yaw = (targ->spr.pos.XY() - proj->spr.pos.XY()).Angle() - DAngle90;
-			if (S_CheckSoundPlaying(POOLBALLHIT) < 2)
-				S_PlayActorSound(POOLBALLHIT, targ);
-		}
-		else
-		{
-			if (krand() & 3)
-			{
-				targ->vel.X = 10.25;
-				targ->spr.Angles.Yaw = proj->spr.Angles.Yaw;
-			}
-			else
-			{
-				lotsofglass(targ, nullptr, 3);
-				targ->Destroy();
-			}
-		}
-		break;
 	case HANGLIGHT:
 	case GENERICPOLE2:
 		for (k = 0; k < 6; k++)
diff --git a/source/games/duke/src/sectors_r.cpp b/source/games/duke/src/sectors_r.cpp
index 7cff6b015..dd9680ec7 100644
--- a/source/games/duke/src/sectors_r.cpp
+++ b/source/games/duke/src/sectors_r.cpp
@@ -1454,21 +1454,11 @@ void checkhitsprite_r(DDukeActor* targ, DDukeActor* proj)
 		S_PlayActorSound(355, targ);
 		break;
 
-	case STRIPEBALL:
-	case QUEBALL:
 	case BOWLINGPIN:
 	case BOWLINGPIN + 1:
 	case HENSTAND:
 	case HENSTAND + 1:
-		if (proj->spr.picnum == QUEBALL || proj->spr.picnum == STRIPEBALL)
-		{
-			proj->vel.X = targ->vel.X * 0.75;
-			proj->spr.Angles.Yaw -= targ->spr.Angles.Yaw.Normalized180() * 2 + DAngle180;
-			targ->spr.Angles.Yaw = (targ->spr.pos.XY() - proj->spr.pos.XY()).Angle() - DAngle90;
-			if (S_CheckSoundPlaying(POOLBALLHIT) < 2)
-				S_PlayActorSound(POOLBALLHIT, targ);
-		}
-		else if (proj->spr.picnum == BOWLINGPIN || proj->spr.picnum == BOWLINGPIN + 1)
+		if (proj->spr.picnum == BOWLINGPIN || proj->spr.picnum == BOWLINGPIN + 1)
 		{
 			proj->vel.X *= 0.75;
 			proj->spr.Angles.Yaw -= targ->spr.Angles.Yaw * 2 + randomAngle(11.25);
diff --git a/source/games/duke/src/spawn_d.cpp b/source/games/duke/src/spawn_d.cpp
index f959ca4fa..91ed4b8d4 100644
--- a/source/games/duke/src/spawn_d.cpp
+++ b/source/games/duke/src/spawn_d.cpp
@@ -401,21 +401,8 @@ DDukeActor* spawninit_d(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 		[[fallthrough]];
 
 	case BLOODYPOLE:
-
-	case QUEBALL:
-	case STRIPEBALL:
-
-		if (act->spr.picnum == QUEBALL || act->spr.picnum == STRIPEBALL)
-		{
-			act->spr.cstat = CSTAT_SPRITE_BLOCK_HITSCAN;
-			act->clipdist = 2;
-		}
-		else
-		{
-			act->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL;
-			act->clipdist = 8;
-		}
-
+		act->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL;
+		act->clipdist = 8;
 		ChangeActorStat(act, STAT_ZOMBIEACTOR);
 		break;
 
diff --git a/source/games/duke/src/spawn_r.cpp b/source/games/duke/src/spawn_r.cpp
index d8f7b5592..b3eb076f0 100644
--- a/source/games/duke/src/spawn_r.cpp
+++ b/source/games/duke/src/spawn_r.cpp
@@ -293,19 +293,8 @@ DDukeActor* spawninit_r(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 	case TOUGHGAL:
 		act->spr.yint = act->spr.hitag;
 		act->spr.hitag = -1;
-		[[fallthrough]];
-	case QUEBALL:
-	case STRIPEBALL:
-		if (act->spr.picnum == QUEBALL || act->spr.picnum == STRIPEBALL)
-		{
-			act->spr.cstat = CSTAT_SPRITE_BLOCK_HITSCAN;
-			act->clipdist = 2;
-		}
-		else
-		{
-			act->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL;
-			act->clipdist = 8;
-		}
+		act->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL;
+		act->clipdist = 8;
 		ChangeActorStat(act, STAT_ZOMBIEACTOR);
 		break;
 	case BOWLINGBALL:
diff --git a/source/games/duke/src/vmexports.cpp b/source/games/duke/src/vmexports.cpp
index f05680a68..cb7e2d9e9 100644
--- a/source/games/duke/src/vmexports.cpp
+++ b/source/games/duke/src/vmexports.cpp
@@ -898,6 +898,18 @@ DEFINE_ACTION_FUNCTION_NATIVE(_DukePlayer, centerview, dukeplayer_centerview)
 	return 0;
 }
 
+inline int DukePlayer_PlayerInput(player_struct* pl, int bit)
+{
+	return (!!((pl->sync.actions) & ESyncBits::FromInt(bit)));
+}
+
+DEFINE_ACTION_FUNCTION_NATIVE(_DukePlayer, playerinput, DukePlayer_PlayerInput)
+{
+	PARAM_SELF_STRUCT_PROLOGUE(player_struct);
+	PARAM_INT(bit);
+	ACTION_RETURN_INT(DukePlayer_PlayerInput(self, bit));
+}
+
 void dukeplayer_settargetangle(player_struct* self, double a, int backup)
 {
 	self->Angles.setYaw(DAngle::fromDeg(a), backup);
diff --git a/wadsrc/static/filter/dukelike/engine/engine.def b/wadsrc/static/filter/dukelike/engine/engine.def
index 5c63ee7b1..b376d9a7c 100644
--- a/wadsrc/static/filter/dukelike/engine/engine.def
+++ b/wadsrc/static/filter/dukelike/engine/engine.def
@@ -44,6 +44,10 @@ spawnclasses
 	625 = DukeCamera
 	3190 = DukeRespawnmarker
 	1267 = DukeRat
+	902 = DukeQueball
+	901 = DukeStripeBall
+	903 = DukePoolPocket
+
 
 	1272 = DukeTrash
 	634 = DukeBolt1
diff --git a/wadsrc/static/filter/dukelike/sndinfo.txt b/wadsrc/static/filter/dukelike/sndinfo.txt
index 3a40c1bc2..51cb831a3 100644
--- a/wadsrc/static/filter/dukelike/sndinfo.txt
+++ b/wadsrc/static/filter/dukelike/sndinfo.txt
@@ -7,6 +7,7 @@ $conreserve INSERT_CLIP          5
 $conreserve CHAINGUN_FIRE        6
 $conreserve RPG_SHOOT            7
 $conreserve POOLBALLHIT          8
+$limit POOLBALLHIT 2
 $conreserve RPG_EXPLODE          9
 $conreserve CAT_FIRE            10
 $conreserve SHRINKER_FIRE       11
diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt
index db942bacb..87b6078ab 100644
--- a/wadsrc/static/zscript.txt
+++ b/wadsrc/static/zscript.txt
@@ -76,6 +76,7 @@ version "4.10"
 #include "zscript/games/duke/actors/viewscreen.zs"
 #include "zscript/games/duke/actors/canwithsomething.zs"
 #include "zscript/games/duke/actors/tongue.zs"
+#include "zscript/games/duke/actors/queball.zs"
 
 #include "zscript/games/duke/actors/genericdestructible.zs"
 #include "zscript/games/duke/actors/redneckmisc.zs"
diff --git a/wadsrc/static/zscript/coreactor.zs b/wadsrc/static/zscript/coreactor.zs
index bfea62199..2d5723a49 100644
--- a/wadsrc/static/zscript/coreactor.zs
+++ b/wadsrc/static/zscript/coreactor.zs
@@ -22,7 +22,7 @@ const maptoworld = (1. / 16.);
 class CoreActor native
 {
 	const REPEAT_SCALE = 1. / 64.;
-	native readonly sectortype sector;
+	native sectortype sector;	// cannot be read-only, some code calls clipmove directly on this.
 	
 	native int16 cstat;
 	//native int16 picnum; // access is disabled to allow later refactoring.
@@ -70,6 +70,7 @@ class CoreActor native
 
 	native clearscope static double deltaangle(double ang1, double ang2);
 	native clearscope static double absangle(double ang1, double ang2);
+	native clearscope static double Normalize180(double ang);
 
 	int randomFlip()
 	{
@@ -90,17 +91,3 @@ class CoreActor native
 
 }
 
-// this only allows function getters to enable validation on the target.
-struct Collision
-{
-	native int type;
-	native int exbits;
-	native walltype hitWall();
-	native sectortype hitSector();
-	native CoreActor hitActor();
-	native void setSector(sectortype s);
-	native void setWall(walltype w);
-	native void setActor(CoreActor a);
-	native void setVoid();
-
-}
\ No newline at end of file
diff --git a/wadsrc/static/zscript/games/duke/actors/queball.zs b/wadsrc/static/zscript/games/duke/actors/queball.zs
new file mode 100644
index 000000000..3475e92a6
--- /dev/null
+++ b/wadsrc/static/zscript/games/duke/actors/queball.zs
@@ -0,0 +1,138 @@
+class DukePoolPocket : DukeActor
+{
+	// we only need this for checking, it's an empty sprite.
+}
+
+class DukeQueball : DukeActor
+{
+	default
+	{
+		clipdist 2;
+		pic "QUEBALL";
+		statnum STAT_ZOMBIEACTOR;
+	}
+	
+	override void Initialize()
+	{
+		self.cstat = CSTAT_SPRITE_BLOCK_HITSCAN;
+	}
+	
+	override void Tick()
+	{
+		if(self.vel.X != 0)
+		{
+			DukeStatIterator it;
+			for(let aa = it.First(STAT_DEFAULT); aa; aa = it.Next())
+			{
+				double dist = (aa.pos.XY - self.pos.XY).Length();
+				if (aa is 'DukePoolPocket' && dist < 3.25)
+				{
+					self.Destroy();
+					return;
+				}
+			}
+
+			CollisionData colli;
+			let move = self.angle.ToVector() * self.vel.X * 0.5;
+			[self.sector, self.pos] = Raze.clipmove(self.pos, self.sector, move, 1.5, 4., 4., CLIPMASK1, colli);
+			int j = colli.type;
+
+			if (j == kHitWall)
+			{
+				let ang = colli.hitWall().delta().Angle();
+				self.angle = ang * 2 - self.angle;
+			}
+			else if (j == kHitSprite)
+			{
+				self.checkhitsprite(DukeActor(colli.hitactor()));
+			}
+
+			self.vel.X -= 1/16.;
+			if(self.vel.X < 0) self.vel.X = 0;
+			if (self is 'DukeStripeBall')
+			{
+				self.cstat = CSTAT_SPRITE_BLOCK_ALL;
+				self.cstat |= (CSTAT_SPRITE_XFLIP | CSTAT_SPRITE_YFLIP) & int(self.vel.X * 16.); // special hack edition...
+			}
+		}
+		else
+		{
+			double x;
+			DukePlayer p;
+			[p, x] = self.findplayer();
+
+			if (x < 99.75)
+			{
+				//						if(self.pal == 12)
+				{
+					let delta = absangle(p.actor.angle, (self.pos.XY - p.actor.pos.XY).Angle());
+					if (delta < 11.25 && p.PlayerInput(Duke.SB_OPEN))
+						if (p.toggle_key_flag == 1)
+						{
+							DukeStatIterator it;
+							DukeActor act2;
+							for (act2 = it.First(STAT_ACTOR); act2; act2 = it.Next())
+							{
+								if (act2 is 'DukeQueball')
+								{
+									delta = absangle(p.Actor.angle, (act2.pos.XY - p.Actor.pos.XY).Angle());
+									if (delta < 11.25)
+									{
+										double l;
+										DukePlayer q;
+										[q, l] = act2.findplayer();
+										if (x > l) break;
+									}
+								}
+							}
+							if (act2 == nullptr)
+							{
+								if (self.pal == 12)
+									self.vel.X = 10.25;
+								else self.vel.X = 8.75;
+								self.angle = p.Actor.angle;
+								p.toggle_key_flag = 2;
+							}
+						}
+				}
+			}
+			if (x < 32 && self.sector == p.cursector)
+			{
+				self.angle = (self.pos.XY - p.Actor.pos.XY).Angle();
+				self.vel.X = 3;
+			}
+		}
+	}
+
+	override void onHit(DukeActor hitter)
+	{
+		if (hitter is 'DukeQueball')
+		{
+			hitter.vel.X = self.vel.X * 0.75;
+			hitter.angle -= Normalize180(self.angle) * 2 + 180;
+			self.angle = (self.pos.XY - hitter.pos.XY).Angle() - 90;
+			self.PlayActorSound("POOLBALLHIT");
+		}
+		else
+		{
+			if (random(0, 3))
+			{
+				self.vel.X = 10.25;
+				self.angle = hitter.angle;
+			}
+			else
+			{
+				self.lotsofglass(3);
+				self.Destroy();
+			}
+		}
+	}
+}
+
+class DukeStripeball : DukeQueball
+{
+	default
+	{
+		pic "STRIPEBALL";
+	}
+}
diff --git a/wadsrc/static/zscript/games/duke/dukeactor.zs b/wadsrc/static/zscript/games/duke/dukeactor.zs
index a9f3eebba..74c090d6a 100644
--- a/wadsrc/static/zscript/games/duke/dukeactor.zs
+++ b/wadsrc/static/zscript/games/duke/dukeactor.zs
@@ -200,7 +200,7 @@ class DukeActor : CoreActor native
 	native void lotsofstuff(Name type, int count);
 	native double gutsoffset();
 	native int movesprite(Vector3 move, int clipmask);
-	native int movesprite_ex(Vector3 move, int clipmask, Collision coll);
+	native int movesprite_ex(Vector3 move, int clipmask, CollisionData coll);
 	
 
 	// temporary flag accessors - need to be eliminated once we can have true actor flags
diff --git a/wadsrc/static/zscript/games/duke/dukegame.zs b/wadsrc/static/zscript/games/duke/dukegame.zs
index 292d3b74c..3aed11145 100644
--- a/wadsrc/static/zscript/games/duke/dukegame.zs
+++ b/wadsrc/static/zscript/games/duke/dukegame.zs
@@ -90,6 +90,49 @@ struct Duke native
 		SF_DTAG = 128,
 	};
 
+	enum ESyncBits
+	{
+		SB_FIRST_WEAPON_BIT = 1 << 0,
+		SB_ITEM_BIT_1 = 1 << 4,
+		SB_ITEM_BIT_2 = 1 << 5,
+		SB_ITEM_BIT_3 = 1 << 6,
+		SB_ITEM_BIT_4 = 1 << 7,
+		SB_ITEM_BIT_5 = 1 << 8,
+		SB_ITEM_BIT_6 = 1 << 9,
+		SB_ITEM_BIT_7 = 1 << 10,
+
+		SB_INVPREV = 1 << 11,
+		SB_INVNEXT = 1 << 12,
+		SB_INVUSE = 1 << 13,
+		SB_CENTERVIEW = 1 << 14,
+		SB_TURNAROUND = 1 << 15,
+		SB_HOLSTER = 1 << 16,
+		SB_OPEN = 1 << 17,
+
+		SB_AIMMODE = 1 << 18,   
+		SB_QUICK_KICK = 1 << 19,
+		SB_ESCAPE = 1 << 20,
+
+		SB_AIM_UP = 1 << 21,
+		SB_AIM_DOWN = 1 << 22,
+		SB_LOOK_LEFT = 1 << 23,
+		SB_LOOK_RIGHT = 1 << 24,
+		SB_LOOK_UP = 1 << 25,
+		SB_LOOK_DOWN = 1 << 26,
+		SB_RUN = 1 << 27,
+		SB_JUMP = 1 << 28,
+		SB_CROUCH = 1 << 29,
+		SB_FIRE = 1 << 30,
+		SB_ALTFIRE = 1u << 31,
+
+		SB_WEAPONMASK_BITS = (15u * SB_FIRST_WEAPON_BIT), // Weapons take up 4 bits
+		SB_ITEMUSE_BITS = (127u * SB_ITEM_BIT_1),
+
+		SB_BUTTON_MASK = SB_ALTFIRE|SB_FIRE|SB_CROUCH|SB_JUMP|SB_LOOK_UP|SB_LOOK_DOWN|SB_AIM_UP|SB_AIM_DOWN|SB_LOOK_LEFT|SB_LOOK_RIGHT,     // all input from buttons (i.e. active while held)
+		SB_INTERFACE_MASK = (SB_INVPREV|SB_INVNEXT|SB_INVUSE|SB_CENTERVIEW|SB_TURNAROUND|SB_HOLSTER|SB_OPEN|SB_ESCAPE|SB_QUICK_KICK),  // all input from CCMDs
+		SB_INTERFACE_BITS = (SB_WEAPONMASK_BITS | SB_ITEMUSE_BITS | SB_INTERFACE_MASK),
+		SB_ALL = ~0u
+	};
 
 	native static void PlaySpecialMusic(int which);
 	native static int PlaySound(Sound num, int channel = CHAN_AUTO, int flags = 0, float vol =0.8f);
@@ -300,6 +343,7 @@ struct DukePlayer native
 	native void quickkill();
 	native void addPitch(double p);
 	native void centerView();
+	native int playerinput(int bit);
 
 
 }
diff --git a/wadsrc/static/zscript/razebase.zs b/wadsrc/static/zscript/razebase.zs
index bff836bbf..355ea3616 100644
--- a/wadsrc/static/zscript/razebase.zs
+++ b/wadsrc/static/zscript/razebase.zs
@@ -137,6 +137,21 @@ struct SummaryInfo native
 	native readonly bool endofgame;
 }
 
+// this only allows function getters to enable validation on the target.
+struct CollisionData
+{
+	int type;
+	int exbits;
+	voidptr hit;	// do not access!
+	native walltype hitWall();
+	native sectortype hitSector();
+	native CoreActor hitActor();
+	native void setSector(sectortype s);
+	native void setWall(walltype w);
+	native void setActor(CoreActor a);
+	native void setVoid();
+
+}
 struct Raze
 {
 	const kAngleMask	= 0x7FF;
@@ -156,6 +171,7 @@ struct Raze
 	native static Sound FindSoundByResID(int id);
 	
 	native static sectortype updatesector(Vector2 pos, sectortype lastsect, double maxdist = 96);
+	native static sectortype, Vector3 clipmove(Vector3 pos, sectortype sect, Vector2 move, double walldist, double ceildist, double flordist, uint cliptype, CollisionData coll, int clipmoveboxtracenum = 3);
 
 	// game check shortcuts