Game Development Community

Complicated Reload System

by Chris Byars · in Torque Game Engine · 04/21/2006 (9:18 am) · 28 replies

Yes, this system is complicated, but it's quite powerful for what I need it for, so no bad comments on how it's done overall. :P I have a problem with it. An odd problem. Basically how it works is a player presses R, which sends a command to the server activating a reload command:

function serverCmdWeaponReload(%client)
{
   %currentWeapon = %client.player.getMountedImage($WeaponSlot);
   %currentWeapon.ReloadAmmoClip(%client.player);
}

Which in turn, for whichever weapon you have on you, checks a bunch of things making sure its safe to reload and then does it to that weapon using %obj.manualSetImageState. (edited out huge code block, unnecessary)
Page «Previous 1 2
#1
04/21/2006 (9:18 am)
Difficult to follow, I know. My problem lies with manualSetImageState, a resource designed by Josh Moore. What it does is manually sets an image's state. (What a surprise) In this case, Reload.

Works all fine and dandy, until in networked play, a client or two join. All of a sudden, the server player's weapon stops obeying the %obj.manualSetImageState(%slot, Reload); command. The clients have no trouble, theirs still sets the state correctly and animates, etc. The server's ceases to function. Only that though, everything else works fine.

I am at a loss as to what I should do to make this set the state correctly for the server player. It appears to me that the command is unsure which or whose weapon to manually set the state for. How can I clarify it?

Extreme thanks.
#2
04/21/2006 (9:39 am)
Had the same prob with manually changing an image across the network, haven't looked into it as yet. In my game it only doesn't work if the host and client are holding the same weapon so i'm pretty sure of what has to be done. Sorry I couldn't be of more help.
#3
04/21/2006 (10:03 am)
How are you referencing the %shape?
%shape.manualSetImageState(%slot, %imageState);

Are you using something like this?
%client = ClientGroup.getObject( %i );
#4
04/21/2006 (10:20 am)
According to the code above, I'm using:
%obj.manualSetImageState(%slot, Reload);

%obj is the player that has the XM8Image in the %slot. But it's not very specific. I don't know how to make it know to act upon ONLY the client that called the reload.

Also upon further testing, it doesn't matter what weapon the client is holding, the server player's and client player's sometimes both work, sometimes only one works. Very odd.
#5
04/21/2006 (2:22 pm)
All I could say is that you _can_ write a complex clip based ammo/reload system without using manual image state changes, as I have a flawless one. But I am using manual Image state changes for some other stuff, so I guess I should go test mine out too, see if I'm get the same problems. I didn't use Josh Moore's resource, though his seems less a hack than my setup.
#6
04/21/2006 (2:28 pm)
I noticed this too before I posted the resource. I just assumed the packet was getting lost thereforce the image state wouldn't get set on the client side. Does it consistantly not recieve the state change command?
#7
04/21/2006 (2:43 pm)
Nearly consistently, 1-2 times in a row of 5 it will work as it should.
#8
04/21/2006 (4:47 pm)
I've been looking over the code a little bit, and it seems it could be sketchy. Firstly I would move the position of the pack/unpack code up near the top. It seems where you said to place it is nested in something else. I also can't see what calling "setMaskBits(ImageMaskN << imageSlot);" does, as that never seems to be called unless ImageMask is already set as well. So I'm not sure if you need to be setting that or you should be using you own setup. All I did was add

ConsoleMethod( ShapeBase, setManualImageState, void, 4, 5, "(U32 slot, U32 state, bool force)"){
   object->setManualImageState(dAtoi(argv[2]), dAtoi(argv[3]), true);
}

void ShapeBase::setManualImageState(U32 imageSlot, U32 newState, bool force){
   setImageState(imageSlot, newState, force);
}

To shapeImage.cc, but I haven't tested it extensively yet, so it prolly ain't workin' right over a networked setup either.
#9
04/21/2006 (5:15 pm)
I just realized how many inconsistencies are in Josh's resource that may be contributing to the overall working-ness of it.

Each code bit seems to change from setManualImageState or manualSetImageState. I think we should choose one only.
#10
04/21/2006 (5:27 pm)
Well that doesn't really matter, though I don't know why he made the console and engine function defs different, as most console functions that are callbacks to engine functions use the exact same name. Of course a few don't aswell, either way it's not going to affect anything.
#11
04/22/2006 (7:25 am)
It's definitely an issue with the source code itself. Even calling "1662.manualSetImageState(0, Reload);" on my client it sometimes doesn't work correctly/set it right if there are other players in the game. Contrary to what it says, the resource doesn't work over a networked connection. :P Hope someone can help fix it.
#12
04/22/2006 (2:37 pm)
I don't think you should call it from the client anyways... I'll prolly look into all this later when I get into my melee code again...
#13
04/22/2006 (2:58 pm)
Argh I hate it when my post gets lost due to "the page cannot be displayed".

