I love the smell of UnrealEd crashing in the morning. – tarquin

Legacy:Carrying Multiple Flags

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

Carrying multiple flags adds a fun element to flag-related game types, but it's a bit tricky to pull off because certain things weren't quite designed to work that way. There are two parts to making it work: getting the visual effect of the player carrying all those flags, and working around the fact that PlayerReplicationInfo only has the one HasFlag field.

The visual effect part works completely differently in UT and UT2003/04, because the regular effect of carrying a single flag is different.

The visual effect in UT[edit]

I'll discuss how this was done in CTF4.

The tricky thing about this effect in UT is that when you see a player carrying a flag, the flag you see isn't the real CTFFlag object; the real flag stays where it was and just goes invisible. The effect is apparently some hard-wired thing that the game automatically draws whenever the PlayerReplicationInfo's HeldFlag field has something in it. So in order to immitate that, I created a HeldFlag class (subclassing Effects (UT)) to be the fake carried flag.

In CTF4Flag's Held.BeginState function, it looks at the other flags in the game to see if the new holder is also holding one of them. If so, it creates a HeldFlag object. There are actually three HeldFlag classes, each with a different offset in its default properties. Later flags have a bigger offset than flags picked up earlier.

To get the HeldFlag to follow the player, at first I tried using PHYS_Trailer, but the flag didn't maintain the correct relative location when the player turned. So I ended up having to use a simulated Tick function to constantly update the flag's position. The weird thing is it still doesn't exactly match the behavior of the game's built-in held flag effect, so I have no idea what method they used.

simulated function Tick(float DeltaTime)
{
	local vector X,Y,Z;
 
	GetAxes(Owner.Rotation,X,Y,Z);
	SetLocation(Owner.Location - (X * (28+Distance)) + (Z * 20));
	SetRotation(Owner.Rotation);
	if (Pawn(Owner).PlayerReplicationInfo.HasFlag == None)
		Destroy();
}

Distance is of course the offset value, and goes up by 10 for each additional held flag.

The visual effect in UT2003/04[edit]

This one is much easier, because the flag object is actually attached to the player. In fact the behavior is implemented one level down, in the GameObject class. In Held.BeginState, it calls the UnrealPawn holder's HoldGameObject function. That function anchors the flag to either the pawn itself or one of its bones, depending on whether there's anything in its GameObjBone field.

So in Flag Domination, my FDFlag class simply lets all that stuff happen, and then calls SetRelativeLocation to change its offset based on any other flags that might already be held.

game = FDGame(Level.Game);
if (game != none) {
	if (game.bMultiCarry) {
		for (i = 0; i < 3; i++) {
			if (game.Bases[i] == none) continue;
			if (game.Bases[i].myFlag.Holder == Holder)
				holderCount++;
		}
		if (holderCount > 1) {
			relLoc = RelativeLocation;
			if (relLoc == vect(0,0,0))
				relLoc = vect(0,-10,0);
			SetRelativeLocation(relLoc * holderCount);
		}
	}
}

Getting around HeldFlag[edit]

This works the same in UT and UT2003/04, and there are two parts to it. One is to make sure that any time you're checking to see if a player is carrying a particular flag, you check the flag's Holder or HolderPRI, and not the player's HeldFlag field, since HeldFlag could point to another flag that the player is also carrying.

The second is to make sure flags get dropped properly. You just need to make sure that any time a flag's Drop method is called, you go through all the flags and make sure any other flags with the same holder also get dropped. And don't forget to avoid the potential infinite loops where flags are continuously telling each other to Drop.

Here's how I did it in Flag Domination 2003/04. Note that the additional flags also give themselves some additional random velocity so they don't all land in the same place.

if (Holder != none) {
	if (HolderPRI.HasFlag == self) {
		game = FDGame(Level.Game);
		if (game != none)
			if (game.bMultiCarry)
				for (i = 0; i < 3; i++) {
					if (game.Bases[i] == none) break;
					if ((game.Bases[i].myFlag.Holder == Holder) &&
						(game.Bases[i].myFlag != self)) {
						game.Bases[i].myFlag.Drop(newVel);
					}
				}
	}
	else {
		if (VSize(newVel) < Holder.GroundSpeed/2)
			newVel = Holder.GroundSpeed*vector(RotRand());
		newVel *= 1 + 2*FRand();
	}
}

This was done differently in CTF4 for UT2004 (it's actually in the base 4-team code, and can be used by other 4-team mods). It uses a custom PlayerReplicationInfo class, with a HasFlags dynamic array.