Compare commits

..

15 Commits

Author SHA1 Message Date
Nereziel
be4de02d50 Merge pull request #290 from rogeraabbccdd/dev
Some fixes and changes for dev branch.
2024-07-04 12:10:30 +02:00
rogeraabbccdd
52e4fd7195 Fixed pin not refreshed bug. 2024-07-03 00:59:04 +08:00
rogeraabbccdd
d7631c51b6 Fixed agent menu. 2024-07-03 00:51:35 +08:00
rogeraabbccdd
74aa5fa40c Fixed music kit menu. 2024-07-03 00:48:41 +08:00
rogeraabbccdd
2a1ef7b489 Fixed OnCommandRefresh conditions 2024-07-03 00:47:10 +08:00
rogeraabbccdd
a6f5eac440 Merge branch 'Nereziel/dev' into dev 2024-07-02 23:39:54 +08:00
Nereziel
deb18acb8f remove slot = 0 2024-07-02 17:35:26 +02:00
rogeraabbccdd
e6d935e16b Basic pin feature 2024-07-02 17:28:08 +08:00
rogeraabbccdd
c3d2209a13 Fix database queries 2024-07-02 17:24:41 +08:00
rogeraabbccdd
67b35d9117 !wp should also refresh music and agents 2024-07-01 13:58:27 +08:00
Dawid Bepierszcz
d102ac371b Merge pull request #277 from daffyyyy/dev
New, New, New
2024-06-12 09:53:09 +02:00
Dawid Bepierszcz
26a31d4bd7 New, new, new!
- Config changed
- Multiple commands (as i remember, wp command fucked now)
- Fixed tables
- Remove expired users
- CenterHtmlMenu - experimental
- Knife from string to defindex
2024-06-12 09:52:01 +02:00
Dawid Bepierszcz
4e9a4b2925 Merge pull request #276 from Nereziel/main
Gamedata
2024-06-10 10:33:23 +02:00
Nereziel
c291005d4d next 2024-05-27 18:05:32 +02:00
Nereziel
322b1b425b part one 2024-05-26 19:28:36 +02:00
121 changed files with 13881 additions and 1330 deletions

View File

