# Sega 500 Remote Demo Charges Jeff “Ezeikeil” Giles

## Presentation on theme: "Sega 500 Remote Demo Charges Jeff “Ezeikeil” Giles"— Presentation transcript:

Sega 500 Remote Demo Charges Jeff “Ezeikeil” Giles jgiles@artschool.com http://gamestudies.cdis.org/~jgiles

Last Time We discovered the joy of remote demolitions charges and how to chain them together to set them of in mass.

Last Time In effect, we set them up so that if they took any damage, they would detonate. Hence, they only had to be placed to close proximity to one another.

Last Time But we had a problem, if one was too far from the group, we couldn’t detonate it remotely. Just the last charge that was deployed. These remaining charges had to be set off by taking damage, there was no other way.

Why was this? This happened because we only kept a handle to the last charge that was deployed. None of the others.

Today, We’re going to fix that. The plan is to be able to iterate over all the deployed charges and detonated any ONE of them at will.

“How?” you ask? In a nutshell, we’re going to create a linked list of charges and iterate through the deployed charges. In other words, every charge that the player deploys, that charge knows about other charges the player deployed.

Getting Started The first thing to note is that the core of this code is functionally the same as in Lesson 50. Hence only the changes and interesting bits will be covered.

Getting Started The first thing to do is provide the charges with a method to know about each other. Working in a list, a charge only needs to know about it’s immediate neighbours.

Getting Started Hence charge 1 knows about charge 2, charge 2 knows about charges 1 and 3 Charge 3 only knows about charge 2. 1 2 3

Getting Started By iterating over these, we can easily and relatively quickly access all the charges in existence. We do this by providing handles to the neighbours in the list. In C++, we’d use pointers.

Creating the List So, every charge gets set up with these variables. By having both of these, we are doubly linking our list allowing us to travel forwards and backwards through it. class DemoCharge extends Projectile; var DemoCharge nextCharge; var DemoCharge prevCharge;

Creating the List So when our projectile gets spawned by the weapon, we use the following function projectile SpawnProjectile(Vector Start, Rotator Dir) { local DemoCharge tempc4; tempc4=spawn(class'DemoCharge',instigator); tempc4.Velocity= tempc4.Speed * Vector(Dir); if(c4List == none) c4List=tempc4; else c4List.AddCharge(tempc4); return tempc4; } Where c4List is the handle to the list itself.

Creating the List But the only interesting thing in this function happens here: if(c4List == none) c4List=tempc4; else c4List.AddCharge(tempc4); The first charge in the list Add a charge to the end of the list

