Compare commits

...

83 Commits

Author SHA1 Message Date
Nereziel
04bb7a2575 Merge pull request #56 from crashzk/patch-1
Update README.md
2023-11-21 19:44:51 +01:00
crashzk
f00ba48f60 Merge branch 'main' into patch-1 2023-11-21 15:43:49 -03:00
crashzk
08342e4a99 Update README.md 2023-11-21 15:43:02 -03:00
Nereziel
29a6041d7a fixed discord link 2023-11-21 19:35:52 +01:00
crashzk
c44433766c Update README.md 2023-11-19 19:22:28 -03:00
crashzk
a987ed972a Update README.md 2023-11-19 19:20:33 -03:00
Nereziel
50777661c5 Merge pull request #55 from daffyyyy/patch-1
Fix when player select "Select skin" as skin
2023-11-19 22:52:08 +01:00
Dawid Bepierszcz
31fd014f55 Fix when player select "Select skin" as skin 2023-11-19 22:42:32 +01:00
Nereziel
760429e644 Merge pull request #54 from daffyyyy/update-readme-1
Update README.md
2023-11-19 22:36:28 +01:00
Dawid Bepierszcz
304d8501cc Update README.md 2023-11-19 22:25:38 +01:00
Nereziel
e9f7db5171 Merge pull request #51 from daffyyyy/website-fix-1
Fixed blank page
2023-11-19 21:14:15 +01:00
Dawid Bepierszcz
648b928b4e Update index.php 2023-11-19 21:10:55 +01:00
Nereziel
530a7d64c7 Update README.md 2023-11-19 20:45:12 +01:00
Nereziel
3453f4c505 Merge pull request #47 from daffyyyy/feature/global-share
EXPERIMENTAL (GlobalShare) - Website for all servers
2023-11-19 19:03:05 +01:00
Nereziel
25b466422b Update build.yml 2023-11-19 19:02:26 +01:00
daffyyyy
6baa59dd9b Update WeaponPaints.cs
New version
2023-11-19 12:30:47 +01:00
daffyyyy
10afe7ce1e Update WeaponPaints.cs 2023-11-19 12:24:57 +01:00
daffyyyy
5eeb0c5fec Initial 2023-11-19 02:07:29 +01:00
Nereziel
e53ee27b39 Merge pull request #44 from daffyyyy/fix-knives_finally
Finally fixed knife problem?
2023-11-18 19:26:37 +01:00
Dawid Bepierszcz
75112b02fe Update WeaponPaints.cs 2023-11-18 19:21:28 +01:00
Nereziel
8e92635231 sync paints on plugin reload 2023-11-18 15:20:26 +01:00
Nereziel
6923295cb6 Update build.yml 2023-11-18 15:14:40 +01:00
Nereziel
e5f98077f3 Update build.yml 2023-11-18 15:12:57 +01:00
Nereziel
3296055841 Merge pull request #42 from daffyyyy/enhancement/mysql-wrapper
Creating tables fix
2023-11-18 11:18:15 +01:00
Dawid Bepierszcz
89de192c20 Update WeaponPaints.cs
Additional check
2023-11-18 09:15:20 +01:00
Dawid Bepierszcz
a1297b9cbe Update WeaponPaints.cs
Creating tables fix
2023-11-18 08:54:33 +01:00
Nereziel
99af078b32 Merge pull request #40 from daffyyyy/enhancement/mysql-wrapper
New mysql stuff, and small changes
2023-11-18 01:29:06 +01:00
Dawid Bepierszcz
b0bef746cd Delete website/database.sql 2023-11-17 23:28:33 +01:00
daffyyyy
117a95e99c New mysql stuff, and small changes 2023-11-17 23:27:07 +01:00
Nereziel
a6d4108ca7 Merge pull request #37 from daffyyyy/hotfix-2
Fixed server crashing
2023-11-16 20:51:10 +01:00
Dawid Bepierszcz
7b45169d8d Update WeaponPaints.cs
Typo
2023-11-16 20:42:20 +01:00
Dawid Bepierszcz
42bd45c3f0 Update WeaponPaints.cs
Fixed crashing
2023-11-16 20:38:35 +01:00
Nereziel
5262739c3d Merge pull request #35 from daffyyyy/fix-knife-1
Fix for knife disappearing and more changes


    Fix for knife disappearing
    Possibility to change commands names
    Skin refresh now also works for knife
    Possibility to set a random knife for players who have not set any via GiveRandomKnife in config
    And probably some smaller changes
