Compare commits

...

15 Commits

Author SHA1 Message Date
Michael Wilson
f1c108087b fix(concommand): don't remove reference flags when running convar/concmd unlocker (#817) 2025-03-24 17:49:09 +10:00
roflmuffin
47ddf42c11 [no ci] add CHANGELOG.md 2025-03-24 12:27:09 +10:00
samyyc
ded133f1db Fix EmitSoundFilter signature for linux (#815) 2025-03-23 19:29:09 +10:00
samyyc
bbc621acdc feat: Implement EmitSoundFilter (#808)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
2025-03-23 07:56:34 +00:00
Michael Wilson
f7784c26c6 fix: change terrorist chat colour to orange to be more in line with the game (#813) 2025-03-23 14:07:05 +10:00
qstage
2da5448c8e feat: add ListenerHandlerAttribute<T> (#757)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
2025-03-17 02:16:33 +00:00
Michael Wilson
e406b78044 [no ci] chore: update protobuf (#803) 2025-03-17 12:10:01 +10:00
Michael Wilson
3839831ea9 [no ci] Update publish-docs.yml (#802) 2025-03-17 11:55:12 +10:00
samyyc
54ad6c0b79 feat: Expose Metamod MetaFactory to NativeAPI (#801) 2025-03-17 11:45:38 +10:00
Michael Wilson
7c2cc8a7f6 [no ci] chore: fix deprecated CI steps (#759) 2025-02-05 13:23:24 +10:00
qstage
e99d27ca30 fix: errors when reloading auto generated config (#754) 2025-01-30 21:08:44 +10:00
dependabot[bot]
44d3c51bc7 [no ci] chore(deps): bump libraries/Protobufs from b46090a to 157162d (#753)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-30 11:22:36 +10:00
Joakim
f72e6d5daf feat: add ReplicateConVar (#751) 2025-01-27 22:10:14 +00:00
samyyc
c8bccb07e0 Remove the characters limit for CenterHtmlMenu (#747) 2025-01-23 13:06:06 +00:00
qstage
2c0640700a fix: ArgumentOutOfRangeException when calling GetArgTargetResult (#745) 2025-01-23 22:53:51 +10:00
33 changed files with 2386 additions and 71 deletions

View File

@@ -74,7 +74,7 @@ jobs:
mkdir build/output/
mv build/addons build/output
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: counterstrikesharp-build-windows-${{ env.GITHUB_SHA_SHORT }}
path: build/output/
@@ -115,7 +115,7 @@ jobs:
mkdir build/output/
mv build/addons build/output
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: counterstrikesharp-build-linux-${{ env.GITHUB_SHA_SHORT }}
path: build/output/
@@ -154,7 +154,7 @@ jobs:
run: dotnet test --logger trx --results-directory "TestResults-${{ env.GITHUB_SHA_SHORT }}" managed/CounterStrikeSharp.API.Tests/CounterStrikeSharp.API.Tests.csproj
- name: Upload dotnet test results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: test-results-${{ env.GITHUB_SHA_SHORT }}
path: TestResults-${{ env.GITHUB_SHA_SHORT }}
@@ -165,7 +165,7 @@ jobs:
dotnet publish -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
dotnet pack -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: counterstrikesharp-build-api-${{ env.GITHUB_SHA_SHORT }}
path: managed/CounterStrikeSharp.API/bin/Release
@@ -181,17 +181,17 @@ jobs:
shell: bash
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: counterstrikesharp-build-windows-${{ env.GITHUB_SHA_SHORT }}
path: build/windows
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: counterstrikesharp-build-linux-${{ env.GITHUB_SHA_SHORT }}
path: build/linux
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: counterstrikesharp-build-api-${{ env.GITHUB_SHA_SHORT }}
path: build/api

View File

@@ -23,10 +23,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Dotnet Setup
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
@@ -35,13 +35,13 @@ jobs:
- run: docfx docfx/docfx.json
- name: Setup Pages
uses: actions/configure-pages@v3
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
uses: actions/upload-pages-artifact@v3
with:
path: "docfx/_site"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4

1951
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -83,6 +83,7 @@ set(SOURCE_FILES
src/scripting/natives/natives_schema.cpp
src/scripting/natives/natives_entities.cpp
src/scripting/natives/natives_voice.cpp
src/scripting/natives/natives_metamod.cpp
src/core/managers/entity_manager.cpp
src/core/managers/entity_manager.h
src/core/managers/chat_manager.cpp

80
cliff.toml Normal file
View File

@@ -0,0 +1,80 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
# [remote.github]
# owner = "roflmuffin"
# repo = "CounterStrikeSharp"
# token = ""
[changelog]
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
## What's Changed
{%- if version %} in {{ version }}{%- endif -%}
{% for commit in commits %}
{% if commit.remote.pr_title -%}
{%- set commit_message = commit.remote.pr_title -%}
{%- else -%}
{%- set commit_message = commit.message -%}
{%- endif -%}
* {{ commit_message | split(pat="\n") | first | trim }}\
{% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
{% if commit.remote.pr_number %} in \
[#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
{%- endif %}
([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
{%- endfor -%}
{%- if github -%}
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
{% raw %}\n{% endraw -%}
## New Contributors
{%- endif %}\
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
* @{{ contributor.username }} made their first contribution
{%- if contributor.pr_number %} in \
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endfor -%}
{%- endif -%}
{% if version %}
{% else -%}
{% raw %}\n{% endraw %}
{% endif %}
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
"""
# Remove leading and trailing whitespaces from the changelog's body.
trim = true
# A Tera template to be rendered as the changelog's footer.
# See https://keats.github.io/tera/docs/#introduction
footer = """
<!-- generated by git-cliff -->
"""
# An array of regex based postprocessors to modify the changelog.
# Replace the placeholder `<REPO>` with a URL.
postprocessors = []
[git]
# Parse commits according to the conventional commits specification.
# See https://www.conventionalcommits.org
conventional_commits = false
# Exclude commits that do not match the conventional commits specification.
filter_unconventional = true
# Split commits on newlines, treating each line as an individual commit.
split_commits = false
# An array of regex based parsers to modify commit messages prior to further processing.
commit_preprocessors = [{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }]
# Exclude commits that are not matched by any commit parser.
filter_commits = false
# Order releases topologically instead of chronologically.
topo_order = false
# Order of commits in each group/release within the changelog.
# Allowed values: newest, oldest
sort_commits = "newest"

View File

@@ -153,6 +153,13 @@
"linux": "48 85 FF 74 ? 55 48 89 E5 41 56"
}
},
"CBaseEntity_EmitSoundFilter": {
"signatures": {
"library": "server",
"windows": "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 48 89 7C 24 20 41 56 48 83 EC 30 48 8B EA",
"linux": "55 48 89 E5 41 56 49 89 D6 41 55 41 89 F5 41 54 48"
}
},
"CEntityInstance_AcceptInput": {
"signatures": {
"library": "server",
@@ -266,4 +273,4 @@
"linux": 584
}
}
}
}

View File

@@ -206,6 +206,18 @@ namespace CounterStrikeSharp.API.Core
}
}
public static void ReplicateConvar(int clientslot, string convarname, string convarvalue){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(clientslot);
ScriptContext.GlobalScriptContext.Push(convarname);
ScriptContext.GlobalScriptContext.Push(convarvalue);
ScriptContext.GlobalScriptContext.SetIdentifier(0xC8728BEC);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static T DynamicHookGetReturn<T>(IntPtr hook, int datatype){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -768,6 +780,21 @@ namespace CounterStrikeSharp.API.Core
}
}
public static uint EmitSoundFilter(ulong filtermask, uint ent, string sound, float volume, float pitch){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(filtermask);
ScriptContext.GlobalScriptContext.Push(ent);
ScriptContext.GlobalScriptContext.Push(sound);
ScriptContext.GlobalScriptContext.Push(volume);
ScriptContext.GlobalScriptContext.Push(pitch);
ScriptContext.GlobalScriptContext.SetIdentifier(0x43C4A2B3);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (uint)ScriptContext.GlobalScriptContext.GetResult(typeof(uint));
}
}
public static void HookEvent(string name, InputArgument callback, bool ispost){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -1160,6 +1187,17 @@ namespace CounterStrikeSharp.API.Core
}
}
public static IntPtr MetaFactory(string interfacename){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(interfacename);
ScriptContext.GlobalScriptContext.SetIdentifier(0x61521EF3);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
}
}
public static short GetSchemaOffset(string classname, string propname){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();

View File

@@ -0,0 +1,9 @@
using System;
namespace CounterStrikeSharp.API.Core.Attributes.Registration;
[AttributeUsage(AttributeTargets.Method)]
public class ListenerHandlerAttribute<T> : Attribute
where T: Delegate
{
}

View File

@@ -396,20 +396,26 @@ namespace CounterStrikeSharp.API.Core
}
/// <summary>
/// Registers all game event handlers that are decorated with the <see cref="GameEventHandlerAttribute"/> attribute.
/// Registers all game event handlers that are decorated with the <see cref="GameEventHandlerAttribute"/> and <see cref="ListenerHandlerAttribute{T}"/> attribute.
/// </summary>
/// <param name="instance">The instance of the object where the event handlers are defined.</param>
public void RegisterAttributeHandlers(object instance)
{
var eventHandlers = instance.GetType()
.GetMethods()
var methods = instance.GetType().GetMethods();
var eventHandlers = methods
.Where(method => method.GetCustomAttribute<GameEventHandlerAttribute>() != null)
.Where(method =>
method.GetParameters().FirstOrDefault()?.ParameterType.IsSubclassOf(typeof(GameEvent)) == true)
.ToArray();
var listenerHandlers = methods
.Where(method => method.GetCustomAttribute(typeof(ListenerHandlerAttribute<>)) != null)
.ToArray();
var method = typeof(BasePlugin).GetMethod("RegisterEventHandlerInternal", BindingFlags.NonPublic |
var registerEvent = typeof(BasePlugin).GetMethod(nameof(RegisterEventHandlerInternal), BindingFlags.NonPublic |
BindingFlags.Instance)!;
var registerListener = GetType().GetMethod(nameof(RegisterListener))!;
foreach (var eventHandler in eventHandlers)
{
@@ -421,8 +427,22 @@ namespace CounterStrikeSharp.API.Core
var actionType = typeof(GameEventHandler<>).MakeGenericType(parameterType);
var action = Delegate.CreateDelegate(actionType, instance, eventHandler);
var generic = method.MakeGenericMethod(parameterType);
generic.Invoke(this, new object[] { eventName, action, hookMode == HookMode.Post });
var registerEventGeneric = registerEvent.MakeGenericMethod(parameterType);
registerEventGeneric.Invoke(this, [eventName, action, hookMode == HookMode.Post]);
}
foreach (var listnerHandler in listenerHandlers)
{
var attribute = listnerHandler.GetCustomAttribute(typeof(ListenerHandlerAttribute<>))!;
var listenerType = attribute.GetType().GetGenericArguments().First();
if (listenerType.GetCustomAttribute<ListenerNameAttribute>() == null)
throw new ArgumentException("Listener of type T is invalid and does not have a name attribute",
listenerType.Name);
var listenerDelegate = Delegate.CreateDelegate(listenerType, instance, listnerHandler);
registerListener.MakeGenericMethod(listenerType).Invoke(this, [listenerDelegate]);
}
}

View File

@@ -4489,14 +4489,6 @@ namespace CounterStrikeSharp.API.Core
}
// ip:port
public string Address
{
get => Get<string>("address");
set => Set<string>("address", value);
}
public bool Bot
{

View File

@@ -170,5 +170,11 @@ namespace CounterStrikeSharp.API.Core
/// <param name="infoList">Transmit info list</param>
[ListenerName("CheckTransmit")]
public delegate void CheckTransmit([CastFrom(typeof(nint))]CCheckTransmitInfoList infoList);
/// <summary>
/// Called when all metamod plugins are loaded.
/// </summary>
[ListenerName("OnMetamodAllPluginsLoaded")]
public delegate void OnMetamodAllPluginsLoaded();
}
}

View File

@@ -50,4 +50,25 @@ public partial class CBaseEntity
return (T)Activator.CreateInstance(typeof(T), Marshal.ReadIntPtr(SubclassID.Handle + 4));
}
/// <summary>
/// Emit a soundevent to all players.
/// </summary>
/// <param name="soundEventName">The name of the soundevent to emit.</param>
/// <param name="recipients">The recipients of the soundevent.</param>
/// <param name="volume">The volume of the soundevent.</param>
/// <param name="pitch">The pitch of the soundevent.</param>
/// <returns>The sound event guid.</returns>
public uint EmitSound(string soundEventName, RecipientFilter? recipients = null, float volume = 1f, float pitch = 0)
{
Guard.IsValidEntity(this);
if (recipients == null)
{
recipients = new RecipientFilter();
recipients.AddAllPlayers();
}
return NativeAPI.EmitSoundFilter(recipients.GetRecipientMask(), this.Index, soundEventName, volume, pitch);
}
}

View File

@@ -347,4 +347,9 @@ public partial class CCSPlayerController
{
base.Teleport(position, angles, velocity);
}
public void ReplicateConVar(string conVar, string value)
{
NativeAPI.ReplicateConvar(Slot, conVar, value);
}
}

View File

@@ -36,7 +36,7 @@ public class Target
private static bool ConstTargetType(string target, out TargetType targetType)
{
targetType = TargetType.Invalid;
if (!target.StartsWith("@"))
if (!target.StartsWith('@'))
{
return false;
}
@@ -50,13 +50,13 @@ public class Target
{
targetType = TargetType.Invalid;
slug = null!;
if (!target.StartsWith("#"))
if (!target.StartsWith('#'))
{
return false;
}
slug = target.TrimStart('#');
if (slug.StartsWith("STEAM")) targetType = TargetType.IdSteamEscaped;
if (slug.StartsWith("STEAM") && slug.Contains(':')) targetType = TargetType.IdSteamEscaped;
else if (!ulong.TryParse(slug, out _)) targetType = TargetType.ExplicitName;
else if (slug.Length == 17) targetType = TargetType.IdSteam64;
else targetType = TargetType.IdUserid;
@@ -101,10 +101,9 @@ public class Target
TargetType.GroupNotMe => player.SteamID != caller?.SteamID,
TargetType.PlayerMe => player.SteamID == caller?.SteamID,
TargetType.IdUserid => player.UserId.ToString() == Slug,
TargetType.IdSteamEscaped => ((SteamID)player.SteamID).SteamId2 == Slug,
TargetType.IdSteam64 => ((SteamID)player.SteamID).SteamId64.ToString() == Slug,
TargetType.ExplicitName => player.PlayerName.Contains(Slug, StringComparison.OrdinalIgnoreCase),
TargetType.ImplicitName => player.PlayerName.Contains(Slug, StringComparison.OrdinalIgnoreCase),
TargetType.IdSteamEscaped when player.SteamID != 0 => (SteamID)player.SteamID == (SteamID)Slug,
TargetType.IdSteam64 => player.SteamID.ToString() == Slug,
TargetType.ExplicitName or TargetType.ImplicitName => player.PlayerName.Contains(Slug, StringComparison.OrdinalIgnoreCase),
_ => false
};
}

View File

@@ -7,7 +7,8 @@ public static class PluginConfigExtensions
{
private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
WriteIndented = true
WriteIndented = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
public static JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;
@@ -62,7 +63,7 @@ public static class PluginConfigExtensions
var configContent = File.ReadAllText(configPath);
var newConfig = JsonSerializer.Deserialize<T>(configContent)
var newConfig = JsonSerializer.Deserialize<T>(configContent, JsonSerializerOptions)
?? throw new JsonException($"Deserialization failed for configuration file '{configPath}'.");
foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))

View File

@@ -29,13 +29,13 @@ public class CenterHtmlMenu : BaseMenu
public string NextPageColor { get; set; } = "yellow";
public string CloseColor { get; set; } = "red";
public CenterHtmlMenu(string title, BasePlugin plugin) : base(ModifyTitle(title))
public CenterHtmlMenu(string title, BasePlugin plugin) : base(title)
{
_plugin = plugin;
}
[Obsolete("Use the constructor that takes a BasePlugin")]
public CenterHtmlMenu(string title) : base(ModifyTitle(title))
public CenterHtmlMenu(string title) : base(title)
{
}
@@ -45,40 +45,18 @@ public class CenterHtmlMenu : BaseMenu
{
throw new InvalidOperationException("This method is unsupported with the CenterHtmlMenu constructor used." +
"Please provide a BasePlugin in the constructor.");
};
};
MenuManager.OpenCenterHtmlMenu(_plugin, player, this);
}
public override ChatMenuOption AddMenuOption(string display, Action<CCSPlayerController, ChatMenuOption> onSelect,
bool disabled = false)
{
var option = new ChatMenuOption(ModifyOptionDisplay(display), disabled, onSelect);
var option = new ChatMenuOption(display, disabled, onSelect);
MenuOptions.Add(option);
return option;
}
private static string ModifyTitle(string title)
{
if (title.Length > 32)
{
Application.Instance.Logger.LogWarning("Title should not be longer than 32 characters for a CenterHtmlMenu");
return title[..32];
}
return title;
}
private static string ModifyOptionDisplay(string display)
{
if (display.Length > 26)
{
Application.Instance.Logger.LogWarning("Display should not be longer than 26 characters for a CenterHtmlMenu item");
return display[..26];
}
return display;
}
}
public class CenterHtmlMenuInstance : BaseMenuInstance

View File

@@ -60,7 +60,7 @@ public class ChatColors
case CsTeam.CounterTerrorist:
return LightBlue;
case CsTeam.Terrorist:
return Yellow;
return Orange;
default:
throw new ArgumentException($"Invalid team: ${team}");
}

View File

@@ -247,5 +247,20 @@ namespace CounterStrikeSharp.API
entity.LastNetworkChange = Server.CurrentTime;
entity.IsSteadyState.Clear();
}
/// <summary>
/// metamod method 'MetaFactory' to get the pointer of api interface exposed by metamod plugins.
/// Returns null when the interface cannot be found.
/// </summary>
/// <param name="interfaceName">The interface name of metamod api, can be found in their api header file</param>
public static IntPtr? MetaFactory(string interfaceName)
{
IntPtr ptr = NativeAPI.MetaFactory(interfaceName);
if (ptr == 0)
{
return null;
}
return ptr;
}
}
}

View File

@@ -392,6 +392,16 @@ namespace TestPlugin
return HookResult.Continue;
}
[ListenerHandler<Listeners.OnClientPutInServer>]
public void OnClientPutInServer(int playerSlot)
{
var player = Utilities.GetPlayerFromSlot(playerSlot);
if (player == null || player.IsBot) return;
player.PrintToChat("Welcome to the server!");
}
[ConsoleCommand("css_testinput", "Test AcceptInput and AddEntityIOEvent")]
public void OnTestInput(CCSPlayerController? player, CommandInfo command)
{

View File

@@ -143,9 +143,9 @@ void DetourGameEventManagerInit(IGameEventManager2* pGameEventManager)
int source_hook_pluginid = 0;
CGlobalVars* getGlobalVars() {
INetworkGameServer *server = networkServerService->GetIGameServer();
if(!server) return nullptr;
return networkServerService->GetIGameServer()->GetGlobals();
INetworkGameServer* server = networkServerService->GetIGameServer();
if (!server) return nullptr;
return networkServerService->GetIGameServer()->GetGlobals();
}
} // namespace globals
} // namespace counterstrikesharp

View File

@@ -216,7 +216,7 @@ void ConCommandManager::OnAllInitialized()
}
}
static uint64 flagsToRemove = (FCVAR_HIDDEN | FCVAR_DEVELOPMENTONLY | FCVAR_MISSING0 | FCVAR_MISSING1 | FCVAR_MISSING2 | FCVAR_MISSING3);
static uint64 flagsToRemove = (FCVAR_HIDDEN | FCVAR_DEVELOPMENTONLY);
void UnlockConVars()
{

View File

@@ -17,6 +17,7 @@
#include "core/managers/entity_manager.h"
#include "core/gameconfig.h"
#include "core/log.h"
#include "core/recipientfilters.h"
#include <funchook.h>
#include <vector>
@@ -72,6 +73,14 @@ void EntityManager::OnAllInitialized()
CSSHARP_CORE_CRITICAL("Failed to find signature for \'CEntitySystem_AddEntityIOEvent\'");
}
CBaseEntity_EmitSoundFilter = decltype(CBaseEntity_EmitSoundFilter) (
modules::server->FindSignature(globals::gameConfig->GetSignature("CBaseEntity_EmitSoundFilter")));
if (!CBaseEntity_EmitSoundFilter)
{
CSSHARP_CORE_CRITICAL("Failed to find signature for \'CBaseEntity_EmitSoundFilter\'");
}
auto m_hook = funchook_create();
funchook_prepare(m_hook, (void**)&m_pFireOutputInternal, (void*)&DetourFireOutputInternal);
funchook_install(m_hook, 0);
@@ -265,4 +274,19 @@ void DetourFireOutputInternal(CEntityIOOutput* const pThis, CEntityInstance* pAc
}
}
SndOpEventGuid_t EntityEmitSoundFilter(IRecipientFilter& filter, uint32 ent, const char* pszSound, float flVolume, float flPitch)
{
if (!CBaseEntity_EmitSoundFilter) {
CSSHARP_CORE_ERROR("[EntityManager][EmitSoundFilter] - Failed to emit a sound. Signature for \'CBaseEntity_EmitSoundFilter\' is not found. The latest update may have broken it.");
return SndOpEventGuid_t{};
}
EmitSound_t params;
params.m_pSoundName = pszSound;
params.m_flVolume = flVolume;
params.m_nPitch = flPitch;
return CBaseEntity_EmitSoundFilter(filter, ent, params);
}
} // namespace counterstrikesharp

View File

@@ -137,4 +137,80 @@ inline void (*CEntitySystem_AddEntityIOEvent)(CEntitySystem* pEntitySystem,
variant_t* value,
float delay,
int nOutputID);
typedef uint32 SoundEventGuid_t;
enum gender_t : uint8
{
GENDER_NONE = 0x0,
GENDER_MALE = 0x1,
GENDER_FEMALE = 0x2,
GENDER_NAMVET = 0x3,
GENDER_TEENGIRL = 0x4,
GENDER_BIKER = 0x5,
GENDER_MANAGER = 0x6,
GENDER_GAMBLER = 0x7,
GENDER_PRODUCER = 0x8,
GENDER_COACH = 0x9,
GENDER_MECHANIC = 0xA,
GENDER_CEDA = 0xB,
GENDER_CRAWLER = 0xC,
GENDER_UNDISTRACTABLE = 0xD,
GENDER_FALLEN = 0xE,
GENDER_RIOT_CONTROL = 0xF,
GENDER_CLOWN = 0x10,
GENDER_JIMMY = 0x11,
GENDER_HOSPITAL_PATIENT = 0x12,
GENDER_BRIDE = 0x13,
GENDER_LAST = 0x14,
};
struct EmitSound_t
{
EmitSound_t() :
m_nChannel(0),
m_pSoundName(0),
m_flVolume(VOL_NORM),
m_SoundLevel(SNDLVL_NONE),
m_nFlags(0),
m_nPitch(PITCH_NORM),
m_pOrigin(0),
m_flSoundTime(0.0f),
m_pflSoundDuration(0),
m_bEmitCloseCaption(true),
m_bWarnOnMissingCloseCaption(false),
m_bWarnOnDirectWaveReference(false),
m_nSpeakerEntity(-1),
m_UtlVecSoundOrigin(),
m_nForceGuid(0),
m_SpeakerGender(GENDER_NONE)
{
}
int m_nChannel;
const char* m_pSoundName;
float m_flVolume;
soundlevel_t m_SoundLevel;
int m_nFlags;
int m_nPitch;
const Vector* m_pOrigin;
float m_flSoundTime;
float* m_pflSoundDuration;
bool m_bEmitCloseCaption;
bool m_bWarnOnMissingCloseCaption;
bool m_bWarnOnDirectWaveReference;
CEntityIndex m_nSpeakerEntity;
CUtlVector<Vector, CUtlMemory<Vector, int> > m_UtlVecSoundOrigin;
SoundEventGuid_t m_nForceGuid;
gender_t m_SpeakerGender;
};
struct SndOpEventGuid_t
{
SoundEventGuid_t m_nGuid;
uint64 m_hStackHash;
};
inline SndOpEventGuid_t(FASTCALL* CBaseEntity_EmitSoundFilter)(IRecipientFilter& filter, CEntityIndex ent, const EmitSound_t& params);
SndOpEventGuid_t EntityEmitSoundFilter(IRecipientFilter& filter, uint32 ent, const char* pszSound, float flVolume = 1.0f, float flPitch = 1.0f);
} // namespace counterstrikesharp

View File

@@ -134,6 +134,7 @@ bool CounterStrikeSharpMMPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, s
CALL_GLOBAL_LISTENER(OnAllInitialized());
on_activate_callback = globals::callbackManager.CreateCallback("OnMapStart");
on_metamod_all_plugins_loaded_callback = globals::callbackManager.CreateCallback("OnMetamodAllPluginsLoaded");
SH_ADD_HOOK_MEMFUNC(IServerGameDLL, GameFrame, globals::server, this, &CounterStrikeSharpMMPlugin::Hook_GameFrame, true);
SH_ADD_HOOK_MEMFUNC(INetworkServerService, StartupServer, globals::networkServerService, this,
@@ -182,6 +183,7 @@ bool CounterStrikeSharpMMPlugin::Unload(char* error, size_t maxlen)
&CounterStrikeSharpMMPlugin::Hook_StartupServer, true);
globals::callbackManager.ReleaseCallback(on_activate_callback);
globals::callbackManager.ReleaseCallback(on_metamod_all_plugins_loaded_callback);
return true;
}
@@ -191,6 +193,8 @@ void CounterStrikeSharpMMPlugin::AllPluginsLoaded()
/* This is where we'd do stuff that relies on the mod or other plugins
* being initialized (for example, cvars added and events registered).
*/
on_metamod_all_plugins_loaded_callback->ScriptContext().Reset();
on_metamod_all_plugins_loaded_callback->Execute();
}
void CounterStrikeSharpMMPlugin::AddTaskForNextFrame(std::function<void()>&& task)

View File

@@ -71,6 +71,7 @@ class CounterStrikeSharpMMPlugin : public ISmmPlugin, public IMetamodListener
};
static ScriptCallback* on_activate_callback;
static ScriptCallback* on_metamod_all_plugins_loaded_callback;
extern CounterStrikeSharpMMPlugin gPlugin;
PLUGIN_GLOBALVARS();

View File

@@ -0,0 +1 @@
OnMetamodAllPluginsLoaded:

View File

@@ -15,13 +15,17 @@
*/
#include <eiface.h>
#include <networksystem/inetworkmessages.h>
#include "scripting/autonative.h"
#include "scripting/callback_manager.h"
#include "core/managers/con_command_manager.h"
#include "core/managers/player_manager.h"
#include "core/recipientfilters.h"
#include "igameeventsystem.h"
#include "scripting/script_engine.h"
#include "core/log.h"
#include <networkbasetypes.pb.h>
namespace counterstrikesharp {
@@ -191,6 +195,25 @@ void SetConVarStringValue(ScriptContext& script_context)
pCvar->values = reinterpret_cast<CVValue_t**>((char*)value);
}
void ReplicateConVar(ScriptContext& script_context)
{
auto slot = script_context.GetArgument<int>(0);
auto name = script_context.GetArgument<const char*>(1);
auto value = script_context.GetArgument<const char*>(2);
INetworkMessageInternal* pNetMsg = globals::networkMessages->FindNetworkMessagePartial("SetConVar");
auto msg = pNetMsg->AllocateMessage()->ToPB<CNETMsg_SetConVar>();
CMsg_CVars_CVar* cvarMsg = msg->mutable_convars()->add_cvars();
cvarMsg->set_name(name);
cvarMsg->set_value(value);
CSingleRecipientFilter filter(slot);
globals::gameEventSystem->PostEventAbstract(-1, false, &filter, pNetMsg, msg, 0);
delete msg;
}
REGISTER_NATIVES(commands, {
ScriptEngine::RegisterNativeHandler("ADD_COMMAND", AddCommand);
ScriptEngine::RegisterNativeHandler("REMOVE_COMMAND", RemoveCommand);
@@ -211,5 +234,6 @@ REGISTER_NATIVES(commands, {
IssueClientCommandFromServer);
ScriptEngine::RegisterNativeHandler("GET_CLIENT_CONVAR_VALUE", GetClientConVarValue);
ScriptEngine::RegisterNativeHandler("SET_FAKE_CLIENT_CONVAR_VALUE", SetFakeClientConVarValue);
ScriptEngine::RegisterNativeHandler("REPLICATE_CONVAR", ReplicateConVar);
})
} // namespace counterstrikesharp

View File

@@ -12,4 +12,5 @@ ISSUE_CLIENT_COMMAND_FROM_SERVER: slot:int,command:string -> void
FIND_CONVAR: name:string -> pointer
SET_CONVAR_STRING_VALUE: convar:pointer,value:string -> void
GET_CLIENT_CONVAR_VALUE: clientIndex:int,convarName:string -> string
SET_FAKE_CLIENT_CONVAR_VALUE: clientIndex:int,convarName:string,convarValue:string -> void
SET_FAKE_CLIENT_CONVAR_VALUE: clientIndex:int,convarName:string,convarValue:string -> void
REPLICATE_CONVAR: clientSlot:int,convarName:string,convarValue:string -> void

View File

@@ -23,6 +23,7 @@
#include "core/managers/entity_manager.h"
#include "core/managers/player_manager.h"
#include "core/memory.h"
#include "core/recipientfilters.h"
#include "scripting/autonative.h"
#include "scripting/script_engine.h"
@@ -256,6 +257,21 @@ void AddEntityIOEvent(ScriptContext& script_context)
CEntitySystem_AddEntityIOEvent(GameEntitySystem(), pTarget, pInputName, pActivator, pCaller, &_value, delay, outputID);
}
SoundEventGuid_t EmitSoundFilter(ScriptContext& script_context)
{
auto filtermask = script_context.GetArgument<uint64>(0);
auto ent = script_context.GetArgument<uint32>(1);
auto sound = script_context.GetArgument<const char*>(2);
auto volume = script_context.GetArgument<float>(3);
auto pitch = script_context.GetArgument<float>(4);
CRecipientFilter filter{};
filter.AddRecipientsFromMask(filtermask);
SndOpEventGuid_t ret = EntityEmitSoundFilter(filter, ent, sound, volume, pitch);
return ret.m_nGuid;
}
REGISTER_NATIVES(entities, {
ScriptEngine::RegisterNativeHandler("GET_ENTITY_FROM_INDEX", GetEntityFromIndex);
ScriptEngine::RegisterNativeHandler("GET_USERID_FROM_INDEX", GetUserIdFromIndex);
@@ -273,5 +289,6 @@ REGISTER_NATIVES(entities, {
ScriptEngine::RegisterNativeHandler("UNHOOK_ENTITY_OUTPUT", UnhookEntityOutput);
ScriptEngine::RegisterNativeHandler("ACCEPT_INPUT", AcceptInput);
ScriptEngine::RegisterNativeHandler("ADD_ENTITY_IO_EVENT", AddEntityIOEvent);
ScriptEngine::RegisterNativeHandler("EMIT_SOUND_FILTER", EmitSoundFilter);
})
} // namespace counterstrikesharp

View File

@@ -14,3 +14,4 @@ HOOK_ENTITY_OUTPUT: classname:string, outputName:string, callback:func, mode:Hoo
UNHOOK_ENTITY_OUTPUT: classname:string, outputName:string, callback:func, mode:HookMode -> void
ACCEPT_INPUT: pThis:pointer, inputName:string, activator:pointer, caller:pointer, value:string, outputID:int -> void
ADD_ENTITY_IO_EVENT: pTarget:pointer, inputName:string, activator:pointer, caller:pointer, value:string, delay:float, outputID:int -> void
EMIT_SOUND_FILTER: filtermask:uint64, ent:uint, sound:string, volume:float, pitch:float -> uint

View File

@@ -0,0 +1,32 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
#include <scripting/autonative.h>
#include <scripting/script_engine.h>
namespace counterstrikesharp {
static void* MetaFactory(ScriptContext& script_context)
{
auto interfaceName = script_context.GetArgument<const char*>(0);
void* ptr = globals::ismm->MetaFactory(interfaceName, nullptr, nullptr);
return ptr;
}
REGISTER_NATIVES(metamod, {
ScriptEngine::RegisterNativeHandler("META_FACTORY", MetaFactory);
})
} // namespace counterstrikesharp

View File

@@ -0,0 +1 @@
META_FACTORY: interfaceName:string -> pointer