Null reference when a PlayerBoatPrivilege is destroyedBuilding Workbench version 1.4.3 throws a null-reference exception inside OnCupboardClearList when a PlayerBoatPrivilege is destroyed.
Error:Failed to call internal hook 'OnEntityKill' on plugin 'BuildingWorkbench v1.4.3' (Object reference not set to an instance of an object)
The exception occurs in OnCupboardClearList(PlayerBoatPrivilege privilege) when the plugin tries to access the Players collection of the returned BuildingData.
Cause:The parent PlayerBoat can still exist while its BoatBuildingBlocks collection has already been cleared. This can happen when another plugin removes a boat and detaches its child entities before destroying them.
In this situation, GetBuildingData(PlayerBoat boat) cannot obtain a building ID and returns null. OnCupboardClearList does not check for a null result before accessing the Players collection.
Suggested fix:Please cache the building ID for each PlayerBoat while its building blocks are still available. When the PlayerBoatPrivilege is destroyed, the cached building ID can be used if the live BoatBuildingBlocks collection is already empty.
OnCupboardClearList should also safely handle a missing BuildingData result and stale or destroyed player references. The cached boat entry should be removed after the cleanup has completed.
A simple null check would stop the exception, but it could skip the normal player and workbench cleanup. Using a cached building ID allows that cleanup to complete correctly.
No changes should be required in the plugin that destroys the boat.
The issue was reproduced while using RaidableBoats, which clears and detaches the PlayerBoat child entities during boat cleanup.
Proposed changes:Add this field next to the existing data dictionaries:
private readonly Dictionary<ulong, uint> _boatBuildingIds = new();
Replace TryGetPlayerBoatBuildingId with:
public bool TryGetPlayerBoatBuildingId(PlayerBoat boat, out uint buildingId)
{
if (boat && boat.BoatBuildingBlocks.Cached.Count != 0)
{
buildingId = boat.BoatBuildingBlocks.Cached[0].buildingID;
if (boat.net != null)
{
_boatBuildingIds[boat.net.ID.Value] = buildingId;
}
return true;
}
if (boat && boat.net != null &&
_boatBuildingIds.TryGetValue(boat.net.ID.Value, out buildingId))
{
return true;
}
buildingId = 0;
return false;
}
Replace OnCupboardClearList(PlayerBoatPrivilege privilege) with:
private void OnCupboardClearList(PlayerBoatPrivilege privilege)
{
if (privilege.ParentVehicle is not PlayerBoat boat)
{
return;
}
ulong boatId = boat.net == null ? 0 : boat.net.ID.Value;
BuildingData data = GetBuildingData(boat);
if (data == null)
{
if (boatId != 0)
{
_boatBuildingIds.Remove(boatId);
}
return;
}
uint buildingId = data.BuildingId;
for (int index = data.Players.Count - 1; index >= 0; index--)
{
BasePlayer player = data.Players[index];
if (!player)
{
data.Players.RemoveAt(index);
continue;
}
OnPlayerLeftBuilding(player, buildingId);
UpdatePlayerWorkbenchLevel(player);
}
if (boatId != 0)
{
_boatBuildingIds.Remove(boatId);
}
}