Hey, sorry for the lengthy message. I am having issues where the CH47 is getting stuck mid-air after dropping its locked crate. I have spent weeks trying to figure out what the problem is. Finally, I have fixed the issue, and it seems to be ZoneManager. I used Visual Studio with the help of the built-in GitHub Copilot and made some changes to ZoneManager. After making the changes, the CH47 finally despawns properly. I could make the changes myself after each update you release, but I thought maybe you could implement in case anyone else has the issue in the future. Here is the error I get when the CH47 gets stuck:
NullReferenceException: Object reference not set to an instance of an object
at RpcTarget.NetworkGroup (System.String funcName, BaseNetworkable entity) [0x00010] in <455f496e536148ebab2f8b303602acdd>:0
at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.BaseMountable.DismountPlayer_Patch0(BaseMountable,BasePlayer,bool)
at BasePlayer.EnsureDismounted () [0x0000e] in <455f496e536148ebab2f8b303602acdd>:0
at BasePlayer.OnDied (HitInfo info) [0x000e8] in <455f496e536148ebab2f8b303602acdd>:0
at ScientistNPC.OnDied (HitInfo info) [0x00000] in <455f496e536148ebab2f8b303602acdd>:0
at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.BaseCombatEntity.Die_Patch0(BaseCombatEntity,HitInfo)
at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.BasePlayer.Die_Patch0(BasePlayer,HitInfo)
at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.BaseCombatEntity.Hurt_Patch0(BaseCombatEntity,HitInfo)
at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.BasePlayer.Hurt_Patch0(BasePlayer,HitInfo)
at HumanNPC.Hurt (HitInfo info) [0x00018] in <455f496e536148ebab2f8b303602acdd>:0
at BaseCombatEntity.Hurt (System.Single amount, Rust.DamageType type, BaseEntity attacker, System.Boolean useProtection) [0x0002f] in <455f496e536148ebab2f8b303602acdd>:0
at CH47HelicopterAIController.DismountAllPlayers () [0x00040] in <455f496e536148ebab2f8b303602acdd>:0
at BaseMountable.DoServerDestroy () [0x00000] in <455f496e536148ebab2f8b303602acdd>:0
at BaseHelicopter.DoServerDestroy () [0x00007] in <455f496e536148ebab2f8b303602acdd>:0
at BaseNetworkable.DoEntityDestroy () [0x00026] in <455f496e536148ebab2f8b303602acdd>:0
at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.BaseNetworkable.Kill_Patch0(BaseNetworkable,BaseNetworkable/DestroyMode)
at CH47HelicopterAIController.DelayedKill () [0x00006] in <455f496e536148ebab2f8b303602acdd>:0
at InvokeHandlerBase`1[T].DoTick () [0x00138] in <bcfd9d22dad547c68fe65bcc93becf9c>:0
at InvokeHandlerBase`1[T].LateUpdate () [0x0001f] in <bcfd9d22dad547c68fe65bcc93becf9c>:0
Here are the changes I made that fixed the problem:
ADD TO HELPERS SECTION:
private static bool TryGetNetId(BaseNetworkable entity, out NetworkableId id)
{
id = default;
if (!entity || entity.IsDestroyed)
return false;
if (entity.net == null)
return false;
id = entity.net.ID;
return true;
}
private static bool TryGetEntityZones(BaseNetworkable entity, out EntityZones ez)
{
ez = null;
if (!TryGetNetId(entity, out var id))
return false;
return zonedEntities.TryGetValue(id, out ez);
}
REPLACE:
private void OnEntityKill(BaseEntity baseEntity)
{
if (!baseEntity || !baseEntity.IsValid() || baseEntity.IsDestroyed)
return;
if (!TryGetEntityZones(baseEntity, out EntityZones entityZones))
return;
for (int i = entityZones.Zones.Count - 1; i >= 0; i--)
{
Zone zone = entityZones.Zones[i];
if (!zone)
continue;
zone.OnEntityExitZone(baseEntity, false, true);
}
if (TryGetNetId(baseEntity, out var id))
zonedEntities.Remove(id);
}
REPLACE:
private void OnEntityEnterZone(BaseEntity baseEntity, Zone zone)
{
if (!baseEntity || !baseEntity.IsValid())
return;
if (zone.HasFlag(ZoneFlags.KeepVehiclesOut) && !zone.entities.Contains(baseEntity) && zone.TryReverseVelocity(baseEntity))
{
BasePlayer player = (baseEntity as BaseVehicle)?.GetDriver();
if (player)
SendMessage(player, Message("novehiclesenter", player.UserIDString));
return;
}
if (!TryGetNetId(baseEntity, out var id))
return;
if (!zonedEntities.TryGetValue(id, out EntityZones entityZones))
zonedEntities[id] = entityZones = new EntityZones();
if (!entityZones.EnterZone(zone))
return;
if (zone.parent)
entityZones.UpdateFlags();
else
entityZones.AddFlags(zone.definition.Flags);
zone.OnEntityEnterZone(baseEntity);
Interface.CallHook("OnEntityEnterZone", zone.definition.Id, baseEntity);
}
private void OnEntityExitZone(BaseEntity baseEntity, Zone zone)
{
if (!baseEntity || !baseEntity.IsValid())
return;
if (zone.HasFlag(ZoneFlags.KeepVehiclesOut) && zone.entities.Contains(baseEntity) && zone.TryReverseVelocity(baseEntity))
{
BasePlayer player = (baseEntity as BaseVehicle)?.GetDriver();
if (player)
SendMessage(player, Message("novehiclesleave", player.UserIDString));
return;
}
if (!TryGetEntityZones(baseEntity, out EntityZones entityZones))
return;
entityZones.LeaveZone(zone);
if (entityZones.ShouldRemove())
{
if (TryGetNetId(baseEntity, out var id))
zonedEntities.Remove(id);
}
else
{
entityZones.UpdateFlags();
}
zone.OnEntityExitZone(baseEntity, !entityZones.HasFlag(ZoneFlags.NoDecay));
Interface.CallHook("OnEntityExitZone", zone.definition.Id, baseEntity);
}
REPLACE:
public void OnEntityEnterZone(BaseEntity baseEntity)
{
entities.Add(baseEntity);
if (ZoneManager.TryGetNetId(baseEntity, out var id) && ZoneManager.zonedEntities.TryGetValue(id, out EntityZones entityZone))
entityZones[id.Value] = entityZone;
if (HasFlag(ZoneFlags.NoDecay))
{
DecayEntity decayEntity = baseEntity.GetComponentInParent<DecayEntity>();
if (decayEntity)
decayEntity.decay = null;
}
if (HasFlag(ZoneFlags.NoStability))
{
if (baseEntity is StabilityEntity entity)
entity.grounded = true;
}
if (HasFlag(ZoneFlags.NpcFreeze) && baseEntity.IsNpc)
{
if (baseEntity is BaseAnimalNPC animalNpc) { animalNpc.brain.SetEnabled(false); return; }
if (baseEntity is global::HumanNPC humanNpc) { humanNpc.Brain.SetEnabled(false); return; }
if (baseEntity is ScarecrowNPC scarecrowNpc) { scarecrowNpc.Brain.SetEnabled(false); return; }
if (baseEntity is BaseNpc npc) { npc.CancelInvoke(npc.TickAi); return; }
}
if (baseEntity is SmartSwitch or ElectricSwitch or RFReceiver)
{
ioEntities.Add((IOEntity)baseEntity);
if (HasFlag(ZoneFlags.PoweredSwitches))
{
((IOEntity)baseEntity).SetFlag(BaseEntity.Flags.Reserved8, true);
((IOEntity)baseEntity).currentEnergy = int.MaxValue;
}
}
if (HasFlag(ZoneFlags.AlwaysLights) || (HasFlag(ZoneFlags.AutoLights) && isLightsOn))
{
if (baseEntity is BaseOven or SearchLight)
ToggleLight(baseEntity, true, Configuration.AutoLights.RequiresFuel);
}
}
public void OnEntityExitZone(BaseEntity baseEntity, bool resetDecay, bool isDead = false)
{
entities.Remove(baseEntity);
if (ZoneManager.TryGetNetId(baseEntity, out var id))
entityZones.Remove(id.Value);
if (isDead)
return;
if (resetDecay && HasFlag(ZoneFlags.NoDecay))
{
DecayEntity decayEntity = baseEntity.GetComponentInParent<DecayEntity>();
if (decayEntity)
decayEntity.decay = PrefabAttribute.server.Find<Decay>(decayEntity.prefabID);
}
if (HasFlag(ZoneFlags.NpcFreeze) && baseEntity.IsNpc)
{
if (baseEntity is BaseAnimalNPC animalNpc) { animalNpc.brain.SetEnabled(true); return; }
if (baseEntity is global::HumanNPC humanNpc) { humanNpc.Brain.SetEnabled(true); return; }
if (baseEntity is ScarecrowNPC scarecrowNpc) { scarecrowNpc.Brain.SetEnabled(true); return; }
if (baseEntity is BaseNpc npc) { npc.InvokeRandomized(npc.TickAi, 0.1f, 0.1f, 0.00500000035f); return; }
}
if (baseEntity is SmartSwitch or ElectricSwitch or RFReceiver)
{
IOEntity ioEntity = (IOEntity)baseEntity;
ioEntities.Remove(ioEntity);
if (HasFlag(ZoneFlags.PoweredSwitches))
{
ioEntity.SetFlag(BaseEntity.Flags.Reserved8, false);
ioEntity.currentEnergy = 0;
for (int i = 0; i < ioEntity.inputs.Length; i++)
{
IOEntity fromEntity = ioEntity.inputs[i].connectedTo.Get();
if (fromEntity)
fromEntity.MarkDirtyForceUpdateOutputs();
}
}
}
if (!HasFlag(ZoneFlags.AlwaysLights) && (!HasFlag(ZoneFlags.AutoLights) || !isLightsOn))
return;
if (baseEntity is BaseOven or SearchLight)
ToggleLight(baseEntity, false, false);
}
REPLACE:
private bool HasEntityFlag(BaseEntity baseEntity, int flag)
{
if (!baseEntity.IsValid())
return false;
return TryGetEntityZones(baseEntity, out EntityZones entityZones) && entityZones.HasFlag(flag);
}
private string[] GetEntityZoneIDs(BaseEntity entity)
{
if (!TryGetEntityZones(entity, out EntityZones entityZones))
return Array.Empty<string>();
string[] array = new string[entityZones.Zones.Count];
for (int i = 0; i < entityZones.Zones.Count; i++)
array[i] = entityZones.Zones[i].definition.Id;
return array;
}
private void GetEntityZoneIDsNoAlloc(BaseEntity entity, List<string> list)
{
if (!TryGetEntityZones(entity, out EntityZones entityZones))
return;
foreach (Zone zone in entityZones.Zones)
list.Add(zone.definition.Id);
}