Compare commits

...

5 Commits

Author SHA1 Message Date
Michael Wilson
44e3f2240c chore: license updates (#199) 2023-12-14 11:03:18 +10:00
BuSheeZy
8af219e7a8 CHANGE: visual adjustments (#198) 2023-12-14 10:44:48 +10:00
Poggu
bff04e7795 Add voice manager (ability to override voice chat / mute players) (#179)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
Co-authored-by: roflmuffin <shortguy014@gmail.com>
2023-12-12 17:46:45 +10:00
Michael Wilson
d495ac6230 chore: bump version 2023-12-12 10:55:55 +10:00
roflmuffin
f78abf0c81 fix: fallback to en language files for invariant mode 2023-12-12 10:51:38 +10:00
29 changed files with 678 additions and 16 deletions

View File

@@ -22,6 +22,22 @@ saul/demofile-net, https://github.com/saul/demofile-net/blob/main/LICENSE:
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
neverlosecc/source2gen, https://github.com/neverlosecc/source2gen
source2gen - Source2 games SDK generator
Copyright 2023 neverlosecc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Source2ZE/CS2Fixes:
alliedmodders/sourcemod:
Source-Python-Dev-Team/Source.Python:

View File

@@ -75,6 +75,7 @@ SET(SOURCE_FILES
src/scripting/natives/natives_memory.cpp
src/scripting/natives/natives_schema.cpp
src/scripting/natives/natives_entities.cpp
src/scripting/natives/natives_voice.cpp
src/core/managers/entity_manager.cpp
src/core/managers/entity_manager.h
src/core/managers/chat_manager.cpp
@@ -83,6 +84,8 @@ SET(SOURCE_FILES
src/core/managers/server_manager.h
src/scripting/natives/natives_server.cpp
libraries/nlohmann/json.hpp
src/core/managers/voice_manager.cpp
src/core/managers/voice_manager.h
src/scripting/natives/natives_dynamichooks.cpp
)

View File

@@ -128,3 +128,4 @@ Build
```bash
cmake --build . --config Debug
```

View File

@@ -0,0 +1,5 @@
[!INCLUDE [WithVoiceOverrides](../../examples/WithVoiceOverrides/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithVoiceOverrides" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithVoiceOverrides/WithVoiceOverridesPlugin.cs)]

View File

@@ -15,5 +15,7 @@ items:
href: WithDatabase.md
- name: Translations
href: WithTranslations.md
- name: Voice Overrides
href: WithVoiceOverrides.md
- name: Warcraft Plugin
href: WarcraftPlugin.md

View File

@@ -14,7 +14,7 @@ description: Write Counter-Strike 2 server plugins in C#.
<span>CounterStrikeSharp is a simpler way to write CS2 server plugins.</span>
<div>
<a href="docs/guides/getting-started.md" class="btn btn-primary btn-lg fw-bold my-5">Get Started <i class="bi bi-arrow-right"></a>
<a href="https://github.com/roflmuffin/CounterStrikeSharp/releases/latest" class="btn btn-primary btn-lg fw-bold my-5">Download <i class="bi bi-arrow-right"></a>
<a href="https://github.com/roflmuffin/CounterStrikeSharp/releases/latest" class="btn btn-secondary btn-lg fw-bold my-5">Download <i class="bi bi-download"></a>
</div>
</div>

View File

@@ -0,0 +1,2 @@
# With Voice Overrides
Provides examples how to manipulate player voice flags & listening overrides to prevent certain players from hearing others.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,79 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using Microsoft.Extensions.Logging;
namespace WithVoiceOverrides;
[MinimumApiVersion(80)]
public class WithVoiceOverridesPlugin : BasePlugin
{
public override string ModuleName => "Example: With Voice Overrides";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A plugin that manipulates voice flags";
[ConsoleCommand("css_hearall")]
public void OnHearAllCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller is null) return;
if (caller.VoiceFlags.HasFlag(VoiceFlags.ListenAll))
{
caller.VoiceFlags = VoiceFlags.Normal;
command.ReplyToCommand("Voice set back to default");
}
else
{
caller.VoiceFlags = VoiceFlags.ListenAll;
command.ReplyToCommand("Can hear both teams");
}
}
[ConsoleCommand("css_muteself")]
public void OnMuteSelfCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller is null) return;
if (caller.VoiceFlags.HasFlag(VoiceFlags.Muted))
{
caller.VoiceFlags = VoiceFlags.Normal;
command.ReplyToCommand("Unmuted yourself");
}
else
{
caller.VoiceFlags = VoiceFlags.Muted;
command.ReplyToCommand("Muted yourself");
}
}
[ConsoleCommand("css_muteothers")]
[CommandHelper(minArgs: 1, usage: "[target]")]
public void OnMuteOthersCommand(CCSPlayerController? caller, CommandInfo command)
{
if (caller is null) return;
var targetResult = command.GetArgTargetResult(1);
foreach (var player in targetResult.Players)
{
if (player == caller) continue;
var existingOverride = caller.GetListenOverride(player);
if (existingOverride == ListenOverride.Mute)
{
caller.SetListenOverride(player, ListenOverride.Default);
command.ReplyToCommand($"Now hearing {player.PlayerName}");
}
else
{
caller.SetListenOverride(player, ListenOverride.Mute);
command.ReplyToCommand($"Muted {player.PlayerName}");
}
}
}
}

View File

@@ -34,6 +34,11 @@ public class TranslationTests
{
Assert.Equal("This is the english translation", _localizer["test.translation"]);
}
using (new WithTemporaryCulture(CultureInfo.InvariantCulture))
{
Assert.Equal("This is the english translation", _localizer["test.translation"]);
}
using (new WithTemporaryCulture(CultureInfo.GetCultureInfo("en-US")))
{

View File

@@ -1311,5 +1311,51 @@ namespace CounterStrikeSharp.API.Core
return (bool)ScriptContext.GlobalScriptContext.GetResult(typeof(bool));
}
}
public static void SetClientListening(IntPtr receiver, IntPtr sender, uint listen){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(receiver);
ScriptContext.GlobalScriptContext.Push(sender);
ScriptContext.GlobalScriptContext.Push(listen);
ScriptContext.GlobalScriptContext.SetIdentifier(0xD38BEE77);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static ListenOverride GetClientListening(IntPtr receiver, IntPtr sender){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(receiver);
ScriptContext.GlobalScriptContext.Push(sender);
ScriptContext.GlobalScriptContext.SetIdentifier(0xE95644E3);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (ListenOverride)ScriptContext.GlobalScriptContext.GetResult(typeof(ListenOverride));
}
}
public static void SetClientVoiceFlags(IntPtr client, uint flags){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(client);
ScriptContext.GlobalScriptContext.Push(flags);
ScriptContext.GlobalScriptContext.SetIdentifier(0x48EB2FC8);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static uint GetClientVoiceFlags(IntPtr client){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(client);
ScriptContext.GlobalScriptContext.SetIdentifier(0x9685205C);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (uint)ScriptContext.GlobalScriptContext.GetResult(typeof(uint));
}
}
}
}

