v1.2.3.b4

- Curses are now tracked at all times and cleared when a player leaves, so a curse can no longer stick to whoever joins into that player slot afterwards
- Wallhack and Nightmare no longer shows up for the whole server during the round start skill reveal
- One skill throwing during round cleanup no longer cancels the rest of the cleanup or leaves entity kills suppressed for the rest of the map
- Fixed the TakeAmmo crash on map change while restoring weapon reserve ammo
- Friendly fire reduction now applies to BlastShot, DeathBomb, ExplodingBarrel, ExplosiveShot and ToxicSmoke
- FireRain molotovs now land around the decoy instead of on nearby rooftops, and the fire keeps its owner and team so friendly fire rules apply to it
- Teammates can no longer shoot the chickens spawned by HealingChicken
- Fortnite wall now spawns with its model and health set up front, so it takes the configured amount of damage instead of breaking on the first bullet
- JetKick, CarefulBullets and WildThrow now clean up after their target disconnects
This commit is contained in:
ByDexter 2026-08-03 01:25:04 +03:00
parent 20f1ba212a
commit db7108517a
43 changed files with 449 additions and 208 deletions

View file

@ -31,7 +31,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
public override string ModuleVersion => "1.2.3.b3";
public override string ModuleVersion => "1.2.3.b4";
public override void Load(bool hotReload)
{
@ -125,7 +125,7 @@ namespace src
SkillsUsedThisMap.TryAdd(skill, 0);
}
if (SkillUtils.CurseLimitEnabled && SkillUtils.IsCurseSkill(skill))
if (SkillUtils.IsCurseSkill(skill))
{
if (methodName == "DisableSkill" && param?.Length > 0 && param[0] is CCSPlayerController curser && curser.IsValid)
SkillUtils.ReleaseCurse(curser.Index);

View file

@ -1,4 +1,4 @@
{
{
"none": "Keine",
"none_desc": "Du hast keine Fertigkeit",
@ -276,6 +276,7 @@
"jetkick": "Jetkick",
"jetkick_desc": "Wähle einen Spieler für einen Jetkick",
"jetkick_select_info": "Wähle einen Spieler für den Jetkick:",
"jetkick_player_info": "Spieler '{0}' wurde wegge-kickt.",
"jetkick_enemy_info": "Jeder Schuss stößt dich nach hinten",
"jetkick_disable_info": "Dein Rückstoß ist wieder normal.",

View file

@ -1,4 +1,4 @@
{
{
"none": "None",
"none_desc": "You have no skill",
@ -269,6 +269,7 @@
"jetkick": "Jetkick",
"jetkick_desc": "Select a player to jetkick",
"jetkick_select_info": "Select a player to jetkick:",
"jetkick_player_info": "Player '{0}' has been jetkicked.",
"jetkick_enemy_info": "Every shot knocks you backwards",
"jetkick_disable_info": "Your recoil is back to normal.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Aucune",
"none_desc": "Vous n’avez aucune compétence",
@ -276,6 +276,7 @@
"jetkick": "Coup de pied propulsé",
"jetkick_desc": "Sélectionnez un joueur pour lui donner un coup de pied propulsé",
"jetkick_select_info": "Sélectionnez un joueur à propulser :",
"jetkick_player_info": "Le joueur '{0}' a reçu un coup de pied propulsé.",
"jetkick_enemy_info": "Chaque tir vous repousse vers l'arrière",
"jetkick_disable_info": "Votre recul est revenu à la normale.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Brak",
"none_desc": "Nie posiadasz żadnej supermocy",
@ -276,6 +276,7 @@
"jetkick": "Odrzutowy Kop",
"jetkick_desc": "Wybierz gracza, aby użyć na nim odrzutowego kopa",
"jetkick_select_info": "Wybierz gracza do odrzutowego kopa:",
"jetkick_player_info": "Gracz '{0}' został odrzucony kopem.",
"jetkick_enemy_info": "Każdy strzał mocno odrzuca Cię do tyłu",
"jetkick_disable_info": "Twój odrzut wrócił do normy.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Nenhuma",
"none_desc": "Você não tem nenhuma habilidade",
@ -276,6 +276,7 @@
"jetkick": "Chute a Jato",
"jetkick_desc": "Selecione um jogador para aplicar um chute a jato",
"jetkick_select_info": "Selecione um jogador para o chute a jato:",
"jetkick_player_info": "O jogador '{0}' recebeu um chute a jato.",
"jetkick_enemy_info": "Cada tiro empurra você para trás",
"jetkick_disable_info": "Seu recuo voltou ao normal.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Нет навыка",
"none_desc": "У вас нет навыка",
@ -276,6 +276,7 @@
"jetkick": "Реактивный удар",
"jetkick_desc": "Выберите игрока для реактивного удара",
"jetkick_select_info": "Выберите игрока для реактивного удара:",
"jetkick_player_info": "Игрок '{0}' получил реактивный удар.",
"jetkick_enemy_info": "Каждый выстрел отбрасывает вас назад",
"jetkick_disable_info": "Ваша отдача вернулась к норме.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Yok",
"none_desc": "Hiçbir yeteneğin yok",
@ -52,13 +52,13 @@
"behind_desc2": "Baş döndürme şansın: %{0}",
"berserker": "Berserker",
"berserker_desc": "Canın azaldıkça daha fazla hasar verir ve daha hızlı hareket edersin",
"berserker_desc": "Canın azaldıkça hasarın artar ve daha hızlanırsın",
"blademaster": "Kılıç Ustası",
"blademaster_desc": "Bıçak tutarken mermileri sektirme şansın artar",
"blastshot": "Patlama Atışı",
"blastshot_desc": "MP5 ile Attack2 tuşuna basarak bir HE bombası ateşle",
"blastshot_desc": "MP5 ile Sağ Tık'a basarak El bombası fırlat",
"bunnyhop": "Tavşan",
"bunnyhop_desc": "Otomatik \"BunnyHop\" yaparsın",
@ -156,14 +156,14 @@
"fastreload_desc": "[css_useSkill] tuşuna basarak şarjörünü hızlıca değiştir",
"firerain": "Ateş Yağmuru",
"firerain_desc": "Molotof yağmuru çağırmak için bir yem at",
"firerain_desc": "Molotof yağdırmak için bir decoy at",
"flash": "Flash",
"flash_desc": "Raund başında rastgele oyuncu hızı",
"flash_desc2": "Hızın: {0}x",
"flashlight": "El Feneri",
"flashlight_desc": "El fenerini açıp kapatmak için [css_useSkill] tuşuna tıkla. Işığı düşmanları kör edebilir",
"flashlight_desc": "[css_useSkill] tuşuna basarak El fenerini açarsın/kaparsın (Işığı düşmanları kör edebilir)",
"fortnite": "Fortnite",
"fortnite_desc": "[css_useSkill] tuşuna basarak barikat kur",
@ -223,7 +223,7 @@
"healingsmoke_desc": "Sis bombaların iyileştirir",
"hermit": "Münzevi",
"hermit_desc": "Öldürdüğünde mermi ve canın yenilenir",
"hermit_desc": "Birini öldürünce mermin ve canın yenilenir",
"holyhandgrenade": "Kutsal El Bombası",
"holyhandgrenade_desc": "El bomban iki kat hasara ve menzile sahip",
@ -267,9 +267,10 @@
"jester_on": "AÇIK",
"jester_off": "KAPALI",
"jetkick": "Jet Tekmesi",
"jetkick_desc": "Jet tekmesi yapmak için bir oyuncu seç",
"jetkick_player_info": "'{0}' adlı oyuncuya jet tekmesi yapıldı.",
"jetkick": "Jetkick",
"jetkick_desc": "Seçtiğin bir rakibin her atışı onu geriye iter",
"jetkick_select_info": "Jetkick uygulanacak oyuncu:",
"jetkick_player_info": "'{0}' adlı oyuncuya jetkick uygulandı.",
"jetkick_enemy_info": "Her atış seni geriye doğru iter",
"jetkick_disable_info": "Geri tepmen normale döndü.",
@ -297,7 +298,7 @@
"knockback_desc": "Havadayken ateş etmek seni geriye savurur",
"lastgasp": "Son Nefes",
"lastgasp_desc": "Öldükten sonra seni öldüren düşmana hasar verirsin",
"lastgasp_desc": "Öldükten sonra seni öldürene hasar verirsin",
"lifeswap": "Can Takası",
"lifeswap_desc": "Can değerlerini takas etmek için bir oyuncu seç",
@ -340,7 +341,7 @@
"deathbomb_desc": "Öldüğünde patlayarak etraftaki oyuncuları öldürürsün",
"deathbomb_explosion": "BOOM!!!",
"demoneye": "Şeytan Gözü",
"demoneye": "Şeytani Göz",
"demoneye_desc": "Baktığın tüm düşmanlara hasar verirsin",
"nightmare": "Kabus",
@ -489,14 +490,14 @@
"swapposition": "Yer Değiştirme",
"swapposition_desc": "[css_useSkill] tuşuna basarak vurduğun düşmanla yer değiştir",
"takeammo": "Mühimmat Al",
"takeammo_desc": "Rastgele bir düşmanın aktif silahının şarjörünü almak için [css_useSkill] tuşuna tıkla",
"takeammo_hud_info1": "Aktif şarjörü olan bir oyuncu bulunamadı.",
"takeammo_hud_info2": "Düşmanın şarjörü yok.",
"takeammo_enemy_info": "Bir miktar mühimmat kaybettin.",
"takeammo": "Cephane Hırsızı",
"takeammo_desc": "[css_useSkill] tuşuna basarak rastgele bir düşmanın silahının şarjörünü çalarsın",
"takeammo_hud_info1": "Cephanesi olan bir oyuncu bulunamadı.",
"takeammo_hud_info2": "Düşmanın cephanesi yok.",
"takeammo_enemy_info": "Birisi cephaneni çaldı.",
"teamteleport": "Takıma Işınlanma",
"teamteleport_desc": "Baktığın takım arkadaşına ışınlanmak için [css_useSkill] tuşuna bas",
"teamteleport_desc": "[css_useSkill] tuşuna basarak baktığın takım arkadaşına ışınlanırsın",
"teamteleport_noenemy": "Takım arkadaşı bulunamadı",
"teamteleport_hud_info": "Bulundu: {0}",

View file

@ -206,7 +206,7 @@ namespace src.player
Server.PrintToConsole($"[jRandomSkills] CheckTransmit dying-filter failed: {ex.Message}");
}
DispatchToActiveSkills("CheckTransmit", infoList);
DispatchCheckTransmit([infoList]);
}
PerfLog.Sample("CheckTransmit", perfStart);
}

View file

@ -136,6 +136,39 @@ namespace src.player
}
}
private static void ReleaseCursesTargeting(uint victimIndex)
{
foreach (uint curserIndex in SkillUtils.GetCursersOf(victimIndex))
{
if (curserIndex == victimIndex) continue;
var curser = Utilities.GetPlayerFromIndex((int)curserIndex);
if (curser == null || !curser.IsValid) continue;
if (PlayerManager.GetPlayerByIndex(curserIndex)?.Skill is not Skills curserSkill || curserSkill == Skills.None)
continue;
try
{
Instance.SkillAction(curserSkill.ToString(), "DisableSkill", [curser]);
}
catch (Exception ex)
{
Server.PrintToConsole($"[jRandomSkills] {curserSkill}.DisableSkill failed while releasing curse on leaving player: {(ex.InnerException ?? ex).Message}");
}
}
}
private static void DispatchCheckTransmit(object[] args)
{
var seen = new HashSet<Skills>();
foreach (var p in Instance.SkillPlayer)
{
if (!seen.Add(p.Skill)) continue;
InvokeSkill(p.Skill, "CheckTransmit", args);
}
}
private static void DispatchOnTakeDamage(DynamicHook h, bool post = false)
{
object[] args = [h];
@ -517,6 +550,9 @@ namespace src.player
Instance.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]);
uint leavingIndex = player.Index;
ReleaseCursesTargeting(leavingIndex);
foreach (var skill in SkillData.Skills)
Instance.SkillAction(skill.Skill.ToString(), "PlayerDisconnect", [leavingIndex]);