@@ -1,4 +1,5 @@
using CounterStrikeSharp.API;
using System.Collections.Concurrent;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
@@ -10,17 +11,17 @@ namespace WeaponPaints
{
private void OnCommandRefresh(CCSPlayerController? player, CommandInfo command)
{
if (!Config.Additional.CommandWpEnabled || !Config.Additional.SkinEnabled || !g_bCommandsAllowed) return;
if (Config.Additional.CommandsRefresh.Count == 0 || !Config.Additional.SkinEnabled || !g_bCommandsAllowed) return;
if (!Utility.IsPlayerValid(player)) return;
if (player == null || !player.IsValid || player.UserId == null || player.IsBot) return;
PlayerInfo? playerInfo = new PlayerInfo
PlayerInfo playerInfo = new PlayerInfo
{
UserId = player.UserId,
Slot = player.Slot,
Index = (int)player.Index,
SteamId = player?.SteamID.ToString(),
SteamId = player.SteamID,
Name = player?.PlayerName,
IpAddress = player?.IpAddress?.Split(":")[0]
};
@@ -37,7 +38,11 @@ namespace WeaponPaints
_ = Task.Run(async () => await weaponSync.GetPlayerData(playerInfo));
GivePlayerGloves(player);
GivePlayerAgent(player);
GivePlayerMusicKit(player);
RefreshWeapons(player);
AddTimer(0.1f, () => GivePlayerPin(player));
AddTimer(0.15f, () => GivePlayerPin(player));
}
if (!string.IsNullOrEmpty(Localizer["wp_command_refresh_done"]))
@@ -95,25 +100,33 @@ namespace WeaponPaints
private void RegisterCommands()
{
AddCommand($"css_{Config.Additional.CommandSkin}", "Skins info", (player, info) =>
Config.Additional.CommandsInfo.ForEach(c =>
{
AddCommand($"css_{c}", "Skins info", (player, info) =>
{
if (!Utility.IsPlayerValid(player)) return;
OnCommandWS(player, info);
});
AddCommand($"css_{Config.Additional.CommandRefresh}", "Skins refresh", (player, info) =>
});
Config.Additional.CommandsRefresh.ForEach(c =>
{
AddCommand($"css_{c}", "Skins refresh", (player, info) =>
{
if (!Utility.IsPlayerValid(player)) return;
OnCommandRefresh(player, info);
});
if (Config.Additional.CommandKillEnabled)
});
Config.Additional.CommandsKill.ForEach(c =>
{
AddCommand($"css_{Config.Additional.CommandKill}", "kill yourself", (player, info) =>
AddCommand($"css_{c}", "kill yourself", (player, info) =>
{
if (player == null || !Utility.IsPlayerValid(player) || player.PlayerPawn.Value == null || !player!.PlayerPawn.IsValid) return;
player.PlayerPawn.Value.CommitSuicide(true, false);
});
}
});
}
private void SetupKnifeMenu()
@@ -124,7 +137,8 @@ namespace WeaponPaints
.Where(pair => pair.Key.StartsWith("weapon_knife") || pair.Key.StartsWith("weapon_bayonet"))
.ToDictionary(pair => pair.Key, pair => pair.Value);
var giveItemMenu = new ChatMenu(Localizer["wp_knife_menu_title"]);
BaseMenu giveItemMenu = Config.Additional.UseHtmlMenu ? new CenterHtmlMenu(Localizer["wp_knife_menu_title"], Instance) : new ChatMenu(Localizer["wp_knife_menu_title"]);
var handleGive = (CCSPlayerController player, ChatMenuOption option) =>
{
if (!Utility.IsPlayerValid(player)) return;
@@ -137,7 +151,7 @@ namespace WeaponPaints
player!.Print(Localizer["wp_knife_menu_select", knifeName]);
}
if (!string.IsNullOrEmpty(Localizer["wp_knife_menu_kill"]) && Config.Additional.CommandKillEnabled)
if (!string.IsNullOrEmpty(Localizer["wp_knife_menu_kill"]) && Config.Additional.CommandsKill.Count > 0)
{
player!.Print(Localizer["wp_knife_menu_kill"]);
}
@@ -147,24 +161,33 @@ namespace WeaponPaints
UserId = player.UserId,
Slot = player.Slot,
Index = (int)player.Index,
SteamId = player.SteamID.ToString(),
SteamId = player.SteamID,
Name = player.PlayerName,
IpAddress = player.IpAddress?.Split(":")[0]
};
g_playersKnife[player.Slot] = knifeKey;
var knife = WeaponDefindex
.FirstOrDefault(entry => entry.Value.Equals(knifeKey, StringComparison.OrdinalIgnoreCase))
.Key;
g_playersKnife[player.Slot] = (ushort)knife;
if (g_bCommandsAllowed && (LifeState_t)player.LifeState == LifeState_t.LIFE_ALIVE)
RefreshWeapons(player);
if (weaponSync != null)
_ = Task.Run(async () => await weaponSync.SyncKnifeToDatabase(playerInfo, knifeKey));
if (weaponSync == null) return;
_ = Task.Run(async () => await weaponSync.SyncKnifeToDatabase(playerInfo, (ushort)knife));
};
foreach (var knifePair in knivesOnly)
{
giveItemMenu.AddMenuOption(knifePair.Value, handleGive);
}
AddCommand($"css_{Config.Additional.CommandKnife}", "Knife Menu", (player, info) =>
Config.Additional.CommandsKnife.ForEach(c =>
{
AddCommand($"css_{c}", "Knife Menu", (player, info) =>
{
if (!Utility.IsPlayerValid(player) || !g_bCommandsAllowed) return;
@@ -175,7 +198,8 @@ namespace WeaponPaints
{
commandsCooldown[player.Slot] = DateTime.UtcNow.AddSeconds(Config.CmdRefreshCooldownSeconds);
giveItemMenu.PostSelectAction = PostSelectAction.Close;
MenuManager.OpenChatMenu(player, giveItemMenu);
giveItemMenu.Open(player);
//MenuManager.open(player, giveItemMenu);
return;
}
if (!string.IsNullOrEmpty(Localizer["wp_command_cooldown"]))
@@ -183,12 +207,13 @@ namespace WeaponPaints
player!.Print(Localizer["wp_command_cooldown"]);
}
});
});
}
private void SetupSkinsMenu()
{
var classNamesByWeapon = WeaponList.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
var weaponSelectionMenu = new ChatMenu(Localizer["wp_skin_menu_weapon_title"]);
BaseMenu weaponSelectionMenu = Config.Additional.UseHtmlMenu ? new CenterHtmlMenu(Localizer["wp_skin_menu_weapon_title"], Instance) : new ChatMenu(Localizer["wp_skin_menu_weapon_title"]);
// Function to handle skin selection for a specific weapon
var handleWeaponSelection = (CCSPlayerController? player, ChatMenuOption option) =>
@@ -202,7 +227,7 @@ namespace WeaponPaints
weaponName?.ToString() == selectedWeaponClassname
)?.ToList();
var skinSubMenu = new ChatMenu(Localizer["wp_skin_menu_skin_title", selectedWeapon]);
BaseMenu skinSubMenu = Config.Additional.UseHtmlMenu ? new CenterHtmlMenu(Localizer["wp_skin_menu_skin_title"], Instance) : new ChatMenu(Localizer["wp_skin_menu_skin_title"]);
// Function to handle skin selection for the chosen weapon
var handleSkinSelection = (CCSPlayerController p, ChatMenuOption opt) =>
@@ -224,8 +249,8 @@ namespace WeaponPaints
if (firstSkin == null ||
!firstSkin.TryGetValue("weapon_defindex", out var weaponDefIndexObj) ||
!int.TryParse(weaponDefIndexObj.ToString(), out var weaponDefIndex) ||
!int.TryParse(selectedPaintId, out var paintId)) return;
!ushort.TryParse(weaponDefIndexObj.ToString(), out var weaponDefIndex) ||
!ushort.TryParse(selectedPaintId, out var paintId)) return;
{
if (Config.Additional.ShowSkinImage && skinsList != null)
{
@@ -256,7 +281,7 @@ namespace WeaponPaints
UserId = p.UserId,
Slot = p.Slot,
Index = (int)p.Index,
SteamId = p.SteamID.ToString(),
SteamId = p.SteamID,
Name = p.PlayerName,
IpAddress = p.IpAddress?.Split(":")[0]
};
@@ -293,7 +318,7 @@ namespace WeaponPaints
}
}
if (player != null && Utility.IsPlayerValid(player))
MenuManager.OpenChatMenu(player, skinSubMenu);
skinSubMenu.Open(player);
};
// Add weapon options to the weapon selection menu
@@ -302,7 +327,9 @@ namespace WeaponPaints
weaponSelectionMenu.AddMenuOption(weaponName, handleWeaponSelection);
}
// Command to open the weapon selection menu for players
AddCommand($"css_{Config.Additional.CommandSkinSelection}", "Skins selection menu", (player, info) =>
Config.Additional.CommandsSkinSelection.ForEach(c =>
{
AddCommand($"css_{c}", "Skins selection menu", (player, info) =>
{
if (!Utility.IsPlayerValid(player)) return;
@@ -312,7 +339,7 @@ namespace WeaponPaints
DateTime.UtcNow >= (commandsCooldown.TryGetValue(player.Slot, out cooldownEndTime) ? cooldownEndTime : DateTime.UtcNow))
{
commandsCooldown[player.Slot] = DateTime.UtcNow.AddSeconds(Config.CmdRefreshCooldownSeconds);
MenuManager.OpenChatMenu(player, weaponSelectionMenu);
weaponSelectionMenu.Open(player);
return;
}
if (!string.IsNullOrEmpty(Localizer["wp_command_cooldown"]))
@@ -320,14 +347,12 @@ namespace WeaponPaints
player!.Print(Localizer["wp_command_cooldown"]);
}
});
});
}
private void SetupGlovesMenu()
{
var glovesSelectionMenu = new ChatMenu(Localizer["wp_glove_menu_title"])
{
PostSelectAction = PostSelectAction.Close
};
BaseMenu glovesSelectionMenu = new ChatMenu(Localizer["wp_glove_menu_title"]);
var handleGloveSelection = (CCSPlayerController? player, ChatMenuOption option) =>
{
@@ -353,7 +378,7 @@ namespace WeaponPaints
UserId = player.UserId,
Slot = player.Slot,
Index = (int)player.Index,
SteamId = player.SteamID.ToString(),
SteamId = player.SteamID,
Name = player.PlayerName,
IpAddress = player.IpAddress?.Split(":")[0]
};
@@ -362,13 +387,20 @@ namespace WeaponPaints
{
g_playersGlove[player.Slot] = (ushort)weaponDefindex;
if (!gPlayerWeaponsInfo[player.Slot].ContainsKey(weaponDefindex))
if (!gPlayerWeaponsInfo.TryGetValue(player.Slot, out var value))
{
value = new ConcurrentDictionary<ushort, WeaponInfo>();
gPlayerWeaponsInfo[player.Slot] = value;
}
if (!value.ContainsKey((ushort)weaponDefindex))
{
WeaponInfo weaponInfo = new()
{
Paint = paint
Paint = (ushort)paint
};
gPlayerWeaponsInfo[player.Slot][weaponDefindex] = weaponInfo;
value[(ushort)weaponDefindex] = weaponInfo;
}
}
else
@@ -387,13 +419,13 @@ namespace WeaponPaints
{
await weaponSync.SyncGloveToDatabase(playerInfo, weaponDefindex);
if (!gPlayerWeaponsInfo[playerInfo.Slot].TryGetValue(weaponDefindex, out var value))
if (!gPlayerWeaponsInfo[playerInfo.Slot].TryGetValue((ushort)weaponDefindex, out var value))
{
value = new WeaponInfo();
gPlayerWeaponsInfo[playerInfo.Slot][weaponDefindex] = value;
gPlayerWeaponsInfo[playerInfo.Slot][(ushort)weaponDefindex] = value;
}
value.Paint = paint;
value.Paint = (ushort)paint;
value.Wear = 0.00f;
value.Seed = 0;
@@ -411,7 +443,9 @@ namespace WeaponPaints
}
// Command to open the weapon selection menu for players
AddCommand($"css_{Config.Additional.CommandGlove}", "Gloves selection menu", (player, info) =>
Config.Additional.CommandsGlove.ForEach(c =>
{
AddCommand($"css_{c}", "Gloves selection menu", (player, info) =>
{
if (!Utility.IsPlayerValid(player) || !g_bCommandsAllowed) return;
@@ -421,7 +455,7 @@ namespace WeaponPaints
DateTime.UtcNow >= (commandsCooldown.TryGetValue(player.Slot, out cooldownEndTime) ? cooldownEndTime : DateTime.UtcNow))
{
commandsCooldown[player.Slot] = DateTime.UtcNow.AddSeconds(Config.CmdRefreshCooldownSeconds);
MenuManager.OpenChatMenu(player, glovesSelectionMenu);
glovesSelectionMenu.Open(player);
return;
}
if (!string.IsNullOrEmpty(Localizer["wp_command_cooldown"]))
@@ -429,6 +463,7 @@ namespace WeaponPaints
player!.Print(Localizer["wp_command_cooldown"]);
}
});
});
}
private void SetupAgentsMenu()
@@ -440,7 +475,7 @@ namespace WeaponPaints
var selectedPaintName = option.Text;
var selectedAgent = agentsList.FirstOrDefault(g =>
g.ContainsKey("agent_name") &&
g["agent_name"] != null && g["agent_name"]!.ToString() == selectedPaintName &&
g["agent_name"] != null && g["agent_name"]!.ToString().Contains(selectedPaintName) == true &&
g["team"] != null && (int)(g["team"]!) == player.TeamNum);
if (selectedAgent == null) return;
@@ -454,7 +489,7 @@ namespace WeaponPaints
UserId = player.UserId,
Slot = player.Slot,
Index = (int)player.Index,
SteamId = player.SteamID.ToString(),
SteamId = player.SteamID,
Name = player.PlayerName,
IpAddress = player.IpAddress?.Split(":")[0]
};
@@ -468,7 +503,7 @@ namespace WeaponPaints
if (!string.IsNullOrEmpty(Localizer["wp_agent_menu_select"]))
{
player!.Print(Localizer["wp_agent_menu_select", selectedPaintName]);
player!.Print(Localizer["wp_agent_menu_select", selectedAgent?["agent_name"] ?? selectedPaintName]);
}
if (player.TeamNum == 3)
@@ -490,13 +525,16 @@ namespace WeaponPaints
_ = Task.Run(async () =>
{
await weaponSync.SyncAgentToDatabase(playerInfo);
GivePlayerAgent(player);
});
}
};
};
// Command to open the weapon selection menu for players
AddCommand($"css_{Config.Additional.CommandAgent}", "Agents selection menu", (player, info) =>
Config.Additional.CommandsAgent.ForEach(c =>
{
AddCommand($"css_{c}", "Agents selection menu", (player, info) =>
{
if (!Utility.IsPlayerValid(player) || !g_bCommandsAllowed) return;
@@ -505,10 +543,7 @@ namespace WeaponPaints
if (!commandsCooldown.TryGetValue(player.Slot, out DateTime cooldownEndTime) ||
DateTime.UtcNow >= (commandsCooldown.TryGetValue(player.Slot, out cooldownEndTime) ? cooldownEndTime : DateTime.UtcNow))
{
var agentsSelectionMenu = new ChatMenu(Localizer["wp_agent_menu_title"])
{
PostSelectAction = PostSelectAction.Close
};
BaseMenu agentsSelectionMenu = Config.Additional.UseHtmlMenu ? new CenterHtmlMenu(Localizer["wp_agent_menu_title"], Instance) : new ChatMenu(Localizer["wp_agent_menu_title"]);
var filteredAgents = agentsList.Where(agentObject =>
{
@@ -533,7 +568,7 @@ namespace WeaponPaints
}
commandsCooldown[player.Slot] = DateTime.UtcNow.AddSeconds(Config.CmdRefreshCooldownSeconds);
MenuManager.OpenChatMenu(player, agentsSelectionMenu);
agentsSelectionMenu.Open(player);
return;
}
if (!string.IsNullOrEmpty(Localizer["wp_command_cooldown"]))
@@ -541,14 +576,12 @@ namespace WeaponPaints
player!.Print(Localizer["wp_command_cooldown"]);
}
});
});
}
private void SetupMusicMenu()
{
var musicSelectionMenu = new ChatMenu(Localizer["wp_music_menu_title"])
{
PostSelectAction = PostSelectAction.Close
};
BaseMenu musicSelectionMenu = Config.Additional.UseHtmlMenu ? new CenterHtmlMenu(Localizer["wp_music_menu_title"], Instance) : new ChatMenu(Localizer["wp_music_menu_title"]);
var handleMusicSelection = (CCSPlayerController? player, ChatMenuOption option) =>
{
@@ -556,7 +589,7 @@ namespace WeaponPaints
var selectedPaintName = option.Text;
var selectedMusic = musicList.FirstOrDefault(g => g.ContainsKey("name") && g["name"]?.ToString() == selectedPaintName);
var selectedMusic = musicList.FirstOrDefault(g => g.ContainsKey("name") && g["name"]?.ToString().Contains(selectedPaintName) == true);
if (selectedMusic != null)
{
if (!selectedMusic.ContainsKey("id") ||
@@ -574,7 +607,7 @@ namespace WeaponPaints
UserId = player.UserId,
Slot = player.Slot,
Index = (int)player.Index,
SteamId = player.SteamID.ToString(),
SteamId = player.SteamID,
Name = player.PlayerName,
IpAddress = player.IpAddress?.Split(":")[0]
};
@@ -590,7 +623,7 @@ namespace WeaponPaints
if (!string.IsNullOrEmpty(Localizer["wp_music_menu_select"]))
{
player!.Print(Localizer["wp_music_menu_select", selectedPaintName]);
player!.Print(Localizer["wp_music_menu_select", selectedMusic["name"] ?? selectedPaintName]);
}
if (weaponSync != null)
@@ -610,7 +643,7 @@ namespace WeaponPaints
UserId = player.UserId,
Slot = player.Slot,
Index = (int)player.Index,
SteamId = player.SteamID.ToString(),
SteamId = player.SteamID,
Name = player.PlayerName,
IpAddress = player.IpAddress?.Split(":")[0]
};
@@ -630,6 +663,8 @@ namespace WeaponPaints
});
}
}
GivePlayerMusicKit(player);
};
musicSelectionMenu.AddMenuOption(Localizer["None"], handleMusicSelection);
@@ -639,8 +674,10 @@ namespace WeaponPaints
musicSelectionMenu.AddMenuOption(paintName, handleMusicSelection);
}
Config.Additional.CommandsMusic.ForEach(c =>
{
// Command to open the weapon selection menu for players
AddCommand($"css_{Config.Additional.CommandMusic}", "Music selection menu", (player, info) =>
AddCommand($"css_{c}", "Music selection menu", (player, info) =>
{
if (!Utility.IsPlayerValid(player) || !g_bCommandsAllowed) return;
@@ -650,7 +687,7 @@ namespace WeaponPaints
DateTime.UtcNow >= (commandsCooldown.TryGetValue(player.Slot, out cooldownEndTime) ? cooldownEndTime : DateTime.UtcNow))
{
commandsCooldown[player.Slot] = DateTime.UtcNow.AddSeconds(Config.CmdRefreshCooldownSeconds);
MenuManager.OpenChatMenu(player, musicSelectionMenu);
musicSelectionMenu.Open(player);
return;
}
if (!string.IsNullOrEmpty(Localizer["wp_command_cooldown"]))
@@ -658,6 +695,7 @@ namespace WeaponPaints
player!.Print(Localizer["wp_command_cooldown"]);
}
});
});
}
}
}

View File