View File

@@ -104,7 +104,7 @@ namespace CounterStrikeSharp.API.Core
info.ReplyToCommand(
" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison, source2gen and CS2Fixes.\n" +
" See ACKNOWLEDGEMENTS.md for more information.\n" +
" Current API Version: " + currentVersion, true);
return;

View File

@@ -150,9 +150,18 @@ namespace CounterStrikeSharp.API.Core
.FirstOrDefault(x => x.Name == ServerLanguage);
if (serverCulture == null)
{
_logger.LogWarning("Server Language \"{ServerLanguage}\" is not supported, falling back to \"en\"", ServerLanguage);
serverCulture = new CultureInfo("en");
_coreConfig.ServerLanguage = "en";
try
{
_logger.LogWarning("Server Language \"{ServerLanguage}\" is not supported, falling back to \"en\"",
ServerLanguage);
_coreConfig.ServerLanguage = "en";
serverCulture = new CultureInfo("en");
}
catch (Exception)
{
_logger.LogWarning("Server is running in invariant mode, translations will not be available.");
serverCulture = CultureInfo.InvariantCulture;
}
}
CultureInfo.DefaultThreadCurrentUICulture = serverCulture;

View File

@@ -202,6 +202,21 @@ public partial class CCSPlayerController
public void ExecuteClientCommand(string command) => NativeAPI.IssueClientCommand(Slot, command);
/// <summary>
/// Overrides who a player can hear in voice chat.
/// </summary>
/// <param name="sender">Player talking in the voice chat</param>
/// <param name="override">Whether the talker should be heard</param>
public void SetListenOverride(CCSPlayerController sender, ListenOverride @override)
{
NativeAPI.SetClientListening(Handle, sender.Handle, (Byte)@override);
}
public ListenOverride GetListenOverride(CCSPlayerController sender)
{
return NativeAPI.GetClientListening(Handle, sender.Handle);
}
public int Slot => (int)Index - 1;
/// <summary>
@@ -234,4 +249,16 @@ public partial class CCSPlayerController
return ipAddress;
}
}
/// <summary>
/// Determines how the player interacts with voice chat.
/// </summary>
public VoiceFlags VoiceFlags
{
get => (VoiceFlags)NativeAPI.GetClientVoiceFlags(Handle);
set
{
NativeAPI.SetClientVoiceFlags(Handle, (Byte)value);
}
}
}