Creating the List We create and addcharge method in our democharge class which appends a charge to the end of the list. function AddCharge(DemoCharge dc) { if(nextCharge==none) //if list is empty, add charge to top { nextCharge=dc; } else nextCharge.AddLinkedCharge(dc,self); }

Creating the List Since UT2k3 doesn’t provide us with pointers, nor a method to access specific memory locations, I found this the easiest way to create a dynamically sized linked list. In effect, we have a handle to the lists tail node as our entry point.

Creating the List The AddCharge method simple adds a charge to the end of the list. But if it’s the 1 st node, it creates it. Hence you may have noticed this guy

Creating the List In effect, this function recursively calls itself until it reached the end on the list (a node == to none) and sets it to the new charge. By passing in self as a parameter, the next charge know about who came first.

Selecting a Specific Charge So, at this point, the list knows about all the charges as they get added…we’ll come back to this in a bit. The first thing to note is that there are some changes to how the player interfaces with this weapon.

Selecting a Specific Charge In effect, I wanted the altfire for selecting which charge to detonate. And the primary fire for deploying charges AND detonating the selected charge.

Selecting a Specific Charge What? That’s crazy talk having 2 modes of operation for a single button? Are you nuts! In most arguments, this would be a fair question. But here, it’s really a simple solution.

Selecting a Specific Charge Can you say “state dependant programming?....I knew you could”. That’s right, our old friend. In effect, we set it so that if the player is looking at a charge (not in 1 st person), then the fire button functions differently.

Selecting a Specific Charge Start by making a change in the RemoteDetonation class. class RemoteDetonation extends WeaponFire; function DoFireEffect() { DemoPack(weapon).SelectCharge(); super.DoFireEffect(); } A new method in the weapon. Changed from detonate

Selecting a Specific Charge And the weapon defines the SelectCharge which does just what the name promises, selects the next charge and cause a state change in the weapon. To get this to work, we use the selectedC4 variable as a handle to the charge we are currently looking at.

Selecting a Specific Charge function SelectCharge() { if(pawn(owner).Controller.isa('playercontroller')) { if(selectedC4 != none) { playerController(pawn(owner).Controller).BehindView(true); playerController(pawn(owner).Controller).SetViewTarget(selectedC4); gotostate('remoteDet'); } else { selectedC4=c4List; playerController(pawn(owner).Controller).BehindView(false); playerController(pawn(owner).Controller).SetViewTarget(owner); gotostate(''); }

Selecting a Specific Charge Nothing overly fancy, we just set our view target according to what we are looking at. Notice that if there are no charges deployed, or we run off the end of the list (c4List ==none), we reset our view to the player.

Selecting a Specific Charge But it does cause a state change to remoteDet state. The reason for this is that we need to redefine 2 functions which are key to the operations of this weapon, SpawnProjectile and SelectCharge.

Selecting a Specific Charge SelectCharge at 1 st glance looks almost identical to the default declaration…and the difference is subtle, but critical. It’s within the state that we actually iterate over the list. And it provides us a way back to the default state.

Selecting a Specific Charge function SelectCharge() { if(pawn(owner).Controller.isa('playercontroller')) { if(selectedC4 != none) { selectedC4=selectedC4.nextCharge; playerController(pawn(owner).Controller).SetViewTarget(selectedC4); } else { selectedC4=c4List; playerController(pawn(owner).Controller).BehindView(false); playerController(pawn(owner).Controller).SetViewTarget(owner); gotostate(''); } RemoteDet State Selected is next charge New view Return to player pawn

Selecting a Specific Charge Where the default selectcharge method looks at the end of the list. Now as mentioned, this state also overrides the SpawnProjectile method. In effect causing the selected charge to detonate as opposed to deploying another.

Selecting a Specific Charge function projectile SpawnProjectile(Vector Start, Rotator Dir) { selectedC4.Detonate(); if(selectedC4 != c4List) selectedC4.Destroy(); else { //cant destroy the head of the list directly or //or we loose our handle into the rest of the charges c4List=c4list.nextCharge; selectedC4.Destroy(); selectedC4=c4List; }

Selecting a Specific Charge playerController(pawn(owner).Controller).BehindView(false); playerController(pawn(owner).Controller).SetViewTarget(owner); gotostate(''); return none; } Notice that changes done here are completely transparent to the calling method in the DeployDetpack class. Aren’t states are sweet!

Selecting a Specific Charge So what’s going on? Well, we had to do some strange stuff here to ensure the list maintained it’s integrity. In short, we don’t allow charges to destroy themselves.

Selecting a Specific Charge The call to the Detonate method in the DemoCharge class is defined as: function Detonate() { prevCharge.nextCharge=nextcharge; nextcharge.prevCharge=prevcharge; BlowUp(location); }

Selecting a Specific Charge We could call destroy on it here after we redirect the handles, and it works in all but one case. The end node…or c4List. If we destroy that, we loose our entry point into the list…and that’s bad!

Selecting a Specific Charge So, for simplicity I simply said that charges don’t clean up after themselves. The onus is on the weapon.

Selecting a Specific Charge if(selectedC4 != c4List) selectedC4.Destroy(); else { c4List=c4list.nextCharge; selectedC4.Destroy(); selectedC4=c4List; } The entry point is c4List. Never destroy it. Just redirect it to the next charge in the list, then destroy the selectedc4 (the old entry point). Otherwise, destroy the node. The detonate has already redirected the handles.

Selecting a Specific Charge Since the charge has been detonated, return to the player. Returns none since we don’t create a projectile here. playerController(pawn(owner).Controller).BehindView(false); playerController(pawn(owner).Controller).SetViewTarget(owner); gotostate(''); return none;

Selecting a Specific Charge Notice though that in this version, we can’t have the charges destroy/detonate themselves as doing so would break the chain. Hence, we can’t allow them to be destroyed by any method other than remotely.

Selecting a Specific Charge So we have to gut our takedamage function. event TakeDamage( int Damage, Pawn EventInstigator, vector HitLocation, vector Momentum, class DamageType) { // super.TakeDamage( Damage, EventInstigator, HitLocation, Momentum, DamageType); // blowup(HitLocation); }

Selecting a Specific Charge And in the blowup function remove the call to destroy. function BlowUp(vector HitLocation) { super.BlowUp(HitLocation); PlaySound(sound'WeaponSounds.BExplosion3'); Spawn(class'RocketExplosion',,, HitLocation, rotator(VNorm)); Spawn(ExplosionDecal,self,,HitLocation, rotator(-VNorm)); if(Base.IsA('Pawn')) Damage *=2; //double damage if I'm stuck to a pawn //destroy(); }

What do we have? At this point, I should be able to deploy a bunch of charges and view which charge is selected (the one on the left here)

What do we have? And detonate them one by one, in any order I please.

Next up… We’re going to add some polish to this weapon. The 1 st thing we’re going to do it make it look like we are viewing the deployed demo charge with a remote camera by overlaying some static onto the HUD.

Remote viewing Now doing this is kind of an interesting trick as we are not actually going to play with the HUD…at all. UT provides us with a class called CameraOverlay which we can use to render a material directly to the players view.

Remote viewing In effect, between the HUD and the world. And doing so is actually really easy…the hard par is finding suitable materials to make the effects work for us.

Remote viewing So getting these to work, well the 1 st thing that we need an instance of the cameraoverlay class in the Weapon (DemoPack)… Nothing new here. var private CameraOverlay co;

Remote viewing And then we want the effects to toggle on an off when we are looking at the charges only. In other words, we toggle it every time we enter or leave the state: remoteDet. Knowing this, lets get it to show up …

Remote viewing This part of the magic happens in the BeginState function. function BeginState() { if(pawn(Owner).Controller.IsA('playercontroller')) { if(co==none) { co=new class'CameraOverlay'; co.OverlayMaterial=material'XGameShaders.ModuNoise'; playercontroller(pawn(Owner).Controller).AddCameraEffect(co); co.OverlayColor.r=0; co.OverlayColor.B=0; } Create a new overlay material, once only

Remote viewing function BeginState() { if(pawn(Owner).Controller.IsA('playercontroller')) { if(co==none) { co=new class'CameraOverlay'; co.OverlayMaterial=material'XGameShaders.ModuNoise'; co.OverlayColor.r=0; co.OverlayColor.B=0; playercontroller(pawn(Owner).Controller).AddCameraEffect(co); } Notice that this is not an actor, so we need to use ‘new‘ to create this object. Specify a material In three steps...but…

Remote viewing function BeginState() { if(pawn(Owner).Controller.IsA('playercontroller')) { if(co==none) { co=new class'CameraOverlay'; co.OverlayMaterial=material'XGameShaders.ModuNoise'; co.OverlayColor.r=0; co.OverlayColor.B=0; playercontroller(pawn(Owner).Controller).AddCameraEffect(co); } We can also yutz with the render colours

Remote viewing function BeginState() { if(pawn(Owner).Controller.IsA('playercontroller')) { if(co==none) { co=new class'CameraOverlay'; co.OverlayMaterial=material'XGameShaders.ModuNoise'; co.OverlayColor.r=0; co.OverlayColor.B=0; playercontroller(pawn(Owner).Controller).AddCameraEffect(co); } And lastly add the effect to the player controllers view.

Remote viewing Do note however that we can only ever have one camera effect active at one time. It tried to have 2 overlays at one time and it resulted in a crash of UT.

Remote viewing Which will yield: Night vision anyone?

Remote viewing Now you can’t really see it in the image, but there is in fact a static effect being played. Resulting in a fairly convincing…and cheap….night vision effect.

Remote viewing Now, at this point, once the effect is turned on, it never turns off. We need to remove the effect from the display when we stop viewing the charge. This is simply a call from the endstate function:

Remote viewing And they don’t get much simpler than that. function EndState() { playercontroller(pawn(Owner).Controller).RemoveCameraEffect(co); }

Remote viewing Feel free to play around with some of the render colours and various textures, you should be able to get some cool effects really easily.

And now… We’re going to deal with the charges always being attached to the bots butt. Hopefully making the bots not look like they are wearing “Huggies”.

To the Bone… So, in effect, we are simply going to something rather similar the what we did in the lesson of combos. We’re going to use the attachtobone function to bind the charges to the bot.

To the Bone… This can be done via the ProcessTouch function where we find the closed bone and stick to that…hopefully. Here’s the magic:

To the Bone… function ProcessTouch(actor Other, vector HitLocation) { local vector bonedir, boneloc; local name daBone; local float dist; bonedir = normal(location -other.Location); //just deployed so can stick to anything if(Pawn(Other) != None && (Other != Instigator )) { setbase(other); daBone=other.GetClosestBone( location,bonedir,dist); boneloc=other.GetBoneCoords(daBone).origin; setlocation(boneloc); other.AttachToBone(self,daBone); }

To the Bone… Heres’ the breakdown… Returns to us the closest bone in the bot, by name only. daBone=other.GetClosestBone( location,bonedir,dist); Btw: there are NO docs on this function that I can find.

To the Bone… Once we have the closest bone we get its absolute location in worlds space with: GetBoneCoords (defined in object) returns a coord which has the axis included with it, but we are only interested in the location. boneloc=attachedP.GetBoneCoords(daBone).origin;

To the Bone… As you can see, we again ask for a specific bone by name to get it’s position And then from there: setlocation(boneloc); attachedP.AttachToBone(self,daBone);

To the Bone… This is a bit redundant and I could likely get away with just doing the attachtobone, but…OCD being what it is…

To the Bone… Look! Sticky bombs! And as you can see, they attach all over the bots…

To the Bone… However, there is a bit of a problem. If you watch the bot run around some, you’ll see that, somehow, the charges can fall off the bot. Why? Who knows…but we need to deal with it.

To the Bone… So, We have to push a few variables in to global space so they persisit. var private vector bonedir, boneloc; var private name daBone; var private float dist; var private pawn attachedP;

To the Bone… And the modify the processtouch to reflect this: function ProcessTouch(actor Other, vector HitLocation) { bonedir = normal(location -other.Location); if(Pawn(Other) != None && (Other != Instigator )) { attachedP=pawn(other); setbase(attachedP); daBone=attachedP.GetClosestBone( location,bonedir,dist); boneloc=attachedP.GetBoneCoords(daBone).origin; setlocation(boneloc); attachedP.AttachToBone(self,daBone); gotostate('attachedto');

To the Bone… Make special notice of the state change! This state will allow us to disable the processtouch …simply turning it into a stub. gotostate('attachedto');

To the Bone… And then, with ever tick of this state, we reposition the charge to match the bones position in the world…using tick. function tick(float deltatime) { daBone=attachedP.GetClosestBone( location,bonedir,dist); boneloc=attachedP.GetBoneCoords(daBone).origin; setlocation(boneloc); attachedP.AttachToBone(self,daBone); }

To the Bone… Now, using tick for this kind of thing is sub- optimal…Why? Well, as you know tick gets called with every game update… A good rule of thumb is to avoid doing processing in the tick function…but sometimes, you just have to pay the piper.

To the Bone… However, by making the updates state specific, I have reduced to cost somewhat down to only when needed. If you run it now, you’ll see that the charges stick to the bot much better.

That’s all folks Thus ends the presentation part for today. I hope this is a useful overview of how to use a linked list in UT as well as a reminder of the power of state dependant programming.