Anyway. The client is the one who presses "R" to send the reload command, how else is it possible for a player to reload at will if not a command sent from the client?

Currently everything else works fine with the system, it's just the manual image state that's not working all the time. I need the image state to change so the animation on my first person weapon model plays. So the problem lies with manualSetImageState, and since its all C++, I'm of little use. :/
#14
04/22/2006 (3:20 pm)
Oh I thought you mean calling it on the actual client... using a cmdToServer is fine of course.

Anyways, this is the image state machine I'm using that handles reloading pretty well
// Initial start up state
   stateName[0]                     = "Preactivate";
   stateTransitionOnLoaded[0]       = "Activate";
   stateTransitionOnNoAmmo[0]       = "NoAmmo";

   // Activating the gun.  Called when the BaseGun is first mounted and there is ammo.
   stateName[1]                     = "Activate";
   stateTransitionOnTimeout[1]      = "Ready";
   stateTimeoutValue[1]             = 0.1;
   stateSequence[1]                 = "Activate";

   // Ready to fire, just waiting for the trigger
   stateName[2]                     = "Ready";
   stateTransitionOnNoAmmo[2]       = "Reload";
   stateTransitionOnTriggerDown[2]  = "Fire";

   // Fire the BaseGun. Calls the fire script which does the actual work.
   // Timeout value will be weapons firing rate
   stateName[3]                     = "Fire";
   stateTransitionOnTimeout[3]      = "Ready";
   stateTransitionOnNoAmmo[3]       = "Reload";
   stateTimeoutValue[3]             = 0.08;
   stateFire[3]                     = true;
   stateRecoil[3]                   = LightRecoil; //Is this a screen offset effect?
   stateAllowImageChange[3]         = false;
   stateSequence[3]                 = "Fire";
   stateScript[3]                   = "onFire";
   stateEmitter[3]                  = BaseGunFireEmitter;
   stateEmitterTime[3]              = 0.12;
   stateEjectShell[3]               = true;

   // Play the reload animation, then go to noAmmo state which tries to actually refill ammo
   // (since image is forced back to ready if we do it here)
   // Timeout value will be the time it takes to reload
   stateName[4]                     = "Reload";
   //stateTransitionOnNoAmmo[4]       = "NoAmmo";
   stateTransitionOnTimeout[4]      = "NoAmmo";
   stateTimeoutValue[4]             = 3.0;
   stateAllowImageChange[4]         = false;   
   stateSequence[4]                 = "Reload";
   

   // Try to reload when we get here, will go to ready if it works 
   // (dryfire ruins reload while holding fire trigger)
   stateName[5]                     = "NoAmmo";
   stateTransitionOnAmmo[5]         = "Ready";
   stateSequence[5]                 = "NoAmmo";
   //stateTransitionOnTriggerDown[5]  = "DryFire";
   stateScript[5]                   = "onNoAmmo";

   // No ammo dry (fire currentley not used as it keeps us from firing after getting ammo)
   stateName[6]                     = "DryFire";
   stateTimeoutValue[6]             = 0.04;
   stateTransitionOnTimeout[6]      = "NoAmmo";
};

function BaseGunImage::onNoAmmo(%this, %obj, %slot){
   //echo("out of ammo");
   ReloadWeapon(%this, %obj, %slot); 
   
   //Set the ammo state of the weapon to true (if it in fact has ammo now)
   if(%obj.getInventory(%this.clip) > 0)
   %obj.setImageAmmo(%slot,true); 
}

//Function to handle clip based weapon reloading
function ReloadWeapon(%weap, %obj, %slot){
      
%neededAmmo = mAbs(%obj.getInventory(%weap.clip) - %weap.clipMax);   
%maxAmmo = %obj.getInventory(%weap.ammoType);   
   
   //We can't fully restore clip, so give what's left
   if(%neededAmmo > %maxAmmo){
    %obj.incInventory(%weap.clip, %maxAmmo);
    %obj.decInventory(%weap.ammoType, %maxAmmo);
   }
   //Restore clip normally
   else{
    %obj.incInventory(%weap.clip, %neededAmmo);
    %obj.decInventory(%weap.ammoType, %neededAmmo);
   }

   UpdateAmmoHUD(%obj);
}


