Загрузка данных
#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;
enum _:SpawnData
{
Float:SpawnOrigin[3],
Float:SpawnAngles[3]
};
new Array:m_arrSpawnPointsVec = Invalid_Array;
new Array:m_arrDefaultSpawnPointsVec = Invalid_Array;
new m_szMapName[64];
new m_szSpawnFile[PLATFORM_MAX_PATH];
new m_iNumClasses;
new m_iNumSpawnPoints;
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_MoneyAsJoin;
new m_pCvar_MoneyDamageRatio;
new m_pCvar_MoneyInfection;
new m_pCvar_MoneyKill;
new m_pCvar_ZombieRegenAmount;
new m_pCvar_ZombieRegenInterval;
new m_pCvar_ZombieRegenDelay;
new m_fwdSpawn;
new m_varClass[MAX_CLIENTS + 1];
new m_iZombieRespawn[MAX_CLIENTS + 1];
new m_iMoney[MAX_CLIENTS + 1];
new Float:m_flZombieRegenTime[MAX_CLIENTS + 1];
new Float:m_flZombieLastDamage[MAX_CLIENTS + 1];
new m_bitZombie;
new m_bitZombieRespawn;
public plugin_precache()
{
register_plugin("Biohazard", "0.0.1", "oxsi. x Vision");
m_arrClassesVec = ArrayCreate(ClassData);
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", "3.0");
m_pCvar_ZombieRespawnMinDistance =
register_cvar("bio_zombie_respawn_min_distance", "1200.0");
m_pCvar_ZombieRespawnLimit =
register_cvar("bio_zombie_respawn_limit", "3");
m_pCvar_ZombieRespawnChanceDecay =
register_cvar("bio_zombie_chance_decay", "25.0");
m_pCvar_MoneyAsJoin =
register_cvar("bio_money_join", "350");
m_pCvar_MoneyDamageRatio =
register_cvar("bio_money_damage_ratio", "0.26");
m_pCvar_MoneyInfection =
register_cvar("bio_money_infection", "100");
m_pCvar_MoneyKill =
register_cvar("bio_money_kill", "100");
m_pCvar_ZombieRegenAmount =
register_cvar("bio_zombie_regen_amount", "1.0");
m_pCvar_ZombieRegenInterval =
register_cvar("bio_zombie_regen_interval", "0.15");
m_pCvar_ZombieRegenDelay =
register_cvar("bio_zombie_regen_delay", "2.48");
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_AddAccount,
"@CBasePlayer_AddAccount_Pre"
);
RegisterHookChain(
RG_CBasePlayer_TakeDamage,
"@CBasePlayer_TakeDamage_Pre"
);
RegisterHookChain(
RG_CBasePlayer_ResetMaxSpeed,
"@CBasePlayer_ResetMaxSpeed_Pre"
);
RegisterHookChain(
RG_CBasePlayer_Killed,
"@CBasePlayer_Killed_Post",
true
);
RegisterHookChain(
RG_CBasePlayer_PreThink,
"@CBasePlayer_PreThink_Pre"
);
}
public plugin_cfg()
{
m_arrSpawnPointsVec = ArrayCreate(SpawnData);
m_arrDefaultSpawnPointsVec = ArrayCreate(1);
get_mapname(
m_szMapName,
charsmax(m_szMapName)
);
get_localinfo(
"amxx_configsdir",
m_szSpawnFile,
charsmax(m_szSpawnFile)
);
add(
m_szSpawnFile,
charsmax(m_szSpawnFile),
"/biohazard/spawns.json"
);
/*
* Загружаем стандартные spawn entity.
*/
@Load_DefaultSpawnPoints();
/*
* Загружаем custom spawn points
* только для текущей карты.
*/
Load_SpawnPoints();
/*
* После загрузки spawn points
* обновляем количество spawn points
* в GameRules.
*/
@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_arrSpawnPointsVec != Invalid_Array)
ArrayDestroy(m_arrSpawnPointsVec);
if (m_arrDefaultSpawnPointsVec != Invalid_Array)
ArrayDestroy(m_arrDefaultSpawnPointsVec);
}
public client_putinserver(id)
{
m_varClass[id] = 0;
}
public client_disconnected(id)
{
m_varClass[id] = 0;
m_iZombieRespawn[id] = 0;
m_iMoney[id] = 0;
m_flZombieRegenTime[id] = 0.0;
m_flZombieLastDamage[id] = 0.0;
BIT_SUB(m_bitZombie, id);
BIT_SUB(m_bitZombieRespawn, id);
}
public cmd_infect(id)
{
@ZombieAttributes(id, 0);
}
public cmd_antidote(id)
{
@HumanAttributes(id);
}
@Open_ClassesMenu(id)
{
if (!m_iNumClasses)
{
client_print(
id,
print_chat,
"Zombie classes not found!"
);
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. %sBack",
pos ? "\w" : "\d"
);
len += formatex(
menu[len],
charsmax(menu) - len,
"^n\y9. %sNext",
end < m_iNumClasses ? "\w" : "\d"
);
formatex(
menu[len],
charsmax(menu) - len,
"^n\y0. \wExit"
);
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,
"You have selected the %s class.",
data[ClassName]
);
return PLUGIN_CONTINUE;
}
@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];
new 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)
{
if (!is_user_connected(receiver))
return PLUGIN_CONTINUE;
set_msg_arg_int(
1,
get_msg_argtype(1),
m_iMoney[receiver]
);
return PLUGIN_CONTINUE;
}
@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;
new TeamName:winTeam = TEAM_UNASSIGNED;
new TeamName:team;
switch (status)
{
case WINSTATUS_CTS:
{
client_print(
0,
print_center,
"Humans won!"
);
client_cmd(
0,
"spk ^"ambience/xtal_down1.wav^""
);
winTeam = TEAM_CT;
}
case WINSTATUS_TERRORISTS:
{
client_print(
0,
print_center,
"Zombies won!"
);
client_cmd(
0,
"spk ^"ambience/the_horror1.wav^""
);
winTeam = TEAM_TERRORIST;
}
case WINSTATUS_DRAW:
{
client_print(
0,
print_center,
"No one won!"
);
client_cmd(
0,
"spk ^"ambience/3dmstart.wav^""
);
}
}
for (new i = 1; i <= MaxClients; i++)
{
if (!is_user_connected(i))
continue;
team = get_member(i, m_iTeam);
if (
team != TEAM_CT &&
team != TEAM_TERRORIST
)
{
continue;
}
if (winTeam == team)
{
new reward = random_num(100, 500);
bio_set_user_money(
i,
bio_get_user_money(i) + reward
);
client_print(
i,
print_chat,
"Won %s!",
team == TEAM_CT
? "humans"
: "zombies"
);
client_print(
i,
print_chat,
"Reward %d.",
reward
);
}
}
}
@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();
}
@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_alive_zombies();
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
);
}
}
@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);
}
}
/*
* ============================================================
* PLAYER SPAWN
* ============================================================
*
* Если custom points существуют:
*
* custom spawn
*
* Если custom points отсутствуют:
*
* вообще не вмешиваемся в ReGameDLL.
* Он использует стандартные:
*
* info_player_start
* info_player_deathmatch
*
* Если custom point существует, но выбрать его невозможно:
*
* fallback -> стандартный spawn.
*
* ============================================================
*/
@CSGameRules_GetPlayerSpawnSpot_Pre(id)
{
if (!IsPlaying(id))
return HC_CONTINUE;
/*
* CUSTOM SPAWN НЕ НАЙДЕН.
*
* Полностью отдаём управление ReGameDLL.
*/
if (!is_spawn_points())
return HC_CONTINUE;
new index;
if (BIT_VALID(m_bitZombie, id))
index = @Select_ZombieSpawnPoint(id);
else
index = @Select_SpawnPoint(id);
/*
* Не удалось подобрать custom point.
*
* Используем стандартную spawn entity.
*/
if (index < 0)
return HC_CONTINUE;
new data[SpawnData];
if (!get_spawn_point(index, data))
return HC_CONTINUE;
new Float:origin[3];
origin = data[SpawnOrigin];
origin[2] += 1.0;
set_entvar(
id,
var_origin,
origin
);
set_entvar(
id,
var_v_angle,
data[SpawnAngles]
);
set_entvar(
id,
var_velocity,
NULL_VECTOR
);
set_entvar(
id,
var_angles,
data[SpawnAngles]
);
set_entvar(
id,
var_punchangle,
NULL_VECTOR
);
set_entvar(
id,
var_fixangle,
1
);
/*
* GetPlayerSpawnSpot() должен вернуть entity.
*
* Для custom spawn нам не нужен реальный
* spawn entity как источник координат.
*
* Используем стандартный spawn entity только
* как технический return value.
*/
new spot = @Get_DefaultSpawnEntity(id);
if (is_nullent(spot))
{
/*
* На карте вообще нет стандартных spawn entity.
*
* Возвращаем самого игрока как последний fallback.
*/
spot = id;
}
SetHookChainReturn(
ATYPE_INTEGER,
spot
);
return HC_SUPERCEDE;
}
@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 (BIT_VALID(m_bitZombieRespawn, id))
{
BIT_SUB(
m_bitZombieRespawn,
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;
}
@CBasePlayer_AddAccount_Pre(
id,
amount,
RewardType:type,
bool:trackChange
)
{
if (type == RT_NONE)
return HC_CONTINUE;
if (type == RT_PLAYER_JOIN)
{
new joinMoney =
get_pcvar_num(
m_pCvar_MoneyAsJoin
);
m_iMoney[id] = joinMoney;
SetHookChainArg(
2,
ATYPE_INTEGER,
joinMoney
);
return HC_CONTINUE;
}
return HC_SUPERCEDE;
}
@CBasePlayer_TakeDamage_Pre(
victim,
inflictor,
attacker,
Float:damage,
damage_type
)
{
if (!is_user_connected(victim))
return;
if (!rg_is_player_can_takedamage(
victim,
attacker
))
{
return;
}
if (BIT_VALID(m_bitZombie, victim))
{
new Float:gametime =
get_gametime();
m_flZombieLastDamage[victim] =
gametime;
m_flZombieRegenTime[victim] =
gametime +
get_pcvar_float(
m_pCvar_ZombieRegenInterval
);
}
if (
victim == attacker ||
!is_user_connected(attacker)
)
{
return;
}
if (BIT_VALID(m_bitZombie, attacker))
{
new weapon =
get_member(
attacker,
m_pActiveItem
);
if (
!is_nullent(weapon) &&
get_member(
weapon,
m_iId
) == WEAPON_KNIFE &&
inflictor == attacker &&
!(damage_type & (
DMG_GRENADE |
DMG_BLAST
))
)
{
if (get_alive_humans() <= 1)
return;
@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
);
}
@CBasePlayer_ResetMaxSpeed_Pre(id)
{
if (!is_user_alive(id))
return HC_CONTINUE;
if (BIT_VALID(m_bitZombie, 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;
}
@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 (is_user_connected(attacker))
{
bio_set_user_money(
attacker,
bio_get_user_money(attacker) +
get_pcvar_num(
m_pCvar_MoneyKill
)
);
}
if (
BIT_VALID(m_bitZombie, id) &&
m_bZombieRespawnAllowed &&
can_zombie_respawn(id)
)
{
new Float:time =
get_pcvar_float(
m_pCvar_ZombieRespawnTime
);
if (time > 0.0)
{
BIT_ADD(
m_bitZombieRespawn,
id
);
m_iZombieRespawn[id]++;
set_member(
id,
m_flRespawnPending,
get_gametime() + time
);
}
}
RequestFrame(
"@RequestFrame_CheckChangeClassWinConditions"
);
}
@CBasePlayer_PreThink_Pre(id)
{
if (!is_user_alive(id))
return;
if (get_member_game(m_bRoundTerminating))
return;
if (!BIT_VALID(m_bitZombie, id))
return;
new data[ClassData];
ArrayGetArray(
m_arrClassesVec,
m_varClass[id],
data
);
new Float:health;
get_entvar(
id,
var_health,
health
);
new Float:gametime =
get_gametime();
if (
gametime -
m_flZombieLastDamage[id] <
get_pcvar_float(
m_pCvar_ZombieRegenDelay
)
)
{
return;
}
if (
gametime <
m_flZombieRegenTime[id]
)
{
return;
}
health +=
get_pcvar_float(
m_pCvar_ZombieRegenAmount
);
if (health > data[ClassHealth])
health = data[ClassHealth];
set_entvar(
id,
var_health,
health
);
m_flZombieRegenTime[id] =
gametime +
get_pcvar_float(
m_pCvar_ZombieRegenInterval
);
}
@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;
}
@ZombieAttributes(id, attacker)
{
BIT_ADD(
m_bitZombie,
id
);
m_flZombieRegenTime[id] =
get_gametime();
m_flZombieLastDamage[id] =
get_gametime();
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,
id
);
m_flZombieRegenTime[id] = 0.0;
m_flZombieLastDamage[id] = 0.0;
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"
);
give_human_equipment(id);
if (
get_member_game(m_bGameStarted) &&
!get_member_game(m_bFreezePeriod)
)
{
RequestFrame(
"@RequestFrame_CheckChangeClassWinConditions"
);
}
}
@RequestFrame_ApplyPlayerAttributes(id)
{
if (!is_user_connected(id))
return;
if (BIT_VALID(m_bitZombie, id))
{
if (is_user_bot(id))
{
m_varClass[id] =
random(m_iNumClasses);
}
@ZombieAttributes(
id,
0
);
}
else
{
@HumanAttributes(id);
}
}
/*
* ============================================================
* CUSTOM ZOMBIE SPAWN
* ============================================================
*/
@Select_ZombieSpawnPoint(id)
{
new numPoints =
ArraySize(m_arrSpawnPointsVec);
if (!numPoints)
return -1;
new safeIndex = -1;
new farthestIndex = -1;
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 = 0; i < numPoints; i++)
{
new data[SpawnData];
if (!get_spawn_point(i, data))
continue;
spawnOrigin =
data[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) ||
BIT_VALID(m_bitZombie, 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;
farthestIndex =
i;
}
if (nearest < minDistance)
continue;
if (nearest > safeDistance)
{
safeDistance =
nearest;
safeIndex =
i;
}
}
if (safeIndex >= 0)
return safeIndex;
if (farthestIndex >= 0)
return farthestIndex;
/*
* Custom точки существуют,
* но все они заняты.
*
* Пытаемся использовать обычный
* custom spawn.
*/
return @Select_SpawnPoint(id);
}
/*
* ============================================================
* CUSTOM HUMAN SPAWN
* ============================================================
*/
@Select_SpawnPoint(id)
{
new numPoints =
ArraySize(m_arrSpawnPointsVec);
if (!numPoints)
return -1;
new lastSpot =
m_iLastSpawn;
new spot = -1;
new Float:origin[3];
do
{
if (++lastSpot >= numPoints)
lastSpot = 0;
new data[SpawnData];
if (!get_spawn_point(
lastSpot,
data
))
{
continue;
}
origin =
data[SpawnOrigin];
if (!IsHullVacant(
id,
origin,
HULL_HUMAN
))
{
continue;
}
spot = lastSpot;
break;
}
while (lastSpot != m_iLastSpawn);
if (spot < 0)
return -1;
m_iLastSpawn =
spot;
return spot;
}
/*
* ============================================================
* STANDARD SPAWN
* ============================================================
*/
@Load_DefaultSpawnPoints()
{
if (
m_arrDefaultSpawnPointsVec ==
Invalid_Array
)
{
return;
}
ArrayClear(
m_arrDefaultSpawnPointsVec
);
new entity = NULLENT;
while (
(
entity =
rg_find_ent_by_class(
entity,
"info_player_start",
true
)
)
)
{
ArrayPushCell(
m_arrDefaultSpawnPointsVec,
entity
);
}
entity = NULLENT;
while (
(
entity =
rg_find_ent_by_class(
entity,
"info_player_deathmatch",
true
)
)
)
{
ArrayPushCell(
m_arrDefaultSpawnPointsVec,
entity
);
}
server_print(
"[Biohazard] Default spawn points: %d",
ArraySize(
m_arrDefaultSpawnPointsVec
)
);
}
static stock @Get_DefaultSpawnEntity(id)
{
new count =
ArraySize(
m_arrDefaultSpawnPointsVec
);
if (!count)
return NULLENT;
/*
* Сначала ищем свободный spawn.
*/
new Float:origin[3];
for (new i = 0; i < count; i++)
{
new spot =
ArrayGetCell(
m_arrDefaultSpawnPointsVec,
i
);
if (is_nullent(spot))
continue;
get_entvar(
spot,
var_origin,
origin
);
if (IsHullVacant(
id,
origin,
HULL_HUMAN
))
{
return spot;
}
}
/*
* Если все заняты — просто
* возвращаем первую валидную.
*/
for (new i = 0; i < count; i++)
{
new spot =
ArrayGetCell(
m_arrDefaultSpawnPointsVec,
i
);
if (!is_nullent(spot))
return spot;
}
return NULLENT;
}
/*
* ============================================================
* LEVEL INITIALIZATION
* ============================================================
*/
@Forcing_LevelInitialize()
{
new count;
/*
* Есть custom spawn?
*/
if (is_spawn_points())
{
count =
ArraySize(
m_arrSpawnPointsVec
);
server_print(
"[Biohazard] Using %d custom spawn points on %s.",
count,
m_szMapName
);
}
else
{
/*
* Custom spawn нет.
*
* Используем стандартные.
*/
count =
ArraySize(
m_arrDefaultSpawnPointsVec
);
server_print(
"[Biohazard] No custom spawn points on %s. Using %d default spawn points.",
m_szMapName,
count
);
}
set_member_game(
m_iSpawnPointCount_Terrorist,
count
);
set_member_game(
m_iSpawnPointCount_CT,
count
);
set_member_game(
m_bLevelInitialized,
true
);
}
/*
* ============================================================
* SPAWN DATA
* ============================================================
*/
static stock add_spawn_points(id)
{
if (!is_user_connected(id))
return;
if (
m_arrSpawnPointsVec ==
Invalid_Array
)
{
return;
}
new data[SpawnData];
get_entvar(
id,
var_origin,
data[SpawnOrigin]
);
get_entvar(
id,
var_v_angle,
data[SpawnAngles]
);
data[SpawnAngles][0] = 0.0;
data[SpawnAngles][2] = 0.0;
ArrayPushArray(
m_arrSpawnPointsVec,
data
);
m_iNumSpawnPoints =
ArraySize(
m_arrSpawnPointsVec
);
}
static stock find_nearest_spawn(id)
{
if (!is_spawn_points())
return -1;
new Float:playerOrigin[3];
get_entvar(
id,
var_origin,
playerOrigin
);
new Float:bestDistance =
999999999.0;
new bestIndex = -1;
new data[SpawnData];
for (
new i = 0;
i < m_iNumSpawnPoints;
i++
)
{
if (!get_spawn_point(
i,
data
))
{
continue;
}
new Float:distance =
get_distance_squared(
playerOrigin,
data[SpawnOrigin]
);
if (distance < bestDistance)
{
bestDistance =
distance;
bestIndex =
i;
}
}
return bestIndex;
}
static stock bool:del_spawn_points(index)
{
if (
index < 0 ||
index >= m_iNumSpawnPoints
)
{
return false;
}
ArrayDeleteItem(
m_arrSpawnPointsVec,
index
);
m_iNumSpawnPoints =
ArraySize(
m_arrSpawnPointsVec
);
if (
m_iLastSpawn >=
m_iNumSpawnPoints
)
{
m_iLastSpawn = 0;
}
return true;
}
static stock bool:move_spawn_points(id, index)
{
new data[SpawnData];
if (!get_spawn_point(
index,
data
))
{
return false;
}
get_entvar(
id,
var_origin,
data[SpawnOrigin]
);
ArraySetArray(
m_arrSpawnPointsVec,
index,
data
);
return true;
}
static stock bool:rotate_spawn_points(id, index)
{
new data[SpawnData];
if (!get_spawn_point(
index,
data
))
{
return false;
}
new Float:vecAngles[3];
get_entvar(
id,
var_v_angle,
vecAngles
);
data[SpawnAngles][0] = 0.0;
data[SpawnAngles][1] = vecAngles[1];
data[SpawnAngles][2] = 0.0;
ArraySetArray(
m_arrSpawnPointsVec,
index,
data
);
return true;
}
static stock bool:teleport_to_spawn(id, index)
{
new data[SpawnData];
if (!get_spawn_point(
index,
data
))
{
return false;
}
set_entvar(
id,
var_origin,
data[SpawnOrigin]
);
set_entvar(
id,
var_v_angle,
data[SpawnAngles]
);
set_entvar(
id,
var_angles,
data[SpawnAngles]
);
set_entvar(
id,
var_velocity,
NULL_VECTOR
);
set_entvar(
id,
var_punchangle,
NULL_VECTOR
);
set_entvar(
id,
var_fixangle,
1
);
return true;
}
static stock del_all_spawn_points()
{
if (
m_arrSpawnPointsVec ==
Invalid_Array
)
{
return;
}
ArrayClear(
m_arrSpawnPointsVec
);
m_iNumSpawnPoints = 0;
m_iLastSpawn = 0;
}
static stock bool:get_spawn_point(
index,
data[SpawnData]
)
{
if (
m_arrSpawnPointsVec ==
Invalid_Array
)
{
return false;
}
if (
index < 0 ||
index >=
ArraySize(
m_arrSpawnPointsVec
)
)
{
return false;
}
ArrayGetArray(
m_arrSpawnPointsVec,
index,
data
);
return true;
}
/*
* ============================================================
* SPAWN CHECKS
* ============================================================
*/
static stock bool:is_spawn_points()
{
return (
m_arrSpawnPointsVec != Invalid_Array &&
ArraySize(m_arrSpawnPointsVec) > 0
);
}
static stock bool:is_default_spawn_points()
{
return (
m_arrDefaultSpawnPointsVec != Invalid_Array &&
ArraySize(m_arrDefaultSpawnPointsVec) > 0
);
}
static stock select_default_spawn_points(id)
{
new count =
ArraySize(
m_arrDefaultSpawnPointsVec
);
if (!count)
return NULLENT;
return ArrayGetCell(
m_arrDefaultSpawnPointsVec,
random(count)
);
}
static stock select_spawn_points(id)
{
new count =
ArraySize(
m_arrSpawnPointsVec
);
if (!count)
return -1;
return random(count);
}
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;
}
/*
* ============================================================
* JSON 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;
}
}
else if (value != Invalid_JSON)
{
json_free(value);
}
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
);
if (!json_is_object(item))
{
if (item != Invalid_JSON)
json_free(item);
continue;
}
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);
}
else if (classes != Invalid_JSON)
{
json_free(classes);
}
json_free(jsonParse);
return true;
}
/*
* ============================================================
* LOAD CUSTOM SPAWN POINTS
* ============================================================
*
* JSON:
*
* {
* "maps": {
* "de_dust2": {
* "spawns": [
* {
* "origin": [125.0, 300.0, 64.0],
* "angles": [0.0, 90.0, 0.0]
* }
* ]
* }
* }
* }
*
* ============================================================
*/
static stock bool:Load_SpawnPoints()
{
if (
m_arrSpawnPointsVec ==
Invalid_Array
)
{
return false;
}
ArrayClear(
m_arrSpawnPointsVec
);
m_iNumSpawnPoints = 0;
m_iLastSpawn = 0;
/*
* Файла нет.
*
* Это НЕ ошибка.
*
* Просто custom spawn отсутствует,
* поэтому будет использоваться
* стандартный spawn.
*/
if (!file_exists(m_szSpawnFile))
{
server_print(
"[Biohazard] Spawn file '%s' not found. Using default spawns.",
m_szSpawnFile
);
return true;
}
new JSON:json =
json_parse(
m_szSpawnFile,
true
);
if (json == Invalid_JSON)
{
server_print(
"[Biohazard] Failed to parse '%s'. Using default spawns.",
m_szSpawnFile
);
return false;
}
new JSON:maps =
json_object_get_value(
json,
"maps"
);
if (!json_is_object(maps))
{
if (maps != Invalid_JSON)
json_free(maps);
json_free(json);
server_print(
"[Biohazard] 'maps' object not found. Using default spawns."
);
return true;
}
/*
* Получаем:
*
* maps[current_map]
*/
new JSON:map =
json_object_get_value(
maps,
m_szMapName
);
if (!json_is_object(map))
{
if (map != Invalid_JSON)
json_free(map);
json_free(maps);
json_free(json);
server_print(
"[Biohazard] No custom spawns for map '%s'. Using default spawns.",
m_szMapName
);
return true;
}
/*
* Получаем:
*
* maps[current_map].spawns
*/
new JSON:spawns =
json_object_get_value(
map,
"spawns"
);
if (!json_is_array(spawns))
{
if (spawns != Invalid_JSON)
json_free(spawns);
json_free(map);
json_free(maps);
json_free(json);
server_print(
"[Biohazard] No spawn array for map '%s'. Using default spawns.",
m_szMapName
);
return true;
}
new data[SpawnData];
for (
new i = 0;
i < json_array_get_count(spawns);
i++
)
{
new JSON:item =
json_array_get_value(
spawns,
i
);
if (!json_is_object(item))
{
if (item != Invalid_JSON)
json_free(item);
continue;
}
/*
* Обязательно обнуляем структуру.
*/
data[SpawnOrigin][0] = 0.0;
data[SpawnOrigin][1] = 0.0;
data[SpawnOrigin][2] = 0.0;
data[SpawnAngles][0] = 0.0;
data[SpawnAngles][1] = 0.0;
data[SpawnAngles][2] = 0.0;
/*
* origin
*/
new JSON:origin =
json_object_get_value(
item,
"origin"
);
if (
json_is_array(origin) &&
json_array_get_count(origin) >= 3
)
{
for (new j = 0; j < 3; j++)
{
data[SpawnOrigin][j] =
json_array_get_real(
origin,
j
);
}
}
/*
* angles
*/
new JSON:angles =
json_object_get_value(
item,
"angles"
);
if (
json_is_array(angles) &&
json_array_get_count(angles) >= 3
)
{
for (new j = 0; j < 3; j++)
{
data[SpawnAngles][j] =
json_array_get_real(
angles,
j
);
}
}
/*
* Если origin отсутствует —
* такую точку не добавляем.
*/
if (
!json_is_array(origin) ||
json_array_get_count(origin) < 3
)
{
if (origin != Invalid_JSON)
json_free(origin);
if (angles != Invalid_JSON)
json_free(angles);
json_free(item);
continue;
}
ArrayPushArray(
m_arrSpawnPointsVec,
data
);
if (origin != Invalid_JSON)
json_free(origin);
if (angles != Invalid_JSON)
json_free(angles);
json_free(item);
}
json_free(spawns);
json_free(map);
json_free(maps);
json_free(json);
m_iNumSpawnPoints =
ArraySize(
m_arrSpawnPointsVec
);
if (m_iNumSpawnPoints)
{
server_print(
"[Biohazard] Loaded %d custom spawn points for '%s'.",
m_iNumSpawnPoints,
m_szMapName
);
}
else
{
server_print(
"[Biohazard] Custom spawn points for '%s' not found. Using default spawns.",
m_szMapName
);
}
return true;
}
/*
* ============================================================
* ZOMBIE RESPAWN
* ============================================================
*/
static stock bool:can_zombie_respawn(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) &&
BIT_VALID(
m_bitZombieRespawn,
i
)
)
{
BIT_SUB(
m_bitZombieRespawn,
i
);
set_member(
i,
m_flRespawnPending,
0.0
);
}
}
}
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) &&
BIT_VALID(
m_bitZombie,
i
)
)
{
count++;
}
}
return count;
}
static stock get_alive_humans()
{
new count;
for (
new i = 1;
i <= MaxClients;
i++
)
{
if (
is_user_alive(i) &&
!BIT_VALID(
m_bitZombie,
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;
}
static stock bool:IsPlaying(id)
{
new TeamName:team =
get_member(
id,
m_iTeam
);
return
team == TEAM_CT ||
team == TEAM_TERRORIST;
}
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
);
}
static stock give_human_equipment(id)
{
new const primary_entities[][] =
{
"weapon_m3",
"weapon_xm1014",
"weapon_mac10",
"weapon_tmp",
"weapon_mp5navy",
"weapon_ump45",
"weapon_p90",
"weapon_galil",
"weapon_famas",
"weapon_ak47",
"weapon_m4a1",
"weapon_sg552",
"weapon_aug",
"weapon_scout",
"weapon_awp",
"weapon_g3sg1",
"weapon_sg550",
"weapon_m249"
};
new const secondary_entities[][] =
{
"weapon_p228",
"weapon_usp",
"weapon_glock18",
"weapon_deagle",
"weapon_fiveseven",
"weapon_elite"
};
new const equipments[][] =
{
"weapon_hegrenade",
"weapon_flashbang"
};
rg_give_item(
id,
"weapon_knife"
);
new primary =
random(
sizeof primary_entities
);
new primary_id =
get_weaponid(
primary_entities[primary]
);
rg_give_item(
id,
primary_entities[primary],
GT_REPLACE
);
rg_set_user_bpammo(
id,
any:primary_id,
999
);
new secondary =
random(
sizeof secondary_entities
);
new secondary_id =
get_weaponid(
secondary_entities[secondary]
);
rg_give_item(
id,
secondary_entities[secondary],
GT_REPLACE
);
rg_set_user_bpammo(
id,
any:secondary_id,
999
);
new grenade =
random(
sizeof equipments
);
rg_give_item(
id,
equipments[grenade]
);
if (random_num(0, 1))
{
new secondGrenade;
do
{
secondGrenade =
random(
sizeof equipments
);
}
while (
secondGrenade == grenade
);
rg_give_item(
id,
equipments[secondGrenade]
);
}
}
/*
* ============================================================
* MONEY NATIVES
* ============================================================
*/
@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();
}
@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;
/*
* m_iMoney — источник истины.
*/
m_iMoney[id] =
amount;
/*
* Синхронизируем CS account.
*/
set_member(
id,
m_iAccount,
amount
);
/*
* Отправляем отображение клиенту.
*/
message_begin(
MSG_ONE,
get_user_msgid("Money"),
.player = id
);
write_long(amount);
write_byte(
get_param(3)
);
message_end();
return 1;
}