Загрузка данных
#include <amxmisc>
#include <fakemeta>
#include <hamsandwich>
#include <json>
#include <reapi>
#include <bitmask>
native bio_get_user_money(id);
native bio_set_user_money(id, amount, bool:flash = true);
#if AMXX_VERSION_NUM < 190
#assert Outdated version of AMX Mod X (AMXX_VERSION_NUM), please update in order to use this plugin properly.
#endif
#pragma semicolon 1
new const TEXT_MESSAGE[][] =
{
"#Game_Commencing",
"#Cstrike_Tutor_Round_Over",
"#Round_Draw",
"#Game_will_restart_in",
"#CTs_Win",
"#Terrorists_Win"
};
new const SEND_AUDIO[][] =
{
"%!MRAD_rounddraw",
"%!MRAD_terwin",
"%!MRAD_ctwin"
};
new const ZOMBIE_SCREAM[][] =
{
"biohazard/scream1.wav",
"biohazard/scream2.wav",
"biohazard/scream3.wav"
};
new const IMMUTABLE_CVARS[][][] =
{
{"mp_limitteams", "0"},
{"mp_autoteambalance", "0"},
{"mp_round_infinite", "b"},
{"mp_roundover", "1"},
{"mp_auto_join_team", "1"},
{"humans_join_team", "CT"},
{"mp_autokick", "0"}
};
new m_cImmutableCvars[sizeof IMMUTABLE_CVARS];
enum
{
TASKID_WARMUP
};
enum _:ClassData
{
ClassName[32],
Float:ClassHealth,
Float:ClassSpeed,
Float:ClassGravity,
ClassModel[64],
ClassClaw[64]
};
new Array:m_arrClassesVec = Invalid_Array;
new Array:m_arrSpawnPoints = Invalid_Array;
new m_iNumClasses;
new m_iMenuPosition[MAX_CLIENTS + 1];
new m_iLastSpawn;
new Trie:m_trUselessEntities = Invalid_Trie;
new Trie:m_trTextMessage = Invalid_Trie;
new Trie:m_trSendAudio = Invalid_Trie;
new bool:m_bWarmup = true;
new bool:m_bZombieRespawnAllowed;
new m_pCvar_FreezeTime;
new m_pCvar_RoundTime;
new m_pCvar_OverHumansWin;
new m_pCvar_ZombieRespawnTime;
new m_pCvar_ZombieRespawnMinDistance;
new m_pCvar_ZombieRespawnLimit;
new m_pCvar_ZombieRespawnChanceDecay;
new m_pCvar_MoneyDamageRatio;
new m_pCvar_MoneyInfection;
new m_fwdSpawn;
new m_varClass[MAX_CLIENTS + 1];
new m_iZombieRespawn[MAX_CLIENTS + 1];
new m_iMoney[MAX_CLIENTS + 1];
new m_bitZombie;
new m_bitZombieRespawn;
/*============================================================
PLUGIN
============================================================*/
public plugin_precache()
{
register_plugin("Biohazard", "0.0.1", "oxsi. x Vision");
m_arrClassesVec = ArrayCreate(ClassData);
m_arrSpawnPoints = ArrayCreate(1, 0);
m_trTextMessage = TrieCreate();
for (new i; i < sizeof TEXT_MESSAGE; i++)
TrieSetCell(m_trTextMessage, TEXT_MESSAGE[i], 0);
m_trSendAudio = TrieCreate();
for (new i; i < sizeof SEND_AUDIO; i++)
TrieSetCell(m_trSendAudio, SEND_AUDIO[i], 0);
for (new i; i < sizeof ZOMBIE_SCREAM; i++)
precache_sound(ZOMBIE_SCREAM[i]);
for (new i; i < sizeof IMMUTABLE_CVARS; i++)
{
m_cImmutableCvars[i] = get_cvar_pointer(IMMUTABLE_CVARS[i][0]);
set_pcvar_string(m_cImmutableCvars[i], IMMUTABLE_CVARS[i][1]);
hook_cvar_change(m_cImmutableCvars[i], "@Hook_ForcingCvarChange");
}
m_pCvar_FreezeTime = get_cvar_pointer("mp_freezetime");
m_pCvar_RoundTime = get_cvar_pointer("mp_roundtime");
set_pcvar_flags(
m_pCvar_FreezeTime,
get_pcvar_flags(m_pCvar_FreezeTime) | FCVAR_UNLOGGED
);
set_pcvar_flags(
m_pCvar_RoundTime,
get_pcvar_flags(m_pCvar_RoundTime) | FCVAR_UNLOGGED
);
if (m_bWarmup)
{
set_pcvar_num(m_pCvar_FreezeTime, 0);
set_pcvar_float(m_pCvar_RoundTime, 20.0 / 60.0);
set_task_ex(1.0, "@Task_Warmup", TASKID_WARMUP, .flags = SetTask_Repeat);
}
else
{
set_pcvar_num(m_pCvar_FreezeTime, 15);
set_pcvar_float(m_pCvar_RoundTime, 180.0 / 60.0);
}
if (!Read_JsonField("biohazard"))
set_fail_state("Failed to read biohazard config.");
}
public plugin_init()
{
m_pCvar_OverHumansWin = register_cvar("bio_over_humans_win", "1");
m_pCvar_ZombieRespawnTime = register_cvar("bio_zombie_respawn", "5.0");
m_pCvar_ZombieRespawnMinDistance = register_cvar("bio_zombie_respawn_min_distance", "1200.0");
m_pCvar_ZombieRespawnLimit = register_cvar("bio_zombie_respawn_limit", "4");
m_pCvar_ZombieRespawnChanceDecay = register_cvar("bio_zombie_chance_decay", "25.0");
m_pCvar_MoneyDamageRatio = register_cvar("bio_money_damage_ratio", "0.5");
m_pCvar_MoneyInfection = register_cvar("bio_money_infection", "150");
register_clcmd("say /class", "@Open_ClassesMenu");
register_clcmd("say_team /class", "@Open_ClassesMenu");
register_clcmd("infect", "cmd_infect");
register_clcmd("antidote", "cmd_antidote");
register_menucmd(
register_menuid("@Display_ClassesMenu"),
(1 << 0 | 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4 |
1 << 5 | 1 << 6 | 1 << 7 | 1 << 8 | 1 << 9),
"@Handle_ClassesMenu"
);
register_message(get_user_msgid("TextMsg"), "@Message_TextMsg");
register_message(get_user_msgid("SendAudio"), "@Message_SendAudio");
register_message(get_user_msgid("Money"), "@Message_Money");
RegisterHookChain(RH_SV_ActivateServer, "@SV_ActivateServer_Post", true);
RegisterHookChain(RG_RoundEnd, "@RoundEnd_Pre");
RegisterHookChain(RG_RoundEnd, "@RoundEnd_Post", true);
RegisterHookChain(RG_CSGameRules_RestartRound, "@CSGameRules_RestartRound_Pre");
RegisterHookChain(RG_CSGameRules_RestartRound, "@CSGameRules_RestartRound_Post", true);
RegisterHookChain(RG_CSGameRules_CheckWinConditions, "@CSGameRules_CheckWinConditions_Pre");
RegisterHookChain(RG_CSGameRules_FPlayerCanTakeDamage, "@CSGameRules_FPlayerCanTakeDamage_Pre");
RegisterHookChain(RG_CSGameRules_OnRoundFreezeEnd, "@CSGameRules_OnRoundFreezeEnd_Pre");
RegisterHookChain(RG_CSGameRules_GetPlayerSpawnSpot, "@CSGameRules_GetPlayerSpawnSpot_Pre");
RegisterHookChain(RG_CBasePlayer_Spawn, "@CBasePlayer_Spawn_Pre");
RegisterHookChain(RG_CBasePlayer_Spawn, "@CBasePlayer_Spawn_Post", true);
RegisterHookChain(RG_CBasePlayer_HasRestrictItem, "@CBasePlayer_HasRestrictItem_Pre");
RegisterHookChain(RG_CBasePlayer_TakeDamage, "@CBasePlayer_TakeDamage_Pre");
RegisterHookChain(RG_CBasePlayer_ResetMaxSpeed, "@CBasePlayer_ResetMaxSpeed_Pre");
RegisterHookChain(RG_CBasePlayer_Killed, "@CBasePlayer_Killed_Post", true);
@Forcing_LevelInitialize();
}
public plugin_natives()
{
register_native("bio_get_user_money", "@native_get_user_money");
register_native("bio_set_user_money", "@native_set_user_money");
}
public plugin_end()
{
if (m_trUselessEntities != Invalid_Trie)
TrieDestroy(m_trUselessEntities);
if (m_trTextMessage != Invalid_Trie)
TrieDestroy(m_trTextMessage);
if (m_trSendAudio != Invalid_Trie)
TrieDestroy(m_trSendAudio);
if (m_arrClassesVec != Invalid_Array)
ArrayDestroy(m_arrClassesVec);
if (m_arrSpawnPoints != Invalid_Array)
ArrayDestroy(m_arrSpawnPoints);
}
/*============================================================
CLIENT
============================================================*/
public client_putinserver(id)
{
m_varClass[id] = 0;
}
public client_disconnected(id)
{
m_varClass[id] = 0;
m_iZombieRespawn[id] = 0;
BIT_SUB(m_bitZombie, BIT_PLAYER(id));
BIT_SUB(m_bitZombieRespawn, BIT_PLAYER(id));
}
/*============================================================
COMMANDS
============================================================*/
public cmd_infect(id)
{
@ZombieAttributes(id, 0);
}
public cmd_antidote(id)
{
@HumanAttributes(id);
}
/*============================================================
CLASS MENU
============================================================*/
@Open_ClassesMenu(id)
{
if (!m_iNumClasses)
{
client_print(id, print_chat, "Классы зомби не найдены!");
return PLUGIN_HANDLED;
}
m_iMenuPosition[id] = 0;
return @Display_ClassesMenu(id, 0);
}
@Display_ClassesMenu(id, pos)
{
if (pos < 0)
return PLUGIN_HANDLED;
new start = pos * 7;
if (start > m_iNumClasses)
start = m_iNumClasses;
start -= start % 7;
pos = start / 7;
new end = start + 7;
if (end > m_iNumClasses)
end = m_iNumClasses;
new page = (m_iNumClasses / 7) + (m_iNumClasses % 7 ? 1 : 0);
new key = 1 << 9;
new menu[512];
new data[ClassData];
new len = formatex(
menu,
charsmax(menu),
"\yClasses %d/%d^n^n",
pos + 1,
page
);
new item;
while (start < end)
{
ArrayGetArray(m_arrClassesVec, start, data);
if (m_varClass[id] != start)
key |= 1 << item;
len += formatex(
menu[len],
charsmax(menu) - len,
"\y%d. %s%s^n",
++item,
m_varClass[id] == start ? "\d" : "\w",
data[ClassName]
);
start++;
}
if (pos)
key |= 1 << 7;
if (end < m_iNumClasses)
key |= 1 << 8;
len += formatex(
menu[len],
charsmax(menu) - len,
"^n\y8. %sНазад",
pos ? "\w" : "\d"
);
len += formatex(
menu[len],
charsmax(menu) - len,
"^n\y9. %sВперёд",
end < m_iNumClasses ? "\w" : "\d"
);
formatex(
menu[len],
charsmax(menu) - len,
"^n\y0. \wВыход"
);
return show_menu(id, key, menu, -1, "@Display_ClassesMenu");
}
@Handle_ClassesMenu(id, key)
{
switch (key)
{
case 7:
return @Display_ClassesMenu(id, --m_iMenuPosition[id]);
case 8:
return @Display_ClassesMenu(id, ++m_iMenuPosition[id]);
case 9:
return PLUGIN_HANDLED;
}
new class = m_iMenuPosition[id] * 7 + key;
if (class >= m_iNumClasses)
return PLUGIN_HANDLED;
m_varClass[id] = class;
new data[ClassData];
ArrayGetArray(m_arrClassesVec, class, data);
client_print(
id,
print_chat,
"Вы выбрали класс %s %d",
data[ClassName],
class
);
return PLUGIN_CONTINUE;
}
/*============================================================
MESSAGES / CVARS
============================================================*/
@Hook_ForcingCvarChange(pcvar, old_value[], new_value[])
{
for (new i; i < sizeof IMMUTABLE_CVARS; i++)
{
if (m_cImmutableCvars[i] != pcvar)
continue;
if (!equal(new_value, IMMUTABLE_CVARS[i][1]))
set_pcvar_string(pcvar, IMMUTABLE_CVARS[i][1]);
break;
}
}
@Message_TextMsg(msg_id, msg_dest, receiver)
{
return @BlockMessage(m_trTextMessage);
}
@Message_SendAudio(msg_id, msg_dest, receiver)
{
return @BlockMessage(m_trSendAudio);
}
@BlockMessage(Trie:trie)
{
new text[32], value;
get_msg_arg_string(2, text, charsmax(text));
return TrieGetCell(trie, text, value)
? PLUGIN_HANDLED
: PLUGIN_CONTINUE;
}
@Message_Money(msg_id, msg_dest, receiver)
{
set_member(receiver, m_iAccount, m_iMoney[receiver]);
set_msg_arg_int(1, get_msg_argtype(1), m_iMoney[receiver]);
}
/*============================================================
WARMUP / ROUND
============================================================*/
@Task_Warmup(task)
{
if (get_member_game(m_bRoundTerminating))
return;
new timeLeft = floatround(
float(get_member_game(m_iRoundTimeSecs))
- get_gametime()
+ Float:get_member_game(m_fRoundStartTimeReal)
);
if (timeLeft == 11)
client_cmd(0, "spk ^"plats/elevbell1.wav^"");
else if (timeLeft <= 4 && timeLeft > 1)
client_cmd(0, "spk ^"buttons/bell1.wav^"");
if (timeLeft < 1)
rg_round_end(3.0, WINSTATUS_DRAW, ROUND_GAME_COMMENCE, .trigger = true);
}
@SV_ActivateServer_Post()
{
if (!m_fwdSpawn)
return;
unregister_forward(FM_Spawn, m_fwdSpawn);
m_fwdSpawn = 0;
TrieDestroy(m_trUselessEntities);
m_trUselessEntities = Invalid_Trie;
}
@RoundEnd_Pre(WinStatus:status, ScenarioEventEndRound:event, Float:delay)
{
if (m_bWarmup)
{
SetHookChainArg(2, ATYPE_INTEGER, ROUND_GAME_COMMENCE);
SetHookChainArg(3, ATYPE_FLOAT, 3.0);
return;
}
if (!get_member_game(m_bGameStarted))
return;
stop_zombie_respawn();
if (status == WINSTATUS_NONE)
status = WINSTATUS_DRAW;
if (status == WINSTATUS_DRAW && get_pcvar_bool(m_pCvar_OverHumansWin))
status = WINSTATUS_CTS;
SetHookChainArg(1, ATYPE_INTEGER, status);
}
@RoundEnd_Post(WinStatus:status, ScenarioEventEndRound:event, Float:delay)
{
if (m_bWarmup)
return;
switch (status)
{
case WINSTATUS_CTS:
{
client_print(0, print_center, "Humans won!");
client_cmd(0, "spk ^"ambience/3dmstart.wav^"");
}
case WINSTATUS_TERRORISTS:
{
client_print(0, print_center, "Zombies won!");
client_cmd(0, "spk ^"ambience/the_horror1.wav^"");
}
case WINSTATUS_DRAW:
{
client_print(0, print_center, "No one won!");
client_cmd(0, "spk ^"ambience/3dmstart.wav^"");
}
}
}
@CSGameRules_RestartRound_Pre()
{
if (m_bWarmup)
{
m_bWarmup = false;
remove_task(TASKID_WARMUP);
}
set_pcvar_num(m_pCvar_FreezeTime, 15);
set_pcvar_float(m_pCvar_RoundTime, 180.0 / 60.0);
}
@CSGameRules_RestartRound_Post()
{
if (!m_bWarmup)
set_member_game(m_bGameStarted, true);
stop_zombie_respawn();
m_bitZombie = 0;
for (new i = 1; i <= MaxClients; i++)
{
if (is_user_connected(i))
m_iZombieRespawn[i] = 0;
}
@Forcing_LevelInitialize();
}
/*============================================================
CUSTOM WIN CONDITIONS
============================================================*/
@CSGameRules_CheckWinConditions_Pre()
{
if (m_bWarmup ||
!get_member_game(m_bGameStarted) ||
get_member_game(m_bRoundTerminating))
return HC_SUPERCEDE;
return HC_SUPERCEDE;
}
@RequestFrame_CheckChangeClassWinConditions()
{
if (get_member_game(m_iRoundWinStatus) != WINSTATUS_NONE ||
!get_member_game(m_bGameStarted) ||
get_member_game(m_bRoundTerminating))
return;
new zombies = get_zombies_in_round();
new humans = get_alive_humans();
if (!zombies)
{
stop_zombie_respawn();
rg_round_end(3.0, WINSTATUS_CTS, ROUND_CTS_WIN, .trigger = true);
return;
}
if (!humans)
{
stop_zombie_respawn();
rg_round_end(3.0, WINSTATUS_TERRORISTS, ROUND_TERRORISTS_WIN, .trigger = true);
}
}
/*============================================================
GAME RULES
============================================================*/
@CSGameRules_FPlayerCanTakeDamage_Pre(id, attacker)
{
if (get_member_game(m_bRoundTerminating) || m_bWarmup)
{
SetHookChainReturn(ATYPE_INTEGER, false);
return HC_SUPERCEDE;
}
return HC_CONTINUE;
}
@CSGameRules_OnRoundFreezeEnd_Pre()
{
if (!get_member_game(m_bGameStarted))
return;
m_bZombieRespawnAllowed = true;
new players[MAX_CLIENTS];
new count;
for (new i = 1; i <= MaxClients; i++)
{
if (is_user_alive(i))
players[count++] = i;
}
if (count < 2)
return;
new need = floatround(float(count) * 0.20, floatround_ceil);
while (need--)
{
new pos = random(count);
new id = players[pos];
players[pos] = players[--count];
@ZombieAttributes(id, 0);
}
}
@CSGameRules_GetPlayerSpawnSpot_Pre(id)
{
if (!IsPlaying(id))
return HC_CONTINUE;
new spot = IsZombie(id)
? @Select_ZombieSpawnPoint(id)
: @Select_SpawnPoint(id);
if (is_nullent(spot))
return HC_CONTINUE;
new Float:origin[3], Float:angles[3];
get_entvar(spot, var_origin, origin);
get_entvar(spot, var_angles, angles);
origin[2] += 1.0;
set_entvar(id, var_origin, origin);
set_entvar(id, var_v_angle, NULL_VECTOR);
set_entvar(id, var_velocity, NULL_VECTOR);
set_entvar(id, var_angles, angles);
set_entvar(id, var_punchangle, NULL_VECTOR);
set_entvar(id, var_fixangle, 1);
SetHookChainReturn(ATYPE_INTEGER, spot);
return HC_SUPERCEDE;
}
/*============================================================
PLAYER SPAWN / ITEMS
============================================================*/
@CBasePlayer_Spawn_Pre(id)
{
if (get_member(id, m_bJustConnected) || !IsPlaying(id))
return;
RequestFrame("@RequestFrame_ApplyPlayerAttributes", id);
}
@CBasePlayer_Spawn_Post(id)
{
if (get_member(id, m_bJustConnected))
{
set_member(id, m_fNextSuicideTime, 999999999.0);
return;
}
if (!IsPlaying(id))
return;
if (get_member_game(m_bFreezePeriod))
set_member(id, m_bCanShootOverride, true);
if (IsZombieRespawning(id))
{
BIT_SUB(m_bitZombieRespawn, BIT_PLAYER(id));
set_member(id, m_flRespawnPending, 0.0);
}
}
@CBasePlayer_HasRestrictItem_Pre(id)
{
if (get_member(id, m_iTeam) != TEAM_TERRORIST)
return HC_CONTINUE;
SetHookChainReturn(ATYPE_BOOL, true);
return HC_SUPERCEDE;
}
/*============================================================
DAMAGE
============================================================*/
@CBasePlayer_TakeDamage_Pre(victim, inflictor, attacker, Float:damage, damage_type)
{
if (victim == attacker || !is_user_connected(attacker))
return;
if (!rg_is_player_can_takedamage(victim, attacker))
return;
if (IsZombie(attacker))
{
new weapon = get_member(attacker, m_pActiveItem);
if (!is_nullent(weapon) && get_member(weapon, m_iId) == WEAPON_KNIFE)
{
@ZombieAttributes(victim, attacker);
bio_set_user_money(
attacker,
bio_get_user_money(attacker) + get_pcvar_num(m_pCvar_MoneyInfection)
);
SetHookChainArg(4, ATYPE_FLOAT, 0.0);
return;
}
}
new money = floatround(
damage * get_pcvar_float(m_pCvar_MoneyDamageRatio),
floatround_floor
);
bio_set_user_money(
attacker,
bio_get_user_money(attacker) + money
);
SetHookChainArg(4, ATYPE_FLOAT, damage * 10.0);
}
/*============================================================
SPEED
============================================================*/
@CBasePlayer_ResetMaxSpeed_Pre(id)
{
if (!is_user_alive(id))
return HC_CONTINUE;
if (IsZombie(id))
{
new data[ClassData];
ArrayGetArray(m_arrClassesVec, m_varClass[id], data);
set_entvar(id, var_maxspeed, data[ClassSpeed]);
return HC_SUPERCEDE;
}
new weapon = get_member(id, m_pActiveItem);
new Float:speed;
if (!is_nullent(weapon))
ExecuteHamB(Ham_CS_Item_GetMaxSpeed, weapon, speed);
else
speed = 240.0;
set_entvar(id, var_maxspeed, speed);
return HC_SUPERCEDE;
}
/*============================================================
KILLED
============================================================*/
@CBasePlayer_Killed_Post(id, attacker, gib)
{
if (is_user_alive(id))
return;
new TeamName:team = get_member(id, m_iTeam);
if (team == TEAM_SPECTATOR || team == TEAM_UNASSIGNED)
return;
if (!get_member_game(m_bGameStarted) ||
get_member_game(m_bRoundTerminating))
return;
if (IsZombie(id) &&
m_bZombieRespawnAllowed &&
CanZombieRespawn(id))
{
new Float:time = get_pcvar_float(m_pCvar_ZombieRespawnTime);
if (time > 0.0)
{
BIT_ADD(m_bitZombieRespawn, BIT_PLAYER(id));
m_iZombieRespawn[id]++;
set_member(
id,
m_flRespawnPending,
get_gametime() + time
);
return;
}
}
RequestFrame("@RequestFrame_CheckChangeClassWinConditions");
}
/*============================================================
ENTITY SPAWN
============================================================*/
@FM_Spawn_Pre(entity)
{
new classname[32];
get_entvar(entity, var_classname, classname, charsmax(classname));
if (!TrieKeyExists(m_trUselessEntities, classname))
return FMRES_IGNORED;
forward_return(FMV_CELL, NULLENT);
return FMRES_SUPERCEDE;
}
/*============================================================
ZOMBIE / HUMAN
============================================================*/
@ZombieAttributes(id, attacker)
{
BIT_ADD(m_bitZombie, BIT_PLAYER(id));
new data[ClassData];
ArrayGetArray(m_arrClassesVec, m_varClass[id], data);
set_entvar(id, var_health, data[ClassHealth]);
set_entvar(id, var_gravity, data[ClassGravity]);
set_entvar(id, var_armorvalue, 0.0);
set_member(id, m_iKevlar, ARMOR_NONE);
rg_set_user_model(id, data[ClassModel]);
rg_remove_all_items(id);
rg_give_item(id, "weapon_knife");
set_entvar(id, var_viewmodel, data[ClassClaw]);
set_entvar(id, var_weaponmodel, NULL_STRING);
rh_emit_sound2(
id,
0,
CHAN_VOICE,
ZOMBIE_SCREAM[random(sizeof ZOMBIE_SCREAM)]
);
@UTIL_DeathMsg(
attacker ? attacker : id,
id,
0,
"infection"
);
@UTIL_ScoreAttrib(id, 0);
rg_set_user_team(id, TEAM_TERRORIST);
if (attacker)
{
ExecuteHamB(Ham_AddPoints, id, 0, true);
ExecuteHamB(Ham_AddPoints, attacker, 1, true);
set_member(
id,
m_iDeaths,
get_member(id, m_iDeaths) + 1
);
}
if (get_member_game(m_bGameStarted) &&
!get_member_game(m_bFreezePeriod))
RequestFrame("@RequestFrame_CheckChangeClassWinConditions");
}
@HumanAttributes(id)
{
rg_set_user_team(id, TEAM_CT);
BIT_SUB(m_bitZombie, BIT_PLAYER(id));
set_entvar(id, var_health, 100.0);
set_entvar(id, var_gravity, 1.0);
rg_reset_user_model(id);
set_entvar(id, var_viewmodel, "models/v_knife.mdl");
set_entvar(id, var_weaponmodel, "models/p_knife.mdl");
rg_remove_all_items(id);
rg_give_item(id, "weapon_knife");
rg_give_item(id, "weapon_g3sg1");
rg_set_user_bpammo(id, WEAPON_G3SG1, 999);
if (get_member_game(m_bGameStarted) &&
!get_member_game(m_bFreezePeriod))
RequestFrame("@RequestFrame_CheckChangeClassWinConditions");
}
@RequestFrame_ApplyPlayerAttributes(id)
{
if (!is_user_connected(id))
return;
if (IsZombie(id))
@ZombieAttributes(id, 0);
else
@HumanAttributes(id);
}
/*============================================================
SPAWN POINTS
============================================================*/
@Select_ZombieSpawnPoint(id)
{
new numPoints = ArraySize(m_arrSpawnPoints);
if (!numPoints)
return 0;
new safeSpot;
new farthestSpot;
new Float:safeDistance = -1.0;
new Float:farthestDistance = -1.0;
new Float:minDistance =
get_pcvar_float(m_pCvar_ZombieRespawnMinDistance);
minDistance *= minDistance;
new Float:spawnOrigin[3];
new Float:playerOrigin[3];
for (new i; i < numPoints; i++)
{
new spot = ArrayGetCell(m_arrSpawnPoints, i);
if (is_nullent(spot))
continue;
get_entvar(spot, var_origin, spawnOrigin);
if (!IsHullVacant(id, spawnOrigin, HULL_HUMAN))
continue;
new Float:nearest = 999999999.0;
for (new j = 1; j <= MaxClients; j++)
{
if (!is_user_alive(j) || IsZombie(j))
continue;
get_entvar(j, var_origin, playerOrigin);
new Float:distance =
get_distance_squared(spawnOrigin, playerOrigin);
if (distance < nearest)
nearest = distance;
}
if (nearest > farthestDistance)
{
farthestDistance = nearest;
farthestSpot = spot;
}
if (nearest < minDistance)
continue;
if (nearest > safeDistance)
{
safeDistance = nearest;
safeSpot = spot;
}
}
if (!is_nullent(safeSpot))
return safeSpot;
if (!is_nullent(farthestSpot))
return farthestSpot;
return @Select_SpawnPoint(id);
}
@Select_SpawnPoint(id)
{
new numPoints = ArraySize(m_arrSpawnPoints);
if (!numPoints)
return 0;
new spot;
new lastSpot = m_iLastSpawn;
new Float:origin[3];
do
{
if (++lastSpot >= numPoints)
lastSpot = 0;
spot = ArrayGetCell(m_arrSpawnPoints, lastSpot);
if (is_nullent(spot))
continue;
get_entvar(spot, var_origin, origin);
if (!IsHullVacant(id, origin, HULL_HUMAN))
continue;
break;
}
while (lastSpot != m_iLastSpawn);
if (is_nullent(spot))
return 0;
m_iLastSpawn = lastSpot;
return spot;
}
@Forcing_LevelInitialize()
{
if (!ArraySize(m_arrSpawnPoints))
{
new entity = NULLENT;
while ((entity = rg_find_ent_by_class(
entity,
"info_player_start",
true
)))
ArrayPushCell(m_arrSpawnPoints, entity);
entity = NULLENT;
while ((entity = rg_find_ent_by_class(
entity,
"info_player_deathmatch",
true
)))
ArrayPushCell(m_arrSpawnPoints, entity);
}
new count = ArraySize(m_arrSpawnPoints);
set_member_game(m_iSpawnPointCount_Terrorist, count);
set_member_game(m_iSpawnPointCount_CT, count);
set_member_game(m_bLevelInitialized, true);
}
/*============================================================
ZOMBIE RESPAWN
============================================================*/
static stock bool:CanZombieRespawn(id)
{
new alive = get_alive_players() + 1;
new zombies = get_alive_zombies();
new humans = get_alive_humans();
if (alive <= 1 || humans <= 1 || zombies <= 0)
return false;
if (zombies >= humans)
return false;
if (zombies + 1 >
floatround(
float(alive) * get_max_zombie_count(),
floatround_ceil
))
return false;
if (m_iZombieRespawn[id] >=
get_pcvar_num(m_pCvar_ZombieRespawnLimit))
return false;
new Float:chance =
100.0
- float(m_iZombieRespawn[id])
* get_pcvar_float(m_pCvar_ZombieRespawnChanceDecay);
return chance > 0.0 &&
random_float(0.0, 100.0) <= chance;
}
static stock stop_zombie_respawn()
{
m_bZombieRespawnAllowed = false;
for (new i = 1; i <= MaxClients; i++)
{
if (is_user_connected(i) && IsZombieRespawning(i))
set_member(i, m_flRespawnPending, 0.0);
}
m_bitZombieRespawn = 0;
}
/*============================================================
PLAYER COUNTS
============================================================*/
static stock get_alive_players()
{
new count;
for (new i = 1; i <= MaxClients; i++)
if (is_user_alive(i))
count++;
return count;
}
static stock get_alive_zombies()
{
new count;
for (new i = 1; i <= MaxClients; i++)
if (is_user_alive(i) && IsZombie(i))
count++;
return count;
}
static stock get_alive_humans()
{
new count;
for (new i = 1; i <= MaxClients; i++)
if (is_user_alive(i) && !IsZombie(i))
count++;
return count;
}
/*
* Зомби, которые ещё считаются участниками раунда:
* - живые зомби;
* - мёртвые зомби с ожидающим респавном.
*
* Это важно: мёртвый zombie с m_bitZombieRespawn
* не должен мгновенно давать победу людям.
*/
static stock get_zombies_in_round()
{
new count;
for (new i = 1; i <= MaxClients; i++)
{
if (!IsZombie(i))
continue;
if (is_user_alive(i) || IsZombieRespawning(i))
count++;
}
return count;
}
static stock get_zombie_player_ratio()
{
new players = get_alive_players();
if (players <= 4)
return 33;
if (players <= 9)
return 40;
if (players <= 19)
return 45;
return 50;
}
static stock get_zombie_time_ratio()
{
new Float:start = get_member_game(m_fRoundStartTime);
if (start <= 0.0)
return 50;
new Float:time = get_gametime() - start;
if (time < 30.0)
return 50;
if (time < 60.0)
return 45;
if (time < 120.0)
return 40;
return 35;
}
static stock get_max_zombie_count()
{
new players = get_alive_players();
if (!players)
return 0;
new playerRatio = get_zombie_player_ratio();
new timeRatio = get_zombie_time_ratio();
new ratio = playerRatio < timeRatio
? playerRatio
: timeRatio;
new count = floatround(
float(players) * float(ratio) / 100.0,
floatround_ceil
);
return count < 1 ? 1 : count;
}
/*============================================================
HELPERS
============================================================*/
static stock bool:IsZombie(id)
{
return BIT_VALID(m_bitZombie, BIT_PLAYER(id));
}
static stock bool:IsZombieRespawning(id)
{
return BIT_VALID(m_bitZombieRespawn, BIT_PLAYER(id));
}
static stock bool:IsPlaying(id)
{
new TeamName:team = get_member(id, m_iTeam);
return team == TEAM_CT || team == TEAM_TERRORIST;
}
static stock Float:get_distance_squared(
const Float:a[3],
const Float:b[3]
)
{
new Float:x = a[0] - b[0];
new Float:y = a[1] - b[1];
new Float:z = a[2] - b[2];
return x * x + y * y + z * z;
}
static stock bool:IsHullVacant(
id,
Float:origin[3],
hull
)
{
engfunc(
EngFunc_TraceHull,
origin,
origin,
0,
hull,
id,
0
);
return !get_tr2(0, TR_StartSolid) &&
!get_tr2(0, TR_AllSolid) &&
get_tr2(0, TR_InOpen);
}
/*============================================================
ENTITY / CONFIG
============================================================*/
static stock bool:Read_JsonField(const fileName[])
{
new path[PLATFORM_MAX_PATH];
get_localinfo("amxx_configsdir", path, charsmax(path));
add(path, charsmax(path), "/biohazard");
if (!dir_exists(path))
{
set_fail_state("Directory '%s' not found.", path);
return false;
}
formatex(
path,
charsmax(path),
"%s/%s.json",
path,
fileName
);
if (!file_exists(path))
{
set_fail_state("File '%s' not found.", path);
return false;
}
new JSON:jsonParse = json_parse(path, true);
if (jsonParse == Invalid_JSON)
{
set_fail_state("Parsing file '%s' error.", path);
return false;
}
new JSON:value = json_object_get_value(
jsonParse,
"useless_entities"
);
if (json_is_array(value))
{
m_trUselessEntities = TrieCreate();
new classname[32];
for (new i; i < json_array_get_count(value); i++)
{
json_array_get_string(
value,
i,
classname,
charsmax(classname)
);
TrieSetCell(
m_trUselessEntities,
classname,
1
);
}
json_free(value);
if (TrieGetSize(m_trUselessEntities))
m_fwdSpawn = register_forward(
FM_Spawn,
"@FM_Spawn_Pre"
);
else
{
TrieDestroy(m_trUselessEntities);
m_trUselessEntities = Invalid_Trie;
}
}
new JSON:classes = json_object_get_value(
jsonParse,
"classes"
);
if (json_is_array(classes))
{
new data[ClassData];
new JSON:item;
for (new i; i < json_array_get_count(classes); i++)
{
item = json_array_get_value(classes, i);
json_object_get_string(
item,
"name",
data[ClassName],
charsmax(data[ClassName])
);
data[ClassHealth] =
_:json_object_get_real(item, "health");
data[ClassSpeed] =
_:json_object_get_real(item, "speed");
data[ClassGravity] =
_:json_object_get_real(item, "gravity");
json_object_get_string(
item,
"model",
data[ClassModel],
charsmax(data[ClassModel])
);
precache_model(fmt(
"models/player/%s/%s.mdl",
data[ClassModel],
data[ClassModel]
));
json_object_get_string(
item,
"claw",
data[ClassClaw],
charsmax(data[ClassClaw])
);
precache_model(data[ClassClaw]);
ArrayPushArray(
m_arrClassesVec,
data
);
json_free(item);
}
m_iNumClasses = ArraySize(m_arrClassesVec);
json_free(classes);
}
json_free(jsonParse);
return true;
}
/*============================================================
UTIL
============================================================*/
@UTIL_DeathMsg(killer, victim, headshot, const weapon[])
{
message_begin(
MSG_ALL,
get_user_msgid("DeathMsg")
);
write_byte(killer);
write_byte(victim);
write_byte(headshot);
write_string(weapon);
message_end();
}
@UTIL_ScoreAttrib(id, flags)
{
message_begin(
MSG_ALL,
get_user_msgid("ScoreAttrib")
);
write_byte(id);
write_byte(flags);
message_end();
}
/*============================================================
NATIVES
============================================================*/
@native_get_user_money(amxx, argc)
{
new id = get_param(1);
if (!is_user_connected(id))
{
log_error(
AMX_ERR_NATIVE,
"Invalid index '%d'.",
id
);
return -1;
}
return m_iMoney[id];
}
@native_set_user_money(amxx, argc)
{
new id = get_param(1);
if (!is_user_connected(id))
{
log_error(
AMX_ERR_NATIVE,
"Invalid index '%d'.",
id
);
return 0;
}
new amount = get_param(2);
if (amount < 0)
amount = 0;
else if (amount > 12000)
amount = 12000;
message_begin(
MSG_ONE,
get_user_msgid("Money"),
.player = id
);
write_long(amount);
write_byte(get_param(3));
message_end();
set_member(id, m_iAccount, amount);
m_iMoney[id] = amount;
return 1;
}