Skip to content

Commit

Permalink
Merge pull request #310 from stefanx111/stickers-and-charms
Browse files Browse the repository at this point in the history
feat: Stickers and Charms
  • Loading branch information
daffyyyy authored Oct 18, 2024
2 parents b6cac17 + e209b12 commit e9f47e9
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 11 deletions.
2 changes: 2 additions & 0 deletions Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo
g_playersMusic.TryRemove(player.Slot, out _);
}

temporaryPlayerWeaponWear.TryRemove(player.Slot, out _);

commandsCooldown.Remove(player.Slot);

return HookResult.Continue;
Expand Down
9 changes: 8 additions & 1 deletion Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ internal static async Task CheckDatabaseTables()
`weapon_defindex` int(6) NOT NULL,
`weapon_paint_id` int(6) NOT NULL,
`weapon_wear` float NOT NULL DEFAULT 0.000001,
`weapon_seed` int(16) NOT NULL DEFAULT 0
`weapon_seed` int(16) NOT NULL DEFAULT 0,
`weapon_nametag` VARCHAR(128) DEFAULT NULL,
`weapon_sticker_0` VARCHAR(128) NOT NULL DEFAULT '0;0;0;0;0;0;0' COMMENT 'id;schema;x;y;wear;scale;rotation',
`weapon_sticker_1` VARCHAR(128) NOT NULL DEFAULT '0;0;0;0;0;0;0' COMMENT 'id;schema;x;y;wear;scale;rotation',
`weapon_sticker_2` VARCHAR(128) NOT NULL DEFAULT '0;0;0;0;0;0;0' COMMENT 'id;schema;x;y;wear;scale;rotation',
`weapon_sticker_3` VARCHAR(128) NOT NULL DEFAULT '0;0;0;0;0;0;0' COMMENT 'id;schema;x;y;wear;scale;rotation',
`weapon_sticker_4` VARCHAR(128) NOT NULL DEFAULT '0;0;0;0;0;0;0' COMMENT 'id;schema;x;y;wear;scale;rotation',
`weapon_keychain`VARCHAR(128) NOT NULL DEFAULT '0;0;0;0;0' COMMENT 'id;x;y;z;seed'
) ENGINE=InnoDB
""",
@"CREATE TABLE IF NOT EXISTS `wp_player_knife` (
Expand Down
114 changes: 110 additions & 4 deletions WeaponAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Runtime.InteropServices;

namespace WeaponPaints
{
public partial class WeaponPaints
{
private void GivePlayerWeaponSkin(CCSPlayerController player, CBasePlayerWeapon weapon)
private void GivePlayerWeaponSkin(CCSPlayerController player, CBasePlayerWeapon weapon)
{
if (!Config.Additional.SkinEnabled) return;
if (!gPlayerWeaponsInfo.TryGetValue(player.Slot, out _)) return;
Expand Down Expand Up @@ -52,12 +54,12 @@ private void GivePlayerWeaponSkin(CCSPlayerController player, CBasePlayerWeapon
weapon.FallbackWear = 0.01f;

weapon.AttributeManager.Item.NetworkedDynamicAttributes.Attributes.RemoveAll();
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture prefab", GetRandomPaint(weaponDefIndex));
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture prefab", GetRandomPaint(weaponDefIndex));
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture seed", 0);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture wear", 0.01f);

weapon.AttributeManager.Item.AttributeList.Attributes.RemoveAll();
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture prefab", GetRandomPaint(weaponDefIndex));
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture prefab", GetRandomPaint(weaponDefIndex));
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture seed", 0);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture wear", 0.01f);

Expand All @@ -75,9 +77,11 @@ private void GivePlayerWeaponSkin(CCSPlayerController player, CBasePlayerWeapon

var weaponInfo = value;
//Log($"Apply on {weapon.DesignerName}({weapon.AttributeManager.Item.ItemDefinitionIndex}) paint {gPlayerWeaponPaints[steamId.SteamId64][weapon.AttributeManager.Item.ItemDefinitionIndex]} seed {gPlayerWeaponSeed[steamId.SteamId64][weapon.AttributeManager.Item.ItemDefinitionIndex]} wear {gPlayerWeaponWear[steamId.SteamId64][weapon.AttributeManager.Item.ItemDefinitionIndex]}");

weapon.AttributeManager.Item.ItemID = 16384;
weapon.AttributeManager.Item.ItemIDLow = 16384 & 0xFFFFFFFF;
weapon.AttributeManager.Item.ItemIDHigh = weapon.AttributeManager.Item.ItemIDLow >> 32;
weapon.AttributeManager.Item.CustomName = weaponInfo.Nametag;
weapon.FallbackPaintKit = weaponInfo.Paint;
weapon.FallbackSeed = weaponInfo.Seed;
weapon.FallbackWear = weaponInfo.Wear;
Expand All @@ -89,9 +93,104 @@ private void GivePlayerWeaponSkin(CCSPlayerController player, CBasePlayerWeapon
return;

if (isKnife) return;

if (weaponInfo.Stickers.Count > 0) SetStickers(player, weapon);
if (weaponInfo.KeyChain != null) SetKeychain(player, weapon);

UpdatePlayerWeaponMeshGroupMask(player, weapon, !newPaints.Contains(fallbackPaintKit));
}


// silly method to update sticker when call RefreshWeapons()
private void IncrementWearForWeaponWithStickers(CCSPlayerController player, CBasePlayerWeapon weapon)
{
int weaponDefIndex = weapon.AttributeManager.Item.ItemDefinitionIndex;
if (gPlayerWeaponsInfo.TryGetValue(player.Slot, out var playerWeapons) &&
playerWeapons.TryGetValue(weaponDefIndex, out var weaponInfo) &&
weaponInfo.Stickers.Count > 0)
{

float wearIncrement = 0.001f;
float currentWear = weaponInfo.Wear;

var playerWear = temporaryPlayerWeaponWear.GetOrAdd(player.Slot, _ => new ConcurrentDictionary<int, float>());

float incrementedWear = playerWear.AddOrUpdate(
weaponDefIndex,
currentWear + wearIncrement,
(_, oldWear) => Math.Min(oldWear + wearIncrement, 1.0f)
);

weapon.FallbackWear = incrementedWear;
}
}

public void SetStickers(CCSPlayerController? player, CBasePlayerWeapon weapon)
{
if (player == null || !player.IsValid) return;

int weaponDefIndex = weapon.AttributeManager.Item.ItemDefinitionIndex;

if (!gPlayerWeaponsInfo.TryGetValue(player.Slot, out var playerWeapons) ||
playerWeapons == null ||
!playerWeapons.TryGetValue(weaponDefIndex, out var weaponInfo) ||
weaponInfo == null)
{
return;
}

foreach (var sticker in weaponInfo.Stickers)
{
int stickerSlot = weaponInfo.Stickers.IndexOf(sticker);

CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
$"sticker slot {stickerSlot} id", ViewAsFloat(sticker.Id));
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
$"sticker slot {stickerSlot} schema", sticker.Schema);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
$"sticker slot {stickerSlot} offset x", sticker.OffsetX);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
$"sticker slot {stickerSlot} offset y", sticker.OffsetY);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
$"sticker slot {stickerSlot} wear", sticker.Wear);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
$"sticker slot {stickerSlot} scale", sticker.Scale);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
$"sticker slot {stickerSlot} rotation", sticker.Rotation);
}

if (temporaryPlayerWeaponWear != null &&
temporaryPlayerWeaponWear.TryGetValue(player.Slot, out var playerWear) &&
playerWear.TryGetValue(weaponDefIndex, out float storedWear))
{
weapon.FallbackWear = storedWear;
}
}

public void SetKeychain(CCSPlayerController? player, CBasePlayerWeapon weapon)
{
if (player == null || !player.IsValid) return;

int weaponDefIndex = weapon.AttributeManager.Item.ItemDefinitionIndex;

if (gPlayerWeaponsInfo.TryGetValue(player.Slot, out var playerWeaponsInfo) &&
playerWeaponsInfo.TryGetValue(weaponDefIndex, out var value) &&
value.KeyChain != null)
{
var keyChain = value.KeyChain;

CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
"keychain slot 0 id", ViewAsFloat(keyChain.Id));
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
"keychain slot 0 offset x", keyChain.OffsetX);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
"keychain slot 0 offset y", keyChain.OffsetY);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
"keychain slot 0 offset z", keyChain.OffsetZ);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle,
"keychain slot 0 seed", keyChain.Seed);
}
}

private static void GiveKnifeToPlayer(CCSPlayerController? player)
{
if (!_config.Additional.KnifeEnabled || player == null || !player.IsValid) return;
Expand Down Expand Up @@ -248,6 +347,8 @@ private void RefreshWeapons(CCSPlayerController? player)
{
newWeapon.Clip1 = ammo.Item1;
newWeapon.ReserveAmmo[0] = ammo.Item2;

IncrementWearForWeaponWithStickers(player, newWeapon);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -447,5 +548,10 @@ public static unsafe T[] GetFixedArray<T>(nint pointer, string @class, string me

return values;
}

public float ViewAsFloat(uint value)
{
return BitConverter.Int32BitsToSingle((int)value);
}
}
}
23 changes: 23 additions & 0 deletions WeaponInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,28 @@ public class WeaponInfo
public int Paint { get; set; }
public int Seed { get; set; } = 0;
public float Wear { get; set; } = 0f;
public string Nametag { get; set; } = "";
public KeyChainInfo? KeyChain { get; set; }
public List<StickerInfo> Stickers { get; set; } = new List<StickerInfo>();
}

public class StickerInfo
{
public uint Id { get; set; }
public uint Schema { get; set; }
public float OffsetX { get; set; }
public float OffsetY { get; set; }
public float Wear { get; set; }
public float Scale { get; set; }
public float Rotation { get; set; }
}

public class KeyChainInfo
{
public uint Id { get; set; }
public float OffsetX { get; set; }
public float OffsetY { get; set; }
public float OffsetZ { get; set; }
public uint Seed { get; set; }
}
}
2 changes: 2 additions & 0 deletions WeaponPaints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ public partial class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig
private ulong _nextItemId = MinimumCustomItemId;
public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

private ConcurrentDictionary<int, ConcurrentDictionary<int, float>> temporaryPlayerWeaponWear = new ConcurrentDictionary<int, ConcurrentDictionary<int, float>>();

public WeaponPaintsConfig Config { get; set; } = new();
public override string ModuleAuthor => "Nereziel & daffyy";
public override string ModuleDescription => "Skin, gloves, agents and knife selector, standalone and web-based";
Expand Down
2 changes: 1 addition & 1 deletion WeaponPaints.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.233" />
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.281" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="MySqlConnector" Version="2.3.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
80 changes: 75 additions & 5 deletions WeaponSynchronization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using MySqlConnector;
using System.Collections.Concurrent;


namespace WeaponPaints
{
internal class WeaponSynchronization
Expand All @@ -20,7 +21,7 @@ internal async Task GetPlayerData(PlayerInfo? player)
try
{
await using var connection = await _database.GetConnectionAsync();

if (_config.Additional.KnifeEnabled)
GetKnifeFromDatabase(player, connection);
if (_config.Additional.GloveEnabled)
Expand Down Expand Up @@ -127,14 +128,85 @@ private void GetWeaponPaintsFromDatabase(PlayerInfo? player, MySqlConnection con
int weaponPaintId = row?.weapon_paint_id ?? 0;
float weaponWear = row?.weapon_wear ?? 0f;
int weaponSeed = row?.weapon_seed ?? 0;
string weaponNameTag = row?.weapon_nametag ?? "";

string[]? keyChainParts = row?.weapon_keychain?.ToString().Split(';');

KeyChainInfo keyChainInfo = new KeyChainInfo();

if (keyChainParts!.Length == 5 &&
uint.TryParse(keyChainParts[0], out uint keyChainId) &&
float.TryParse(keyChainParts[1], out float keyChainOffsetX) &&
float.TryParse(keyChainParts[2], out float keyChainOffsetY) &&
float.TryParse(keyChainParts[3], out float keyChainOffsetZ) &&
uint.TryParse(keyChainParts[4], out uint keyChainSeed))
{
// Successfully parsed the values
keyChainInfo.Id = keyChainId;
keyChainInfo.OffsetX = keyChainOffsetX;
keyChainInfo.OffsetY = keyChainOffsetY;
keyChainInfo.OffsetZ = keyChainOffsetZ;
keyChainInfo.Seed = keyChainSeed;
}
else
{
// Failed to parse the values, default to 0
keyChainInfo.Id = 0;
keyChainInfo.OffsetX = 0f;
keyChainInfo.OffsetY = 0f;
keyChainInfo.OffsetZ = 0f;
keyChainInfo.Seed = 0;
}

// Create the WeaponInfo object
WeaponInfo weaponInfo = new WeaponInfo
{
Paint = weaponPaintId,
Seed = weaponSeed,
Wear = weaponWear
Wear = weaponWear,
Nametag = weaponNameTag,
KeyChain = keyChainInfo
};

// Retrieve and parse sticker data (up to 5 slots)
for (int i = 0; i <= 4; i++)
{
// Access the sticker data dynamically using reflection
string stickerColumn = $"weapon_sticker_{i}";
var stickerData = ((IDictionary<string, object>)row!)[stickerColumn]; // Safely cast row to a dictionary

if (stickerData != null && !string.IsNullOrEmpty(stickerData.ToString()))
{
var parts = stickerData.ToString()!.Split(';');

//"id;schema;x;y;wear;scale;rotation"
if (parts.Length == 7 &&
uint.TryParse(parts[0], out uint stickerId) &&
uint.TryParse(parts[1], out uint stickerSchema) &&
float.TryParse(parts[2], out float stickerOffsetX) &&
float.TryParse(parts[3], out float stickerOffsetY) &&
float.TryParse(parts[4], out float stickerWear) &&
float.TryParse(parts[5], out float stickerScale) &&
float.TryParse(parts[6], out float stickerRotation))
{
StickerInfo stickerInfo = new StickerInfo
{
Id = stickerId,
Schema = stickerSchema,
OffsetX = stickerOffsetX,
OffsetY = stickerOffsetY,
Wear = stickerWear,
Scale = stickerScale,
Rotation = stickerRotation
};

weaponInfo.Stickers.Add(stickerInfo);


}
}
}

weaponInfos[weaponDefIndex] = weaponInfo;
}

Expand Down Expand Up @@ -167,14 +239,12 @@ private void GetMusicFromDatabase(PlayerInfo? player, MySqlConnection connection
}
}



internal async Task SyncKnifeToDatabase(PlayerInfo player, string knife)
{
if (!_config.Additional.KnifeEnabled || string.IsNullOrEmpty(player.SteamId) || string.IsNullOrEmpty(knife)) return;

const string query = "INSERT INTO `wp_player_knife` (`steamid`, `knife`) VALUES(@steamid, @newKnife) ON DUPLICATE KEY UPDATE `knife` = @newKnife";

try
{
await using var connection = await _database.GetConnectionAsync();
Expand Down

0 comments on commit e9f47e9

Please sign in to comment.