From c1539c22862cec14f2f1a76df183741ddd3a6580 Mon Sep 17 00:00:00 2001 From: Boondorl Date: Sun, 28 Jan 2024 15:30:03 -0500 Subject: [PATCH] Added client-side item pick ups Includes feature to disable Actor rendering locally (this cannot be checked from the playsim) and options for disabling co-op only things. --- src/d_main.cpp | 4 ++ src/doomdef.h | 6 +- src/playsim/actor.h | 4 ++ src/playsim/p_mobj.cpp | 65 ++++++++++++++++-- wadsrc/static/menudef.txt | 4 ++ wadsrc/static/zscript/actors/actor.zs | 2 + .../zscript/actors/inventory/inventory.zs | 66 +++++++++++++++++-- .../static/zscript/actors/inventory_util.zs | 2 + 8 files changed, 140 insertions(+), 13 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index d225066bf61..98b6fb222ce 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -575,6 +575,10 @@ CUSTOM_CVAR(Int, dmflags3, 0, CVAR_SERVERINFO | CVAR_NOINITCALL) CVAR(Flag, sv_noplayerclip, dmflags3, DF3_NO_PLAYER_CLIP); CVAR(Flag, sv_coopsharekeys, dmflags3, DF3_COOP_SHARE_KEYS); +CVAR(Flag, sv_localitems, dmflags3, DF3_LOCAL_ITEMS); +CVAR(Flag, sv_nolocaldrops, dmflags3, DF3_NO_LOCAL_DROPS); +CVAR(Flag, sv_nocoopitems, dmflags3, DF3_NO_COOP_ONLY_ITEMS); +CVAR(Flag, sv_nocoopthings, dmflags3, DF3_NO_COOP_ONLY_THINGS); //========================================================================== // diff --git a/src/doomdef.h b/src/doomdef.h index a2ae2f35e42..668059909f0 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -180,7 +180,11 @@ enum : unsigned enum : unsigned { DF3_NO_PLAYER_CLIP = 1 << 0, // Players can walk through and shoot through each other - DF3_COOP_SHARE_KEYS = 1 << 1, // Keys will be given to all players in coop + DF3_COOP_SHARE_KEYS = 1 << 1, // Keys and other core items will be given to all players in coop + DF3_LOCAL_ITEMS = 1 << 2, // Items are picked up client-side rather than fully taken by the client who picked it up + DF3_NO_LOCAL_DROPS = 1 << 3, // Drops from Actors aren't picked up locally + DF3_NO_COOP_ONLY_ITEMS = 1 << 4, // Items that only appear in co-op are disabled + DF3_NO_COOP_ONLY_THINGS = 1 << 5, // Any Actor that only appears in co-op is disabled }; // [RH] Compatibility flags. diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 4a1faa5da3d..6f58a995d11 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -904,6 +904,9 @@ class AActor final : public DThinker // Returns true if this view is considered "local" for the player. bool CheckLocalView() const; + // Allows for enabling/disabling client-side rendering in a way the playsim can't access. + void DisableLocalRendering(const unsigned int pNum, const bool disable); + bool ShouldRenderLocally() const; // Finds the first item of a particular type. AActor *FindInventory (PClassActor *type, bool subclass=false); @@ -1125,6 +1128,7 @@ class AActor final : public DThinker uint32_t RenderRequired; // current renderer must have this feature set uint32_t RenderHidden; // current renderer must *not* have any of these features + bool NoLocalRender; // DO NOT EXPORT THIS! This is a way to disable rendering such that the playsim cannot access it. ActorRenderFlags renderflags; // Different rendering flags ActorRenderFlags2 renderflags2; // More rendering flags... ActorFlags flags; diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index d3a8eeb3413..c14f6d27de3 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -970,6 +970,43 @@ DEFINE_ACTION_FUNCTION(AActor, CheckLocalView) ACTION_RETURN_BOOL(self->CheckLocalView()); } +void AActor::DisableLocalRendering(const unsigned int pNum, const bool disable) +{ + if (pNum == consoleplayer) + NoLocalRender = disable; +} + +static void DisableLocalRendering(AActor* const self, const unsigned int pNum, const int disable) +{ + self->DisableLocalRendering(pNum, disable); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, DisableLocalRendering, DisableLocalRendering) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_UINT(pNum); + PARAM_INT(disable); + + DisableLocalRendering(self, pNum, disable); +} + +bool AActor::ShouldRenderLocally() const +{ + return !NoLocalRender; +} + +static int ShouldRenderLocally(const AActor* const self) +{ + return self->ShouldRenderLocally(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, ShouldRenderLocally, ShouldRenderLocally) +{ + PARAM_SELF_PROLOGUE(AActor); + + ACTION_RETURN_INT(ShouldRenderLocally(self)); +} + //============================================================================ // // AActor :: IsInsideVisibleAngles @@ -1049,6 +1086,9 @@ bool AActor::IsVisibleToPlayer() const // [BB] Safety check. This should never be NULL. Nevertheless, we return true to leave the default ZDoom behavior unaltered. if (p == nullptr || p->camera == nullptr ) return true; + + if (!ShouldRenderLocally()) + return false; if (VisibleToTeam != 0 && teamplay && (signed)(VisibleToTeam-1) != p->userinfo.GetTeam() ) @@ -5701,15 +5741,26 @@ AActor *FLevelLocals::SpawnMapThing (FMapThing *mthing, int position) const AActor *info = GetDefaultByType (i); - // don't spawn keycards and players in deathmatch - if (deathmatch && info->flags & MF_NOTDMATCH) - return NULL; + // Don't spawn keycards and players in deathmatch. + if (deathmatch && (info->flags & MF_NOTDMATCH)) + return nullptr; - // don't spawn extra things in coop if so desired - if (multiplayer && !deathmatch && (dmflags2 & DF2_NO_COOP_THING_SPAWN)) + // Don't spawn extra things in co-op if desired. + if (multiplayer && !deathmatch) { - if ((mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH) - return NULL; + // Don't spawn DM-only things in co-op. + if ((dmflags2 & DF2_NO_COOP_THING_SPAWN) && (mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH) + return nullptr; + // Having co-op only functionality is a bit odd, but you never know. + if (!mthing->special && !mthing->thingid && (mthing->flags & (MTF_COOPERATIVE | MTF_SINGLE)) == MTF_COOPERATIVE) + { + // Don't spawn co-op only things in general. + if (dmflags3 & DF3_NO_COOP_ONLY_THINGS) + return nullptr; + // Don't spawn co-op only items. + if ((dmflags3 & DF3_NO_COOP_ONLY_ITEMS) && i->IsDescendantOf(NAME_Inventory)) + return nullptr; + } } // [RH] don't spawn extra weapons in coop if so desired diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 0f384a4270f..5bb306d7e1e 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -1688,6 +1688,8 @@ OptionMenu CoopOptions protected Title "$GMPLYMNU_COOPERATIVE" Option "$GMPLYMNU_MULTIPLAYERTHINGS", "sv_nothingspawn", "NoYes" + Option "$GMPLYMNU_COOPTHINGS", "sv_nocoopthings", "NoYes" + Option "$GMPLYMNU_COOPITEMS", "sv_nocoopitems", "NoYes" Option "$GMPLYMNU_MULTIPLAYERWEAPONS", "sv_noweaponspawn", "NoYes" Option "$GMPLYMNU_LOSEINVENTORY", "sv_cooploseinventory", "YesNo" Option "$GMPLYMNU_KEEPKEYS", "sv_cooplosekeys", "NoYes" @@ -1699,6 +1701,8 @@ OptionMenu CoopOptions protected Option "$GMPLYMNU_SPAWNWHEREDIED", "sv_samespawnspot", "YesNo" Option "$GMPLYMNU_NOPLAYERCLIP", "sv_noplayerclip", "YesNo" Option "$GMPLYMNU_SHAREKEYS", "sv_coopsharekeys", "YesNo" + Option "$GMPLYMNU_LOCALITEMS", "sv_localitems", "YesNo" + Option "$GMPLYMNU_NOLOCALDROP", "sv_nolocaldrops", "YesNo" Class "GameplayMenu" } diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index e9f14f56478..fed44435974 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -503,6 +503,8 @@ class Actor : Thinker native virtual native void FallAndSink(double grav, double oldfloorz); private native void Substitute(Actor replacement); native ui void DisplayNameTag(); + native clearscope void DisableLocalRendering(uint playerNum, bool disable); + native ui bool ShouldRenderLocally(); // Only clients get to check this, never the playsim. // Called by inventory items to see if this actor is capable of touching them. // If true, the item will attempt to be picked up. Useful for things like diff --git a/wadsrc/static/zscript/actors/inventory/inventory.zs b/wadsrc/static/zscript/actors/inventory/inventory.zs index ddcbb68be35..3b1514411ee 100644 --- a/wadsrc/static/zscript/actors/inventory/inventory.zs +++ b/wadsrc/static/zscript/actors/inventory/inventory.zs @@ -10,6 +10,8 @@ class Inventory : Actor const BLINKTHRESHOLD = (4*32); const BONUSADD = 6; + private bool pickedUp[MAXPLAYERS]; // If items are set to local, track who already picked it up. + deprecated("3.7") private int ItemFlags; Actor Owner; // Who owns this item? NULL if it's still a pickup. int Amount; // Amount of item this instance has @@ -65,6 +67,7 @@ class Inventory : Actor flagdef IsHealth: ItemFlags, 22; flagdef AlwaysPickup: ItemFlags, 23; flagdef Unclearable: ItemFlags, 24; + flagdef NeverLocal: ItemFlags, 25; flagdef ForceRespawnInSurvival: none, 0; flagdef PickupFlash: none, 6; @@ -768,12 +771,17 @@ class Inventory : Actor override void Touch (Actor toucher) { + bool localPickUp; let player = toucher.player; - - // If a voodoo doll touches something, pretend the real player touched it instead. - if (player != NULL) + if (player) { + // If a voodoo doll touches something, pretend the real player touched it instead. toucher = player.mo; + // Client already picked this up, so ignore them. + if (HasPickedUpLocally(toucher)) + return; + + localPickUp = CanPickUpLocally(toucher); } bool localview = toucher.CheckLocalView(); @@ -781,9 +789,23 @@ class Inventory : Actor if (!toucher.CanTouchItem(self)) return; + Inventory give = self; + if (localPickUp) + { + give = Inventory(Spawn(GetClass())); + if (!give) + return; + } + bool res; - [res, toucher] = CallTryPickup(toucher); - if (!res) return; + [res, toucher] = give.CallTryPickup(toucher); + if (!res) + { + if (give != self) + give.Destroy(); + + return; + } // This is the only situation when a pickup flash should ever play. if (PickupFlash != NULL && !ShouldStay()) @@ -829,6 +851,9 @@ class Inventory : Actor ac.GiveSecret(true, true); } + if (localPickUp) + PickUpLocally(toucher); + //Added by MC: Check if item taken was the roam destination of any bot for (int i = 0; i < MAXPLAYERS; i++) { @@ -1014,6 +1039,37 @@ class Inventory : Actor SetStateLabel("HoldAndDestroy"); } } + + // Check if the Actor can recieve a local copy of the item instead of outright taking it. + clearscope bool CanPickUpLocally(Actor other) const + { + return other && other.player + && multiplayer && !deathmatch && sv_localitems + && !bNeverLocal && (!bDropped || !sv_nolocaldrops); + } + + // Check if a client has already picked up this item locally. + clearscope bool HasPickedUpLocally(Actor client) const + { + return pickedUp[client.PlayerNumber()]; + } + + // When items are dropped, clear their local pick ups. + void ClearLocalPickUps() + { + DisableLocalRendering(consoleplayer, false); + for (int i; i < MAXPLAYERS; ++i) + pickedUp[i] = false; + } + + // Client picked up this item. Mark it as invisible to that specific player and + // prevent them from picking it up again. + protected void PickUpLocally(Actor client) + { + int pNum = client.PlayerNumber(); + pickedUp[pNum] = true; + DisableLocalRendering(pNum, true); + } //=========================================================================== // diff --git a/wadsrc/static/zscript/actors/inventory_util.zs b/wadsrc/static/zscript/actors/inventory_util.zs index e1820a86da1..1bb9be51269 100644 --- a/wadsrc/static/zscript/actors/inventory_util.zs +++ b/wadsrc/static/zscript/actors/inventory_util.zs @@ -291,6 +291,8 @@ extend class Actor { Inventory drop = item.CreateTossable(amt); if (drop == null) return NULL; + drop.ClearLocalPickUps(); + drop.bNeverLocal = true; drop.SetOrigin(Pos + (0, 0, 10.), false); drop.Angle = Angle; drop.VelFromAngle(5.);