Problems With Interactions
Whoohooo!! Good news...
Interactions CAN self-destruct, from patch 2166 onwards. This means that if you are using Patch 2166 (and so are the other users of your mod), you dont have to worry about complicated level change detection, just remove the Interaction in the NotifyLevelChange
function. Check this out:
event NotifyLevelChange() { Master.RemoveInteraction(self); // (destroy any actors spawned by this interaction) // (clean up any uscript objects that have been loaded) }
Simple enough, huh?
This means the rest of this article is obsolete, unless you want your interactions to be compatible with older versions of the game, which might a good idea.
No Self-Destruct?
Quite simply, Interactions do not kill themselves at the end of a game, whether you "Forfeit" it, or leave a server. This can cause problems when connecting to other servers.
In pre-2164 builds of UT2003 it was impossible to detect a level change before it actually occured. Later builds have the NotifyLevelChange
event which allows the Interaction to destroy itself before the level changes. The remaining part of this page deals with pre-2164 builds which can't use this event.
Removing Interactions
I have managed to PARTIALLY solve this problem. By partially I mean that it works, except if you use the OPEN command to connect to a server. It will fail with a "Corrupt Connection" error. Though if you run the same command after the faliure, it will work.
To do this, do the following:
Add these two variables at the top of your interaction:
Var GameReplicationInfo GRI; Var String LevelName;
Add this into your Initialize() function:
log("Interaction Initialized"); foreach ViewportOwner.Actor.DynamicActors(class'GameReplicationInfo', GRI) If (GRI != None) Break; LevelName = Left(String(ViewportOwner.Actor.PlayerReplicationInfo), InStr(String(ViewportOwner.Actor.PlayerReplicationInfo), "."));
Put this in for Tick:
Function Tick(Float TimeDelta) { Local int i; Local bool FoundIt; If (GRI == None) //If the GRI becomes none for some reason, restore it. RestoreGRI(); //If the level name has changed If (Left(String(ViewportOwner.Actor.PlayerReplicationInfo), InStr(String(ViewportOwner.Actor.PlayerReplicationInfo), ".")) != LevelName) { Log("Removing Self -- Level Different"); ViewportOwner.InteractionMaster.RemoveInteraction(Self); //Destroy self } //Start at newest, and work backwards to oldest. Means the newest one will always be the one that stays. For (i = ViewPortOwner.LocalInteractions.Length - 1; i >= 0; i--) { If (ViewPortOwner.LocalInteractions[i].IsA('icu_interaction')) { If (!FoundIt) { FoundIt = True; } Else If (FoundIt) { Log("Dupicate Found -- Destroying!"); ViewportOwner.InteractionMaster.RemoveInteraction(ViewPortOwner.LocalInteractions[i]); //More than one, destroy et! } } } }
And add this in DefaultProperties (so Tick is executed):
bRequiresTick=True
Now you have a self-destructing Interaction. Well, pretty much.
No Self-Destruct solution for Mutators
It seems that I've solved the Interaction problem at least for Mutators. Here's all the solution:
First, we need to make sure that when restarting the round (while using our mutator) Interaction is not duplicated. Let's handle this in Mutator's Tick() function:
simulated function Tick(float DeltaTime) { local PlayerController PC; local Interaction Inter; local int i; // If the player has an interaction already, exit if(bHasInteraction) { return; } foreach DynamicActors(Class'PlayerController', PC) { // If local player is found if(Viewport(PC.Player) != None) { for(i = 0; i < PC.Player.LocalInteractions.Length; i++) { // If Interaction is already exists if(PC.Player.LocalInteractions[i].IsA('MyInteraction')) { // Remove it PC.Player.InteractionMaster.RemoveInteraction(PC.Player.LocalInteractions[i]); break; } } // Create the new interaction Inter = PC.Player.InteractionMaster.AddInteraction("MyInteraction", PC.Player); // If Interaction is successfully created if(Inter != None) { // Register Mutator on it MyInteraction(Inter).RegisterMutator(self); } bHasInteraction = true; return; } } }
Then add this two functions and one variable to the MyInteraction class:
var Mutator M; function RegisterMutator(Mutator mut) { // Set instance of the mutator that has add this Interaction M = mut; } Function Tick(Float TimeDelta) { // If mutator that responsible for // adding this Interaction is gone if(M == None) { // Remove this Interaction (destroy self) Master.RemoveInteraction(Self); } } defaultproperties { bRequiresTick=True }
So the whole thing works like that: when playing the game with the mutator that adds your Interaction, it first checks is this Interaction already exists on this client. If so, mutator removes it, and then adds a new one (maybe it's better to reuse an old one . So the previous Interaction is gone. When a player reconnect to another game or leave current game, the mutator is goes irrelevant and Interaction (that still exists on the client) destroy itself in the first Tick() call on the next game.
Will: Any problems understanding this? Anything I could do to make it clearer? If so: tell me, or do it yourself
Bjorn: If I'm adding my Interaction from a mutator or playercontroller, I'll be able to remove it on their Destroyed call, or not?
Wormbo: Actors are not Destroy()-ed upon level change. They are simply garbage-collected without the Destroyed() function ever being called. You don't have a real chance to catch that in the Interaction.
Bjorn: Hmm, that seems stupid to me. Oh well, then this hack is for me too...
32_Pistoleta Somebody, correct my English please on No Self-Destruct Solution for Mutators 8) At least this solution works perfectly for me. Any notes?
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. I didn't look into that yet so I might be way off, but could it be possible to exploit LocalMessage (UT)'s RenderComplexMessage function to paint arbitrary stuff on the client's HUD?
Wormbo: That sounds ok, but there's a small problem: RenderComplexMessage isn't called anywhere in UT2k3. Only UT1 chat messages (and other messages appearing in the chat window) had a chance to use this function.
Mychaeel: Drat. Well, so much for this. Let's keep looking...
Boksha: Darnit. If the "open" command doesn't work with one try, so does the new in-game server browser. Bah! Epic should have added in some native code that calls an event for all objects when you switch to a different URL. (Like an "event ShovedInGarbageCollect()" or something) Or heck, maybe even only for actors. It'd be enough. (Since actors often keep references to Objects anyway). Also, all interactions have a direct reference to the InteractionMaster, called Master.
Will: I saw something posted in #unrealwiki about lists of changes in an upcoming patch, it was something like "LevelChange now propgated through interaction and menu subsystem" (I forget the actual meaning). Does this mean this page will soon become pointless? I hope so
Wormbo: Currently only the Console interaction gets the LevelChange(), but that patch will really make the event available to all Interaction subclasses. BU recently posted the complete list of changes on their front page.
Boksha: Removing interactions in a LevelChange works. I've done it, and no corrupt connection appeared. I haven't tested it thoroughly though. (read: I haven't tried joining a server through the "open" console command. If that works, evenrything is fine )