View file

@ -152,12 +152,24 @@ namespace src.player
// nobody drew now would otherwise never clear state left over from an earlier round.
// Skills that never ran cannot hold state, so they stay out of the sweep.
foreach (var skillName in SkillsUsedThisMap.Keys)
Instance.SkillAction(skillName, "NewRound");
SafeNewRound(skillName);
ActiveSkillsThisRound.Clear();
tickFailuresLogged.Clear();
}
}
private static void SafeNewRound(string skillName)
{
try
{
Instance.SkillAction(skillName, "NewRound");
}
catch (Exception ex)
{
Server.PrintToConsole($"[jRandomSkills] {skillName}.NewRound failed, cleanup continues: {(ex.InnerException ?? ex).Message}");
}
}
public static void OnMapChange()
{
lock (setLock)
@ -168,10 +180,16 @@ namespace src.player
Fortnite.skillInThisRound = false;
EntityManager.SuppressKills = true;
EntityManager.DestroyAllTracked();
foreach (var skill in SkillData.Skills)
Instance.SkillAction(skill.Skill.ToString(), "NewRound");
EntityManager.SuppressKills = false;
try
{
EntityManager.DestroyAllTracked();
foreach (var skill in SkillData.Skills)
SafeNewRound(skill.Skill.ToString());
}
finally
{
EntityManager.SuppressKills = false;
}
ActiveSkillsThisRound.Clear();
SkillsUsedThisMap.Clear();

View file

@ -135,7 +135,7 @@ namespace src.player.skills
heProjectile.DmgRadius = SkillsInfo.GetValue<float>(skillName, "explosionRadius");
if (nades.TryRemove(lastTick, out var source))
heProjectile.Globalname = $"deathbomb_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
heProjectile.Globalname = $"blastshot_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
});
}
@ -150,7 +150,7 @@ namespace src.player.skills
if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("deathbomb_team_")) return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("blastshot_team_")) return;
var parts = nade.Globalname.Split('_');
if (parts.Length < 4) return;
@ -169,33 +169,12 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam)
{
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
if (IsFriendlyFireOff())
{
int teamDamage = (int)param2.Damage;
param2.Damage = 0;
if (teamDamage > 0)
SkillUtils.TakeHealth(victimPawn, teamDamage, owner, KillfeedIcons.Explosion);
return;
}
}
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
}
private static bool IsFriendlyFireOff()
{
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
public class PlayerSkillInfo

View file

@ -234,6 +234,29 @@ namespace src.player.skills
SkillUtils.CloseMenu(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
lock (setLock)
{
Forget(playerIndex);
if (playersToTarget.TryRemove(playerIndex, out uint targetIndex))
Forget(targetIndex);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
}
private static void Forget(uint index)
{
targetPlayers.TryRemove(index, out _);
lastShot.TryRemove(index, out _);
hitPlayer.TryRemove(index, out _);
targetToPlayer.TryRemove(index, out _);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#db6c35", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int damageAfterMiss = 5) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int DamageAfterMiss { get; set; } = damageAfterMiss;

View file

@ -13,12 +13,12 @@ namespace src.player.skills
private static readonly HashSet<string> disabledWeapons =
[
"weapon_ak47", "weapon_m4a4", "weapon_m4a1", "weapon_m4a1_silencer",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg553",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg556",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7",
"weapon_ump45", "weapon_p90", "weapon_mp5sd", "weapon_ssg08",
"weapon_awp", "weapon_scar20", "weapon_g3sg1", "weapon_nova",
"weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249",
"weapon_negev", "weapon_sg556"
"weapon_negev"
];
private const string chickenModelPath = "models/chicken/chicken.vmdl";
private static readonly ConcurrentDictionary<uint, uint> chickens = [];
@ -139,7 +139,8 @@ namespace src.player.skills
chickenModel.CBodyComponent.SceneNode.GetSkeletonInstance().Scale = 1;
Utilities.SetStateChanged(chickenModel, "CBaseEntity", "m_CBodyComponent");
Server.NextFrame(() => {
Server.NextFrame(() =>
{
if (chickenModel == null || !chickenModel.IsValid) return;
chickenModel.AcceptInput("SetScale", chickenModel, chickenModel, "1");
});

View file

@ -123,33 +123,12 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam)
{
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
if (IsFriendlyFireOff())
{
int teamDamage = (int)param2.Damage;
param2.Damage = 0;
if (teamDamage > 0)
SkillUtils.TakeHealth(victimPawn, teamDamage, owner, KillfeedIcons.Explosion);
return;
}
}
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
}
private static bool IsFriendlyFireOff()
{
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
private static bool IsDeadPlayerValid(CCSPlayerController? player)

View file

@ -229,33 +229,12 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam)
{
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
if (IsFriendlyFireOff())
{
int teamDamage = (int)param2.Damage;
param2.Damage = 0;
if (teamDamage > 0)
SkillUtils.TakeHealth(victimPawn, teamDamage, owner, KillfeedIcons.Explosion);
return;
}
}
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
}
private static bool IsFriendlyFireOff()
{
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
public class PlayerSkillInfo

View file

@ -1,7 +1,10 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils;
using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
@ -13,6 +16,13 @@ namespace src.player.skills
private static readonly QAngle angle = new(5, 10, -4);
private static int lastTick = 0;
private static byte pendingTeam = (byte)CsTeam.None;
private static readonly ConcurrentDictionary<int, (byte Team, uint Owner)> nades = [];
public static void NewRound()
{
nades.Clear();
}
public static void LoadSkill()
{
@ -31,10 +41,12 @@ namespace src.player.skills
border: !PlayerManager.GetTickPlayers().Any(p => p.Team == player.Team && p != player) ? "tb" : "t");
}
private static void SpawnExplosion(Vector vector)
private static void SpawnExplosion(Vector vector, CCSPlayerController player)
{
lastTick = Server.TickCount;
SkillUtils.CreateHEGrenadeProjectile(vector, angle, new Vector(0, 0, 0), 0);
pendingTeam = player.TeamNum;
nades.AddOrUpdate(Server.TickCount, (player.TeamNum, player.Index), (_, _) => (player.TeamNum, player.Index));
SkillUtils.CreateHEGrenadeProjectile(vector, angle, new Vector(0, 0, 0), player.TeamNum);
}
public static void OnEntitySpawned(CEntityInstance entity)
@ -44,6 +56,8 @@ namespace src.player.skills
var heProjectile = entity.As<CBaseCSGrenadeProjectile>();
if (heProjectile == null || !heProjectile.IsValid || heProjectile.AbsRotation == null) return;
int spawnTick = Server.TickCount;
Server.NextFrame(() =>
{
if (heProjectile == null || !heProjectile.IsValid) return;
@ -51,13 +65,52 @@ namespace src.player.skills
return;
heProjectile.TicksAtZeroVelocity = 100;
heProjectile.TeamNum = (byte)CsTeam.None;
heProjectile.TeamNum = pendingTeam;
heProjectile.Damage = SkillsInfo.GetValue<float>(skillName, "damage");
heProjectile.DmgRadius = SkillsInfo.GetValue<float>(skillName, "damageRadius");
heProjectile.DetonateTime = 0;
if (nades.TryRemove(spawnTick, out var source))
heProjectile.Globalname = $"explosiveshot_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
});
}
public static void OnTakeDamage(DynamicHook h)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null) return;
var nade = param2.Attacker?.Value;
if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("explosiveshot_team_")) return;
var parts = nade.Globalname.Split('_');
if (parts.Length < 4) return;
if (!int.TryParse(parts[2], out int nadeTeam)) return;
if (!uint.TryParse(parts[3], out uint ownerIndex)) return;
CCSPlayerPawn victimPawn = new(param.Handle);
if (victimPawn.DesignerName != "player") return;
if (victimPawn.Controller?.Value == null) return;
var victim = victimPawn.Controller.Value.As<CCSPlayerController>();
if (victim == null || !victim.IsValid || victim.Index == ownerIndex) return;
if (victimPawn.TeamNum == nadeTeam)
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner != null && !owner.IsValid) owner = null;
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
public static void BulletImpact(EventBulletImpact @event)
@ -73,15 +126,16 @@ namespace src.player.skills
if (playerInfo == null || playerInfo.Skill != skillName) return;
if (Instance.Random.NextDouble() <= playerInfo.SkillChance)
SpawnExplosion(pos);
SpawnExplosion(pos, player);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9c0000", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float damage = 25f, float damageRadius = 210f, float chanceFrom = .15f, float chanceTo = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9c0000", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float damage = 25f, float damageRadius = 210f, float chanceFrom = .15f, float chanceTo = .3f, float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float Damage { get; set; } = damage;
public float DamageRadius { get; set; } = damageRadius;
public float ChanceFrom { get; set; } = chanceFrom;
public float ChanceTo { get; set; } = chanceTo;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
}
}
}

