Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ set(SOURCE_FILES
src/core/managers/usermessage_manager.cpp
src/core/managers/usermessage_manager.h
src/scripting/natives/natives_raytrace.cpp
src/scripting/natives/natives_sounds.cpp
src/core/sound_event.cpp
src/scripting/natives/natives_dynamichooks.cpp
src/core/game_system.h
src/core/game_system.cpp
Expand Down
121 changes: 101 additions & 20 deletions managed/CounterStrikeSharp.API/Generated/Natives/API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1690,26 +1690,6 @@ public static void GetEntityWorldSpaceAabb(IntPtr entity, IntPtr minsout, IntPtr
}
}

public static IntPtr GetEconItemSystem(){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.SetIdentifier(0x981E9B5B);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return ScriptContext.GlobalScriptContext.GetResultPrimitive<IntPtr>();
}
}

public static bool IsServerPaused(){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.SetIdentifier(0xB216AAAC);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return ScriptContext.GlobalScriptContext.GetResultPrimitive<bool>();
}
}

public static short GetSchemaOffset(string classname, string propname){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
Expand Down Expand Up @@ -1799,6 +1779,107 @@ public static void SchemaNetworkStateChanged(IntPtr instance, uint offset, uint
}
}

public static IntPtr GetEconItemSystem(){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.SetIdentifier(0x981E9B5B);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return ScriptContext.GlobalScriptContext.GetResultPrimitive<IntPtr>();
}
}

public static bool IsServerPaused(){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.SetIdentifier(0xB216AAAC);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return ScriptContext.GlobalScriptContext.GetResultPrimitive<bool>();
}
}

public static IntPtr SoundEventCreate(string name){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.PushString(name);
ScriptContext.GlobalScriptContext.SetIdentifier(0xB724884E);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return ScriptContext.GlobalScriptContext.GetResultPrimitive<IntPtr>();
}
}

public static void SoundEventRelease(IntPtr soundevent){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.PushPrimitive(soundevent);
ScriptContext.GlobalScriptContext.SetIdentifier(0xFF002123);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}

public static void SoundEventSetFloat(IntPtr soundevent, string name, float value){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.PushPrimitive(soundevent);
ScriptContext.GlobalScriptContext.PushString(name);
ScriptContext.GlobalScriptContext.PushPrimitive(value);
ScriptContext.GlobalScriptContext.SetIdentifier(0x311228E7);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}

public static void SoundEventSetInt(IntPtr soundevent, string name, int value){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.PushPrimitive(soundevent);
ScriptContext.GlobalScriptContext.PushString(name);
ScriptContext.GlobalScriptContext.PushPrimitive(value);
ScriptContext.GlobalScriptContext.SetIdentifier(0x470A0744);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}

public static void SoundEventSetVector(IntPtr soundevent, string name, IntPtr value){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.PushPrimitive(soundevent);
ScriptContext.GlobalScriptContext.PushString(name);
ScriptContext.GlobalScriptContext.PushPrimitive(value);
ScriptContext.GlobalScriptContext.SetIdentifier(0x2D238D2E);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}

public static int SoundEventEmit(IntPtr soundevent, int sourceentityindex, ulong recipients){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.PushPrimitive(soundevent);
ScriptContext.GlobalScriptContext.PushPrimitive(sourceentityindex);
ScriptContext.GlobalScriptContext.PushPrimitive(recipients);
ScriptContext.GlobalScriptContext.SetIdentifier(0x30D4F65F);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return ScriptContext.GlobalScriptContext.GetResultPrimitive<int>();
}
}

public static void SoundEventStop(int guid, ulong recipients){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.PushPrimitive(guid);
ScriptContext.GlobalScriptContext.PushPrimitive(recipients);
ScriptContext.GlobalScriptContext.SetIdentifier(0x30DCA4D2);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}

