├── .gitignore
├── img
└── 1.jpg
├── .gitattributes
├── textureExample
├── materials
│ └── Example
│ │ ├── exampleTexture.png
│ │ ├── exampleTexture_Trans.png
│ │ └── exampleTexture.vmat
└── README.md
├── src
├── CS2_Poor_MapDecals.csproj
├── Models
│ └── PropModel.cs
├── Config
│ └── Config.cs
├── CS2_Poor_MapDecals.cs
├── Managers
│ ├── Prop.cs
│ ├── Events.cs
│ └── Commands.cs
└── Utilities
│ └── Utils.cs
├── lang
├── en.json
└── pl.json
├── CS2-Poor-MapDecals.sln
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | .vs
3 | obj
4 |
--------------------------------------------------------------------------------
/img/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Letaryat/CS2-Poor-MapDecals/HEAD/img/1.jpg
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/textureExample/materials/Example/exampleTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Letaryat/CS2-Poor-MapDecals/HEAD/textureExample/materials/Example/exampleTexture.png
--------------------------------------------------------------------------------
/textureExample/materials/Example/exampleTexture_Trans.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Letaryat/CS2-Poor-MapDecals/HEAD/textureExample/materials/Example/exampleTexture_Trans.png
--------------------------------------------------------------------------------
/src/CS2_Poor_MapDecals.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | CS2_Poor_MapDecals
6 | enable
7 | enable
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/textureExample/README.md:
--------------------------------------------------------------------------------
1 | ## Some basic information about texture
2 |
3 | Here you can find an example texture that you can just change name of it, change texture and it should work with the plugin after you compile it and upload to steam workshop.
4 | Remember to have:
5 | - Resolution of that texture to be 2 to the power of n (for example 64x64, 128x128, 512x512, 1024x1024, 2048x2048)
6 | - To be rotated the same as exampleTexture.png
7 |
8 | .png and .vmat need to be placed in materials/
9 |
10 | (I wanted to figure it out so the png would be straight but it bugged so I gave up.)
--------------------------------------------------------------------------------
/src/Models/PropModel.cs:
--------------------------------------------------------------------------------
1 | namespace CS2_Poor_MapDecals.Models
2 | {
3 | public class PropModel
4 | {
5 | public int Id { get; set; }
6 | public int ModelIndex { get; set; }
7 | public float posX { get; set; }
8 | public float posY { get; set; }
9 | public float posZ { get; set; }
10 | public float angleX { get; set; }
11 | public float angleY { get; set; }
12 | public float angleZ { get; set; }
13 | public float width { get; set; }
14 | public float height { get; set; }
15 | public bool forceOnVip { get; set; }
16 | }
17 | }
--------------------------------------------------------------------------------
/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Prefix": "{lime}[Poor-MapDecals]{default}",
3 | "NoAccess": "{red}You have no access to this command!",
4 | "NoArg": "{red}Arguments are invalid!",
5 | "PlaceAd": "{yellow}Ad placed successfully!",
6 | "PlacingMode": "Placing mode set to: {orange}{0}",
7 | "PingMode": "Ping mode is now set to: {orange}{0}",
8 | "SetModel": "Successfully set to model with ID: {green}{0}{default} Width: {green}{1}{default}, Height: {green}{2}{default}, Force on vip: {green}{3}",
9 | "RemoveEntity": "Successfully removed advertisement!",
10 | "Teleport": "You have been teleported!",
11 | "Console": "List has been printed in your console!"
12 | }
13 |
--------------------------------------------------------------------------------
/lang/pl.json:
--------------------------------------------------------------------------------
1 | {
2 | "Prefix": "{lime}[Poor-MapDecals]{default}",
3 | "NoAccess": "{red}Nie posiadasz dostepu do tej komendy!",
4 | "NoArg": "{red}Arguments are invalid!",
5 | "PlaceAd": "{yellow}Reklama zostala postawiona pomyslnie!",
6 | "PlacingMode": "Tryb tworzenia zostal ustawiony na: {orange}{0}",
7 | "PingMode": "Tryb pingu jest teraz: {orange}{0}",
8 | "SetModel": "Uzywasz modelu o ID: {green}{0}{default} Szerokosci: {green}{1}{default}, Wysokosci: {green}{2}{default}, Wymuszenie dla vip: {green}{3}",
9 | "RemoveEntity": "Pomyslnie usunieto reklame!",
10 | "Teleport": "Zostales teleportowany!",
11 | "Console": "Lista zostala wydrukowana w konsoli!"
12 | }
13 |
--------------------------------------------------------------------------------
/src/Config/Config.cs:
--------------------------------------------------------------------------------
1 | using CounterStrikeSharp.API.Core;
2 | using System.Text.Json.Serialization;
3 |
4 |
5 | namespace CS2_Poor_MapDecals.Config
6 | {
7 | public class PluginConfig : BasePluginConfig
8 | {
9 | [JsonPropertyName("Admin Flag")]
10 | public string AdminFlag { get; set; } = "@css/root";
11 |
12 | [JsonPropertyName("Vip Flag")]
13 | public string VipFlag { get; set; } = "@vip/noadv";
14 |
15 | [JsonPropertyName("Props Path")]
16 | public string[] Props { get; set; } = [];
17 |
18 | [JsonPropertyName("Enable commands")]
19 | public bool EnableCMD { get; set; } = true;
20 |
21 | [JsonPropertyName("Debug Mode")]
22 | public bool Debug { get; set; } = true;
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/CS2-Poor-MapDecals.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.5.2.0
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2_Poor_MapDecals", "src\CS2_Poor_MapDecals.csproj", "{A4AFFBB9-CCED-E799-4DDD-E3664B4F410F}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {A4AFFBB9-CCED-E799-4DDD-E3664B4F410F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {A4AFFBB9-CCED-E799-4DDD-E3664B4F410F}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {A4AFFBB9-CCED-E799-4DDD-E3664B4F410F}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {A4AFFBB9-CCED-E799-4DDD-E3664B4F410F}.Release|Any CPU.Build.0 = Release|Any CPU
17 | EndGlobalSection
18 | GlobalSection(SolutionProperties) = preSolution
19 | HideSolutionNode = FALSE
20 | EndGlobalSection
21 | GlobalSection(ExtensibilityGlobals) = postSolution
22 | SolutionGuid = {A25EB39B-D16F-49C6-B8B4-D587D34FAED5}
23 | EndGlobalSection
24 | EndGlobal
25 |
--------------------------------------------------------------------------------
/textureExample/materials/Example/exampleTexture.vmat:
--------------------------------------------------------------------------------
1 | // THIS FILE IS AUTO-GENERATED
2 |
3 | Layer0
4 | {
5 | shader "csgo_projected_decals.vfx"
6 |
7 | //---- Decal ----
8 | F_BLEND_MODE 1 // Mod2X
9 |
10 | //---- Alpha ----
11 | g_flDecalZAlphaScale "0.010"
12 |
13 | //---- Color ----
14 | TextureColor "materials/example/exampletexture.png"
15 |
16 | //---- Translucent ----
17 | TextureTranslucency "materials/example/exampletexture_trans.png"
18 |
19 | ToolAttributes
20 | {
21 | UVSet1FastTextureOp "2"
22 | UVSet2FastTextureOp "2"
23 | }
24 |
25 | UnusedVariables
26 | {
27 | "g_flAlphaCutoffSoftness" "0.1"
28 | "g_flAdditiveAmount" "1"
29 | "g_flEmissiveBrightness" "0"
30 | "g_vEmissiveTint" "[1.000000 1.000000 1.000000 0.000000]"
31 | "g_bFogEnabled" "1"
32 | "g_flGlossiness" "0.5"
33 | "TextureAmbientOcclusion" ""
34 | "TextureMetalness" ""
35 | "g_flHeightMapScale" "0.02"
36 | "g_nLODThreshold" "4"
37 | "g_nMaxSamples" "32"
38 | "g_nMinSamples" "8"
39 | "TextureHeight" ""
40 | "g_vShrinkWrapBB_01" "[0.000 0.000 0.000 1.000]"
41 | "g_vShrinkWrapBB_23" "[1.000 1.000 1.000 0.000]"
42 | }
43 |
44 |
45 | VariableState
46 | {
47 | "Alpha"
48 | {
49 | }
50 | "Color"
51 | {
52 | }
53 | "Translucent"
54 | {
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/src/CS2_Poor_MapDecals.cs:
--------------------------------------------------------------------------------
1 | using CounterStrikeSharp.API.Core;
2 | using CS2_Poor_MapDecals.Config;
3 | using CS2_Poor_MapDecals.Managers;
4 | using CS2_Poor_MapDecals.Utils;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace CS2_Poor_MapDecals;
8 | public class CS2_Poor_MapDecals : BasePlugin, IPluginConfig
9 | {
10 | public override string ModuleName => "CS2_Poor_MapDecals";
11 |
12 | public override string ModuleVersion => "1.0";
13 |
14 | public override string ModuleAuthor => "Letaryat | github.com/letaryat";
15 |
16 | public override string ModuleDescription => "Creates decals with advertisements that can be placed on map.";
17 |
18 | public required PluginConfig Config { get; set; }
19 |
20 | public static CS2_Poor_MapDecals? Instance { get; private set; }
21 |
22 | public EventManager? EventManager { get; private set; }
23 | public PropManager? PropManager { get; private set; }
24 |
25 | public PluginUtils? PluginUtils { get; private set; }
26 | public CommandsManager? CommandsManager { get; private set; }
27 |
28 | public bool AllowAdminCommands = false;
29 | public bool PingPlacement = false;
30 | public int DecalAdToPlace = 0;
31 | public float DecalWidth = 128;
32 | public float DecalHeight = 128;
33 | public bool ForceOnVip = false;
34 |
35 | public override void Load(bool hotReload)
36 | {
37 | Console.WriteLine("Loaded CS2_Poor_MapDecals");
38 | Instance = this;
39 |
40 | EventManager = new EventManager(this);
41 | PluginUtils = new PluginUtils(this);
42 | CommandsManager = new CommandsManager(this);
43 | PropManager = new PropManager(this);
44 |
45 | EventManager.RegisterEvents();
46 | CommandsManager.RegisterCommands();
47 |
48 | }
49 |
50 | public void OnConfigParsed(PluginConfig config)
51 | {
52 | Config = config;
53 | }
54 | public override void Unload(bool hotReload)
55 | {
56 | Console.WriteLine("Unloaded CS2_Poor_MapDecals");
57 | }
58 |
59 | public void DebugMode(string message)
60 | {
61 | if (Config.Debug)
62 | {
63 | Logger.LogInformation(message);
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [!CAUTION]
2 | > With the 28.07.2025 update, decal plugin might crash clients / server. Seems that .vmat files are not cached properly. Until it is fixed I recommend using [CS2-Poor-MapPropAds](https://github.com/Letaryat/CS2-Poor-MapPropAds) that uses props instead of decals.
3 |
4 | # CS2-Poor-MapDecals
5 |
6 | This plugin allows for server owners to create spray type advertisements that are placed on wall.
7 | [](https://ko-fi.com/H2H8TK0L9)
8 |
9 | ## [📺] Video presentation
10 | SoonTM
11 |
12 |
13 |
14 |
15 | ## [📌] Setup
16 | - Download latest release,
17 | - Drag files to /plugins/
18 | - Restart your server,
19 | - Config file should be created in configs/plugins/
20 | - Edit to your liking,
21 |
22 | ## [📝] Configuration
23 | | Option | Description |
24 | | ------------- | ------------- |
25 | | Admin Flag (string) | Which flag will have access to all of the commands |
26 | | Vip Flag (string) | Which flag would not see advertisements that are not forced on vip users |
27 | | Props Path (string[]) | Paths for all advertisements that your addon have |
28 | | Enable commands (bool) | If you want commands to be enabled. (for example, after you placed all of the advertisements you might not need commands anymore) |
29 | | Debug Mode (bool) | If plugin should log errors, etc |
30 |
31 | ### [📝] Config example:
32 | ```
33 | {
34 | "Admin Flag": "@css/root",
35 | "Vip Flag": "@vip/noadv",
36 | "Props Path": [
37 | "materials/Example/exampleTexture.vmat", // ID 0
38 | "materials/Example/exampleTexture2.vmat" // ID 1 etc...
39 | ],
40 | "Enable commands": true,
41 | "Debug Mode": true,
42 | "ConfigVersion": 1
43 | }
44 | ```
45 |
46 | ## [🛡️] Admin commands
47 | Tried to make plugin idiot proof (since I did a lot of mistakes).
48 | | Command | Description |
49 | | ------------- | ------------- |
50 | | css_placedecals | Allow to place advertisements |
51 | | css_setdecal **ID_OF_DECAL** **WIDTH** **HEIGHT** **FORCE_ON_VIP (TRUE/FALSE)** | Configure decal that you want to place |
52 | | css_pingdecals | Allows to place decals using Ping function |
53 | | css_removedecal **ID** | Remove already placed decal using ID |
54 | | css_tpdecal **ID** | Teleports to already existing decal using ID |
55 | | css_showdecals | Prints info to console about all decals that are placed on map |
56 | | css_printdecals | Prints a list of all decals that can be placed to console |
57 |
58 | ## [❤️] Special thanks to:
59 | - [CS2-SkyboxChanger by samyycX](https://github.com/samyycX/CS2-SkyboxChanger) - For function to find id of cached material.
60 | - [Edgegamers JailBreak](https://github.com/edgegamers/Jailbreak/blob/main/mod/Jailbreak.Warden/Paint/WardenPaintBehavior.cs#L131) - For function to check if player is looking at his pretty feet.
61 |
62 | ### [🚨] Plugin might be poorly written and have some issues. I have no idea what I am doing, but when tested it worked fine.
--------------------------------------------------------------------------------
/src/Managers/Prop.cs:
--------------------------------------------------------------------------------
1 | using CounterStrikeSharp.API.Modules.Utils;
2 | using CS2_Poor_MapDecals.Models;
3 | using System.Text.Json;
4 |
5 | namespace CS2_Poor_MapDecals.Managers
6 | {
7 | public class PropManager(CS2_Poor_MapDecals plugin)
8 | {
9 | private readonly CS2_Poor_MapDecals _plugin = plugin;
10 | public string? _mapName;
11 | public string? _mapFilePath;
12 | public readonly List _props = [];
13 | private static readonly object _fileLock = new();
14 |
15 | public void GenerateJsonFile()
16 | {
17 | string directoryPath = Path.Combine(_plugin.ModuleDirectory, "maps");
18 | try
19 | {
20 | if(!Directory.Exists(directoryPath))
21 | {
22 | Directory.CreateDirectory(directoryPath);
23 | }
24 | if(!File.Exists(_mapFilePath))
25 | {
26 | File.WriteAllText(_mapFilePath!, "[]");
27 | }
28 | }
29 | catch(Exception e)
30 | {
31 | _plugin.DebugMode($"{e}");
32 | }
33 | }
34 |
35 | public void PushCordsToFile(Vector pos, QAngle angle, int newIndex, float width, float height, bool forceToVip)
36 | {
37 | lock (_fileLock)
38 | {
39 | if (pos == null || angle == null) return;
40 | int newId = _props.Count();
41 |
42 | _props.Add(new PropModel
43 | {
44 | Id = newId,
45 | ModelIndex = newIndex,
46 | posX = pos.X,
47 | posY = pos.Y,
48 | posZ = pos.Z,
49 | angleX = angle.X,
50 | angleY = angle.Y,
51 | angleZ = angle.Z,
52 | width = width,
53 | height = height,
54 | forceOnVip = forceToVip
55 | });
56 | var options = new JsonSerializerOptions { WriteIndented = true };
57 | File.WriteAllText(_mapFilePath!, JsonSerializer.Serialize(_props, options));
58 | }
59 | }
60 |
61 | public void LoadPropsFromMap()
62 | {
63 | if (File.Exists(_mapFilePath))
64 | {
65 | string json = File.ReadAllText(_mapFilePath);
66 | if(!string.IsNullOrEmpty(json))
67 | {
68 | _props.Clear();
69 | var loadedProps = JsonSerializer.Deserialize>(json) ?? [];
70 | _props.AddRange(loadedProps);
71 | }
72 | }
73 |
74 | }
75 |
76 | public void SpawnProps()
77 | {
78 | foreach (var prop in _props)
79 | {
80 | _plugin.PluginUtils!.CreateDecal(new Vector(prop.posX, prop.posY, prop.posZ), new QAngle(prop.angleX, prop.angleY, prop.angleZ), prop.ModelIndex, prop.width, prop.height, prop.forceOnVip);
81 | }
82 | }
83 |
84 | public PropModel? GetPropById(int id)
85 | {
86 | return id >= 0 && id < _props.Count ? _props[id] : null;
87 | }
88 | public void RemovePropFromFile(string idstring)
89 | {
90 | int id = Convert.ToInt32(idstring);
91 | if (id < 0 || id >= _props.Count()) return;
92 | _props.RemoveAt(id);
93 |
94 | for (int i = 0; i < _props.Count; i++)
95 | {
96 | _props[i].Id = i;
97 | }
98 | var options = new JsonSerializerOptions { WriteIndented = true };
99 | File.WriteAllText(_mapFilePath!, JsonSerializer.Serialize(_props, options));
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Managers/Events.cs:
--------------------------------------------------------------------------------
1 | using CounterStrikeSharp.API;
2 | using CounterStrikeSharp.API.Core;
3 | using CounterStrikeSharp.API.Modules.Admin;
4 | using CounterStrikeSharp.API.Modules.Utils;
5 |
6 | namespace CS2_Poor_MapDecals.Managers;
7 |
8 | public class EventManager(CS2_Poor_MapDecals plugin)
9 | {
10 | private readonly CS2_Poor_MapDecals _plugin = plugin;
11 | public void RegisterEvents()
12 | {
13 | //Events:
14 | _plugin.RegisterEventHandler(OnRoundStart);
15 | _plugin.RegisterEventHandler(OnPlayerPing);
16 | //Listeners:
17 | _plugin.RegisterListener((ResourceManifest manifest) =>
18 | {
19 | foreach (var prop in _plugin.Config.Props)
20 | {
21 | manifest.AddResource(prop);
22 | }
23 | });
24 | _plugin.RegisterListener(OnMapStart);
25 | _plugin.RegisterListener(OnCheckTransmit);
26 | }
27 |
28 |
29 | private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
30 | {
31 | _plugin.PropManager!.SpawnProps();
32 | return HookResult.Continue;
33 | }
34 |
35 | private HookResult OnPlayerPing(EventPlayerPing @event, GameEventInfo info)
36 | {
37 | var ping = @event;
38 | var player = ping.Userid;
39 | if (player == null) return HookResult.Continue;
40 |
41 | if (!_plugin.AllowAdminCommands || !AdminManager.PlayerHasPermissions(player, _plugin.Config.AdminFlag) || !_plugin.PingPlacement)
42 | {
43 | return HookResult.Continue;
44 | }
45 |
46 | var pawn = player.PlayerPawn.Value;
47 | if (pawn == null) return HookResult.Continue;
48 |
49 |
50 | _plugin.PluginUtils!.CreateDecalOnClick(pawn, new Vector(ping.X, ping.Y, ping.Z), _plugin.DecalWidth, _plugin.DecalHeight, _plugin.ForceOnVip);
51 |
52 | return HookResult.Continue;
53 | }
54 |
55 | private void OnCheckTransmit(CCheckTransmitInfoList infoList)
56 | {
57 | var allAdvs = Utilities.FindAllEntitiesByDesignerName("env_decal");
58 | if (allAdvs == null || !allAdvs.Any()) return;
59 | try
60 | {
61 | foreach (var entry in infoList)
62 | {
63 | CCheckTransmitInfo info;
64 | CCSPlayerController? player;
65 |
66 | try
67 | {
68 | (info, player) = ((CCheckTransmitInfo, CCSPlayerController))entry;
69 | }
70 | catch
71 | {
72 | continue;
73 | }
74 |
75 | if (player == null) continue;
76 |
77 | if (AdminManager.PlayerHasPermissions(player, _plugin.Config.VipFlag))
78 | {
79 | foreach (var ad in allAdvs)
80 | {
81 | //if (ad?.Entity?.Name != null && ad.Entity.Name.Contains("advert"))
82 | if (ad.Entity!.Name == null) continue;
83 | if (ad!.Entity!.Name.StartsWith("advert") && !ad!.Entity!.Name.Contains("force"))
84 | {
85 | info.TransmitEntities.Remove(ad);
86 | }
87 | }
88 | }
89 | }
90 | }
91 | catch (Exception error)
92 | {
93 | _plugin.DebugMode($"CheckTransmit: ${error}");
94 | }
95 |
96 | }
97 |
98 |
99 |
100 | private void OnMapStart(string mapName)
101 | {
102 | _plugin.PropManager!._props.Clear();
103 | _plugin.AllowAdminCommands = false;
104 | _plugin.PingPlacement = false;
105 | _plugin.DecalAdToPlace = 0;
106 | Server.NextFrame(() =>
107 | {
108 | _plugin.PropManager._mapName = mapName;
109 | _plugin.PropManager!._mapFilePath = Path.Combine(_plugin.ModuleDirectory, "maps", $"{mapName}.json");
110 |
111 | _plugin.PropManager.GenerateJsonFile();
112 | Server.NextFrame(() =>
113 | {
114 | _plugin.PropManager.LoadPropsFromMap();
115 | });
116 | });
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/src/Utilities/Utils.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using CounterStrikeSharp.API;
3 | using CounterStrikeSharp.API.Core;
4 | using CounterStrikeSharp.API.Modules.Memory;
5 | using CounterStrikeSharp.API.Modules.Utils;
6 | using System.Runtime.CompilerServices;
7 |
8 |
9 | namespace CS2_Poor_MapDecals.Utils;
10 |
11 | public class PluginUtils(CS2_Poor_MapDecals plugin)
12 | {
13 | private readonly CS2_Poor_MapDecals _plugin = plugin;
14 |
15 | public unsafe void CreateDecal(Vector cords, QAngle angle, int index, float width, float height, bool forceOnVip)
16 | {
17 | var entity = Utilities.CreateEntityByName("env_decal");
18 | if (entity == null) return;
19 |
20 | try
21 | {
22 | var material = _plugin.Config.Props[index];
23 | var materialPtr = FindMaterialByPath(material);
24 | if (materialPtr == IntPtr.Zero)
25 | {
26 | _plugin.DebugMode("Could not find a material. Skipping.");
27 | return;
28 | }
29 |
30 | entity.Entity!.Name = "advert";
31 |
32 | if (forceOnVip)
33 | {
34 | entity.Entity!.Name += "force";
35 | }
36 |
37 | entity.Width = width;
38 | entity.Height = height;
39 | entity.Depth = 4;
40 |
41 | Unsafe.Write((void*)entity.DecalMaterial.Handle, materialPtr);
42 | Utilities.SetStateChanged(entity, "CEnvDecal", "m_hDecalMaterial");
43 |
44 | entity.RenderOrder = 1;
45 | entity.RenderMode = RenderMode_t.kRenderNormal;
46 |
47 | entity.ProjectOnWorld = true;
48 |
49 | entity.Teleport(cords, new QAngle(angle.X, angle.Y, 0));
50 | entity.DispatchSpawn();
51 | }
52 | catch (Exception error)
53 | {
54 | _plugin.DebugMode($"{error}");
55 | }
56 |
57 | }
58 |
59 | public void CreateDecalOnClick(CCSPlayerPawn pawn, Vector position, float width, float height, bool forceOnVip)
60 | {
61 | float flippedYaw = (pawn.EyeAngles.Y + 180.0f) % 360.0f;
62 | QAngle spriteAngle = new QAngle(pawn.EyeAngles.X, flippedYaw, pawn.EyeAngles.Z);
63 | Vector impactPos = new Vector(position.X, position.Y, position.Z);
64 |
65 | Vector backward = -GetForwardVector(pawn.EyeAngles);
66 | backward = Normalize(backward);
67 |
68 | Vector offsetPos = impactPos + backward * 2f;
69 |
70 | var eyeAngleZ = GetPlayerEyeVector(pawn);
71 |
72 | try
73 | {
74 | if (eyeAngleZ < -0.90)
75 | {
76 | offsetPos.Z += 1f;
77 | CreateDecal(offsetPos, new QAngle(0, spriteAngle.Y, 0), _plugin.DecalAdToPlace, width, height, forceOnVip);
78 | _plugin.PropManager!.PushCordsToFile(offsetPos, new QAngle(0, spriteAngle.Y, 0), _plugin.DecalAdToPlace, width, height, forceOnVip);
79 | }
80 | else
81 | {
82 | CreateDecal(offsetPos, new QAngle(90, spriteAngle.Y, 0), _plugin.DecalAdToPlace, width, height, forceOnVip);
83 | _plugin.PropManager!.PushCordsToFile(offsetPos, new QAngle(90, spriteAngle.Y, 0), _plugin.DecalAdToPlace, width, height, forceOnVip);
84 | }
85 | }
86 | catch (Exception error)
87 | {
88 | _plugin.DebugMode($"{error}");
89 | }
90 | }
91 |
92 | public Vector GetForwardVector(QAngle angles)
93 | {
94 | float radYaw = angles.Y * (float)(Math.PI / 180.0);
95 | return new Vector((float)Math.Cos(radYaw), (float)Math.Sin(radYaw), 0);
96 | }
97 | public Vector Normalize(Vector vec)
98 | {
99 | float length = MathF.Sqrt(vec.X * vec.X + vec.Y * vec.Y + vec.Z * vec.Z);
100 | if (length == 0)
101 | return new Vector(0, 0, 0);
102 | return new Vector(vec.X / length, vec.Y / length, vec.Z / length);
103 | }
104 |
105 | private float GetPlayerEyeVector(CCSPlayerPawn pawn)
106 | {
107 | // Credits to:
108 | // https://github.com/edgegamers/Jailbreak/blob/main/mod/Jailbreak.Warden/Paint/WardenPaintBehavior.cs#L131
109 | if (pawn == null || !pawn.IsValid) return 0;
110 | var eyeAngle = pawn.EyeAngles;
111 | var pitch = Math.PI / 180 * eyeAngle.X;
112 | var yaw = Math.PI / 180 * eyeAngle.Y;
113 | var eyeVector = new Vector((float)(Math.Cos(yaw) * Math.Cos(pitch)), (float)(Math.Sin(yaw) * Math.Cos(pitch)), (float)-Math.Sin(pitch));
114 | return eyeVector.Z;
115 | }
116 |
117 | // Credits to:
118 | // https://github.com/samyycX/CS2-SkyboxChanger/blob/master/Helper.cs#L26
119 |
120 | public static unsafe IntPtr FindMaterialByPath(string material)
121 | {
122 | if (material.EndsWith("_c"))
123 | {
124 | material = material.Substring(0, material.Length - 2);
125 | }
126 | IntPtr pIMaterialSystem2 = NativeAPI.GetValveInterface(0, "VMaterialSystem2_001");
127 | var FindOrCreateFromResource = VirtualFunction.Create(pIMaterialSystem2, 14);
128 | IntPtr outMaterial = 0;
129 | IntPtr pOutMaterial = (nint)(&outMaterial);
130 | IntPtr materialptr3;
131 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
132 | {
133 | materialptr3 = FindOrCreateFromResource.Invoke(pIMaterialSystem2, pOutMaterial, material);
134 | }
135 | else
136 | {
137 | materialptr3 = FindOrCreateFromResource.Invoke(pOutMaterial, 0, material);
138 | }
139 | if (materialptr3 == 0)
140 | {
141 | return 0;
142 | }
143 | return *(IntPtr*)materialptr3;
144 | }
145 |
146 |
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/src/Managers/Commands.cs:
--------------------------------------------------------------------------------
1 | using CounterStrikeSharp.API.Core;
2 | using CounterStrikeSharp.API.Modules.Admin;
3 | using CounterStrikeSharp.API.Modules.Commands;
4 | using CounterStrikeSharp.API.Modules.Utils;
5 |
6 | namespace CS2_Poor_MapDecals.Managers;
7 |
8 | public class CommandsManager(CS2_Poor_MapDecals plugin)
9 | {
10 | private readonly CS2_Poor_MapDecals _plugin = plugin;
11 |
12 | public void RegisterCommands()
13 | {
14 | if (_plugin.Config.EnableCMD)
15 | {
16 | _plugin.AddCommand("css_placedecals", "Allow to place advertisements", OnAllowPlacing);
17 | _plugin.AddCommand("css_setdecal", "Set advertisement model for bullet & ping", OnConfigureAd);
18 | _plugin.AddCommand("css_pingdecals", "Place advertisement by ping", OnClickAdvertisement);
19 | _plugin.AddCommand("css_removedecal", "Remove BS Entity using ID", OnRemoveEntity);
20 | _plugin.AddCommand("css_tpdecal", "Teleport to BS Entity using ID", TeleportToEntity);
21 | _plugin.AddCommand("css_showdecals", "List of entities on this map", ShowEntityList);
22 | _plugin.AddCommand("css_printdecals", "List of a models that can be placed", PrintAdModels);
23 | }
24 | }
25 |
26 | [CommandHelper(minArgs: 4, usage: "[modelid], [width], [height] [ForceToVip (true / false)]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
27 | private void OnConfigureAd(CCSPlayerController? player, CommandInfo commandInfo)
28 | {
29 | if (player == null || !player!.PlayerPawn.IsValid || player.PlayerPawn.Value == null) return;
30 | var pawn = player.PlayerPawn.Value;
31 | if (pawn == null) return;
32 |
33 | if (!AdminManager.PlayerHasPermissions(player, _plugin.Config.AdminFlag))
34 | {
35 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["NoAccess"]}");
36 | return;
37 | }
38 |
39 | var model = commandInfo.GetArg(1);
40 | var width = commandInfo.GetArg(2);
41 | var height = commandInfo.GetArg(3);
42 | var forceToVipArg = commandInfo.GetArg(4);
43 | bool forceToVipParsed = bool.TryParse(forceToVipArg, out bool forceToVip);
44 |
45 | var Count = _plugin.Config.Props.Count();
46 | if (model == null || width == null || height == null || Int32.Parse(model) >= Count || !forceToVipParsed)
47 | {
48 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["NoArg"]}");
49 | return;
50 | }
51 |
52 | _plugin.DecalAdToPlace = Int32.Parse(model);
53 | _plugin.DecalWidth = float.Parse(width);
54 | _plugin.DecalHeight = float.Parse(height);
55 | _plugin.ForceOnVip = forceToVip;
56 |
57 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["SetModel", model, width, height, forceToVip]}");
58 |
59 | }
60 | private void OnAllowPlacing(CCSPlayerController? player, CommandInfo commandInfo)
61 | {
62 | if (player == null || !player!.PlayerPawn.IsValid || player.PlayerPawn.Value == null) return;
63 | var pawn = player.PlayerPawn.Value;
64 | if (pawn == null) return;
65 |
66 | if (!AdminManager.PlayerHasPermissions(player, _plugin.Config.AdminFlag))
67 | {
68 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["NoAccess"]}");
69 | return;
70 | }
71 |
72 | if (_plugin.AllowAdminCommands)
73 | {
74 | _plugin.AllowAdminCommands = false;
75 | }
76 | else
77 | {
78 | _plugin.AllowAdminCommands = true;
79 | }
80 |
81 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["PlacingMode", _plugin.AllowAdminCommands]}");
82 | }
83 |
84 |
85 | private void OnClickAdvertisement(CCSPlayerController? player, CommandInfo commandInfo)
86 | {
87 | if (player == null || !player!.PlayerPawn.IsValid || player.PlayerPawn.Value == null) return;
88 | var pawn = player.PlayerPawn.Value;
89 | if (pawn == null) return;
90 |
91 | if (!_plugin.AllowAdminCommands || !AdminManager.PlayerHasPermissions(player, _plugin.Config.AdminFlag))
92 | {
93 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["NoAccess"]}");
94 | return;
95 | }
96 |
97 | if (_plugin.PingPlacement)
98 | {
99 | _plugin.PingPlacement = false;
100 | }
101 | else
102 | {
103 | _plugin.PingPlacement = true;
104 | }
105 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["PingMode", _plugin.PingPlacement]}");
106 |
107 | }
108 |
109 |
110 | [CommandHelper(minArgs: 1, usage: "[id]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
111 | private void OnRemoveEntity(CCSPlayerController? player, CommandInfo commandInfo)
112 | {
113 | if (player == null || player.PlayerPawn == null) return;
114 | var pawn = player.PlayerPawn.Value;
115 | if (pawn == null) return;
116 |
117 | if (!_plugin.AllowAdminCommands || !AdminManager.PlayerHasPermissions(player, _plugin.Config.AdminFlag))
118 | {
119 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["NoAccess"]}");
120 | return;
121 | }
122 |
123 | if (commandInfo.ArgCount < 2)
124 | {
125 | return;
126 | }
127 | var arg = commandInfo.GetArg(1);
128 |
129 | _plugin.PropManager!.RemovePropFromFile(arg);
130 |
131 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["RemoveEntity"]}");
132 | }
133 |
134 | [CommandHelper(minArgs: 1, usage: "[id]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
135 | private void TeleportToEntity(CCSPlayerController? player, CommandInfo commandInfo)
136 | {
137 | if (player == null || player.PlayerPawn == null) return;
138 |
139 | var pawn = player.PlayerPawn.Value;
140 | if (pawn == null) return;
141 |
142 | if (!AdminManager.PlayerHasPermissions(player, _plugin.Config.AdminFlag))
143 | {
144 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["NoAccess"]}");
145 | return;
146 | }
147 |
148 | var arg = commandInfo.GetArg(1);
149 | var id = Convert.ToInt32(arg);
150 | var prop = _plugin.PropManager!.GetPropById(id);
151 |
152 | var propPos = new Vector(prop!.posX, prop!.posY, prop!.posZ);
153 |
154 | player.PlayerPawn.Value!.Teleport(propPos);
155 |
156 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["Teleport"]}");
157 | }
158 |
159 |
160 | private void ShowEntityList(CCSPlayerController? player, CommandInfo commandInfo)
161 | {
162 | if (player == null || player.PlayerPawn == null) return;
163 | var pawn = player.PlayerPawn.Value;
164 | if (pawn == null) return;
165 |
166 | if (!AdminManager.PlayerHasPermissions(player, _plugin.Config.AdminFlag))
167 | {
168 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["NoAccess"]}");
169 | return;
170 | }
171 | player.PrintToConsole($"| ID | MODELID | POS | ");
172 | foreach (var entity in _plugin.PropManager!._props)
173 | {
174 | player.PrintToConsole($"| {entity.Id} | {entity.ModelIndex} | X: {entity.posX}, Y: {entity.posY}, Z: {entity.posZ} |");
175 | }
176 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["Console"]}");
177 | }
178 | private void PrintAdModels(CCSPlayerController? player, CommandInfo commandInfo)
179 | {
180 | if (player == null || !player!.PlayerPawn.IsValid || player.PlayerPawn.Value == null) return;
181 | var pawn = player.PlayerPawn.Value;
182 | if (pawn == null) return;
183 | var i = 0;
184 | if (!AdminManager.PlayerHasPermissions(player, _plugin.Config.AdminFlag))
185 | {
186 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["NoAccess"]}");
187 | return;
188 | }
189 | player.PrintToConsole($"| ID | MODEL ");
190 | foreach (var models in _plugin.Config.Props)
191 | {
192 | player.PrintToConsole($"| {i} | {models}");
193 | i++;
194 | }
195 | player.PrintToChat($"{_plugin.Localizer["Prefix"]}{_plugin.Localizer["Console"]}");
196 |
197 | }
198 |
199 | }
200 |
--------------------------------------------------------------------------------