View file

@ -12,6 +12,10 @@ namespace src.player.skills
private const Skills skillName = Skills.FireRain;
private static readonly QAngle angle = new(10, -4, 13);
private static readonly ConcurrentDictionary<uint, byte> decoys = [];
private static int rainTick = -1;
private static CCSPlayerPawn? rainThrower;
private static byte rainTeam = (byte)CsTeam.None;
private static readonly ConcurrentDictionary<uint, (uint ThrowerRaw, byte Team)> rainMolotovs = [];
public static void LoadSkill()
{
@ -21,6 +25,7 @@ namespace src.player.skills
public static void NewRound()
{
KillAllDecoys();
rainMolotovs.Clear();
}
private static void KillAllDecoys()
@ -43,58 +48,79 @@ namespace src.player.skills
return;
const int grenadeCount = 5;
const float spawnHeight = 800.0f;
const float approachDistance = 600.0f;
const float clusterRadius = 130.0f;
const float flightTime = 0.65f;
const float gravity = 800.0f;
const float maxSpawnHeight = 400.0f;
const float clusterRadius = 110.0f;
const float dropSpeed = 250.0f;
float approachAngle = Random.Shared.NextSingle() * MathF.Tau;
float dirX = MathF.Cos(approachAngle);
float dirY = MathF.Sin(approachAngle);
rainTick = Server.TickCount;
rainThrower = pawn;
rainTeam = player.TeamNum;
Vector skyCenter = new(
targetPos.X + dirX * approachDistance,
targetPos.Y + dirY * approachDistance,
targetPos.Z + spawnHeight
);
float dx = targetPos.X - skyCenter.X;
float dy = targetPos.Y - skyCenter.Y;
float dz = targetPos.Z - skyCenter.Z;
float vx = dx / flightTime;
float vy = dy / flightTime;
float vz = (dz + 0.5f * gravity * flightTime * flightTime) / flightTime;
Vector velocity = new(vx, vy, vz);
float yaw = MathF.Atan2(vy, vx) * (180.0f / MathF.PI);
float horizontalSpeed = MathF.Sqrt(vx * vx + vy * vy);
float pitch = -MathF.Atan2(vz, horizontalSpeed) * (180.0f / MathF.PI);
QAngle launchAngle = new(pitch, yaw, 0.0f);
Vector decoyGround = new(targetPos.X, targetPos.Y, targetPos.Z + 8f);
Vector dropVelocity = new(0, 0, -dropSpeed);
for (int i = 0; i < grenadeCount; i++)
{
float offsetAngle = Random.Shared.NextSingle() * MathF.Tau;
float offsetDist = MathF.Sqrt(Random.Shared.NextSingle()) * clusterRadius;
float offsetX = MathF.Cos(offsetAngle) * offsetDist;
float offsetY = MathF.Sin(offsetAngle) * offsetDist;
Vector spawnPos = new(
skyCenter.X + offsetX,
skyCenter.Y + offsetY,
skyCenter.Z
Vector ground = new(
decoyGround.X + MathF.Cos(offsetAngle) * offsetDist,
decoyGround.Y + MathF.Sin(offsetAngle) * offsetDist,
decoyGround.Z
);
SkillUtils.CreateMolotovProjectile(spawnPos, launchAngle, velocity, player.TeamNum);
var reach = RayTrace.TraceShape(player, decoyGround, ground, null, 0);
if (reach.HasValue && reach.Value.DidHit)
ground = decoyGround;
float height = maxSpawnHeight;
var ceiling = RayTrace.TraceShape(player, ground, new Vector(ground.X, ground.Y, ground.Z + maxSpawnHeight), null, 0);
if (ceiling.HasValue && ceiling.Value.DidHit)
height = Math.Clamp(maxSpawnHeight * ceiling.Value.Fraction - 24f, 24f, maxSpawnHeight);
Vector spawnPos = new(ground.X, ground.Y, ground.Z + height);
SkillUtils.CreateMolotovProjectile(spawnPos, angle, dropVelocity, player.TeamNum);
}
}
public static void OnEntitySpawned(CEntityInstance entity)
{
var name = entity.DesignerName;
if (name == "molotov_projectile")
{
if (Server.TickCount != rainTick) return;
var thrower = rainThrower;
if (thrower == null || !thrower.IsValid) return;
var molotov = entity.As<CMolotovProjectile>();
if (molotov == null || !molotov.IsValid) return;
molotov.TeamNum = rainTeam;
molotov.Thrower.Raw = thrower.EntityHandle.Raw;
molotov.OwnerEntity.Raw = thrower.EntityHandle.Raw;
rainMolotovs[molotov.Index] = (thrower.EntityHandle.Raw, rainTeam);
return;
}
if (name == "inferno")
{
var inferno = entity.As<CInferno>();
if (inferno == null || !inferno.IsValid) return;
var ownerHandle = inferno.OwnerEntity;
var source = ownerHandle?.Value;
if (ownerHandle == null || source == null || !source.IsValid) return;
if (!rainMolotovs.TryGetValue(source.Index, out var origin)) return;
inferno.TeamNum = origin.Team;
ownerHandle.Raw = origin.ThrowerRaw;
return;
}
if (name != "decoy_projectile")
return;

View file

@ -112,13 +112,31 @@ namespace src.player.skills
box.Entity!.Name = box.Globalname = $"FortniteWall_{Server.TickCount}";
box.Collision.SolidType = SolidType_t.SOLID_VPHYSICS;
box.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(box.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
box.DispatchSpawn();
box.Health = box.MaxHealth = 1_000_000;
var keys = new CEntityKeyValues();
try
{
keys.SetString("model", SkillsInfo.GetValue<string>(skillName, "propModel"));
keys.SetVector("origin", pos);
keys.SetBool("solid", true);
box.DispatchSpawn(keys);
}
finally
{
keys.Dispose();
}
if (!box.IsValid) return;
box.Teleport(pos, angle, null);
barricades.TryAdd(box.Index, SkillsInfo.GetValue<int>(skillName, "barricadeHealth"));
Server.NextFrame(() =>
{
if (box == null || !box.IsValid) return;
box.SetModel(SkillsInfo.GetValue<string>(skillName, "propModel"));
box.Teleport(pos, angle, null);
Utilities.SetStateChanged(box, "CBaseEntity", "m_CBodyComponent");
});
}

View file

@ -72,6 +72,8 @@ namespace src.player.skills
{
if (Server.TickCount % 32 != 0) return;
ReapplyScales();
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
@ -155,6 +157,29 @@ namespace src.player.skills
}
}
private static void ReapplyScales()
{
lock (setLock)
{
foreach (var kvp in enlargedEnemies)
{
float expected = PlayerManager.GetPlayerByIndex(kvp.Key)?.SkillChance ?? 0f;
if (expected <= 0f) continue;
var target = Utilities.GetPlayerFromIndex((int)kvp.Value);
if (target == null || !target.IsValid) continue;
var pawn = target.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) continue;
var skeleton = pawn.CBodyComponent?.SceneNode?.GetSkeletonInstance();
if (skeleton == null || Math.Abs(skeleton.Scale - expected) < 0.01f) continue;
SkillUtils.ChangePlayerScale(target, expected);
}
}
}
private static void ResetScale(uint targetIndex, bool notify = false)
{
var target = Utilities.GetPlayerFromIndex((int)targetIndex);

View file

@ -1,5 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
@ -19,6 +20,7 @@ namespace src.player.skills
}
private static readonly ConcurrentDictionary<uint, List<ChickenState>> activeChickens = new();
private static readonly ConcurrentDictionary<uint, uint> chickenOwners = new();
public static void LoadSkill()
{
@ -28,6 +30,29 @@ namespace src.player.skills
public static void NewRound()
{
activeChickens.Clear();
chickenOwners.Clear();
}
public static void OnTakeDamage(DynamicHook h)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param2 == null) return;
if (!chickenOwners.TryGetValue(param.Index, out uint ownerIndex)) return;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner == null || !owner.IsValid) return;
var attackerEnt = param2.Attacker?.Value;
if (attackerEnt == null || !attackerEnt.IsValid) return;
var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle);
if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return;
if (attackerPawn.TeamNum != owner.TeamNum) return;
param2.Damage = 0;
}
public static void EnableSkill(CCSPlayerController player)
@ -40,7 +65,10 @@ namespace src.player.skills
if (activeChickens.TryRemove(player.Index, out var chickens))
foreach (var state in chickens)
if (state.Chicken != null && state.Chicken.IsValid)
{
chickenOwners.TryRemove(state.Chicken.Index, out _);
state.Chicken.Remove();
}
}
private static void SpawnChicken(CCSPlayerController player)
@ -59,6 +87,7 @@ namespace src.player.skills
if (chicken == null || !chicken.IsValid) continue;
chicken.Render = Color.LightGreen;
chickenOwners[chicken.Index] = player.Index;
Vector offset = new(
(float)(100 * Math.Cos(2 * Math.PI * i / amount)),

View file

@ -33,6 +33,21 @@ namespace src.player.skills
}
}
public static void PlayerDisconnect(uint playerIndex)
{
lock (setLock)
{
targetedPlayers.TryRemove(playerIndex, out _);
if (playersToTarget.TryRemove(playerIndex, out uint targetIndex))
targetedPlayers.TryRemove(targetIndex, out _);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
}
public static void OnTick()
{
if (Server.TickCount % 32 != 0) return;
@ -75,7 +90,7 @@ namespace src.player.skills
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
if (enemy == null)
if (enemy == null || !enemy.IsValid || enemy.Team == player.Team)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;

View file

@ -25,7 +25,9 @@ namespace src.player.skills
lock (setLock)
{
RemoveVolume(playerIndex);
playersToTarget.TryRemove(playerIndex, out _);
if (playersToTarget.TryRemove(playerIndex, out uint leavingTarget))
RemoveVolume(leavingTarget);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
@ -104,7 +106,7 @@ namespace src.player.skills
}
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
if (enemy == null || !enemy.IsValid)
if (enemy == null || !enemy.IsValid || enemy.Team == player.Team)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;

View file

@ -15,12 +15,12 @@ namespace src.player.skills
private static readonly string[] disabledWeapons =
[
"weapon_ak47", "weapon_m4a4", "weapon_m4a1", "weapon_m4a1_silencer",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg553",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg556",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7",
"weapon_ump45", "weapon_p90", "weapon_mp5sd", "weapon_ssg08",
"weapon_awp", "weapon_scar20", "weapon_g3sg1", "weapon_nova",
"weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249",
"weapon_negev", "weapon_sg556"
"weapon_negev"
];
public static void LoadSkill()

View file

@ -23,7 +23,7 @@ namespace src.player.skills
{
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a1", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg553", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev"
};

View file

@ -20,7 +20,7 @@ namespace src.player.skills
[
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a1", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg553", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev"
];

View file

@ -185,10 +185,12 @@ namespace src.player.skills
camNode.Flags = (uint)(camNode.Flags & ~(1 << 2));
camera.SetModel(cameraViewModel);
camera.Render = Color.FromArgb(1, 255, 255, 255);
camera.Teleport(pos, angle);
camera.DispatchSpawn();
camera.Render = Color.FromArgb(1, 255, 255, 255);
Utilities.SetStateChanged(camera, "CBaseModelEntity", "m_clrRender");
CBaseEntity? target = pawn != null && pawn.IsValid ? pawn : null;
var entities = EntityManager.GetPlayerEntities(enemy.Index, "empty_prop");
@ -203,10 +205,11 @@ namespace src.player.skills
camera.AcceptInput("SetParent", target, target, "!activator");
});
if (cameras.TryGetValue(player.Index, out var cameraInfo))
cameras.AddOrUpdate(player.Index, (cameraInfo.Item1, camera.Index, enemy.Index), (k, v) => (cameraInfo.Item1, camera.Index, enemy.Index));
else
cameras.AddOrUpdate(player.Index, (pawn.CameraServices.ViewEntity.Raw, camera.Index, enemy.Index), (k, v) => (pawn.CameraServices.ViewEntity.Raw, camera.Index, enemy.Index));
uint originalView = cameras.TryGetValue(player.Index, out var cameraInfo)
? cameraInfo.Item1
: player.PlayerPawn?.Value?.CameraServices?.ViewEntity.Raw ?? 0;
cameras[player.Index] = (originalView, camera.Index, enemy.Index);
return camera.EntityHandle.Raw;
}

