Announcement

Collapse
No announcement yet.

OutOfAmmo function and Replication Problem

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    OutOfAmmo function and Replication Problem

    Hi, I'm mainly having replication problems with some code I added to the OutOfAmmo function for my weapon. For my weapon when the ammo gets to zero I want it to give 100 ammo back. As usual this all works nicely offline, but when testing it online I discovered that when the weapon's ammo got tozero it stayed at zero I think it must be a replication problem and I have tried several things to get it to work but it just wont! What makes me even more confused is that I am using custom code within an already replicated function, (atleast I presume it is replicated as when I add the function name to the replication block it says that it is already defined in weapon). Does anyone have any ideas or know how to get this to replicate, or does anyone know of any other ways to reset ammo (that works online too). Thank you in advance.

    oh here is the code I am trying to replicate.

    Code:
    simulated function OutOfAmmo() //Poker's code from epic forums
    {
    	m = 0;
    	k = 0;
    	if (Level.NetMode != NM_DedicatedServer)
    		DestroyScorches();
    		AddAmmo(100,m);
    		AddAmmo(100,k);
    
    	super.OutOfAmmo();
    }
    edit: I changed the code to this...:
    Code:
    simulated function OutOfAmmo() 
    {
    		DestroyScorches();
                    FillToInitialAmmo();
    }
    ...and again it all works offline, but online something strange happens, when it reaches zero, it calls the destroyscorches function and sets the ammo back to the initial ammount. But it resets it back to zero if you click fire again. :S Is there any more ways to change the ammo amount once it reaches 0?

    #2
    Originally posted by INIQUITOUS View Post
    Code:
    simulated function OutOfAmmo() 
    {
    		DestroyScorches();
                    FillToInitialAmmo();
    }
    ...and again it all works offline, but online something strange happens, when it reaches zero, it calls the destroyscorches function and sets the ammo back to the initial ammount. But it resets it back to zero if you click fire again. :S Is there any more ways to change the ammo amount once it reaches 0?
    I'm not sure whether or not OutOfAmmo() is called on the server. I assume it does, but to made sure, add
    Code:
    log("Level.NetMode == NM_DedicatedServer:"@Level.NetMode == NM_DedicatedServer);
    to that function. It should be true for the dedicated server.

    The strange thing is caused by the server and client disagreeing on the amount of ammo. The server thinks it's 0, but the client sets it to 100. When you shoot, the amount of ammo is updated by the server, which comes back to the client, and reckons the amount of ammo is 0.

    A fix: call FillToInitialAmmo() on the serverside. If OutOfAmmo() is not called serversidely, do this:

    Code:
    replication
    {
      reliable if (Role < Role_Authority)
        FillToInitialAmmo();
    }
    That should replicate the function call to the server when it's called on the client.

    Comment


      #3
      Thanks for the advice, its very intersting. I can't get it to compile tho for some reason. I added the log check and it logged this:

      Code:
      ScriptLog: Level.NetMode == NM_DedicatedServer: False
      ScriptLog: DM-1on1-Idoma.MiniBlast ClientForceAmmoUpdate mode 0 newamount 0
      when I try and rebuild with the new replication it gives me this error. I don't know why it is bad tho. Can you help me more please?

      Code:
      : Error, Bad variable or function 'FillToInitialAmmo' in replication definition
      edit: in a desperate attempt I put code in to "ClientForceAmmoUpdate" to 100 on mode 0, and in the log it showed this. It stil didn't work and I let it run out of ammo twice (once with mode 0 and once with mode 1);
      Code:
      ScriptLog: DM-1on1-Idoma.MiniBlast ClientForceAmmoUpdate mode 0 newamount 100
      ScriptLog: Level.NetMode == NM_DedicatedServer: False
      ScriptLog: DM-1on1-Idoma.MiniBlast ClientForceAmmoUpdate mode 0 newamount 0
      ScriptLog: DM-1on1-Idoma.MiniBlast ClientForceAmmoUpdate mode 0 newamount 100
      ScriptLog: Level.NetMode == NM_DedicatedServer: False
      ScriptLog: DM-1on1-Idoma.MiniBlast ClientForceAmmoUpdate mode 1 newamount 0
      it looks like it knew the new ammo amount was 100 but then it changed its mind

      Edit2: I decided to just make another function that was exactly the same as the FillToInitialAmmo one I was trying to replicate and replicate the new one instead using the exact same technique. It works ! Theres one small bug where the firing sound is stopped if you are holding down the button, until you let get and click again. Thank you for your help Jrubzjeknf !

      Comment


        #4
        OutOfAmmo is called at different times both on the server and the client, so to understand where a certain bit of code should go, it’s probably a good idea to trace the flow of execution that leads to the OutOfAmmo function getting called ... here's the short and sweet as I understand it:

        On the server, it starts with the WeaponFire.ModeDoFire event, called natively from within the ModeTick event (see UWeaponFire::ModeTick() in Engine.UnLevTic.cpp). ModeDoFire calls Weapon.ConsumeAmmo, and ConsumeAmmo calls CheckOutOfAmmo, which calls OutOfAmmo if the ammo counts are zero.

        On the client it’s simpler; CheckOutOfAmmo, and subsequently OutOfAmmo, is called only from the Weapon.PostNetReceive event, which is called anytime after a variable has been replicated, in this case from the server to the client.

        What you want, then, is to make sure that whatever method you use for changing the ammo count—AddAmmo, FillToInitialAmmo, whatever—it’s done on the server and not the client, since the client needs to let the server have authority over the actual ammo count (see how AmmoCharge is declared within the replication block in the Weapon class).

        With that in mind, my first suggestion would be to keep the DestroyScorches call the way you had it originally, but place your FillToInitialAmmo or AddAmmo call inside a (Role == ROLE_Authority) check:

        Code:
        simulated function OutOfAmmo()
        {
        	if (Level.NetMode != NM_DedicatedServer)
        		DestroyScorches();
        	if (Role == ROLE_Authority)
        	{
        		AddAmmo(100,m);
        		AddAmmo(100,k);
        	}
        }
        Notice also the absence of the Super.OutOfAmmo call. In the Weapon class, OutOfAmmo is only used by rendering machines (i.e., Level.NetMode != NM_DedicatedServer) to switch away from the local player’s empty weapon. Because you’re designing your weapon never to run out of ammo, you have no need for this to happen.



        Originally posted by Jrubzjeknf View Post
        If OutOfAmmo() is not called serversidely, do this:

        Code:
        replication
        {
          reliable if (Role < Role_Authority)
            FillToInitialAmmo();
        }
        That should replicate the function call to the server when it's called on the client.
        Originally posted by INIQUITOUS View Post
        when I try and rebuild with the new replication it gives me this error. I don't know why it is bad tho. Can you help me more please?

        Code:
        : Error, Bad variable or function 'FillToInitialAmmo' in replication definition
        Naming a derived function such as FillToInitialAmmo inside the replication block of a subclass will not work. Variables and functions can only be named in the replication block of the class in which they are declared.

        --Just saw you tried creating a new function, which is a good easy way around this limitation.

        For the record though, it's better practice to avoid replicating a new function unless you really need it, and for what you're doing you shouldn't have to replicate a function from client to server, since the server authoritatively already knows what the ammo count is.



        Originally posted by INIQUITOUS View Post
        Thanks for the advice, its very intersting. I can't get it to compile tho for some reason. I added the log check and it logged this:

        Code:
        ScriptLog: Level.NetMode == NM_DedicatedServer: False
        ScriptLog: DM-1on1-Idoma.MiniBlast ClientForceAmmoUpdate mode 0 newamount 0
        What you’re seeing there is the client’s log. The server and client keep separate log files, even if both are being run on the same machine. To see the server’s log, check the server.log file (not UT2004.log—that’s the client), or if you started your dedicated server via commandline (e.g., “ucc.exe server <mapname>…”), the ucc.log file.



        Anyway, if you care to try this method, I'd be interested to know if the code above works out for you.

        Comment


          #5
          Hey Poker, thanks again. And thankyou for all the info!! I'm testing it out now in more detail on my server, now that I managed to get it to work... one way or the other! I have named it beta ofcorse because there is stil lots to do and I think I will have a go your way next.

          The server wasn't doing anything in the log, but the client was so that's why I posted that.

          here is the code I ended up using. (notice the ROLE_Authority I added )

          Code:
          replication
          {
              reliable if (bNetInitial && Role == ROLE_Authority)
                  Scorches, DestroyScorches;
                    reliable if (Role < Role_Authority)
              FillToInitialAmmoINI;
          }
          
          simulated function OutOfAmmo() //Poker's code from epic forums
          {
          	DestroyScorches();
          	if (Level.NetMode != NM_DedicatedServer)
          	FillToInitialAmmoINI();
          //log("Level.NetMode == NM_DedicatedServer:"@Level.NetMode == NM_DedicatedServer);
          }
          i'm not too sure at this point if I need all that replication stuff, but I noticed a few hundred compiles ago that some of the decals wasn't being destroyed everytime (about 1 every 100) , So I added that and I havnt seen any stray decals since. I will report back when I next change it all.

          Comment


            #6
            Originally posted by Poker View Post
            Naming a derived function such as FillToInitialAmmo inside the replication block of a subclass will not work. Variables and functions can only be named in the replication block of the class in which they are declared.
            Didn't know that. Thx for correcting me and learning me something new!

            Comment


              #7
              Poker, I tried doing what you said but online the ammo stil wasn't replicated nvm, i'll stick to the other way for now, I do however have another problem that i think you would be able to help with I've been working to get the scorches destroyed if the player dies, (like the grenades and spidermines do), I've used this code, copied from the ONSMineLayer.

              Code:
              simulated function Destroyed()
              {
              	local int x;
              
              	if (Role == ROLE_Authority)
              		for (x = 0; x < Scorches.Length; x++)
              			if (Scorches[x] != None)
              				Scorches[x].Destroy();
              
              	Super.Destroyed();
              }
              It works great offline, but again, online they arent being destroyed. Again its in a function that is replicated. I must be missing something really simple. Can you or anyone help me?

              Comment


                #8
                What are Scorches? Emitters or xEmitters? In that case, they only exist on the client, not the server. If so, replace the
                Code:
                if (Role == ROLE_Authority)
                statement with
                Code:
                if (Level.NetMode != NM_DedicatedServer)
                That means that the code will be executed in single player, on listen servers and clients.

                Comment


                  #9
                  Thanks i'll try that, sorry I forgot to say again what class scorches are, Poker already knew. They are a subclass of xScorch which is a subclass of Projector.

                  Is there a list somewhere of which classes only exist on the client? is it just all the "effects" mainly?

                  edit: it worked, thanks again. That's good to know.

                  Comment


                    #10
                    heh yep Jrubzjeknf nailed it.

                    The way that Projectile.HitWall() is written, you'll notice that the scorch effects are only spawned if (Level.NetMode != NM_DedicatedServer), and the Scorches array that keeps track of them in the Weapon class naturally is subject to that same condition. [Link]



                    That sucks that you couldn't work the ammo reset by just using OutOfAmmo on the server. It's been awhile since I've looked through the Weapon code, so I'll probably look again here soon to see why it didn't work, if only to satisfy my own curiosity. Glad the other way works for now though.

                    Comment


                      #11
                      Originally posted by INIQUITOUS View Post
                      Is there a list somewhere of which classes only exist on the client? is it just all the "effects" mainly?
                      Nope, but you can figure it out this way. Check the parent classes for their RemoteRole. In the case of xScorch, you'll find this:

                      Code:
                      class Projector extends Actor
                      	placeable
                      	native;
                      
                      <snip>
                      
                      defaultproperties
                      {
                      
                      	<snip>
                      
                      	RemoteRole=ROLE_None
                      }
                      It basically means that if the actor is spawned on the server, it isn't spawned on the client and visa versa.

                      Comment


                        #12
                        Back after lots of testing, I have 1 problem that I havn't been able to fix yet. When the player of the weapon dies, for him/her all the decals get destroyed, but for spectators and other players, the decals stay and arent destroyed.

                        I've tried adding this to the weapon class but it makes no difference. is it possibly in the wrong class or do I need to add more code to get this to work?

                        Code:
                        bOnlyRelevantToOwner=False
                        thankyou for anymore help you can give me.

                        edit: im not sure if these properties might be needed:

                        Code:
                             bNetTemporary=True
                             bReplicateInstigator=True
                             RemoteRole=ROLE_SimulatedProxy

                        Comment


                          #13
                          This is probably because a Weapon actor is considered relevant only if it is owned by the local player or if the weapon's owner is visible to the local player. Therefore the weapon's Destroyed event won't be simulated to clients that cannot see the player who owns the weapon, because as far as they know, the weapon doesn't exist.

                          Simply setting bAlwaysRelevant to true for your weapon might work, though I don't think it would be the best idea, as doing so would consume a fair amount of unnecessary bandwidth. Instead, there are other ways you could go about storing the Scorches array, such as creating a special subclass of ReplicationInfo that is attached to each Controller. That way you could have the server use a replicated function call from the Destroyed event to alert each client that it's time to destroy a certain player's scorches.

                          Comment


                            #14
                            it happens if you can see the player or not and if you are spectating the player at the time or not. I have no ideas at all how to attach ReplicationIno class to each controller. Thanks for the help anyway.

                            I made it log when the destroy function was being called and it looks like both clients and server are calling the destroy function, but only the owner actually sees it get destroyed. But that can't be right...

                            edit: bAlwaysRelevant didnt work either ...

                            Comment


                              #15
                              Hmm, alright well based on what you're describing, having the array in the Weapon class may not be to blame after all. Nonetheless, here's the basic idea of what I was suggesting with a ReplicationInfo subclass, if you want to try this approach:

                              Code:
                              // =======================================================
                              
                              class ScorchReplicationInfo extends ReplicationInfo;
                              
                              struct ScorchInfo {
                              	var xScorch Scorch;
                              	var Controller Controller;
                              };
                              var array<ScorchInfo> Scorches;
                              
                              replication
                              {
                              	reliable if (Role == ROLE_Authority)
                              		ClientDestroyScorches;
                              }
                              
                              simulated function AddScorch(xScorch Scorch, Controller C)
                              {
                              	local int i;
                              	i = Scorches.Length;
                              	Scorches.Insert(i, 1);
                              	Scorches[i].Scorch = Scorch;
                              	Scorches[i].Controller = C;
                              }
                              
                              simulated function ClientDestroyScorches(Controller C)
                              {
                              	local int i;
                              	for (i=0; i < Scorches.Length; i++)
                              		if (Scorches[i] != None && Scorches[i].Controller == C)
                              		{
                              			Scorches[i].Scorch.DetachProjector();
                              			Scorches[i].Scorch.Destroy();
                              			Scorches.Remove(i, 1);
                              			i--;
                              		}
                              }
                              
                              static function ScorchReplicationInfo GetSRI(PlayerController PC)
                              {
                              	local ScorchReplicationInfo SRI;
                              	if (PC != None)
                              		foreach PC.ChildActors(class'ScorchReplicationInfo', SRI)
                              			return SRI;
                              	return None;
                              }
                              
                              // =======================================================
                              
                              class MyMutator extends Mutator;
                              
                              function ModifyPlayer(Pawn Other)
                              {
                              	if (PlayerController(Other.Controller) != None)
                              		Other.Controller.Spawn(class'ScorchReplicationInfo', Other.Controller);
                              	super.ModifyPlayer(Other);
                              }
                              
                              // =======================================================
                              
                              class MyProjectile extends Projectile;
                              
                              simulated singular function HitWall(vector HitNormal, actor Wall)
                              {
                              	local xScorch thisScorch;
                              	local ScorchReplicationInfo SRI;
                              	
                              	// ...
                              	// Assign the ExplosionDecal actor to thisScorch anywhere in the code that it's spawned.
                              	thisScorch = Spawn(ExplosionDecal,self,,Location, rotator(-HitNormal));
                              	// ...
                              	
                              	if (thisScorch != None && Instigator != None && Instigator.Controller != None)
                              	{
                              		SRI = class'ScorchReplicationInfo'.static.GetSRI(GetLocalPlayerController());
                              		if (SRI != None)
                              			SRI.AddScorch(thisScorch, Instigator.Controller);
                              	}
                              }
                              
                              // =======================================================
                              
                              class MyWeapon extends Weapon;
                              
                              simulated function OutOfAmmo()
                              {
                              	local Controller C;
                              	local ScorchReplicationInfo SRI;
                              	
                              	if (Role == ROLE_Authority && Instigator != None && PlayerController(Instigator.Controller) != None)
                              		for (C = Level.ControllerList; C != None; C = C.NextController)
                              		{
                              			SRI = class'ScorchReplicationInfo'.static.GetSRI(PlayerController(C));
                              			if (SRI != None)
                              				SRI.ClientDestroyScorches(Instigator.Controller);
                              		}
                              }
                              Now this is what I just tapped out on Notepad off the top of my head, lol, so as usual, no guarantees whatsoever that this will work, or for that matter is anywhere close to error-free.

                              Comment

                              Working...
                              X