Destroyed vehicles play explosion effects each time they become relevant to client

Issue #388 resolved
Colin Basnett created an issue

This has been a long-standing bug that I'm pretty sure existed in Red Orchestra as well.

Steps to reproduce are simple:

  1. Destroy a vehicle somewhere
  2. Go out of replication range of destroyed vehicle (dying is preferable)
  3. Spawn back in replication range of the destroyed vehicle

The vehicle destroyed sound and particle effects will be played when step 3 is completed.

Comments (3)

  1. Matt Hands

    Fixed in commit 6c04b06. Added to DHArmoredVehicle and DHWheeledVehicle.

    When a vehicle is killed, the server calls VehicleExplosion(), in which it increments the byte ExplosionCount, which gets replicated to all net clients. This triggers native code (specifically native PostNetReceive(), I believe, as opposed to script PNRec) to call the ClientVehicleExplosion() (CVE) event for all relevant net clients. That's where the explosion effects are done.

    The problem is that when a killed vehicle gets replicated to a new net client, the changed (non-zero) value of ExplosionCount triggers CVE on that client. Which plays the explosion again.

    We need CVE to spawn the DestructionEffect, which is the big blazing fire. But we need to stop if playing an explosion sound or view shake in this situation.

    My solution is to use the handy bClientInitialized bool that I added previously. It's a clientside flag that the actor has completed its initialization after being replicated to the client. Gets set at the end of PostNetBeginPlay(), which is when a replicated actor has received its initial bath of replicated variables.

    The timing is that CVE gets called before PostNetBeginPlay(). I guess an example of native stuff happening before scripted stuff. That's good, as if CVE gets called before bClientInitialized has been set at the end of PostNetBeginPlay(), we know that the actor has just replicated. But if bClientInitialized has been set, we know the explosion must have just happened and is real.

    So, overridden CVE so that it only plays the explosion sound & view shake if bClientInitialized is true or if it's an authority role (single player or listen server). But the DestructionEffect is always spawned.

    This reveals another problem, where the client sets the LifeSpan of the DestructionEffect emitter to be the vehicle's normal TimeTilDissapear. But in the situation where a killed vehicle has replicated to a client, that's going to be too long, as some time has already passed. The net client cannot adjust the time, as it has no idea of when the vehicle was killed. The server will Destroy() the vehicle at the right time, which will Destroy() it on the client - but the big fire effect will just keep burning in the middle of nowhere.

    Notice I've avoided referring to a destroyed vehicle in this commentary, instead calling it killed. In vehicle terms, the actor is destroyed when it disappears, not when it gets killed/blown up. ROVehicle.Destroyed() calls DestructionEffect.Kill() & clears the actor ref, but that doesn't seem to do the job. Instead I Destroy() the DestructionEffect at the start of the vehicle's Destroyed() event. In our replication situation, that means that when the server destroys the vehicle actor & it also gets destroyed on the client, Destroyed() gets called clientside & that destroys the effect.

    Another ancient bug stomped !

  2. Matt Hands

    Noticed a further problem re the DestructionEffect not always disappearing when it should, fixed in commit 7d0aafc.

    I noticed that the M3 halftrack always left a stranded fire burning after the replicated destroyed vehicle disappeared, but not other similar vehicles. The reason is that for some reason the M3 doesn't have a specified DisintegrationHealth, so it inherited a default value that made it disintegrate every time it was killed, where most vehicles don't disintegrate.

    Disintegration causes CVE to be called twice on the client - 1st for normal destruction, then immediately again for disintegration. The 1st call spawns a normal DestructionEffect, which gets saved as the actor reference. But then the 2nd call spawns a disintegration effect, which gets saved as the DestructionEffect, leaving the 1st effect still present, but now unreferenced. So a disintegrated vehicle ended up with 2 fireball effects, but only had a reference to 1, so it could only destroy that 1 effect when the vehicle actor got destroyed. That left a fire burning on its own, until that effect reached the end of it's LifeSpan.

    Can't prevent the double call to CVE as it's all handled by native code. But easy and effective enough to Destroy() any existing DestructionEffect in CVE, so if a vehicle disintegrates, it ends up with only the 2nd, correct disintegration effect.

  3. Log in to comment