@@ -3,6 +3,24 @@ using System.Text.Json.Serialization;
namespace WeaponPaints
{
public class DatabaseCredentials
{
[JsonPropertyName("DatabaseHost")]
public string DatabaseHost { get; set; } = "";
[JsonPropertyName("DatabasePort")]
public int DatabasePort { get; set; } = 3306;
[JsonPropertyName("DatabaseUser")]
public string DatabaseUser { get; set; } = "";
[JsonPropertyName("DatabasePassword")]
public string DatabasePassword { get; set; } = "";
[JsonPropertyName("DatabaseName")]
public string DatabaseName { get; set; } = "";
}
public class Additional
{
[JsonPropertyName("KnifeEnabled")]
@@ -20,35 +38,35 @@ namespace WeaponPaints
[JsonPropertyName("SkinEnabled")]
public bool SkinEnabled { get; set; } = true;
[JsonPropertyName("CommandWpEnabled")]
public bool CommandWpEnabled { get; set; } = true;
[JsonPropertyName("NameTagEnabled")]
public bool NameTagEnabled { get; set; } = true;
[JsonPropertyName("CommandKillEnabled")]
public bool CommandKillEnabled { get; set; } = true;
[JsonPropertyName("PinEnabled")]
public bool PinEnabled { get; set; } = true;
[JsonPropertyName("CommandKnife")]
public string CommandKnife { get; set; } = "knife";
[JsonPropertyName("CommandsKnife")]
public List<string> CommandsKnife { get; set; } = ["knife", "knives"];
[JsonPropertyName("CommandMusic")]
public string CommandMusic { get; set; } = "music";
[JsonPropertyName("CommandsMusic")]
public List<string> CommandsMusic { get; set; } = ["music", "musickits", "mkit"];
[JsonPropertyName("CommandGlove")]
public string CommandGlove { get; set; } = "gloves";
[JsonPropertyName("CommandsGlove")]
public List<string> CommandsGlove { get; set; } = ["gloves", "glove"];
[JsonPropertyName("CommandAgent")]
public string CommandAgent { get; set; } = "agents";
[JsonPropertyName("CommandsAgent")]
public List<string> CommandsAgent { get; set; } = ["agents", "agent"];
[JsonPropertyName("CommandSkin")]
public string CommandSkin { get; set; } = "ws";
[JsonPropertyName("CommandsInfo")]
public List<string> CommandsInfo { get; set; } = ["ws", "skininfo"];
[JsonPropertyName("CommandSkinSelection")]
public string CommandSkinSelection { get; set; } = "skins";
[JsonPropertyName("CommandsSkinSelection")]
public List<string> CommandsSkinSelection { get; set; } = ["skins", "skin"];
[JsonPropertyName("CommandRefresh")]
public string CommandRefresh { get; set; } = "wp";
[JsonPropertyName("CommandsRefresh")]
public List<string> CommandsRefresh { get; set; } = ["wp", "refreshskins"];
[JsonPropertyName("CommandKill")]
public string CommandKill { get; set; } = "kill";
[JsonPropertyName("CommandsKill")]
public List<string> CommandsKill { get; set; } = ["kill", "suicide"];
[JsonPropertyName("GiveRandomKnife")]
public bool GiveRandomKnife { get; set; } = false;
@@ -58,26 +76,20 @@ namespace WeaponPaints
[JsonPropertyName("ShowSkinImage")]
public bool ShowSkinImage { get; set; } = true;
[JsonPropertyName("UseHtmlMenu")]
public bool UseHtmlMenu { get; set; } = true;
[JsonPropertyName("ExpireOlderThan")]
public int ExpireOlderThan { get; set; } = 90;
}
public class WeaponPaintsConfig : BasePluginConfig
{
public override int Version { get; set; } = 6;
public override int Version { get; set; } = 7;
[JsonPropertyName("DatabaseHost")]
public string DatabaseHost { get; set; } = "";
[JsonPropertyName("DatabasePort")]
public int DatabasePort { get; set; } = 3306;
[JsonPropertyName("DatabaseUser")]
public string DatabaseUser { get; set; } = "";
[JsonPropertyName("DatabasePassword")]
public string DatabasePassword { get; set; } = "";
[JsonPropertyName("DatabaseName")]
public string DatabaseName { get; set; } = "";
[JsonPropertyName("DatabaseCredentials")]
public DatabaseCredentials DatabaseCredentials { get; set; } = new();
[JsonPropertyName("CmdRefreshCooldownSeconds")]
public int CmdRefreshCooldownSeconds { get; set; } = 60;

View File

@@ -5,13 +5,11 @@ namespace WeaponPaints
{
public class Database(string dbConnectionString)
{
private readonly string _dbConnectionString = dbConnectionString;
public async Task<MySqlConnection> GetConnectionAsync()
{
try
{
var connection = new MySqlConnection(_dbConnectionString);
var connection = new MySqlConnection(dbConnectionString);
await connection.OpenAsync();
return connection;
}

View File

@@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Entities;
@@ -23,14 +23,14 @@ namespace WeaponPaints
UserId = player.UserId,
Slot = player.Slot,
Index = (int)player.Index,
SteamId = player.SteamID.ToString(),
SteamId = player.SteamID,
Name = player.PlayerName,
IpAddress = player.IpAddress?.Split(":")[0]
};
try
{
_ = Task.Run(async () => await weaponSync.GetPlayerData(playerInfo));
_ = Task.Run(async () => await weaponSync.GetPlayerDatabaseIndex(playerInfo));
/*
if (Config.Additional.SkinEnabled)
{
@@ -54,8 +54,9 @@ namespace WeaponPaints
}
*/
}
catch
catch (Exception)
{
return HookResult.Continue;
}
return HookResult.Continue;
@@ -68,6 +69,8 @@ namespace WeaponPaints
if (player is null || !player.IsValid || player.IsBot) return HookResult.Continue;
g_playersDatabaseIndex.TryRemove(player.Slot, out _);
if (Config.Additional.SkinEnabled)
{
gPlayerWeaponsInfo.TryRemove(player.Slot, out _);
@@ -88,6 +91,10 @@ namespace WeaponPaints
{
g_playersMusic.TryRemove(player.Slot, out _);
}
if (Config.Additional.PinEnabled)
{
g_playersPin.TryRemove(player.Slot, out _);
}
commandsCooldown.Remove(player.Slot);
@@ -100,6 +107,9 @@ namespace WeaponPaints
if (_database != null)
weaponSync = new WeaponSynchronization(_database, Config);
if (weaponSync != null)
Task.Run(async () => await weaponSync.PurgeExpiredUsers());
}
private HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info)
@@ -119,6 +129,7 @@ namespace WeaponPaints
GivePlayerMusicKit(player);
GivePlayerAgent(player);
GivePlayerGloves(player);
GivePlayerPin(player);
return HookResult.Continue;
}

View File

@@ -5,7 +5,7 @@
public int Index { get; set; }
public int Slot { get; init; }
public int? UserId { get; set; }
public string? SteamId { get; init; }
public ulong? SteamId { get; init; }
public string? Name { get; set; }
public string? IpAddress { get; set; }
}

View File

@@ -25,7 +25,6 @@ Unfinished, unoptimized and not fully functional ugly demo weapon paints plugin
- Run server with plugin, **it will generate config if installed correctly!**
- Edit `addons/counterstrikesharp/configs/`**`plugins/WeaponPaints/WeaponPaints.json`** include database credentials
- In `addons/counterstrikesharp/configs/`**`core.json`** set **FollowCS2ServerGuidelines** to **`false`**
- Copy from plugins folder gamedata file **`weaponpaints.json`** to folder **`addons/counterstrikesharp/gamedata/`**
## Plugin Configuration
<details>
@@ -72,7 +71,7 @@ Unfinished, unoptimized and not fully functional ugly demo weapon paints plugin
</details>
## Web install
- Requires PHP >= 7.4 with curl and pdo_mysql ***(Tested on php ver **`8.2.3`** and nginx webserver)***
- Requires PHP >= 7.4 ***(Tested on php ver **`8.2.3`** and nginx webserver)***
- **Before using website, make sure the plugin is correctly loaded in cs2 server!** Mysql tables are created by plugin not by website.
- Copy website to web server ***(Folder `img` not needed)***
- Get [Steam API Key](https://steamcommunity.com/dev/apikey)

View File

@@ -1,4 +1,4 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Translations;
using Dapper;
using Microsoft.Extensions.Logging;
@@ -23,45 +23,16 @@ namespace WeaponPaints
try
{
string[] createTableQueries =
[
"""
CREATE TABLE IF NOT EXISTS `wp_player_skins` (
`steamid` varchar(18) NOT NULL,
`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
) ENGINE=InnoDB
""",
@"CREATE TABLE IF NOT EXISTS `wp_player_knife` (
`steamid` varchar(18) NOT NULL,
`knife` varchar(64) NOT NULL,
UNIQUE (`steamid`)
) ENGINE = InnoDB",
"""
CREATE TABLE IF NOT EXISTS `wp_player_gloves` (
`steamid` varchar(18) NOT NULL,
`weapon_defindex` int(11) NOT NULL,
UNIQUE (`steamid`)
) ENGINE=InnoDB
""",
"""
CREATE TABLE IF NOT EXISTS `wp_player_agents` (
`steamid` varchar(18) NOT NULL,
`agent_ct` varchar(64) DEFAULT NULL,
`agent_t` varchar(64) DEFAULT NULL,
UNIQUE (`steamid`)
) ENGINE=InnoDB
""",
"""
CREATE TABLE IF NOT EXISTS `wp_player_music` (
`steamid` varchar(64) NOT NULL,
`music_id` int(11) NOT NULL,
UNIQUE (`steamid`)
) ENGINE=InnoDB
""",
];
var createTableQueries = new[]
{
"CREATE TABLE IF NOT EXISTS `wp_users` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `steamid` BIGINT UNSIGNED NOT NULL, `last_online` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `unique_steamid` (`steamid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;",
"CREATE TABLE IF NOT EXISTS `wp_users_skins` (`user_id` INT UNSIGNED NOT NULL, `weapon` SMALLINT UNSIGNED NOT NULL, `paint` SMALLINT UNSIGNED NOT NULL, `wear` FLOAT NOT NULL DEFAULT 0.001, `seed` SMALLINT UNSIGNED NOT NULL DEFAULT 0, `nametag` VARCHAR(20) DEFAULT NULL, `stattrack` INT UNSIGNED NOT NULL DEFAULT 0, `stattrack_enabled` SMALLINT NOT NULL DEFAULT 0, `quality` SMALLINT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`user_id`,`weapon`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;",
"CREATE TABLE IF NOT EXISTS `wp_users_knives` (`user_id` INT UNSIGNED NOT NULL, `knife` SMALLINT UNSIGNED NOT NULL, PRIMARY KEY (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;",
"CREATE TABLE IF NOT EXISTS `wp_users_gloves` (`user_id` INT UNSIGNED NOT NULL, `weapon_defindex` SMALLINT UNSIGNED DEFAULT NULL, PRIMARY KEY (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;",
"CREATE TABLE IF NOT EXISTS `wp_users_musics` (`user_id` INT UNSIGNED NOT NULL, `music` SMALLINT UNSIGNED DEFAULT NULL, PRIMARY KEY (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;",
"CREATE TABLE IF NOT EXISTS `wp_users_agents` (`user_id` INT UNSIGNED NOT NULL,`agent_ct` varchar(64) DEFAULT NULL,`agent_t` varchar(64) DEFAULT NULL, PRIMARY KEY (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;",
"CREATE TABLE IF NOT EXISTS `wp_users_pins` (`user_id` INT UNSIGNED NOT NULL, `pin` SMALLINT UNSIGNED DEFAULT NULL, PRIMARY KEY (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;",
};
foreach (var query in createTableQueries)
{

View File

@@ -1,14 +1,16 @@
using CounterStrikeSharp.API;
using System.Diagnostics;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
using System.Runtime.InteropServices;
namespace WeaponPaints
{
namespace WeaponPaints;
public partial class WeaponPaints
{
private void GivePlayerWeaponSkin(CCSPlayerController player, CBasePlayerWeapon weapon)
@@ -16,35 +18,35 @@ namespace WeaponPaints
if (!Config.Additional.SkinEnabled) return;
if (!gPlayerWeaponsInfo.TryGetValue(player.Slot, out _)) return;
bool isKnife = weapon.DesignerName.Contains("knife") || weapon.DesignerName.Contains("bayonet");
var isKnife = weapon.DesignerName.Contains("knife") || weapon.DesignerName.Contains("bayonet");
if (isKnife && !g_playersKnife.ContainsKey(player.Slot) || isKnife && g_playersKnife[player.Slot] == "weapon_knife") return;
if ((isKnife && !g_playersKnife.ContainsKey(player.Slot)) ||
(isKnife && g_playersKnife[player.Slot] < 500)) return;
int[] newPaints = { 106, 112, 113, 114, 115, 117, 118, 120, 121, 123, 126, 127, 128, 129, 130, 131, 133, 134, 137, 138, 139, 140, 142, 144, 145, 146, 152, 160, 161, 163, 173, 239, 292, 324, 331, 412, 461, 513, 766, 768, 770, 773, 774, 830, 831, 832, 834, 874, 875, 877, 878, 882, 883, 901, 912, 936, 937, 938, 939, 940, 1054, 1062, 1159, 1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1177, 1178, 1179, 1180 };
int[] newPaints =
[1171, 1170, 1169, 1164, 1162, 1161, 1159, 1175, 1174, 1167, 1165, 1168, 1163, 1160, 1166, 1173];
if (isKnife)
{
var newDefIndex = WeaponDefindex.FirstOrDefault(x => x.Value == g_playersKnife[player.Slot]);
if (newDefIndex.Key == 0) return;
//var newDefIndex = WeaponDefindex.FirstOrDefault(x => x.Value == g_playersKnife[player.Slot]);
//if (newDefIndex.Key == 0) return;
if (weapon.AttributeManager.Item.ItemDefinitionIndex != newDefIndex.Key)
{
SubclassChange(weapon, (ushort)newDefIndex.Key);
}
if (weapon.AttributeManager.Item.ItemDefinitionIndex != g_playersKnife[player.Slot])
SubclassChange(weapon, (ushort)g_playersKnife[player.Slot]);
weapon.AttributeManager.Item.ItemDefinitionIndex = (ushort)newDefIndex.Key;
weapon.AttributeManager.Item.ItemDefinitionIndex = (ushort)g_playersKnife[player.Slot];
weapon.AttributeManager.Item.EntityQuality = 3;
}
UpdatePlayerEconItemId(weapon.AttributeManager.Item);
int weaponDefIndex = weapon.AttributeManager.Item.ItemDefinitionIndex;
int fallbackPaintKit = 0;
var fallbackPaintKit = 0;
weapon.AttributeManager.Item.AccountID = (uint)player.SteamID;
if (_config.Additional.GiveRandomSkin &&
!gPlayerWeaponsInfo[player.Slot].ContainsKey(weaponDefIndex))
!gPlayerWeaponsInfo[player.Slot].ContainsKey((ushort)weaponDefIndex))
{
// Random skins
weapon.FallbackPaintKit = GetRandomPaint(weaponDefIndex);
@@ -52,14 +54,21 @@ namespace WeaponPaints
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 seed", 0);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture wear", 0.01f);
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 seed", 0);
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.AttributeList.Handle, "set item texture wear", 0.01f);
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);
fallbackPaintKit = weapon.FallbackPaintKit;
@@ -71,7 +80,7 @@ namespace WeaponPaints
return;
}
if (!gPlayerWeaponsInfo[player.Slot].TryGetValue(weaponDefIndex, out var value) || value.Paint == 0) return;
if (!gPlayerWeaponsInfo[player.Slot].TryGetValue((ushort)weaponDefIndex, out var value) || value.Paint == 0) return;
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]}");
@@ -81,7 +90,9 @@ namespace WeaponPaints
weapon.FallbackPaintKit = weaponInfo.Paint;
weapon.FallbackSeed = weaponInfo.Seed;
weapon.FallbackWear = weaponInfo.Wear;
CAttributeListSetOrAddAttributeValueByName.Invoke(weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture prefab", weapon.FallbackPaintKit);
CAttributeListSetOrAddAttributeValueByName.Invoke(
weapon.AttributeManager.Item.NetworkedDynamicAttributes.Handle, "set item texture prefab",
weapon.FallbackPaintKit);
fallbackPaintKit = weapon.FallbackPaintKit;
@@ -106,12 +117,10 @@ namespace WeaponPaints
{
if (!_config.Additional.KnifeEnabled) return false;
if (player == null || !player.IsValid || !player.PlayerPawn.IsValid)
{
return false;
}
if (player == null || !player.IsValid || !player.PlayerPawn.IsValid) return false;
if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.WeaponServices == null || player.PlayerPawn.Value.ItemServices == null)
if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.WeaponServices == null ||
player.PlayerPawn.Value.ItemServices == null)
return false;
var weapons = player.PlayerPawn.Value.WeaponServices?.MyWeapons;
@@ -120,17 +129,17 @@ namespace WeaponPaints
{
if (!weapon.IsValid || weapon.Value == null || !weapon.Value.IsValid) continue;
if (weapon.Value.DesignerName.Contains("knife") || weapon.Value.DesignerName.Contains("bayonet"))
{
return true;
}
}
return false;
}
private void RefreshWeapons(CCSPlayerController? player)
{
if (!g_bCommandsAllowed) return;
if (player == null || !player.IsValid || player.PlayerPawn?.Value == null || (LifeState_t)player.LifeState != LifeState_t.LIFE_ALIVE)
if (player == null || !player.IsValid || player.PlayerPawn?.Value == null ||
(LifeState_t)player.LifeState != LifeState_t.LIFE_ALIVE)
return;
if (player.PlayerPawn.Value.WeaponServices == null || player.PlayerPawn.Value.ItemServices == null)
return;
@@ -152,7 +161,7 @@ namespace WeaponPaints
!weapon.Value.IsValid || !weapon.Value.DesignerName.Contains("weapon_"))
continue;
CCSWeaponBaseGun gun = weapon.Value.As<CCSWeaponBaseGun>();
var gun = weapon.Value.As<CCSWeaponBaseGun>();
if (weapon.Value.Entity == null) continue;
if (!weapon.Value.OwnerEntity.IsValid) continue;
@@ -162,17 +171,19 @@ namespace WeaponPaints
try
{
CCSWeaponBaseVData? weaponData = weapon.Value.As<CCSWeaponBase>().VData;
var weaponData = weapon.Value.As<CCSWeaponBase>().VData;
if (weaponData == null) continue;
if (weaponData.GearSlot == gear_slot_t.GEAR_SLOT_RIFLE || weaponData.GearSlot == gear_slot_t.GEAR_SLOT_PISTOL)
if (weaponData.GearSlot == gear_slot_t.GEAR_SLOT_RIFLE ||
weaponData.GearSlot == gear_slot_t.GEAR_SLOT_PISTOL)
{
if (!WeaponDefindex.TryGetValue(weapon.Value.AttributeManager.Item.ItemDefinitionIndex, out var weaponByDefindex))
if (!WeaponDefindex.TryGetValue(weapon.Value.AttributeManager.Item.ItemDefinitionIndex,
out var weaponByDefindex))
continue;
int clip1 = weapon.Value.Clip1;
int reservedAmmo = weapon.Value.ReserveAmmo[0];
var clip1 = weapon.Value.Clip1;
var reservedAmmo = weapon.Value.ReserveAmmo[0];
if (!weaponsWithAmmo.TryGetValue(weaponByDefindex, out var value))
{
@@ -190,7 +201,6 @@ namespace WeaponPaints
catch (Exception ex)
{
Logger.LogWarning(ex.Message);
continue;
}
}
@@ -201,7 +211,7 @@ namespace WeaponPaints
var weapon = player.PlayerPawn.Value.WeaponServices.ActiveWeapon;
if (!weapon.IsValid || weapon.Value == null) return;
CCSWeaponBaseVData? weaponData = weapon.Value.As<CCSWeaponBase>().VData;
var weaponData = weapon.Value.As<CCSWeaponBase>().VData;
if (weapon.Value.DesignerName.Contains("knife") || weaponData?.GearSlot == gear_slot_t.GEAR_SLOT_KNIFE)
{
@@ -238,7 +248,6 @@ namespace WeaponPaints
if (!g_bCommandsAllowed) return;
foreach (var entry in weaponsWithAmmo)
{
foreach (var ammo in entry.Value)
{
var newWeapon = new CBasePlayerWeapon(player.GiveNamedItem(entry.Key));
@@ -255,7 +264,6 @@ namespace WeaponPaints
}
});
}
}
}, TimerFlags.STOP_ON_MAPCHANGE);
}
@@ -263,7 +271,7 @@ namespace WeaponPaints
{
if (!Utility.IsPlayerValid(player) || (LifeState_t)player.LifeState != LifeState_t.LIFE_ALIVE) return;
CCSPlayerPawn? pawn = player.PlayerPawn.Value;
var pawn = player.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid)
return;
@@ -276,7 +284,7 @@ namespace WeaponPaints
Instance.AddTimer(0.06f, () =>
{
CEconItemView item = pawn.EconGloves;
var item = pawn.EconGloves;
try
{
if (!player.IsValid)
@@ -287,21 +295,26 @@ namespace WeaponPaints
if (!g_playersGlove.TryGetValue(player.Slot, out var gloveInfo) || gloveInfo == 0) return;
WeaponInfo weaponInfo = gPlayerWeaponsInfo[player.Slot][gloveInfo];
var weaponInfo = gPlayerWeaponsInfo[player.Slot][gloveInfo];
item.ItemDefinitionIndex = gloveInfo;
item.ItemIDLow = 16384 & 0xFFFFFFFF;
item.ItemIDHigh = 16384;
CAttributeListSetOrAddAttributeValueByName.Invoke(item.NetworkedDynamicAttributes.Handle, "set item texture prefab", weaponInfo.Paint);
CAttributeListSetOrAddAttributeValueByName.Invoke(item.NetworkedDynamicAttributes.Handle, "set item texture seed", weaponInfo.Seed);
CAttributeListSetOrAddAttributeValueByName.Invoke(item.NetworkedDynamicAttributes.Handle, "set item texture wear", weaponInfo.Wear);
CAttributeListSetOrAddAttributeValueByName.Invoke(item.NetworkedDynamicAttributes.Handle,
"set item texture prefab", weaponInfo.Paint);
CAttributeListSetOrAddAttributeValueByName.Invoke(item.NetworkedDynamicAttributes.Handle,
"set item texture seed", weaponInfo.Seed);
CAttributeListSetOrAddAttributeValueByName.Invoke(item.NetworkedDynamicAttributes.Handle,
"set item texture wear", weaponInfo.Wear);
item.Initialized = true;
SetBodygroup(pawn.Handle, "default_gloves", 1);
}
catch (Exception) { }
catch (Exception)
{
}
}, TimerFlags.STOP_ON_MAPCHANGE);
}
@@ -310,7 +323,7 @@ namespace WeaponPaints
if (skinsList.Count == 0)
return 0;
Random rnd = new Random();
var rnd = new Random();
// Filter weapons by the provided defindex
var filteredWeapons = skinsList.Where(w => w["weapon_defindex"]?.ToString() == defindex.ToString()).ToList();
@@ -338,13 +351,11 @@ namespace WeaponPaints
var skeleton = weapon.CBodyComponent.SceneNode.GetSkeletonInstance();
var value = (ulong)(isLegacy ? 2 : 1);
if (skeleton.ModelState.MeshGroupMask != value)
{
skeleton.ModelState.MeshGroupMask = value;
}
if (skeleton.ModelState.MeshGroupMask != value) skeleton.ModelState.MeshGroupMask = value;
}
private static void UpdatePlayerWeaponMeshGroupMask(CCSPlayerController player, CBasePlayerWeapon weapon, bool isLegacy)
private static void UpdatePlayerWeaponMeshGroupMask(CCSPlayerController player, CBasePlayerWeapon weapon,
bool isLegacy)
{
UpdateWeaponMeshGroupMask(weapon, isLegacy);
@@ -390,6 +401,18 @@ namespace WeaponPaints
Utilities.SetStateChanged(player, "CCSPlayerController", "m_iMusicKitID");
}
private static void GivePlayerPin(CCSPlayerController player)
{
if (!g_playersPin.TryGetValue(player.Slot, out var value)) return;
if (player.InventoryServices == null) return;
for (var index = 0; index < player.InventoryServices.Rank.Length; index++)
{
player.InventoryServices.Rank[index] = index == 5 ? (MedalRank_t)value : MedalRank_t.MEDAL_RANK_NONE;
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInventoryServices");
}
}
private void GiveOnItemPickup(CCSPlayerController player)
{
var pawn = player.PlayerPawn.Value;
@@ -400,10 +423,7 @@ namespace WeaponPaints
foreach (var handle in myWeapons)
{
var weapon = handle.Value;
if (weapon != null && weapon.DesignerName.Contains("knife"))
{
GivePlayerWeaponSkin(player, weapon);
}
if (weapon != null && weapon.DesignerName.Contains("knife")) GivePlayerWeaponSkin(player, weapon);
}
}
@@ -424,28 +444,26 @@ namespace WeaponPaints
return !Utility.IsPlayerValid(player) ? null : player;
}
private static unsafe CBaseViewModel? GetPlayerViewModel(CCSPlayerController player)
private static CBaseViewModel? GetPlayerViewModel(CCSPlayerController player)
{
if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.ViewModelServices == null) return null;
CCSPlayer_ViewModelServices viewModelServices = new(player.PlayerPawn.Value.ViewModelServices!.Handle);
var ptr = viewModelServices.Handle + Schema.GetSchemaOffset("CCSPlayer_ViewModelServices", "m_hViewModel");
var references = MemoryMarshal.CreateSpan(ref ptr, 3);
var viewModel = (CHandle<CBaseViewModel>)Activator.CreateInstance(typeof(CHandle<CBaseViewModel>), references[0])!;
var viewModel =
(CHandle<CBaseViewModel>)Activator.CreateInstance(typeof(CHandle<CBaseViewModel>), references[0])!;
return viewModel.Value == null ? null : viewModel.Value;
}
public static unsafe T[] GetFixedArray<T>(nint pointer, string @class, string member, int length) where T : CHandle<CBaseViewModel>
public static T[] GetFixedArray<T>(nint pointer, string @class, string member, int length)
where T : CHandle<CBaseViewModel>
{
var ptr = pointer + Schema.GetSchemaOffset(@class, member);
var references = MemoryMarshal.CreateSpan(ref ptr, length);
var values = new T[length];
for (var i = 0; i < length; i++)
{
values[i] = (T)Activator.CreateInstance(typeof(T), references[i])!;
}
for (var i = 0; i < length; i++) values[i] = (T)Activator.CreateInstance(typeof(T), references[i])!;
return values;
}
}
}

