Jump to content

a couple new peculiarities encountered


Recommended Posts

sorry for the vague title , I sorta don't know exactly where to post this , so .

I mention the turrret issue in a recent post on SHOW OFF! forum , here's a pic of the scene when I return to a point after having moved some miles away , say 10 miles .

see the turretShape has removed from its node to which it was attached in the onAdd function and is on the ground beneath the vehicle . if I nudge the turretShape in the world editor , it goes back to its original position on the wheeled vehicle to which it was first attached in the onAdd function .this will reoccur if I move far away and return .



this is an issue with this same levels' terrain . its 1024 x 1024 using a 16 square size . I added a second terrain (the same one with same settings) and I get this result after I place the new terrain  16384 units to one side . the first terrain is only actually extending about 16368 something like 16 units short . the second terrains' starting point seems to bee correct .


thank you .

Link to comment
Share on other sites

`If I move far distance from other wheeledVehicle/turretShape objects then return to that spot , the turrets will have removed from the wheeledVehicle and be rotating on the ground and even though they're rotaing property is false .` from the other post.


To try to further isolate the problem, I'll note that there are onMount and onUnmount callbacks you could shove


lines in.


Mind trying that for your turrets to see if those are tripping?

Link to comment
Share on other sites

Sounds like floating point imprecision, it happens when you move further than 10km or 10 000 units from the origin, because the numbers to determine your coordinates are limited, so weird things may happen. I hardly can think of any game that has a larger game world than 10km, probably because of that issue.

Link to comment
Share on other sites

*Edit: Accidental early post, should all be here now.

I just ran into an issue related to this same thing, but with a different object-pairing interaction. I seem to have fixed this specific case with turrets years ago and just forgot about it until it came up in another situation. The turret object isn't sending any network updates while it's mounted and un-manned, so it's never actually getting de-ghosted when it goes "out of scope" and this messes up the assumptions of the mounting system networking.

First, how it's supposed to be working:

  1. When an object is more than visibleDistance from a given client's camera, it will no longer be flagged as "in scope" for that client on the server.
  2. Next time the server sends an update about this object to that client, it will inform them that this object is out of scope and the client will delete its ghost of the object.
  3. When the object returns to within visibleDistance of the client, the server will tell the client to create a new ghost for the object, and then will force an "initial update" packet for the object, which means it sets every mask bit on.
  4. As part of "initial update" the object will send the ghost ID of the object it's mounted to. If the client doesn't have a ghost of that object yet, the object will keep re-attempting to send the mount ID until it succeeds (it will keep re-setting MountedMask if the mount's ghost doesn't exist yet).

That last point is supposed to prevent the scenario you're experiencing from occurring, however when mounting support was added to the TurretShape they missed something from the Player implementation which I'll get to in a second. Here's what's going wrong:

  1. The vehicle is out of range to the client, goes "out of scope" to the client. So does the turret. Nothing's deleted yet, this is just the server deciding that these objects are out of range to this client's camera.
  2. As in (2) above, the server will only tell a client to kill its ghost if that object is generating network events in the first place. T3D vehicles send network updates all the time, even if they're asleep from a physics perspective. This means they de-ghost as soon as they go out of range. Stock T3D turrets don't send any updates when they're unmanned and stationary. And when they're mounted, they're considered completely stationary.
  3. The vehicle de-ghosts. The turret doesn't de-ghost because it's not sending any updates while out of range.
  4. You come back into range, the vehicle re-ghosts and sends its initial update packet. The turret didn't need to re-ghost, it's just still there, but your client sees it as mounted to an object that no longer exists (the old ghost of the vehicle, that was deleted) and so behaves as though unmounted.
  5. The turret sees no reason to update the client about its mounting status, because MountedMask was never set (it would have been set during initial update if the turret had been re-ghosted).
  6. When you do anything to the turret in the editor, it calls SceneObject::inspectPostApply, which sets MountedMask and fixes the glitch.

So what's different about Player objects that prevents this? It turns out that when Player objects are mounted they force basic positional network updates:

