/****************************************************************************** *SBKeycardMover* is a Bump Mover that accepts a bump only if the Pawn has a specific item "Keycard" in its inventory. If *Keycard* is not set by mapper or *Keycard* is found in Bumper's inventory, this mover reacts like a normal mover. If *Keycard* is NOT found in inventory: if a Pawn has bumped more than *ReBumpDelay* seconds before: if bumper is a PlayerPawn, a *BumpDeniedMessage* is displayed in his HUD. Use %KEYCARD% within this message, where the class name of *Keycard* should be inserted. *Article* is inserted in front of Keycard's name. Example: BumpDeniedMessage="You need %KEYCARD% to open this door." Article="a " Then the event *BumpDeniedEvent* is risen. If the last bump was less than *ReBumpDelay* before, nothing happens for this pawn (but maybe for other bumping pawns). SeriousBarbie AT Barbies DOT World History: V1: 28 Jan. 2026 * fixed description + added debug logging functions + verified multiuser usage (array edge NOT tested) * renamed from "KeyMover" to "SBKeycardMover" V0: 27 Jan. 2026 (first release) ******************************************************************************/ class SBKeycardMover expands Mover; var() class Keycard; var() String BumpDeniedMessage; // use %KEYCARD% where 'Keycard.Name' should be inserted var() String Article; var() name BumpDeniedEvent; var() float ReBumpDelay; // in seconds var() bool bDebugLog; // deactivate for released to public var Pawn Bumper[16]; var float BumperTimes[16]; var byte BumperIndex; event PostBeginPlay() { if (Keycard == None) warn("'Keycard' is not set"); super.PostBeginPlay(); } function bool FindInBumperList(Pawn P, out byte Index) { LogBumperList(); for (Index = 0; Index < ArrayCount(Bumper); Index++) if (Bumper[Index] == P) return true; return false; } function byte AddToBumperList(Pawn P) { if (BumperIndex >= ArrayCount(Bumper)) BumperIndex = 0; Bumper[BumperIndex] = P; BumperTimes[BumperIndex] = Level.TimeSeconds; BumperIndex++; return BumperIndex - 1; } event Logger(string msg) { if ( ! bDebugLog) return; BroadCastMessage(Msg); log(Msg); } event LogBumperList() { local byte i; for (i = 0; i < ArrayCount(Bumper); i++) if (Bumper[i] != None) logger("Bumper[" $ i $ "]=" $ Bumper[i] $ ", BumperTimes=" $ BumperTimes[i]); } state() BumpOpenTimed { function Bump(actor Other) { local Actor A; local string msg; local byte BumperIndex; local Inventory Inv; if (Pawn(Other) != None && Keycard != None) { for (Inv = Pawn(Other).Inventory; Inv != None; Inv = Inv.Inventory) logger(string(Inv)); if (Pawn(Other).FindInventoryType(Keycard) != None) { logger(Keycard @ "found in inventory of" @ other $ ", calling Super.Bump()"); Super.Bump(Other); } else { logger(Keycard @ "NOT found in inventory of" @ other $ ", checking BumperList"); if (FindInBumperList(Pawn(Other), BumperIndex)) { if (Level.TimeSeconds - BumperTimes[BumperIndex] <= ReBumpDelay) return; } else BumperIndex = AddToBumperList(Pawn(Other)); BumperTimes[BumperIndex] = Level.TimeSeconds; if (PlayerPawn(Other) != None && BumpDeniedMessage != "") { msg = BumpDeniedMessage; PlayerPawn(Other).ReplaceText(msg, "%KEYCARD%", Article $ string(Keycard.Name)); // replaced message not cached, because values may have changed by external property changing code PlayerPawn(Other).ClientMessage(msg); } if (BumpDeniedEvent != '') foreach AllActors(class'Actor', A, BumpDeniedEvent) A.Trigger(self, Pawn(Other)); } } else Super.Bump(Other); } }