To follow up on this, this issue happens when a skin is added or removed. While testing with the eoka pistol and this skin ID: 2524297461, I was able to consistently recreate this issue. When the plugin is reloaded the skin list updates are visible, so it seems like the plugin isn't handling reloading the list of skins correctly when adding or removing a skin.
Merged post
I was able to fix the bug by having always calling GenerateUI at the end of any section of code that calls LoadConfig, as LoadConfig seems to overwrite some of the UI data that GenerateUI used. This will prevent the NRE from earlier, but the modified skin will still not appear. In order for this to happen, container.TotalSkinsCache.Clear must also be called. As an example, this is a fixed version of the /skin add command:
case "add":
case "a":
{
if (args.Length < 3)
goto default;
if (!isAdmin)
{
player.Reply(GetMsg("Not Allowed", player.Id));
break;
}
var shortname = args[1];
ulong skin;
if (!ulong.TryParse(args[2], out skin))
{
player.Reply(GetMsg("Incorrect Skin", player.Id));
break;
}
string permission = null;
if (args.Length == 4)
permission = args[3];
LoadConfig();
var skinData = Configuration.SkinItem.Find(null, shortname)
.FirstOrDefault(x => permission == null || x.Permission == permission);
if (skinData == null)
{
_config.Skins.Add(new Configuration.SkinItem
{
Permission = permission ?? string.Empty,
Shortname = shortname,
Skins = new List<ulong> {skin}
});
_config.IndexSkins();
player.Reply(GetMsg("Skin Added", player.Id));
}
else
{
if (skinData.Skins.Contains(skin))
player.Reply(GetMsg("Skin Already Exists", player.Id));
else
{
skinData.Skins.Add(skin);
player.Reply(GetMsg("Skin Added", player.Id));
}
}
SaveConfig();
//Added code after here
GenerateUI();
if (isPlayer)
PurgeCache(ulong.Parse(player.Id), null);
break;
}
Also, could you please add these hooks for checking whether a skin exists and for adding skins through the API?
[HookMethod(nameof(AddSkin))]
private bool AddSkin(string shortname, ulong skinID, ulong playerId = 0)
{
LoadConfig();
var skinData = Configuration.SkinItem.Find(null, shortname).FirstOrDefault();
if (skinData == null)
{
return false;
}
else
{
if (skinData.Skins.Contains(skinID))
{
return false;
}
else
{
skinData.Skins.Add(skinID);
SaveConfig();
LoadConfig();
GenerateUI();
if (playerId != 0)
PurgeCache(playerId, null);
return true;
}
}
}
[HookMethod(nameof(SkinExists))]
private bool SkinExists(string shortname, ulong skinID)
{
LoadConfig();
GenerateUI();
var skinData = Configuration.SkinItem.Find(null, shortname).FirstOrDefault();
if (skinData == null)
{
return false;
}
else
{
if (skinData.Skins.Contains(skinID))
{
return true;
}
else
{
return false;
}
}
}
Merged postQuick correction to my last comment, PurgeCache will only purge the cache of the player that initiated the skin update, which will cause everyone else to not see the skin update. In order to address this, replace
if (isPlayer)
PurgeCache(ulong.Parse(player.Id), null);
// or
if (playerId != 0)
PurgeCache(ulong.Parse(playerId), null);
with
PurgeAllCache();
and add this function as a helper:
private void PurgeAllCache()
{
foreach (BasePlayer basePlayer in BasePlayer.activePlayerList) {
ContainerController container;
if (!_controllers.TryGetValue(basePlayer.userID, out container))
break;
container.TotalSkinsCache.Clear();
}
}
Also, this gets rid of the need to have the playerId argument in the AddSkin hook.