2023-11-16 11:37:58 +01:00
Dawid Bepierszcz
c82a0c0d26 Update Config.cs
Changed refresh command to wp by default
2023-11-16 11:30:16 +01:00
Dawid Bepierszcz
9ad7f9fc83 Update Config.cs 2023-11-16 11:23:59 +01:00
Dawid Bepierszcz
9e2dac52e5 Update WeaponPaints.cs 2023-11-16 11:23:26 +01:00
Nereziel
118931f44d Merge pull request #34 from daffyyyy/enhancement/website-knife-choosing
Choosing knife on website
2023-11-16 00:39:19 +01:00
Nereziel
843ae34e31 Merge pull request #33 from daffyyyy/enhancement/config-expansion
Added an additional section to the config
2023-11-16 00:38:33 +01:00
Dawid Bepierszcz
46a720ff83 default knife image 2023-11-16 00:10:26 +01:00
Dawid Bepierszcz
63d059b607 Update utils.php 2023-11-16 00:07:27 +01:00
Dawid Bepierszcz
005ef18338 Update index.php 2023-11-16 00:06:16 +01:00
Dawid Bepierszcz
c0c1fe2566 Update WeaponPaints.cs 2023-11-16 00:05:34 +01:00
Dawid Bepierszcz
c4ad17a7ed MinimumApiVersion 2023-11-15 22:46:42 +01:00
Dawid Bepierszcz
99fde6ec52 Update Config.cs 2023-11-15 22:35:57 +01:00
Dawid Bepierszcz
ca2c3eb212 Update WeaponPaints.cs 2023-11-15 22:35:19 +01:00
Nereziel
b5a4577af1 Update README.md 2023-11-15 22:15:38 +01:00
Nereziel
ba93e4a3aa Update build.yml 2023-11-15 21:22:24 +01:00
Nereziel
7d435da9d1 Update build.yml 2023-11-15 21:19:48 +01:00
Nereziel
50008cf74a Merge pull request #32 from daffyyyy/enhancement/website-skins
Website now use json file to load skins
2023-11-15 20:52:20 +01:00
Dawid Bepierszcz
4b9b169483 Update database.sql, remove weapon_paints 2023-11-15 20:47:16 +01:00
Dawid Bepierszcz
25d251deff Skins in json format 2023-11-15 20:45:55 +01:00
Dawid Bepierszcz
d8f7798f9f New utils class 2023-11-15 20:44:46 +01:00
Dawid Bepierszcz
fb023aff5b Update index.php to base on json 2023-11-15 20:44:18 +01:00
Dawid Bepierszcz
6c91e20a4d Delete website/skins.json
Not needed
2023-11-15 20:43:28 +01:00
Nereziel
93dae346d0 Merge pull request #31 from daffyyyy/patch-giving_knife
Improve knife giving
2023-11-15 18:20:48 +01:00
Dawid Bepierszcz
8e0b1cc99a Update WeaponPaints.cs
respecting the team
2023-11-15 18:18:31 +01:00
Dawid Bepierszcz
c99acbcdb3 Update WeaponPaints.cs
Improved knife giving
And small changes to the code
2023-11-15 18:03:37 +01:00
Nereziel
8beaee94da Merge pull request #28 from daffyyyy/improvement-website-2
Weapon paint  images locally
2023-11-15 16:43:59 +01:00
Nereziel
62238b4446 Merge pull request #27 from daffyyyy/fix-visibility_skins
Weird fix of skin visibility, but at the moment it works xD
2023-11-15 15:03:29 +01:00
Nereziel
03f194ac4e Added discord server 2023-11-15 13:14:12 +01:00
Nereziel
d88ce552f6 Merge pull request #30 from daffyyyy/improvement-config_messages
Add messages to config
2023-11-15 13:08:53 +01:00
Dawid Bepierszcz
9f982d1e63 Update Config.cs 2023-11-15 13:03:16 +01:00
Dawid Bepierszcz
f01d4b0bf0 Update WeaponPaints.cs 2023-11-15 12:52:07 +01:00
Dawid Bepierszcz
3a3f186673 Update Config.cs 2023-11-15 12:51:01 +01:00
daffyyyy
e775b05f9f Upload paint images
Added paint images
2023-11-14 23:13:55 +01:00
Dawid Bepierszcz
b09b97bbd5 Update database.sql
Paints image locally
2023-11-14 23:06:10 +01:00
Dawid Bepierszcz
e441ccc601 Update WeaponPaints.cs
Weird fix of skin visibility, but at the moment it works xD
2023-11-14 22:49:59 +01:00
Nereziel
dc7fb183f9 update 2023-11-14 22:17:43 +01:00
Nereziel
5a5b120674 change knife give handling 2023-11-14 19:54:22 +01:00
Nereziel
5a24f3b9fa bots had no knife 2023-11-14 19:08:17 +01:00
Nereziel
1527eea686 Update README.md 2023-11-14 16:56:18 +01:00
Nereziel
953788c327 Update README.md 2023-11-14 16:52:00 +01:00
Nereziel
837b000126 Merge pull request #24 from daffyyyy/hotfix-1
Update WeaponPaints.cs
2023-11-14 16:42:41 +01:00
Nereziel
87a78b01b4 Merge branch 'main' into hotfix-1 2023-11-14 16:41:06 +01:00
Nereziel
f7c914a267 missing exception 2023-11-14 16:37:47 +01:00
Nereziel
f000c13e51 kinder surprise 2023-11-14 16:24:30 +01:00
Nereziel
dce4532b9f Merge pull request #23 from daffyyyy/patch-display_mysql_error
Update WeaponPaints.cs
2023-11-14 16:14:43 +01:00
Dawid Bepierszcz
2c0fad230d Update WeaponPaints.cs
Temporary fix for player loading
2023-11-14 15:45:50 +01:00
Dawid Bepierszcz
41575403cc Update WeaponPaints.cs
Show mysql error for better knowledge (bad credentials and more)
2023-11-14 15:23:19 +01:00
Nereziel
c68069be93 Merge pull request #22 from daffyyyy/improvement-website-1
Always show player skin as selected option
2023-11-14 15:22:44 +01:00
Dawid Bepierszcz
741d602beb Update index.php
Always show player skin as selected option
2023-11-14 15:15:01 +01:00
Nereziel
2fa3483bce Merge pull request #21 from daffyyyy/improvement-csharp-version
Update WeaponPaints.csproj
2023-11-14 15:12:09 +01:00
Dawid Bepierszcz
486c4d1afe Update WeaponPaints.csproj
Always build on the latest version of counterstrikesharp
2023-11-14 15:09:08 +01:00
1705 changed files with 1158 additions and 115755 deletions

View File

@@ -28,19 +28,38 @@ jobs:
run: dotnet restore
- name: Build
run: dotnet build ${{ env.PROJECT_PATH }} -c WeaponPaints -o ${{ env.OUTPUT_PATH }}
publish:
if: github.event_name == 'push'
permissions: write-all
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build ${{ env.PROJECT_PATH }} -c WeaponPaints -o ${{ env.OUTPUT_PATH }}
- name: Clean files
run: |
rm -f \
${{ env.OUTPUT_PATH }}/CounterStrikeSharp.API.dll \
${{ env.OUTPUT_PATH }}/McMaster.NETCore.Plugins.dll \
${{ env.OUTPUT_PATH }}/Microsoft.DotNet.PlatformAbstractions.dll \
${{ env.OUTPUT_PATH }}/Microsoft.Extensions.DependencyModel.dll
${{ env.OUTPUT_PATH }}/Microsoft.Extensions.DependencyModel.dll \
- name: Zip
uses: thedoctor0/zip-release@0.7.5
with:
type: 'zip'
filename: '${{ env.PROJECT_NAME }}.zip'
path: ${{ env.OUTPUT_PATH }}
- name: Clean files Website
run: |
rm -rf website/img/
- name: Zip
uses: thedoctor0/zip-release@0.7.5
with:

View File