bool Player::updatePos(const F32 travelTime)

   // When mounted to another object, only Z rotation used.
   if (isMounted()) {
      mVelocity = mMount.object->getVelocity();
      setPosition(Point3F(0.0f, 0.0f, 0.0f), mRot);
      return true;

The velocity and position components are secondary, the main point is that this sets MoveMask and thus forces an update packet, meaning that mounted Player objects that go out of scope range will be de-ghosted.

When I looked at my code, it turns out I'd fixed this in my turrets a long time ago, and the comment there even indicates that I'd specifically done it to resolve this scoping issue:

void TurretShape::processTick(const Move* move)

   if (!isGhost())
   // addthis
   if (isMounted()) {
      setVelocity(mMount.object->getVelocity()); // this sets PositionMask at the Item level, forcing a network update and fixing scoping/de-ghosting
   // endhere

   [...more code...]

setVelocity sets the PositionMask (from Item). You could just set PositionMask manually instead, but I prefer that mounted objects do have the correct velocity values so this handled two things at once.

Technically even setting PositionMask is really overkill, it triggers a totally un-needed write of the transform. You could actually just use any mask bit, including an unused one that won't result in anything being written during packUpdate. It just needs to trigger a ghost update.

Edited by Atomic Walrus
Link to comment
Share on other sites

Just a heads up that the one solution posted there, setting MountedMask on every SceneObject::setTransform event is going to add network overhead for every mounted object (similar to how my dumb hack adds network overhead for turrets). Doing this basically makes the MountedMask have no function, since the mounting info will be sent every update packet.

Mounted ShapeBase objects call setTransform every tick in processTick. The reason for these setTransform calls is that we want mounted objects to be culled correctly, set their detail levels from distance, have radius damage applied, be raycasted against, etc. and so they need to have the correct transform stored. Just modifying the getTransform function to return the mount transform won't work, because not all interactions with SceneObject transforms go through getTransform (you'll find lots of code directly accessing mObjToWorld, mWorldBox, mWorldToObj, etc.).

stream->writeInt( gIndex, NetConnection::GhostIdBitSize );
if ( stream->writeFlag( mMount.node != -1 ) )
   stream->writeInt( mMount.node, NumMountPointBits );
mathWrite( *stream, mMount.xfm );

The above writes are going to be a minimum of 64 bytes for every mounted object, in every packet. It's not a trade-off I would make in my project because of how many things I mount to Vehicles, but obviously YMMV (my hacky solution with turrets is just as bad, but it was meant to be a temporary bandaid and I forgot to come back to it).

I'm going to throw out a different quick-fix option that sort of has less overhead: Just de-ghost everything that isn't in scope, even if it's not waiting to send an update.

I say "sort of" less overhead because this functionality will cause "static" ghostable objects to de-ghost and need to be re-sent to clients based on distance, something the current system avoids by not killing ghosts unless they are actively sending updates. It will, however, be much closer to the "expected behavior" people have of this system; I think most users would assume that out of scope objects get de-ghosted immediately, instead of next time they set a mask bit. You also still have the option to just make your static objects ScopeAlways (like TSStatic), which could make more sense anyway.

So here is the change to de-ghost things immediately whether or not they have a mask bit set. I'm just swapping mGhostFreeIndex (first free ghost ID) in place of mGhostZeroUpdateIndex (first ghost with no updates).

Edit: Fixed a small bug, should only increment skip count on objects that are queueing to send an update, otherwise the other objects will accumulate unnaturally high update priority

netGhost.cpp, NetConnection::ghostWritePacket
   for(i = 0; i < mGhostFreeIndex; i++) // CHANGE HERE: mGhostFreeIndex replacing mGhostZeroUpdateIndex, consider all ghosts for de-ghosting even if not waiting to update
      // increment the updateSkip for everyone... it's all good
      walk = mGhostArray[i];
      if (i < mGhostZeroUpdateIndex) // CHANGE HERE: Only increment the skip count on things actually waiting to update
      if(!(walk->flags & (GhostInfo::ScopeAlways | GhostInfo::ScopeLocalAlways)))
         walk->flags &= ~GhostInfo::InScope;

   if( mScopeObject )
      mScopeObject->onCameraScopeQuery( this, &camInfo );

   for(i = mGhostFreeIndex - 1; i >= 0; i--) // CHANGE HERE: mGhostFreeIndex replacing mGhostZeroUpdateIndex, consider all ghosts for de-ghosting even if not waiting to update
      // [rene, 07-Mar-11] Killing ghosts depending on the camera scope queries
      //    seems like a bad thing to me and something that definitely has the potential
      //    of causing scoping to eat into bandwidth rather than preserve it.  As soon
      //    as an object comes back into scope, it will have to completely retransmit its
      //    full server-side state from scratch.

      if(!(mGhostArray[i]->flags & GhostInfo::InScope))

What this does is have it iterate through the full active ghost list, instead of just the "has updates" portion (zero to mGhostZeroUpdateIndex, they're sorted). The detachObject function is prepared for this possibility; When called on an object without an update queued it will set a mask bit for that object and then re-sort the list so that it is in the "updates pending" portion of it.

I tested this and it resolves the issue in question (with the other fixed discussed previously disabled). An alternate solution that would have even less overhead would be to have mounted objects link their scoping state and ghosting behavior entirely to the thing they're mounted to, but that's going to be a bit more complex from a code perspective so I'll have to circle back on that.


Some extra discussion... While we're looking at it, that comment from either 2007 or 2011 is worth additional consideration. In my own project I'm planning to disable render distance based de-ghosting in favor of putting client ghosts of game objects into a "sleep" state where they don't process ticks, advance time, render, or receive network updates. It accomplishes the same thing from a performance-saving perspective, without the need to delete and recreate game objects on the client and receive new initialUpdate packets. I'll share this code and any other improvements I make to the scoping behavior when it's ready, but no promises on the timeline (I'm notorious for planning to do things like this and then getting too busy with my day job).

I think you'd optimally want both of these system to exist together, so that objects can still go in and out of scope when desired, OR be put to sleep, depending on what fits the project.

Edited by Atomic Walrus
Link to comment
Share on other sites

Fair. Had similar overhead concerns, as well as still following up a bit of tracing to be sure how it'd interact with the pathshape pseudomounting code. I'll note in general once 4.0 drops we'll be wanting to return focus to fleshing out ECS so self-propagating fixes based on sceneobject/entities or a reusable component method reference'll be the ideal end goal.

Link to comment
Share on other sites

thank you all for taking the time to help ,

in the console , I set the state of the aiTurret to "scanning" . after that there was no issue with the turret not being mounted after going out of scope . it was mounted as well as still scanning and tracking targets . I checked it without setting the state and the issue returned . sorry if i was too vague concerning that its an aiTurret .

Link to comment
Share on other sites

also having an aiPlayer mounted to the aiTurret caused the turret to be mounted after coming back in , even without the state being changed . 


I'd skimmed over something , in a file I made by using dumpConsoleClasses , which I think is essentially the read the docs doc ,which led me to try these couple of things ,

I think , said something about the mounted object causing a dirty flag for other objects , something like that , maybe that sounds ridiculous and I possibly could have been looking at a source code file , I haven't been able to find it again , yet  . IF I find it Ill post it .. here . thanx again .


concerning the terrain , the edges are short on both X and Y , the ground cover extends beyond the edges several units , I'm not sure how many .

Link to comment
Share on other sites

  • 1 month later...

I also had a problem with mounted objects (T3D 3.10). Made a simple demo where to collect an item which is then mounted on the back of a player character using mountobject. The first problem was the camera collided with the mounted item in 3thPerson. After I fixed this the new problem was, that the items or lights drop on the floor (on clientA) when a player (clientB) get out of range.

After all I did a hackish solution: Setting the items to setRenderEnabled(false);  on remove at clientA and setRenderEnabled(true) on unpackUpdate at clientA and modified packUpdate. Also added some isRenderEnabled checks to lightbase so it also works with lights.

Link to comment
Share on other sites

ohmtal , you're clearly far more advanced than I am . this is interesting to me because I haven't added lights yet , Im pecking at the GUI for the tank , I'll be getting to the lights , Im hoping to use at least 5 but Im not sure how many mount nodes Ill be able to use by the end , thanks for giving me that solution , I'll think about it .

THANK you .

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Create New...