View file

@ -29,9 +29,16 @@ namespace src.player.skills
foreach (var kvp in OriginalWeaponMaxAmmo)
{
var vdata = VirtualFunctions.GetCSWeaponDataFromKey(-1, kvp.Value.Item2);
if (vdata != null)
try
{
var vdata = VirtualFunctions.GetCSWeaponDataFromKey(-1, kvp.Value.Item2);
if (vdata == null || vdata.Handle == nint.Zero) continue;
vdata.PrimaryReserveAmmoMax = kvp.Value.Item1;
}
catch (Exception ex)
{
Server.PrintToConsole($"[jRandomSkills] TakeAmmo could not restore ammo for {kvp.Value.Item2}: {ex.Message}");
}
}
OriginalWeaponMaxAmmo.Clear();
}

View file

@ -95,9 +95,17 @@ namespace src.player.skills
var pawn = eventPlayer.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) continue;
if (SkillUtils.GetDistance(smoke.Key, pawn.AbsOrigin) <= smokeRadius)
if (SkillUtils.TakeHealth(pawn, smokeDamage, thrower, KillfeedIcons.Smokegrenade))
player.EmitSound("Player.DamageBody.Onlooker");
if (SkillUtils.GetDistance(smoke.Key, pawn.AbsOrigin) > smokeRadius) continue;
int damage = smokeDamage;
if (thrower != null && eventPlayer.Index != thrower.Index && pawn.TeamNum == thrower.TeamNum)
{
damage = (int)MathF.Round(smokeDamage * SkillUtils.GetTeamDamageMultiplier(skillName));
if (damage <= 0) continue;
}
if (SkillUtils.TakeHealth(pawn, damage, thrower, KillfeedIcons.Smokegrenade))
player.EmitSound("Player.DamageBody.Onlooker");
}
}
}
@ -162,12 +170,13 @@ namespace src.player.skills
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int smokeDamage = 2, float smokeRadius = 180, int tickCooldown = 17, int grenadeLimit = 1) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, int smokeDamage = 2, float smokeRadius = 180, int tickCooldown = 17, int grenadeLimit = 1, float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int SmokeDamage { get; set; } = smokeDamage;
public float SmokeRadius { get; set; } = smokeRadius;
public int TickCooldown { get; set; } = tickCooldown;
public int GrenadeLimit { get; set; } = grenadeLimit;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
}
}
}