@@ -1,32 +1,97 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using System.Text.Json.Serialization;
namespace WeaponPaints
{
public class Messages
{
[JsonPropertyName("WebsiteMessageCommand")]
public string WebsiteMessageCommand { get; set; } = "Visit {WEBSITE} where you can change skins.";
[JsonPropertyName("SynchronizeMessageCommand")]
public string SynchronizeMessageCommand { get; set; } = "Type !wp to synchronize chosen skins.";
[JsonPropertyName("KnifeMessageCommand")]
public string KnifeMessageCommand { get; set; } = "Type !knife to open knife menu.";
[JsonPropertyName("CooldownRefreshCommand")]
public string CooldownRefreshCommand { get; set; } = "You can't refresh weapon paints right now.";
[JsonPropertyName("SuccessRefreshCommand")]
public string SuccessRefreshCommand { get; set; } = "Refreshing weapon paints.";
[JsonPropertyName("ChosenKnifeMenu")]
public string ChosenKnifeMenu { get; set; } = "You have chosen {KNIFE} as your knife.";
[JsonPropertyName("ChosenKnifeMenuKill")]
public string ChosenKnifeMenuKill { get; set; } = "To correctly apply skin for knife, you need to type !kill.";
[JsonPropertyName("KnifeMenuTitle")]
public string KnifeMenuTitle { get; set; } = "Knife Menu.";
}
public class Additional
{
[JsonPropertyName("SkinVisibilityFix")]
public bool SkinVisibilityFix { get; set; } = true;
[JsonPropertyName("KnifeEnabled")]
public bool KnifeEnabled { get; set; } = true;
[JsonPropertyName("SkinEnabled")]
public bool SkinEnabled { get; set; } = true;
[JsonPropertyName("CommandWpEnabled")]
public bool CommandWpEnabled { get; set; } = true;
[JsonPropertyName("CommandKillEnabled")]
public bool CommandKillEnabled { get; set; } = true;
[JsonPropertyName("CommandKnife")]
public string CommandKnife { get; set; } = "knife";
[JsonPropertyName("CommandSkin")]
public string CommandSkin { get; set; } = "ws";
[JsonPropertyName("CommandRefresh")]
public string CommandRefresh { get; set; } = "wp";
[JsonPropertyName("CommandKill")]
public string CommandKill { get; set; } = "kill";
[JsonPropertyName("GiveRandomKnife")]
public bool GiveRandomKnife { get; set; } = false;
}
public class WeaponPaintsConfig : BasePluginConfig
{
public override int Version { get; set; } = 1;
public override int Version { get; set; } = 4;
[JsonPropertyName("DatabaseHost")]
public string DatabaseHost { get; set; } = "localhost";
public string DatabaseHost { get; set; } = "";
[JsonPropertyName("DatabasePort")]
public int DatabasePort { get; set; } = 3306;
[JsonPropertyName("DatabaseUser")]
public string DatabaseUser { get; set; } = "dbuser";
public string DatabaseUser { get; set; } = "";
[JsonPropertyName("DatabasePassword")]
public string DatabasePassword { get; set; } = "dbuserpw";
public string DatabasePassword { get; set; } = "";
[JsonPropertyName("DatabaseName")]
public string DatabaseName { get; set; } = "dbname";
public string DatabaseName { get; set; } = "";
[JsonPropertyName("GlobalShare")]
public bool GlobalShare { get; set; } = false;
[JsonPropertyName("CmdRefreshCooldownSeconds")]
public int CmdRefreshCooldownSeconds { get; set; } = 60;
[JsonPropertyName("WebSite")]
public string WebSite { get; set; } = "http://wp.example.com";
[JsonPropertyName("Prefix")]
public string Prefix { get; set; } = "[WeaponPaints]";
[JsonPropertyName("Website")]
public string Website { get; set; } = "example.com/skins";
[JsonPropertyName("Messages")]
public Messages Messages { get; set; } = new Messages();
[JsonPropertyName("Additional")]
public Additional Additional { get; set; } = new Additional();
}
}

100
README.md
View File

