Multumesc anticipat.
- | Afiseaza codul
/*************************************************************************** * This plugin reads keyword/wav/mp3 combinations from a configfile and when * a player says one of the keywords, it will trigger HL to play that Wav/MP3 * file to all or dead/alive players. It allows reloading of the file without * restarting the current level, as well as adding keyword/wav/mp3 * combinations from the console during gameplay. Also includes banning * players from playing sounds. * * Credits: * - Luke Sankey -> original author * - HunteR -> modifications * * Functions included in this plugin: * mp_sank_sounds_download 1/0 - turn internal download system on/off * mp_sank_sounds_freezetime <x> - x = time in seconds to wait till first sounds are played (connect sound) * mp_sank_sounds_obey_duration <x> - determine whos sounds may overlap (bit mask) (see readme.txt) * amx_sound - turn Sank Sounds on/off * amx_sound_help - prints all available sounds to console * amx_sound_play <dir/sound> - plays a specific wav/mp3/speech * amx_sound_add <keyword> <dir/sound> - adds a word/wav/mp3/speech * amx_sound_reload <filename> - reload your snd-list.cfg or custom .cfg * amx_sound_remove <keyword> <dir/sound> - remove a word/wav/mp3 * amx_sound_write <filename> - write all settings to custom .cfg * amx_sound_reset <player> - resets quota for specified player * amx_sound_debug - prints debugs (debug mode must be on, see define below) * amx_sound_ban <player> - bans player from using sounds for current map * amx_sound_unban <player> - unbans player from using sounds for current map * amx_sound_top <x> - shows the top <x> most played keywords (leave <x> away for top 10) * * Config file settings: * SND_WARN - The number at which a player will get warned for playing too many sounds each map * SND_MAX - The number at which a player will get muted for playing too many sounds each map * SND_MAX_DUR - The maximum amount of seconds a player can play sounds each map (float ) * SND_JOIN - The Sounds to play when a person joins the game * SND_EXIT - The Sounds to play when a person exits the game * SND_DELAY - Minimum delay between sounds (float) * SND_MODE XX - Determinates who can play and who can hear sounds (see readme.txt for details) * SND_IMMUNITY "XYZ" - Determine the access levels which shall have immunity to warn/ban * EXACT_MATCH 1/0 - Determinates if plugin triggers on exact match, or partial speech match * ADMINS_ONLY 1 - Determinates if only admins are allowed to play sounds * DISPLAY_KEYWORDS 1/0 - Determinates if keywords are shown in chat or not * * Commands available for each player: * amx_sound_help - prints all available sounds to console * say "/soundson" - now the player can hear sounds again * say "/soundsoff" - player disables ability to hear sounds * say "/sounds" - shows a list of all sounds * say "/soundlist" - shows a list of all sounds * * ported to Amx Mod X by White Panther * * v1.0.2 (original 4.1 but this is AmxModX): * - initial release for AmxModX * - renamed commands to fit with AmxModX * - Admin sounds cannot be seen by normal people when using amx_sound_help * - sounds are precached from file * - fix: check if soundfile exist before precache (that should solve some probs) * - fix: if chat message was longer than 29 chars the first wav in cfg was played * * v1.1.3 : * - fixed bug with spaces between keywords and wavs * - multiple Join and Exit sounds can now be used * - fixed bug where connect and disconnect sound have not been played * - fixed bug where dead players could not hear sounds * - added bot check * - added option to only allow admins to play sounds * * v 1.2.4 : * - added mp3 support (they have to be in <Mod-Dir>/sound too) (engine module needed therefore) (+ hotfix: wavs not being played) * - changed the way of initializing each sound file (if bad file it wont be loaded and error msg will be printed) * - changed SND_KICK to SND_MAX * - increased default defines ( words: 40 - > 80 / each wavs: 10 -> 14 / file chars: 30 -> 60 ) * - fixed bug for 32 players * - increased memory usage for variables to 64K (should fix probs) * - while parsing there is now a check if file exists (if not it wont be put in list) * * v1.2.5: * - added a cvar to enable or disable auto download (change will take place after restart/mapchange) * * v1.3: * - fixed: * - fixed prob where strings were copied into other strings with no size match * - removed bot detection (maybe this was causing some problems, playing sounds to bots does not do any harm) * - admin sounds could not be played (eg: hallo; misc/hi.wav;@misc/hi2.wav -> hi2.wav was not played, even by admins) * - added: * - type "/sounds" in chat to get a MOTD window with all sounds available (not all mods support MOTD window) * - ability for speech sounds (like the AmxModX's speechmenu) * - admin check to "amx_sound_debug" so in debugmode only admins can use it * - list is now sorted by name for more readable output (sort by Bailopan) (sort can be turned off by define) * * v1.3.2: * - fixed: * - mp3 support not working * - changed: * - mp3 now dont need to be in sound folder but anywhere you want (anywhere in your mod folder though) * just specify the correct path (eg: music/mymusic/my.mp3 or sound/testmp3/test.mp3 or mainfolder.mp3) * - amx_sound_debug can now also be used if debug mode is off (this function prints the sound matrix) * * v1.3.3: * - added: * - cvar "mp_sank_sounds_freezetime" to define when first connect/disconnect sounds are played after mapchange (in seconds) * * v1.3.4: * - fixed: * - error where some players could not hear any sound * - changed: * - some log messages got better checks * - reimplemented check for bots * * v1.3.5: * - added: * - with "/soundson" and "/soundsoff" each player can activate/deactivate the ability to hear sounds * * v1.3.7: * - added: * - "DISPLAY_KEYWORDS" to config, it determinates if keywords are shown in chat or not * - option to load specific sounds only on specific maps * - changed: * - "SND_DELAY" is now a float * * v1.4.0: * - added: * - option to load packages of sounds, packages cycle with each map-change (packages must be numbered) * - ability to ban people from using sounds (only for current map) ( amx_sound_ban <player> <1/0 OR on/off> ) * - changed: * - precache method changed * - all keywords are now stored into buffer, even those sounds that are not precached * - code improvements * * v1.4.1: * - fixed: * - when setting DISPLAY_KEYWORDS to 0 chat was disabled * * v1.4.2: * - fixed: * - players could be banned from sounds after reconnect * - added: * - option to include sounds from "half-life.gcf" and <current mod>.gcf * * v1.4.2b: * - fixed: * - compile error when disabling mp3 support * * v1.4.3: * - fixed: * - keywords without or with wrong files will not be added anymore * - possible errors fixed * - error with MOTD display fixed * * v1.4.5: * - fixed: * - ADMINS_ONLY was not working always * - players could only play less sound than specified in SND_MAX * - runtime error with amx_sound_reload * - added: * - sounds can now also be used in team chat * - amx_sound_unban to unban players * - changed: * - keyword check tweaked * - amx_sound_ban now do not expect additional parameter "on / off" or "1 / 0" * * v1.4.7: * - fixed: * - keywords with admin and public sounds, could block normal players from playing normal sounds * - runtime error which could stop plugin to work * - message telling players to wait till next sound can be played is not displayed on every word anymore * * v1.5.0: ( AmxModX 1.71 or better ONLY ) * - fixed: * - sounds being not in a subfolder ( eg: sound/mysound.wav ) will now be played * - reconnecting to reset quota will not work anymore * - no more overlapping sounds ( Join and Exit sounds will still overlap other but others cannot overlap them ) * - amx_sound_reset now accepts IDs too * - sound quota could be increased even if no sound was played * - added: * - sound duration is now calculated * - changed: * - SND_DELAY does not affect admins anymore * - SND_SPLIT has been replaced with more customizable SND_MODE * - removed support to disable MP3 * * v1.5.0b: * - fixed: * - rare runtime error * * v1.5.1: * - fixed: * - calculation for MP3's encoded with MPEG 2 * - added: * - saying "/soundlist" will now show sound list like "/sounds" does * - CVAR: "mp_sank_sounds_obey_duration" to determine if sounds may overlap or not ( default: 1 = do not overlap ) * * v1.5.1b: * - fixed: * - runtime error in mp3 calculation * * v1.5.2: * - fixed: * - support for SND_DELAY was accidently removed * - some possible minor bugs * - added: * - SND_MAX_DUR: maximum of seconds a player can play sounds each map * - two new options for SND_MODE ( read help for more information ) * * v1.5.3: * - fixed: * - admin being able to play sounds when "mp_sank_sounds_obey_duration" was on * - added: * - CVAR: "mp_sank_sounds_motd_address" to use a website to show all sounds ( empty cvar = no website will be used ) * * v1.5.4: * - fixed: * - error in mp3 calculation * - when using "mapnameonly" option, following options have been ignored * - added: * - minor detection for damaged/invalid files * - changed: * - both "SND-LIST.CFG" and "snd-list.cfg" will work now ( linux ) * - code improvements * - faster config parsing/writing * * v1.5.5: * - fixed: * - error in mp3 calculation ( once again ) * - added: * - additional debug info for mp3's when compiled in DEGUB_MODE 1 * * v1.5.6: * - fixed: * - sounds located in <MODDIR>/sounds/ (no subfolder) not being played if dead and alive not being splitted * - long lines not being parsed correctly * - players could play one more sound than allowed * * v1.6.0: (16.4.2007) * - fixed: * - speech sounds not being played * - join / exit sound duration was incorrect * - SND_WARN / SND_MAX error checking could display wrong error * - added: * - access can be defined for every sound and keyword seperately * - changed: * - partly rewritten * - way of saving data * - sounds when enabling and disabling Sank Sounds are not precached anymore ( hard coded ) * - many code improvements * * v1.6.2: (16.01.2008) * - fixed: * - removed debug message * - admins are not included in overlapping check anymore * - non admins could see sounds that are for admins only * - bug when adding and removing sounds ingame to list (wierd keywords and sounds) * - added: * - "PLAY_COUNT_KEY" and "PLAY_COUNT" to data structure to count how often a key and sound has been used * - messages for players when enabling/disabling sounds and if players have to wait cause of delay * - changed: * - sank sounds is now precaching sounds after plugin init (fakemeta modul needed) * - no more engine, but therefore fakemeta is needed * - minor code tweaks * * v1.6.3: (29.02.2008) * - fixed: * - runtime error if more sounds added than defined in MAX_KEYWORDS * - commenting SND_JOIN and SND_EXIT (adding # or // infront of them) made the following sounds to be added to these options * - changed: * - CVAR "mp_sank_sounds_obey_duration" is now a bitmask (see readme.txt) * * v1.6.4: (21.12.2008) * - added: * - warning for unsupported mp3 files * - changed: * - mp3 detection code rewritten * * v1.6.5: (14.01.2009) * - fixed: * - wav detection for bad files * * v1.6.5b: (22.01.2009) * - changed: * - removed warning for unsupported mp3s (they are supported) * * v1.6.6: (03.03.2009) * - fixed: * - last entry in configfile was not sorted * - runtime error with keywords without any sound * - exploit where SND_JOIN and SND_EXIT could be used as keywords * - changed: * - SND_JOIN and SND_JOIN do not have to be before any other keyword * * v1.6.6b: (29.03.2009) * - fixed: * - runtime error * - if SND_JOIN or SND_JOIN was not at the beginning and more sounds were added afterwards, those new sounds overwrote previous sounds * * v1.6.6c: (30.06.2009) * - fixed: * - removed debug message * * v1.6.6d: (03.07.2009) * - fixed: * - speech files not being played * * v1.7.0: (08.08.2011) * - added: * - further checks for bad configfiles * - new option SND_IMMUNITY (defines all levels that shall get immunity) * - info when used last sound * - amx_sound_top <x> shows the top <x> (default 10) most played keywords during current map * - using keywords followed by a ! (eg: haha!) will play the sound bound to a location (WAVs only) * you can move away from the sound. NO config change needed * - changed: * - WAVs are not bound to <mod-dir>/sound folder anymore (config change needed unfortunately) * all sounds now need the full path (eg: haha; sound/misc/haha.wav) * * v1.7.1: (11.08.2011) * - fixed: * - WAVs not downloading and producing error messages * * v1.8.0: (11.01.2012) * - fixed: * - adding new sounds with console command could add it but it would not be available * - changed: * - dynamic arrays are now used to store key/sound data to remove limits * - saving to default config file is now allowed * * v1.8.1: (14.03.2013) * - fixed: * - determination if sound can be played for admins * - "amx_sound_add" could add empty keywords if sound was invalid * - "amx_sound_add" is not checking sounds if they exist anymore * - changed: * - increased motd webpage link length to 255 characters * * v1.8.2: (01.09.2013) * - fixed: * - exit sounds could be replaced by keyword sounds * * v1.8.3: (21.10.2013) * - fixed: * - ADMINS_ONLY setting was ignored * * v1.8.4: (06.01.2014) * - fixed: * - admins could get spammed with "You are muted" messages * * v1.8.5: (04.05.2014) * - added: * - support to also load files from "<MOD>_downloads" folder * - fixed: * - issue with ADMINS_ONLY and RCON access level * * v1.8.6: (22.05.2016) * - fixed: * - players could get stuck in a "spectator" mode state or being kicked if trying to play a sound with exactly TOK_LENGTH length * * v1.8.7: (2017.11.26) * - fixed: * - console warnings when "developer" is set to "1" * - misc * - changed: * - support to also load files from "<MOD>_<xxx>" folders * * v1.8.8: (2019.05.05) * - fixed: * - Players had to wait up to SND_DELAY after map change before being able to play sounds * - Some warnings not being displayed * - Issue where downloading was not working for WAV files (due to "developer 1" fix) * * IMPORTANT: * a) if u want to use the internal download system do not use more than 200 sounds (HL cannot handle it) * (also depending on map, you may need to use even less) * but if u disable the internal download system u can use as many sounds as the plugin can handle * (max should be over 100000 sounds (depending on the Array Defines ), BUT the plugin speed * is another question with thousands of sounds ;) ) * * b) File has to look like this: * SND_MAX; 20 * SND_MAX_DUR; 180.0 * SND_WARN; 17 * SND_JOIN; misc/hi.wav * SND_EXIT; misc/comeagain.wav * SND_DELAY; 0.0 * SND_MODE; 15 * SND_IMMUNITY; "l" * EXACT_MATCH; 1 * ADMINS_ONLY; 1 * DISPLAY_KEYWORDS; 1 * * # Word/Sound combinations: * crap; misc/awwcrap.Wav;misc/awwcrap2.wav * woohoo; misc/woohoo.wav * @ha ha; misc/haha.wav * @abm@godlike; misc/godlike.wav * doh; misc/doh.wav;misc/doh2.wav;@misc/doh3.wav * mp3; sound/mymp3.mp3;music/mymp3s/number2.mp3;mainfolder.mp3 * target; "target destroyed" * * mapname TESTMAP * testmap; misc/doh.wav * mapname TESTMAP2 * testmap2; misc/haha.wav;sound/mymp3.mp3 * testmap3; misc/hi.wav * * package 1 * haha2; misc/haha.wav * doh3; misc/doh3.wav * package 2 * hi; misc/hi.wav * * modspecific * <keyword>; <location>/<name>.wav * * Follow these instructions * wavs: * - base directory is "mod-dir/sound/" * - put EXACT PATH to the wav beginning from base directory (eg misc/test.wav or test2.wav) * mp3: * - base directory is "mod-dir/" * - put the EXACT PATH to the mp3 (eg sound/test.mp3 or music/mymp3s/test2.mp3 or mainfolder.mp3) * speech: * - base directory is "mod-dir/sound/vox/" * - these files are inside the steam package * - for a list look at c) * mapname: * - type mapname <space> the real mapname (without .bsp) * - everthing below will be loaded only on this map * mapnameonly: * - type mapnameonly <space> the real mapname (without .bsp) * - everthing below will be loaded only on this map * - everthing below will be only available on this map * package: * - type package <space> number * - everthing below will be loaded only once and switched to next package on map-change * - if only 1 package this package will be used every map-change * modspecific: * - every sound below that line must be inside half-life.gcf or <yourmod>.gcf * - if you add other files then said above they may/will crash your server as these sounds are assumed to be existent * * c) speech sounds must be put in quotes (eg: target; "target destroyed") * you may not put different speech types into 1 speech or the speech wont be played * speech without directory is used from "vox/.." * first specify the speech type (ONLY ONCE eg hgrunt/) and then put the words with spaces between each speech * eg "hgrunt/yessir barney/stop1" will not work as 2 different speeches * BUT "hgrunt/yessir no" will work * get all available speech sounds here: * "http://www.adminmod.org/help/online/Adm ... Sounds.htm" * * d) "@" infront of a * - word means only admin can use this word * - wav/mp3/speech/word means players can use the word but this sound is only played by admins * * e) custom admin access: * - infront of a word/sound add @<ACCESS_LEVELS>@ * - replace <ACCESS_LEVELS> with the access levels you desire * - @abc@ means: everyone with access a, b or c can use it ***************************************************************************/ #include <amxmodx> #include <amxmisc> #include <fakemeta> // set this to 1 to get some debug messages #define DEBUG_MODE 0 // turn this off to stop list from being sorted by keywords in alphabetic order #define ALLOW_SORT 1 // Array Defines, ATTENTION: ( MAX_RANDOM + 1 ) * TOK_LENGTH must be smaller 2048 !!! #define BUFFER_LEN 2048 // Maximum number of space per line to use when reading configfile #define MAX_RANDOM 15 // Maximum number of tries to find a sound file #define TOK_LENGTH 60 // Maximum length of keyword and sound file strings #define MAX_BANS 32 // Maximum number of bans stored #define NUM_PER_LINE 6 // Number of words per line from amx_sound_help //#pragma dynamic 16384 #pragma dynamic 65536 #define ACCESS_ADMIN ADMIN_LEVEL_A #define PLUGIN_AUTHOR "White Panther, Luke Sankey, HunteR" #define PLUGIN_VERSION "1.8.8" new Enable_Sound[] = "sound/misc/woohoo.wav" // Sound played when Sank Sounds being enabled new Disable_Sound[] = "sound/misc/awwcrap.wav" // Sound played when Sank Sounds being disabled new config_filename[128] new SndCount[33] = {0, ...} // Holds the number telling how many sounds a player has played new Float:SndLenghtCount[33] = {0.0, ...} new SndOn[33] = {1, ...} new SND_WARN = 0 // The number at which a player will get warned for playing too many sounds new SND_MAX = 0 // The number at which a player will get muted for playing too many sounds new Float:SND_MAX_DUR = 0.0 new Float:SND_DELAY = 0.0 // Minimum delay between sounds new SND_MODE = 15 // Determinates who can play and who can hear sounds (dead and alive) new SND_IMMUNITY = ACCESS_ADMIN // Determine the access levels which shall have immunity to warn/ban (default ACCESS_ADMIN for backwards compatability) new EXACT_MATCH = 1 // Determinates if plugin triggers on exact match, or partial speech match new ADMINS_ONLY = 1 // Determinates if only admins are allowed to play sounds new DISPLAY_KEYWORDS = 1 // Determinates if keywords are shown in chat or not new Float:NextSoundTime // spam protection new Float:Join_exit_SoundTime // spam protection 2 new Float:LastSoundTime = 0.0 new bSoundsEnabled = 1 // amx_sound <on/off> or <1/0> new CVAR_freezetime, CVAR_obey_duration new g_max_players new banned_player_steamids[MAX_BANS][60] new restrict_playing_sounds[33] new sound_quota_steamids[33][60] new motd_sound_list_address[256] new Array:modSearchPaths enum { PARSE_SND_MAX, PARSE_SND_MAX_DUR, PARSE_SND_WARN, PARSE_SND_DELAY, PARSE_SND_MODE, PARSE_SND_IMMUNITY, PARSE_EXACT_MATCH, PARSE_ADMINS_ONLY, PARSE_DISPLAY_KEYWORDS, PARSE_KEYWORD } enum { ERROR_NONE, ERROR_MAX_KEYWORDS, ERROR_STRING_LENGTH } enum { RESULT_OK = 1, RESULT_BAD_ALIVE_STATUS = 0, RESULT_QUOTA_EXCEEDED = -1, RESULT_QUOTA_DURATION_EXCEEDED = -2, RESULT_SOUND_DELAY = -3, RESULT_ADMINS_ONLY = -4, } enum { FLAG_IGNORE_AMOUNT = 1, FLAGS_JOIN_SND = 2, FLAGS_EXIT_SND = 4 } enum { SOUND_TYPE_SPEECH, SOUND_TYPE_MP3, SOUND_TYPE_WAV, SOUND_TYPE_WAV_LOCAL } enum _:SOUND_DATA_BASE { KEYWORD[TOK_LENGTH + 1], ADMIN_LEVEL_BASE, SOUND_AMOUNT, FLAGS, PLAY_COUNT_KEY, Array:SUB_INDEX } enum _:SOUND_DATA_SUB { SOUND_FILE[TOK_LENGTH + 1], Float:DURATION, ADMIN_LEVEL, SOUND_TYPE, PLAY_COUNT } new Array:soundData public plugin_init( ) { register_plugin("Sank Sounds Plugin", PLUGIN_VERSION, PLUGIN_AUTHOR) register_cvar("sanksounds_version", PLUGIN_VERSION, FCVAR_SERVER) new tmpStr[32] get_cvar_string("sanksounds_version", tmpStr, 31) if ( !equal(tmpStr, PLUGIN_VERSION) ) { set_cvar_string("sanksounds_version", PLUGIN_VERSION) } register_concmd("amx_sound_reset", "amx_sound_reset", ACCESS_ADMIN, " <user | all> : Resets sound quota for ^"user^", or everyone if ^"all^"") register_concmd("amx_sound_add", "amx_sound_add", ACCESS_ADMIN, " <keyword> <dir/sound> : Adds a Word/Sound combo to the sound list") register_clcmd("amx_sound_help", "amx_sound_help") register_concmd("amx_sound", "amx_sound", ACCESS_ADMIN, " : Turns sounds on/off") register_concmd("amx_sound_play", "amx_sound_play", ACCESS_ADMIN, " <dir/sound> : Plays sound to all users") register_concmd("amx_sound_reload", "amx_sound_reload", ACCESS_ADMIN, " : Reloads config file. Filename is optional. If no filename, default is loaded") register_concmd("amx_sound_remove", "amx_sound_remove", ACCESS_ADMIN, " <keyword> <dir/sound> : Removes a Word/Sound combo from the sound list. Must use quotes") register_concmd("amx_sound_write", "amx_sound_write", ACCESS_ADMIN, " : Writes current sound configuration to file") register_concmd("amx_sound_debug", "amx_sound_debug", ACCESS_ADMIN, "prints the whole Word/Sound combo list") register_concmd("amx_sound_ban", "amx_sound_ban", ACCESS_ADMIN, " <name or #userid>: Bans player from using sounds for current map") register_concmd("amx_sound_unban", "amx_sound_unban", ACCESS_ADMIN, " <name or #userid>: Unbans player from using sounds for current map") register_concmd("amx_sound_top", "amx_sound_top", ACCESS_ADMIN, " <number> (optional): Shows the top X (default 10) most used keywords during this map") register_clcmd("say", "HandleSay") register_clcmd("say_team", "HandleSay") register_cvar("mp_sank_sounds_download", "1") CVAR_freezetime = register_cvar("mp_sank_sounds_freezetime", "0") CVAR_obey_duration = register_cvar("mp_sank_sounds_obey_duration", "1") register_cvar("mp_sank_sounds_motd_address", "") g_max_players = get_maxplayers() NextSoundTime = get_gametime() - SND_DELAY LastSoundTime = NextSoundTime soundData = ArrayCreate(SOUND_DATA_BASE) modSearchPaths = ArrayCreate(64) new tmpLen = strlen(Enable_Sound) if ( tmpLen > 4 && equali(Enable_Sound[tmpLen - 4], ".wav") ) { Enable_Sound[tmpLen - 4] = 0 } tmpLen = strlen(Disable_Sound) if ( tmpLen > 4 && equali(Disable_Sound[tmpLen - 4], ".wav") ) { Disable_Sound[tmpLen - 4] = 0 } } public plugin_cfg( ) { get_cvar_string("mp_sank_sounds_motd_address", motd_sound_list_address, 255) new configpath[61] get_configsdir(configpath, 60) format(config_filename, 127, "%s/SND-LIST.CFG", configpath) // Name of file to parse // check if file in capital letter exists // otherwise make it all lowercase and try to load it if ( file_exists(config_filename) ) { parse_sound_file(config_filename) }else { strtolower(config_filename) parse_sound_file(config_filename) } } public client_putinserver( id ) { restrict_playing_sounds[id] = -1 new steamid[60], i get_user_authid(id, steamid, 59) for ( i = 0; i < MAX_BANS; ++i ) { if ( equal(steamid, banned_player_steamids) ) restrict_playing_sounds[id] = i } if ( !equal(steamid, sound_quota_steamids[id]) ) { copy(sound_quota_steamids[id], 59, steamid) SndCount[id] = 0 SndLenghtCount[id] = 0.0 } SndOn[id] = 1 new Float:gametime = get_gametime() if ( gametime <= get_pcvar_num(CVAR_freezetime) ) return new sData[SOUND_DATA_BASE] ArrayGetArray(soundData, 0, sData) if ( sData[SOUND_AMOUNT] == 0 ) return if ( Join_exit_SoundTime >= gametime ) return if ( sData[SOUND_AMOUNT] == 0 ) return new rand = random(sData[SOUND_AMOUNT]) new subData[SOUND_DATA_SUB] ArrayGetArray(sData[SUB_INDEX], rand, subData) if ( subData[ADMIN_LEVEL] != 0 && !(get_user_flags(id) & subData[ADMIN_LEVEL]) ) return playsoundall(subData[SOUND_FILE], subData[SOUND_TYPE]) Join_exit_SoundTime = gametime + subData[DURATION] if ( NextSoundTime < Join_exit_SoundTime ) NextSoundTime = Join_exit_SoundTime } #if AMXX_VERSION_NUM < 183 public client_disconnect( id ) #else public client_disconnected( id ) #endif { SndOn[id] = 1 restrict_playing_sounds[id] = -1 new Float:gametime = get_gametime() if ( gametime <= get_pcvar_num(CVAR_freezetime) ) return new sData[SOUND_DATA_BASE] ArrayGetArray(soundData, 1, sData) if ( sData[SOUND_AMOUNT] == 0 ) return if ( Join_exit_SoundTime >= gametime ) return if ( sData[SOUND_AMOUNT] == 0 ) return new rand = random(sData[SOUND_AMOUNT]) new subData[SOUND_DATA_SUB] ArrayGetArray(sData[SUB_INDEX], rand, subData) if ( subData[ADMIN_LEVEL] != 0 && !(get_user_flags(id) & subData[ADMIN_LEVEL]) ) return playsoundall(subData[SOUND_FILE], subData[SOUND_TYPE]) Join_exit_SoundTime = gametime + subData[DURATION] if ( NextSoundTime < Join_exit_SoundTime ) NextSoundTime = Join_exit_SoundTime } public amx_sound_reset( id , level , cid ) { if ( !cmd_access(id, level, cid, 2) ) return PLUGIN_HANDLED new arg[33], target read_argv(1, arg, 32) if ( equal(arg, "all") == 1 ) { client_print(id, print_console, "Sank Sounds >> Quota has been reseted for all players") for ( target = 1; target <= g_max_players; ++target ) { SndCount[target] = 0 SndLenghtCount[target] = 0.0 } }else { target = cmd_target(id, arg, 1) if ( !target ) return PLUGIN_HANDLED SndCount[target] = 0 SndLenghtCount[target] = 0.0 new name[33] get_user_name(target, name, 32) client_print(id, print_console, "Sank Sounds >> Quota has been reseted for ^"%s^"", name) } return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Adds a Word/Sound combo to the list. If it is a valid line in the config // file, then it is a valid parameter here. The only difference is you can // only specify one Sound file at a time with this command. // // Usage: amx_sound_add <keyword> <dir/sound> // Usage: amx_sound_add <setting> <value> ////////////////////////////////////////////////////////////////////////////// public amx_sound_add( id , level , cid ) { if ( !cmd_access(id, level, cid, 2) ) return PLUGIN_HANDLED new Word[TOK_LENGTH + 1], Sound[TOK_LENGTH + 1] new configOption = 0 read_argv(1, Word, TOK_LENGTH) read_argv(2, Sound, TOK_LENGTH) if ( strlen(Word) <= 0 || strlen(Sound) == 0 ) { client_print(id, print_console, "Sank Sounds >>Invalid format") client_print(id, print_console, "Sank Sounds >>USAGE: amx_sound_add keyword <dir/sound>") return PLUGIN_HANDLED } // First look for special parameters if ( equali(Word, "SND_MAX") ) { SND_MAX = str_to_num(Sound) configOption = 1 }else if ( equali(Word, "SND_MAX_DUR") ) { SND_MAX_DUR = floatstr(Sound) configOption = 1 }else if ( equali(Word, "SND_WARN") ) { SND_WARN = str_to_num(Sound) configOption = 1 }else if ( equali(Word, "SND_DELAY") ) { SND_DELAY = floatstr(Sound) configOption = 1 }else if ( equali(Word, "SND_MODE") ) { SND_MODE = str_to_num(Sound) configOption = 1 }else if ( equali(Word, "SND_IMMUNITY") ) { SND_IMMUNITY = str_to_num(Sound) configOption = 1 }else if ( equali(Word, "EXACT_MATCH") ) { EXACT_MATCH = str_to_num(Sound) configOption = 1 }else if ( equali(Word, "ADMINS_ONLY") ) { ADMINS_ONLY = str_to_num(Sound) configOption = 1 }else if ( equali(Word, "DISPLAY_KEYWORDS") ) { DISPLAY_KEYWORDS = str_to_num(Sound) configOption = 1 } if ( configOption ) { // Do some error checking on the user-input numbers ErrorCheck() return PLUGIN_HANDLED } // Loop once for each keyword new i, j new sData[SOUND_DATA_BASE] new subData[SOUND_DATA_SUB] new aLen = ArraySize(soundData) new subLen new resCode for( i = 0; i < aLen; ++i ) { ArrayGetArray(soundData, i, sData) // If no match found, keep looping if ( !equal(Word, sData[KEYWORD], TOK_LENGTH) ) continue // See if the Sound already exists subLen = ArraySize(sData[SUB_INDEX]) for( j = 0; j < subLen; ++j ) { ArrayGetArray(sData[SUB_INDEX], j, subData) // See if this is the same as the new Sound if ( equali(Sound, subData[SOUND_FILE], TOK_LENGTH) ) { client_print(id, print_console, "Sank Sounds >> ^"%s; %s^" already exists", Word, Sound) return PLUGIN_HANDLED } } // Word exists, but Sound is new to the list, so add entry resCode = array_add_inner_element(i, j, Sound, 0) if ( resCode != -1 ) client_print(id, print_console, "Sank Sounds >> ^"%s^" successfully added to ^"%s^"", Sound, Word) return PLUGIN_HANDLED } // Word/Sound combo is new to the list, so make a new entry array_add_element(i, Word) resCode = array_add_inner_element(i, 0, Sound, 0) if ( resCode != -1 ) { ArraySort(soundData, "sortSoundDataFunc") client_print(id, print_console, "Sank Sounds >> ^"%s; %s^" successfully added", Word, Sound) }else { // removed keyword because no sound could be added array_remove(i) } return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // amx_sound_help lists all amx_sound commands and keywords to the user. // // Usage: amx_sound_help ////////////////////////////////////////////////////////////////////////////// public amx_sound_help( id ) { print_sound_list(id) return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Turns on/off the playing of the Sound files for this plugin only ////////////////////////////////////////////////////////////////////////////// public amx_sound( id , level , cid ) { if ( !cmd_access(id, level, cid, 2) ) return PLUGIN_HANDLED new onoff[5] read_argv(1, onoff, 4) if ( equal(onoff, "on") || equal(onoff, "1") ) { if ( bSoundsEnabled == 1 ) console_print(id, "Sank Sounds >> Plugin already enabled") else { bSoundsEnabled = 1 console_print(id, "Sank Sounds >> Plugin enabled") client_print(0, print_chat, "Sank Sounds >> Plugin has been enabled") if ( Enable_Sound[0] ) { new type = Enable_Sound[0] == '^"' ? SOUND_TYPE_SPEECH : ( Enable_Sound[strlen(Enable_Sound) - 1] == '3' ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV ) playsoundall(Enable_Sound, type) } } return PLUGIN_HANDLED }else if ( equal(onoff, "off") || equal(onoff, "0") ) { if ( bSoundsEnabled == 0 ) console_print(id, "Sank Sounds >> Plugin already disabled") else { bSoundsEnabled = 0 console_print(id, "Sank Sounds >> Plugin disabled") client_print(0, print_chat, "Sank Sounds >> Plugin has been disabled") if ( Disable_Sound[0] ) { new type = Disable_Sound[0] == '^"' ? SOUND_TYPE_SPEECH : ( Disable_Sound[strlen(Disable_Sound) - 1] == '3' ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV ) playsoundall(Disable_Sound, type) } } } return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Plays a sound to all players // // Usage: amx_sound_play <dir/sound> ////////////////////////////////////////////////////////////////////////////// public amx_sound_play( id , level , cid ) { if ( !cmd_access(id, level, cid, 2) ) return PLUGIN_HANDLED new arg[128] read_argv(1, arg, 127) new arg_len = strlen(arg) if ( arg_len < 1 ) { client_print(id, print_console, "Sank Sounds >> Sound is invalid.") return PLUGIN_HANDLED } new type = arg[0] == '^"' ? SOUND_TYPE_SPEECH : ( arg[arg_len - 1] == '3' ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV ) if ( type == SOUND_TYPE_WAV && arg_len > 4 && equali(arg[arg_len - 4], ".wav") ) // WAV with extension { arg[arg_len - 4] = 0 } playsoundall(arg, type) return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Reloads the Word/Sound combos from filename // // Usage: amx_sound_reload <filename> ////////////////////////////////////////////////////////////////////////////// public amx_sound_reload( id , level , cid ) { if ( !cmd_access(id, level, cid, 0) ) return PLUGIN_HANDLED new parsefile[128] read_argv(1, parsefile, 127) // Initialize sound_data array array_clear() soundData = ArrayCreate(SOUND_DATA_BASE) parse_sound_file(parsefile, 0) return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Removes a Word/Sound combo from the list. You must specify a keyword, but it // is not necessary to specify a Sound if you want to remove all Sounds associated // with that keyword // // Usage: amx_sound_remove <keyWord> <dir/sound>" ////////////////////////////////////////////////////////////////////////////// public amx_sound_remove( id , level , cid ) { if ( !cmd_access(id, level, cid, 2) ) return PLUGIN_HANDLED new Word[TOK_LENGTH + 1], Sound[TOK_LENGTH + 1] read_argv(1, Word, TOK_LENGTH) read_argv(2, Sound, TOK_LENGTH) if ( strlen(Word) == 0 ) { client_print(id, print_console, "Sank Sounds >> Invalid format") client_print(id, print_console, "Sank Sounds >> USAGE: amx_sound_remove keyword <dir/sound>") return PLUGIN_HANDLED } // speech must have extra "" if ( strlen(Sound) != 0 && containi(Sound, ".wav") == -1 && containi(Sound, ".mp") == -1 ) format(Sound, TOK_LENGTH, "^"%s^"", Sound) // Loop once for each keyWord new iCurWord, jCurSound new sData[SOUND_DATA_BASE] new subData[SOUND_DATA_SUB] new aLen = ArraySize(soundData) new subLen for( iCurWord = 0; iCurWord < aLen; ++iCurWord ) { ArrayGetArray(soundData, iCurWord, sData) // Look for a Word match if ( !equali(Word, sData[KEYWORD], TOK_LENGTH) ) continue // If no Sound was specified, then remove the whole Word's entry if ( strlen(Sound) == 0 ) { array_remove(iCurWord) client_print(id, print_console, "Sank Sounds >> %s successfully removed", Word) return PLUGIN_HANDLED } // Just remove the one Sound, if it exists subLen = ArraySize(sData[SUB_INDEX]) for( jCurSound = 0; jCurSound < subLen; ++jCurSound ) { ArrayGetArray(sData[SUB_INDEX], iCurWord, subData) // Look for a Sound match if ( !equali(Sound, subData[SOUND_FILE], TOK_LENGTH) ) continue if ( sData[SOUND_AMOUNT] == 1 ) // If this is the only Sound entry, then remove the entry altogether { array_remove(iCurWord) client_print(id, print_console, "Sank Sounds >> %s successfully removed", Word) }else { array_remove_inner(iCurWord, jCurSound) client_print(id, print_console, "Sank Sounds >> %s successfully removed from %s", Sound, Word) } return PLUGIN_HANDLED } // We reached the end for this Word, and the Sound didn't exist client_print(id, print_console, "Sank Sounds >> %s not found", Sound) return PLUGIN_HANDLED } // We reached the end, and the Word didn't exist client_print(id, print_console, "Sank Sounds >> %s not found", Word) return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Saves the current configuration of Word/Sound combos to filename for possible // reloading at a later time. You cannot overwrite the default file. // // Usage: amx_sound_write <filename> ////////////////////////////////////////////////////////////////////////////// public amx_sound_write( id , level , cid ) { if ( !cmd_access(id, level, cid, 2) ) return PLUGIN_HANDLED new savefile[128] read_argv(1, savefile, 127) if ( strlen(savefile) <= 1 ) { if ( strlen(savefile) == 1 && savefile[0] == '!' ) copy(savefile, 127, config_filename) else { client_print(id, print_console, "Sank Sounds >> You have not specified any filename. To save to default configfile use ! as filename/parameter.") return PLUGIN_HANDLED } } new TimeStamp[128], name[33], Text[64] new Textlen = 63 get_user_name(id, name, 32) get_time("%H:%M:%S %A %B %d, %Y", TimeStamp, 127) new file = fopen(savefile, "w+") if ( !file ) { log_amx("Sank Sounds >> Unable to read from ^"%s^" file", savefile) return PLUGIN_HANDLED } formatex(Text, Textlen, "# TimeStamp:^t^t%s^n", TimeStamp) fputs(file, Text) formatex(Text, Textlen, "# File created by:^t%s^n", name) fputs(file, Text) fputs(file, "^n") // blank line fputs(file, "# Important parameters:^n") formatex(Text, Textlen, "SND_MAX;^t^t%d^n", SND_MAX) fputs(file, Text) formatex(Text, Textlen, "SND_MAX_DUR;^t^t%.1f^n", SND_MAX_DUR) fputs(file, Text) formatex(Text, Textlen, "SND_WARN;^t^t%d^n", SND_WARN) fputs(file, Text) new sData[SOUND_DATA_BASE] new subData[SOUND_DATA_SUB] ArrayGetArray(soundData, 0, sData); new subLen = ArraySize(sData[SUB_INDEX]) fputs(file, "SND_JOIN;^t^t") for ( new j = 0; j < subLen; ++j ) { ArrayGetArray(sData[SUB_INDEX], j, subData); cfg_write_keysound(file, subData) } fputc(file, '^n') ArrayGetArray(soundData, 1, sData); subLen = ArraySize(sData[SUB_INDEX]) fputs(file, "SND_EXIT;^t^t") for ( new j = 0; j < subLen; ++j ) { ArrayGetArray(sData[SUB_INDEX], j, subData); cfg_write_keysound(file, subData) } fputc(file, '^n') formatex(Text, Textlen, "SND_DELAY;^t^t%f^n", SND_DELAY) fputs(file, Text) formatex(Text, Textlen, "SND_MODE;^t^t%d^n", SND_MODE) fputs(file, Text) new snd_imm_str[32] get_flags(SND_IMMUNITY, snd_imm_str, 26) formatex(Text, Textlen, "SND_IMMUNITY;^t^t^"%s^"^n", snd_imm_str) fputs(file, Text) formatex(Text, Textlen, "EXACT_MATCH;^t^t%d^n", EXACT_MATCH) fputs(file, Text) formatex(Text, Textlen, "ADMINS_ONLY;^t^t%d^n", ADMINS_ONLY) fputs(file, Text) formatex(Text, Textlen, "DISPLAY_KEYWORDS;^t%d^n", DISPLAY_KEYWORDS) fputs(file, Text) fputs(file, "^n") // blank line fputs(file, "# Word/Sound combinations:^n") new aLen = ArraySize(soundData) new j for ( new i = 2; i < aLen; ++i ) // first 2 elements are reserved for Join / Exit sounds { ArrayGetArray(soundData, i, sData); cfg_write_keyword(file, sData) subLen = ArraySize(sData[SUB_INDEX]) for ( j = 0; j < subLen; ++j ) { ArrayGetArray(sData[SUB_INDEX], j, subData); cfg_write_keysound(file, subData) } fputc(file, '^n') } fclose(file) client_print(id, print_console, "Sank Sounds >> Configuration successfully written to %s", savefile) return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Prints out Word/Sound combo matrix for debugging purposes. Kinda cool, even // if you're not really debugging. // // Usage: amx_sound_debug // Usage: amx_sound_reload <filename> ////////////////////////////////////////////////////////////////////////////// public amx_sound_debug( id , level , cid ) { if ( !cmd_access(id, level, cid, 1) && id > 0 ) return PLUGIN_HANDLED new i, j, join_snd_buff[BUFFER_LEN], exit_snd_buff[BUFFER_LEN] if ( !is_dedicated_server() && id == 1 ) // for listenserver and with id = 1 we can use server_print id = 0 if ( id ) client_print(id, print_console, "SND_WARN: %d^nSND_MAX: %d^nSND_MAX_DUR: %5.1f^n", SND_WARN, SND_MAX, SND_MAX_DUR) else server_print("SND_WARN: %d^nSND_MAX: %d^nSND_MAX_DUR: %5.1f", SND_WARN, SND_MAX, SND_MAX_DUR) new sData[SOUND_DATA_BASE] new subData[SOUND_DATA_SUB] new aLen = ArraySize(soundData) new subLen ArrayGetArray(soundData, 0, sData) subLen = ArraySize(sData[SUB_INDEX]) new tempstr[TOK_LENGTH] for( i = 0; i < subLen; ++i ) { ArrayGetArray(sData[SUB_INDEX], i, subData) formatex(tempstr, TOK_LENGTH, "%s;", subData[SOUND_FILE]) add(join_snd_buff, BUFFER_LEN, tempstr) } ArrayGetArray(soundData, 1, sData) subLen = ArraySize(sData[SUB_INDEX]) for( i = 0; i < subLen; ++i ) { ArrayGetArray(sData[SUB_INDEX], i, subData) formatex(tempstr, TOK_LENGTH, "%s;", subData[SOUND_FILE]) add(exit_snd_buff, BUFFER_LEN, tempstr) } new snd_imm_str[32] get_flags(SND_IMMUNITY, snd_imm_str, 26) if ( id ) { client_print(id, print_console, "SND_JOIN: %s", join_snd_buff) client_print(id, print_console, "SND_EXIT: %s", exit_snd_buff) client_print(id, print_console, "SND_DELAY: %f^nSND_MODE: %d^nSND_IMMUNITY: %s^nEXACT_MATCH: %d", SND_DELAY, SND_MODE, snd_imm_str, EXACT_MATCH) client_print(id, print_console, "ADMINS_ONLY: %d^nDISPLAY_KEYWORDS: %d", ADMINS_ONLY, DISPLAY_KEYWORDS) }else { server_print("SND_JOIN: %s", join_snd_buff) server_print("SND_EXIT: %s", exit_snd_buff) server_print("SND_DELAY: %f^nSND_MODE: %d^nSND_IMMUNITY: %s^nEXACT_MATCH: %d", SND_DELAY, SND_MODE, snd_imm_str, EXACT_MATCH) server_print("ADMINS_ONLY: %d^nDISPLAY_KEYWORDS: %d", ADMINS_ONLY, DISPLAY_KEYWORDS) } // Print out the matrix of sound data, so we got what we think we did for( i = 2; i < aLen; ++i ) // first 2 elements are reserved for Join / Exit sounds { ArrayGetArray(soundData, i, sData) new access_level[32] get_flags(sData[ADMIN_LEVEL_BASE], access_level, 31) if ( id ) client_print(id, print_console, "^n[%d] ^"%s^" with %d sound%s and level ^"%s^" (played: %d)", i - 2, sData[KEYWORD], sData[SOUND_AMOUNT], sData[SOUND_AMOUNT] > 1 ? "s" : "", access_level, sData[PLAY_COUNT_KEY]) else server_print("^n[%d] ^"%s^" with %d sound%s and level ^"%s^" (played: %d)", i - 2, sData[KEYWORD], sData[SOUND_AMOUNT], sData[SOUND_AMOUNT] > 1 ? "s" : "", access_level, sData[PLAY_COUNT_KEY]) subLen = ArraySize(sData[SUB_INDEX]) for( j = 0; j < subLen; ++j ) { ArrayGetArray(sData[SUB_INDEX], j, subData) get_flags(subData[ADMIN_LEVEL], access_level, 31) if ( id ) client_print(id, print_console, " ^"%s^" - time: %5.2f - admin level ^"%s^" (played: %d)", subData[SOUND_FILE], subData[DURATION], access_level, subData[PLAY_COUNT]) else server_print(" ^"%s^" - time: %5.2f - admin level ^"%s^" (played: %d)", subData[SOUND_FILE], subData[DURATION], access_level, subData[PLAY_COUNT]) } } return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Bans players from using sounds for current map // // Usage: amx_sound_ban <player> ////////////////////////////////////////////////////////////////////////////// public amx_sound_ban( id , level , cid ) { if ( !cmd_access(id, level, cid, 2) ) return PLUGIN_HANDLED new arg[33] read_argv(1, arg, 32) new player = cmd_target(id, arg, 1) if ( !player ) return PLUGIN_HANDLED if ( get_user_flags(player) & (SND_IMMUNITY | ACCESS_ADMIN) ) return PLUGIN_HANDLED if ( restrict_playing_sounds[player] == -1 ) { new found, empty = -1 new steamid[60] get_user_authid(id, steamid, 59) for ( new i = 0; i < MAX_BANS; ++i ) { if ( empty == -1 && !banned_player_steamids[0] ) empty = i if ( !equal(steamid, banned_player_steamids) ) continue found = 1 break } if ( !found ) { if ( empty == -1 ) empty = 0 copy(banned_player_steamids[empty], 59, steamid) restrict_playing_sounds[player] = empty } } new name[33] get_user_name(player, name, 32) client_print(id, print_console, "Sank Sounds >> Player ^"%s^" has been banned from using sounds", name) return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Unbans players from using sounds for current map // // Usage: amx_sound_unban <player> ////////////////////////////////////////////////////////////////////////////// public amx_sound_unban( id , level , cid ) { if ( !cmd_access(id, level, cid, 2) ) return PLUGIN_HANDLED new arg[33] read_argv(1, arg, 32) new player = cmd_target(id, arg) if ( !player ) return PLUGIN_HANDLED if ( restrict_playing_sounds[player] != -1 ) { new found = -1 new steamid[60] get_user_authid(id, steamid, 59) for ( new i = 0; i < MAX_BANS; ++i ) { if ( !equal(steamid, banned_player_steamids) ) continue found = i break } if ( found != -1 ) banned_player_steamids[found][0] = 0 restrict_playing_sounds[player] = -1 } new name[33] get_user_name(player, name, 32) client_print(id, print_console, "Sank Sounds >> Player ^"%s^" has been unbanned from using sounds", name) return PLUGIN_HANDLED } public amx_sound_top( id , level , cid ) { if ( !cmd_access(id, level, cid, 1) ) return PLUGIN_HANDLED new arg[33] read_argv(1, arg, 32) new topX = 10 if ( strlen(arg) > 0 ) topX = str_to_num(arg) if ( topX < 1 || topX > 50 ) { client_print(id, print_console, "Sank Sounds >> Set a value from 1 to 50 or leave it blank") return PLUGIN_HANDLED } new topIDs[50] = {-1, ...} new topCount[50] = {0, ...} new sData[SOUND_DATA_BASE] new aLen = ArraySize(soundData) for ( new keyIndex = 0; keyIndex < aLen; ++keyIndex ) { ArrayGetArray(soundData, keyIndex, sData) for ( new i = 0; i < topX; ++i ) { if ( sData[PLAY_COUNT_KEY] <= topCount ) continue // copy all other down for ( new j = topX - 1; j > i; --j ) { topIDs[j] = topIDs[j - 1] topCount[j] = topCount[j - 1] } topIDs = keyIndex topCount = sData[PLAY_COUNT_KEY] break } } new text[512] new counter = 0 client_print(id, print_console, "Sank Sounds >> Top %d:", topX) while ( counter < topX ) { if ( topIDs[counter] != -1 ) { ArrayGetArray(soundData, topIDs[counter], sData) format(text, 511, "%s(%d) %s^n", text, topCount[counter], sData[KEYWORD]) }else counter = topX - 1 if ( (counter % 10 == 0 && counter != 0 ) || counter == topX - 1 ) { client_print(id, print_console, text) text[0] = 0 } ++counter } return PLUGIN_HANDLED } ////////////////////////////////////////////////////////////////////////////// // Everything a person says goes through here, and we determine if we want to // play a sound or not. // // Usage: say <anything> ////////////////////////////////////////////////////////////////////////////// public HandleSay( id ) { // If sounds are not enabled, then skip this whole thing if ( !bSoundsEnabled ) return PLUGIN_CONTINUE // player is banned from playing sounds if ( restrict_playing_sounds[id] != -1 ) return PLUGIN_CONTINUE new Speech[128] read_args(Speech, 127) remove_quotes(Speech) // credit to SR71Goku for fixing this oversight: new speachLen = strlen(Speech) if ( !speachLen ) return PLUGIN_CONTINUE if ( equal(Speech, "/sound", 6) ) { if ( Speech[6] == 's' ) { if ( Speech[7] == 'o' && Speech[8] == 'n' && Speech[9] == 0 ) { SndOn[id] = 1 client_print(id, print_chat, "Sank Sounds >> You will hear all sounds again") }else if ( Speech[7] == 'o' && Speech[8] == 'f' && Speech[9] == 'f' && Speech[10] == 0 ) { SndOn[id] = 0 client_print(id, print_chat, "Sank Sounds >> I will stop playing sounds for you") }else if ( Speech[7] == 0 ) print_sound_list(id, 1) else return PLUGIN_CONTINUE return PLUGIN_HANDLED }else if ( Speech[6] == 'l' && Speech[7] == 'i' && Speech[8] == 's' && Speech[9] == 't' && Speech[10] == 0 ) { print_sound_list(id, 1) return PLUGIN_HANDLED } return PLUGIN_CONTINUE } new ListIndex = -1 new pinToLocation = (Speech[speachLen - 1] == '!') new sData[SOUND_DATA_BASE] new subData[SOUND_DATA_SUB] new aLen = ArraySize(soundData) // Check to see if what the player said is a trigger for a sound for ( new i = 2; i < aLen; ++i ) // first 2 elements are reserved for Join / Exit sounds { ArrayGetArray(soundData, i, sData) if ( equali(Speech, sData[KEYWORD]) || (EXACT_MATCH == 1 && pinToLocation == 1 && speachLen == strlen(sData[KEYWORD]) + 1 && equali(Speech, sData[KEYWORD], speachLen - 1) ) || ( EXACT_MATCH == 0 && containi(Speech, sData[KEYWORD]) != -1 ) ) { // check for access if ( sData[ADMIN_LEVEL_BASE] == 0 || get_user_flags(id) & sData[ADMIN_LEVEL_BASE] ) ListIndex = i break } } // check If player used NO sound trigger if ( ListIndex == -1 ) return PLUGIN_CONTINUE if ( sData[SOUND_AMOUNT] == 0 ) return PLUGIN_CONTINUE new obey_duration_mode = get_pcvar_num(CVAR_obey_duration) new Float:gametime = get_gametime() new allowedToPlay = isUserAllowed2Play(id, gametime, obey_duration_mode) if ( allowedToPlay == RESULT_OK ) { displayQuotaWarning(id) new rand new timeout // This for loop runs around until it finds a real file to play // Defaults to the first Sound file, if no file is found at random. new foundFile = false for( timeout = MAX_RANDOM; // Initial condition timeout >= 0; // While these are true --timeout ) // Update each iteration { rand = random(sData[SOUND_AMOUNT]) // If for some reason we never find a file // then default to the first Sound entry if ( !timeout ) rand = 0 ArrayGetArray(sData[SUB_INDEX], rand, subData) // check if sound has access defined, if so only allow admins to use it if ( subData[ADMIN_LEVEL] == 0 || ( get_user_flags(id) & subData[ADMIN_LEVEL] ) ) { foundFile = true } } if ( foundFile ) { NextSoundTime = gametime + subData[DURATION] // Increment their playsound count ++SndCount[id] SndLenghtCount[id] += subData[DURATION] // increment counter ++sData[PLAY_COUNT_KEY] ++subData[PLAY_COUNT] new type = subData[SOUND_TYPE] if ( pinToLocation == 1 && type == SOUND_TYPE_WAV ) type = SOUND_TYPE_WAV_LOCAL playsoundall(subData[SOUND_FILE], type, SND_MODE & 16, is_user_alive(id)) LastSoundTime = gametime ArraySetArray(sData[SUB_INDEX], rand, subData) ArraySetArray(soundData, ListIndex, sData) } }else if ( allowedToPlay == RESULT_QUOTA_EXCEEDED || allowedToPlay == RESULT_QUOTA_DURATION_EXCEEDED || allowedToPlay == RESULT_SOUND_DELAY ) { if ( !displayQuotaExceeded(id) ) { if ( allowedToPlay == RESULT_SOUND_DELAY && obey_duration_mode != 0 ) client_print(id, print_chat, "Sank Sounds >> Sound is still playing ( wait %3.1f seconds )", NextSoundTime + SND_DELAY - gametime) else if ( allowedToPlay != RESULT_QUOTA_EXCEEDED && allowedToPlay != RESULT_QUOTA_DURATION_EXCEEDED ) client_print(id, print_chat, "Sank Sounds >> Do not use sounds too often ( wait %3.1f seconds )", LastSoundTime + SND_DELAY - gametime) } } if ( DISPLAY_KEYWORDS == 0 ) return PLUGIN_HANDLED return PLUGIN_CONTINUE } ////////////////////////////////////////////////////////////////////////////// // Parses the sound file specified by loadfile. If loadfile is empty, then // it parses the default config_filename. ////////////////////////////////////////////////////////////////////////////// parse_sound_file( loadfile[] , precache_sounds = 1 ) { if ( !strlen(loadfile) ) copy(loadfile, 127, config_filename) if ( !file_exists(loadfile) ) { // file does not exist log_amx("Sank Sounds >> Cannot find ^"%s^" file", loadfile) return } new current_package_str[4] new current_package, package_num if ( vaultdata_exists("sank_sounds_current_package") ) { get_vaultdata("sank_sounds_current_package", current_package_str, 3) current_package = str_to_num(current_package_str) } new allowed_to_precache = 1, allow_check_existence = 1, allow_to_use_sounds = 1 new allow_global_precache = get_cvar_num("mp_sank_sounds_download") new mapname[32] get_mapname(mapname, 31) new i new ListIndex = -1 new tmpIndex = -1 new maxLineBuf_len = BUFFER_LEN - 1 new strLineBuf[BUFFER_LEN] new error_code = ERROR_NONE new parse_option = PARSE_KEYWORD new temp_str[128] new check_for_semi new position new sData[SOUND_DATA_BASE] new file = fopen(loadfile, "r") if ( !file ) { log_amx("Sank Sounds >> Unable to read from ^"%s^" file", loadfile) return } while ( fgets(file, strLineBuf, maxLineBuf_len) ) { if ( (strLineBuf[0] == '^n') // empty line || ( strLineBuf[0] == 10 && strLineBuf[1] == '^n' ) // empty line || ( strLineBuf[0] == '/' && strLineBuf[1] == '/' ) // comment || (strLineBuf[0] == '#') ) // another comment continue trim(strLineBuf) // remove newline and spaces if ( equali(strLineBuf, "package ", 8) ) { ++package_num if ( current_package ) { if ( current_package == str_to_num(strLineBuf[8]) ) allowed_to_precache = 1 else allowed_to_precache = 0 }else { current_package = 1 allowed_to_precache = 1 } allow_to_use_sounds = 1 allow_check_existence = 1 continue }else if ( equali(strLineBuf, "mapname ", 8) ) { if ( equali(strLineBuf[8], mapname) ) allowed_to_precache = 1 else allowed_to_precache = 0 allow_to_use_sounds = 1 allow_check_existence = 1 continue }else if ( equali(strLineBuf, "mapnameonly ", 12) ) { if ( equali(strLineBuf[12], mapname) ) { allowed_to_precache = 1 allow_to_use_sounds = 1 }else { allowed_to_precache = 0 allow_to_use_sounds = 0 } allow_check_existence = 1 continue }else if ( equali(strLineBuf, "modspecific", 11) ) { allow_to_use_sounds = 1 allow_check_existence = 0 continue } if ( !allow_to_use_sounds ) // check for sounds that can be used only on specified map continue error_code = ERROR_NONE position = 0 for( i = 0; ; ++i ) { // check if reached end of buffer ( input has been parsed ) if ( position >= strlen(strLineBuf) ) { strLineBuf[0] = 0 break } temp_str[0] = 0 // reset check_for_semi = contain(strLineBuf[position], ";") if ( check_for_semi != -1 ) { copyc(temp_str, 127, strLineBuf[position], ';') position += check_for_semi + 1 }else { copy(temp_str, 127, strLineBuf[position]) position += strlen(temp_str) } // Now remove any spaces or tabs from around the strings -- clean them up trim(temp_str) // check if file length is bigger than array if ( strlen(temp_str) > TOK_LENGTH ) { error_code = ERROR_STRING_LENGTH break } if ( i == 0 ) { // first entry is not a sound file if ( equali(temp_str, "SND_MAX") ) parse_option = PARSE_SND_MAX else if ( equali(temp_str, "SND_MAX_DUR") ) parse_option = PARSE_SND_MAX_DUR else if ( equali(temp_str, "SND_WARN") ) parse_option = PARSE_SND_WARN else if ( equali(temp_str, "SND_DELAY") ) parse_option = PARSE_SND_DELAY else if ( equali(temp_str, "SND_MODE") ) parse_option = PARSE_SND_MODE else if ( equali(temp_str, "SND_IMMUNITY") ) parse_option = PARSE_SND_IMMUNITY else if ( equali(temp_str, "EXACT_MATCH") ) parse_option = PARSE_EXACT_MATCH else if ( equali(temp_str, "ADMINS_ONLY") ) parse_option = PARSE_ADMINS_ONLY else if ( equali(temp_str, "DISPLAY_KEYWORDS") ) parse_option = PARSE_DISPLAY_KEYWORDS else { parse_option = PARSE_KEYWORD if ( ListIndex != -1 ) ArrayGetArray(soundData, ListIndex, sData) if ( ListIndex != -1 && sData[SOUND_AMOUNT] == 0 && !(sData[FLAGS] & FLAG_IGNORE_AMOUNT) ) // check if allowed to ignore amount of sounds ( eg: SND_JOIN / SND_EXIT ) { log_amx("Sank Sounds >> Found keyword without any valid sound. Skipping this keyword: ^"%s^"", sData[KEYWORD]) array_remove(ListIndex) }else ++ListIndex new result = array_add_element(ListIndex, temp_str) if ( result > -1 ) { tmpIndex = result --ListIndex }else { tmpIndex = -1 if ( result == -1 ) ListIndex = 2 } } }else { switch ( parse_option ) { case PARSE_SND_MAX: { SND_MAX = str_to_num(temp_str) } case PARSE_SND_MAX_DUR: { SND_MAX_DUR = floatstr(temp_str) } case PARSE_SND_WARN: { SND_WARN = str_to_num(temp_str) } case PARSE_SND_DELAY: { SND_DELAY = floatstr(temp_str) } case PARSE_SND_MODE: { SND_MODE = str_to_num(temp_str) } case PARSE_SND_IMMUNITY: { if ( temp_str[0] == '^"' ) { new temp_str2[32] copyc(temp_str2, 31, temp_str[1], '^"') if ( strlen(temp_str2) == 0 ) SND_IMMUNITY = (1<<30) else SND_IMMUNITY = read_flags(temp_str2) }else SND_IMMUNITY = read_flags(temp_str) } case PARSE_EXACT_MATCH: { EXACT_MATCH = str_to_num(temp_str) } case PARSE_ADMINS_ONLY: { ADMINS_ONLY = str_to_num(temp_str) } case PARSE_DISPLAY_KEYWORDS: { DISPLAY_KEYWORDS = str_to_num(temp_str) } case PARSE_KEYWORD: { new error_value = -1 if ( tmpIndex != -1 ) error_value = array_add_inner_element(tmpIndex, i - 1, temp_str, allow_check_existence, allow_global_precache, precache_sounds, allowed_to_precache) else error_value = array_add_inner_element(ListIndex, i - 1, temp_str, allow_check_existence, allow_global_precache, precache_sounds, allowed_to_precache) if ( error_value == -1 ) { // sound could not be added continue } } } } } if ( error_code == ERROR_STRING_LENGTH ) { log_amx("Sank Sounds >> Skipping this word/sound combo. Word or Sound is too long: ^"%s^". Length is %i but max is %i (change name/remove spaces in config or increase TOK_LENGTH)", temp_str, strlen(temp_str), TOK_LENGTH) continue } if ( error_code != ERROR_NONE ) { log_amx("Sank Sounds >> Fatal Error") continue } // If we finished MAX_RANDOM times, and strLineBuf[position] still has contents // then we should have a bigger MAX_RANDOM else if ( position < strlen(strLineBuf) ) { log_amx("Sank Sounds >> Sound list partially truncated. Increase MAX_RANDOM. Continuing to parse file ^"%s^"^n", loadfile) } } fclose(file) if ( ListIndex != -1 ) { ArrayGetArray(soundData, ListIndex, sData) if ( sData[SOUND_AMOUNT] == 0 && !(sData[FLAGS] & FLAG_IGNORE_AMOUNT) ) // check if allowed to ignore amount of sounds ( eg: SND_JOIN / SND_EXIT ) { log_amx("Sank Sounds >> Found keyword without any valid sound. Skipping this keyword: ^"%s^"", sData[KEYWORD]) array_remove(ListIndex) --ListIndex } } // Now we have all of the data from the text file in our data structures. // Next we do some error checking, some setup, and we're done parsing! ErrorCheck() ++current_package if ( current_package > package_num ) current_package = 1 num_to_str(current_package, current_package_str, 3) set_vaultdata("sank_sounds_current_package", current_package_str) //++ListIndex #if ALLOW_SORT == 1 if ( ListIndex > 1 ) ArraySort(soundData, "sortSoundDataFunc") #endif } ////////////////////////////////////////////////////////////////////////////// // Returns status indicating if user is allowed to play a sound // or the reason why he is not ////////////////////////////////////////////////////////////////////////////// isUserAllowed2Play( id , Float:gametime , obey_duration_mode ) { // order of checks is important new admin_flags = get_user_flags(id) // check if super admin if ( admin_flags & ADMIN_RCON ) { // check if super admin has to obey duration if ( !(obey_duration_mode & 4) ) return RESULT_OK return RESULT_SOUND_DELAY } // check if only admins can play sounds if ( ADMINS_ONLY && !(admin_flags & ACCESS_ADMIN) ) return RESULT_ADMINS_ONLY // check if admin if ( admin_flags & ACCESS_ADMIN ) { // check if admin has to obey duration if ( !(obey_duration_mode & 2) ) return RESULT_OK return RESULT_SOUND_DELAY } if ( SND_MAX != 0 && SndCount[id] >= SND_MAX ) return RESULT_QUOTA_EXCEEDED if ( SND_MAX_DUR != 0.0 && SndLenghtCount[id] > SND_MAX_DUR ) return RESULT_QUOTA_DURATION_EXCEEDED // check if player is allowed to play sounds depending on alive config if ( !(SND_MODE & (is_user_alive(id) + 1)) ) return RESULT_BAD_ALIVE_STATUS // check for sound overlapping + delay time if ( gametime > NextSoundTime + SND_DELAY ) return RESULT_OK // check if overlapping is allowed // or for delay time if ( !(obey_duration_mode & 1) && gametime > LastSoundTime + SND_DELAY ) return RESULT_OK return RESULT_SOUND_DELAY } displayQuotaWarning( id ) { new admin_flags = get_user_flags(id) if ( (admin_flags & (SND_IMMUNITY | ACCESS_ADMIN)) > 0) return if ( SND_MAX != 0 ) { if ( SndCount[id] >= SND_WARN ) { if ( SndCount[id] + 1 == SND_MAX ) client_print(id, print_chat, "Sank Sounds >> This was your last sound") else client_print(id, print_chat, "Sank Sounds >> You have %d left before you get muted", SND_MAX - SndCount[id] - 1) } } } displayQuotaExceeded( id ) { new admin_flags = get_user_flags(id) if ( (admin_flags & (SND_IMMUNITY | ACCESS_ADMIN)) > 0) return 0 if ( SND_MAX != 0 ) { if ( SndCount[id] >= SND_MAX ) { if ( SndCount[id] - 3 < SND_MAX ) { client_print(id, print_chat, "Sank Sounds >> You were warned, you are muted") // player is already muted, we increament here to save a variable to protect player from "you are muted" spam ( only 3 warnings ) ++SndCount[id] } return 1 } } return 0 } ////////////////////////////////////////////////////////////////////////////// // Checks the input variables for invalid values ////////////////////////////////////////////////////////////////////////////// ErrorCheck( ) { // Can't have negative delay between sounds if ( SND_DELAY < 0.0 ) { log_amx("Sank Sounds >> SND_DELAY cannot be negative. Setting to value: 0") SND_DELAY = 0.0 } // If SND_MAX is zero, then sounds quota is disabled. Can't have negative quota if ( SND_MAX < 0 ) { SND_MAX = 0 // in case it was negative log_amx("Sank Sounds >> SND_MAX cannot be negative. Setting to value: 0") } // If SND_MAX_DUR is zero, then sounds quota is disabled. Can't have negative quota if ( SND_MAX_DUR < 0.0 ) { SND_MAX_DUR = 0.0 // in case it was negative log_amx("Sank Sounds >> SND_MAX_DUR cannot be negative. Setting to value: 0.0") } // If SND_WARN is zero, then we can't have warning every time a keyword is said, // so we default to 3 less than max else if ( ( SND_WARN <= 0 && SND_MAX != 0 ) || SND_MAX < SND_WARN ) { if ( SND_MAX < SND_WARN ) // And finally, if they want to warn after a person has been // muted, that's silly, so we'll fix it. log_amx("Sank Sounds >> SND_WARN cannot be higher than SND_MAX") else if ( SND_WARN <= 0 ) log_amx("Sank Sounds >> SND_WARN cannot be set to zero") if ( SND_MAX > 3 ) SND_WARN = SND_MAX - 3 else SND_WARN = SND_MAX - 1 log_amx("Sank Sounds >> SND_WARN set to default value: %i", SND_WARN) } } playsoundall( sound[] , type , split_dead_alive = 0 , sender_alive_status = 0 ) { new alive for( new i = 1; i <= g_max_players; ++i ) { if ( !is_user_connected(i) ) continue if ( is_user_bot(i) ) continue if ( !SndOn ) continue alive = is_user_alive(i) if ( !(SND_MODE & ( alive * 4 + 4 )) ) continue if ( split_dead_alive && alive != sender_alive_status // make sure if splited both are in same group && !(SND_MODE & ( alive * 32 + 32 )) ) // OR check if different groups may hear each other continue if ( type == SOUND_TYPE_MP3 ) client_cmd(i, "mp3 play ^"%s^"", sound) else if ( type == SOUND_TYPE_WAV_LOCAL ) client_cmd(i, "play ^"%s^"", sound) else if ( type == SOUND_TYPE_SPEECH ) { if ( sound[0] == '^"' ) client_cmd(i, "spk %s", sound) else client_cmd(i, "spk ^"%s^"", sound) }else client_cmd(i, "spk ^"%s^"", sound) } } print_sound_list( id , motd_msg = 0 ) { new text[256], motd_buffer[2048], ilen, skip_for_loop new info_text[64] = "say < keyword >: plays A sound. keYwords are listed Below:" if ( strlen(motd_sound_list_address) > 3 ) // make sure at least you have something like: a.b ( http://a.b ) { copy(motd_buffer, 255, motd_sound_list_address) skip_for_loop = 1 motd_msg = 1 }else if ( motd_msg ) ilen = format(motd_buffer, 2047, "<body bgcolor=#000000><font color=#FFB000><pre>%s^n", info_text) else client_print(id, print_console, info_text) // Loop once for each keyword new i, j = -1 new sData[SOUND_DATA_BASE] new aLen = ArraySize(soundData) for ( i = 2; i < aLen && skip_for_loop == 0; ++i ) // first 2 elements are reserved for Join / Exit sounds { ArrayGetArray(soundData, i, sData) // check if player can see admin sounds ++j new found_stricted = 0 if ( sData[ADMIN_LEVEL_BASE] == 0 || get_user_flags(id) & sData[ADMIN_LEVEL_BASE] ) { if ( motd_msg ) ilen += format(motd_buffer[ilen], 2047 - ilen, "%s", sData[KEYWORD]) else add(text, 255, sData[KEYWORD]) }else { --j found_stricted = 1 } if ( !found_stricted ) { if ( j % NUM_PER_LINE == NUM_PER_LINE - 1 ) { // We got NUM_PER_LINE on this line, // so print it and start on the next line if ( motd_msg ) ilen += format(motd_buffer[ilen], 2047 - ilen, "^n") else { client_print(id, print_console, "%s", text) text[0] = 0 } }else { if ( motd_msg ) ilen += format(motd_buffer[ilen], 2047 - ilen, " | ") else add(text, 255, " | ") } } } if ( motd_msg && strlen(motd_buffer) ) show_motd(id, motd_buffer) else if ( strlen(text) ) client_print(id, print_console, text) } #if ALLOW_SORT == 1 public sortSoundDataFunc( Array:array , item1 , item2 , const data[] , data_size ) { new data1[SOUND_DATA_BASE] new data2[SOUND_DATA_BASE] ArrayGetArray(array, item1, data1) ArrayGetArray(array, item2, data2) if ( (data1[FLAGS] & FLAGS_JOIN_SND) == FLAGS_JOIN_SND ) return -1; if ( (data2[FLAGS] & FLAGS_JOIN_SND) == FLAGS_JOIN_SND ) return 1; if ( (data1[FLAGS] & FLAGS_EXIT_SND) == FLAGS_EXIT_SND ) return -1; if ( (data2[FLAGS] & FLAGS_EXIT_SND) == FLAGS_EXIT_SND ) return 1; return strcmp(data1[KEYWORD], data2[KEYWORD]) } #endif array_add_element( num , keyword[] ) { new join_check = equali(keyword, "SND_JOIN") new exit_check = equali(keyword, "SND_EXIT") // if index is 0 or 1 but not the correct keyword then make sure to save in correct array position if ( join_check == 0 && exit_check == 0 ) { if ( num == 0 || num == 1 ) { join_check = -1 exit_check = -1 num = 2 new sData[SOUND_DATA_BASE] ArrayPushArray(soundData, sData) ArrayPushArray(soundData, sData) } }else { if ( num > 1 ) { if ( join_check != 0 ) { num = 0 exit_check = -1 }else if ( exit_check != 0 ) { num = 1 join_check = -1 } } } new sData[SOUND_DATA_BASE] if ( join_check > 0 ) sData[FLAGS] |= FLAGS_JOIN_SND | FLAG_IGNORE_AMOUNT if ( exit_check > 0 ) sData[FLAGS] |= FLAGS_EXIT_SND | FLAG_IGNORE_AMOUNT sData[ADMIN_LEVEL_BASE] = cfg_parse_access(keyword) copy(sData[KEYWORD], TOK_LENGTH, keyword) sData[PLAY_COUNT_KEY] = 0 sData[SUB_INDEX] = _:ArrayCreate(SOUND_DATA_SUB) new diffSize = ArraySize(soundData) - num if ( num < ArraySize(soundData) ){ ArraySetArray(soundData, num, sData) } else if ( diffSize < 0 ) { log_amx("Sank Sounds >> Bad index when adding keyword ^"%s^".", keyword) return -2 } else { ArrayPushArray(soundData, sData) } return (join_check == -1 && exit_check == -1) ? -1 : (join_check == -1 || exit_check == -1) ? num : -2 } array_add_inner_element( num , elem , soundfile[] , allow_check_existence = 1 , allow_global_precache = 0 , precache_sounds = 0 , allowed_to_precache = 0 ) { new subData[SOUND_DATA_SUB] subData[ADMIN_LEVEL] = cfg_parse_access(soundfile) subData[SOUND_TYPE] = soundfile[0] == '^"' ? SOUND_TYPE_SPEECH : ( soundfile[strlen(soundfile) - 1] == '3' ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV ) subData[PLAY_COUNT] = 0 if ( subData[SOUND_TYPE] == SOUND_TYPE_SPEECH ) { // remove the quotes copy(soundfile, strlen(soundfile) - 2, soundfile[1]); }else { new sound_file_name[TOK_LENGTH + 1 + 10] new is_mp3 = ( containi(soundfile, ".mp3") != -1 ) new isWav_inSound_folder = 0 if ( !is_mp3 ) { // ".mp3" in not in the string if ( equali(soundfile, "sound/", 6) ) { formatex(sound_file_name, TOK_LENGTH + 10, "%s", soundfile) isWav_inSound_folder = 1 }else formatex(sound_file_name, TOK_LENGTH + 10, "sound/../%s", soundfile) } else { copy(sound_file_name, TOK_LENGTH + 10, soundfile) } if ( allow_check_existence ) { if ( !file_exists(sound_file_name) ) { // now check for all sub folders that start with the same name followed by an underscore if ( ArraySize(modSearchPaths) == 0 ) { new modname[32 + 1] get_modname(modname, 31) new modName_len = strlen(modname) modname[modName_len + 1] = 0 modname[modName_len] = '_' ++modName_len new dirFileName[64] new dirFileName_len = charsmax(dirFileName) new dirHandle = open_dir("..", dirFileName, dirFileName_len) if ( dirHandle != 0 ) { do { if ( !equali(modname, dirFileName, modName_len) ) continue ArrayPushArray(modSearchPaths, dirFileName) } while ( next_file(dirHandle, dirFileName, dirFileName_len) ) close_dir(dirHandle) } } new foundFile = false new alt_sound_file_name[TOK_LENGTH + 1 + 10 + 32] new i new modSearchPath[64] for ( i = 0; i < ArraySize(modSearchPaths); ++i ) { ArrayGetArray(modSearchPaths, i, modSearchPath) formatex(alt_sound_file_name, TOK_LENGTH + 10 + 32, "../%s/%s", modSearchPath, sound_file_name) if ( file_exists(alt_sound_file_name) ) { foundFile = true copy(sound_file_name, TOK_LENGTH + 10, alt_sound_file_name); break } } if ( !foundFile ) { log_amx("Sank Sounds >> Trying to load a file that does not exist. Skipping this file: ^"%s^"", sound_file_name) return -1 } } subData[DURATION] = _:cfg_get_duration(sound_file_name, is_mp3 ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV ) if ( subData[DURATION] <= 0.0 ) { log_amx("Sank Sounds >> Sound duration is not valid. File is damaged. Skipping this file: ^"%s^"", sound_file_name) return -1 } } if ( allow_global_precache && precache_sounds == 1 && allowed_to_precache ) { if ( is_mp3 || !isWav_inSound_folder ) engfunc(EngFunc_PrecacheGeneric, soundfile) else engfunc(EngFunc_PrecacheSound, soundfile[6]) } // remove ".wav" from files to prevent runtime warnings (using: developer 1) if ( subData[SOUND_TYPE] == SOUND_TYPE_WAV ) { new len = strlen(soundfile) if ( len > 4 && equali(soundfile[len - 4], ".wav") ) { soundfile[len - 4] = 0; } } } copy(subData[SOUND_FILE], TOK_LENGTH, soundfile) subData[SOUND_FILE][TOK_LENGTH] = 0 // ensure that string operations will terminate new sData[SOUND_DATA_BASE] ArrayGetArray(soundData, num, sData) ++sData[SOUND_AMOUNT] if ( elem < ArraySize(sData[SUB_INDEX]) ) ArrayInsertArrayBefore(sData[SUB_INDEX], elem, subData) else ArrayPushArray(sData[SUB_INDEX], subData) ArraySetArray(soundData, num, sData) return 1 } array_clear( ) { new sData[SOUND_DATA_BASE] new aLen = ArraySize(soundData) for ( new i = 0; i < aLen; ++i ) { ArrayGetArray(soundData, i, sData) ArrayDestroy(sData[SUB_INDEX]) } ArrayDestroy(soundData) } array_remove( index ) { new sData[SOUND_DATA_BASE] ArrayGetArray(soundData, index, sData) ArrayDestroy(sData[SUB_INDEX]) if ( index > 1 ) // join/exit keywords may not be removed ArrayDeleteItem(soundData, index) else sData[SUB_INDEX] = _:ArrayCreate(SOUND_DATA_SUB) } array_remove_inner( index , elem ) { new sData[SOUND_DATA_BASE] ArrayGetArray(soundData, index, sData) --sData[SOUND_AMOUNT] ArrayDeleteItem(sData[SUB_INDEX], elem) ArraySetArray(soundData, index, sData) } cfg_write_keyword( file , data[] ) { if ( data[ADMIN_LEVEL_BASE] ) { new access_str[32] get_flags(data[ADMIN_LEVEL_BASE], access_str, 31) fputc(file, '@') fputs(file, access_str) fputc(file, '@') } fputs(file, data[KEYWORD]) fputs(file, ";^t^t") } cfg_write_keysound( file , subdata[] ) { if ( subdata[ADMIN_LEVEL] ) { new access_str[32] get_flags(subdata[ADMIN_LEVEL], access_str, 31) fputc(file, '@') fputs(file, access_str) fputc(file, '@') } fputs(file, subdata[SOUND_FILE]) fputs(file, ";") } cfg_parse_access( str[] ) { new access_level if ( str[0] == '@' ) { new second_at = contain(str[1], "@") if ( second_at != -1 ) { new temp_access[32] copy(temp_access, second_at, str[1]) strtolower(temp_access) access_level = read_flags(temp_access) copy(str, 127, str[second_at + 1 + 1]) }else { access_level = SND_IMMUNITY copy(str, 127, str[1]) } } return access_level } Float:cfg_get_duration( sound_file[] , type ) { switch ( type ) { case SOUND_TYPE_WAV: { return cfg_get_wav_duration(sound_file) } case SOUND_TYPE_MP3: { return cfg_get_mp3_duration(sound_file) } } return 0.0 } Float:cfg_get_wav_duration( wav_file[] ) { new file = fopen(wav_file, "rb") new dummy_input new i for ( i = 0; i < 24; ++i ) dummy_input = fgetc(file) // 24th byte new hertz = fgetc(file) // 25th byte hertz += fgetc(file) * 256 // 26th byte hertz += fgetc(file) * 256 * 256 for ( i = 27; i < 34; ++i ) dummy_input = fgetc(file) // 34th byte new bitrate = fgetc(file) // bytes for data length start right after ascii "data", so search for it // normally it is at 35 but also saw at 44, so just in case add bigger search area new data_found do { dummy_input = fgetc(file) if ( dummy_input == 'd' ) data_found = 1 else if ( dummy_input == 'a' && data_found == 1 ) data_found = 2 else if ( dummy_input == 't' && data_found == 2 ) data_found = 3 else if ( dummy_input == 'a' && data_found == 3 ) data_found = 4 else data_found = 0 }while ( dummy_input != -1 && data_found < 4 ) if ( dummy_input == -1 || hertz <= 0 || bitrate <= 0 || data_found != 4 ) { fclose(file) return 0.0 } // 1st byte after data new data_length = fgetc(file) // 2nd byte after data data_length += fgetc(file) * 256 // 3rd byte after data data_length += fgetc(file) * 256 * 256 // 4th byte after data data_length += fgetc(file) * 256 * 256 * 256 fclose(file) return float(data_length) / ( float(hertz * bitrate) / 8.0 ) } enum { MP3_MPEG_VERSION_BIT1 = 8, MP3_MPEG_VERSION_BIT2 = 16, MP3_LAYER_BIT1 = 2, MP3_LAYER_BIT2 = 4, MP3_PROTECT_BIT = 1, MP3_BITRATE_BIT1 = 16, MP3_BITRATE_BIT2 = 32, MP3_BITRATE_BIT3 = 64, MP3_BITRATE_BIT4 = 128, MP3_BITRATE_INVALID = 15, MP3_SAMPLERATE_BIT1 = 4, MP3_SAMPLERATE_BIT2 = 8, MP3_SAMPLERATE_INVALID = 3, MP3_PADDING_BIT = 2, MP3_PRIVATE_BIT = 1, } // bitrate info new const bitrate_table[] = { //MPEG 2 & 2.5 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1, // Layer I 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1, // Layer II 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1, // Layer III //MPEG 1 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1, // Layer I 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1, // Layer II 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1, // Layer III } #if DEBUG_MODE == 1 // frequency info new const samplingrate_table[] = { 11025, 12000, 8000, 0, // MPEG 2.5 // have not seen MPEG 2.5, so UNTESTED -1, -1, -1, 0, // reserved 22050, 24000, 16000, 0, // MPEG 2 44100, 48000, 32000, 0 // MPEG 1 } #endif Float:cfg_get_mp3_duration( mp3_file[] ) { new file = fopen(mp3_file, "rb") new byte, found_header, file_pos new byte2 new mpeg_version new layer new mp3_bitrate new mp3_samplerate new result = -1 do { byte = fgetc(file) if ( byte == -1 ) break ++file_pos if ( byte != 255 ) continue byte = fgetc(file) byte2 = fgetc(file) result = verify_header(byte, byte2, mpeg_version, layer, mp3_bitrate, mp3_samplerate) if ( result == -1 ) { fseek(file, file_pos, SEEK_SET) ++file_pos continue }else break }while ( !found_header && byte != -1 ) fclose(file) if ( byte == -1 ) return 0.0 new mpeg_version_for_bitrate = 0 if ( mpeg_version == 3 ) mpeg_version_for_bitrate = 1 new mp3_bitrate_kbps = bitrate_table[mpeg_version_for_bitrate * ( 3 * 16 ) + ( layer - 1 ) * 16 + mp3_bitrate] #if DEBUG_MODE == 1 log_amx("Sank Sounds >> DEBUG for file ^"%s^"", mp3_file) log_amx("Sank Sounds >> Data bytes = %i / %i", byte, byte2) log_amx("Sank Sounds >> Header position = %i", file_pos) new mpeg_version_str[10] if ( mpeg_version == 0 ) copy(mpeg_version_str, 9, "MPEG 2.5") else if ( mpeg_version == 2 ) copy(mpeg_version_str, 9, "MPEG 2") else if ( mpeg_version == 3 ) copy(mpeg_version_str, 9, "MPEG 1") log_amx("Sank Sounds >> MPEG version = %i / Format: %s", mpeg_version, mpeg_version_str) log_amx("Sank Sounds >> Layer = %i", layer) log_amx("Sank Sounds >> Bitrate = %iKbps (%i)", mp3_bitrate_kbps, mp3_bitrate) new mp3_samplerate_hz = samplingrate_table[mpeg_version * 4 + mp3_samplerate] log_amx("Sank Sounds >> Samplerate = %iHz (%i)", mp3_samplerate_hz, mp3_samplerate) #endif new size_of_file = file_size(mp3_file, 0) if ( mp3_bitrate_kbps == 0 ) return 0.0 //song length... return float(size_of_file) / ( float(mp3_bitrate_kbps) * 1000.0 ) * 8.0 } verify_header( header , header2 , &mpeg_version , &layer , &mp3_bitrate , &mp3_samplerate) { // check if first 3 bits set if ( header & 0xe0 != 0xe0 ) return -1 layer = 4 - ( header & MP3_LAYER_BIT1 ) / MP3_LAYER_BIT1 + ( header & MP3_LAYER_BIT2 ) / MP3_LAYER_BIT1 if ( layer != 3 ) return -1 mp3_bitrate = ( header2 & MP3_BITRATE_BIT1 ) / MP3_BITRATE_BIT1 + ( header2 & MP3_BITRATE_BIT2 ) / MP3_BITRATE_BIT1 + ( header2 & MP3_BITRATE_BIT3 ) / MP3_BITRATE_BIT1 + ( header2 & MP3_BITRATE_BIT4 ) / MP3_BITRATE_BIT1 if ( mp3_bitrate & MP3_BITRATE_INVALID == MP3_BITRATE_INVALID ) return -1 mp3_samplerate = ( header2 & MP3_SAMPLERATE_BIT1 ) / MP3_SAMPLERATE_BIT1 + ( header2 & MP3_SAMPLERATE_BIT2 ) / MP3_SAMPLERATE_BIT1 if ( mp3_samplerate & MP3_SAMPLERATE_INVALID == MP3_SAMPLERATE_INVALID ) return -1 mpeg_version = ( header & MP3_MPEG_VERSION_BIT1 ) / MP3_MPEG_VERSION_BIT1 + ( header & MP3_MPEG_VERSION_BIT2 ) / MP3_MPEG_VERSION_BIT1 return 1 } /* * plugin_sank_sounds.sma * Author: Luke Sankey * Date: March 21, 2001 - Original hard-coded version * Date: July 2, 2001 - Rewrote to be text file configurable * Date: November 18, 2001 - Added admin_sound_play command, new variables * SND_DELAY, SND_SPLIT and EXACT_MATCH, as well as the ability to * have admin-only sounds, like the original version had. * Date: March 30, 2002 - Now ignores speech of length zero. * Date: May 30, 2002 - Updated for use with new playerinfo function * Date: November 12, 2002 - Moved snd-list.cfg file to new location, and * made it all lower-case. Sorry, linux guys, if it confuses you. * Added some new ideas from Bill Bateman: * 1.) added SND_PUNISH and changed SND_KICK to SND_MAX * 2.) ability to either speak or play sounds * * Last Updated: May 12, 2003 * * * * HunteR's modifications: * - Players no longer kicked, they are "muted" (no longer able to play sounds) * - All sounds are now "spoken" (using the speak command) * - As a result, all "\" must become "/" * - Ability to reset a player's sound count mid-game * * My most deepest thanks goes to William Bateman (aka HunteR) * http://thepit.shacknet.nu * [email protected] * For he was the one who got me motivated once again to write this plugin * since I don't run a server anymore. And besides that, he helped write * parts of it. * * I hope you enjoy this new functionality on the old plugin_sank_sounds */