View File

@@ -22,6 +22,14 @@ namespace CounterStrikeSharp.API.Core.Translations
}
public string ResourcesPath { get; }
public virtual ConcurrentDictionary<string, string> GetResourceSet(string cultureName)
{
TryLoadResourceSet(cultureName);
_resourcesCache.TryGetValue(cultureName, out ConcurrentDictionary<string, string> resources);
return resources;
}
public virtual ConcurrentDictionary<string, string> GetResourceSet(CultureInfo culture, bool tryParents)
{
@@ -53,6 +61,21 @@ namespace CounterStrikeSharp.API.Core.Translations
return resources;
}
}
public string GetFallbackString(string name)
{
GetResourceSet("en");
if (_resourcesCache.ContainsKey("en"))
{
if (_resourcesCache["en"].TryGetValue(name, out string value))
{
return value;
}
}
return null;
}
public virtual string GetString(string name)
{
@@ -93,20 +116,25 @@ namespace CounterStrikeSharp.API.Core.Translations
? value
: null;
}
private void TryLoadResourceSet(CultureInfo culture)
private void TryLoadResourceSet(string cultureName)
{
if (!_resourcesCache.ContainsKey(culture.Name))
if (!_resourcesCache.ContainsKey(cultureName))
{
var file = Path.Combine(ResourcesPath, $"{culture.Name}.json");
var file = Path.Combine(ResourcesPath, $"{cultureName}.json");
var resources = LoadJsonResources(file);
_resourcesCache.TryAdd(culture.Name,
_resourcesCache.TryAdd(cultureName,
new ConcurrentDictionary<string, string>(resources.ToDictionary(r => r.Key, r => r.Value)));
}
}
private void TryLoadResourceSet(CultureInfo culture)
{
TryLoadResourceSet(culture.Name);
}
private static IDictionary<string, string> LoadJsonResources(string filePath)
{
var resources = new Dictionary<string, string>();

View File

@@ -64,6 +64,12 @@ public class JsonStringLocalizer : IStringLocalizer
var result = _resourceManager.GetString(name, culture);
// Fallback to en if running in invariant mode.
if (result == null && culture.Equals(CultureInfo.InvariantCulture))
{
result = _resourceManager.GetFallbackString(name);
}
// Fallback to the default culture (en-US) if the resource is not found for the current culture.
if (result == null && !culture.Equals(CultureInfo.DefaultThreadCurrentUICulture))
{

View File

@@ -0,0 +1,36 @@
/*
* 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/>. *
*/
namespace CounterStrikeSharp.API
{
[Flags]
public enum VoiceFlags : Byte
{
Normal = 0,
Muted = (1 << 0),
All = (1 << 1),
ListenAll = (1 << 2),
Team = (1 << 3),
ListenTeam = (1 << 4),
}
public enum ListenOverride
{
Default = 0,
Mute,
Hear
}
}

View File

@@ -30,6 +30,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CounterStrikeSharp.API.Test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithTranslations", "..\examples\WithTranslations\WithTranslations.csproj", "{BB44E08E-CCA8-4E22-A132-11B2F69D1890}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithVoiceOverrides", "..\examples\WithVoiceOverrides\WithVoiceOverrides.csproj", "{6FA3107D-42AF-42A0-BF51-2230D13268B5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -92,6 +94,10 @@ Global
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB44E08E-CCA8-4E22-A132-11B2F69D1890}.Release|Any CPU.Build.0 = Release|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FA3107D-42AF-42A0-BF51-2230D13268B5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
@@ -104,5 +110,6 @@ Global
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{31EABE0B-871F-497B-BF36-37FFC6FAD15F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{BB44E08E-CCA8-4E22-A132-11B2F69D1890} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{6FA3107D-42AF-42A0-BF51-2230D13268B5} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
EndGlobalSection
EndGlobal

View File

@@ -1,3 +1,12 @@
// Copyright (C) 2023 neverlosecc
// See end of file for extended copyright information.
/**
* =============================================================================
* Source2Gen
* Copyright (C) 2023 neverlose (https://github.com/neverlosecc/source2gen)
* =============================================================================
**/
#pragma once
#include <vector>
@@ -392,3 +401,18 @@ class CSchemaSystem
return CALL_VIRTUAL(CSchemaSystemTypeScope*, 13, this, m_module_name, nullptr);
}
};
// source2gen - Source2 games SDK generator
// Copyright 2023 neverlosecc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

View File

@@ -20,6 +20,7 @@
#include "interfaces/cs2_interfaces.h"
#include "core/managers/entity_manager.h"
#include "core/managers/server_manager.h"
#include "core/managers/voice_manager.h"
#include <public/game/server/iplayerinfo.h>
#include <public/entity2/entitysystem.h>
@@ -77,6 +78,7 @@ ConCommandManager conCommandManager;
EntityManager entityManager;
ChatManager chatManager;
ServerManager serverManager;
VoiceManager voiceManager;
bool gameLoopInitialized = false;
GetLegacyGameEventListener_t* GetLegacyGameEventListener = nullptr;

View File

@@ -53,6 +53,7 @@ class HookManager;
class EntityManager;
class ChatManager;
class ServerManager;
class VoiceManager;
class CCoreConfig;
class CGameConfig;
@@ -98,6 +99,7 @@ extern TimerSystem timerSystem;
extern ChatCommands chatCommands;
extern ChatManager chatManager;
extern ServerManager serverManager;
extern VoiceManager voiceManager;
extern HookManager hookManager;
extern SourceHook::ISourceHook *source_hook;

View File

@@ -31,6 +31,7 @@
#include "core/managers/player_manager.h"
#include "core/managers/con_command_manager.h"
#include "core/managers/voice_manager.h"
#include <public/eiface.h>
#include <public/inetchannelinfo.h>
@@ -273,7 +274,7 @@ void PlayerManager::OnLevelEnd()
{
CSSHARP_CORE_TRACE("[PlayerManager][OnLevelEnd]");
for (int i = 0; i <= m_max_clients; i++) {
for (int i = 0; i <= MaxClients(); i++) {
if (m_players[i].IsConnected()) {
OnClientDisconnect(m_players[i].m_slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, m_players[i].GetName(), 0,
m_players[i].GetIpAddress());
@@ -290,6 +291,8 @@ void PlayerManager::OnClientCommand(CPlayerSlot slot, const CCommand& args) cons
const char* cmd = args.Arg(0);
globals::voiceManager.OnClientCommand(slot, args);
auto result = globals::conCommandManager.ExecuteCommandCallbacks(
cmd, CCommandContext(CommandTarget_t::CT_NO_TARGET, slot), args, HookMode::Pre);
@@ -302,11 +305,11 @@ int PlayerManager::ListenClient() const { return m_listen_client; }
int PlayerManager::NumPlayers() const { return m_player_count; }
int PlayerManager::MaxClients() const { return m_max_clients; }
int PlayerManager::MaxClients() const { return globals::getGlobalVars()->maxClients; }
CPlayer* PlayerManager::GetPlayerBySlot(int client) const
{
if (client > m_max_clients || client < 0) {
if (client > MaxClients() || client < 0) {
return nullptr;
}
@@ -426,7 +429,6 @@ INetChannelInfo* CPlayer::GetNetInfo() const { return globals::engine->GetPlayer
PlayerManager::PlayerManager()
{
m_max_clients = 64;
m_players = new CPlayer[66];
m_player_count = 0;
m_user_id_lookup = new int[USHRT_MAX + 1];
@@ -441,7 +443,7 @@ void PlayerManager::RunAuthChecks()
m_last_auth_check_time = globals::timerSystem.GetTickedTime();
for (int i = 0; i <= m_max_clients; i++) {
for (int i = 0; i <= MaxClients(); i++) {
if (m_players[i].IsConnected()) {
if (m_players[i].IsAuthorized() || m_players[i].IsFakeClient())
continue;
@@ -532,6 +534,23 @@ float CPlayer::GetLatency() const
return GetNetInfo()->GetLatency(FLOW_INCOMING) + GetNetInfo()->GetLatency(FLOW_OUTGOING);
}
void CPlayer::SetListen(CPlayerSlot slot, ListenOverride listen)
{
m_listenMap[slot.Get()] = listen;
}
void CPlayer::SetVoiceFlags(VoiceFlag_t flags)
{
m_voiceFlag = flags;
}
VoiceFlag_t CPlayer::GetVoiceFlags() { return m_voiceFlag; }
ListenOverride CPlayer::GetListen(CPlayerSlot slot) const
{
return m_listenMap[slot.Get()];
}
void CPlayer::Connect()
{
if (m_is_in_game) {
@@ -551,6 +570,9 @@ void CPlayer::Disconnect()
m_user_id = -1;
m_is_authorized = false;
m_ip_address.clear();
m_selfMutes->ClearAll();
memset(m_listenMap, 0, sizeof m_listenMap);
m_voiceFlag = 0;
}
QAngle CPlayer::GetAbsAngles() const { return m_info->GetAbsAngles(); }

View File

@@ -45,6 +45,25 @@ namespace counterstrikesharp {
class ScriptCallback;
class CBaseEntityWrapper;
enum ListenOverride
{
Listen_Default = 0,
Listen_Mute,
Listen_Hear
};
enum VoiceFlagValue
{
Speak_Normal = 0,
Speak_Muted = 1 << 0,
Speak_All = 1 << 1,
Speak_ListenAll = 1 << 2,
Speak_Team = 1 << 3,
Speak_ListenTeam = 1 << 4,
};
typedef uint8_t VoiceFlag_t;
class CPlayer {
friend class PlayerManager;
@@ -92,6 +111,10 @@ public:
int GetUserId() const;
float GetTimeConnected() const;
float GetLatency() const;
void SetListen(CPlayerSlot slot, ListenOverride listen);
void SetVoiceFlags(VoiceFlag_t flags);
VoiceFlag_t GetVoiceFlags();
ListenOverride GetListen(CPlayerSlot slot) const;
public:
std::string m_name;
@@ -105,6 +128,9 @@ public:
CPlayerSlot m_slot = CPlayerSlot(-1);
const CSteamID* m_steamId;
std::string m_ip_address;
ListenOverride m_listenMap[66] = {};
VoiceFlag_t m_voiceFlag = 0;
CPlayerBitVec m_selfMutes[64] = {};
void SetName(const char *name);
INetChannelInfo *GetNetInfo() const;
};

View File

@@ -0,0 +1,129 @@
/*
* 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 "core/managers/voice_manager.h"
#include "core/managers/player_manager.h"
#include <public/eiface.h>
#include "scripting/callback_manager.h"
#include <schema.h>
#include <entity2/entitysystem.h>
SH_DECL_HOOK3(IVEngineServer2, SetClientListening, SH_NOATTRIB, 0, bool, CPlayerSlot, CPlayerSlot,
bool);
namespace counterstrikesharp {
VoiceManager::VoiceManager() {}
VoiceManager::~VoiceManager() {}
void VoiceManager::OnAllInitialized()
{
SH_ADD_HOOK(IVEngineServer2, SetClientListening, globals::engine,
SH_MEMBER(this, &VoiceManager::SetClientListening), false);
}
void VoiceManager::OnShutdown()
{
SH_REMOVE_HOOK(IVEngineServer2, SetClientListening, globals::engine,
SH_MEMBER(this, &VoiceManager::SetClientListening), false);
}
bool VoiceManager::SetClientListening(CPlayerSlot iReceiver, CPlayerSlot iSender, bool bListen)
{
auto pReceiver = globals::playerManager.GetPlayerBySlot(iReceiver.Get());
auto pSender = globals::playerManager.GetPlayerBySlot(iSender.Get());
if (pReceiver && pSender)
{
auto listenOverride = pReceiver->GetListen(iSender);
auto senderFlags = pSender->GetVoiceFlags();
auto receiverFlags = pReceiver->GetVoiceFlags();
if (pReceiver->m_selfMutes->Get(iSender.Get()))
{
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, false));
}
if (senderFlags & Speak_Muted)
{
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, false));
}
if (listenOverride == Listen_Mute)
{
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, false));
} else if (listenOverride == Listen_Hear) {
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, true));
}
if ((senderFlags & Speak_All) || (receiverFlags & Speak_ListenAll)) {
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen, &IVEngineServer2::SetClientListening,
(iReceiver, iSender, true));
}
if ((senderFlags & Speak_Team) || (receiverFlags & Speak_ListenTeam))
{
static auto classKey = hash_32_fnv1a_const("CBaseEntity");
static auto memberKey = hash_32_fnv1a_const("m_iTeamNum");
const static auto m_key = schema::GetOffset("CBaseEntity", classKey, "m_iTeamNum", memberKey);
auto receiverController = globals::entitySystem->GetBaseEntity(CEntityIndex(iReceiver.Get() + 1));
auto senderController = globals::entitySystem->GetBaseEntity(CEntityIndex(iSender.Get() + 1));
if (receiverController && senderController)
{
auto receiverTeam = *reinterpret_cast<std::add_pointer_t<unsigned int>>(
(uintptr_t)(receiverController) + m_key.offset);
auto senderTeam = *reinterpret_cast<std::add_pointer_t<unsigned int>>(
(uintptr_t)(senderController) + m_key.offset);
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, bListen,
&IVEngineServer2::SetClientListening,
(iReceiver, iSender, receiverTeam == senderTeam));
}
}
}
RETURN_META_VALUE(MRES_IGNORED, bListen);
}
void VoiceManager::OnClientCommand(CPlayerSlot slot, const CCommand& args)
{
auto pPlayer = globals::playerManager.GetPlayerBySlot(slot.Get());
if (!pPlayer)
return;
if (args.ArgC() > 1 && stricmp(args.Arg(0), "vban") == 0)
{
// clients just refuse to send vban for indexes over 32 and all 4 fields are just the same number, so we only get the first one
//for (int i = 1; (i < args.ArgC()) && (i < 3); i++) {
unsigned int mask = 0;
sscanf(args.Arg(1), "%x", &mask);
pPlayer->m_selfMutes->SetDWord(0, mask);
//}
}
}
} // namespace counterstrikesharp

View File

@@ -0,0 +1,38 @@
/*
* 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/>. *
*/
#pragma once
#include "core/globals.h"
#include "core/global_listener.h"
#include "scripting/script_engine.h"
namespace counterstrikesharp {
class ScriptCallback;
class VoiceManager : public GlobalClass
{
public:
VoiceManager();
~VoiceManager();
void OnAllInitialized() override;
void OnShutdown() override;
bool SetClientListening(CPlayerSlot iReceiver, CPlayerSlot iSender, bool bListen);
void OnClientCommand(CPlayerSlot slot, const CCommand& args);
private:
};
} // namespace counterstrikesharp

View File

@@ -0,0 +1,129 @@
/*
* 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"
#include "core/managers/player_manager.h"
#include <public/entity2/entitysystem.h>
namespace counterstrikesharp {
void SetClientListening(ScriptContext& scriptContext)
{
auto receiver = scriptContext.GetArgument<CBaseEntity*>(0);
auto sender = scriptContext.GetArgument<CBaseEntity*>(1);
auto listen = scriptContext.GetArgument<ListenOverride>(2);
if (!receiver) {
scriptContext.ThrowNativeError("Receiver is a null pointer");
return;
}
if (!sender) {
scriptContext.ThrowNativeError("Sender is a null pointer");
return;
}
auto iSenderSlot = sender->GetEntityIndex().Get() - 1;
if (iSenderSlot < 0 || iSenderSlot >= globals::getGlobalVars()->maxClients)
scriptContext.ThrowNativeError("Invalid sender");
auto pPlayer = globals::playerManager.GetPlayerBySlot(receiver->GetEntityIndex().Get() - 1);
if (pPlayer == nullptr) {
scriptContext.ThrowNativeError("Invalid receiver");
return;
}
pPlayer->SetListen(iSenderSlot, listen);
}
ListenOverride GetClientListening(ScriptContext& scriptContext)
{
auto receiver = scriptContext.GetArgument<CBaseEntity*>(0);
auto sender = scriptContext.GetArgument<CBaseEntity*>(1);
if (!receiver) {
scriptContext.ThrowNativeError("Receiver is a null pointer");
return Listen_Default;
}
if (!sender) {
scriptContext.ThrowNativeError("Sender is a null pointer");
return Listen_Default;
}
auto iSenderSlot = sender->GetEntityIndex().Get() - 1;
if (iSenderSlot < 0 || iSenderSlot >= globals::getGlobalVars()->maxClients)
scriptContext.ThrowNativeError("Invalid sender");
auto pPlayer = globals::playerManager.GetPlayerBySlot(receiver->GetEntityIndex().Get() - 1);
if (pPlayer == nullptr) {
scriptContext.ThrowNativeError("Invalid receiver");
return Listen_Default;
}
return pPlayer->GetListen(iSenderSlot);
}
void SetClientVoiceFlags(ScriptContext& scriptContext)
{
auto client = scriptContext.GetArgument<CBaseEntity*>(0);
auto flags = scriptContext.GetArgument<VoiceFlag_t>(1);
if (!client) {
scriptContext.ThrowNativeError("Receiver is a null pointer");
return;
}
auto pPlayer = globals::playerManager.GetPlayerBySlot(client->GetEntityIndex().Get() - 1);
if (pPlayer == nullptr) {
scriptContext.ThrowNativeError("Invalid receiver");
return;
}
pPlayer->SetVoiceFlags(flags);
}
VoiceFlag_t GetClientVoiceFlags(ScriptContext& scriptContext)
{
auto client = scriptContext.GetArgument<CBaseEntity*>(0);
if (!client) {
scriptContext.ThrowNativeError("Receiver is a null pointer");
return VoiceFlag_t{};
}
auto pPlayer = globals::playerManager.GetPlayerBySlot(client->GetEntityIndex().Get() - 1);
if (pPlayer == nullptr) {
scriptContext.ThrowNativeError("Invalid receiver");
}
return pPlayer->GetVoiceFlags();
}
REGISTER_NATIVES(voice, {
ScriptEngine::RegisterNativeHandler("SET_CLIENT_LISTENING", SetClientListening);
ScriptEngine::RegisterNativeHandler("GET_CLIENT_LISTENING", GetClientListening);
ScriptEngine::RegisterNativeHandler("SET_CLIENT_VOICE_FLAGS", SetClientVoiceFlags);
ScriptEngine::RegisterNativeHandler("GET_CLIENT_VOICE_FLAGS", GetClientVoiceFlags);
})
} // namespace counterstrikesharp

View File

@@ -0,0 +1,4 @@
SET_CLIENT_LISTENING: receiver:pointer, sender:pointer, listen:uint -> void
GET_CLIENT_LISTENING: receiver:pointer, sender:pointer -> ListenOverride
SET_CLIENT_VOICE_FLAGS: client:pointer, flags:uint -> void
GET_CLIENT_VOICE_FLAGS: client:pointer -> uint

View File

@@ -63,6 +63,8 @@ public class Mapping
return "[CastFrom(typeof(ulong))]SteamID";
case "HookMode":
return "HookMode";
case "ListenOverride":
return "ListenOverride";
case "DataType_t":
return "DataType";
case "any":