/*******************************************************************************
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you must extend this exception
* to your version of the file.
*
********************************************************************************
Molotov Cocktail
Version 3.30
Maintained by: DynamicBits (Andy)
* Commands:
- say molotov - Buy a Molotov
- say /molotov - Buy a Molotov
- molotov_give <player|@all|@t|@ct|@al|@ax|@br|@b|@r|@y|@g> - Give Molotov(s) to a player, a team, or everyone
- molotov_cocktail [0|1] - Enable/disable the plugin (If no arguments, show the status)
- molotov_override [0|1] - Enable/disable the standard grenade override (If no arguments, show the status)
* Cvars
- molotov_enabled <0|1> - (Default: 1) Enable(1)/disable(0) the plugin
- molotov_price <N> - (Default: 300) Set the Molotov price (Counter-Strike only)
- molotov_damage <N> - (Default: 50.0) Set the damage done by initial Molotov explosion
- molotov_radius <N> - (Default: 150.0) Set the radius of Molotov damage
- molotov_firetime <N> - (Default: 6) Duration (in seconds) of fire effects, sounds, etc.
- molotov_firedamage <N> - (Default: 3) Amount of damage done by fire effects (every 0.2 secs)
- molotov_ff <0|1|-1|-2> - (Default: 1) Set Molotov friendly fire status (Was molotov_tk)
* 0 - Disable friendly fire for Molotovs (regardless of mp_friendlyfire)
* 1 - Enable friendly fire for Molotovs (regardless of mp_friendlyfire)
* -1 - Use mp_friendlyfire value
* -2 - Check bit 5 (decimal: 16) of mp_teamplay (DOD and TFC only)
- molotov_override_he <0|1> - (Default: 0) Override the mod's standard grenade automatically with Molotov (Was molotov_tempoverride)
- molotov_max <N> - (Default: 1) Limit carried Molotovs to this amount
* (Recommended: CSTRIKE: ≤ 10; DOD: ≤ 9; TFC: ≤ 4;)
- molotov_buyzone <0|1> - (Default: 1) Limit Molotov buying to buyzone (Counter-Strike only)
- molotov_menu <0|1> - (Default: 0) Enable menu at beginning of each round (Was amx_molotovmenu)
* Required Modules:
- Fakemeta
- Cstrike (Counter-Strike only)
- Csx (Counter-Strike only)
- Dodfun (Day of Defeat only)
- Dodx (Day of Defeat only)
- Tfcx (Team Fortress Classic only)
- Engine (TFC with debugging only)
* Changelog/Credit:
- DynamicBits
* Version 3.30 (2014-04-13)
- (Beta) Day of Defeat support was added
- (Beta) Team Fortress Classic support was added
- (Untested) Stats logging support was added
- New values for molotov_ff were added
- Friction/velocity after explosion was adjusted for realism
- Bottle breaking sound was added
- Molotov suicides no longer reward extra score points
- Frag count calculations were fixed
- Money calculations were fixed
- Override no longer sets a negative number of Molotovs
- Default price was changed to match standard grenades
- Console text now goes to correct console
- Text (non-VGUI) buy menu support was removed
- Converted fun functions to fakemeta_util functions (removed fun include)
- Various optimizations
- A few typos were fixed
* Version 3.20 (2008-11-20)
- My first public release
- Finally tracked down and fixed the intermittent crashing problem (I hope!)
- Modified default damage values
- molotov_cocktail/molotov_override commands now change settings *or* display status
- Broken Molotov model stays closer to the explosion (looks more realistic)
- Task IDs are now guaranteed to be unique
- Modified anti-lag calculations to be more accurate (less likely to lag)
- Changed amx_molotovmenu CVAR to molotov_menu
- Changed molotov_tk CVAR to molotov_ff
- Changed molotov_tempoverride CVAR to molotov_override_he
- Preparation for support of mods other than Counter-Strike
- Fixed lots of coding mistakes
- Optimized several sections of code
- Corrected grammar/typos
- Clean up code (Removed unused code/unhelpful comments, fixed formatting, and semicolons!)
- Raffe (CantShoot)
* (Unversioned release)
- Originally fixed plugin to run on Linux servers
- Added optional menu to purchase Molotov cocktails each round
- Moved models and sounds into proper molotov/ subdirectories
- Fixed Molotovs not being reset upon player disconnect
- (Almost) fixed Molotovs not being removed for new round
- Added @all/@ct/@t arguments to molotov_give command
- Changed some models/sound
- [ --<-@ ] Black Rose
* Version 3.0-3.1c ?
- Unknown changes
- SAMURAI
* Original plugin author
*/
#pragma semicolon 1
// Uncomment only the define that applies to your mod
#define CSTRIKE
//#define DOD
//#define TFC
// Uncomment the following line to enable debug logging.
//#define MOLOTOV_DEBUG
#include <amxmodx>
#include <amxmisc>
//#include <fakemeta> // (runtime only)
#include <fakemeta_util>
#if defined CSTRIKE
#include <cstrike>
#include <csx> // Used only for custom_weapon_* functions
#endif
#if defined DOD
#include <dodfun>
#include <dodx> // Used only for custom_weapon_* functions
#endif
#if defined TFC
#include <tfcx>
#include <engine> // Used only with debugging enabled
#endif
// If you really want to same some memory and know you won't have 32 players, you can change this.
#define MAX_PLAYERS 32
#define ADMIN_ACCESS ADMIN_KICK
#define ANTI_LAGG 7 // Defines max calculations before a flame is spawned without check if on ground
// This is to prevent lag at really narrow ents where you could end up with 400 calculations per flame. Suggested: <= 10
#define MOLOTOV_HARD_LIMIT 10 // Maximum Molotov cocktails this code is capable of handling without bugs (per player)
#define ID_TO_INDEX(%0) %0 - 1 // Use this macro rather than dim the arrays with 33
#define MOLOTOV_MENU_KEYS MENU_KEY_0|MENU_KEY_1|MENU_KEY_2 // Choices to look for with optional menu
// Task IDs
#define MOLOTOV_TASKID_RESET 1000 // Set g_bReset to false after a short delay (task is TFC only)
#define MOLOTOV_TASKID_OFFSET MOLOTOV_HARD_LIMIT
// These task IDs are dynamically set per-Molotov
#define MOLOTOV_TASKID_BASE1 2000 // By default, with 32 players, task ids
#define MOLOTOV_TASKID_BASE2 MOLOTOV_TASKID_BASE1 + (MOLOTOV_TASKID_OFFSET * MAX_PLAYERS) // from 2000 to 2959 can
#define MOLOTOV_TASKID_BASE3 MOLOTOV_TASKID_BASE2 + (MOLOTOV_TASKID_OFFSET * MAX_PLAYERS) // potentially be used used.
#define TEAM_UNASSIGNED 0
#define TEAM_ONE 1
#define TEAM_TWO 2
#define TEAM_THREE 3
#define TEAM_FOUR 4
#define MC_TFC_PC_CIVILIAN 11 // Temporary workaround for AMXX bug 6042
new const g_PLUGIN[] = "Molotov Cocktail";
new const g_AUTHORS[] = "DynamicBits";
new const g_VERSION[] = "3.30";
new pEnabled; // Pointer to molotov_enabled
new pMlDamage; // Pointer to molotov_damage
new pMlRadius; // Pointer to molotov_radius
new pFireTime; // Pointer to molotov_firetime
new pOverride; // Pointer to molotov_override_he
new pMFF; // Pointer to molotov_ff
new pFriendlyFire; // Pointer to mp_friendlyfire
new pFireDmg; // Pointer to molotov_firedamage
new pMaxMolotovs; // Pointer to molotov_max
#if defined DOD || defined TFC
new pTeamPlay; // Pointer to mp_teamplay
#endif
#if defined CSTRIKE
new pBuyZone; // Pointer to molotov_buyzone
new pMolotovMenu; // Pointer to molotov_menu
new pPrice; // Pointer to molotov_price
new g_msgScoreInfo; // ScoreInfo message ID
#endif
new g_msgDeathMsg; // DeathMsg message ID
new g_NumMolotov[MAX_PLAYERS]; // How many Molotovs each player has
#if defined CSTRIKE
new bool:g_bRestarted; // Reset Molotovs after first round restart
#endif
new g_MaxPlayers; // Max players (calculated at runtime to make loops more efficient)
new g_wpnMolotov; // Custom weapon ID
new bool:g_bReset; // Reset and stop explosions after round ends; Stop reset_tasks() from getting called once per player
new g_iFireSprite, g_iSmokeSprite[2]; // Handles to the precached sprites
new g_iMolotovOffset[MAX_PLAYERS]; // Offset used for a player's task ID
// The Pawn compiler does not optimize the DATA section. Any string that appears multiple times should be optimized with a global constant.
// *Unused* constants do not affect the compiled size, however they create compiler warnings. (#pragma unused is a quick fix for the warnings.)
new const EVENT_ROUND_END[] = "event_round_end";
#if defined CSTRIKE
new const BUY_MOLOTOV[] = "buy_molotov";
new const WEAPON_HEGRENADE[] = "weapon_hegrenade";
#endif
#if defined DOD
new const WEAPON_HANDGRENADE[] = "weapon_handgrenade";
new const WEAPON_STICKGRENADE[] = "weapon_stickgrenade";
#endif
#if defined DOD || defined TFC
new const HUDTEXT[] = "HudText";
#endif
#if defined CSTRIKE || defined TFC
new const TEXTMSG[] = "TextMsg";
#endif
// Check for outdated tfcconst.inc file (and likely outdated AMX Mod X core/modules).
// My patch for AMXX bug 6042 was accepted, but I think I'll wait for a new final release of AMXX to enable this check.
// In the meantime, I created the MC_TFC_PC_CIVILIAN define.
//#if defined TFC && TFC_PC_CIVILIAN != 11 // TFC_PC_CIVILIAN was (incorrectly) 10 in older versions
// #error TFC_PC_CIVILIAN != 11. Update your tfcconst.inc include file. Get the latest AMX Mod X snapshots at www.amxmodx.org/snapshots.php
//#endif
// Initialize the plugin
public plugin_init() {
register_plugin(g_PLUGIN, g_VERSION, g_AUTHORS);
server_print("[MC] ---- Molotov Cocktail %s loaded ----", g_VERSION);
register_cvar("MolotovCocktail", g_VERSION, FCVAR_SERVER|FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
#if defined CSTRIKE
register_menucmd(register_menuid("Buy Molotov Cocktail"), MOLOTOV_MENU_KEYS, "giveMolotov");
#if defined MOLOTOV_DEBUG
register_clcmd("molotov_menutest", "show_molotov_menu");
#endif
register_clcmd("say /molotov", BUY_MOLOTOV);
register_clcmd("say molotov", BUY_MOLOTOV);
#endif
register_concmd("molotov_give", "molotov_give", ADMIN_ACCESS, "<player|@all|@t|@ct|@al|@ax|@br|@b|@r|@y|@g> - Give free Molotov cocktails");
register_concmd("molotov_override", "molotov_override", ADMIN_ACCESS, "[0|1] - Enable/disable the standard grenade override (If no arguments, show the status)");
register_concmd("molotov_cocktail", "molotov_cocktail", ADMIN_ACCESS, "[0|1] - Enable/disable the plugin (If no arguments, show the status)");
pEnabled = register_cvar("molotov_enabled", "1", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pOverride = register_cvar("molotov_override_he", "0", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pMlDamage = register_cvar("molotov_damage", "50.0", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pMlRadius = register_cvar("molotov_radius", "150.0", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pFireTime = register_cvar("molotov_firetime", "6", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pFireDmg = register_cvar("molotov_firedamage", "3", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pMFF = register_cvar("molotov_ff", "1", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pMaxMolotovs = register_cvar("molotov_max", "1", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pFriendlyFire = register_cvar("mp_friendlyfire", "0", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
#if defined CSTRIKE
pBuyZone = register_cvar("molotov_buyzone", "1", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pMolotovMenu = register_cvar("molotov_menu", "0", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
pPrice = register_cvar("molotov_price", "300", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
#endif
#if defined DOD
pTeamPlay = register_cvar("mp_teamplay", "0", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
#endif
#if defined TFC
pTeamPlay = register_cvar("mp_teamplay", "21", FCVAR_EXTDLL|FCVAR_SPONLY|FCVAR_PRINTABLEONLY);
#endif
register_event("DeathMsg", "event_deathmsg", "a", "2>0"); // For some reason, arg2 (Victim) is sometimes -1 (at least in TFC on Windows HLDS with FoxBot).
#if defined CSTRIKE || defined DOD
register_event("CurWeapon", "event_curweapon", "be", "1=1");
register_event("HLTV", "event_new_round", "a", "1=0", "2=0"); // cstrike/dod new round; So far, I haven't found a TFC equivalent
#endif
#if defined CSTRIKE
register_event(TEXTMSG, "event_gamerestart", "a", "2=#Game_Commencing", "2=#Game_will_restart_in");
#endif
#if defined DOD
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1&VICTORY"); // Sent once per player on round end
#endif
#if defined TFC
// Since TFC doesn't have any generic end of round event/message, specific messages need to be caught for certain maps.
// Maps that don't have traditional rounds (2fort, badlands, casbah, crossover2, cz2, ravelin, skate2, well, etc.) don't apply here.
// All of the default maps are accounted for. If there is demand for specific custom maps, I will add the appropriate message(s).
new sCurrentMap[32];
get_mapname(sCurrentMap, charsmax(sCurrentMap));
if (!strcmp(sCurrentMap, "avanti")) {
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1=#italy_endround_win"); // Was Avanti originally called Italy?
} else if ((!strcmp(sCurrentMap, "dustbowl")) || (!strcmp(sCurrentMap, "castleargh")) || (!strcmp(sCurrentMap, "castleargh2"))) {
register_event(TEXTMSG, EVENT_ROUND_END, "b", "2=#dustbowl_blue_secures_one"); // Technically these are "stages," not "rounds"
register_event(TEXTMSG, EVENT_ROUND_END, "b", "2=#dustbowl_blue_secures_two");
//register_event(TEXTMSG, "event_round_end", "b", "2=#dustbowl_blue_caps"); // The map ends after this cap
} else if (!strcmp(sCurrentMap, "epicenter")) {
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1=#dblmint_you_capped_flag"); // dblmint?!
} else if (!strcmp(sCurrentMap, "flagrun")) {
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1&you won this round!");
} else if (!strcmp(sCurrentMap, "hunted")) {
register_event(TEXTMSG, EVENT_ROUND_END, "b", "2=#hunted_target_killed");
} else if (!strcmp(sCurrentMap, "push")) {
register_event(TEXTMSG, EVENT_ROUND_END, "b", "2&_netname_scores");
} else if (!strcmp(sCurrentMap, "rock2")) {
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1=1 . . .^n");
} else if (!strcmp(sCurrentMap, "warpath")) {
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1=#warpath_red_wins");
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1=#warpath_blue_wins");
// ---------- Custom Maps ----------
} else if (!strcmp(sCurrentMap, "castleargh3")) {
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1&You have secured"); // This works for all four stages
} else if (!strcmp(sCurrentMap, "hwguyz2")) {
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1=#2fort_you_capped_flag");
register_event(HUDTEXT, EVENT_ROUND_END, "b", "1&Time Ran Out");
}
//#if defined MOLOTOV_DEBUG
//register_event(TEXTMSG, "event_textmsg_a", "a");
//register_event(TEXTMSG, "event_textmsg_b", "b");
//register_event(HUDTEXT, "event_hudtext_a", "a");
//register_event(HUDTEXT, "event_hudtext_b", "b");
//#endif
#endif
#if defined CSTRIKE
register_logevent(EVENT_ROUND_END, 2, "1=Round_End");
#endif
register_forward(FM_EmitSound, "fw_emitsound");
#if defined TFC
register_forward(FM_SetModel, "fw_setmodel_post", 1);
#endif
g_MaxPlayers = get_maxplayers();
#if defined CSTRIKE
g_msgScoreInfo = get_user_msgid("ScoreInfo");
#endif
g_msgDeathMsg = get_user_msgid("DeathMsg");
g_wpnMolotov = custom_weapon_add("molotov", 0, "molotov"); // I can hardly find any documentation or sample code for this. I have no
// idea if I'm using it correctly or not. I'm not even sure what it affects.
}
// These are primarily for catching messages in TFC to add custom round end triggers.
/*
#if defined MOLOTOV_DEBUG
public event_textmsg_a() {
new sArg2[64];
read_data(2, sArg2, charsmax(sArg2));
client_print(0, print_chat, "event_textmsg_a 1(%d) 2(%s)", read_data(1), sArg2);
console_print(0, "event_textmsg_a 1(%d) 2(%s)", read_data(1), sArg2);
}
public event_textmsg_b() {
new sArg2[64];
read_data(2, sArg2, charsmax(sArg2));
client_print(0, print_chat, "event_textmsg_b 1(%d) 2(%s)", read_data(1), sArg2);
console_print(0, "event_textmsg_b 1(%d) 2(%s)", read_data(1), sArg2);
}
public event_hudtext_a() {
new sArg1[64];
read_data(1, sArg1, charsmax(sArg1));
client_print(0, print_chat, "event_hudtext_a 1(%s) 2(%d)", sArg1, read_data(1));
console_print(0, "event_hudtext_a 1(%s) 2(%d)", sArg1, read_data(1));
}
public event_hudtext_b() {
new sArg1[64];
read_data(1, sArg1, charsmax(sArg1));
client_print(0, print_chat, "event_hudtext_b 1(%s) 2(%d)", sArg1, read_data(1));
console_print(0, "event_hudtext_b 1(%s) 2(%d)", sArg1, read_data(1));
}
#endif
*/
// Precache models and sound(s)
public plugin_precache() {
g_iFireSprite = precache_model("sprites/flame.spr");
g_iSmokeSprite[0] = precache_model("sprites/black_smoke3.spr");
#if defined DOD
g_iSmokeSprite[1] = g_iSmokeSprite[0]; // steam1.spr shows a black background in dod
#else
g_iSmokeSprite[1] = precache_model("sprites/steam1.spr");
#endif
#if defined CSTRIKE || defined DOD
precache_model("models/molotov/p_molotov.mdl");
precache_model("models/molotov/v_molotov.mdl");
#endif
precache_model("models/molotov/w_molotov.mdl");
precache_model("models/molotov/w_broke_molotov.mdl");
precache_sound("molotov/molotov_fire.wav");
}
// Reset Molotovs so that a new player doesn't have any
public client_disconnect(id) {
g_NumMolotov[ID_TO_INDEX(id)] = 0;
}
// Catch the first impact of the Molotov and start the sound/explosion
// A Molotov cocktail should "explode" on impact, not after a set time.
public fw_emitsound(ent, channel, sample[]) {
#if defined CSTRIKE
if (equal(sample[8], "he_bounce", 9)) {
#else
#if defined DOD || defined TFC
// DOD: debris/bustglass2.wav and debris/bustglass1.wav are played for breaking glass, but ent is not the grenade, so Molotovs "disappear" (This is a bug in this plugin)
// A fix would be to use FM_Touch or Ham_Touch or register_touch instead of FM_EmitSound
if (equal(sample[8], "grenade_hit", 11)) {
#endif
#endif
new sModel[32];
pev(ent, pev_model, sModel, charsmax(sModel));
// Depending on where the Molotov lands, the EmitSound forward may get called 50+ times.
// After the first hit, the model is changed to w_broke_molotov, so this code is skipped on successive calls
if (equal(sModel[15], "w_molotov.mdl")) {
#if defined TFC
set_pev(ent, pev_nextthink, 99999.0); // For TFC, this is about the only way I can stop the explosion.
#endif
// The glass breaking sound has a low range (ATTN_STATIC) so as not to be overpowering
emit_sound(ent, CHAN_AUTO, "debris/glass2.wav", VOL_NORM, ATTN_STATIC, 0, PITCH_LOW);
new Float:fFriction, Float:fVelocity[3];
pev(ent, pev_friction, fFriction);
fFriction *= 1.15; // Increase friction to make it look more realistic
set_pev(ent, pev_friction, fFriction);
pev(ent, pev_velocity, fVelocity);
fVelocity[0] *= 0.3; // Decrease velocity because friction doesn't do it all
fVelocity[1] *= 0.3;
fVelocity[2] *= 0.3;
set_pev(ent, pev_velocity, fVelocity);
molotov_explode(ent); // Replacement for normal grenade explosion
return FMRES_SUPERCEDE;
} else if (equal(sModel[15], "w_broke_molotov.")) { // "mdl" is truncated because of the array size, which is OK
return FMRES_SUPERCEDE; // Don't play any sounds for bounces.
}
}
return FMRES_IGNORED;
}
// Since TFC handles grenades differently, this is roughly equivalant to event_curweapon() used by cstrike and dod.
#if defined TFC
public fw_setmodel_post(ent, const model[]) {
if (!pev_valid(ent)) { // Check if it's a valid entity to prevent errors
return FMRES_IGNORED;
}
new sClassname[32];
pev(ent, pev_classname, sClassname, charsmax(sClassname));
if (!get_pcvar_num(pEnabled) || !equal(sClassname, "normalgrenade")) {
return FMRES_IGNORED;
}
new iOwner = pev(ent, pev_owner);
if (!g_NumMolotov[ID_TO_INDEX(iOwner)] && !get_pcvar_num(pOverride)) { // If no Molotovs and override is disabled, return
return FMRES_IGNORED;
}
if (g_NumMolotov[ID_TO_INDEX(iOwner)] > 0) { // Prevent negative values
g_NumMolotov[ID_TO_INDEX(iOwner)]--;
}
set_pev(ent, pev_team, get_user_team(iOwner));
custom_weapon_shot(g_wpnMolotov, iOwner);
engfunc(EngFunc_SetModel, ent, "models/molotov/w_molotov.mdl");
return FMRES_HANDLED;
}
#endif
// When the player changes weapons to the Molotov, update the model
#if defined CSTRIKE || defined DOD
public event_curweapon(id) {
if (!get_pcvar_num(pEnabled) || !is_user_alive(id)) {
return PLUGIN_CONTINUE;
}
if (!g_NumMolotov[ID_TO_INDEX(id)] && !get_pcvar_num(pOverride)) { // If no Molotovs and override is disabled, return
return PLUGIN_CONTINUE;
}
new iWeaponID = get_user_weapon(id, _, _);
#if defined CSTRIKE
if (iWeaponID != CSW_HEGRENADE) {
#else // elseif *should* work, but there is a bug in the compiler
#if defined DOD
// current weapon is never set to DODW_MILLS_BOMB in this event; only DODW_HANDGRENADE/DODW_STICKGRENADE
if ((iWeaponID != DODW_HANDGRENADE) && (iWeaponID != DODW_STICKGRENADE)) {
#endif
#endif
return PLUGIN_CONTINUE;
}
set_pev(id, pev_viewmodel2, "models/molotov/v_molotov.mdl"); // View model (First person) *model2 doesn't require allocating the string
set_pev(id, pev_weaponmodel2, "models/molotov/p_molotov.mdl"); // Player model (Third person)
#if defined DOD
// I think 3 is correct, but it looks strange..
set_pev(id, pev_weaponanim, 3); // 0: "idle"; 1: "pullpin"; 2: "throw"; 3: "deploy"
#endif
return PLUGIN_CONTINUE;
}
#endif
// Reset Molotovs on death
public event_deathmsg() {
#if defined MOLOTOV_DEBUG
log_amx("[MC] ========== DeathMsg ========== K(%d) V(%d)", read_data(1), read_data(2));
#endif
g_NumMolotov[ID_TO_INDEX(read_data(2))] = 0;
}
// cstrike only
#if defined CSTRIKE
public event_gamerestart() {
#if defined MOLOTOV_DEBUG
log_amx("[MC] ========== Game Restart ==========");
#endif
g_bRestarted = true;
}
#endif
// cstrike, dod, and tfc will all call this once per player on round end
public event_round_end() {
#if defined MOLOTOV_DEBUG
log_amx("[MC] ========== Round End ==========");
#endif
if (g_bReset == false) {
reset_tasks();
g_bReset = true;
#if defined TFC
set_task(2.0, "cancel_reset", MOLOTOV_TASKID_RESET); // TFC won't call event_new_round, so do that stuff here instead
#endif
}
}
// cstrike and dod will call this once per round, but TFC won't.
#if defined CSTRIKE || defined DOD
public event_new_round(id) {
#if defined MOLOTOV_DEBUG
log_amx("[MC] ========== New Round ==========");
#endif
g_bReset = false; // Stop blocking
if (!get_pcvar_num(pEnabled)) {
return PLUGIN_CONTINUE;
}
reset_tasks(); // This probably isn't needed anymore, but it shouldn't hurt anything
#if defined CSTRIKE
if (get_pcvar_num(pMolotovMenu)) {
if (get_pcvar_num(pOverride)) {
client_print(id, print_center, "Molotov cocktails will replace purchased HE grenades");
} else {
show_molotov_menu(id);
}
}
// For cstrike only, make sure the player didn't quickly purchase a Molotov before the first actual round
if (g_bRestarted) {
arrayset(g_NumMolotov, 0, sizeof(g_NumMolotov)); // Reset everyone to zero Molotovs
g_bRestarted = false;
}
#endif
if (get_pcvar_num(pOverride)) {
set_molotovs();
} else {
reset_molotovs();
}
return PLUGIN_CONTINUE;
}
#endif
// Enable/Disable/Get status of override
public molotov_override(id, level, cid) {
if (!cmd_access(id, level, cid, 1)) { // First argument (passed to molotov_override) is optional
return PLUGIN_HANDLED;
}
if (!get_pcvar_num(pEnabled)) {
return PLUGIN_HANDLED;
}
if (read_argc() == 1) { // No arguments; Display status
console_print(id, "Override is currently %s.", get_pcvar_num(pOverride) ? "enabled" : "disabled");
return PLUGIN_HANDLED;
}
new sArg[2];
read_argv(1, sArg, charsmax(sArg));
new iArg = str_to_num(sArg);
if ((iArg < 0) || (iArg > 1) || (!isdigit(sArg[0]))) { // If less than 0 or greater than 1 or not a digit
console_print(id, "Invalid argument(%s). Valid arguments are ^"0^" and ^"1^".", sArg);
return PLUGIN_HANDLED;
}
if (iArg == get_pcvar_num(pOverride)) {
console_print(id, "Override is already %s.", iArg ? "enabled" : "disabled");
return PLUGIN_HANDLED;
}
set_pcvar_num(pOverride, iArg);
console_print(id, "Override was %s.", iArg ? "enabled" : "disabled");
#if defined CSTRIKE || defined DOD
if (iArg) { // If plugin is enabled (checked above) and override is enabled, set models to Molotov
set_molotovs();
} else {
reset_molotovs();
}
#endif
return PLUGIN_HANDLED;
}
// Enable/Disable/Get status of plugin
public molotov_cocktail(id, level, cid) {
if (!cmd_access(id, level, cid, 1)) { // First argument (passed to molotov_cocktail) is optional
return PLUGIN_HANDLED;
}
if (read_argc() == 1) { // No arguments; Display status
console_print(id, "Plugin is currently %s. (Override:%d; MFF:%d)", get_pcvar_num(pEnabled) ? "enabled" : "disabled", get_pcvar_num(pOverride), get_pcvar_num(pMFF));
return PLUGIN_HANDLED;
}
new sArg[2];
read_argv(1, sArg, charsmax(sArg));
new iArg = str_to_num(sArg);
if ((iArg < 0) || (iArg > 1) || (!isdigit(sArg[0]))) { // If less than 0 or greater than 1 or not a digit
console_print(id, "Invalid argument(%s). Valid arguments are ^"0^" and ^"1^".", sArg);
return PLUGIN_HANDLED;
}
if (iArg == get_pcvar_num(pEnabled)) {
console_print(id, "Plugin is already %s.", iArg ? "enabled" : "disabled");
return PLUGIN_HANDLED;
}
set_pcvar_num(pEnabled, iArg);
console_print(id, "Plugin was %s.", iArg ? "enabled" : "disabled");
#if defined CSTRIKE || defined DOD
if (iArg && get_pcvar_num(pOverride)) { // If the plugin was enabled and override is enabled, set models to Molotov
set_molotovs();
} else {
reset_molotovs();
}
#endif
return PLUGIN_HANDLED;
}
// Handle molotov_give console command
public molotov_give(id, level, cid) {
if (!cmd_access(id, level, cid, 2)) {
return PLUGIN_HANDLED;
}
new sArg1[16], iTarget;
read_argv(1, sArg1, charsmax(sArg1));
#if defined MOLOTOV_DEBUG
log_amx("[MC] molotov_give sArg1[0](%s)", sArg1[0]);
#endif
new sAdmin[32];
get_user_name(id, sAdmin, charsmax(sAdmin));
new iGiveAmount = (get_pcvar_num(pMaxMolotovs) < MOLOTOV_HARD_LIMIT ? get_pcvar_num(pMaxMolotovs) : MOLOTOV_HARD_LIMIT);
if (sArg1[0] == '@') {
new iTargetTeam, sTeamName[32];
new Players[MAX_PLAYERS], iNum;
if (equali(sArg1[1], "all")) {
iTargetTeam = 0;
} else if (equali(sArg1[1], "t") || equali(sArg1[1], "al") || equali(sArg1[1], "br") || equali(sArg1[1], "b")) { // CS_TEAM_T or ALLIES/British or Blue
iTargetTeam = TEAM_ONE;
} else if (equali(sArg1[1], "ct") || equali(sArg1[1], "ax") || equali(sArg1[1], "r")) { // CS_TEAM_CT or AXIS or Red
iTargetTeam = TEAM_TWO;
#if defined TFC
} else if (equali(sArg1[1], "y")) { // Yellow
iTargetTeam = TEAM_THREE;
} else if (equali(sArg1[1], "g")) { // Green
iTargetTeam = TEAM_FOUR;
#endif
}
get_players(Players, iNum, "ach"); // alive, no bots, no HLTV
for (new i = 0; i < iNum; ++i) {
iTarget = Players[i];
if ((iTargetTeam == 0) || (get_user_team(iTarget) == iTargetTeam)) {
g_NumMolotov[ID_TO_INDEX(iTarget)] = iGiveAmount;
#if defined CSTRIKE
fm_give_item(iTarget, WEAPON_HEGRENADE);
cs_set_user_bpammo(iTarget, CSW_HEGRENADE, iGiveAmount);
#endif
#if defined DOD
// TODO - This sets the count, but it is not immediately updated on the HUD
switch(get_user_team(iTarget)) {
case ALLIES: { // (or British)
fm_give_item(iTarget, WEAPON_HANDGRENADE);
dod_set_user_ammo(iTarget, DODW_HANDGRENADE, iGiveAmount);
}
case AXIS: {
fm_give_item(iTarget, WEAPON_STICKGRENADE);
dod_set_user_ammo(iTarget, DODW_STICKGRENADE, iGiveAmount);
}
}
#endif
#if defined TFC
new iClass = pev(iTarget, pev_playerclass);
if ((iClass > 0) && (iClass != TFC_PC_SCOUT) && (iClass != MC_TFC_PC_CIVILIAN)) { // No unselected/spectator, scout, or civilian
tfc_setbammo(iTarget, TFC_AMMO_NADE1, iGiveAmount); // Requires 1.8.3-dev-hg185 or newer
}
#endif
#if defined CSTRIKE
emit_sound(iTarget, CHAN_WEAPON, "items/gunpickup2.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
#endif
#if defined DOD
emit_sound(iTarget, CHAN_WEAPON, "items/ammopickup.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); // "items/weaponpickup.wav" could work too, I suppose
#endif
#if defined TFC
// Shotgun pumping sound for picking up grenades... That's how TFC does it!
emit_sound(iTarget, CHAN_WEAPON, "weapons/scock1.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
#endif
}
}
switch(iTargetTeam) {
case 0: {
sTeamName = "everyone";
}
case TEAM_ONE: {
#if defined CSTRIKE
sTeamName = "all terrorists";
#endif
#if defined DOD
sTeamName = "all allies"; // TODO - Allies or British
#endif
#if defined TFC
sTeamName = "all blue"; // I *could* pull the team1_name value from the info_tfdetect entity (but only for some maps?)
#endif
}
case TEAM_TWO: {
#if defined CSTRIKE
sTeamName = "all ct's";
#endif
#if defined DOD
sTeamName = "all axis";
#endif
#if defined TFC
sTeamName = "all red";
}
case TEAM_THREE: {
sTeamName = "all yellow";
}
case TEAM_FOUR: {
sTeamName = "all green";
#endif
}
}
client_print(0, print_chat, "ADMIN %s has given %s %d Molotov cocktails!", sAdmin, sTeamName, iGiveAmount);
} else {
iTarget = cmd_target(id, sArg1, 6);
if (!is_user_connected(iTarget) || !is_user_alive(iTarget)) {
return PLUGIN_HANDLED;
}
g_NumMolotov[ID_TO_INDEX(iTarget)] = iGiveAmount;
#if defined CSTRIKE
fm_give_item(iTarget, WEAPON_HEGRENADE);
cs_set_user_bpammo(iTarget, CSW_HEGRENADE, iGiveAmount);
#endif
#if defined DOD
switch(get_user_team(iTarget)) {
case ALLIES: { // (or British)
fm_give_item(iTarget, WEAPON_HANDGRENADE);
dod_set_user_ammo(iTarget, DODW_HANDGRENADE, iGiveAmount);
}
case AXIS: {
fm_give_item(iTarget, WEAPON_STICKGRENADE);
dod_set_user_ammo(iTarget, DODW_STICKGRENADE, iGiveAmount);
}
}
#endif
#if defined TFC
new iClass = pev(iTarget, pev_playerclass);
if ((iClass > 0) && (iClass != TFC_PC_SCOUT) && (iClass != MC_TFC_PC_CIVILIAN)) { // No unselected/spectator, scout, or civilian
tfc_setbammo(iTarget, TFC_AMMO_NADE1, iGiveAmount); // Requires 1.8.3-dev-hg185 or newer
}
#endif
#if defined CSTRIKE
emit_sound(iTarget, CHAN_WEAPON, "items/gunpickup2.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
#endif
#if defined DOD
emit_sound(iTarget, CHAN_WEAPON, "items/ammopickup.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM); // "items/weaponpickup.wav" could work too, I suppose
#endif
#if defined TFC
// Shotgun pumping sound for picking up grenades... That's how TFC does it!
emit_sound(iTarget, CHAN_WEAPON, "weapons/scock1.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
#endif
client_print(iTarget, print_chat, "ADMIN %s has given you %d Molotov cocktails!", sAdmin, iGiveAmount);
}
return PLUGIN_HANDLED;
}
// Handle the /molotov command and molotov menu
#if defined CSTRIKE
public buy_molotov(id) {
if (!get_pcvar_num(pEnabled)) {
return PLUGIN_HANDLED;
}
//if (get_pcvar_num(pOverride)) {
// client_print(id, print_center, "Just buy a HE grenade and get Molotov automatically!");
// return PLUGIN_HANDLED;
//}
if (!is_user_alive(id)) {
client_print(id, print_center, "You can't buy Molotov cocktails because you are dead.");
return PLUGIN_HANDLED;
}
if (!cs_get_user_buyzone(id) && get_pcvar_num(pBuyZone)) {
client_print(id, print_center, "You are not in a buyzone.");
return PLUGIN_HANDLED;
}
new iMoney = cs_get_user_money(id);
if (iMoney < get_pcvar_num(pPrice)) {
client_print(id, print_center, "You don't have enough $ to buy a Molotov cocktail.");
return PLUGIN_HANDLED;
}
if (!g_NumMolotov[ID_TO_INDEX(id)] && user_has_weapon(id, CSW_HEGRENADE)) {
if (get_pcvar_num(pOverride)) {
g_NumMolotov[ID_TO_INDEX(id)] = cs_get_user_bpammo(id, CSW_HEGRENADE); // If the user buys one from the VGUI menu with the override enabled, this updates g_NumMolotov
} else {
client_print(id, print_center, "You already have an HE Grenade.");
return PLUGIN_HANDLED;
}
}
if (g_NumMolotov[ID_TO_INDEX(id)] == get_pcvar_num(pMaxMolotovs)) {
if (g_NumMolotov[ID_TO_INDEX(id)] == 1) {
client_print(id, print_center, "You already have a Molotov cocktail.");
} else {
client_print(id, print_center, "You already have %d Molotov cocktails.", g_NumMolotov[ID_TO_INDEX(id)]);
}
return PLUGIN_HANDLED;
}
cs_set_user_money(id, iMoney - get_pcvar_num(pPrice));
fm_give_item(id, WEAPON_HEGRENADE);
cs_set_user_bpammo(id, CSW_HEGRENADE, ++g_NumMolotov[ID_TO_INDEX(id)]);
client_print(id, print_chat, "You got a Molotov cocktail!");
return PLUGIN_HANDLED;
}
#endif
// Just before the grenade is thrown, change the model
#if defined CSTRIKE || defined DOD
public grenade_throw(id, ent, wid) {
#if defined CSTRIKE
if (!get_pcvar_num(pEnabled) || !is_user_connected(id) || wid != CSW_HEGRENADE) {
#else
#if defined DOD
// current weapon can be DODW_MILLS_BOMB in this forward, but not in CurWeapon
if (!get_pcvar_num(pEnabled) || !is_user_connected(id) || ((wid != DODW_HANDGRENADE) && (wid != DODW_STICKGRENADE) && (wid != DODW_MILLS_BOMB))) {
#endif
#endif
return PLUGIN_CONTINUE;
}
if (!g_NumMolotov[ID_TO_INDEX(id)] && !get_pcvar_num(pOverride)) { // If no Molotovs and override is disabled, return
return PLUGIN_CONTINUE;
}
if (g_NumMolotov[ID_TO_INDEX(id)] > 0) { // Prevent negative values
g_NumMolotov[ID_TO_INDEX(id)]--;
}
engfunc(EngFunc_SetModel, ent, "models/molotov/w_molotov.mdl");
set_pev(ent, pev_nextthink, 99999.0);
custom_weapon_shot(g_wpnMolotov, id);
#if defined CSTRIKE // dod sets the team, cstrike doesn't, TFC sets this in fw_setmodel_post()
set_pev(ent, pev_team, get_user_team(id));
#endif
#if defined DOD
//set_pev(id, pev_weaponanim, 0); // 0:"idle"; 1:"pullpin"; 2:"throw"; 3:"deploy"
#endif
return PLUGIN_HANDLED;
}
#endif
// Set up the explosion, sound, damage, etc.
molotov_explode(ent) {
new param[7], iOrigin[3];
new Float:fOrigin[3];
new iOwner = pev(ent, pev_owner);
// The broken bottle may continue to travel, but the fire will be centered around the explosion site, marked by this temporary info_target entity.
new ent2 = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target"));
pev(ent, pev_origin, fOrigin);
#if defined MOLOTOV_DEBUG
log_amx("[MC] molotov_explode ent(%d) owner(%d) ent2(%d) -----", ent, iOwner, ent2);
#endif
param[0] = ent;
param[1] = ent2;
param[2] = iOwner;
param[3] = pev(ent, pev_team);
param[4] = iOrigin[0] = floatround(fOrigin[0]);
param[5] = iOrigin[1] = floatround(fOrigin[1]);
param[6] = iOrigin[2] = floatround(fOrigin[2]);
engfunc(EngFunc_SetModel, ent, "models/molotov/w_broke_molotov.mdl");
random_fire(iOrigin, ent2);
radius_damage2(iOwner, param[3], fOrigin, get_pcvar_float(pMlDamage), get_pcvar_float(pMlRadius), DMG_BLAST, true);
// If the round ends because of damage inflicted by the initial blast (in the previous line of code), skip any further Molotov effects.
// g_bReset may already be set, so it is safe to check it at this point.
if (g_bReset == true) {
set_pev(ent, pev_flags, pev(ent, pev_flags) | FL_KILLME); // Remove the Molotov and later cancel the explosion
return PLUGIN_HANDLED;
}
new Float:FireTime = get_pcvar_float(pFireTime);
if (++g_iMolotovOffset[ID_TO_INDEX(iOwner)] == MOLOTOV_HARD_LIMIT) {
g_iMolotovOffset[ID_TO_INDEX(iOwner)] = 0;
}
set_task(0.2, "fire_damage", MOLOTOV_TASKID_BASE1 + (MOLOTOV_TASKID_OFFSET * (iOwner - 1)) + g_iMolotovOffset[ID_TO_INDEX(iOwner)], param, 7, "a", floatround(FireTime / 0.2, floatround_floor));
set_task(1.0, "fire_sound", MOLOTOV_TASKID_BASE2 + (MOLOTOV_TASKID_OFFSET * (iOwner - 1)) + g_iMolotovOffset[ID_TO_INDEX(iOwner)], param, 7, "a", floatround(FireTime) - 1);
// This task removes the broken Molotov and "info_target" entity once molotov_firetime has expired
set_task(FireTime, "fire_stop", MOLOTOV_TASKID_BASE3 + (MOLOTOV_TASKID_OFFSET * (iOwner - 1)) + g_iMolotovOffset[ID_TO_INDEX(iOwner)], param, 7);
return PLUGIN_CONTINUE;
}
// Since there isn't a reliable new round trigger in TFC, a task is created at round end that calls this function after a delay
#if defined TFC
public cancel_reset() {
g_bReset = false;
}
#endif
// Make fire sounds
public fire_sound(param[]) {
emit_sound(param[1], CHAN_AUTO, "molotov/molotov_fire.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
}
// Remove Molotov entities
public fire_stop(param[]) {
if (pev_valid(param[0])) { set_pev(param[0], pev_flags, pev(param[0], pev_flags) | FL_KILLME); } // Molotov entity
if (pev_valid(param[1])) { set_pev(param[1], pev_flags, pev(param[1], pev_flags) | FL_KILLME); } // info_target entity
}
// Call visual effect and damage functions
public fire_damage(param[]) {
new iOrigin[3], Float:fOrigin[3];
iOrigin[0] = param[4];
iOrigin[1] = param[5];
iOrigin[2] = param[6];
random_fire(iOrigin, param[1]); // Visual effect
IVecFVec(iOrigin, fOrigin);
radius_damage2(param[2], param[3], fOrigin, get_pcvar_float(pFireDmg), get_pcvar_float(pMlRadius), DMG_BURN, false); // Actual damage
}
// There is a radius_damage() in engine, so this was renamed.
stock radius_damage2(iAttacker, iAttackerTeam, Float:fOrigin[3], Float:fDamage, Float:fRange, iDmgType, bool:bCalc = true) {
new Float:pOrigin[3], Float:fDist, Float:fTmpDmg;
new i, iFF = get_pcvar_num(pMFF);
if (iFF == -1) { // Obey mp_friendlyfire
iFF = get_pcvar_num(pFriendlyFire);
#if defined DOD || defined TFC
} else if (iFF == -2) { // Obey mp_teamplay (bit 5)
new iTeamPlay = get_pcvar_num(pTeamPlay);
if (iTeamPlay & (1 << 4)) { // bit 5 (16 = teammates take no damage from explosive weaponfire)
iFF = 0;
}
#endif
} // else, leave it at 0 or 1
while (i++ < g_MaxPlayers) {
if (!is_user_alive(i)) {
continue;
}
#if defined TFC
if ((iFF == 0) && ((iAttackerTeam == get_user_team(i)) || (tfc_is_team_ally(iAttackerTeam, get_user_team(i))))) { // TODO: tfc_is_team_ally is broken in AMX Mod X
#else
if ((iFF == 0) && (iAttackerTeam == get_user_team(i))) {
#endif
continue;
}
pev(i, pev_origin, pOrigin);
fDist = get_distance_f(fOrigin, pOrigin);
if (fDist > fRange) {
continue;
}
if (bCalc) {
fTmpDmg = fDamage - (fDamage / fRange) * fDist;
} else {
fTmpDmg = fDamage;
}
if (floatround(fTmpDmg) > 0) { // This eliminated the "[CSX] Invalid damage 0" error
custom_weapon_dmg(g_wpnMolotov, iAttacker, i, floatround(fTmpDmg), 0);
}
if (pev(i, pev_health) <= fTmpDmg) {
kill(iAttacker, i, iAttackerTeam);
} else {
fm_fakedamage(i, "molotov", fTmpDmg, iDmgType);
}
}
// At this point, i is one higher than the highest possible player ID, so this loop only affects non-player entities
while ((i = engfunc(EngFunc_FindEntityInSphere, i, fOrigin, fRange))) { // Extra parentheses fix warning 211: possibly unintended assignment
if (pev(i, pev_takedamage)) {
if (bCalc) {
pev(i, pev_origin, pOrigin);
fTmpDmg = fDamage - (fDamage / fRange) * get_distance_f(fOrigin, pOrigin);
} else {
fTmpDmg = fDamage;
}
fm_fakedamage(i, "molotov", fTmpDmg, iDmgType);
}
}
}
// This stock only creates the visual effect. It does not handle any damage.
// I tried using TE_FIREFIELD, but I can't make it look good in dod.
stock random_fire(Origin[3], ent) {
static iRange, iOrigin[3], g_g, i;
iRange = get_pcvar_num(pMlRadius);
for (i = 1; i <= 5; i++) {
g_g = 1;
iOrigin[0] = Origin[0] + random_num(-iRange, iRange);
iOrigin[1] = Origin[1] + random_num(-iRange, iRange);
iOrigin[2] = Origin[2];
iOrigin[2] = ground_z(iOrigin, ent);
while (get_distance(iOrigin, Origin) > iRange) { // If iOrigin is too far away, recalculate its position
iOrigin[0] = Origin[0] + random_num(-iRange, iRange);
iOrigin[1] = Origin[1] + random_num(-iRange, iRange);
iOrigin[2] = Origin[2];
if (++g_g >= ANTI_LAGG) {
iOrigin[2] = ground_z(iOrigin, ent, 1);
} else {
iOrigin[2] = ground_z(iOrigin, ent);
}
}
new rand = random_num(5, 15);
message_begin(MSG_BROADCAST, SVC_TEMPENTITY);
write_byte(TE_SPRITE);
write_coord(iOrigin[0]); // Position
write_coord(iOrigin[1]);
write_coord(iOrigin[2] + rand * 5);
write_short(g_iFireSprite); // Sprite index
write_byte(rand); // Scale
write_byte(100); // Brightness
message_end();
}
// One smoke puff for each call to random_fire, regardless of number of flames
message_begin(MSG_BROADCAST, SVC_TEMPENTITY);
write_byte(TE_SMOKE);
write_coord(iOrigin[0]); // Position
write_coord(iOrigin[1]);
write_coord(iOrigin[2] + 120);
write_short(g_iSmokeSprite[random_num(0, 1)]); // Sprite index
write_byte(random_num(10, 30)); // Scale
write_byte(random_num(10, 20)); // Framerate
message_end();
}
// Stop all visual effect/physical damage tasks
stock reset_tasks() {
#if defined MOLOTOV_DEBUG
new tmpdbgid;
#endif
for (new i; i < g_MaxPlayers; i++) { // for 0..31
for (new o; o < MOLOTOV_TASKID_OFFSET; o++) {
if (task_exists(MOLOTOV_TASKID_BASE1 + (MOLOTOV_TASKID_OFFSET * i) + o)) {
remove_task(MOLOTOV_TASKID_BASE1 + (MOLOTOV_TASKID_OFFSET * i) + o);
#if defined MOLOTOV_DEBUG
tmpdbgid = MOLOTOV_TASKID_BASE1 + (MOLOTOV_TASKID_OFFSET * i) + o;
log_amx("[MC] %d exists. ----------==========----------", tmpdbgid);
#endif
}
if (task_exists(MOLOTOV_TASKID_BASE2 + (MOLOTOV_TASKID_OFFSET * i) + o)) {
remove_task(MOLOTOV_TASKID_BASE2 + (MOLOTOV_TASKID_OFFSET * i) + o);
#if defined MOLOTOV_DEBUG
tmpdbgid = MOLOTOV_TASKID_BASE2 + (MOLOTOV_TASKID_OFFSET * i) + o;
log_amx("[MC] %d exists. ----------==========----------", tmpdbgid);
#endif
}
// The third task for each Molotov is not stopped so it can remove the Molotov/info_target entities.
}
}
}
// This function handles the killing and scoring.
// iKillerTeam is stored because the killer can disconnect before the Molotov kills someone, leading to inaccurate scoring
stock kill(iKiller, iVictim, iKillerTeam) {
//TFC: DeathMsg, ScoreInfo, ScoreInfo // One ScoreInfo for killer and one for victim (order varies)
//DOD: DeathMsg, ScoreShort, Frags // ScoreShort=victim, Frags=killer
// CS: DeathMsg, Money, ScoreInfo, ScoreInfo
// Scoreboard
// CSTRIKE: Score Deaths
// DMC: Frags Deaths
// DOD: Score Kills Deaths
// HL: Score Deaths
// HLOF: Kills Deaths
// TFC: Score Deaths
// Ricochet: Points
/* ----- Attacker ----- ------ Victim ------
Score Deaths Kills Score Death Kills
CS kill +1 - N/A - +1 N/A
CS team kill -1 - N/A - +1 N/A
CS suicide -1 +1 N/A ---------------------
CS detonate/defuse +3 - N/A ---------------------
DOD kill - - +1 - +1 -
DOD team kill - - - - +1 - (mp_tkpenalty handles punishment)
DOD suicide - +1 - ---------------------
DOD cap +1 - - ---------------------
TFC kill +1 - N/A - +1 N/A
TFC team kill -1 - N/A - +1 N/A
TFC suicide -1 +1 N/A ---------------------
TFC cap/control varies - N/A ---------------------
*/
// ------------------------------------------------------------------------------------------------- DeathMsg (CS, DOD, TFC)
// DeathMsg - Triggers HUD message and player console message
// DOD and CSTRIKE have different formats for DeathMsg; most other mods should be default
message_begin(MSG_ALL, g_msgDeathMsg, {0,0,0}, 0);
write_byte(iKiller); // Killer ID
write_byte(iVictim); // Victim ID
#if defined CSTRIKE
write_byte(0); // Is Headshot?
#endif
#if defined DOD
write_byte(0); // Weapon ID - These don't match the DODW_* constants, and a custom weapon ID does not work, so we use "world"
#else
write_string("molotov"); // Truncated Weapon Name
#endif
message_end();
// ------------------------------------------------------------------------------------------------- /DeathMsg (CS, DOD, TFC)
// This block of code actually kills the user (silently - DeathMsg was already created)
new iVictimTeam = get_user_team(iVictim);
new iMsgBlock = get_msg_block(g_msgDeathMsg); // Store original block value
set_msg_block(g_msgDeathMsg, BLOCK_ONCE); // Start blocking DeathMsg
#if defined CSTRIKE
new iKillerFrags = get_user_frags(iKiller);
new iVictimFrags = get_user_frags(iVictim);
// TFC and CS scoring are mostly the same. See TFC comment. (I did most of my testing with TFC first)
if (iKiller != iVictim) {
fm_set_user_frags(iVictim, iVictimFrags + 1); // Add frag that user_kill() will remove
}
if (iKillerTeam != iVictimTeam) {
iKillerFrags++; // Killer's Score = Score + 1
} else {
iKillerFrags--; // Killer's Score = Score - 1
}
fm_set_user_frags(iKiller, iKillerFrags);
// CSTRIKE Results --------------------------------------------------------------------------------------------------
// DeathMsg ScoreInfoMsg KScore KDeath VScore VDeath Internally
//user_kill(iVictim, 0); // Yes Yes (Victim) 0 0 -1 +1 Everything matches
//user_kill(iVictim, 1); // Yes Yes (Victim) 0 0 -1 +1 VScore is different internally (unchanged)
//user_silentkill(iVictim); // No Yes (Victim) 0 0 -1 +1 VScore is different internally (unchanged)
//dllfunc(DLLFunc_ClientKill, iVictim); // Yes Yes (Victim) 0 0 -1 +1 Everything matches
//fm_user_kill(iVictim, 0); // Yes Yes (Victim) 0 0 -1 +1 Everything matches
//fm_user_kill(iVictim, 1); // Yes Yes (Victim) 0 0 0 +1 Everything matches (fm_user_kill adds 1 to VScore)
// user_silentkill() blocks the DeathMsg, but it doesn't update the scoreboard properly
// fm_user_kill() updates the scoreboard properly, but generates a DeathMsg with Victim=Killer, so we have to block that.
#endif
#if defined TFC
new iVictimFrags = tfc_get_user_frags(iVictim);
// TFC treats every type of kill in code as a suicide and takes away 1 frag from the victim.
// After *extensive* testing, I found that the easiest solution is to increment the victim frags
// (stored in pdata) beforehand and let the game decrement it and send out ScoreInfo messages.
// This applies to *victims* of team kills as well as normal kills. Suicides still lose a frag.
if (iKiller != iVictim) {
tfc_set_user_frags(iVictim, iVictimFrags + 1);
}
if ((iKillerTeam == iVictimTeam) || tfc_is_team_ally(iKillerTeam, iVictimTeam)) { // TODO: tfc_is_team_ally() is broken in AMX Mod X
tfc_set_user_frags(iKiller, get_user_frags(iKiller) - 1); // Killer's Score = Score - 1
} else {
tfc_set_user_frags(iKiller, get_user_frags(iKiller) + 1); // Killer's Score = Score + 1
}
// TFC Results --------------------------------------------------------------------------
// DeathMsg ScoreInfoMsg VFrag Internally
//user_kill(iVictim, 0); // Yes Yes -1 Everything matches
//user_kill(iVictim, 1); // Yes Yes 0 VFrags doesn't match
//user_silentkill(iVictim); // No Yes 0 VFrags doesn't match
//dllfunc(DLLFunc_ClientKill, iVictim); // Yes Yes -1 Everything matches
//fm_user_kill(iVictim, 0); // Yes Yes -1 Everything matches
//fm_user_kill(iVictim, 1); // Yes Yes -1 Everything matches
#endif
// DOD just works..
// DOD Results --------------------------------------------------------------------------------------------------
// DeathMsg ScoreShortMsg FragMsg KFrag VDeath VFrag Internally
//user_kill(iVictim, 0); // Yes Yes N 0 +1 0 Everything matches
//user_kill(iVictim, 1); // Yes Yes N 0 +1 0 Everything matches
//user_silentkill(iVictim); // No Yes N 0 +1 0 Everything matches
//dllfunc(DLLFunc_ClientKill, iVictim); // Yes Yes N 0 +1 0 Everything matches
//fm_user_kill(iVictim, 0); // Yes Yes N 0 +1 0 Everything matches
//fm_user_kill(iVictim, 1); // Yes Yes N 0 +1 +1 OK, but VFrags shouldn't be changed
user_kill(iVictim, 0);
set_msg_block(g_msgDeathMsg, iMsgBlock); // Stop blocking DeathMsg
//CSTRIKE client console messages:
//Kill "Player1 killed [P0D]M0rbid Desire (2) with molotov"
//TK "Player1 killed [POD]Kate_Winslet (2) with molotov"
//Self "Player1 killed self with molotov"
//DOD client console messages: (DOD uses indexes instead of strings and a custom weapon name can't be sent to the player console)
//Kill "Player1 killed Sgt.Moving_Target with world"
//TK "Player1 killed his teammate Sgt.dontSHOOTiJUSTwannaTALK with world"
//Self "Player1 killed self"
//TFC client console messages:
//Kill "Player1 killed [FoX]JesseJames with molotov"
//TK "Player1 killed [FoX]Barry with molotov"
//Self "Player1 killed self with molotov"
// I'm not really sure if this does anything, but it seems to match the Valve wiki: https://developer.valvesoftware.com/wiki/HL_Log_Standard
new sVictim[32], sVictimAuth[35], sVictimTeam[32];
get_user_name(iVictim, sVictim, charsmax(sVictim));
get_user_authid(iVictim, sVictimAuth, charsmax(sVictimAuth));
get_user_team(iVictim, sVictimTeam, charsmax(sVictimTeam)); // TERRORIST, CT, Allies, Axis, #Dustbowl_team1 (Attackers/Blue), #Dustbowl_team2 (Defenders/Red)
if (iKiller == iVictim) {
log_message("^"%s<%d><%s><%s>^" committed suicide with ^"molotov^"", sVictim, get_user_userid(iVictim), sVictimAuth, sVictimTeam);
} else if (is_user_connected(iKiller)) {
new sKiller[32], sKillerAuth[35], sKillerTeam[32];
get_user_name(iKiller, sKiller, charsmax(sKiller));
get_user_authid(iKiller, sKillerAuth, charsmax(sKillerAuth));
get_user_team(iKiller, sKillerTeam, charsmax(sKillerTeam));
log_message("^"%s<%d><%s><%s>^" killed ^"%s<%d><%s><%s>^" with ^"molotov^"", sKiller, get_user_userid(iKiller), sKillerAuth, sKillerTeam, sVictim, get_user_userid(iVictim), sVictimAuth, sVictimTeam);
}
// TODO: There currently isn't a log message for a kill by a disconnected player. The wiki doesn't show the expected format.
// ------------------------------------------------------------------------------------------------- Money (CS)
#if defined CSTRIKE
new iMoney;
if (iKillerTeam == iVictimTeam) {
iMoney = cs_get_user_money(iKiller) - 3300; // TODO - $1500 hostage kill penalty
cs_set_user_money(iKiller, iMoney < 0 ? 0 : iMoney);
} else {
iMoney = cs_get_user_money(iKiller) + 300;
cs_set_user_money(iKiller, iMoney > 16000 ? 16000 : iMoney);
}
#endif
// ------------------------------------------------------------------------------------------------- /Money (CS)
// ------------------------------------------------------------------------------------------------- ScoreInfo (CS and TFC)
// ScoreInfo - Updates scoreboard on clients (actual values are changed elsewhere)
// TFC - ScoreInfo messages are sent automatically after killing a player.
// CS - ScoreInfo is sent automatically for the victim, but not killer.
#if defined CSTRIKE
message_begin(MSG_ALL, g_msgScoreInfo); // Killer ScoreInfo
write_byte(iKiller);
write_short(iKillerFrags);
write_short(get_user_deaths(iKiller));
write_short(0);
write_short(iKillerTeam);
message_end();
#endif
// ------------------------------------------------------------------------------------------------- /ScoreInfo (CS and TFC)
#if defined DOD
// ------------------------------------------------------------------------------------------------- ScoreShort (DOD)
// ScoreShort is sent by user_kill()
// ------------------------------------------------------------------------------------------------- /ScoreShort (DOD)
// ------------------------------------------------------------------------------------------------- Frags (DOD)
if (iKillerTeam != iVictimTeam) { // Only give a frag if the player killed was an enemy (not suicide or TK)
dod_set_user_kills(iKiller, dod_get_user_kills(iKiller) + 1, 1); // These natives seem to work properly.
}
// ------------------------------------------------------------------------------------------------- /Frags (DOD)
#endif
}
// Attempt to drop the passed coordinates to ground level
stock ground_z(iOrigin[3], ent, skip = 0, iRecursion = 0) {
iOrigin[2] += random_num(5, 80);
if (!pev_valid(ent)) {
return iOrigin[2];
}
new Float:fOrigin[3];
IVecFVec(iOrigin, fOrigin);
set_pev(ent, pev_origin, fOrigin);
engfunc(EngFunc_DropToFloor, ent);
if (!skip && !engfunc(EngFunc_EntIsOnFloor, ent)) {
if (iRecursion >= ANTI_LAGG) {
skip = 1;
}
return ground_z(iOrigin, ent, skip, ++iRecursion);
}
pev(ent, pev_origin, fOrigin);
return floatround(fOrigin[2]);
}
// If plugin or override is disabled, reset Molotov models to original models
#if defined CSTRIKE || defined DOD
stock reset_molotovs() {
new ent = g_MaxPlayers;
#if defined CSTRIKE
// TODO - My limited testing showed this code is pointless.
// It has no negative effect, so I'm leaving it for the 3.30 release.
// (I don't think "model" is a valid parameter.)
new iOwner;
while ((ent = engfunc(EngFunc_FindEntityByString, ent, "model", "models/molotov/w_molotov.mdl"))) {
iOwner = pev(ent, pev_owner);
#if defined MOLOTOV_DEBUG
client_print(0, print_chat, "reset_molotovs - found one Molotov! Owner(%d)", iOwner);
#endif
// If plugin is disabled or player owns no molotovs, reset their model
if (!get_pcvar_num(pEnabled) || !g_NumMolotov[ID_TO_INDEX(iOwner)]) {
engfunc(EngFunc_SetModel, ent, "models/w_hegrenade.mdl");
}
}
#endif
#if defined DOD
new iOwner;
while ((ent = engfunc(EngFunc_FindEntityByString, ent, "model", "models/molotov/w_molotov.mdl"))) {
#if defined MOLOTOV_DEBUG
client_print(0, print_chat, "reset_molotovs - found one Molotov!");
#endif
iOwner = pev(ent, pev_owner);
if (iOwner) {
switch(get_user_team(iOwner)) {
case ALLIES: { // (or British)
engfunc(EngFunc_SetModel, ent, "models/w_grenade.mdl"); // Mills is the same model, so this is probably fine
}
case AXIS: {
engfunc(EngFunc_SetModel, ent, "models/w_stick.mdl");
}
}
}
}
#endif
}
#endif
// Mods that show the model before it is thrown need the model set (I think)
#if defined CSTRIKE || defined DOD
stock set_molotovs() {
new ent = g_MaxPlayers;
#if defined CSTRIKE
while ((ent = engfunc(EngFunc_FindEntityByString, ent, "model", "models/w_hegrenade.mdl"))) {
#if defined MOLOTOV_DEBUG
client_print(0, print_chat, "set_molotovs - found one hegrenade!");
#endif
engfunc(EngFunc_SetModel, ent, "models/molotov/w_molotov.mdl");
}
#endif
#if defined DOD
while ((ent = engfunc(EngFunc_FindEntityByString, ent, "model", "models/w_grenade.mdl"))) {
#if defined MOLOTOV_DEBUG
client_print(0, print_chat, "set_molotovs - found one grenade!");
#endif
engfunc(EngFunc_SetModel, ent, "models/molotov/w_molotov.mdl");
}
ent = g_MaxPlayers;
while ((ent = engfunc(EngFunc_FindEntityByString, ent, "model", "models/w_stick.mdl"))) {
#if defined MOLOTOV_DEBUG
client_print(0, print_chat, "set_molotovs - found one stick!");
#endif
engfunc(EngFunc_SetModel, ent, "models/molotov/w_molotov.mdl");
}
ent = g_MaxPlayers;
while ((ent = engfunc(EngFunc_FindEntityByString, ent, "model", "models/w_mills.mdl"))) {
#if defined MOLOTOV_DEBUG
client_print(0, print_chat, "set_molotovs - found one Mills!");
#endif
engfunc(EngFunc_SetModel, ent, "models/molotov/w_molotov.mdl");
}
#endif
}
#endif
// Show optional buy menu
#if defined CSTRIKE
public show_molotov_menu(id) {
new menu[128];
formatex(menu, charsmax(menu), "Buy Molotov Cocktail ($%d)?^n^n1. Yes^n2. No^n^n0. Exit", get_pcvar_num(pPrice));
// This shows the menu for 30 seconds, I tried first with get_cvar_num("mp_buytime")*60 , but it didn't work well
// when using 0.5 as mp_buytime. If you want to, just change the time below.
show_menu(id, MOLOTOV_MENU_KEYS, menu, 30);
return PLUGIN_HANDLED;
}
#endif
// Our menu function will get the player id and the key they pressed
#if defined CSTRIKE
public giveMolotov(id, key) {
//key will start at zero
switch(key) {
case 0: buy_molotov(id);
//I don't think these messages are necessary.
//case 1: client_print(id, print_center, "You have chosen not to buy a Molotov cocktail");
//default: client_print(id, print_center, "You have chosen to exit the Molotov menu");
}
}
#endif
// Set user frags (score) in TFC
#if defined TFC
stock tfc_set_user_frags(iIndex, iNewFrags) {
if (is_linux_server()) {
set_pdata_int(iIndex, 76, iNewFrags); // real_frags = 76 (on Linux) Required!
} else {
set_pdata_int(iIndex, 77, iNewFrags); // real_frags = 77 (on Windows) Required?
} // Is there a mac version?
// As far as I can tell, real_frags is what should be set, and something copies it to m_iClientFrags.
//set_pdata_int(iIndex, 643, iNewFrags); // m_iClientFrags = 643 (on Linux/Windows)
// Sometimes this is required, sometimes it isn't. I think what is happening is that in
// the cases it doesn't seem to be required, pev_frags is getting updated internally.
set_pev(iIndex, pev_frags, float(iNewFrags));
#if defined MOLOTOV_DEBUG
mydump(iIndex, 65, 85);
mydump(iIndex, 635, 655);
#endif
}
#endif
// Return a user's frags, and verify that the TFC offsets haven't changed
#if defined TFC
stock tfc_get_user_frags(iIndex) {
new iOffset = (is_linux_server() ? 76 : 77); // real_frags = 76 (Linux), 77 (Windows)
new iFrags = get_user_frags(iIndex);
// This code is the easiest way to detect a change in the offsets and help prevent annoying troubleshooting.
// get_user_frags seems to always return the correct value.
if (iFrags != get_pdata_int(iIndex, iOffset)) {
client_print(0, print_chat, "OFFSET CHANGED! get_user_frags(%d):%d; get_pdata_int(%d, %d):%d; Contact plugin author!", iIndex, iFrags, iIndex, iOffset, get_pdata_int(iIndex, iOffset));
console_print(0, "WARNING! get_user_frags != real_frags! Contact plugin author!!!!!!!!!!!!!!!!!!!!");
}
return get_pdata_int(iIndex, iOffset);
}
#endif
// This won't be compiled unless MOLOTOV_DEBUG is set (and only for TFC)
#if defined TFC
stock mydump(iIndex, iStart, iEnd) {
new sLine[512];
new FILE[] = "addons/amxmodx/logs/pdatadump.log";
new sClassname[64];
entity_get_string(iIndex, EV_SZ_classname, sClassname, charsmax(sClassname));
format(sLine, charsmax(sLine), "Starting dump of entity %s %d", sClassname, iIndex);
console_print(1, "%s to file %s", sLine, FILE);
if (!write_file(FILE, sLine)) {
console_print(1, "Error dumping to %s!", FILE);
return PLUGIN_HANDLED;
}
for (new i = iStart; i <= iEnd; i++) {
format(sLine, charsmax(sLine), "%s %d: Offset %d:^t%d^t%f", sClassname, iIndex, i, get_pdata_int(iIndex, i), get_pdata_float(iIndex, i));
if (!write_file(FILE, sLine)) {
console_print(1, "Error dumping to %s!", FILE);
return PLUGIN_HANDLED;
}
}
console_print(1, "Dump done. Check %s!", FILE);
return 1;
}
#endif