@@ -1,30 +1,80 @@
# cs2-WeaponPaints
# CS2 Weapon Paints
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/E1E2G0P2O) or [Donate on Steam](https://steamcommunity.com/tradeoffer/new/?partner=41515647&token=gW2W-nXE)
## Description
Unfinished, unoptimized and not fully functional ugly demo weapon paints plugin for **[CSSharp](https://docs.cssharp.dev/)**.
## Created [Discord server](https://discord.gg/EEg6qtNScq) where you can discus about plugin.
### Consider to donate instead of buying from unknown sources.
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/E1E2G0P2O) or [![Donate on Steam](https://github.com/Nereziel/cs2-WeaponPaints/assets/32937653/a0d53822-4ca7-4caf-83b4-e1a9b5f8c94e)](https://steamcommunity.com/tradeoffer/new/?partner=41515647&token=gW2W-nXE)
## Features
- Changes only paint, seed and wear on weapons and knives;
- MySQL based or global website at [weaponpaints.fun](https://weaponpaints.fun/), so you dont need MySQL/Website;
- Data sync on player connect;
- Added command **`!wp`** to refresh skins; ***(with cooldown in second can be configured)***
- Added command **`!ws`** to show website;
- Added command **`!knife`** to show menu with knives;
- Knife change is now limited to have these cvars empty **`mp_t_default_melee ""`** and **`mp_ct_default_melee ""`**;
## CS2 Server
- Compile and copy plugin to plugins, [more info here](https://docs.cssharp.dev/guides/hello-world-plugin/);
- Setup **`addons/counterstrikesharp/configs/plugins/WeaponPaints/WeaponPaints.json`** set **`GlobalShare`** to **`true`** for global, or include database credentials;
- in **`addons/counterstrikesharp/configs/core.json`** set **FollowCS2ServerGuidelines** to **`false`**;
## Plugin Configuration
<details>
<summary>Spoiler warning</summary>
<code><pre>{
"Version": 4, // Don't touch
"DatabaseHost": "", // MySQL host (required if GlobalShare = false)
"DatabasePort": 3306, // MySQL port (required if GlobalShare = false)
"DatabaseUser": "", // MySQL username (required if GlobalShare = false)
"DatabasePassword": "", // MySQL user password (required if GlobalShare = false)
"DatabaseName": "", // MySQL database name (required if GlobalShare = false)
"GlobalShare": false, // Enable or disable GlobalShare, plugin can work without mysql credentials but with shared website at weaponpaints.fun
"CmdRefreshCooldownSeconds": 60, // Cooldown time in refreshing skins (!wp command)
"Prefix": "[WeaponPaints]", // Prefix every chat message
"Website": "example.com/skins", // Website used in WebsiteMessageCommand (!ws command)
"Messages": {
"WebsiteMessageCommand": "Visit {WEBSITE} where you can change skins.", // Information about website where player can change skins (!ws command) Set to empty to disable
"SynchronizeMessageCommand": "Type !wp to synchronize chosen skins.", // Information about skins refreshing (!ws command) Set to empty to disable
"KnifeMessageCommand": "Type !knife to open knife menu.", // Information about knife menu (!ws command) Set to empty to disable
"CooldownRefreshCommand": "You can\u0027t refresh weapon paints right now.", // Cooldown information (!wp command) Set to empty to disable
"SuccessRefreshCommand": "Refreshing weapon paints.", // Information about refreshing skins (!wp command) Set to empty to disable
"ChosenKnifeMenu": "You have chosen {KNIFE} as your knife.", // Information about choosen knife (!knife command) Set to empty to disable
"ChosenKnifeMenuKill": "To correctly apply skin for knife, you need to type !kill.", // Information about suicide after knife selection (!knife command) Set to empty to disable
"KnifeMenuTitle": "Knife Menu." // Menu title (!knife menu)
},
"Additional": {
"SkinVisibilityFix": true, // Enable or disable fix for skin visibility
"KnifeEnabled": true, // Enable or disable knife feature
"SkinEnabled": true, // Enable or disable skin feature
"CommandWpEnabled": true, // Enable or disable refreshing command
"CommandKillEnabled": true, // Enable or disable kill command
"CommandKnife": "knife", // Name of knife menu command, u can change to for e.g, knives
"CommandSkin": "ws", // Name of skin information command, u can change to for e.g, skins
"CommandRefresh": "wp", // Name of skin refreshing command, u can change to for e.g, refreshskins
"CommandKill": "kill", // Name of kill command, u can change to for e.g, suicide
"GiveRandomKnife": false // Give random knife to players if they didn't choose
},
"ConfigVersion": 4 // Don't touch
}</pre></code>
</details>
## Web install
Disregard if the config is **`GlobalShare = true`**;
- Requires PHP >= 7.3; ***(Tested on php ver **`8.2.3`** and nginx webserver)***
- Copy website to web server; ***(Folder `img` not needed)***
- Get [Steam API Key](https://steamcommunity.com/dev/apikey);
- Fill in database credentials and api key in `class/config.php`;
- Visit website and login via steam;
## Known issues
- Issue on Windows servers, no knives are given.
### Use this plugin at your own risk! Using this may lead to GSLT ban or something else Valve come with. [Valve Server guidelines](https://blog.counter-strike.net/index.php/server_guidelines/)
### Description
Unfinished, unoptimized and not fully functional ugly demo weapon paints plugin for [CSSharp](https://docs.cssharp.dev/).
### Features
- changes only paint, seed, wear on weapons
- mysql based
- data sync on player connect or playe
- Added command `!wp` to refresh skins (with cooldown in second can be configured)
- Added command `!ws` to show website
### CS2 server:
- compile and copy plugin to plugins. Info here [https://docs.cssharp.dev/guides/hello-world-plugin/](https://docs.cssharp.dev/guides/hello-world-plugin/)
- setup `addons/counterstrikesharp/configs/plugins/WeaponPaints/WeaponPaints.json` with database credentials
- in `addons/counterstrikesharp/configs/core.json` set **FollowCS2ServerGuidelines** to **false**
### Web install:
- copy website to web server
- import `database.sql` to mysql
- get steam api key [https://steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey)
- fill in database credentials and api key in `class/config.php`
- visit website and login via steam
### Preview
## Preview
![preview](https://github.com/Nereziel/cs2-WeaponPaints/blob/main/website/preview.png?raw=true)

View File

@@ -1,51 +1,54 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Nexd.MySQL;
using static CounterStrikeSharp.API.Core.Listeners;
using MySqlConnector;
using Dapper;
using System.Runtime.ExceptionServices;
using System.Reflection;
using CounterStrikeSharp.API.Modules.Cvars;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace WeaponPaints;
[MinimumApiVersion(55)]
public class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig>
{
public override string ModuleName => "WeaponPaints";
public override string ModuleDescription => "Connector for web-based player chosen wepaon paints.";
public override string ModuleDescription => "Connector for web-based player chosen wepaon paints, and standalone for knife.";
public override string ModuleAuthor => "Nereziel";
public override string ModuleVersion => "0.7";
public override string ModuleVersion => "1.0";
public WeaponPaintsConfig Config { get; set; } = new();
MySqlDb? MySql = null;
public DateTime[] commandCooldown = new DateTime[Server.MaxPlayers];
private Dictionary<ulong, Dictionary<nint, int>> gPlayerWeaponPaints = new Dictionary<ulong, Dictionary<nint, int>>();
private Dictionary<ulong, Dictionary<nint, int>> gPlayerWeaponSeed = new Dictionary<ulong, Dictionary<nint, int>>();
private Dictionary<ulong, Dictionary<nint, float>> gPlayerWeaponWear = new Dictionary<ulong, Dictionary<nint, float>>();
private static Dictionary<string, string> knifeTypes = new Dictionary<string, string>()
private string DatabaseConnectionString = string.Empty;
private Uri GlobalShareApi = new Uri("https://weaponpaints.fun/api.php");
public bool IsMatchZy = false;
public int GlobalShareServerId = 0;
private DateTime[] commandCooldown = new DateTime[Server.MaxPlayers];
private Dictionary<ulong, Dictionary<nint, int>> gPlayerWeaponPaints = new();
private Dictionary<ulong, Dictionary<nint, int>> gPlayerWeaponSeed = new();
private Dictionary<ulong, Dictionary<nint, float>> gPlayerWeaponWear = new();
private Dictionary<int, string> g_playersKnife = new();
private static readonly Dictionary<string, string> knifeTypes = new()
{
{ "m9", "weapon_knife_m9_bayonet" },
{ "karambit", "weapon_knife_karambit" },
{ "bayonet", "weapon_bayonet" },
{ "bowie", "weapon_knife_survival_bowie" },
{ "butterfly", "weapon_knife_butterfly" },
{ "falchion", "weapon_knife_falchion" },
{ "flip", "weapon_knife_flip" },
{ "gut", "weapon_knife_gut" },
{ "tactical", "weapon_knife_tactical" },
{ "shadow", "weapon_knife_push" },
{ "navaja", "weapon_knife_gypsy_jackknife" },
{ "stiletto", "weapon_knife_stiletto" },
{ "talon", "weapon_knife_widowmaker" },
{ "ursus", "weapon_knife_ursus" },
{ "css", "weapon_knife_css" },
{ "paracord", "weapon_knife_cord" },
{ "survival", "weapon_knife_canis" },
{ "nomad", "weapon_knife_outdoor" },
{ "skeleton", "weapon_knife_skeleton" },
{ "default", "weapon_knife" }
{ "m9", "weapon_knife_m9_bayonet" }, { "karambit", "weapon_knife_karambit" },
{ "bayonet", "weapon_bayonet" }, { "bowie", "weapon_knife_survival_bowie" },
{ "butterfly", "weapon_knife_butterfly" }, { "falchion", "weapon_knife_falchion" },
{ "flip", "weapon_knife_flip" }, { "gut", "weapon_knife_gut" },
{ "tactical", "weapon_knife_tactical" }, { "shadow", "weapon_knife_push" },
{ "navaja", "weapon_knife_gypsy_jackknife" }, { "stiletto", "weapon_knife_stiletto" },
{ "talon", "weapon_knife_widowmaker" }, { "ursus", "weapon_knife_ursus" },
{ "css", "weapon_knife_css" }, { "paracord", "weapon_knife_cord" },
{ "survival", "weapon_knife_canis" }, { "nomad", "weapon_knife_outdoor" },
{ "skeleton", "weapon_knife_skeleton" }, { "default", "weapon_knife" }
};
private static List<string> weaponList = new List<string>()
private static readonly List<string> weaponList = new()
{
"weapon_deagle", "weapon_elite", "weapon_fiveseven", "weapon_glock",
"weapon_ak47", "weapon_aug", "weapon_awp", "weapon_famas",
@@ -57,42 +60,298 @@ public class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig>
"weapon_sg556", "weapon_ssg08", "weapon_m4a1_silencer", "weapon_usp_silencer",
"weapon_cz75a", "weapon_revolver", "weapon_bayonet", "weapon_knife"
};
public override void Load(bool hotReload)
{
MySql = new MySqlDb(Config.DatabaseHost!, Config.DatabaseUser!, Config.DatabasePassword!, Config.DatabaseName!, Config.DatabasePort);
SetGlobalExceptionHandler();
if (!Config.GlobalShare)
{
BuildDatabaseConnectionString();
TestDatabaseConnection();
}
RegisterListener<Listeners.OnEntitySpawned>(OnEntitySpawned);
RegisterListener<Listeners.OnClientAuthorized>(OnClientAuthorized);
RegisterListener<Listeners.OnClientPutInServer>(OnClientPutInServer);
RegisterListener<Listeners.OnClientDisconnect>(OnClientDisconnect);
RegisterListener<Listeners.OnMapStart>(OnMapStart);
RegisterEventHandler<EventPlayerSpawn>(OnPlayerSpawn);
RegisterEventHandler<EventRoundStart>(OnRoundStart, HookMode.Pre);
RegisterEventHandler<EventItemPickup>(OnItemPickup, HookMode.Pre);
if (hotReload)
{
OnMapStart(string.Empty);
Task.Run(async () =>
{
for (int i = 1; i <= Server.MaxPlayers; i++)
{
if (Config.Additional.KnifeEnabled)
await GetKnifeFromDatabase(i);
if (Config.Additional.SkinEnabled)
await GetWeaponPaintsFromDatabase(i);
}
});
}
if (Config.Additional.KnifeEnabled)
SetupMenus();
RegisterCommands();
}
public void OnConfigParsed(WeaponPaintsConfig config)
{
if (!config.GlobalShare)
{
if (config.DatabaseHost.Length < 1 || config.DatabaseName.Length < 1 || config.DatabaseUser.Length < 1)
{
throw new Exception("You need to setup Database credentials in config!");
}
}
Config = config;
}
private void OnClientAuthorized(int playerSlot, SteamID steamId)
private void BuildDatabaseConnectionString()
{
int slot = playerSlot;
Server.NextFrame(() =>
var builder = new MySqlConnectionStringBuilder
{
Task.Run(() => GetWeaponPaintsFromDatabase(slot));
Server = Config.DatabaseHost,
UserID = Config.DatabaseUser,
Password = Config.DatabasePassword,
Database = Config.DatabaseName,
Port = (uint)Config.DatabasePort,
};
DatabaseConnectionString = builder.ConnectionString;
}
private void TestDatabaseConnection()
{
try
{
using var connection = new MySqlConnection(DatabaseConnectionString);
connection.Open();
if (connection.State != System.Data.ConnectionState.Open)
{
throw new Exception("Unable connect to database!");
}
}
catch (Exception ex)
{
throw new Exception("Unknown mysql exception! " + ex.Message);
}
CheckDatabaseTables();
}
async private void CheckDatabaseTables()
{
try
{
using var connection = new MySqlConnection(DatabaseConnectionString);
await connection.OpenAsync();
using var transaction = await connection.BeginTransactionAsync();
try
{
string createTable1 = "CREATE TABLE IF NOT EXISTS `wp_player_skins` (`steamid` varchar(64) NOT NULL, `weapon_defindex` int(6) NOT NULL, `weapon_paint_id` int(6) NOT NULL, `weapon_wear` float NOT NULL DEFAULT 0.0001, `weapon_seed` int(16) NOT NULL DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci";
string createTable2 = "CREATE TABLE IF NOT EXISTS `wp_player_knife` (`steamid` varchar(64) NOT NULL, `knife` varchar(64) NOT NULL, UNIQUE (`steamid`)) ENGINE = InnoDB";
await connection.ExecuteAsync(createTable1, transaction: transaction);
await connection.ExecuteAsync(createTable2, transaction: transaction);
await transaction.CommitAsync();
}
catch (Exception)
{
await transaction.RollbackAsync();
throw new Exception("Unable to create tables!");
}
}
catch (Exception ex)
{
throw new Exception("Unknown mysql exception! " + ex.Message);
}
}
// TODO: fix for map which change mp_t_default_melee
/*private HookResult OnRoundPreStart(EventRoundPrestart @event, GameEventInfo info)
{
NativeAPI.IssueServerCommand("mp_t_default_melee \"\"");
NativeAPI.IssueServerCommand("mp_ct_default_melee \"\"");
return HookResult.Continue;
}
*/
public override void Unload(bool hotReload)
{
RemoveGlobalExceptionHandler();
base.Unload(hotReload);
}
private void GlobalExceptionHandler(object? sender, FirstChanceExceptionEventArgs @event)
{
Log(@event.Exception.ToString());
}
private void SetGlobalExceptionHandler()
{
AppDomain.CurrentDomain.FirstChanceException += this.GlobalExceptionHandler;
}
private void RemoveGlobalExceptionHandler()
{
AppDomain.CurrentDomain.FirstChanceException -= this.GlobalExceptionHandler;
}
private void RegisterCommands()
{
AddCommand($"css_{Config.Additional.CommandSkin}", "Skins info", (player, info) => { if (player == null) return; OnCommandWS(player, info); });
AddCommand($"css_{Config.Additional.CommandRefresh}", "Skins refresh", (player, info) => { if (player == null) return; OnCommandRefresh(player, info); });
if (Config.Additional.CommandKillEnabled)
{
AddCommand($"css_{Config.Additional.CommandKill}", "kill yourself", (player, info) =>
{
if (player == null || !player.IsValid || !player.PlayerPawn.IsValid)
return;
player.PlayerPawn.Value.CommitSuicide(true, false);
});
}
}
private void IncompatibilityCheck()
{
// MatchZy
if (Directory.Exists(Path.GetDirectoryName(ModuleDirectory) + "/MatchZy"))
{
Console.WriteLine("[WeaponPaints] Incompatibility found: MatchZy");
IsMatchZy = true;
}
}
private void OnMapStart(string mapName)
{
if (!Config.Additional.KnifeEnabled) return;
// TODO
// needed for now
AddTimer(2.0f, () =>
{
NativeAPI.IssueServerCommand("mp_t_default_melee \"\"");
NativeAPI.IssueServerCommand("mp_ct_default_melee \"\"");
IncompatibilityCheck();
});
if (Config.GlobalShare)
GlobalShareConnect();
}
private void GlobalShareConnect()
{
if (!Config.GlobalShare) return;
var values = new Dictionary<string, string>
{
{ "server_address", $"{ConVar.Find("ip")!.StringValue}:{ConVar.Find("hostport")!.GetPrimitiveValue<int>().ToString()}" },
{ "server_hostname", ConVar.Find("hostname")!.StringValue }
};
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = GlobalShareApi;
var formContent = new FormUrlEncodedContent(values);
Task<HttpResponseMessage> responseTask = httpClient.PostAsync("", formContent);
responseTask.Wait();
HttpResponseMessage response = responseTask.Result;
if (response.IsSuccessStatusCode)
{
Task<string> responseBodyTask = response.Content.ReadAsStringAsync();
responseBodyTask.Wait();
string responseBody = responseBodyTask.Result;
GlobalShareServerId = Int32.Parse(responseBody);
}
else
{
throw new Exception("Unable to retrieve serverid from GlobalShare!");
}
}
Console.WriteLine("[WeaponPaints] GlobalShare ONLINE");
}
private void OnClientPutInServer(int playerSlot)
{
int playerIndex = playerSlot + 1;
Task.Run(async () =>
{
if (Config.Additional.KnifeEnabled)
await GetKnifeFromDatabase(playerIndex);
if (Config.Additional.SkinEnabled)
await GetWeaponPaintsFromDatabase(playerIndex);
});
}
private void OnClientDisconnect(int playerSlot)
{
// Clean up after player
CCSPlayerController player = Utilities.GetPlayerFromSlot(playerSlot);
if (!player.IsValid || player.IsBot) return;
// TODO: Clean up after player
if (Config.Additional.KnifeEnabled)
g_playersKnife.Remove((int)player.EntityIndex!.Value.Value);
if (Config.Additional.SkinEnabled)
gPlayerWeaponPaints.Remove(new SteamID(player.SteamID).SteamId64);
}
private HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info)
{
var player = @event.Userid;
if (!player.IsValid || !player.PlayerPawn.IsValid)
{
return HookResult.Continue;
}
if (Config.Additional.KnifeEnabled)
{
GiveKnifeToPlayer(player);
}
return HookResult.Continue;
}
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
{
if (!IsMatchZy) return HookResult.Continue;
NativeAPI.IssueServerCommand("mp_t_default_melee \"\"");
NativeAPI.IssueServerCommand("mp_ct_default_melee \"\"");
return HookResult.Continue;
}
private HookResult OnItemPickup(EventItemPickup @event, GameEventInfo info)
{
if (@event.Defindex == 42 || @event.Defindex == 59)
{
CCSPlayerController player = @event.Userid;
if (player.IsValid && !player.IsBot && @event.Item == "knife")
{
if (g_playersKnife.ContainsKey((int)player.EntityIndex!.Value.Value)
&&
g_playersKnife[(int)player.EntityIndex!.Value.Value] != "weapon_knife")
{
RefreshPlayerKnife(player, true);
}
}
}
return HookResult.Continue;
}
private void OnEntitySpawned(CEntityInstance entity)
{
if (!Config.Additional.SkinEnabled) return;
var designerName = entity.DesignerName;
if (!weaponList.Contains(designerName)) return;
bool isKnife = false;
var weapon = new CBasePlayerWeapon(entity.Handle);
if (designerName.Contains("knife") || designerName.Contains("bayonet"))
{
isKnife = true;
}
Server.NextFrame(() =>
{
try
{
if (!weapon.IsValid) return;
if (weapon.OwnerEntity.Value == null) return;
@@ -103,11 +362,19 @@ public class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig>
var playerIndex = (int)pawn.Controller.Value.EntityIndex!.Value.Value;
var player = Utilities.GetPlayerFromIndex(playerIndex);
if (player == null || !player.IsValid || player.IsBot) return;
// TODO: Remove knife crashes here, needs another solution
/*if (isKnife && g_playersKnife[(int)player.EntityIndex!.Value.Value] != "weapon_knife" && (weapon.AttributeManager.Item.ItemDefinitionIndex == 42 || weapon.AttributeManager.Item.ItemDefinitionIndex == 59))
{
RemoveKnifeFromPlayer(player);
return;
}*/
var steamId = new SteamID(player.SteamID);
if (!gPlayerWeaponPaints.ContainsKey(steamId.SteamId64)) return;
if (!gPlayerWeaponPaints[steamId.SteamId64].ContainsKey(weapon.AttributeManager.Item.ItemDefinitionIndex)) return;
weapon.AttributeManager.Item.ItemIDLow = unchecked((uint)-1);
weapon.AttributeManager.Item.ItemIDHigh = unchecked((uint)-1);
//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.FallbackPaintKit = gPlayerWeaponPaints[steamId.SteamId64][weapon.AttributeManager.Item.ItemDefinitionIndex];
weapon.FallbackSeed = gPlayerWeaponSeed[steamId.SteamId64][weapon.AttributeManager.Item.ItemDefinitionIndex];
weapon.FallbackWear = gPlayerWeaponWear[steamId.SteamId64][weapon.AttributeManager.Item.ItemDefinitionIndex];
@@ -116,75 +383,396 @@ public class WeaponPaints : BasePlugin, IPluginConfig<WeaponPaintsConfig>
var skeleton = GetSkeletonInstance(weapon.CBodyComponent.SceneNode);
skeleton.ModelState.MeshGroupMask = 2;
}
}
catch (Exception) { }
});
}
[ConsoleCommand("css_ws", "weaponskins")]
public void OnCommandWS(CCSPlayerController? player, CommandInfo command)
public void GiveKnifeToPlayer(CCSPlayerController? player)
{
if (player == null) return;
player.PrintToChat($"Change weapon skins at {ChatColors.Purple}{Config.WebSite}");
player.PrintToChat($"To synchronize weapon paints type {ChatColors.Purple}!wp");
if (!Config.Additional.KnifeEnabled) return;
if (player == null || !player.IsValid) return;
if (g_playersKnife.TryGetValue((int)player.EntityIndex!.Value.Value, out var knife))
{
player.GiveNamedItem(knife);
}
[ConsoleCommand("css_wp", "refreshskins")]
public void OnCommandRefresh(CCSPlayerController? player, CommandInfo command)
else
{
if (player == null) return;
int playerSlot = (int)player.EntityIndex!.Value.Value - 1;
if (DateTime.UtcNow >= commandCooldown[playerSlot].AddSeconds(Config.CmdRefreshCooldownSeconds))
if (Config.Additional.GiveRandomKnife)
{
commandCooldown[playerSlot] = DateTime.UtcNow;
Task.Run(async () => await GetWeaponPaintsFromDatabase(playerSlot));
player.PrintToChat("Refreshed weapon paints.");
Random random = new Random();
int index = random.Next(knifeTypes.Count);
player.GiveNamedItem(knifeTypes.Values.ElementAt(index));
}
else
{
player.GiveNamedItem((CsTeam)player.TeamNum == CsTeam.Terrorist ? "weapon_knife_t" : "weapon_knife");
}
}
}
public void RemoveKnifeFromPlayer(CCSPlayerController? player)
{
if (player == null || !player.IsValid || !player.PawnIsAlive) return;
var weapons = player.PlayerPawn.Value.WeaponServices!.MyWeapons;
foreach (var weapon in weapons)
{
if (weapon.IsValid && weapon.Value.IsValid)
{
//if (weapon.Value.AttributeManager.Item.ItemDefinitionIndex == 42 || weapon.Value.AttributeManager.Item.ItemDefinitionIndex == 59)
if (weapon.Value.DesignerName.Contains("knife"))
{
weapon.Value.Remove();
return;
}
player.PrintToChat("You can't refresh weapon paints right now.");
}
public CSkeletonInstance GetSkeletonInstance(CGameSceneNode node)
}
}
public void RefreshPlayerKnife(CCSPlayerController? player, bool remove = false)
{
if (player == null || !player.IsValid || !player.PawnIsAlive) return;
if (remove == true)
RemoveKnifeFromPlayer(player);
AddTimer(0.2f, () =>
{
if (!PlayerHasKnife(player))
GiveKnifeToPlayer(player);
});
if (Config.Additional.SkinVisibilityFix)
{
AddTimer(0.2f, () => NativeAPI.IssueClientCommand((int)player.EntityIndex!.Value.Value - 1, "slot3"));
AddTimer(0.3f, () => NativeAPI.IssueClientCommand((int)player.EntityIndex!.Value.Value - 1, "slot2"));
AddTimer(0.36f, () => NativeAPI.IssueClientCommand((int)player.EntityIndex!.Value.Value - 1, "slot1"));
}
}
public bool PlayerHasKnife(CCSPlayerController? player)
{
if (!Config.Additional.KnifeEnabled) return false;
if (player == null || !player.IsValid || !player.PawnIsAlive)
{
return false;
}
var weapons = player.PlayerPawn.Value.WeaponServices!.MyWeapons;
if (weapons == null) return false;
foreach (var weapon in weapons)
{
if (weapon.IsValid && weapon.Value.IsValid)
{
if (weapon.Value.DesignerName.Contains("knife"))
{
return true;
}
}
}
return false;
}
private void SetupMenus()
{
if (!Config.Additional.KnifeEnabled) return;
var giveItemMenu = new ChatMenu(ReplaceTags(Config.Messages.KnifeMenuTitle));
var handleGive = (CCSPlayerController player, ChatMenuOption option) =>
{
string temp = "";
if (knifeTypes.TryGetValue(option.Text, out var knife))
{
g_playersKnife[(int)player.EntityIndex!.Value.Value] = knifeTypes[option.Text];
if (!string.IsNullOrEmpty(Config.Messages.ChosenKnifeMenu))
{
temp = $"{Config.Prefix} {Config.Messages.ChosenKnifeMenu}".Replace("{KNIFE}", option.Text);
player.PrintToChat(ReplaceTags(temp));
}
if (!string.IsNullOrEmpty(Config.Messages.ChosenKnifeMenuKill) && Config.Additional.CommandKillEnabled)
{
temp = $"{Config.Prefix} {Config.Messages.ChosenKnifeMenuKill}";
player.PrintToChat(ReplaceTags(temp));
}
Task.Run(() => SyncKnifeToDatabase((int)player.EntityIndex!.Value.Value, knife));
RemoveKnifeFromPlayer(player);
AddTimer(0.1f, () => GiveKnifeToPlayer(player));
}
};
foreach (var knife in knifeTypes)
{
giveItemMenu.AddMenuOption(knife.Key, handleGive);
}
AddCommand($"css_{Config.Additional.CommandKnife}", "Knife Menu", (player, info) => { if (player == null) return; ChatMenus.OpenMenu(player, giveItemMenu); });
}
// [ConsoleCommand($"css_{Config.Additional.CommandRefresh}", "refreshskins")]
public void OnCommandRefresh(CCSPlayerController? player, CommandInfo command)
{
if (!Config.Additional.CommandWpEnabled || !Config.Additional.SkinEnabled) return;
if (player == null) return;
string temp = "";
int playerIndex = (int)player.EntityIndex!.Value.Value;
if (commandCooldown != null && DateTime.UtcNow >= commandCooldown[playerIndex].AddSeconds(Config.CmdRefreshCooldownSeconds))
{
commandCooldown[playerIndex] = DateTime.UtcNow;
Task.Run(async () => await GetWeaponPaintsFromDatabase(playerIndex));
if (Config.Additional.KnifeEnabled)
{
Task.Run(async () => await GetKnifeFromDatabase(playerIndex));
RemoveKnifeFromPlayer(player);
AddTimer(0.2f, () => GiveKnifeToPlayer(player));
}
if (!string.IsNullOrEmpty(Config.Messages.SuccessRefreshCommand))
{
temp = $"{Config.Prefix} {Config.Messages.SuccessRefreshCommand}";
player.PrintToChat(ReplaceTags(temp));
}
return;
}
if (!string.IsNullOrEmpty(Config.Messages.CooldownRefreshCommand))
{
temp = $"{Config.Prefix} {Config.Messages.CooldownRefreshCommand}";
player.PrintToChat(ReplaceTags(temp));
}
}
// [ConsoleCommand($"css_{Config.Additional.CommandSkin}", "weaponskins")]
public void OnCommandWS(CCSPlayerController? player, CommandInfo command)
{
if (!Config.Additional.SkinEnabled) return;
if (player == null) return;
string temp = "";
if (!string.IsNullOrEmpty(Config.Messages.WebsiteMessageCommand))
{
temp = $"{Config.Prefix} {Config.Messages.WebsiteMessageCommand}";
player.PrintToChat(ReplaceTags(temp));
}
if (!string.IsNullOrEmpty(Config.Messages.SynchronizeMessageCommand))
{
temp = $"{Config.Prefix} {Config.Messages.SynchronizeMessageCommand}";
player.PrintToChat(ReplaceTags(temp));
}
if (!Config.Additional.KnifeEnabled) return;
if (!string.IsNullOrEmpty(Config.Messages.KnifeMessageCommand))
{
temp = $"{Config.Prefix} {Config.Messages.KnifeMessageCommand}";
player.PrintToChat(ReplaceTags(temp));
}
}
public static CSkeletonInstance GetSkeletonInstance(CGameSceneNode node)
{
Func<nint, nint> GetSkeletonInstance = VirtualFunction.Create<nint, nint>(node.Handle, 8);
return new CSkeletonInstance(GetSkeletonInstance(node.Handle));
}
private async Task GetWeaponPaintsFromDatabase(int playerIndex)
{
if (!Config.Additional.SkinEnabled) return;
try
{
CCSPlayerController player = Utilities.GetPlayerFromIndex(playerIndex);
if (player == null || !player.IsValid || player.IsBot) return;
var steamId = new SteamID(player.SteamID);
if (Config.GlobalShare)
{
var values = new Dictionary<string, string>
{
{ "server_id", GlobalShareServerId.ToString() },
{ "steamid", steamId.SteamId64.ToString() },
{ "skins", "1" }
};
UriBuilder builder = new UriBuilder(GlobalShareApi);
builder.Query = string.Join("&", values.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = GlobalShareApi;
var formContent = new FormUrlEncodedContent(values);
HttpResponseMessage response = await httpClient.GetAsync(builder.Uri);
if (response.IsSuccessStatusCode)
{
string responseBody = await response.Content.ReadAsStringAsync();
JArray jsonArray = JArray.Parse(responseBody);
if (jsonArray.Count > 0)
{
gPlayerWeaponPaints[steamId.SteamId64] = new Dictionary<nint, int>();
gPlayerWeaponWear[steamId.SteamId64] = new Dictionary<nint, float>();
gPlayerWeaponSeed[steamId.SteamId64] = new Dictionary<nint, int>();
foreach (var weapon in jsonArray)
{
int weaponDefIndex = weapon["weapon_defindex"].Value<int>();
int weaponPaintId = weapon["weapon_paint_id"].Value<int>();
float weaponWear = weapon["weapon_wear"].Value<float>();
int weaponSeed = weapon["weapon_seed"].Value<int>();
gPlayerWeaponPaints[steamId.SteamId64][weaponDefIndex] = weaponPaintId;
gPlayerWeaponWear[steamId.SteamId64][weaponDefIndex] = weaponWear;
gPlayerWeaponSeed[steamId.SteamId64][weaponDefIndex] = weaponSeed;
}
}
return;
}
else
{
return;
}
}
}
using (var connection = new MySqlConnection(DatabaseConnectionString))
{
await connection.OpenAsync();
string query = "SELECT * FROM `wp_player_skins` WHERE `steamid` = @steamid";
IEnumerable<dynamic> PlayerSkins = await connection.QueryAsync<dynamic>(query, new { steamid = steamId.SteamId64.ToString() });
if (PlayerSkins != null && PlayerSkins.AsList().Count > 0)
{
gPlayerWeaponPaints[steamId.SteamId64] = new Dictionary<nint, int>();
gPlayerWeaponWear[steamId.SteamId64] = new Dictionary<nint, float>();
gPlayerWeaponSeed[steamId.SteamId64] = new Dictionary<nint, int>();
PlayerSkins.ToList().ForEach(row =>
{
int weaponDefIndex = row.weapon_defindex ?? default(int);
int weaponPaintId = row.weapon_paint_id ?? default(int);
float weaponWear = row.weapon_wear ?? default(float);
int weaponSeed = row.weapon_seed ?? default(int);
gPlayerWeaponPaints[steamId.SteamId64][weaponDefIndex] = weaponPaintId;
gPlayerWeaponWear[steamId.SteamId64][weaponDefIndex] = weaponWear;
gPlayerWeaponSeed[steamId.SteamId64][weaponDefIndex] = weaponSeed;
});
}
else
{
return;
}
}
}
catch (Exception e)
{
Log(e.Message);
return;
}
}
private async Task GetKnifeFromDatabase(int playerIndex)
{
if (!Config.Additional.KnifeEnabled) return;
try
{
CCSPlayerController player = Utilities.GetPlayerFromIndex(playerIndex);
if (player == null || !player.IsValid || player.IsBot) return;
var steamId = new SteamID(player.SteamID);
if (Config.GlobalShare)
{
var values = new Dictionary<string, string>
{
{ "server_id", GlobalShareServerId.ToString() },
{ "steamid", steamId.SteamId64.ToString() },
{ "knife", "1" }
};
UriBuilder builder = new UriBuilder(GlobalShareApi);
builder.Query = string.Join("&", values.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = GlobalShareApi;
var formContent = new FormUrlEncodedContent(values);
HttpResponseMessage response = await httpClient.GetAsync(builder.Uri);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(result))
{
g_playersKnife[playerIndex] = result;
}
else
{
return;
}
}
else
{
return;
}
}
return;
}
using (var connection = new MySqlConnection(DatabaseConnectionString))
{
await connection.OpenAsync();
string query = "SELECT `knife` FROM `wp_player_knife` WHERE `steamid` = @steamid";
string? PlayerKnife = await connection.QueryFirstOrDefaultAsync<string>(query, new { steamid = steamId.SteamId64.ToString() });
if (PlayerKnife != null)
{
g_playersKnife[playerIndex] = PlayerKnife;
}
else
{
return;
}
}
//Log($"{player.PlayerName} has this knife -> {g_playersKnife[playerIndex]}");
}
catch (Exception e)
{
Log(e.Message);
return;
}
}
private async Task SyncKnifeToDatabase(int playerIndex, string knife)
{
if (!Config.Additional.KnifeEnabled) return;
try
{
CCSPlayerController player = Utilities.GetPlayerFromIndex(playerIndex);
if (player == null || !player.IsValid) return;
var steamId = new SteamID(player.SteamID);
MySqlQueryCondition conditions = new MySqlQueryCondition()
.Add("steamid", "=", steamId.SteamId64.ToString());
MySqlQueryResult result = await MySql!.Table("wp_player_skins").Where(conditions).SelectAsync();
result.ToList().ForEach(pair =>
{
int WeaponDefIndex = result.Get<int>(pair.Key, "weapon_defindex");
int PaintId = result.Get<int>(pair.Key, "weapon_paint_id");
float Wear = result.Get<float>(pair.Key, "weapon_wear");
int Seed = result.Get<int>(pair.Key, "weapon_seed");
if (!gPlayerWeaponPaints.ContainsKey(steamId.SteamId64))
{
gPlayerWeaponPaints[steamId.SteamId64] = new Dictionary<nint, int>();
using var connection = new MySqlConnection(DatabaseConnectionString);
await connection.OpenAsync();
string query = "INSERT INTO `wp_player_knife` (`steamid`, `knife`) VALUES(@steamid, @newKnife) ON DUPLICATE KEY UPDATE `knife` = @newKnife";
await connection.ExecuteAsync(query, new { steamid = steamId.SteamId64.ToString(), newKnife = knife });
}
if (!gPlayerWeaponWear.ContainsKey(steamId.SteamId64))
{
gPlayerWeaponWear[steamId.SteamId64] = new Dictionary<nint, float>();
}
if (!gPlayerWeaponSeed.ContainsKey(steamId.SteamId64))
{
gPlayerWeaponSeed[steamId.SteamId64] = new Dictionary<nint, int>();
}
gPlayerWeaponPaints[steamId.SteamId64][WeaponDefIndex] = PaintId;
gPlayerWeaponWear[steamId.SteamId64][WeaponDefIndex] = Wear;
gPlayerWeaponSeed[steamId.SteamId64][WeaponDefIndex] = Seed;
});
}
catch (Exception)
catch (Exception e)
{
Log(e.Message);
return;
}
}
private string ReplaceTags(string message)
{
if (message.Contains('{'))
{
string modifiedValue = message;
modifiedValue = modifiedValue.Replace("{WEBSITE}", Config.Website);
foreach (FieldInfo field in typeof(ChatColors).GetFields())
{
string pattern = $"{{{field.Name}}}";
if (message.Contains(pattern, StringComparison.OrdinalIgnoreCase))
{
modifiedValue = modifiedValue.Replace(pattern, field.GetValue(null)!.ToString(), StringComparison.OrdinalIgnoreCase);
}
}
return modifiedValue;
}
return message;
}
private static void Log(string message)
{
Console.BackgroundColor = ConsoleColor.DarkGray;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(message);
Console.ResetColor();
}
}

View File

@@ -8,8 +8,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.52" />
<PackageReference Include="Nexd.MySQL" Version="1.0.1" />
<PackageReference Include="CounterStrikeSharp.API" Version="*" />
<PackageReference Include="Dapper" Version="2.1.21" />
<PackageReference Include="MySqlConnector" Version="2.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>

96
website/class/utils.php Normal file
View File

@@ -0,0 +1,96 @@
<?php
class UtilsClass
{
public static function skinsFromJson(): array
{
$skins = [];
$json = json_decode(file_get_contents(__DIR__ . "/../data/skins.json"), true);
foreach ($json as $skin) {
$skins[(int) $skin['weapon_defindex']][(int) $skin['paint']] = [
'weapon_name' => $skin['weapon_name'],
'paint_name' => $skin['paint_name'],
'image_url' => $skin['image'],
];
}
return $skins;
}
public static function getWeaponsFromArray()
{
$weapons = [];
$temp = self::skinsFromJson();
foreach ($temp as $key => $value) {
if (key_exists($key, $weapons))
continue;
$weapons[$key] = [
'weapon_name' => $value[0]['weapon_name'],
'paint_name' => $value[0]['paint_name'],
'image_url' => $value[0]['image_url'],
];
}
return $weapons;
}
public static function getKnifeTypes()
{
$knifes = [];
$temp = 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
])
)
continue;
$knifes[$key] = [
'weapon_name' => $weapon['weapon_name'],
'paint_name' => rtrim(explode("|", $weapon['paint_name'])[0]),
'image_url' => $weapon['image_url'],
];
$knifes[0] = [
'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)
{
$selected = [];
foreach ($temp as $weapon) {
$selected[$weapon['weapon_defindex']] = $weapon['weapon_paint_id'];
}
return $selected;
}
}

1
website/data/skins.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

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