View File

@@ -2,8 +2,12 @@
{
public class WeaponInfo
{
public int Paint { get; set; }
public int Seed { get; set; } = 0;
public ushort Paint { get; set; }
public ushort Seed { get; set; } = 0;
public float Wear { get; set; } = 0f;
public string? NameTag { get; set; }
public ushort Quality { get; set; }
public uint StatTrack { get; set; }
public bool StatTrackEnabled { get; set; }
}
}

View File

@@ -8,10 +8,11 @@ using MySqlConnector;
using Newtonsoft.Json.Linq;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Modules.Entities.Constants;
namespace WeaponPaints;
[MinimumApiVersion(230)]
[MinimumApiVersion(238)]
public partial class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig>
{
internal static WeaponPaints Instance { get; private set; } = new();
@@ -79,15 +80,18 @@ public partial class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig
private static WeaponPaintsConfig _config = new();
internal static IStringLocalizer? _localizer;
private static Dictionary<int, int> g_knifePickupCount = new();
internal static ConcurrentDictionary<int, string> g_playersKnife = new();
internal static ConcurrentDictionary<int, ushort> g_playersKnife = new();
internal static ConcurrentDictionary<int, ushort> g_playersGlove = new();
internal static ConcurrentDictionary<int, ushort> g_playersMusic = new();
internal static ConcurrentDictionary<int, ushort> g_playersPin = new();
internal static ConcurrentDictionary<int, (string? CT, string? T)> g_playersAgent = new();
internal static ConcurrentDictionary<int, ConcurrentDictionary<int, WeaponInfo>> gPlayerWeaponsInfo = new();
internal static List<JObject> skinsList = new();
internal static List<JObject> glovesList = new();
internal static List<JObject> agentsList = new();
internal static List<JObject> musicList = new();
internal static ConcurrentDictionary<int, ConcurrentDictionary<ushort, WeaponInfo>> gPlayerWeaponsInfo = new();
internal static ConcurrentDictionary<int, int> g_playersDatabaseIndex = new();
internal static List<JObject> skinsList = [];
internal static List<JObject> glovesList = [];
internal static List<JObject> agentsList = [];
internal static List<JObject> musicList = [];
internal static WeaponSynchronization? weaponSync;
private static bool g_bCommandsAllowed = true;
private Dictionary<int, string> PlayerWeaponImage = new();
@@ -160,7 +164,6 @@ public partial class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig
{ 525, "weapon_knife_skeleton" },
{ 526, "weapon_knife_kukri" }
};
private const ulong MinimumCustomItemId = 65578;
private ulong _nextItemId = MinimumCustomItemId;
public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
@@ -196,14 +199,14 @@ public partial class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig
UserId = player.UserId,
Slot = player.Slot,
Index = (int)player.Index,
SteamId = player?.SteamID.ToString(),
SteamId = player?.SteamID,
Name = player?.PlayerName,
IpAddress = player?.IpAddress?.Split(":")[0]
};
_ = Task.Run(async () =>
{
if (weaponSync != null) await weaponSync.GetPlayerData(playerInfo);
if (weaponSync != null) await weaponSync.GetPlayerDatabaseIndex(playerInfo);
});
}
}
@@ -230,7 +233,7 @@ public partial class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig
public void OnConfigParsed(WeaponPaintsConfig config)
{
if (config.DatabaseHost.Length < 1 || config.DatabaseName.Length < 1 || config.DatabaseUser.Length < 1)
if (config.DatabaseCredentials.DatabaseHost.Length < 1 || config.DatabaseCredentials.DatabaseName.Length < 1 || config.DatabaseCredentials.DatabaseUser.Length < 1)
{
Logger.LogError("You need to setup Database credentials in \"configs/plugins/WeaponPaints/WeaponPaints.json\"!");
Unload(false);
@@ -246,11 +249,11 @@ public partial class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig
var builder = new MySqlConnectionStringBuilder
{
Server = config.DatabaseHost,
UserID = config.DatabaseUser,
Password = config.DatabasePassword,
Database = config.DatabaseName,
Port = (uint)config.DatabasePort,
Server = config.DatabaseCredentials.DatabaseHost,
UserID = config.DatabaseCredentials.DatabaseUser,
Password = config.DatabaseCredentials.DatabasePassword,
Database = config.DatabaseCredentials.DatabaseName,
Port = (uint)config.DatabaseCredentials.DatabasePort,
Pooling = true,
MaximumPoolSize = 640,
};

View File

@@ -1,9 +1,9 @@
using Dapper;
using MySqlConnector;
using System.Collections.Concurrent;
using Dapper;
using MySqlConnector;
namespace WeaponPaints;
namespace WeaponPaints
{
internal class WeaponSynchronization
{
private readonly WeaponPaintsConfig _config;
@@ -15,6 +15,46 @@ namespace WeaponPaints
_config = config;
}
internal async Task GetPlayerDatabaseIndex(PlayerInfo playerInfo)
{
if (playerInfo.SteamId == null) return;
Console.WriteLine("test");
try
{
await using var connection = await _database.GetConnectionAsync();
var query = "SELECT `id` FROM `wp_users` WHERE `steamid` = @steamid";
var databaseIndex =
await connection.QueryFirstOrDefaultAsync<int?>(query, new { steamid = playerInfo.SteamId });
if (databaseIndex != null)
{
WeaponPaints.g_playersDatabaseIndex[playerInfo.Slot] = (int)databaseIndex;
query = "UPDATE `wp_users` SET `last_online` = @lastUpdate WHERE `id` = @databaseIndex";
await connection.ExecuteAsync(query, new
{
lastUpdate = DateTime.Now,
databaseIndex
});
}
else
{
Console.WriteLine("test");
const string insertQuery = "INSERT INTO `wp_users` (`steamid`) VALUES (@steamid)";
await connection.ExecuteAsync(insertQuery, new { steamid = playerInfo.SteamId });
Console.WriteLine("SQL Insert Query: " + insertQuery);
databaseIndex =
await connection.QueryFirstOrDefaultAsync<int?>(query, new { steamid = playerInfo.SteamId });
WeaponPaints.g_playersDatabaseIndex[playerInfo.Slot] = (int)databaseIndex;
}
await GetPlayerData(playerInfo);
}
catch (Exception ex)
{
Utility.Log($"An error occurred in GetPlayerDatabaseIndex: {ex.Message}");
}
}
internal async Task GetPlayerData(PlayerInfo? player)
{
try
@@ -31,6 +71,8 @@ namespace WeaponPaints
GetMusicFromDatabase(player, connection);
if (_config.Additional.SkinEnabled)
GetWeaponPaintsFromDatabase(player, connection);
if (_config.Additional.PinEnabled)
GetPinFromDatabase(player, connection);
}
catch (Exception ex)
{
@@ -43,16 +85,14 @@ namespace WeaponPaints
{
try
{
if (!_config.Additional.KnifeEnabled || string.IsNullOrEmpty(player?.SteamId))
if (!_config.Additional.KnifeEnabled || string.IsNullOrEmpty(player?.SteamId.ToString()))
return;
const string query = "SELECT `knife` FROM `wp_player_knife` WHERE `steamid` = @steamid";
var playerKnife = connection.QueryFirstOrDefault<string>(query, new { steamid = player.SteamId });
const string query = "SELECT `knife` FROM `wp_users_knives` WHERE `user_id` = @userId";
var playerKnife = connection.QueryFirstOrDefault<int>(query,
new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot] });
if (!string.IsNullOrEmpty(playerKnife))
{
WeaponPaints.g_playersKnife[player.Slot] = playerKnife;
}
WeaponPaints.g_playersKnife[player.Slot] = (ushort)playerKnife;
}
catch (Exception ex)
{
@@ -64,16 +104,13 @@ namespace WeaponPaints
{
try
{
if (!_config.Additional.GloveEnabled || string.IsNullOrEmpty(player?.SteamId))
if (!_config.Additional.GloveEnabled || string.IsNullOrEmpty(player?.SteamId.ToString()))
return;
const string query = "SELECT `weapon_defindex` FROM `wp_player_gloves` WHERE `steamid` = @steamid";
var gloveData = connection.QueryFirstOrDefault<ushort?>(query, new { steamid = player.SteamId });
const string query = "SELECT `weapon_defindex` FROM `wp_users_gloves` WHERE `user_id` = @userId";
var gloveData = connection.QueryFirstOrDefault<ushort?>(query, new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot] });
if (gloveData != null)
{
WeaponPaints.g_playersGlove[player.Slot] = gloveData.Value;
}
if (gloveData != null) WeaponPaints.g_playersGlove[player.Slot] = gloveData.Value;
}
catch (Exception ex)
{
@@ -85,24 +122,22 @@ namespace WeaponPaints
{
try
{
if (!_config.Additional.AgentEnabled || string.IsNullOrEmpty(player?.SteamId))
if (!_config.Additional.AgentEnabled || string.IsNullOrEmpty(player?.SteamId.ToString()))
return;
const string query = "SELECT `agent_ct`, `agent_t` FROM `wp_player_agents` WHERE `steamid` = @steamid";
var agentData = connection.QueryFirstOrDefault<(string, string)>(query, new { steamid = player.SteamId });
const string query = "SELECT `agent_ct`, `agent_t` FROM `wp_users_agents` WHERE `user_id` = @userId";
var agentData = connection.QueryFirstOrDefault<(string, string)>(query, new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot] });
if (agentData == default) return;
var agentCT = agentData.Item1;
var agentT = agentData.Item2;
if (!string.IsNullOrEmpty(agentCT) || !string.IsNullOrEmpty(agentT))
{
WeaponPaints.g_playersAgent[player.Slot] = (
agentCT,
agentT
);
}
}
catch (Exception ex)
{
Utility.Log($"An error occurred in GetAgentFromDatabase: {ex.Message}");
@@ -113,26 +148,29 @@ namespace WeaponPaints
{
try
{
if (!_config.Additional.SkinEnabled || player == null || string.IsNullOrEmpty(player.SteamId))
if (!_config.Additional.SkinEnabled || player == null || string.IsNullOrEmpty(player.SteamId.ToString()))
return;
var weaponInfos = new ConcurrentDictionary<int, WeaponInfo>();
var weaponInfos = new ConcurrentDictionary<ushort, WeaponInfo>();
const string query = "SELECT * FROM `wp_player_skins` WHERE `steamid` = @steamid";
var playerSkins = connection.Query<dynamic>(query, new { steamid = player.SteamId });
const string query = "SELECT `weapon`, `paint`, `wear`, `seed`, `nametag` FROM `wp_users_skins` WHERE `user_id` = @userId";
var playerSkins = connection.Query<dynamic>(query, new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot] });
foreach (var row in playerSkins)
{
int weaponDefIndex = row?.weapon_defindex ?? 0;
int weaponPaintId = row?.weapon_paint_id ?? 0;
float weaponWear = row?.weapon_wear ?? 0f;
int weaponSeed = row?.weapon_seed ?? 0;
ushort weaponDefIndex = (ushort)(row.weapon ?? 0);
ushort weaponPaintId = (ushort)(row.paint ?? 0);
float weaponWear = row.wear ?? 0f;
ushort weaponSeed = (ushort)(row.seed ?? 0);
string weaponNameTag = row.nametag ?? string.Empty;
WeaponInfo weaponInfo = new WeaponInfo
var weaponInfo = new WeaponInfo
{
Paint = weaponPaintId,
Seed = weaponSeed,
Wear = weaponWear
Wear = weaponWear,
NameTag = weaponNameTag
};
weaponInfos[weaponDefIndex] = weaponInfo;
@@ -150,15 +188,81 @@ namespace WeaponPaints
{
try
{
if (!_config.Additional.MusicEnabled || string.IsNullOrEmpty(player?.SteamId))
if (!_config.Additional.MusicEnabled || string.IsNullOrEmpty(player?.SteamId.ToString()))
return;
const string query = "SELECT `music_id` FROM `wp_player_music` WHERE `steamid` = @steamid";
var musicData = connection.QueryFirstOrDefault<ushort?>(query, new { steamid = player.SteamId });
const string query = "SELECT `music` FROM `wp_users_musics` WHERE `user_id` = @userId";
var musicData = connection.QueryFirstOrDefault<ushort?>(query, new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot] });
if (musicData != null)
if (musicData != null) WeaponPaints.g_playersMusic[player.Slot] = musicData.Value;
}
catch (Exception ex)
{
WeaponPaints.g_playersMusic[player.Slot] = musicData.Value;
Utility.Log($"An error occurred in GetMusicFromDatabase: {ex.Message}");
}
}
private void GetPinFromDatabase(PlayerInfo? player, MySqlConnection connection)
{
try
{
if (!_config.Additional.PinEnabled || string.IsNullOrEmpty(player?.SteamId.ToString()))
return;
const string query = "SELECT `pin` FROM `wp_users_pins` WHERE `user_id` = @userId";
var pinData = connection.QueryFirstOrDefault<ushort?>(query, new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot] });
if (pinData != null) WeaponPaints.g_playersPin[player.Slot] = pinData.Value;
}
catch (Exception ex)
{
Utility.Log($"An error occurred in GetPinFromDatabase: {ex.Message}");
}
}
internal async Task PurgeExpiredUsers()
{
try
{
await using var connection = await _database.GetConnectionAsync();
await using var transaction = await connection.BeginTransactionAsync();
var userIds = await connection.QueryAsync<int>(
$"SELECT id FROM wp_users WHERE last_online < NOW() - INTERVAL {_config.Additional.ExpireOlderThan} DAY",
transaction: transaction
);
var ids = string.Join(",", userIds);
string query;
if (userIds.AsList().Count > 0)
{
// Step 2: Delete related records in other tables using the retrieved IDs
query = $"DELETE FROM wp_users_agents WHERE user_id IN ({ids})";
await connection.ExecuteAsync(query, transaction: transaction);
query = $"DELETE FROM wp_users_gloves WHERE user_id IN ({ids})";
await connection.ExecuteAsync(query, transaction: transaction);
query = $"DELETE FROM wp_users_skins WHERE user_id IN ({ids})";
await connection.ExecuteAsync(query, transaction: transaction);
query = $"DELETE FROM wp_users_knives WHERE user_id IN ({ids})";
await connection.ExecuteAsync(query, transaction: transaction);
query = $"DELETE FROM wp_users_musics WHERE user_id IN ({ids})";
await connection.ExecuteAsync(query, transaction: transaction);
query = $"DELETE FROM wp_users_pins WHERE user_id IN ({ids})";
await connection.ExecuteAsync(query, transaction: transaction);
// Step 3: Delete users from wp_users
query = $"DELETE FROM wp_users WHERE id IN ({ids})";
await connection.ExecuteAsync(query, transaction: transaction);
// Commit transaction
await transaction.CommitAsync();
}
}
catch (Exception ex)
@@ -167,18 +271,17 @@ namespace WeaponPaints
}
}
internal async Task SyncKnifeToDatabase(PlayerInfo player, string knife)
internal async Task SyncKnifeToDatabase(PlayerInfo player, ushort knife)
{
if (!_config.Additional.KnifeEnabled || string.IsNullOrEmpty(player.SteamId) || string.IsNullOrEmpty(knife)) return;
if (!_config.Additional.KnifeEnabled || string.IsNullOrEmpty(player.SteamId.ToString())) return;
const string query = "INSERT INTO `wp_player_knife` (`steamid`, `knife`) VALUES(@steamid, @newKnife) ON DUPLICATE KEY UPDATE `knife` = @newKnife";
const string query =
"INSERT INTO `wp_users_knives` (`user_id`, `knife`) VALUES(@userId, @newKnife) ON DUPLICATE KEY UPDATE `knife` = @newKnife";
try
{
await using var connection = await _database.GetConnectionAsync();
await connection.ExecuteAsync(query, new { steamid = player.SteamId, newKnife = knife });
await connection.ExecuteAsync(query, new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot], newKnife = knife });
}
catch (Exception e)
{
@@ -188,13 +291,14 @@ namespace WeaponPaints
internal async Task SyncGloveToDatabase(PlayerInfo player, int defindex)
{
if (!_config.Additional.GloveEnabled || string.IsNullOrEmpty(player.SteamId)) return;
if (!_config.Additional.GloveEnabled || string.IsNullOrEmpty(player.SteamId.ToString())) return;
try
{
await using var connection = await _database.GetConnectionAsync();
const string query = "INSERT INTO `wp_player_gloves` (`steamid`, `weapon_defindex`) VALUES(@steamid, @weapon_defindex) ON DUPLICATE KEY UPDATE `weapon_defindex` = @weapon_defindex";
await connection.ExecuteAsync(query, new { steamid = player.SteamId, weapon_defindex = defindex });
const string query =
"INSERT INTO `wp_users_gloves` (`user_id`, `weapon_defindex`) VALUES(@userId, @weapon_defindex) ON DUPLICATE KEY UPDATE `weapon_defindex` = @weapon_defindex";
await connection.ExecuteAsync(query, new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot], weapon_defindex = defindex });
}
catch (Exception e)
{
@@ -204,11 +308,11 @@ namespace WeaponPaints
internal async Task SyncAgentToDatabase(PlayerInfo player)
{
if (!_config.Additional.AgentEnabled || string.IsNullOrEmpty(player.SteamId)) return;
if (!_config.Additional.AgentEnabled || string.IsNullOrEmpty(player.SteamId.ToString())) return;
const string query = """
INSERT INTO `wp_player_agents` (`steamid`, `agent_ct`, `agent_t`)
VALUES(@steamid, @agent_ct, @agent_t)
INSERT INTO `wp_users_agents` (`user_id`, `agent_ct`, `agent_t`)
VALUES(@userId, @agent_ct, @agent_t)
ON DUPLICATE KEY UPDATE
`agent_ct` = @agent_ct,
`agent_t` = @agent_t
@@ -217,7 +321,12 @@ namespace WeaponPaints
{
await using var connection = await _database.GetConnectionAsync();
await connection.ExecuteAsync(query, new { steamid = player.SteamId, agent_ct = WeaponPaints.g_playersAgent[player.Slot].CT, agent_t = WeaponPaints.g_playersAgent[player.Slot].T });
await connection.ExecuteAsync(query,
new
{
userId = WeaponPaints.g_playersDatabaseIndex[player.Slot], agent_ct = WeaponPaints.g_playersAgent[player.Slot].CT,
agent_t = WeaponPaints.g_playersAgent[player.Slot].T
});
}
catch (Exception e)
{
@@ -227,7 +336,8 @@ namespace WeaponPaints
internal async Task SyncWeaponPaintsToDatabase(PlayerInfo player)
{
if (string.IsNullOrEmpty(player.SteamId) || !WeaponPaints.gPlayerWeaponsInfo.TryGetValue(player.Slot, out var weaponsInfo))
if (string.IsNullOrEmpty(player.SteamId.ToString()) ||
!WeaponPaints.gPlayerWeaponsInfo.TryGetValue(player.Slot, out var weaponsInfo))
return;
try
@@ -240,23 +350,27 @@ namespace WeaponPaints
var wear = weaponInfo.Wear;
var seed = weaponInfo.Seed;
const string queryCheckExistence = "SELECT COUNT(*) FROM `wp_player_skins` WHERE `steamid` = @steamid AND `weapon_defindex` = @weaponDefIndex";
const string queryCheckExistence =
"SELECT COUNT(*) FROM `wp_users_skins` WHERE `user_id` = @userId AND `weapon` = @weaponDefIndex";
var existingRecordCount = await connection.ExecuteScalarAsync<int>(queryCheckExistence, new { steamid = player.SteamId, weaponDefIndex = weaponDefIndex });
var existingRecordCount = await connection.ExecuteScalarAsync<int>(queryCheckExistence,
new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot], weaponDefIndex });
string query;
object parameters;
if (existingRecordCount > 0)
{
query = "UPDATE `wp_player_skins` SET `weapon_paint_id` = @paintId, `weapon_wear` = @wear, `weapon_seed` = @seed WHERE `steamid` = @steamid AND `weapon_defindex` = @weaponDefIndex";
parameters = new { steamid = player.SteamId, weaponDefIndex = weaponDefIndex, paintId, wear, seed };
query =
"UPDATE `wp_users_skins` SET `paint` = @paintId, `wear` = @wear, `seed` = @seed WHERE `user_id` = @userId AND `weapon` = @weaponDefIndex";
parameters = new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot], weaponDefIndex, paintId, wear, seed };
}
else
{
query = "INSERT INTO `wp_player_skins` (`steamid`, `weapon_defindex`, `weapon_paint_id`, `weapon_wear`, `weapon_seed`) " +
"VALUES (@steamid, @weaponDefIndex, @paintId, @wear, @seed)";
parameters = new { steamid = player.SteamId, weaponDefIndex = weaponDefIndex, paintId, wear, seed };
query =
"INSERT INTO `wp_users_skins` (`user_id`, `weapon`, `paint`, `wear`, `seed`) " +
"VALUES (@userId, @weaponDefIndex, @paintId, @wear, @seed)";
parameters = new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot], weaponDefIndex, paintId, wear, seed };
}
await connection.ExecuteAsync(query, parameters);
@@ -270,13 +384,14 @@ namespace WeaponPaints
internal async Task SyncMusicToDatabase(PlayerInfo player, ushort music)
{
if (!_config.Additional.MusicEnabled || string.IsNullOrEmpty(player.SteamId)) return;
if (!_config.Additional.MusicEnabled || string.IsNullOrEmpty(player.SteamId.ToString())) return;
try
{
await using var connection = await _database.GetConnectionAsync();
const string query = "INSERT INTO `wp_player_music` (`steamid`, `music_id`) VALUES(@steamid, @newMusic) ON DUPLICATE KEY UPDATE `music_id` = @newMusic";
await connection.ExecuteAsync(query, new { steamid = player.SteamId, newMusic = music });
const string query =
"INSERT INTO `wp_users_musics` (`user_id`, `music`) VALUES(@userId, @newMusic) ON DUPLICATE KEY UPDATE `music` = @newMusic";
await connection.ExecuteAsync(query, new { userId = WeaponPaints.g_playersDatabaseIndex[player.Slot], newMusic = music });
}
catch (Exception e)
{
@@ -284,4 +399,3 @@ namespace WeaponPaints
}
}
}
}

View File

@@ -3,7 +3,7 @@
"signatures": {
"library": "server",
"windows": "40 57 48 83 EC 20 48 8B F9 41 B0 01",
"linux": "55 48 89 E5 41 57 41 56 41 55 49 89 F5 41 54 49 89 FC 53 48 81 EC A8 00 00 00"
"linux": "55 48 89 E5 41 57 41 56 49 89 FE 41 55 41 54 45 31 E4 53 48 81 EC 98 00 00 00"
}
},
"CAttributeList_SetOrAddAttributeValueByName": {

View File

@@ -1,29 +1,66 @@
<?php
/**
* Class DataBase
*
* This class handles database operations using PDO.
*/
class DataBase {
/**
* @var PDO The PDO instance for database connection.
*/
private $PDO;
/**
* Constructor method to initialize the database connection.
*/
public function __construct() {
try {
$this->PDO = new PDO("mysql:host=".DB_HOST."; port=".DB_PORT."; dbname=".DB_NAME, DB_USER, DB_PASS, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
// Establish a connection to the database using PDO
$this->PDO = new PDO(
"mysql:host=".DB_HOST.";port=".DB_PORT.";dbname=".DB_NAME,
DB_USER,
DB_PASS
);
// Set the connection to use utf8 encoding
$this->PDO->exec("SET NAMES utf8");
}
catch(PDOException $ex)
{
catch(PDOException $ex) {
// Display error message if connection fails
echo "<div style='display: flex; flex-direction: column;align-items: center;justify-content: center;text-align: center;'><h2>Problem with database!</h2>";
die("<pre style='padding: 10px;text-wrap: balance; border: 2px solid #ed6bd3;background: #252525; color: #ed6bd3; width: 50%;'>" . $ex . "</pre>");
}
}
public function select($query, $bindings = []) {
/**
* Perform a SELECT query on the database.
*
* @param string $query The SQL query to execute.
* @param array $bindings An associative array of parameters and their values.
* @return array|false Returns an array of rows as associative arrays or false if no results are found.
*/
public function select($query, $bindings = array()) {
// Prepare and execute the SQL query
$STH = $this->PDO->prepare($query);
$STH->execute($bindings);
// Fetch the results as associative arrays
$result = $STH->fetchAll(PDO::FETCH_ASSOC);
$result ??= false;
if ($result === false) {
$result = array(); // Set $result to an empty array if no results found
}
return $result;
}
public function query($query, $bindings = []){
/**
* Perform a non-query SQL statement on the database.
*
* @param string $query The SQL query to execute.
* @param array $bindings An associative array of parameters and their values.
* @return bool Returns true on success or false on failure.
*/
public function query($query, $bindings = array()) {
// Prepare and execute the SQL query
$STH = $this->PDO->prepare($query);
return $STH->execute($bindings);
}
}

78
website/class/header.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Set security headers to enhance security
// header("X-Frame-Options: SAMEORIGIN");
// header("X-XSS-Protection: 1; mode=block");
// header("X-Content-Type-Options: nosniff");
// header("Referrer-Policy: no-referrer-when-downgrade");
// header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://code.jquery.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https://cdn.jsdelivr.net https://steamcommunity-a.akamaihd.net https://raw.githubusercontent.com;");
// Include necessary classes and files
require 'class/config.php';
require 'class/database.php';
require 'steamauth/steamauth.php';
require 'class/utils.php';
// Create a database instance
$db = new DataBase();
// Check if the user is logged in
if (isset($_SESSION['steamid'])) {
// Insert or update user's Steam ID in the database
$steamid = $_SESSION['steamid'];
$db->query("INSERT INTO `wp_users` (`steamid`) VALUES ('{$steamid}') ON DUPLICATE KEY UPDATE `updated_at` = CURRENT_TIMESTAMP");
// Get user's database index
$userInfoQuery = $db->select("SELECT `id` FROM `wp_users` WHERE `steamid` = :steamid", ["steamid" => $steamid]);
$_SESSION['userDbIndex'] = $userDbIndex = (int)$userInfoQuery[0]['id'];
// Get weapons and skins information
$weapons = UtilsClass::getWeaponsFromArray();
$skins = UtilsClass::skinsFromJson();
$gloves = UtilsClass::glovesFromJson();
// Retrieve user's selected skins and knife
$querySelected = $db->select("SELECT `weapon`, `paint`, `wear`, `seed`, `nametag` FROM `wp_users_skins` WHERE `user_id` = :user_id", ["user_id" => $userDbIndex]);
$selectedSkins = UtilsClass::getSelectedSkins($querySelected);
$selectedKnifeResult = $db->select("SELECT `knife` FROM `wp_users_knives` WHERE `user_id` = :user_id", ["user_id" => $userDbIndex]);
$selectedGlovesResult = $db->select("SELECT `weapon_defindex` FROM `wp_users_gloves` WHERE `user_id` = :user_id", ["user_id" => $userDbIndex]);
$selectedGloves = !empty($selectedGlovesResult) ? $selectedGlovesResult[0] : $gloves[0][0];
// Determine user's selected knife or set default knife
if (!empty($selectedKnifeResult)) {
$selectedKnife = $selectedKnifeResult[0]['knife'];
} else {
$selectedKnife = "weapon_knife";
}
$knifes = UtilsClass::getKnifeTypes();
// Handle form submission
if (isset($_POST['forma'])) {
$ex = explode("-", $_POST['forma']);
// Handle knife selection
if ($ex[0] == "knife") {
$db->query("INSERT INTO `wp_users_knives` (`user_id`, `knife`) VALUES(:user_id, :knife) ON DUPLICATE KEY UPDATE `knife` = :knife", ["user_id" => $userDbIndex, "knife" => $knifes[$ex[1]]['weapon_name']]);
} else {
// Handle skin selection
if (array_key_exists($ex[1], $skins[$ex[0]]) && isset($_POST['wear']) && $_POST['wear'] >= 0.00 && $_POST['wear'] <= 1.00 && isset($_POST['seed'])) {
$wear = floatval($_POST['wear']); // wear
$seed = intval($_POST['seed']); // seed
// Check if the skin is already selected and update or insert accordingly
if (array_key_exists($ex[0], $selectedSkins)) {
$db->query("UPDATE wp_users_skins SET paint = :weapon_paint_id, wear = :weapon_wear, seed = :weapon_seed WHERE user_id = :user_id AND weapon = :weapon_defindex", ["user_id" => $userDbIndex, "weapon_defindex" => $ex[0], "weapon_paint_id" => $ex[1], "weapon_wear" => $wear, "weapon_seed" => $seed]);
} else {
$db->query("INSERT INTO wp_users_skins (`user_id`, `weapon`, `paint`, `wear`, `seed`) VALUES (:user_id, :weapon_defindex, :weapon_paint_id, :weapon_wear, :weapon_seed)", ["user_id" => $userDbIndex, "weapon_defindex" => $ex[0], "weapon_paint_id" => $ex[1], "weapon_wear" => $wear, "weapon_seed" => $seed]);
}
}
}
// Redirect to the same page after form submission
header("Location: {$_SERVER['PHP_SELF']}");
}
}
?>

View File

@@ -1,99 +1,158 @@
<?php
/**
* Class UtilsClass
*
* Provides utility methods for handling skin and weapon data.
*/
class UtilsClass
{
public static function skinsFromJson(): array
/**
* Retrieve skins data from the JSON file.
*
* @return array An associative array containing skin data.
*/
public static function skinsFromJson()
{
$skins = [];
$json = json_decode(file_get_contents(__DIR__ . "/../data/skins.json"), true);
$skins = array();
$jsonFilePath = __DIR__ . "/../data/skins.json";
if (file_exists($jsonFilePath) && is_readable($jsonFilePath)) {
$json = json_decode(file_get_contents($jsonFilePath), true);
foreach ($json as $skin) {
$skins[(int) $skin['weapon_defindex']][(int) $skin['paint']] = [
$skins[(int) $skin['weapon_defindex']][(int) $skin['paint']] = array(
'weapon_name' => $skin['weapon_name'],
'paint_name' => $skin['paint_name'],
'image_url' => $skin['image'],
];
);
}
} else {
// Handle file not found or unreadable error
// You can throw an exception or log an error message
}
return $skins;
}
/**
* Retrieve music data from the JSON file.
*
* @return array An associative array containing music data.
*/
public static function musicFromJson()
{
$music = array();
$jsonFilePath = __DIR__ . "/../data/music.json";
if (file_exists($jsonFilePath) && is_readable($jsonFilePath)) {
$json = json_decode(file_get_contents($jsonFilePath), true);
foreach ($json as $track) {
$music[$track['id']] = array(
'name' => $track['name'],
'image' => $track['image'],
);
}
} else {
// Handle file not found or unreadable error
// You can throw an exception or log an error message
}
return $music;
}
public static function glovesFromJson()
{
$gloves = array();
$jsonFilePath = __DIR__ . "/../data/gloves.json";
if (file_exists($jsonFilePath) && is_readable($jsonFilePath)) {
$json = json_decode(file_get_contents($jsonFilePath), true);
foreach ($json as $glove) {
$gloves[$glove['weapon_defindex']][$glove['paint']] = array(
'paint_name' => $glove['paint_name'],
'image_url' => $glove['image'],
);
}
} else {
// Handle file not found or unreadable error
// You can throw an exception or log an error message
}
return $gloves;
}
/**
* Retrieve weapons data from the skin data array.
*
* @return array An associative array containing weapon data.
*/
public static function getWeaponsFromArray()
{
$weapons = [];
$temp = self::skinsFromJson();
$weapons = array();
$skinsData = self::skinsFromJson();
foreach ($temp as $key => $value) {
if (key_exists($key, $weapons))
continue;
$weapons[$key] = [
foreach ($skinsData as $key => $value) {
$weapons[$key] = array(
'weapon_name' => $value[0]['weapon_name'],
'paint_name' => $value[0]['paint_name'],
'image_url' => $value[0]['image_url'],
];
);
}
return $weapons;
}
/**
* Retrieve knife types from the weapon data array.
*
* @return array An associative array containing knife types data.
*/
public static function getKnifeTypes()
{
$knifes = [];
$temp = self::getWeaponsFromArray();
$knifes = array();
$weaponsData = self::getWeaponsFromArray();
foreach ($temp as $key => $weapon) {
if (
!in_array($key, [
500,
503,
505,
506,
507,
508,
509,
512,
514,
515,
516,
517,
518,
519,
520,
521,
522,
523,
525,
526
])
)
continue;
$allowedKnifeKeys = array(
500, 503, 505, 506, 507, 508, 509, 512, 514, 515,
516, 517, 518, 519, 520, 521, 522, 523, 525
);
$knifes[$key] = [
foreach ($weaponsData as $key => $weapon) {
if (in_array($key, $allowedKnifeKeys)) {
$knifes[$key] = array(
'weapon_name' => $weapon['weapon_name'],
'paint_name' => rtrim(explode("|", $weapon['paint_name'])[0]),
'image_url' => $weapon['image_url'],
];
$knifes[0] = [
);
}
}
// Add default knife
$knifes[0] = array(
'weapon_name' => "weapon_knife",
'paint_name' => "Default knife",
'image_url' => "https://raw.githubusercontent.com/Nereziel/cs2-WeaponPaints/main/website/img/skins/weapon_knife.png",
];
}
);
ksort($knifes);
return $knifes;
}
public static function getSelectedSkins(array $temp)
/**
* Retrieve selected skins data from the database result.
*
* @param array $temp An array containing the selected skins data.
* @return array An associative array containing selected skins data.
*/
public static function getSelectedSkins($temp)
{
$selected = [];
$selected = array();
foreach ($temp as $weapon) {
$selected[$weapon['weapon_defindex']] = [
'weapon_paint_id' => $weapon['weapon_paint_id'],
'weapon_seed' => $weapon['weapon_seed'],
'weapon_wear' => $weapon['weapon_wear'],
];
$selected[$weapon['weapon']] = array(
'weapon_paint_id' => $weapon['paint'],
'weapon_seed' => $weapon['seed'],
'weapon_wear' => $weapon['wear'],
);
}
return $selected;

View File

@@ -119,12 +119,6 @@
"model": "tm_professional/tm_professional_varf4",
"agent_name": "Sir Bloody Loudmouth Darryl | The Professionals"
},
{
"team": 2,
"image": "https://raw.githubusercontent.com/daffyyyy/cs2-WeaponPaints/main/website/img/skins/agent-4613.png",
"model": "tm_professional/tm_professional_varf5",
"agent_name": "Bloody Darryl The Strapped | The Professionals"
},
{
"team": 3,
"image": "https://raw.githubusercontent.com/daffyyyy/cs2-WeaponPaints/main/website/img/skins/agent-4749.png",

View File

@@ -2,7 +2,7 @@
{
"weapon_defindex": 0,
"paint": "0",
"image": "https://raw.githubusercontent.com/Nereziel/cs2-WeaponPaints/main/website/img/skins/default_gloves.png",
"image": "",
"paint_name": "Gloves | Default"
},
{

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

54
website/db-sqlite.sql Normal file
View File

@@ -0,0 +1,54 @@
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS `wp_players` (
`user_id` INTEGER PRIMARY KEY AUTOINCREMENT,
`steamid` INTEGER NOT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(`steamid`)
);
CREATE TABLE IF NOT EXISTS `wp_player_skins` (
`user_id` INTEGER NOT NULL,
`team` INTEGER NOT NULL,
`weapon_defindex` INTEGER NOT NULL,
`paint` INTEGER NOT NULL,
`wear` REAL NOT NULL DEFAULT 0.001,
`seed` INTEGER NOT NULL DEFAULT 0,
`nametag` TEXT DEFAULT NULL,
`stattrack` INTEGER NOT NULL DEFAULT 0,
`stattrack_enabled` INTEGER NOT NULL DEFAULT 0,
`quality` INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (`user_id`,`team`,`weapon_defindex`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `wp_players_knife` (
`user_id` INTEGER NOT NULL,
`knife` TEXT DEFAULT NULL,
PRIMARY KEY (`user_id`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `wp_players_gloves` (
`user_id` INTEGER NOT NULL,
`weapon_defindex` INTEGER DEFAULT NULL,
`team` INTEGER DEFAULT NULL,
PRIMARY KEY (`user_id`,`team`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `wp_players_music` (
`user_id` INTEGER NOT NULL,
`music` INTEGER DEFAULT NULL,
PRIMARY KEY (`user_id`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `wp_players_agents` (
`user_id` INTEGER NOT NULL,
`agent_ct` TEXT DEFAULT NULL,
`agent_t` TEXT DEFAULT NULL,
PRIMARY KEY (`user_id`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
);

53
website/db.sql Normal file
View File

@@ -0,0 +1,53 @@
CREATE TABLE IF NOT EXISTS `wp_players` (
`user_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`steamid` BIGINT UNSIGNED NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
UNIQUE KEY `unique_steamid` (`steamid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `wp_player_skins` (
`user_id` INT UNSIGNED NOT NULL,
`team` SMALLINT UNSIGNED NOT NULL,
`weapon_defindex` SMALLINT UNSIGNED NOT NULL,
`paint` SMALLINT UNSIGNED NOT NULL,
`wear` FLOAT NOT NULL DEFAULT 0.001,
`seed` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`nametag` VARCHAR(20) DEFAULT NULL,
`stattrack` INT UNSIGNED NOT NULL DEFAULT 0,
`stattrack_enabled` SMALLINT NOT NULL DEFAULT 0,
`quality` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`user_id`,`team`,`weapon_defindex`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `wp_players_knife` (
`user_id` INT UNSIGNED NOT NULL,
`knife` VARCHAR(32) DEFAULT NULL,
PRIMARY KEY (`user_id`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `wp_players_gloves` (
`user_id` INT UNSIGNED NOT NULL,
`weapon_defindex` SMALLINT UNSIGNED DEFAULT NULL,
`team` SMALLINT UNSIGNED DEFAULT NULL,
PRIMARY KEY (`user_id`,`team`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `wp_players_music` (
`user_id` INT UNSIGNED NOT NULL,
`music` SMALLINT UNSIGNED DEFAULT NULL,
PRIMARY KEY (`user_id`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `wp_players_agents` (
`user_id` INT UNSIGNED NOT NULL,
`agent_ct` varchar(64) DEFAULT NULL,
`agent_t` varchar(64) DEFAULT NULL,
PRIMARY KEY (`user_id`),
FOREIGN KEY (`user_id`) REFERENCES `wp_players`(`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -8,7 +8,7 @@ $weapons = array (
"weapon_aug" => 8,
"weapon_awp" => 9,
"weapon_famas" => 10,
"weapon_g3sg1" => 11,
"weapon_g3sg1" => 10,
"weapon_galilar" => 13,
"weapon_m249" => 14,
"weapon_m4a1" => 16,
@@ -53,14 +53,7 @@ $weapons = array (
"weapon_knife_stiletto" => 522,
"weapon_knife_widowmaker" => 523,
"weapon_knife_skeleton" => 525);
$
$json = json_decode(file_get_contents('https://bymykel.github.io/CSGO-API/api/en/skins.json'));
die(var_dump($json));
$json = json_decode(file_get_contents('skins.json'));
echo "<pre>";
foreach($json as $skin)
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Some files were not shown because too many files have changed in this diff Show More