public static IntPtr CreateTimer(float interval, InputArgument callback, int flags){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
Expand Down
141 changes: 141 additions & 0 deletions managed/CounterStrikeSharp.API/Modules/Sounds/SoundEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* 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/>. *
*/

using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;

namespace CounterStrikeSharp.API.Modules.Sounds;

/// <summary>
/// A sound event built and sent as a network message, which lets sound operator parameters such
/// as volume and pitch be chosen per emit.
/// </summary>
/// <example>
/// <code>
/// using var sound = new SoundEvent("Weapon_AK47.Single");
/// sound.SourceEntityIndex = (int)player.Index;
/// sound.SetParam(SoundEvent.Volume, 0.5f);
/// sound.SetParam(SoundEvent.Pitch, 1.5f);
/// sound.EmitToAll();
/// </code>
/// </example>
public class SoundEvent : NativeObject, IDisposable
{
/// <summary>Volume the sound plays at, where 1 leaves it unchanged.</summary>
public const string Volume = "public.volume";

/// <summary>Playback rate, where 1 leaves it unchanged, 2 raises it an octave and 0.5 lowers it one.</summary>
public const string Pitch = "public.pitch";

/// <summary>
/// World position the sound plays from. Takes effect with <see cref="SourceEntityIndex"/> set
/// to 0, and is ignored while the sound plays at the recipient or follows an entity.
/// </summary>
public const string Position = "public.position";

private bool _disposed;

/// <param name="name">Name of the sound event, for example <c>Weapon_AK47.Single</c>.</param>
/// <remarks>
/// The name is resolved by the client, so the server cannot tell a valid one from a typo. A name
/// that does not exist plays nothing and reports no error.
/// </remarks>
public SoundEvent(string name) : base(NativeAPI.SoundEventCreate(name))
{
}

/// <summary>
/// Where the sound plays from.
/// <list type="bullet">
/// <item><description>-1, the default, plays it at each recipient, and <see cref="Position"/> is ignored.</description></item>
/// <item><description>0 plays it at <see cref="Position"/> in the world, which is how the game emits placed sounds.</description></item>
/// <item><description>Any other index attaches it to that entity and follows it.</description></item>
/// </list>
/// </summary>
public int SourceEntityIndex { get; set; } = -1;

/// <summary>
/// Sets a sound operator parameter. Setting the same parameter twice keeps the later value.
/// </summary>
/// <param name="name">
/// Any <c>public.*</c> parameter name. <see cref="Volume"/>, <see cref="Pitch"/> and
/// <see cref="Position"/> are the ones verified to change what the player hears. Every parameter
/// reaches the client, but whether it has an effect depends on the operator stack behind the sound.
/// </param>
/// <param name="value">Value to send.</param>
public void SetParam(string name, float value) => NativeAPI.SoundEventSetFloat(Handle, name, value);

/// <inheritdoc cref="SetParam(string,float)"/>
public void SetParam(string name, int value) => NativeAPI.SoundEventSetInt(Handle, name, value);

/// <inheritdoc cref="SetParam(string,float)"/>
public void SetParam(string name, Vector value) => NativeAPI.SoundEventSetVector(Handle, name, value.Handle);

/// <summary>
/// Emits the sound to the given players.
/// </summary>
/// <returns>The guid the sound was started with, which <see cref="Stop"/> takes.</returns>
public int Emit(RecipientFilter recipients) =>
NativeAPI.SoundEventEmit(Handle, SourceEntityIndex, recipients.GetRecipientMask());

/// <inheritdoc cref="Emit"/>
public int EmitToAll()
{
var recipients = new RecipientFilter();
recipients.AddAllPlayers();

return Emit(recipients);
}

/// <inheritdoc cref="Emit"/>
public int EmitTo(CCSPlayerController player) => Emit(new RecipientFilter(player));

/// <summary>
/// Stops a sound started by <see cref="Emit"/>, using the guid it returned.
/// </summary>
public static void Stop(int guid, RecipientFilter? recipients = null)
{
if (recipients == null)
{
recipients = new RecipientFilter();
recipients.AddAllPlayers();
}

NativeAPI.SoundEventStop(guid, recipients.GetRecipientMask());
}

private void ReleaseUnmanagedResources()
{
if (_disposed) return;

_disposed = true;

// The finalizer runs off the game thread, where natives cannot be called.
var handle = Handle;
Server.NextFrame(() => NativeAPI.SoundEventRelease(handle));
}

public void Dispose()
{
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}

~SoundEvent()
{
ReleaseUnmanagedResources();
}
}
Loading