//Manually reload both weapons if necessary
//Just tell the client they have no clip for the weapon
//And the state machine handles the rest
function serverCmdmanualReload(%client){	
%player = %client.player;
 
 if(isObject(%player)){
   //Dont try to reload on a full clip or if there is no reserve ammo
   //Reload anim will still play if you reloaded with a full clip (not good)
   %weap = %player.getMountedImage($PrimaryWeaponSlot);   
   if((%player.getInventory(%weap.clip) < %weap.clipMax) && (%player.getInventory(%weap.ammoType) > 0))
    %player.setImageAmmo($PrimaryWeaponSlot,false);
  
   %weap = %player.getMountedImage($SecondaryWeaponSlot);
   if((%player.getInventory(%weap.clip) < %weap.clipMax) && (%player.getInventory(%weap.ammoType) > 0))
    %player.setImageAmmo($SecondaryWeaponSlot,false);    
 }
}

It won't work on a copy paste or anything, and could be written better. I know I changed some stuff in the inventory system so it looks for "clip" instead of "ammo" for the callbacks it makes, and mebee some other stuff, but it works friggin' great in my setup.
#15
04/22/2006 (3:40 pm)
Yeah, mine works perfectly for what I need. I just need manualSetImageState to work. :)
#16
04/22/2006 (6:33 pm)
Word of advice: do not use commandToServer and commandToClient for time-critical gameplay commands/feedback. They are highly suscectible to lag, and depending on what you do, there might be sync problems, jerkiness and other undesirable effects.

If you want to allow players to manually reload weapons, use a move trigger (a button press), just like shooting and jumping, and switch the mounted image state in the player's updateMove() (look for the place where it triggers the mounted image for firing and alternate firing).
Use setImageTrigger to witch to reload state (it should be straightforward to add more image triggers to the shapeImage code). That way, when the player press the reload button, the reload animation will play right away on the client, without network delays, while the packet travels to the server.

We're finishing our first TGE game with online multiplayer support, and while everything was fairy perfect in LAN tests, oddities surfaced when we experienced the higher latency of real-world internet connections. We used some serverCmds to perform some actions, and those get an horrid delay under high ping rates, degrading the gameplay, and making some time-sensitive actions almost unusable.

Another example: we had coded support for setting a speed multiplier on the players, which is adjusted by colliding with triggers. The speed multiplier variable is networked, but that wasn't quite enough.
Under high ping values (like 100 and above), the server-side player and it's ghost would get slightly out of sync, due to the delay between setting the speed multiplier on the server, and the value changing on the client, making the client travel for a few milliseconds faster/slower than the server, and being pushed back/forth due to that.

The only solution we could find was make the triggers ghost themselves, call a different console function when something enters/leaves them, and replicate the speed adjustement code on the client-side, so speed adjustements are instantaneous on boths sides. It was a double pain, due to the unexpected re-work, and the fact we had to resort to something so hackish-sounding as ghosting triggers... so, if you are working on online multiplayer support, take lag into account in every gameplay detail you are planing.
#17
04/22/2006 (6:44 pm)
That would require a hefty re-evaluation on the way reloading is handled and calculated, and put nearly completly into the source, something I am not prepared to do. Many online games I've played, if you take on some hefty lag (500ms+), the reload will lag as well, as it will in my project if one gets such latency. That's not a big deal for me.

The entire reload function is handled in transfer between server/client because it needs to check the weapon's capabilities, current ammo, backpacked ammo, etc, and that is where it's calculated. It works perfectly over a network, even with high latency. What doesn't is the manual setting of the image state. If that were to be sorted out (fixed in the C++ source, fixing the resource by Josh Moore), I would be perfectly content.
#18
04/22/2006 (6:56 pm)
I understand.

I read the resource... don't have the TGE source here at me right now, but the lines in bold bother me somehow:

Packupdate:
if(stream->writeFlag(image.changeState))
   [b]stream->write(image.manualChangeState);[/b]

if (image.changeState) 
{
   setImageState(i, image.manualChangeState, true);	
   image.changeState = false;
}

UnPackupdate:
if(image.changeState = stream->readFlag())
   [b]stream->read(&image.manualChangeState);[/b]

if (image.changeState) 
{
   setImageState(i, image.manualChangeState, true);	
   image.changeState = false;
}

It's a wild guess, but I wonder if using plain write() and read() is OK there? Maybe it should use writeInt() and readInt(), using a fixed number of bits based on the maximum number of states (I don't know how many there are by default). I never looked up on it, but I was under the impression that the generic write/read methods will write down the specified variable at it's full precision (32-bits for an U32, as example).

If you got, like 32 states max, writing 5 bits unsigned ints should be enough. But it's no telling that's the actual reason it's not working.

You said it worked fine for the clients, but not for the server... this means remote clients can reload fine, but the localClientConnection cannot?
#19
04/22/2006 (7:00 pm)
I just tested it out and it seems that the server player is unable to set the state manually once a client joins.
#20
04/22/2006 (7:12 pm)
--EDIT--

D'oh! You edited while I was posting. I have a clue of what might be happening... just a minute.
Page «Previous 1 2