View file

@ -17,7 +17,7 @@ namespace src.player.skills
"weapon_cz75a", "weapon_fiveseven", "weapon_p250", "weapon_tec9", "weapon_elite", "weapon_hkp2000",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90", "weapon_sg556",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a4", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg553", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev" ];
public static void LoadSkill()

View file

@ -75,6 +75,21 @@ namespace src.player.skills
SkillUtils.CloseMenu(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
lock (setLock)
{
infectedPlayers.TryRemove(playerIndex, out _);
if (playersToTarget.TryRemove(playerIndex, out uint targetIndex))
infectedPlayers.TryRemove(targetIndex, out _);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
}
public static void OnTick()
{
if (Server.TickCount % 32 != 0) return;

View file

@ -279,7 +279,6 @@ namespace jRandomSkills.src.utils
"weapon_p90" => KillfeedIcons.P90,
"weapon_mp5sd" => KillfeedIcons.Mp5,
"weapon_sg556" => KillfeedIcons.Sg556,
"weapon_sg553" => KillfeedIcons.Sg556,
"weapon_famas" => KillfeedIcons.Famas,
"weapon_galilar" => KillfeedIcons.Galilar,
"weapon_m4a4" => KillfeedIcons.M4A4,

View file

@ -210,7 +210,7 @@ namespace src.utils
collision.CollisionAttribute.CollisionGroup = collisionGroup;
Utilities.SetStateChanged(pawn, "CCollisionProperty", "m_collisionAttribute");
// CollisionRulesChanged(pawn);
// CollisionRulesChanged(pawn);
}
//public static void CollisionRulesChanged(CBaseEntity? entity)
@ -249,7 +249,8 @@ namespace src.utils
skeleton.Scale = scale;
playerPawn.AcceptInput("SetScale", null, null, scale.ToString(CultureInfo.InvariantCulture));
Server.NextWorldUpdate(() => {
Server.NextWorldUpdate(() =>
{
if (playerPawn == null || !playerPawn.IsValid) return;
Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_CBodyComponent");
});
@ -338,6 +339,12 @@ namespace src.utils
return !ff && !tae; // same team + FF off -> engine will zero this damage
}
public static float GetTeamDamageMultiplier(Skills skill)
{
float reduction = SkillsInfo.GetValue<float>(skill, "dmgReductionForTeamates");
return 1f - Math.Clamp(reduction, 0f, 1f);
}
private static readonly ConcurrentDictionary<uint, (uint AttackerIndex, string? Weapon, int ExpiryTick)> pendingKillCredits = [];
public static void RegisterKillCredit(uint victimIndex, uint attackerIndex, KillfeedIcons? killfeedIcon = null)
@ -367,7 +374,7 @@ namespace src.utils
"deagle", "revolver", "glock", "usp_silencer", "cz75a",
"fiveseven", "p250", "tec9", "elite", "hkp2000",
"mp9", "mac10", "bizon", "mp7", "ump45", "p90", "mp5sd",
"famas", "galilar", "m4a1", "m4a1_silencer", "ak47", "aug", "sg553",
"famas", "galilar", "m4a1", "m4a1_silencer", "ak47", "aug", "sg556",
"ssg08", "awp", "scar20", "g3sg1",
"nova", "xm1014", "mag7", "sawedoff",
"m249", "negev"
@ -389,7 +396,7 @@ namespace src.utils
Skills.Deaf, Skills.ExpensiveAmmo, Skills.Giant, Skills.Glitch,
Skills.Jammer, Skills.JumpBan, Skills.JumpCurse, Skills.LifeSwap,
Skills.Magnifier, Skills.MoneySwap, Skills.Nightmare, Skills.Poison,
Skills.PrimaryBan, Skills.Thief, Skills.WildThrow
Skills.JetKick, Skills.PrimaryBan, Skills.Thief, Skills.WildThrow
];
private static readonly HashSet<string> curseSkillNames = new(curseSkills.Select(s => s.ToString()), StringComparer.Ordinal);
@ -436,15 +443,14 @@ namespace src.utils
public static bool TryClaimCurse(uint curserIndex, uint victimIndex, bool force = false)
{
if (!CurseLimitEnabled) return true;
int limit = Config.LoadedConfig.CurseSkillPerPlayer!.Value;
lock (curseLock)
{
ReleaseCurseLocked(curserIndex);
curseCounts.TryGetValue(victimIndex, out int used);
if (!force && used >= limit) return false;
if (CurseLimitEnabled && !force && used >= Config.LoadedConfig.CurseSkillPerPlayer!.Value)
return false;
curseCounts[victimIndex] = used + 1;
curserToVictim[curserIndex] = victimIndex;
@ -454,15 +460,17 @@ namespace src.utils
public static void ReleaseCurse(uint curserIndex)
{
if (!CurseLimitEnabled) return;
lock (curseLock) ReleaseCurseLocked(curserIndex);
}
public static uint[] GetCursersOf(uint victimIndex)
{
lock (curseLock)
return [.. curserToVictim.Where(kvp => kvp.Value == victimIndex).Select(kvp => kvp.Key)];
}
public static void ClearCursesFor(uint playerIndex)
{
if (!CurseLimitEnabled) return;
lock (curseLock)
{
ReleaseCurseLocked(playerIndex);
@ -1025,7 +1033,7 @@ namespace src.utils
if (roundsInOvertime % overtimeMaxRounds == 0)
{
int currentOvertime = roundsInOvertime / overtimeMaxRounds;
if ( currentOvertime < overtimeLimit)
if (currentOvertime < overtimeLimit)
{
gameRulesProxy.SwapTeamsOnRestart = true;
gameRulesProxy.SwitchingTeamsAtRoundReset = true;

View file

@ -1,4 +1,4 @@
[
[
{
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
@ -105,6 +105,7 @@
"SmokeRadius": 180.0,
"TickCooldown": 17,
"GrenadeLimit": 1,
"DmgReductionForTeamates": 0.5,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
@ -1522,6 +1523,7 @@
"DamageRadius": 210.0,
"ChanceFrom": 0.15,
"ChanceTo": 0.3,
"DmgReductionForTeamates": 0.5,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,
@ -1857,9 +1859,9 @@
{
"ExplosionRadius": 400.0,
"ExplosionDamage": 60,
"DmgReductionForTeamates": 0.5,
"Cooldown": 10.0,
"Force": 1000.0,
"DmgReductionForTeamates": 0.5,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"OnlyTeam": 0,

View file

@ -1,4 +1,4 @@
{
{
"none": "Keine",
"none_desc": "Du hast keine Fertigkeit",
@ -276,6 +276,7 @@
"jetkick": "Jetkick",
"jetkick_desc": "Wähle einen Spieler für einen Jetkick",
"jetkick_select_info": "Wähle einen Spieler für den Jetkick:",
"jetkick_player_info": "Spieler '{0}' wurde wegge-kickt.",
"jetkick_enemy_info": "Jeder Schuss stößt dich nach hinten",
"jetkick_disable_info": "Dein Rückstoß ist wieder normal.",

View file

@ -1,4 +1,4 @@
{
{
"none": "None",
"none_desc": "You have no skill",
@ -269,6 +269,7 @@
"jetkick": "Jetkick",
"jetkick_desc": "Select a player to jetkick",
"jetkick_select_info": "Select a player to jetkick:",
"jetkick_player_info": "Player '{0}' has been jetkicked.",
"jetkick_enemy_info": "Every shot knocks you backwards",
"jetkick_disable_info": "Your recoil is back to normal.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Aucune",
"none_desc": "Vous n’avez aucune compétence",
@ -276,6 +276,7 @@
"jetkick": "Coup de pied propulsé",
"jetkick_desc": "Sélectionnez un joueur pour lui donner un coup de pied propulsé",
"jetkick_select_info": "Sélectionnez un joueur à propulser :",
"jetkick_player_info": "Le joueur '{0}' a reçu un coup de pied propulsé.",
"jetkick_enemy_info": "Chaque tir vous repousse vers l'arrière",
"jetkick_disable_info": "Votre recul est revenu à la normale.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Brak",
"none_desc": "Nie posiadasz żadnej supermocy",
@ -276,6 +276,7 @@
"jetkick": "Odrzutowy Kop",
"jetkick_desc": "Wybierz gracza, aby użyć na nim odrzutowego kopa",
"jetkick_select_info": "Wybierz gracza do odrzutowego kopa:",
"jetkick_player_info": "Gracz '{0}' został odrzucony kopem.",
"jetkick_enemy_info": "Każdy strzał mocno odrzuca Cię do tyłu",
"jetkick_disable_info": "Twój odrzut wrócił do normy.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Nenhuma",
"none_desc": "Você não tem nenhuma habilidade",
@ -276,6 +276,7 @@
"jetkick": "Chute a Jato",
"jetkick_desc": "Selecione um jogador para aplicar um chute a jato",
"jetkick_select_info": "Selecione um jogador para o chute a jato:",
"jetkick_player_info": "O jogador '{0}' recebeu um chute a jato.",
"jetkick_enemy_info": "Cada tiro empurra você para trás",
"jetkick_disable_info": "Seu recuo voltou ao normal.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Нет навыка",
"none_desc": "У вас нет навыка",
@ -276,6 +276,7 @@
"jetkick": "Реактивный удар",
"jetkick_desc": "Выберите игрока для реактивного удара",
"jetkick_select_info": "Выберите игрока для реактивного удара:",
"jetkick_player_info": "Игрок '{0}' получил реактивный удар.",
"jetkick_enemy_info": "Каждый выстрел отбрасывает вас назад",
"jetkick_disable_info": "Ваша отдача вернулась к норме.",

View file

@ -1,4 +1,4 @@
{
{
"none": "Yok",
"none_desc": "Hiçbir yeteneğin yok",
@ -52,13 +52,13 @@
"behind_desc2": "Baş döndürme şansın: %{0}",
"berserker": "Berserker",
"berserker_desc": "Canın azaldıkça daha fazla hasar verir ve daha hızlı hareket edersin",
"berserker_desc": "Canın azaldıkça hasarın artar ve daha hızlanırsın",
"blademaster": "Kılıç Ustası",
"blademaster_desc": "Bıçak tutarken mermileri sektirme şansın artar",
"blastshot": "Patlama Atışı",
"blastshot_desc": "MP5 ile Attack2 tuşuna basarak bir HE bombası ateşle",
"blastshot_desc": "MP5 ile Sağ Tık'a basarak El bombası fırlat",
"bunnyhop": "Tavşan",
"bunnyhop_desc": "Otomatik \"BunnyHop\" yaparsın",
@ -156,14 +156,14 @@
"fastreload_desc": "[css_useSkill] tuşuna basarak şarjörünü hızlıca değiştir",
"firerain": "Ateş Yağmuru",
"firerain_desc": "Molotof yağmuru çağırmak için bir yem at",
"firerain_desc": "Molotof yağdırmak için bir decoy at",
"flash": "Flash",
"flash_desc": "Raund başında rastgele oyuncu hızı",
"flash_desc2": "Hızın: {0}x",
"flashlight": "El Feneri",
"flashlight_desc": "El fenerini açıp kapatmak için [css_useSkill] tuşuna tıkla. Işığı düşmanları kör edebilir",
"flashlight_desc": "[css_useSkill] tuşuna basarak El fenerini açarsın/kaparsın (Işığı düşmanları kör edebilir)",
"fortnite": "Fortnite",
"fortnite_desc": "[css_useSkill] tuşuna basarak barikat kur",
@ -223,7 +223,7 @@
"healingsmoke_desc": "Sis bombaların iyileştirir",
"hermit": "Münzevi",
"hermit_desc": "Öldürdüğünde mermi ve canın yenilenir",
"hermit_desc": "Birini öldürünce mermin ve canın yenilenir",
"holyhandgrenade": "Kutsal El Bombası",
"holyhandgrenade_desc": "El bomban iki kat hasara ve menzile sahip",
@ -267,9 +267,10 @@
"jester_on": "AÇIK",
"jester_off": "KAPALI",
"jetkick": "Jet Tekmesi",
"jetkick_desc": "Jet tekmesi yapmak için bir oyuncu seç",
"jetkick_player_info": "'{0}' adlı oyuncuya jet tekmesi yapıldı.",
"jetkick": "Jetkick",
"jetkick_desc": "Seçtiğin bir rakibin her atışı onu geriye iter",
"jetkick_select_info": "Jetkick uygulanacak oyuncu:",
"jetkick_player_info": "'{0}' adlı oyuncuya jetkick uygulandı.",
"jetkick_enemy_info": "Her atış seni geriye doğru iter",
"jetkick_disable_info": "Geri tepmen normale döndü.",
@ -297,7 +298,7 @@
"knockback_desc": "Havadayken ateş etmek seni geriye savurur",
"lastgasp": "Son Nefes",
"lastgasp_desc": "Öldükten sonra seni öldüren düşmana hasar verirsin",
"lastgasp_desc": "Öldükten sonra seni öldürene hasar verirsin",
"lifeswap": "Can Takası",
"lifeswap_desc": "Can değerlerini takas etmek için bir oyuncu seç",
@ -340,7 +341,7 @@
"deathbomb_desc": "Öldüğünde patlayarak etraftaki oyuncuları öldürürsün",
"deathbomb_explosion": "BOOM!!!",
"demoneye": "Şeytan Gözü",
"demoneye": "Şeytani Göz",
"demoneye_desc": "Baktığın tüm düşmanlara hasar verirsin",
"nightmare": "Kabus",
@ -489,14 +490,14 @@
"swapposition": "Yer Değiştirme",
"swapposition_desc": "[css_useSkill] tuşuna basarak vurduğun düşmanla yer değiştir",
"takeammo": "Mühimmat Al",
"takeammo_desc": "Rastgele bir düşmanın aktif silahının şarjörünü almak için [css_useSkill] tuşuna tıkla",
"takeammo_hud_info1": "Aktif şarjörü olan bir oyuncu bulunamadı.",
"takeammo_hud_info2": "Düşmanın şarjörü yok.",
"takeammo_enemy_info": "Bir miktar mühimmat kaybettin.",
"takeammo": "Cephane Hırsızı",
"takeammo_desc": "[css_useSkill] tuşuna basarak rastgele bir düşmanın silahının şarjörünü çalarsın",
"takeammo_hud_info1": "Cephanesi olan bir oyuncu bulunamadı.",
"takeammo_hud_info2": "Düşmanın cephanesi yok.",
"takeammo_enemy_info": "Birisi cephaneni çaldı.",
"teamteleport": "Takıma Işınlanma",
"teamteleport_desc": "Baktığın takım arkadaşına ışınlanmak için [css_useSkill] tuşuna bas",
"teamteleport_desc": "[css_useSkill] tuşuna basarak baktığın takım arkadaşına ışınlanırsın",
"teamteleport_noenemy": "Takım arkadaşı bulunamadı",
"teamteleport_hud_info": "Bulundu: {0}",