Creating An Interaction From A Mutator
So you want to use an interaction - but dont know how to create one and have it work offline and online? Read on and see...
Interactions are created by calling
PlayerController.Player.interactionMaster.AddInteraction( "mypackage.interactionclass", PlayerController.Player);
where:
- mypackage is the name of the package the interaction class is contained in
- interactionclass is the name of the class that extends interaction.
This mypackage.interactionclass format is maintained throughout all the code samples on this page.
The problem with this? An InteractionMaster is client-side, so you need to do a couple of things to allow this to work online. First off, you need to make your mutator execute client-side. Mainly adding these two lines to your defaultproperties:
RemoteRole=ROLE_SimulatedProxy bAlwaysRelevant=True
You also need a simulated function that will run clientside and locate the 'local' playercontroller. Let's use [[Tick]]
.
Getting the local PlayerController requires a simple call to Level.GetLocalPlayerController().
Now we have a mutator that executes client-side, a function that can locate the local playercontroller, and we know how to create an interaction. Put it all together and you should have something that looks like:
class icu_mut extends Mutator; var bool bAffectSpectators; // If this is set to true, an interaction will be created for spectators var bool bAffectPlayers; // If this is set to true, an interaction will be created for players var bool bHasInteraction; function PreBeginPlay() { //Log("ICU Mutator Started"); // Always comment out your logs unless they're errors } simulated function Tick(float DeltaTime) { local PlayerController PC; // If the player has an interaction already, exit function. if (bHasInteraction) Return; PC = Level.GetLocalPlayerController(); // Run a check to see whether this mutator should create an interaction for the player if ( PC != None && ((PC.PlayerReplicationInfo.bIsSpectator && bAffectSpectators) || (bAffectPlayers && !PC.PlayerReplicationInfo.bIsSpectator)) ) { PC.Player.InteractionMaster.AddInteraction("ICU.icu_interaction", PC.Player); // Create the interaction bHasInteraction = True; // Set the variable so this lot isn't called again } } DefaultProperties { bAffectSpectators=false bAffectPlayers=true RemoteRole=ROLE_SimulatedProxy bAlwaysRelevant=true }
Now you have the Mutator part done.
Adding an Interaction through a GameRule.
Using that first block of code, you can add an Interaction through a GameRules actor as well. Adding an interaction is possible at any point that you have access to the PlayerController.
PlayerController.Player.interactionMaster.AddInteraction("mypackage.interactionclass", PlayerController.Player);
This is essentially the same code just contained in a GameRules instead of a Mutator. It is simply to show an additional way of appending an Interaction to the PlayerController's InteractionMaster.
function bool OverridePickupQuery(Pawn Other, Pickup item, out byte bAllowPickup) { local PlayerController PC; PC = PlayerController(Other.Controller); // Run a check to see whether this mutator should create an interaction for the player if ( PC != None && ((PC.PlayerReplicationInfo.bIsSpectator && bAffectSpectators) || (bAffectPlayers && !PC.PlayerReplicationInfo.bIsSpectator)) ) { PC.Player.InteractionMaster.AddInteraction("mypackage.interactionclass", PC.Player); // Create the interaction bHasInteraction = True; // Set the variable so this lot isn't called again } //...Rest of the OverridePickupQuery code.
Adding an Interaction through a new third party class.
Instead of making the mutator client side, you can have the mutator create a new class that is responsible for creating the interactions. Here's an example of using this method:
class SuperWeaponBarMutator extends Mutator; function PostBeginPlay() { Spawn(class'SWBReplicator'); } defaultproperties { GroupName="SuperWeaponBar" FriendlyName="Super Weapon Bar" Description="Creates a new look and feel for the weapon bar." }
Here's the 3rd party class:
class SWBReplicator extends ReplicationInfo; var bool bUpdated; simulated function Tick(float dt) { if (Level.NetMode != NM_DedicatedServer && Level.GetLocalPlayerController() != None && !bUpdated) { //Add extra conditions if needed. Level.GetLocalPlayerController().Player.interactionMaster.AddInteraction("SuperWeaponBar.SuperWeaponBarInteract", Level.GetLocalPlayerController().Player); bUpdated = true; } }
Putting Interactions to use
Interactions can do a whole load of stuff though, so choose a section relating to what you want to do:
Will: Any problems understanding this? Anything I could do to make it clearer? If so: tell me, or do it yourself
Bjorn: I've removed the .Player.Actor.Pawn in PC.Player.Actor.Pawn.PlayerReplicationInfo because the PRI is already available from the PlayerController (Controller). Oh and, this document was just what I needed and does a good job at explaining stuff to the point, thanks a bunch!
Boksha: Some minor changes in the comments. Anyway, if the local player is not affected by this mutator, the foreach command will go on forever, slowing it down more than it should. Someone think of a workaround for this. Also, damnation. Apparently Mychaeel added something after I load the editing page, so I removed it. Here it is:
Mychaeel: A thought just occurred to me: If the only purpose of creating an interaction from a mutator is being able to paint something on the HUD, there might be a less problem-prone way to achieve the same goal (from what I gather interactions don't play well when players try to connect to a different server using the "open" console command). I didn't look into that yet so I might be way off, but could it be possible to exploit LocalMessage's RenderComplexMessage function to paint arbitrary stuff on the client's HUD?
Rork: I downloaded the ICU mutator that is supposed to work online, but it doesn't: clients don't get the interaction, except if they are on the server (listen server). Do I live in twilight zone ?
Wormbo: The usual preparations for client-side mods apply: Add the package to the ServerPackages list on the server and use UCC MasterMD5 to calculate the package's checksum before running the mod on the server.
Rork: Oh well I feel stupid now... Thanks Wormbo.
Mychaeel: At least the "ucc mastermd5" step should be obsolete as of UT2003 patch 1 – isn't it, Wormbo?
Wormbo: Oh right, forgot that.
Burtlo: I was about to create a Mutator to include an interaction and I thought there must be a way to perform this in a GameRules. It worked. So I thought it would be appropriate to mention it here, that it is obviously possible to implement. And perhaps slightly more effecient because it's not on the Tick method. But that is something I don't know enough about to make a comment. Oh sh*t. This is interaction from a mutator. Is there even one for gamerule or other. Perhaps this should be retitled or moved to [Implementing an Interaction]?. Boy I'm slow.
NickR: For adding an interaction via a GameInfo, I would use the PostLogin function.
Burtlo: So NickR would you write one for GameInfo, and then we could perhaps create a series of pages for registering/adding interactions and include all the ways of doing it. From the Keypress Interactions I found this one, but it seems narrow to say that it can only be done in a Mutator.
Melaneus: I have been creating extra classes that the mutator spawns and makes them responsible for giving out interactions. I make one that runs through the Level.Controller list, and when it finds a new controller it spawns another class that's a subclass of ReplicationInfo and is owned by the new controller. It then calls a function that replicates to the client, and in this function is the call to create the interaction. I don't know... is running through the Level.ControllerList every tick going to slow things down much? Was my explanation a bit too weird? You could also just have a separate class that ran a tick that added an interaction only when Role < Role_Authority or it's a listen or standalone.
Newbie: How do I get keypress events in UT1?
porkmanii: In reply to Mychaeel's previous comment about "less problem-prone ways to paint to the HUD": In UT2004 (the [UDN] says v3323 and up), HudOverlays (Engine.HudOverlay) can be used to render to the HUD. My mutator (Alternate HUDs) finds the local PlayerController (similarly to the example on this page), and then spawns a HudOverlay and adds it to the player's HUD's array of HudOverlays (HUD.Overlays). Once the HUD is done rendering (in HUD.PostRender()), it calls HudOverlay.Render() for each HudOverlay in the Overlays array. This seems to work just as well in network games as Instant Action.
Tango Whiskey: I keep seeing the tick function and am fuzzy on exactly what it's doing in terms of being called. At the top of the tree, it's an event in Actor. 1. Is the Engine calling actor.tick or is actor.tick simply available to be called. 2. For the tick function in this mutator, what is passing it the DeltaTime parm and how? Do I really need to use it here?
porkmanii: Actor.Tick() is called by the engine each and every tick/frame. DeltaTime is the time that has passed since the last tick/frame. According to Actor/Methods, Tick is not called for any actor which has bStatic=True.