Compare commits

...
207 Commits
Author SHA1 Message Date
jenz 54a921c1a5 1000 should be completely fine, but increasing to 2000. Also fixed that cleararray only happens on mapstart and not every time the function is called again to fetch more results from impact api 2026-10-05 10:55:18 +02:00
jenz 1e969e697f should now exclude the webclient from vpn checking as its an internal docker IP that cant be resolved obviously 2026-10-05 10:03:42 +02:00
jenz eb19a779a6 adding at most 50 maps to casualpool from playtime session frontend. also assuring they are not on cooldown 2026-10-05 00:30:52 +02:00
jenz f38a714a2e forgot to check 2026-10-04 21:36:58 +02:00
jenz 64bc2be4aa reverted that leaders can skip cooldowns and restrictions for nominating maps. added that mapchooser for casualpool fetches more than original 50 maps in case most of them are actually admin only or deleted. added that cooldown hours are scaled by if a map has a negative impact on the server population 2026-10-04 20:56:43 +02:00
jenz 854da9b615 adds maps to the casualpool from the playtime frontend based on best average population gains on maps 2026-10-04 12:25:14 +02:00
jenz 0a1bf2e910 added simple way to disable lag compensation for every round on a map by adding entry into mapchooser cfg 2026-10-02 11:35:05 +02:00
marisa f7995a3656 Changez 2026-09-29 21:58:54 +01:00
marisa 014f15acea Couch turbophysics HUD 2026-09-29 21:40:51 +01:00
marisa d6f3d622dd EventNotifier should show dates/times in the client's timezone 2026-09-29 18:58:22 +01:00
marisa cbe461ac26 Couch push scale 2026-09-29 18:18:33 +01:00
marisa aa7b60343f Couch fixes 2026-09-29 17:54:40 +01:00
marisa 63c218a0e9 Add extra couch features 2026-09-29 17:36:33 +01:00
marisa 598dc20ae3 Add sm_gotocouch & effect intensity 2026-09-29 02:41:20 +01:00
marisa 867b3bfbba Add Couch plugins 2026-09-29 02:24:47 +01:00
marisa 41f86102e5 Add EventNotifier 2026-09-28 21:40:26 +01:00
jenz fc64e0ca07 preventing index array out of bounds 2026-09-28 21:24:36 +02:00
Irineu 2250d0009c Small mistake 2026-09-28 05:29:05 -03:00
jenz 2841927ea0 Merge pull request 'More PropSpawner changes' (#6) from Irineu/sm-plugins:master into master
Reviewed-on: #6
2026-09-28 09:35:23 +02:00
Irineu 9c1a884643 Added sm_propspawner_tier_requirement and sm_propspawner_button_tier_requirement 2026-09-28 04:17:37 -03:00
Irineu b7b7828f65 Limited notify hud msg to 80 chars, limited it to once per frame and added a 2nd mode to sm_propspawner_notify_shot that will send the message to console instead 2026-09-28 04:17:37 -03:00
Irineu 6268e9ed19 Repurpose g_aSpawnedProps, and remove some no longer needed stuff 2026-09-28 04:17:37 -03:00
jenz 5729eaa051 AI slop update to lagcomp that simply disables lagcompensation for the remaining round if more than cvar amount of entities have to be lagcompensated. also an option to enable it again through admin command which means it can be enabled again through stripper files for the same round. 2026-09-27 20:52:40 +02:00
jenz 79bd5af4dd just adding a config file to control if the fakeclients should join a team based on player count 2026-09-23 13:13:15 +02:00
Irineu c6b1c74135 PropSpawner: Fixed the notify list hud changing position depending on how many lines it has, and moved it up a little bit too 2026-09-22 12:24:34 +02:00
Irineu 1b3860f7f6 PropSpawner: Small fix related to the channel used in ShowHudText 2026-09-22 12:24:34 +02:00
Irineu fd63b4c235 PropSpawner: Set prop team to CT for it to work with filtered triggers 2026-09-22 12:24:34 +02:00
Irineu da984e5266 PropSpawner: Remove collision between func_clip_vphysics and plugin spawned props 2026-09-22 12:24:34 +02:00
Irineu 82b0fa7eb1 PropSpawner: Added sm_propspawner_use_mode to determine what should happen when a plugin spawned prop is +use'd 2026-09-22 12:24:34 +02:00
Irineu 27e584bbca PropSpawner: Added a menu option and cmd to choose your prop's color 2026-09-22 12:24:34 +02:00
jenz 2947bef434 sourcemod 1.13 upgrade. moved all plugins from steamworks to ripext. added smjanson and asyncsocket.inc an extra time so its easier to find. updated plugins that would not compile with 1.13 2026-09-22 01:55:09 +02:00
jenz 789b8ebef9 now the menu displays the category in parantheses for each map 2026-09-11 16:05:36 +02:00
jenz 1ccb5cc387 Merge pull request 'Update lag compensation' (#4) from marisa-lagcomp into master
Reviewed-on: #4
2026-09-11 12:07:30 +02:00
marisa fbc8a56fe1 Update lag compensation 2026-09-11 12:06:03 +02:00
jenz 42ffd211e7 Merge pull request 'Add game_text style hud list for couch shooters, and remove the chat message' (#3) from Irineu/sm-plugins:master into master
Reviewed-on: #3
2026-09-09 17:56:14 +02:00
Irineu cfa37daeb0 Add game_text style hud list for couch shooters, and remove the chat message 2026-09-09 12:20:57 -03:00
jenz 07f9a6768e Merge pull request 'Allow USE anywhere to activate entWatch items' (#2) from marisa-entwatch-useanywhere into master
Reviewed-on: #2
Allow USE anywhere to activate entWatch items. now put into its own separate module.
2026-09-08 22:23:10 +02:00
marisa 6b7803d46e Allow USE anywhere to activate entWatch items 2026-09-08 21:16:04 +01:00
shino ac67f2e27e Update Hitmarker/scripting/Hitmarker.sp
increased the max number of skins
2026-09-06 16:21:41 +02:00
jenz ef4bc91712 now the event couch can go through portals 2026-09-04 18:12:42 +02:00
jenz e3e70fcb12 expanded array size 2026-08-29 23:22:05 +02:00
jenz 4fd3017e5f re-added feature to just have the full list instead 2026-08-29 15:30:52 +02:00
jenz b791b050fc a bit clean up on features we abandoned again. remaking logaction to logmessage and allowing the webclient to go through without being blocked by steamID check 2026-08-29 11:43:06 +02:00
jenz 48ff38522b added cvars for being enabled/disabled for specific maps 2026-08-24 21:20:51 +02:00
jenz 5a8c97e082 missing check 2026-08-24 15:47:36 +02:00
jenz d697faa470 forward for the clients steamID, mapchoice and vote weight 2026-08-20 14:42:08 +02:00
jenz cb8d244030 simple feature through cvar to report when people shoot the couch 2026-08-18 15:02:18 +02:00
jenz 2e21de0c47 potential self fixing mechanic in case a steam player would briefly be marked as nosteamer before the connect extension gets it right? 2026-08-18 13:55:39 +02:00
jenz 80642cb0f0 now reading tickrate from mapchooser config instead of separate file 2026-08-18 12:47:30 +02:00
jenz 06d0b43b9c if vote is in progress just put bosshp in centertext 2026-08-17 20:50:50 +02:00
jenz b6c0cb232d added category menu for nominations and putting maps into sub menus 2026-08-14 16:40:31 +02:00
jenz a02fea8cc8 reintroducing revotes again for normal mapvotes 2026-08-14 15:38:06 +02:00
jenz a4e2d541a9 further modifications to make it compatible with the entwatch bans website 2026-08-12 15:32:06 +02:00
jenz 1cb0905d78 oversight on my part 2026-08-11 16:29:34 +02:00
jenz 14e83ec9a3 modified to play along with the webpannel for knifebans as well 2026-08-11 13:24:37 +02:00
jenz a72b0d9a8d had forgotten checking if client index is valid even 2026-07-31 18:57:07 +02:00
jenz 4cf475c109 moving the trail a little out of the ground so its visible when standing on moving objects like boats 2026-07-30 22:07:00 +02:00
jenz ef5ba318c1 mistake 2026-07-28 22:55:43 +02:00
jenz 2bde95afcc added config file 2026-07-28 22:29:57 +02:00
jenz 1fa8a916d6 added cvar for trail lifetime, end and starth width 2026-07-28 21:54:40 +02:00
jenz 52a452708e added native in mapchooser for the trails entry for maps that enabled trails. also added the trails plugin itself 2026-07-28 20:05:15 +02:00
jenz 8ef2f2f2cc added forward from connect announce to log in live chat the connect message with steamID 2026-07-26 13:35:32 +02:00
jenz b527ce5473 added forward for the connect message so stoat can log it to livechat 2026-07-26 13:18:28 +02:00
jenz 921ae4e136 probably good enough for displaying in console who shot and who did not 2026-07-25 23:35:46 +02:00
jenz fd32ddc101 made it its own cvar 2026-07-25 14:32:29 +02:00
jenz 2b9224f11a only counting all cvars in real players now 2026-07-25 11:26:46 +02:00
jenz ef15dfc71c no zr edition for mg/zr 2026-07-24 11:08:49 +02:00
jenz 2d91202469 forwards finally detect the webclient, handling password and retry through the plugin for the webclient 2026-07-24 11:03:05 +02:00
jenz 622bc8da4e AI slop update that enables current leaders with rcon access to write clients to the leader file or remove them again from it 2026-07-20 23:08:44 +02:00
jenz 295c92f49f not counting the fakeclients again. done in the past as well. made sense to try again. 2026-07-20 20:31:36 +02:00
jenz 47460a2db4 forgot check 2026-07-14 14:18:00 +02:00
jenz a23be2483f minor correction check 2026-07-13 23:14:28 +02:00
jenz b6e7443448 i am just going to assume it logs correctly to console 2026-07-13 23:11:35 +02:00
jenz e3e5945e77 finally adapted the plugin a bit to the new stripper modification that changed the levelcounter to info_target as a permanent entity. also added some extra modes for the vote menu 2026-07-11 23:14:39 +02:00
jenz a8dbd00f89 why did i do that mistake, self mute also works as gag even with this enabled 2026-07-11 17:36:21 +02:00
jenz f6536fd3c5 just making sure all nosteamers always have steam players muted and not only the webclient 2026-07-09 15:42:52 +02:00
jenz 8ca6ecae97 not displaying on connect anymore, changed my mind 2026-07-06 12:35:30 +02:00
jenz 02a990a29f obvious oversight for steam clients connecting after the nosteam client connected 2026-07-04 20:46:12 +02:00
jenz 1139fd89d0 improved the check and tells how many people still are needed for self extend 2026-07-04 16:54:07 +02:00
jenz 5986bedc42 should prevent webclient from being mother zombie on maps where only 1-2 zms spawn 2026-07-04 15:43:34 +02:00
jenz ee18f49be0 missed checks 2026-07-04 02:18:27 +02:00
jenz 5a1ca4959d if its the webclient just filter out all chat messages with ; or " in them 2026-07-04 00:19:24 +02:00
jenz 4a9a8e06cf nvm 2026-07-03 22:45:17 +02:00
jenz 6b625bc7ea prevent users from literally copy pasting "; into the chat 2026-07-03 21:21:44 +02:00
jenz bf6ed6e4e1 tricking nosteamers into auto muting steam clients because of broadcast voice data using steam codec and not celt. however steam clients still hear sendvoicedata that transcodes opus to celt 2026-07-01 22:19:55 +02:00
jenz 4469d07602 very silly but the webclient is skipping all the connection related forwards, so doing this weird work around for it 2026-06-30 11:14:16 +02:00
jenz ce78793029 just using default priority 2026-06-26 00:45:32 +02:00
jenz 509bd521fb should quell the error message i suppose 2026-06-25 20:03:16 +02:00
jenz 71d8773767 added myactivity command cause machu asked 2026-06-25 19:41:36 +02:00
jenz 111cc27521 added new laserfag entry in mapchooser 2026-06-25 14:04:07 +02:00
jenz c03f450e8e added extra connect rank 2026-06-24 22:05:11 +02:00
jenz e2b3ead9c3 just the exact same plugin but without hlstatsx 2026-06-23 01:05:37 +02:00
jenz c379458632 uploading remade source code of neons grenadeskin plugin because he never uploaded it. also now has tier support 2026-06-20 14:14:53 +02:00
jenz 9e59258ab0 just blocking out the hide option from working in settings in case of not having reached the tier yet 2026-06-20 01:15:58 +02:00
jenz e2659d79a6 fixed check so admin nominate still works 2026-06-19 16:12:01 +02:00
jenz e74db611c4 zspawn and joinclass if afk for 1 minute and below mce_enable_map_cooldowns_player_count counted for real players 2026-06-19 15:07:07 +02:00
jenz fefad5ce8a now having 3 map nominations as a tier perk 2026-06-18 13:48:25 +02:00
jenz 3d7e279cc7 just a tiny plugin moving afk spectators to teams 2026-06-18 12:38:36 +02:00
jenz e86e90838a moved playtime restrictions from hour requirement over to tier requirement 2026-06-17 13:55:13 +02:00
jenz 7ec6fed775 replaced hour requirement with tier requirement 2026-06-17 10:54:56 +02:00
jenz e5974b7478 yeah no reason to display member anymore on connect 2026-06-16 23:12:31 +02:00
jenz 429a2d1b0f playerranking without hlstats 2026-06-16 22:31:06 +02:00
jenz 07c8d450b3 added new formatting based on tiers for the connect message 2026-06-16 01:32:18 +02:00
jenz a460fbeaeb initial commit of render distance controller for the webclient to compensate for lacking rasterize 2026-06-10 01:05:57 +02:00
jenz 4b89862452 probably a bit cheaper calculationwise 2026-06-08 23:07:13 +02:00
jenz 8be8ced61a tried changing mask slightly 2026-06-08 12:49:11 +02:00
jenz 88b153ea3c modified the render distance plugin from madness, could not get gamedata or translations to work so just threw those out 2026-06-08 12:30:58 +02:00
jenz e0672ab09b removed the map end version and avg version. now its just called unloze. it still has average and new features from mapend, but i removed the actual mapend functionality due to players always saying they were stucked by the scoreboard pannel 2026-06-07 12:15:39 +02:00
jenz 56c97b7d29 should not had checked alive, instead increased number 2026-06-05 17:51:02 +02:00
jenz bf4c76a17c redoing adjustments 2026-06-05 15:21:57 +02:00
jenz 06e5c325ac just a timer running each second deciding if nowinpannel is enabled or not 2026-06-02 16:52:14 +02:00
jenz 4914461539 hopefully that stops the scoreboard from blocking people from participating in the vote 2026-06-02 14:46:30 +02:00
jenz e3a85e1b65 just spamming the stub to make sure it gets through 2026-06-02 00:07:59 +02:00
jenz 5cce927ced updating README to point towards correct branch 2026-05-31 01:15:18 +02:00
jenz b8ac8cc571 comitting the sourcemod plugin part for voice extension update 2026-05-30 16:58:35 +02:00
jenz 723525f8c5 removed comment 2026-05-30 15:26:54 +02:00
jenz 890e2e1418 added a stringmap to store steam ID's so during plugins lifetime it only shows the menu once when connecting 2026-05-17 13:36:06 +02:00
jenz 61568dc032 preventing first map after server crash/restart to go on cooldown 2026-05-16 14:38:57 +02:00
jenz 8b6f2e0d96 adding top activity display ingame 2026-05-16 13:06:47 +02:00
jenz 9ff0faf337 added that admins can toggle easy help on and off 2026-05-07 10:44:55 +02:00
jenz b6a1c15b7e redoing timing a little 2026-05-06 00:16:12 +02:00
jenz 0866d58b59 using the nide config approach but actually works i guess 2026-05-05 11:38:07 +02:00
jenz 6acdc80767 uploading nides edition of fakeclients with sm_debugfakes added 2026-05-03 13:18:12 +02:00
jenz 80d0b85367 probably prevents current map from randomly showing up in the mapvote itself 2026-04-30 14:18:23 +02:00
jenz 1df4cdf8dc furhter adjustments based on time 2026-04-30 14:17:41 +02:00
jenz c819974dd6 just further adjustments to autism 2026-04-24 23:01:31 +02:00
jenz d48a5b14c1 changed dbprio to high from low to stop people from nominating the maps before the query finishes 2026-04-21 21:59:58 +02:00
jenz 45258da9e1 just adding a boolean check on mapend to skip updating playercount first time the timer triggers 2026-04-21 21:35:14 +02:00
jenz 8e46053c0b instead of checking player count on each round start its checked globally every 2 minutes so that the first round on new maps dont give the extra help just because of players joining late 2026-04-21 20:00:36 +02:00
jenz 2b67f52368 not checking autismbots and fakes any longer 2026-04-21 13:46:02 +02:00
jenz f5804eed72 more time 2026-04-21 13:45:04 +02:00
jenz c57aa5bcc2 updated so that low pop helper has its own configuration now in the .cfg to decide with how many people on each map to help with easy settings 2026-04-19 19:34:58 +02:00
jenz 4a1ac398d0 forgot updating port 2026-04-19 15:32:15 +02:00
jenz f590dcc7d1 added new cooldown system that tracks minutes played into database rows and sets maps on cooldown until they fallen down enough of the listing 2026-04-19 15:26:58 +02:00
jenz 01816792ae removing a bunch of unneeded things and assuring the nomlist wont confuse leader and regular nominations any more when displaying them 2026-04-17 20:29:12 +02:00
jenz f37711fbdf majorly redid fakeclients behaviour to be balanced, updated knockback modifier to only consider mapcooldowns of over 60 hours and to check the timeleft on maps to prevent triggering the condition hopefully too early 2026-04-17 20:15:42 +02:00
jenz 7d5e56540a redoing with new native to not screw up the nominations related native 2026-04-14 22:46:24 +02:00
jenz 8e33a8bd87 just moving stuff in fakeclients over to functions. in the low pop helper plugin using a new native from mapchooser. skipping boost if first round and also including spectators now 2026-04-14 22:45:35 +02:00
jenz 208d084024 added low player count feature that gives extra hp and infinite ammo on difficult maps with low population 2026-04-14 15:17:19 +02:00
jenz b4c4b11c3d the minutes displayed would go negative which was unintended 2026-04-14 15:16:49 +02:00
jenz 778fdd2f7a probably final redux of it 2026-04-14 15:16:14 +02:00
jenz d396e006b9 added natives in mapchooser for knockback low pop plugin which now on low pop gives infintie ammo and high HP for doing difficult maps 2026-04-13 14:23:11 +02:00
jenz d12aff14ac missed ingamecheck 2026-04-07 12:18:52 +02:00
jenz 7e3ad44699 probably last edits for now 2026-04-07 11:32:34 +02:00
jenz 13f4727826 further clean up of unneeded stuff 2026-04-06 23:28:09 +02:00
jenz 06734d1b6e adjustments 2026-04-06 15:07:24 +02:00
jenz ff012d2df5 this should now catch the player count correctly to set the cooldown and shared cooldowns 2026-04-02 20:47:46 +02:00
jenz ef537b8826 renamed variable for skipping all restrictions on low pop and now also made it skip cooldowns as well as adding a new variable that decides how many players are required before applying cooldowns at all. also changed player counting not consider fakes, autism bots, afks and spec anymore to make it a lot simpler to understand. 2026-03-31 21:26:26 +02:00
jenz 6790e463fc updated fakeclients slightly and changed mapchooser for loops to not miss the last client 2026-03-27 17:34:04 +01:00
jenz 6a5cbf74f1 had it wrong before, was meant to be total_votes and not num_votes 2026-03-26 20:54:04 +01:00
jenz a5a856073e thanks madness 2026-03-23 15:40:25 +01:00
jenz 3122ed0210 thanks madness, i guess you were right about that 2026-03-23 15:37:14 +01:00
jenz 9d3a9e6b89 renamed cvar to make more sense, set shared cooldowns on map start and map ends, removed global bool g_SaveCDOnMapEnd, added shared cooldowns as group in mapchooser_extended.cfg, giving leader nominations same priority as admin nominations, removed a lot of translation phrases to instead english only. removing mapstatus enabled, disabled, and the others. removed the whole fucking trie, prevent people from nominating maps nominated by leader or admin. re added the boost messages by removing translation files. added support in nominate and nomlist to indicate admin and leader nomination. 2026-03-21 23:41:09 +01:00
jenz 4c69c978b2 allowing fps_max 0 is probably ok 2026-03-21 12:22:38 +01:00
jenz 940b53fffc show in the menu that a map was nominated by a leader 2026-03-15 15:13:30 +01:00
jenz 5855e49f78 backup map is now based on casualpool 2026-03-14 17:39:25 +01:00
jenz 1bbed31610 some rare times props disappear on prop heavy maps, maybe this helps 2026-03-08 17:38:22 +01:00
jenz 6867ef2482 why did we not auto kick for this before?.... oh well lost knowledge 2026-03-08 15:05:44 +01:00
jenz d78c39cf3c instead of hard coding to ze_random_v9 it now takes the startup map when server is restarted 2026-03-05 14:49:42 +01:00
jenz c0cddad8cb minplayer and maxplayer requirement now consider afk's as well 2026-03-04 15:38:14 +01:00
jenz c0fb1dd61a only tested on dev so far but should be stable probably. rtv is now not disabled even if there is not enough time left over and the last round ends. instead the final round gets delayed until the rtv map vote is over 2026-02-26 21:20:00 +01:00
jenz 01fad8b648 mg support 2026-02-23 12:45:49 +01:00
jenz 4efd426893 sending map info to mapnotifications 2026-02-22 23:35:39 +01:00
jenz 005552248b did not know until now that sm_chat existed lol 2026-02-16 22:02:41 +01:00
jenz 3dc480d564 support for admin chat from stoat 2026-02-16 21:14:50 +01:00
jenz 2e99fec5de added calladmin support 2026-02-16 19:47:15 +01:00
jenz b81b8d0773 stoat support for calladmin 2026-02-16 19:43:39 +01:00
jenz b789c71d23 listening to teamchat too 2026-02-06 22:18:19 +01:00
jenz cb7dd7462b this has no reason to be static 2026-02-05 20:57:26 +01:00
jenz 363233b044 this time comitting without api, and tokens by accident 2026-02-05 17:31:35 +01:00
jenz cd4937521b another stoat support 2026-02-04 12:14:59 +01:00
jenz f3995b4b6d replaced the translation file with actual print message so it wont switch to random languages anymore 2026-01-29 12:24:02 +01:00
jenz e49f0a3f3a experimental update to prevent mapend being stuck with nothing happening when nobody voted anything 2026-01-27 21:56:42 +01:00
jenz 29971e96fc corrected wrong logic for ban durations 2026-01-27 21:00:09 +01:00
jenz 41b775f611 adding minute support from unloze playtime into discord message, entwatch, knifeban and spraymanager 2026-01-27 18:55:28 +01:00
jenz 7d940c6509 lazy fix for invisible zm 2026-01-26 23:26:21 +01:00
jenz c1da687fa8 well less mistakes happen now 2026-01-26 18:00:44 +01:00
jenz 31d550bbcf further logmessages that are helpful atm 2026-01-26 08:26:51 +01:00
jenz 2ad829bd54 corrected incorrect time reporting and now fakeclients cant be knifed forward either i guess 2026-01-18 12:33:57 +01:00
jenz 414ade0856 hide using the transmitmanager plugin with leader and entwatch support integrated 2026-01-15 18:39:49 +01:00
jenz d45bcf955a updated entwatch message and ban report message 2026-01-14 00:06:04 +01:00
pan32 4f57a8957c [Radiomute] Added scuffed timed mutes 2026-01-13 21:50:45 +00:00
jenz 484430cd50 excludemap and excludemaptime are now always respected 2026-01-10 14:44:38 +01:00
jenz 4d3d774cc0 remade so its based on ingame play time instead of real time 2026-01-08 20:28:41 +01:00
jenz 1cc618ed04 added differentation between restricted clients and clients with not enough time in ebanlist command 2026-01-08 16:03:34 +01:00
jenz 3da429b91f adding entwatch restriction module based on players ingame time 2026-01-08 12:55:11 +01:00
jenz b57f8fdd68 updated knockback low pop modifier 2026-01-08 12:53:51 +01:00
jenz 9606606baf nominate_removemap should now remove every instance of the same nominated map 2026-01-06 22:17:12 +01:00
jenz 9527c5bc9b just a small thing to increase knockback for low population hours 2025-12-14 22:50:13 +01:00
jenz 126c976a65 fixed the torchlight not being targeted i guess 2025-12-14 12:35:52 +01:00
jenz 4b27950b98 excluding specs too 2025-12-12 21:04:36 +01:00
jenz 9e79d95041 minor correction 2025-12-11 01:17:22 +01:00
jenz 8ff4eee7e0 minor updates after talking in admin chat 2025-12-11 01:13:07 +01:00
jenz c72a911bcb forgot to delete snapshot after use 2025-11-18 11:57:06 +01:00
jenz 60d4ba7828 checking autismbots 2025-11-16 14:54:47 +01:00
jenz 72bd0c3ce5 just some minor adjustments to placing fakes on teams 2025-11-13 20:16:13 +01:00
jenz fca3e99528 removed message forwarding. people demanded that self mute also should serve as a self gag 2025-10-20 20:58:50 +02:00
zaCade c002407410 [SB++] Allow blocks on unverified and skip saving them. 2025-07-20 17:53:11 +02:00
jenz f3575929f7 i got asked to revert it again so sure why not 2025-07-09 20:09:07 +02:00
jenz 895c9639d6 added extra rtv requirements. we add more rtv's on lower player count than on higher player count. so its harder to rtv with an empty server and less difficult on a full. was requested by some admins 2025-07-09 19:36:46 +02:00
zaCade 15d44df991 [KnifeBan] Check if attacker is alive. 2025-07-06 09:19:53 +02:00
zaCade de4f04a310 [KnifeBan] CChange function return type to void. 2025-07-06 09:19:44 +02:00
jenz 6a0b75ed61 not including nosteamers again. very simply 2025-06-30 17:16:49 +02:00
jenz 3ec612167d added play time requirement for being allowed to spray 2025-06-09 20:31:34 +02:00
jenz cf58c745d2 fixing minor mistake 2025-06-09 17:51:38 +02:00
zaCade 8c72b1cae1 [GlowColors] Void instead of Int, because of no return. 2025-06-01 14:33:09 +02:00
zaCade c9637a753f [GlowColors] Implement minimal frequency. 2025-06-01 14:32:42 +02:00
89 changed files with 26465 additions and 5089 deletions
+76
View File
@@ -0,0 +1,76 @@
#include <sourcemod>
#include <sdktools>
#include <cstrike>
#include <AFKManager>
int g_iPlayer_move_spec_count;
public Plugin myinfo =
{
name = "spectator_handler",
author = "jenz",
description = "moves afk spectators to team",
version = "1.3",
url = ""
};
public void OnPluginStart()
{
CreateTimer(60.0, CheckAfks, _, TIMER_REPEAT);
ConVar cvar;
HookConVarChange((cvar = CreateConVar("sm_move_spec_to_team_count", "30", "Move real players from spec to teams until this many real players are connected")), Cvar_move_spec_team);
g_iPlayer_move_spec_count = cvar.IntValue;
delete cvar;
}
public void Cvar_move_spec_team(ConVar convar, const char[] oldValue, const char[] newValue)
{
g_iPlayer_move_spec_count = convar.IntValue;
}
public Action CheckAfks(Handle timer)
{
int real_players = 0;
for (int i = 0; i < MaxClients; i++)
{
if (IsValidClient(i) && !IsFakeClient(i) && !IsClientSourceTV(i))
{
real_players++;
}
}
if (real_players < g_iPlayer_move_spec_count)
{
for (int i = 0; i < MaxClients; i++)
{
if (IsValidClient(i) && !IsFakeClient(i) && !IsClientSourceTV(i) && GetClientTeam(i) <= CS_TEAM_SPECTATOR && GetClientIdleTime(i) > 60)
{
ChangeClientTeam(i, CS_TEAM_T);
CreateTimer(1.0, zspawn, GetClientSerial(i));
}
}
}
return Plugin_Handled;
}
public Action zspawn(Handle hTimer, int Serial)
{
int client;
if ((client = GetClientFromSerial(Serial)) == 0)
{
return Plugin_Handled;
}
if (IsValidClient(client))
{
FakeClientCommandEx(client, "joinclass");
FakeClientCommandEx(client, "say /zspawn");
}
return Plugin_Handled;
}
stock bool IsValidClient(int client)
{
if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
return true;
return false;
}
@@ -4,7 +4,7 @@
#include <sourcemod> #include <sourcemod>
#include <sdktools> #include <sdktools>
#include <SteamWorks> #include <ripext>
#include <cstrike> #include <cstrike>
#include <AdvancedTargeting> #include <AdvancedTargeting>
@@ -437,17 +437,14 @@ public void OnClientAuthorized(int client, const char[] auth)
char sSteam64ID[32]; char sSteam64ID[32];
Steam32IDtoSteam64ID(auth, sSteam64ID, sizeof(sSteam64ID)); Steam32IDtoSteam64ID(auth, sSteam64ID, sizeof(sSteam64ID));
// format=json instead of format=vdf: ripext only exposes the response body
// as decoded JSON, so we ask Steam's own API for JSON directly rather than
// working around a missing raw-body accessor.
static char sRequest[256]; static char sRequest[256];
FormatEx(sRequest, sizeof(sRequest), "http://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=%s&steamid=%s&relationship=friend&format=vdf", STEAM_API_KEY, sSteam64ID); FormatEx(sRequest, sizeof(sRequest), "http://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=%s&steamid=%s&relationship=friend&format=json", STEAM_API_KEY, sSteam64ID);
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest); HTTPRequest hRequest = new HTTPRequest(sRequest);
if (!hRequest || hRequest.Get(OnTransferComplete, client);
!SteamWorks_SetHTTPRequestContextValue(hRequest, client) ||
!SteamWorks_SetHTTPCallbacks(hRequest, OnTransferComplete) ||
!SteamWorks_SendHTTPRequest(hRequest))
{
CloseHandle(hRequest);
}
} }
public void OnClientDisconnect(int client) public void OnClientDisconnect(int client)
@@ -458,52 +455,38 @@ public void OnClientDisconnect(int client)
g_FriendsArray[client] = INVALID_HANDLE; g_FriendsArray[client] = INVALID_HANDLE;
} }
public int OnTransferComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, int client) public void OnTransferComplete(HTTPResponse response, any client)
{ {
if(bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK) if(response.Status != HTTPStatus_OK || response.Data == null)
{ {
// Private profile or maybe steam down? // Private profile or maybe steam down?
//LogError("SteamAPI HTTP Response failed: %d", eStatusCode); //LogError("SteamAPI HTTP Response failed: %d", response.Status);
CloseHandle(hRequest);
return; return;
} }
int Length; APIWebResponse(view_as<JSONObject>(response.Data), client);
SteamWorks_GetHTTPResponseBodySize(hRequest, Length);
char[] sData = new char[Length];
SteamWorks_GetHTTPResponseBodyData(hRequest, sData, Length);
//SteamWorks_GetHTTPResponseBodyCallback(hRequest, APIWebResponse, client);
CloseHandle(hRequest);
APIWebResponse(sData, client);
} }
public void APIWebResponse(const char[] sData, int client) public void APIWebResponse(JSONObject Response, int client)
{ {
KeyValues Response = new KeyValues("SteamAPIResponse"); if(!Response.HasKey("friendslist"))
if(!Response.ImportFromString(sData, "SteamAPIResponse"))
{ {
LogError("ImportFromString(sData, \"SteamAPIResponse\") failed."); LogError("GetFriendList response missing \"friendslist\" key.");
delete Response; delete Response;
return; return;
} }
if(!Response.JumpToKey("friends")) JSONObject friendslist = view_as<JSONObject>(Response.Get("friendslist"));
// No friends, or private profile - Steam returns {"friendslist":{}} in that case.
if(!friendslist.HasKey("friends"))
{ {
LogError("JumpToKey(\"friends\") failed."); delete friendslist;
delete Response; delete Response;
return; return;
} }
// No friends? JSONArray friends = view_as<JSONArray>(friendslist.Get("friends"));
if(!Response.GotoFirstSubKey())
{
//LogError("GotoFirstSubKey() failed.");
delete Response;
return;
}
if(g_FriendsArray[client] != INVALID_HANDLE) if(g_FriendsArray[client] != INVALID_HANDLE)
CloseHandle(g_FriendsArray[client]); CloseHandle(g_FriendsArray[client]);
@@ -511,18 +494,20 @@ public void APIWebResponse(const char[] sData, int client)
g_FriendsArray[client] = CreateArray(); g_FriendsArray[client] = CreateArray();
char sCommunityID[32]; char sCommunityID[32];
do int len = friends.Length;
for(int i = 0; i < len; i++)
{ {
Response.GetString("steamid", sCommunityID, sizeof(sCommunityID)); JSONObject friendEntry = view_as<JSONObject>(friends.Get(i));
if(friendEntry.GetString("steamid", sCommunityID, sizeof(sCommunityID)))
PushArrayCell(g_FriendsArray[client], Steam64toSteam3(sCommunityID)); PushArrayCell(g_FriendsArray[client], Steam64toSteam3(sCommunityID));
delete friendEntry;
} }
while(Response.GotoNextKey());
delete friends;
delete friendslist;
delete Response; delete Response;
} }
stock bool Steam32IDtoSteam64ID(const char[] sSteam32ID, char[] sSteam64ID, int Size) stock bool Steam32IDtoSteam64ID(const char[] sSteam32ID, char[] sSteam64ID, int Size)
{ {
if(strlen(sSteam32ID) < 11 || strncmp(sSteam32ID[0], "STEAM_0:", 8)) if(strlen(sSteam32ID) < 11 || strncmp(sSteam32ID[0], "STEAM_0:", 8))
@@ -634,3 +619,4 @@ public int Native_ReadClientFriends(Handle plugin, int numParams)
return 0; return 0;
} }
+12 -7
View File
@@ -426,9 +426,12 @@ NotifyAdmins(int client, const char[] sReason)
if(IsClientInGame(i) && !IsFakeClient(i) && CheckCommandAccess(i, "sm_stats", ADMFLAG_GENERIC)) if(IsClientInGame(i) && !IsFakeClient(i) && CheckCommandAccess(i, "sm_stats", ADMFLAG_GENERIC))
{ {
CPrintToChat(i, "{green}[SM]{default} %L has been detected for {red}%s{default}, please check your console!", client, sReason); CPrintToChat(i, "{green}[SM]{default} %L has been detected for {red}%s{default}, please check your console!", client, sReason);
FormatStats(i, client); char sBuffer[2000];
FormatStats(i, client, sBuffer, 0);
PrintToConsole(client, "%s", "\n"); PrintToConsole(client, "%s", "\n");
FormatStreak(i, client, 0); int len = FormatStats(-1, client, sBuffer, sizeof(sBuffer));
sBuffer[len++] = '\n';
FormatStreak(i, client, 0, sBuffer[len], sizeof(sBuffer) - len);
} }
} }
@@ -464,12 +467,13 @@ public Action Command_Stats(int client, int argc)
for(int i = 0; i < iTargetCount; i++) for(int i = 0; i < iTargetCount; i++)
{ {
FormatStats(client, iTargets[i]); char sBuffer[2000];
FormatStats(i, client, sBuffer, 0);
PrintToConsole(client, "%s", "\n"); PrintToConsole(client, "%s", "\n");
for(int j = 0; j < 3; j++) for(int j = 0; j < 3; j++)
{ {
FormatStreak(client, iTargets[i], j); FormatStreak(client, iTargets[i], j, sBuffer[0], sizeof(sBuffer) - 0);
PrintToConsole(client, "%s", "\n"); PrintToConsole(client, "%s", "\n");
} }
} }
@@ -509,13 +513,14 @@ public Action Command_Streak(int client, int argc)
for(int i = 0; i < iTargetCount; i++) for(int i = 0; i < iTargetCount; i++)
{ {
FormatStreak(client, iTargets[i], iStreak); char sBuffer[2000];
FormatStreak(client, iTargets[i], iStreak, sBuffer[0], 0);
} }
return Plugin_Handled; return Plugin_Handled;
} }
int FormatStats(int client, int iTarget, char[] sBuf=0, int len=0) int FormatStats(int client, int iTarget, char[] sBuf, int len=0)
{ {
int iUserID = GetClientUserId(iTarget); int iUserID = GetClientUserId(iTarget);
char sAuth[32]; char sAuth[32];
@@ -545,7 +550,7 @@ int FormatStats(int client, int iTarget, char[] sBuf=0, int len=0)
return iBuf; return iBuf;
} }
int FormatStreak(int client, int iTarget, int iStreak, char[] sBuf=0, int len=0) int FormatStreak(int client, int iTarget, int iStreak, char[] sBuf, int len=0)
{ {
int iUserID = GetClientUserId(iTarget); int iUserID = GetClientUserId(iTarget);
char sAuth[32]; char sAuth[32];
@@ -0,0 +1,140 @@
#include <sourcemod>
#include <cstrike>
#include <mapchooser_extended>
int g_iDoingMapTouristMode;
int g_iActivePlayerCount;
bool g_bMapended;
public Plugin myinfo =
{
name = "knockback low pop modifier and helper",
author = "jenz",
description = "balances knockback and human settings for low population",
version = "2.1.0",
url = ""
}
public void OnPluginStart()
{
HookEvent("round_start", OnRoundStart);
RegAdminCmd("sm_helpon", Cmd_enableHelp, ADMFLAG_GENERIC);
RegAdminCmd("sm_helpoff", Cmd_disableHelp, ADMFLAG_GENERIC);
CreateTimer(80.0, CheckPopulation, _, TIMER_REPEAT);
g_iDoingMapTouristMode = 0;
g_iActivePlayerCount = 0;
g_bMapended = false;
}
public Action Cmd_enableHelp(int client, int args)
{
PrintToChatAll("ADMIN ENABLED LOW POP HELP");
CreateTimer(1.0, SetHealthOnCT);
return Plugin_Handled;
}
public Action Cmd_disableHelp(int client, int args)
{
PrintToChatAll("ADMIN DISABLED LOW POP HELP");
ServerCommand("sm_iammo @all 0");
ServerCommand("sm_hp @ct 100");
return Plugin_Handled;
}
public Action CheckPopulation(Handle timer)
{
int TimeLeft;
if (GetMapTimeLeft(TimeLeft) && TimeLeft < 0)
{
return Plugin_Continue;
}
if (g_bMapended)
{
g_bMapended = false;
return Plugin_Continue;
}
g_iActivePlayerCount = 0;
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientInGame(i) && IsClientAuthorized(i))
{
g_iActivePlayerCount++;
}
}
return Plugin_Continue;
}
public void OnPluginEnd()
{
ServerCommand("zr_class_set_multiplier zombies knockback 1.0");
g_iDoingMapTouristMode = 0;
g_iActivePlayerCount = 0;
}
public void OnMapEnd()
{
g_iDoingMapTouristMode = 0;
ServerCommand("zr_class_set_multiplier zombies knockback 1.0");
g_bMapended = true;
}
public void OnMapStart()
{
char map[PLATFORM_MAX_PATH];
GetCurrentMap(map, PLATFORM_MAX_PATH);
//skipping de_, cs_, zm_ maps
if (StrContains(map, "ze_") == 0)
{
//LowPopHelpCount
g_iDoingMapTouristMode = GetMapLowPopHelpCount(map);
}
}
public void OnRoundStart(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
ServerCommand("zr_class_set_multiplier zombies knockback 1.0");
ServerCommand("sm_iammo @all 0");
int choosen_number = 35;
if (g_iDoingMapTouristMode > g_iActivePlayerCount > 0)
{
CreateTimer(3.0, SetHealthOnCT);
}
else if (g_iActivePlayerCount < choosen_number)
{
int zombie_ratio = GetConVarInt(FindConVar("zr_infect_mzombie_ratio"));
int infectectable_players = 0;
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientInGame(i) && IsClientAuthorized(i) && GetClientTeam(i) > CS_TEAM_SPECTATOR)
{
infectectable_players++;
}
}
float knockback_increase = float(zombie_ratio) / float(infectectable_players);
if (knockback_increase > 3.0)
knockback_increase = 3.0;
else if(knockback_increase < 1.0)
knockback_increase = 1.0
if (knockback_increase > 1.0)
{
PrintToChatAll("LOW POP: increased zombie knockback to X%.2f to balance gameplay", knockback_increase);
PrintToChatAll("LOW POP: increased zombie knockback to X%.2f to balance gameplay", knockback_increase);
PrintToChatAll("LOW POP: increased zombie knockback to X%.2f to balance gameplay", knockback_increase);
ServerCommand("zr_class_set_multiplier zombies knockback %.2f", knockback_increase);
}
}
}
public Action SetHealthOnCT(Handle timer)
{
PrintToChatAll("LOW POP ON DIFFICULT MAP: ENABLING INFINITE AMMO AND 500 HP FOR CT");
PrintToChatAll("LOW POP ON DIFFICULT MAP: ENABLING INFINITE AMMO AND 500 HP FOR CT");
PrintToChatAll("LOW POP ON DIFFICULT MAP: ENABLING INFINITE AMMO AND 500 HP FOR CT");
ServerCommand("sm_iammo @all 1");
ServerCommand("sm_hp @ct 500");
return Plugin_Handled;
}
+29 -12
View File
@@ -21,7 +21,7 @@ ConVar g_hCvar_DebugMode;
char g_sHUDText[256]; char g_sHUDText[256];
Handle g_aHurtEntityToBoss[4096] = {INVALID_HANDLE, ...}; Handle g_aHurtEntityToBoss[8191] = {INVALID_HANDLE, ...};
public Plugin myinfo = public Plugin myinfo =
{ {
@@ -248,7 +248,8 @@ public void OnMapStart()
if(StrEqual(sMethod, "breakable")) if(StrEqual(sMethod, "breakable"))
{ {
char sBreakable[64]; char sBreakable[64];
if(!KvConfig.GetString("breakable", sBreakable, sizeof(sBreakable))) KvConfig.GetString("breakable", sBreakable, sizeof(sBreakable), "not found");
if(StrEqual(sBreakable, "not found"))
{ {
LogError("Could not find \"breakable\" in \"%s\"", sSection); LogError("Could not find \"breakable\" in \"%s\"", sSection);
continue; continue;
@@ -263,7 +264,8 @@ public void OnMapStart()
else if(StrEqual(sMethod, "counter")) else if(StrEqual(sMethod, "counter"))
{ {
char sCounter[64]; char sCounter[64];
if(!KvConfig.GetString("counter", sCounter, sizeof(sCounter))) KvConfig.GetString("counter", sCounter, sizeof(sCounter), "not found");
if (StrEqual(sCounter, "not found"))
{ {
LogError("Could not find \"counter\" in \"%s\"", sSection); LogError("Could not find \"counter\" in \"%s\"", sSection);
continue; continue;
@@ -278,21 +280,24 @@ public void OnMapStart()
else if(StrEqual(sMethod, "hpbar")) else if(StrEqual(sMethod, "hpbar"))
{ {
char sIterator[64]; char sIterator[64];
if(!KvConfig.GetString("iterator", sIterator, sizeof(sIterator))) KvConfig.GetString("iterator", sIterator, sizeof(sIterator), "not found");
if (StrEqual(sIterator, "not found"))
{ {
LogError("Could not find \"iterator\" in \"%s\"", sSection); LogError("Could not find \"iterator\" in \"%s\"", sSection);
continue; continue;
} }
char sCounter[64]; char sCounter[64];
if(!KvConfig.GetString("counter", sCounter, sizeof(sCounter))) KvConfig.GetString("counter", sCounter, sizeof(sCounter), "not found");
if (StrEqual(sCounter, "not found"))
{ {
LogError("Could not find \"counter\" in \"%s\"", sSection); LogError("Could not find \"counter\" in \"%s\"", sSection);
continue; continue;
} }
char sBackup[64]; char sBackup[64];
if(!KvConfig.GetString("backup", sBackup, sizeof(sBackup))) KvConfig.GetString("backup", sBackup, sizeof(sBackup), "not found");
if (StrEqual(sBackup, "not found"))
{ {
LogError("Could not find \"backup\" in \"%s\"", sSection); LogError("Could not find \"backup\" in \"%s\"", sSection);
continue; continue;
@@ -495,7 +500,7 @@ public void OnEntitySpawned(int entity, const char[] classname)
{ {
char sBoss[64]; char sBoss[64];
Config.GetName(sBoss, sizeof(sBoss)); Config.GetName(sBoss, sizeof(sBoss));
LogDebugMessage("Couldn't find boss \"%s\" for hurttrigger %s:%s", sBoss, sHurtTrigger, sHurtOutput); LogDebugMessage("Couldnt find boss \"%s\" for hurttrigger %s:%s", sBoss, sHurtTrigger, sHurtOutput);
continue; continue;
} }
@@ -931,15 +936,21 @@ public void OnGameFrame()
} }
} }
if(!IsVoteInProgress()) bool voteInProgress = IsVoteInProgress();
{
if(g_sHUDText[0]) if(g_sHUDText[0])
{ {
for(int client = 1; client <= MaxClients; client++) for(int client = 1; client <= MaxClients; client++)
{ {
if(IsClientInGame(client)) if(IsClientInGame(client))
{
if (voteInProgress)
{
PrintCenterText(client, g_sHUDText);
}
else
{ {
PrintHintText(client, g_sHUDText); PrintHintText(client, g_sHUDText);
}
StopSound(client, SNDCHAN_STATIC, "UI/hint.wav"); StopSound(client, SNDCHAN_STATIC, "UI/hint.wav");
} }
} }
@@ -951,13 +962,19 @@ public void OnGameFrame()
{ {
if(IsClientInGame(client)) if(IsClientInGame(client))
{ {
PrintHintText(client, ""); if (voteInProgress)
{
PrintCenterText(client, g_sHUDText);
}
else
{
PrintHintText(client, g_sHUDText);
}
StopSound(client, SNDCHAN_STATIC, "UI/hint.wav"); StopSound(client, SNDCHAN_STATIC, "UI/hint.wav");
} }
} }
s_bLastHudPrinted = false; s_bLastHudPrinted = false;
} }
}
} }
bool BossInit(CBoss _Boss) bool BossInit(CBoss _Boss)
@@ -1415,7 +1432,7 @@ bool BossProcess(CBoss _Boss)
_Boss.iLastChange = iLastChange; _Boss.iLastChange = iLastChange;
} }
// Boss hasn't initialized HP yet. // Boss hasnt initialized HP yet.
if(iHealth == 0 && iLastHealth == 0) if(iHealth == 0 && iLastHealth == 0)
{ {
// Boss invalid: Delete boss // Boss invalid: Delete boss
+16 -4
View File
@@ -1,7 +1,6 @@
#include <sourcemod> #include <sourcemod>
#include <BossHP> #include <BossHP>
#include <zombiereloaded> #include <zombiereloaded>
#include <hlstatsx_loghelper>
ArrayList g_hStats[MAXPLAYERS+1]; ArrayList g_hStats[MAXPLAYERS+1];
@@ -208,19 +207,16 @@ public void OnBossKilled(CBoss Boss, CConfig Config, int reason)
{ {
Format(sBuffer, sizeof(sBuffer), "%s\n1. %N - %d %s", sBuffer, iSortedList[0][0], iSortedList[0][1], sType); Format(sBuffer, sizeof(sBuffer), "%s\n1. %N - %d %s", sBuffer, iSortedList[0][0], iSortedList[0][1], sType);
LH_LogPlayerEvent(iSortedList[0][0], "triggered", "ze_boss_damage_first", true);
} }
if (iSortedList[1][0]) if (iSortedList[1][0])
{ {
Format(sBuffer, sizeof(sBuffer), "%s\n2. %N - %d %s", sBuffer, iSortedList[1][0], iSortedList[1][1], sType); Format(sBuffer, sizeof(sBuffer), "%s\n2. %N - %d %s", sBuffer, iSortedList[1][0], iSortedList[1][1], sType);
LH_LogPlayerEvent(iSortedList[1][0], "triggered", "ze_boss_damage_second", true);
} }
if (iSortedList[2][0]) if (iSortedList[2][0])
{ {
Format(sBuffer, sizeof(sBuffer), "%s\n3. %N - %d %s", sBuffer, iSortedList[2][0], iSortedList[2][1], sType); Format(sBuffer, sizeof(sBuffer), "%s\n3. %N - %d %s", sBuffer, iSortedList[2][0], iSortedList[2][1], sType);
LH_LogPlayerEvent(iSortedList[2][0], "triggered", "ze_boss_damage_third", true);
} }
Format(sBuffer, sizeof(sBuffer), "%s\n*************************", sBuffer); Format(sBuffer, sizeof(sBuffer), "%s\n*************************", sBuffer);
@@ -280,6 +276,22 @@ public void OnBossKilled(CBoss Boss, CConfig Config, int reason)
} }
else else
PrintToChatAll(sBuffer); PrintToChatAll(sBuffer);
for (int i = 0; i <= MaxClients; i++)
{
if (iSortedList[i][0])
{
char sBuffer_rest[512];
Format(sBuffer_rest, sizeof(sBuffer_rest), "%s\n%i. %N - %d %s", sBuffer_rest, i, iSortedList[i][0], iSortedList[i][1], sType);
PrintToConsoleAll(sBuffer_rest);
}
else if (IsValidClient(i))
{
char sBuffer_rest[512];
Format(sBuffer_rest, sizeof(sBuffer_rest), "%s\n-1. %N - Did not shoot.", sBuffer_rest, i);
PrintToConsoleAll(sBuffer_rest);
}
}
} }
} }
} }
@@ -0,0 +1,340 @@
#include <sourcemod>
#include <BossHP>
#include <zombiereloaded>
ArrayList g_hStats[MAXPLAYERS+1];
bool g_bZRLoaded;
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Plugin myinfo =
{
name = "BossHP - Ranking",
author = "Neon & zaCade",
description = "",
version = "1.0.0",
};
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
HookEvent("player_death", EventHook_PlayerDeath, EventHookMode_Pre);
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client))
continue;
OnClientPutInServer(client);
}
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnAllPluginsLoaded()
{
g_bZRLoaded = LibraryExists("zombiereloaded");
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnLibraryAdded(const char[] sName)
{
if (strcmp(sName, "zombiereloaded", false) == 0)
g_bZRLoaded = true;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnLibraryRemoved(const char[] sName)
{
if (strcmp(sName, "zombiereloaded", false) == 0)
g_bZRLoaded = false;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientPutInServer(int client)
{
g_hStats[client] = new ArrayList(64);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientDisconnect(int client)
{
delete g_hStats[client];
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnBossDamaged(CBoss Boss, CConfig Config, int client, float damage)
{
if (!IsValidClient(client))
return;
int iMaxCash = GetConVarInt(FindConVar("sm_maxcash"));
if(!(GetEntProp(client, Prop_Send, "m_iAccount") >= iMaxCash))
SetEntProp(client, Prop_Send, "m_iAccount", GetEntProp(client, Prop_Send, "m_iAccount") + 2);
bool bBreakable;
CConfig _Config = view_as<CConfig>(Config);
if (_Config.IsBreakable)
bBreakable = true;
else
bBreakable = false;
delete _Config;
for (int index = 0; index < g_hStats[client].Length; index++)
{
int BossDamage[2];
g_hStats[client].GetArray(index, BossDamage, sizeof(BossDamage));
if (BossDamage[0] == view_as<int>(Boss))
{
if (bBreakable)
BossDamage[1] += RoundToNearest(damage);
else
BossDamage[1] += 1;
g_hStats[client].SetArray(index, BossDamage, sizeof(BossDamage));
return;
}
}
int BossDamage[2];
BossDamage[0] = view_as<int>(Boss);
if (bBreakable)
BossDamage[1] = RoundToNearest(damage);
else
BossDamage[1] = 1;
g_hStats[client].PushArray(BossDamage, sizeof(BossDamage));
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnBossKilled(CBoss Boss, CConfig Config, int reason)
{
if (reason == 0)
{
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client))
continue;
for (int index = 0; index < g_hStats[client].Length; index++)
{
int BossDamage[2];
g_hStats[client].GetArray(index, BossDamage, sizeof(BossDamage));
if (BossDamage[0] == view_as<int>(Boss))
{
g_hStats[client].Erase(index);
break;
}
}
}
}
else
{
int iSortedList[MAXPLAYERS+1][2];
int iSortedCount;
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client))
continue;
for (int index = 0; index < g_hStats[client].Length; index++)
{
int BossDamage[2];
g_hStats[client].GetArray(index, BossDamage, sizeof(BossDamage));
if (BossDamage[0] == view_as<int>(Boss))
{
iSortedList[iSortedCount][0] = client;
iSortedList[iSortedCount][1] = BossDamage[1];
iSortedCount++;
g_hStats[client].Erase(index);
break;
}
}
}
SortCustom2D(iSortedList, iSortedCount, SortBossHitsList);
if (iSortedCount)
{
CConfig _Config = view_as<CConfig>(Config);
char sBossName[64];
_Config.GetName(sBossName, sizeof(sBossName));
char sBuffer[512];
char sType[16];
if (_Config.IsBreakable)
{
sType = "damage"
Format(sBuffer, sizeof(sBuffer), "BOSS DAMAGE [%s]:", sBossName);
}
else
{
sType = "hits"
Format(sBuffer, sizeof(sBuffer), "BOSS HITS [%s]:", sBossName);
}
delete _Config;
Format(sBuffer, sizeof(sBuffer), "%s\n*************************", sBuffer);
if (iSortedList[0][0])
{
Format(sBuffer, sizeof(sBuffer), "%s\n1. %N - %d %s", sBuffer, iSortedList[0][0], iSortedList[0][1], sType);
}
if (iSortedList[1][0])
{
Format(sBuffer, sizeof(sBuffer), "%s\n2. %N - %d %s", sBuffer, iSortedList[1][0], iSortedList[1][1], sType);
}
if (iSortedList[2][0])
{
Format(sBuffer, sizeof(sBuffer), "%s\n3. %N - %d %s", sBuffer, iSortedList[2][0], iSortedList[2][1], sType);
}
Format(sBuffer, sizeof(sBuffer), "%s\n*************************", sBuffer);
Handle hMessage = StartMessageAll("HudMsg");
if (hMessage)
{
if (GetUserMessageType() == UM_Protobuf)
{
PbSetInt(hMessage, "channel", 50);
PbSetInt(hMessage, "effect", 0);
PbSetColor(hMessage, "clr1", {255, 255, 255, 255});
PbSetColor(hMessage, "clr2", {255, 255, 255, 255});
PbSetVector2D(hMessage, "pos", Float:{0.02, 0.45});
PbSetFloat(hMessage, "fade_in_time", 0.1);
PbSetFloat(hMessage, "fade_out_time", 0.1);
PbSetFloat(hMessage, "hold_time", 7.0);
PbSetFloat(hMessage, "fx_time", 0.0);
PbSetString(hMessage, "text", sBuffer);
EndMessage();
}
else
{
BfWriteByte(hMessage, 50);
BfWriteFloat(hMessage, 0.02);
BfWriteFloat(hMessage, 0.25);
BfWriteByte(hMessage, 255);
BfWriteByte(hMessage, 255);
BfWriteByte(hMessage, 0);
BfWriteByte(hMessage, 255);
BfWriteByte(hMessage, 255);
BfWriteByte(hMessage, 255);
BfWriteByte(hMessage, 255);
BfWriteByte(hMessage, 255);
BfWriteByte(hMessage, 0);
BfWriteFloat(hMessage, 0.1);
BfWriteFloat(hMessage, 0.1);
BfWriteFloat(hMessage, 7.0);
BfWriteFloat(hMessage, 0.0);
BfWriteString(hMessage, sBuffer);
EndMessage();
}
}
if(GetEngineVersion() == Engine_CSGO)
{
int iSplits
char sSplits[16][512];
if((iSplits = ExplodeString(sBuffer, "\n", sSplits, sizeof(sSplits), sizeof(sSplits[]))) != 0)
{
for (int iSplit; iSplit < iSplits; iSplit++)
{
PrintToChatAll(sSplits[iSplit]);
}
}
}
else
PrintToChatAll(sBuffer);
}
}
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action EventHook_PlayerDeath(Event hEvent, const char[] sEventName, bool bDontBroadcast)
{
if (!g_bZRLoaded)
return Plugin_Continue;
int iClient = GetClientOfUserId(hEvent.GetInt("userid"));
if (!IsValidClient(iClient))
return Plugin_Continue;
if (IsPlayerAlive(iClient) && !ZR_IsClientHuman(iClient))
return Plugin_Continue;
int iPacked = (iClient<<16) | (GetEntProp(iClient, Prop_Send, "m_iAccount")&0xFFFF);
RequestFrame(RequestFrame_Callback, iPacked);
return Plugin_Continue;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
void RequestFrame_Callback(int iPacked)
{
int iOldCash = iPacked&0xFFFF;
int iClient = iPacked>>16;
if (!IsValidEntity(iClient) || !IsValidClient(iClient))
return;
SetEntProp(iClient, Prop_Send, "m_iAccount", iOldCash);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int SortBossHitsList(int[] elem1, int[] elem2, const int[][] array, Handle hndl)
{
if (elem1[1] > elem2[1]) return -1;
if (elem1[1] < elem2[1]) return 1;
return 0;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock int IsValidClient(int client, bool nobots = true)
{
if (client <= 0 || client > MaxClients || !IsClientConnected(client) || (nobots && IsFakeClient(client)))
return false;
return IsClientInGame(client);
}
+6
View File
@@ -0,0 +1,6 @@
this is used for the voice extension:
https://git.unloze.com/UNLOZE/sm-ext-Voice/src/branch/CeltOpusTranscoder_2026
//the voice chat is now transcoded so both celt and opus work by sending each stream to the player that needs it.
the include belongs to the voice extension but is also put here extra
+35
View File
@@ -0,0 +1,35 @@
#if defined _voice_included
#endinput
#endif
#define _voice_included
native bool IsClientTalking(int client);
native bool SetClientNoSteam(int client, bool isNosteam);
native bool SendCeltVoiceInit(int client);
native bool ClientMutedOtherClient(int client, int otherclient, bool muted);
/**
* Do not edit below this line!
*/
public Extension __ext_voice =
{
name = "Voice",
file = "Voice.ext",
#if defined AUTOLOAD_EXTENSIONS
autoload = 1,
#else
autoload = 0,
#endif
#if defined REQUIRE_EXTENSIONS
required = 1,
#else
required = 0,
#endif
};
#if !defined REQUIRE_EXTENSIONS
public __ext_voice_SetNTVOptional()
{
MarkNativeAsOptional("IsClientTalking");
}
#endif
+357
View File
@@ -0,0 +1,357 @@
#include <sourcemod>
#include <PlayerManager>
#include <sdktools>
#include <basecomm>
#include <ccc>
#include <clientprefs>
#include <ripext>
#tryinclude <zombiereloaded>
#include <selfmute>
#include <connect>
#include <voice>
#pragma semicolon 1
#pragma newdecls required
Handle g_hCheckMutes = null;
Handle g_hCookieTorchMuted = null;
bool g_bWebclient[MAXPLAYERS + 1];
bool g_bReRunWebClientName;
bool g_bSkipInfection = false;
char g_sLastWebclientIp[64];
int g_iHttpFileCounter;
public Plugin myinfo =
{
name = "NoSteam CELT Voice override",
author = "jenz",
version = "1.5"
};
public void OnPluginStart()
{
Format(g_sLastWebclientIp, sizeof(g_sLastWebclientIp), "");
g_hCheckMutes = CreateTimer(2.0, check_mutes, _, TIMER_REPEAT);
g_hCookieTorchMuted = RegClientCookie("torch_muted", "is torch muted", CookieAccess_Protected);
}
public void OnPluginEnd()
{
if (g_hCheckMutes != null)
{
delete g_hCheckMutes;
}
}
public void set_nosteam_listen_override(int client)
{
for (int j = 1; j <= MaxClients; j++)
{
if (!IsValidClient(j) || client == j || IsFakeClient(j))
continue;
if (PM_IsPlayerSteam(j))
{
//the nosteam client is not allowed to hear the steam client because broadcastvoicedata would ear rape them.
//LogMessage("%N has muted %N", client, j);
SetListenOverride(client, j, Listen_No);
}
}
}
public Action check_mutes(Handle timer, any data)
{
for (int i = 1; i <= MaxClients; i++)
{
if (!IsValidClient(i) || IsFakeClient(i))
{
continue;
}
if (!PM_IsPlayerSteam(i))
{
set_nosteam_listen_override(i);
}
else
{
//maybe this is a fix? maybe not.
clear_steam_listen_override(i);
}
for (int j = 1; j <= MaxClients; j++)
{
if (!IsValidClient(j) || i == j || IsFakeClient(j))
{
continue;
}
bool isMuted = BaseComm_IsClientMuted(j);
if (!isMuted)
{
isMuted = SelfMute_IsIgnoring(i, j);
}
//PrintToChatAll("i: %N. j: %N. isMuted: %i", i, j, isMuted);
ClientMutedOtherClient(i, j, isMuted);
}
if (g_bReRunWebClientName && g_bWebclient[i])
{
char dummy[64];
OnClientAuthorized(i, dummy);
g_bReRunWebClientName = false;
}
}
//sourceTV special case, index 0.
for (int j = 1; j <= MaxClients; j++)
{
if (!IsValidClient(j) || IsFakeClient(j))
{
continue;
}
//yeah its monkey work but server can probably handle it fine.
char sBuffer[2];
GetClientCookie(j, g_hCookieTorchMuted, sBuffer, sizeof(sBuffer));
//PrintToChatAll("sBuffer: %s. j: %N", sBuffer, j);
if(sBuffer[0] != '\0')
{
ClientMutedOtherClient(j, 0, true);
}
else
{
ClientMutedOtherClient(j, 0, false);
}
}
//check if webclient has new IP every 2 seconds through endpoint.
PollWebclientCurrentIp();
return Plugin_Continue;
}
public void clear_steam_listen_override(int client)
{
for (int j = 1; j <= MaxClients; j++)
{
if (!IsValidClient(j) || client == j || IsFakeClient(j))
continue;
if (PM_IsPlayerSteam(j) && !BaseComm_IsClientMuted(j) && !SelfMute_IsIgnoring(client, j))
{
SetListenOverride(client, j, Listen_Default);
}
}
}
//AI slop
public void SendWebclientNameToSignaling(const char[] webclientName)
{
char url[256];
// Adjust port/path to match your signaling servers actual endpoint
FormatEx(url, sizeof(url), "http://127.0.0.1:3000/webclient-name");
HTTPRequest hRequest = new HTTPRequest(url);
// Send the name as a POST body parameter
hRequest.AppendFormParam("name", "%s", webclientName);
hRequest.PostForm(OnWebclientNamePosted);
}
public void OnWebclientNamePosted(HTTPResponse response, any value)
{
// Fire-and-forget: we dont care about the response. ripext closes the
// request handle for us once this callback returns.
}
public void OnMapStart()
{
g_bReRunWebClientName = true;
}
public void OnClientDisconnect(int client)
{
g_bWebclient[client] = false;
}
public void OnClientPostAdminCheck(int client)
{
g_bWebclient[client] = false;
if (IsFakeClient(client))
{
return;
}
char sIP[32];
GetClientIP(client, sIP, sizeof(sIP));
char allowed_ips[128];
ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips");
if (sv_set_steam_id_ips != null)
{
sv_set_steam_id_ips.GetString(allowed_ips, sizeof(allowed_ips));
if (StrEqual(sIP, allowed_ips))
{
g_bWebclient[client] = true;
g_bSkipInfection = GetConVarInt(FindConVar("zr_infect_mzombie_ratio")) > 10 ? true : false;
//this works fine with one webclient only existing.
char webclientName[64];
GetConVarString(FindConVar("sv_webclient_name"), webclientName, sizeof(webclientName));
SetClientName(client, webclientName);
SendWebclientNameToSignaling(webclientName);
}
}
}
public void OnClientAuthorized(int client, const char[] auth)
{
if (IsFakeClient(client))
{
return;
}
SetClientNoSteam(client, false);
if (!PM_IsPlayerSteam(client))
{
SetClientNoSteam(client, true);
//LogMessage("here in onauthorized client: %N", client);
//setting it too early does not work, so some delay needed.
CreateTimer(10.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(20.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(30.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(40.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(50.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(60.0, Timer_SendVoiceInit, GetClientSerial(client));
}
}
public Action CCC_OnChatMessage(int client, int author, const char[] message)
{
if (!g_bWebclient[client])
{
return Plugin_Continue;
}
int index = FindCharInString(message, '"', false);
int index1 = FindCharInString(message, ';', false);
if (index != -1 || index1 != -1)
{
return Plugin_Handled;
}
return Plugin_Continue;
}
#if defined _zr_included
public Action ZR_OnClientMotherZombieEligible(int client)
{
if (g_bWebclient[client] && g_bSkipInfection)
{
return Plugin_Handled;
}
return Plugin_Continue;
}
#endif
public Action Timer_SendVoiceInit(Handle timer, int Serial)
{
int client;
if ((client = GetClientFromSerial(Serial)) == 0)
{
return Plugin_Handled;
}
if (IsValidClient(client) && !PM_IsPlayerSteam(client))
{
SendCeltVoiceInit(client);
}
return Plugin_Handled;
}
/**
* Polls the signaling server for the current webclient controller's real IP.
* If the IP has changed since the last
* check and a webclient player is currently connected, forces them to
* reconnect with the new password via ClientCommand.
*
* ripext's HTTPResponse only exposes the body as decoded JSON, unlike
* SteamWorks' GetHTTPResponseBodyCallback which handed back a plain char[].
* Rather than change the signaling server's response format, we use
* HTTPRequest.DownloadFile() to write the raw body to a temp file and read
* it back as plain text, keeping the endpoint untouched.
**/
public void PollWebclientCurrentIp()
{
g_iHttpFileCounter++;
char sPath[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sPath, sizeof(sPath), "data/nosteamcelt_ip_%d.tmp", g_iHttpFileCounter);
HTTPRequest hRequest = new HTTPRequest("http://127.0.0.1:3000/webclient-current-ip");
hRequest.DownloadFile(sPath, OnWebclientCurrentIpDownloaded, g_iHttpFileCounter);
}
public void OnWebclientCurrentIpDownloaded(HTTPStatus status, any value)
{
char sPath[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sPath, sizeof(sPath), "data/nosteamcelt_ip_%d.tmp", value);
if (status != HTTPStatus_OK)
{
DeleteFile(sPath);
return;
}
char newIp[64];
newIp[0] = '\0';
File hFile = OpenFile(sPath, "r");
if (hFile != null)
{
hFile.ReadLine(newIp, sizeof(newIp));
delete hFile;
}
DeleteFile(sPath);
TrimString(newIp);
if (newIp[0] == '\0')
return;
if (StrEqual(newIp, g_sLastWebclientIp))
return;
for (int i = 1; i <= MaxClients; i++)
{
if (IsValidClient(i) && g_bWebclient[i])
{
strcopy(g_sLastWebclientIp, sizeof(g_sLastWebclientIp), newIp);
LogMessage("Forcing webclient reconnect for client %N with new IP: %s", i, newIp);
ClientCommand(i, "retry");
break;
}
}
}
public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255])
{
char allowedIp[64];
ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips");
if (sv_set_steam_id_ips != null)
{
sv_set_steam_id_ips.GetString(allowedIp, sizeof(allowedIp));
//this is to handle that the connect extension receives the password parameter for the webclient as its real connecting IP.
//works both for the client command retry and for the case that somebody presses the connect button in the clients Menu as the same password
//is applied again.
if (StrEqual(sIP, allowedIp) && strlen(g_sLastWebclientIp) > 0)
{
strcopy(sPassword, 255, g_sLastWebclientIp);
LogMessage("Backfilled empty password for webclient with cached IP: %s", g_sLastWebclientIp);
}
}
// ET_LowEvent takes the LOWEST value returned across all plugins hooking this forward.
//this plugin returns 1 (Accept), meanwhile the reserved slot plugin still might return 0 (reject) or -1 (async) to overrule the decision from this return value.
return k_OnClientPreConnectEx_Accept;
}
stock bool IsValidClient(int client)
{
if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
return true;
return false;
}
@@ -0,0 +1,333 @@
#include <sourcemod>
#include <PlayerManager>
#include <sdktools>
#include <basecomm>
#include <ccc>
#include <clientprefs>
#include <SteamWorks>
#include <selfmute>
#include <connect>
#include <voice>
#pragma semicolon 1
#pragma newdecls required
Handle g_hCheckMutes = null;
Handle g_hCookieTorchMuted = null;
bool g_bWebclient[MAXPLAYERS + 1];
bool g_bReRunWebClientName;
char g_sLastWebclientIp[64];
public Plugin myinfo =
{
name = "NoSteam CELT Voice override",
author = "jenz",
version = "1.5"
};
public void OnPluginStart()
{
Format(g_sLastWebclientIp, sizeof(g_sLastWebclientIp), "");
g_hCheckMutes = CreateTimer(2.0, check_mutes, _, TIMER_REPEAT);
g_hCookieTorchMuted = RegClientCookie("torch_muted", "is torch muted", CookieAccess_Protected);
}
public void OnPluginEnd()
{
if (g_hCheckMutes != null)
{
delete g_hCheckMutes;
}
}
public void set_nosteam_listen_override(int client)
{
for (int j = 1; j <= MaxClients; j++)
{
if (!IsValidClient(j) || client == j || IsFakeClient(j))
continue;
if (PM_IsPlayerSteam(j))
{
//the nosteam client is not allowed to hear the steam client because broadcastvoicedata would ear rape them.
//LogMessage("%N has muted %N", client, j);
SetListenOverride(client, j, Listen_No);
}
}
}
public Action check_mutes(Handle timer, any data)
{
for (int i = 1; i <= MaxClients; i++)
{
if (!IsValidClient(i) || IsFakeClient(i))
{
continue;
}
if (!PM_IsPlayerSteam(i))
{
set_nosteam_listen_override(i);
}
for (int j = 1; j <= MaxClients; j++)
{
if (!IsValidClient(j) || i == j || IsFakeClient(j))
{
continue;
}
bool isMuted = BaseComm_IsClientMuted(j);
if (!isMuted)
{
isMuted = SelfMute_IsIgnoring(i, j);
}
//PrintToChatAll("i: %N. j: %N. isMuted: %i", i, j, isMuted);
ClientMutedOtherClient(i, j, isMuted);
}
if (g_bReRunWebClientName && g_bWebclient[i])
{
char dummy[64];
OnClientAuthorized(i, dummy);
g_bReRunWebClientName = false;
}
}
//sourceTV special case, index 0.
for (int j = 1; j <= MaxClients; j++)
{
if (!IsValidClient(j) || IsFakeClient(j))
{
continue;
}
//yeah its monkey work but server can probably handle it fine.
char sBuffer[2];
GetClientCookie(j, g_hCookieTorchMuted, sBuffer, sizeof(sBuffer));
//PrintToChatAll("sBuffer: %s. j: %N", sBuffer, j);
if(sBuffer[0] != '\0')
{
ClientMutedOtherClient(j, 0, true);
}
else
{
ClientMutedOtherClient(j, 0, false);
}
}
//check if webclient has new IP every 2 seconds through endpoint.
PollWebclientCurrentIp();
return Plugin_Continue;
}
//AI slop
public void SendWebclientNameToSignaling(const char[] webclientName)
{
char url[256];
// Adjust port/path to match your signaling servers actual endpoint
FormatEx(url, sizeof(url), "http://127.0.0.1:3000/webclient-name");
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, url);
if (hRequest == null)
{
return;
}
// Send the name as a POST body parameter
SteamWorks_SetHTTPRequestGetOrPostParameter(hRequest, "name", webclientName);
if (!SteamWorks_SetHTTPCallbacks(hRequest, OnWebclientNamePosted) ||
!SteamWorks_SendHTTPRequest(hRequest))
{
delete hRequest;
}
}
public int OnWebclientNamePosted(Handle hRequest, bool bFailure, bool bRequestSuccessful,
EHTTPStatusCode eStatusCode, any data)
{
// Fire-and-forget: we dont care about the response, just clean up.
delete hRequest;
return 0;
}
public void OnMapStart()
{
g_bReRunWebClientName = true;
}
public void OnClientDisconnect(int client)
{
g_bWebclient[client] = false;
}
public void OnClientPostAdminCheck(int client)
{
g_bWebclient[client] = false;
if (IsFakeClient(client))
{
return;
}
char sIP[32];
GetClientIP(client, sIP, sizeof(sIP));
char allowed_ips[128];
ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips");
if (sv_set_steam_id_ips != null)
{
sv_set_steam_id_ips.GetString(allowed_ips, sizeof(allowed_ips));
if (StrEqual(sIP, allowed_ips))
{
g_bWebclient[client] = true;
//this works fine with one webclient only existing.
char webclientName[64];
GetConVarString(FindConVar("sv_webclient_name"), webclientName, sizeof(webclientName));
SetClientName(client, webclientName);
SendWebclientNameToSignaling(webclientName);
}
}
}
public void OnClientAuthorized(int client, const char[] auth)
{
if (IsFakeClient(client))
{
return;
}
SetClientNoSteam(client, false);
if (!PM_IsPlayerSteam(client))
{
SetClientNoSteam(client, true);
//LogMessage("here in onauthorized client: %N", client);
//setting it too early does not work, so some delay needed.
CreateTimer(10.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(20.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(30.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(40.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(50.0, Timer_SendVoiceInit, GetClientSerial(client));
CreateTimer(60.0, Timer_SendVoiceInit, GetClientSerial(client));
}
}
public Action CCC_OnChatMessage(int client, int author, const char[] message)
{
if (!g_bWebclient[client])
{
return Plugin_Continue;
}
int index = FindCharInString(message, '"', false);
int index1 = FindCharInString(message, ';', false);
if (index != -1 || index1 != -1)
{
return Plugin_Handled;
}
return Plugin_Continue;
}
public Action Timer_SendVoiceInit(Handle timer, int Serial)
{
int client;
if ((client = GetClientFromSerial(Serial)) == 0)
{
return Plugin_Handled;
}
if (IsValidClient(client))
{
SendCeltVoiceInit(client);
}
return Plugin_Handled;
}
/**
* Polls the signaling server for the current webclient controller's real IP.
* If the IP has changed since the last
* check and a webclient player is currently connected, forces them to
* reconnect with the new password via ClientCommand.
**/
public void PollWebclientCurrentIp()
{
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, "http://127.0.0.1:3000/webclient-current-ip");
if (hRequest == null)
{
return;
}
if (!SteamWorks_SetHTTPCallbacks(hRequest, OnWebclientCurrentIpReceived) ||
!SteamWorks_SendHTTPRequest(hRequest))
{
delete hRequest;
}
}
public int OnWebclientCurrentIpReceived(Handle hRequest, bool bFailure, bool bRequestSuccessful,
EHTTPStatusCode eStatusCode, any data)
{
if (bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK)
{
delete hRequest;
return 0;
}
int bodySize;
bool gotSize = SteamWorks_GetHTTPResponseBodySize(hRequest, bodySize);
char newIp[64];
newIp[0] = '\0';
if (gotSize && bodySize > 0 && bodySize < sizeof(newIp))
{
SteamWorks_GetHTTPResponseBodyData(hRequest, newIp, bodySize);
}
delete hRequest;
if (newIp[0] == '\0')
{
return 0;
}
if (StrEqual(newIp, g_sLastWebclientIp))
{
return 0;
}
for (int i = 1; i <= MaxClients; i++)
{
if (IsValidClient(i) && g_bWebclient[i])
{
strcopy(g_sLastWebclientIp, sizeof(g_sLastWebclientIp), newIp);
LogMessage("Forcing webclient reconnect for client %N with new IP: %s", i, newIp);
ClientCommand(i, "retry");
break;
}
}
return 0;
}
public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255])
{
char allowedIp[64];
ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips");
if (sv_set_steam_id_ips != null)
{
sv_set_steam_id_ips.GetString(allowedIp, sizeof(allowedIp));
//this is to handle that the connect extension receives the password parameter for the webclient as its real connecting IP.
//works both for the client command retry and for the case that somebody presses the connect button in the clients Menu as the same password
//is applied again.
if (StrEqual(sIP, allowedIp) && strlen(g_sLastWebclientIp) > 0)
{
strcopy(sPassword, 255, g_sLastWebclientIp);
LogMessage("Backfilled empty password for webclient with cached IP: %s", g_sLastWebclientIp);
}
}
// ET_LowEvent takes the LOWEST value returned across all plugins hooking this forward.
//this plugin returns 1 (Accept), meanwhile the reserved slot plugin still might return 0 (reject) or -1 (async) to overrule the decision from this return value.
return k_OnClientPreConnectEx_Accept;
}
stock bool IsValidClient(int client)
{
if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
return true;
return false;
}
@@ -0,0 +1 @@
{BLUE}{PLAYERTIER} {WHITE}{PLAYERTYPE} {GREEN}{NAME} {green}[{lightgreen}{STEAMID}{NOSTEAM}{green}] connected{COUNTRY}
+200 -1
View File
@@ -5,6 +5,7 @@
#include <multicolors> #include <multicolors>
#include <clientprefs> #include <clientprefs>
#include <PlayerManager> #include <PlayerManager>
#include <unloze_playtime>
#pragma newdecls required #pragma newdecls required
@@ -16,6 +17,8 @@ Database g_hDatabase;
Handle g_hCustomMessageFile; Handle g_hCustomMessageFile;
Handle g_hCustomMessageFile2; Handle g_hCustomMessageFile2;
static Handle g_hForwardConnectMessage;
bool g_bHideCsays[MAXPLAYERS + 1] = { false, ... }; bool g_bHideCsays[MAXPLAYERS + 1] = { false, ... };
Handle g_hCookieHideCsays = null; Handle g_hCookieHideCsays = null;
@@ -71,6 +74,17 @@ public void OnDatabaseConnect(Database db, const char[] error, any data)
g_hDatabase = db; g_hDatabase = db;
} }
public APLRes AskPluginLoad2(Handle myself, bool late, char [] error, int err_max)
{
g_hForwardConnectMessage = CreateGlobalForward("GetPlayerConnectMessage", ET_Ignore, Param_String);
return APLRes_Success;
}
public void OnPluginEnd()
{
CloseHandle(g_hForwardConnectMessage);
}
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -175,6 +189,7 @@ public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int se
delete menu; delete menu;
} }
} }
return 0;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -290,6 +305,189 @@ public Action Command_ResetJoinMsg(int client, int args)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void GetPlayerTier(int client, int tier)
{
Handle hFile = OpenFile(g_sDataFile, "r");
static char sRawMsg[301];
if(hFile != INVALID_HANDLE)
{
ReadFileLine(hFile, sRawMsg, sizeof(sRawMsg));
TrimString(sRawMsg);
delete hFile;
}
else
{
LogError("[SM] File not found! (configs/ConnectAnnounce/settings.txt)");
return;
}
static char sIP[16];
static char sAuth[32];
static char sCountry[32];
static char sName[128];
GetClientAuthId(client, AuthId_Steam2, sAuth, sizeof(sAuth));
GetClientName(client, sName, sizeof(sName));
AdminId aid;
if(StrContains(sRawMsg, "{PLAYERTIER}"))
{
char s_tier[64];
Format(s_tier, sizeof(s_tier), "Tier %i", tier);
ReplaceString(sRawMsg, sizeof(sRawMsg), "{PLAYERTIER}", s_tier);
}
if(StrContains(sRawMsg, "{PLAYERTYPE}"))
{
aid = GetUserAdmin(client);
if(GetAdminFlag(aid, Admin_Cheats) || GetAdminFlag(aid, Admin_Root))
{
ReplaceString(sRawMsg, sizeof(sRawMsg), "{PLAYERTYPE}", "Root-Admin");
}
else if(GetAdminFlag(aid, Admin_Generic))
{
ReplaceString(sRawMsg, sizeof(sRawMsg), "{PLAYERTYPE}", "Admin");
}
else if(GetAdminFlag(aid, Admin_Custom1))
{
ReplaceString(sRawMsg, sizeof(sRawMsg), "{PLAYERTYPE}", "VIP");
}
/*
else if(GetAdminFlag(aid, Admin_Custom6))
{
ReplaceString(sRawMsg, sizeof(sRawMsg), "{PLAYERTYPE}", "Member");
}
*/
else
{
ReplaceString(sRawMsg, sizeof(sRawMsg), "{PLAYERTYPE}", "Player");
}
}
if(StrContains(sRawMsg, "{NOSTEAM}"))
{
if(!PM_IsPlayerSteam(client))
ReplaceString(sRawMsg, sizeof(sRawMsg), "{NOSTEAM}", " <NoSteam>");
else
ReplaceString(sRawMsg, sizeof(sRawMsg), "{NOSTEAM}", "");
}
if(StrContains(sRawMsg, "{STEAMID}"))
{
ReplaceString(sRawMsg, sizeof(sRawMsg), "{STEAMID}", sAuth);
}
if(StrContains(sRawMsg, "{NAME}"))
{
ReplaceString(sRawMsg, sizeof(sRawMsg), "{NAME}", sName);
}
if(StrContains(sRawMsg, "{COUNTRY}"))
{
if(GetClientIP(client, sIP, sizeof(sIP)) && GeoipCountry(sIP, sCountry, sizeof(sCountry)))
{
char sBuffer[128];
Format(sBuffer, sizeof(sBuffer), " from %s", sCountry);
ReplaceString(sRawMsg, sizeof(sRawMsg), "{COUNTRY}", sBuffer);
}
else
ReplaceString(sRawMsg, sizeof(sRawMsg), "{COUNTRY}", "");
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (!CheckCommandAccess(client, "sm_joinmsg", ADMFLAG_CUSTOM1))
{
CPrintToChatAll(sRawMsg);
Call_StartForward(g_hForwardConnectMessage);
Call_PushString(sRawMsg);
Call_Finish();
return;
}
delete g_hCustomMessageFile2;
g_hCustomMessageFile2 = CreateKeyValues("custom_messages");
if (!FileToKeyValues(g_hCustomMessageFile2, g_sCustomMessageFile))
{
SetFailState("[ConnectAnnounce] Config file missing!");
return;
}
KvRewind(g_hCustomMessageFile2);
char sBanned[16];
char sFinalMessage[512];
char sCustomMessage[256];
if (KvJumpToKey(g_hCustomMessageFile2, sAuth))
{
KvGetString(g_hCustomMessageFile2, "banned", sBanned, sizeof(sBanned), "");
KvGetString(g_hCustomMessageFile2, "message", sCustomMessage, sizeof(sCustomMessage), "");
if(StrEqual(sCustomMessage, "reset") || StrEqual(sBanned, "true"))
{
CPrintToChatAll(sRawMsg);
Call_StartForward(g_hForwardConnectMessage);
Call_PushString(sRawMsg);
Call_Finish();
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i))
{
if(g_bHideCsays[i])
continue;
PrintCenterText(i, "%s %s %s", "[VIP]", sName, "connected");
}
}
}
else
{
Format(sFinalMessage, sizeof(sFinalMessage), "%s %s", sRawMsg, sCustomMessage);
CPrintToChatAll(sFinalMessage);
Call_StartForward(g_hForwardConnectMessage);
Call_PushString(sRawMsg);
Call_Finish();
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i))
{
if(g_bHideCsays[i])
continue;
CRemoveTags(sCustomMessage, 256);
PrintCenterText(i, "%s %s %s %s", "[VIP]", sName, "connected", sCustomMessage);
}
}
}
}
else
{
CPrintToChatAll(sRawMsg);
Call_StartForward(g_hForwardConnectMessage);
Call_PushString(sRawMsg);
Call_Finish();
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i))
{
if(g_bHideCsays[i])
continue;
PrintCenterText(i, "%s %s %s", "[VIP]", sName, "connected");
}
}
}
}
public void TQueryCB(Handle owner, Handle rs, const char[] error, any data) public void TQueryCB(Handle owner, Handle rs, const char[] error, any data)
{ {
int client = 0; int client = 0;
@@ -338,7 +536,6 @@ public void TQueryCB(Handle owner, Handle rs, const char[] error, any data)
if(StrContains(sRawMsg, "{PLAYERTYPE}")) if(StrContains(sRawMsg, "{PLAYERTYPE}"))
{ {
aid = GetUserAdmin(client); aid = GetUserAdmin(client);
if(GetAdminFlag(aid, Admin_Generic)) if(GetAdminFlag(aid, Admin_Generic))
@@ -492,7 +689,9 @@ public void OnClientPostAdminCheck(int client)
char sServer[16]; char sServer[16];
g_cvHlstatsServerName.GetString(sServer, sizeof(sServer)); g_cvHlstatsServerName.GetString(sServer, sizeof(sServer));
/*
char sQuery[512]; char sQuery[512];
Format(sQuery, sizeof(sQuery), "SELECT COUNT(*) AS 'rank' FROM hlstats_Players WHERE hlstats_Players.game = '%s' AND hideranking = 0 AND skill > (SELECT skill from hlstats_Players JOIN hlstats_PlayerUniqueIds ON hlstats_Players.playerId = hlstats_PlayerUniqueIds.playerId WHERE uniqueId = '%s' AND hlstats_PlayerUniqueIds.game = '%s')", sServer, sAuth, sServer); Format(sQuery, sizeof(sQuery), "SELECT COUNT(*) AS 'rank' FROM hlstats_Players WHERE hlstats_Players.game = '%s' AND hideranking = 0 AND skill > (SELECT skill from hlstats_Players JOIN hlstats_PlayerUniqueIds ON hlstats_Players.playerId = hlstats_PlayerUniqueIds.playerId WHERE uniqueId = '%s' AND hlstats_PlayerUniqueIds.game = '%s')", sServer, sAuth, sServer);
g_hDatabase.Query(TQueryCB, sQuery, GetClientUserId(client), DBPrio_High); g_hDatabase.Query(TQueryCB, sQuery, GetClientUserId(client), DBPrio_High);
*/
} }
@@ -0,0 +1,6 @@
#if defined _connect_announce_included_
#endinput
#endif
#define _connect_announce_included_
forward void GetPlayerConnectMessage(char[] message);
+41
View File
@@ -0,0 +1,41 @@
"Games"
{
"cstrike"
{
"Offsets"
{
// CBaseEntity::ForceVPhysicsCollide(CBaseEntity *pEntity). Same offsets as PropSpawner.
"ForceVPhysicsCollide"
{
"linux" "160"
"windows" "159"
}
// CBasePlayer::PlayerUse(). Same offsets as PropSpawner.
"PlayerUse"
{
"linux" "414"
"windows" "413"
}
}
"Signatures"
{
// CCollisionEvent::ShouldCollide(IPhysicsObject *, IPhysicsObject *, void *, void *).
// The vphysics collision filter, detoured for couch bans. Local symbol from .symtab,
// so the server binary must be unstripped (as for LagCompensation).
"CCollisionEvent::ShouldCollide"
{
"library" "server"
"linux" "@_ZN15CCollisionEvent13ShouldCollideEP14IPhysicsObjectS1_PvS2_"
}
// CBaseEntity::CollisionRulesChanged(). Makes vphysics re-ask ShouldCollide for cached pairs.
"CBaseEntity::CollisionRulesChanged"
{
"library" "server"
"linux" "@_ZN11CBaseEntity21CollisionRulesChangedEv"
}
}
}
}
File diff suppressed because it is too large Load Diff
+635
View File
@@ -0,0 +1,635 @@
#include <sourcemod>
#include <Couch>
#pragma semicolon 1
#pragma newdecls required
#define PREFIX "\x01[SM] \x04"
// A player who keeps moving the couch the same way is reported at most once per this many seconds.
#define LOG_REPEAT_INTERVAL 2.0
// How much each press in the push scale menu changes sm_couch_push_scale.
#define PUSH_SCALE_STEP 0.25
// So commands fail with a message instead of an unbound-native error while the core is unloaded.
bool g_bCoreLoaded;
// Admins who asked to see couch interactions. Per slot, so it survives map changes.
bool g_bLogging[MAXPLAYERS + 1];
// Per player: what was last reported, and when the same thing may be reported again.
CouchMethod g_iLastMethod[MAXPLAYERS + 1];
char g_sLastWeapon[MAXPLAYERS + 1][64];
float g_fNextReport[MAXPLAYERS + 1];
public Plugin myinfo =
{
name = "Couch Admin",
author = "marisakirisame.net",
description = "Admin commands and menu for the ZE couch",
version = "2.0",
url = "https://marisakirisame.net"
};
public void OnPluginStart()
{
RegAdminCmd("sm_couch", Command_Couch, ADMFLAG_CHANGEMAP, "Opens the couch menu");
RegAdminCmd("sm_spawncouch", Command_SpawnCouch, ADMFLAG_CHANGEMAP, "Spawns the couch where you are aiming");
RegAdminCmd("sm_bringcouch", Command_BringCouch, ADMFLAG_CHANGEMAP, "Brings the couch to where you are aiming");
RegAdminCmd("sm_gotocouch", Command_GotoCouch, ADMFLAG_CHANGEMAP, "Teleports you to the couch");
RegAdminCmd("sm_removecouch", Command_RemoveCouch, ADMFLAG_CHANGEMAP, "Removes the couch");
RegAdminCmd("sm_couchpistolonly", Command_PistolOnly, ADMFLAG_CHANGEMAP, "sm_couchpistolonly [0|1] - only pistols and knives move the couch; no argument toggles");
RegAdminCmd("sm_couchlog", Command_Log, ADMFLAG_CHANGEMAP, "sm_couchlog [0|1] - show who moves the couch and how in your console, grenades in chat too; no argument toggles");
}
// Game time restarts with the map.
public void OnMapStart()
{
for (int i = 1; i <= MAXPLAYERS; i++)
g_fNextReport[i] = 0.0;
}
public void OnClientDisconnect(int client)
{
g_bLogging[client] = false;
g_fNextReport[client] = 0.0;
}
public void OnAllPluginsLoaded()
{
g_bCoreLoaded = LibraryExists("Couch");
}
public void OnLibraryAdded(const char[] name)
{
if (StrEqual(name, "Couch"))
g_bCoreLoaded = true;
}
public void OnLibraryRemoved(const char[] name)
{
if (StrEqual(name, "Couch"))
g_bCoreLoaded = false;
}
// ---------------------------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------------------------
public Action Command_Couch(int client, int args)
{
if (!RequireInGame(client) || !RequireCore(client))
return Plugin_Handled;
ShowCouchMenu(client);
return Plugin_Handled;
}
public Action Command_SpawnCouch(int client, int args)
{
if (!RequireInGame(client) || !RequireCore(client))
return Plugin_Handled;
DoSpawn(client);
return Plugin_Handled;
}
public Action Command_BringCouch(int client, int args)
{
if (!RequireInGame(client) || !RequireCore(client))
return Plugin_Handled;
DoBring(client);
return Plugin_Handled;
}
public Action Command_GotoCouch(int client, int args)
{
if (!RequireInGame(client) || !RequireCore(client))
return Plugin_Handled;
DoGoto(client);
return Plugin_Handled;
}
// Needs no position, so it also works from the server console.
public Action Command_RemoveCouch(int client, int args)
{
if (!RequireCore(client))
return Plugin_Handled;
DoRemove(client);
return Plugin_Handled;
}
public Action Command_PistolOnly(int client, int args)
{
if (!RequireCore(client))
return Plugin_Handled;
bool bEnable = !Couch_IsPistolOnly();
if (args >= 1)
bEnable = (GetCmdArgInt(1) != 0);
DoSetPistolOnly(client, bEnable);
return Plugin_Handled;
}
public Action Command_Log(int client, int args)
{
if (!RequireInGame(client))
return Plugin_Handled;
bool bEnable = !g_bLogging[client];
if (args >= 1)
bEnable = (GetCmdArgInt(1) != 0);
DoSetLogging(client, bEnable);
return Plugin_Handled;
}
bool RequireInGame(int client)
{
if (client >= 1 && IsClientInGame(client))
return true;
ReplyToCommand(client, "[SM] This command can only be used in-game.");
return false;
}
bool RequireCore(int client)
{
if (g_bCoreLoaded)
return true;
ReplyToCommand(client, "[SM] The Couch core plugin is not loaded.");
return false;
}
// ---------------------------------------------------------------------------------------------
// Actions
// ---------------------------------------------------------------------------------------------
void DoSpawn(int client)
{
if (Couch_Spawn(client) == -1)
{
ReplyToCommand(client, "[SM] Could not create the couch.");
return;
}
ShowActivity2(client, PREFIX, "\x01Spawned the \x04couch\x01, it will respawn here each round until removed.");
LogAction(client, -1, "\"%L\" spawned the couch.", client);
}
void DoBring(int client)
{
if (!Couch_Bring(client))
{
ReplyToCommand(client, "[SM] There is no couch to bring. Use sm_spawncouch first.");
return;
}
ShowActivity2(client, PREFIX, "\x01Brought the \x04couch\x01.");
LogAction(client, -1, "\"%L\" brought the couch.", client);
}
void DoGoto(int client)
{
if (!IsPlayerAlive(client))
{
ReplyToCommand(client, "[SM] You must be alive to go to the couch.");
return;
}
if (!Couch_Goto(client))
{
ReplyToCommand(client, "[SM] There is no couch to go to. Use sm_spawncouch first.");
return;
}
ShowActivity2(client, PREFIX, "\x01Teleported to the \x04couch\x01.");
LogAction(client, -1, "\"%L\" teleported to the couch.", client);
}
void DoRemove(int client)
{
if (!Couch_Remove())
{
ReplyToCommand(client, "[SM] There is no couch to remove.");
return;
}
ShowActivity2(client, PREFIX, "\x01Removed the \x04couch\x01.");
LogAction(client, -1, "\"%L\" removed the couch.", client);
}
void DoToggleEffect(int client)
{
bool bEnable = !Couch_IsEffectEnabled();
Couch_SetEffectEnabled(bEnable);
ShowActivity2(client, PREFIX, "\x01Turned the couch effect \x04%s\x01.", bEnable ? "on" : "off");
LogAction(client, -1, "\"%L\" turned the couch effect %s.", client, bEnable ? "on" : "off");
}
// Announced to everyone so players know why their rifle stopped moving the couch.
void DoSetPistolOnly(int client, bool bEnable)
{
Couch_SetPistolOnly(bEnable);
ShowActivity2(client, PREFIX, "\x01Turned couch pistol-only mode \x04%s\x01.", bEnable ? "on" : "off");
LogAction(client, -1, "\"%L\" turned couch pistol-only mode %s.", client, bEnable ? "on" : "off");
}
// Announced to everyone so players know to shoot the couch instead.
void DoToggleUse(int client)
{
bool bAllow = !Couch_IsUseAllowed();
Couch_SetUseAllowed(bAllow);
if (bAllow)
ShowActivity2(client, PREFIX, "\x01Allowed pushing the \x04couch\x01 with USE.");
else
ShowActivity2(client, PREFIX, "\x01Disabled pushing the \x04couch\x01 with USE, it must be shot to move.");
LogAction(client, -1, "\"%L\" %s couch use.", client, bAllow ? "allowed" : "disallowed");
}
// The tier cvars belong to the core plugin, so they are looked up by name. sName is "move" or "button".
void DoChangeTier(int client, const char[] sName, int iDelta)
{
ConVar cvar = FindTierConVar(sName);
if (cvar == null)
return;
int iTier = cvar.IntValue + iDelta;
if (iTier < 0)
iTier = 0;
cvar.IntValue = iTier;
if (iTier == 0)
{
ShowActivity2(client, PREFIX, "\x01Turned off the couch \x04%s\x01 tier requirement.", sName);
LogAction(client, -1, "\"%L\" turned off the couch %s tier requirement.", client, sName);
}
else
{
ShowActivity2(client, PREFIX, "\x01Set the couch \x04%s\x01 tier requirement to \x04%d\x01.", sName, iTier);
LogAction(client, -1, "\"%L\" set the couch %s tier requirement to %d.", client, sName, iTier);
}
}
ConVar FindTierConVar(const char[] sName)
{
char sCvar[32];
FormatEx(sCvar, sizeof(sCvar), "sm_couch_%s_tier", sName);
return FindConVar(sCvar);
}
// Also a core plugin cvar. Rounded to the step so repeated presses don't drift, and read back
// afterwards so the announcement shows the value after the cvar's own bounds have clamped it.
void DoSetPushScale(int client, float fScale)
{
ConVar cvar = FindConVar("sm_couch_push_scale");
if (cvar == null)
return;
fScale = RoundToNearest(fScale / PUSH_SCALE_STEP) * PUSH_SCALE_STEP;
if (fScale < 0.0)
fScale = 0.0;
cvar.FloatValue = fScale;
fScale = cvar.FloatValue;
ShowActivity2(client, PREFIX, "\x01Set the couch \x04push scale\x01 to \x04%.2fx\x01.", fScale);
LogAction(client, -1, "\"%L\" set the couch push scale to %.2f.", client, fScale);
}
// Only affects what this admin sees, so it is neither announced nor logged.
void DoSetLogging(int client, bool bEnable)
{
g_bLogging[client] = bEnable;
if (bEnable)
PrintToChat(client, PREFIX ... "\x01Couch log \x04on\x01, see your console for who moves the couch and how. Grenades also show here.");
else
PrintToChat(client, PREFIX ... "\x01Couch log \x04off\x01.");
}
// ---------------------------------------------------------------------------------------------
// Couch log
// ---------------------------------------------------------------------------------------------
public void Couch_OnCouchInteraction(int couch, int client, CouchMethod method, const char[] weapon)
{
if (!AnyoneLogging())
return;
// Held fire and shotgun pellets would flood the console; a change of weapon or method is always shown.
float fNow = GetGameTime();
if (fNow < g_fNextReport[client] && method == g_iLastMethod[client] && StrEqual(weapon, g_sLastWeapon[client]))
return;
g_iLastMethod[client] = method;
strcopy(g_sLastWeapon[client], sizeof(g_sLastWeapon[]), weapon);
g_fNextReport[client] = fNow + LOG_REPEAT_INTERVAL;
// Plain text: the console doesn't render chat colour codes.
char sHow[96];
switch (method)
{
case CouchMethod_Bullet:
{
if (weapon[0] != '\0')
FormatEx(sHow, sizeof(sHow), "shot the couch with %s", weapon);
else
strcopy(sHow, sizeof(sHow), "shot the couch");
}
case CouchMethod_Knife: strcopy(sHow, sizeof(sHow), "knifed the couch");
case CouchMethod_Grenade: strcopy(sHow, sizeof(sHow), "hit the couch with a grenade");
case CouchMethod_Use: strcopy(sHow, sizeof(sHow), "pushed the couch with USE");
default: FormatEx(sHow, sizeof(sHow), "moved the couch with %s", weapon[0] != '\0' ? weapon : "something");
}
// Everything goes to the console; grenades are rare and knock the couch far, so they go to chat too.
// The userid is there so it can go straight into sm_couchban.
int userid = GetClientUserId(client);
for (int i = 1; i <= MaxClients; i++)
{
if (!g_bLogging[i] || !IsClientInGame(i))
continue;
PrintToConsole(i, "[Couch] %N (#%d) %s", client, userid, sHow);
if (method == CouchMethod_Grenade)
PrintToChat(i, "\x01[Couch] \x04%N\x01 (#%d) hit the couch with a \x04grenade", client, userid);
}
}
bool AnyoneLogging()
{
for (int i = 1; i <= MaxClients; i++)
{
if (g_bLogging[i])
return true;
}
return false;
}
// ---------------------------------------------------------------------------------------------
// Menu
// ---------------------------------------------------------------------------------------------
// Rebuilt on every display so the labels always reflect the current state.
void ShowCouchMenu(int client)
{
bool bHasCouch = (Couch_GetCouch() != -1);
bool bPlaced = Couch_IsPlaced();
Menu menu = new Menu(MenuHandler_Couch);
menu.SetTitle("Couch");
// With a couch out, bring is the way to move it; without one there is nothing to bring.
menu.AddItem("spawn", "Spawn couch at crosshair", bHasCouch ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
menu.AddItem("bring", "Bring couch to crosshair", bHasCouch ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
menu.AddItem("goto", "Teleport to couch", bHasCouch ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
if (bHasCouch)
menu.AddItem("remove", "Remove couch");
else if (bPlaced)
menu.AddItem("remove", "Remove couch (respawns next round)");
else
menu.AddItem("remove", "Remove couch", ITEMDRAW_DISABLED);
char sItem[64];
FormatEx(sItem, sizeof(sItem), "Effect: %s", Couch_IsEffectEnabled() ? "ON" : "OFF");
menu.AddItem("effect", sItem);
FormatEx(sItem, sizeof(sItem), "Pistol only: %s", Couch_IsPistolOnly() ? "ON" : "OFF");
menu.AddItem("pistol", sItem);
FormatEx(sItem, sizeof(sItem), "Allow USE: %s", Couch_IsUseAllowed() ? "ON" : "OFF");
menu.AddItem("use", sItem);
FormatEx(sItem, sizeof(sItem), "Couch logs (for me): %s", g_bLogging[client] ? "ON" : "OFF");
menu.AddItem("log", sItem);
menu.AddItem("tiers", "Tier requirements");
menu.AddItem("pushscale", "Push scale");
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
public int MenuHandler_Couch(Menu menu, MenuAction action, int param1, int param2)
{
switch (action)
{
case MenuAction_Select:
{
int client = param1;
if (!g_bCoreLoaded)
{
RequireCore(client);
return 0;
}
char sInfo[16];
menu.GetItem(param2, sInfo, sizeof(sInfo));
if (StrEqual(sInfo, "spawn"))
DoSpawn(client);
else if (StrEqual(sInfo, "bring"))
DoBring(client);
else if (StrEqual(sInfo, "goto"))
DoGoto(client);
else if (StrEqual(sInfo, "remove"))
DoRemove(client);
else if (StrEqual(sInfo, "effect"))
DoToggleEffect(client);
else if (StrEqual(sInfo, "pistol"))
DoSetPistolOnly(client, !Couch_IsPistolOnly());
else if (StrEqual(sInfo, "use"))
DoToggleUse(client);
else if (StrEqual(sInfo, "log"))
DoSetLogging(client, !g_bLogging[client]);
else if (StrEqual(sInfo, "tiers"))
{
ShowTierMenu(client);
return 0;
}
else if (StrEqual(sInfo, "pushscale"))
{
ShowPushScaleMenu(client);
return 0;
}
ShowCouchMenu(client);
}
case MenuAction_End:
{
delete menu;
}
}
return 0;
}
void ShowTierMenu(int client)
{
Menu menu = new Menu(MenuHandler_Tier);
menu.SetTitle("Couch - Tier requirements");
AddTierItems(menu, "move", "Move");
AddTierItems(menu, "button", "Button");
menu.ExitBackButton = true;
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
// Adds "<label> tier: <value>", increase and decrease. Info strings are "+move", "-move" and so on.
void AddTierItems(Menu menu, const char[] sName, const char[] sLabel)
{
ConVar cvar = FindTierConVar(sName);
int iTier = (cvar == null) ? 0 : cvar.IntValue;
char sItem[64], sInfo[16];
if (iTier > 0)
FormatEx(sItem, sizeof(sItem), "%s tier: %d", sLabel, iTier);
else
FormatEx(sItem, sizeof(sItem), "%s tier: OFF", sLabel);
menu.AddItem("", sItem, ITEMDRAW_DISABLED);
FormatEx(sInfo, sizeof(sInfo), "+%s", sName);
FormatEx(sItem, sizeof(sItem), "Increase %s tier", sName);
menu.AddItem(sInfo, sItem, (cvar == null) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
FormatEx(sInfo, sizeof(sInfo), "-%s", sName);
FormatEx(sItem, sizeof(sItem), "Decrease %s tier", sName);
menu.AddItem(sInfo, sItem, (iTier > 0) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
}
public int MenuHandler_Tier(Menu menu, MenuAction action, int param1, int param2)
{
switch (action)
{
case MenuAction_Select:
{
int client = param1;
char sInfo[16];
menu.GetItem(param2, sInfo, sizeof(sInfo));
DoChangeTier(client, sInfo[1], (sInfo[0] == '+') ? 1 : -1);
ShowTierMenu(client);
}
case MenuAction_Cancel:
{
if (param2 == MenuCancel_ExitBack)
{
if (g_bCoreLoaded)
ShowCouchMenu(param1);
else
RequireCore(param1);
}
}
case MenuAction_End:
{
delete menu;
}
}
return 0;
}
// The core plugin's default for sm_couch_push_scale, so the reset button follows it.
float GetPushScaleDefault(ConVar cvar)
{
if (cvar == null)
return 6.0;
char sDefault[16];
cvar.GetDefault(sDefault, sizeof(sDefault));
return StringToFloat(sDefault);
}
void ShowPushScaleMenu(int client)
{
ConVar cvar = FindConVar("sm_couch_push_scale");
float fScale = (cvar == null) ? 6.0 : cvar.FloatValue;
Menu menu = new Menu(MenuHandler_PushScale);
menu.SetTitle("Couch - Push scale");
char sItem[64];
FormatEx(sItem, sizeof(sItem), "Push scale: %.2fx", fScale);
menu.AddItem("", sItem, ITEMDRAW_DISABLED);
FormatEx(sItem, sizeof(sItem), "Increase push scale (+%.2f)", PUSH_SCALE_STEP);
menu.AddItem("+", sItem, (cvar == null) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
FormatEx(sItem, sizeof(sItem), "Decrease push scale (-%.2f)", PUSH_SCALE_STEP);
menu.AddItem("-", sItem, (cvar != null && fScale > 0.0) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
float fDefault = GetPushScaleDefault(cvar);
FormatEx(sItem, sizeof(sItem), "Reset push scale (%.2fx)", fDefault);
menu.AddItem("reset", sItem, (cvar != null && fScale != fDefault) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
menu.ExitBackButton = true;
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
public int MenuHandler_PushScale(Menu menu, MenuAction action, int param1, int param2)
{
switch (action)
{
case MenuAction_Select:
{
int client = param1;
ConVar cvar = FindConVar("sm_couch_push_scale");
if (cvar != null)
{
char sInfo[16];
menu.GetItem(param2, sInfo, sizeof(sInfo));
if (StrEqual(sInfo, "+"))
DoSetPushScale(client, cvar.FloatValue + PUSH_SCALE_STEP);
else if (StrEqual(sInfo, "-"))
DoSetPushScale(client, cvar.FloatValue - PUSH_SCALE_STEP);
else if (StrEqual(sInfo, "reset"))
DoSetPushScale(client, GetPushScaleDefault(cvar));
}
ShowPushScaleMenu(client);
}
case MenuAction_Cancel:
{
if (param2 == MenuCancel_ExitBack)
{
if (g_bCoreLoaded)
ShowCouchMenu(param1);
else
RequireCore(param1);
}
}
case MenuAction_End:
{
delete menu;
}
}
return 0;
}
+246
View File
@@ -0,0 +1,246 @@
#include <sourcemod>
#include <Couch>
#pragma semicolon 1
#pragma newdecls required
#define PREFIX "\x01[SM] \x04"
// So commands fail with a message instead of an unbound-native error while the core is unloaded.
bool g_bCoreLoaded;
// Account ids banned this map. This is the ban itself, so leaving and rejoining does not clear it.
ArrayList g_alBannedAccounts;
// Slot mirror. Also carries bans of unauthenticated clients (account id 0), which last the session only.
bool g_bBanned[MAXPLAYERS + 1];
public Plugin myinfo =
{
name = "Couch Ban",
author = "marisakirisame.net",
description = "Bans players from the ZE couch until the end of the map",
version = "2.0",
url = "https://marisakirisame.net"
};
// ---------------------------------------------------------------------------------------------
// Plugin lifecycle
// ---------------------------------------------------------------------------------------------
public void OnPluginStart()
{
LoadTranslations("common.phrases"); // FindTarget's replies.
g_alBannedAccounts = new ArrayList();
RegAdminCmd("sm_couchban", Command_CouchBan, ADMFLAG_BAN, "sm_couchban <#userid|name> - bans a player from the couch until the end of the map");
RegAdminCmd("sm_cban", Command_CouchBan, ADMFLAG_BAN, "Alias of sm_couchban");
RegAdminCmd("sm_couchunban", Command_CouchUnban, ADMFLAG_BAN, "sm_couchunban <#userid|name> - lifts a couch ban");
RegAdminCmd("sm_cunban", Command_CouchUnban, ADMFLAG_BAN, "Alias of sm_couchunban");
}
public void OnAllPluginsLoaded()
{
g_bCoreLoaded = LibraryExists("Couch");
// After a reload of this plugin the core still knows who is blocked.
if (!g_bCoreLoaded)
return;
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || !Couch_IsClientBlocked(i))
continue;
g_bBanned[i] = true;
RememberAccount(i);
}
}
public void OnLibraryAdded(const char[] name)
{
if (!StrEqual(name, "Couch"))
return;
g_bCoreLoaded = true;
// A core reload wiped its flags. A frame later so its natives are bound.
RequestFrame(Frame_ReapplyBans);
}
public void OnLibraryRemoved(const char[] name)
{
if (StrEqual(name, "Couch"))
g_bCoreLoaded = false;
}
void Frame_ReapplyBans(any data)
{
if (!g_bCoreLoaded)
return;
for (int i = 1; i <= MaxClients; i++)
{
if (g_bBanned[i] && IsClientInGame(i))
Couch_SetClientBlocked(i, true);
}
}
public void OnMapStart()
{
for (int i = 1; i <= MAXPLAYERS; i++)
g_bBanned[i] = false;
}
public void OnMapEnd()
{
g_alBannedAccounts.Clear();
}
// The account id stays on the list so a rejoin picks the ban back up.
public void OnClientDisconnect(int client)
{
g_bBanned[client] = false;
}
// Either "in game" or "authorized" can come last, so both are hooked.
public void OnClientPutInServer(int client)
{
TryReapplyBan(client);
}
public void OnClientAuthorized(int client, const char[] auth)
{
TryReapplyBan(client);
}
void TryReapplyBan(int client)
{
if (g_bBanned[client] || !IsClientInGame(client))
return;
int account = GetSteamAccountID(client);
if (account == 0 || g_alBannedAccounts.FindValue(account) == -1)
return;
g_bBanned[client] = true;
if (g_bCoreLoaded)
Couch_SetClientBlocked(client, true);
PrintToChat(client, PREFIX ... "You are couch-banned\x01 until the end of the map.");
}
// ---------------------------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------------------------
public Action Command_CouchBan(int client, int args)
{
if (!RequireCore(client))
return Plugin_Handled;
if (args < 1)
{
ReplyToCommand(client, "[SM] Usage: sm_couchban <#userid|name>");
return Plugin_Handled;
}
char sArg[MAX_TARGET_LENGTH];
GetCmdArg(1, sArg, sizeof(sArg));
int target = FindTarget(client, sArg, true, true);
if (target == -1)
return Plugin_Handled;
if (g_bBanned[target])
{
ReplyToCommand(client, "[SM] %N is already couch-banned.", target);
return Plugin_Handled;
}
DoBan(client, target);
return Plugin_Handled;
}
public Action Command_CouchUnban(int client, int args)
{
if (!RequireCore(client))
return Plugin_Handled;
if (args < 1)
{
ReplyToCommand(client, "[SM] Usage: sm_couchunban <#userid|name>");
return Plugin_Handled;
}
char sArg[MAX_TARGET_LENGTH];
GetCmdArg(1, sArg, sizeof(sArg));
int target = FindTarget(client, sArg, true, true);
if (target == -1)
return Plugin_Handled;
if (!g_bBanned[target])
{
ReplyToCommand(client, "[SM] %N is not couch-banned.", target);
return Plugin_Handled;
}
DoUnban(client, target);
return Plugin_Handled;
}
bool RequireCore(int client)
{
if (g_bCoreLoaded)
return true;
ReplyToCommand(client, "[SM] The Couch core plugin is not loaded.");
return false;
}
// ---------------------------------------------------------------------------------------------
// Actions
// ---------------------------------------------------------------------------------------------
void DoBan(int client, int target)
{
g_bBanned[target] = true;
RememberAccount(target);
Couch_SetClientBlocked(target, true);
// Announced to everyone so players know why the couch stopped reacting to someone.
PrintToChatAll("\x01[SM] \x04%N\x01 couch-banned \x04%N\x01 until the end of the map.", client, target);
LogAction(client, target, "\"%L\" couch-banned \"%L\" until the end of the map.", client, target);
}
void DoUnban(int client, int target)
{
g_bBanned[target] = false;
ForgetAccount(target);
Couch_SetClientBlocked(target, false);
PrintToChatAll("\x01[SM] \x04%N\x01 removed couch-ban from \x04%N\x01.", client, target);
LogAction(client, target, "\"%L\" removed couch-ban from \"%L\".", client, target);
}
// An unauthenticated client has no account id, so their ban ends with the session.
void RememberAccount(int client)
{
int account = GetSteamAccountID(client);
if (account != 0 && g_alBannedAccounts.FindValue(account) == -1)
g_alBannedAccounts.Push(account);
}
void ForgetAccount(int client)
{
int account = GetSteamAccountID(client);
if (account == 0)
return;
int index = g_alBannedAccounts.FindValue(account);
if (index != -1)
g_alBannedAccounts.Erase(index);
}
+384
View File
@@ -0,0 +1,384 @@
#include <sourcemod>
#include <sdktools>
#include <Couch>
#pragma semicolon 1
#pragma newdecls required
// How often expired names are swept off the list. Only resends if a name actually dropped.
#define HUD_EXPIRE_INTERVAL 0.25
// Longer names are cut so one can't eat the whole message.
#define HUD_NAME_BYTES 20
// +use reports every tick it's held; the list is resent at most this often for it. Well under
// sm_couch_hud_time's minimum, so the text never lapses while someone keeps pushing.
#define HUD_USE_RESEND 0.1
// Put in front of the name of anyone whose grenade hit the couch recently.
#define HUD_GRENADE_TAG "<G!> "
// A user message holds 255 bytes; HudMsg's fixed fields take 34 and the string its terminator.
// Overflowing drops the whole message.
#define HUD_MSG_BYTES 255
#define HUD_MSG_HEADER_BYTES 34
#define HUD_TEXT_MAX (HUD_MSG_BYTES - HUD_MSG_HEADER_BYTES - 1)
ConVar g_cvEnabled;
ConVar g_cvTime;
ConVar g_cvGrenadeTime;
Handle g_hHudSync;
Handle g_hExpireTimer;
float g_fExpire[MAXPLAYERS + 1]; // GetGameTime() at which this client drops off the list.
float g_fLastHit[MAXPLAYERS + 1]; // Ordering key: the most recent shooter is listed first.
float g_fGrenadeUntil[MAXPLAYERS + 1]; // GetGameTime() at which this client loses the grenade tag.
float g_fNextUseSend[MAXPLAYERS + 1]; // GetGameTime() before which this client's +use doesn't resend.
float g_fNextExpiry; // Soonest expiry or tag drop among the names currently listed.
float g_fLastExpiry; // Latest expiry among them; the text is held until then.
bool g_bFlushQueued; // A send is already scheduled for the next frame.
bool g_bHudShown; // Clients currently have text up, so an empty list must clear it.
char g_sHudText[HUD_TEXT_MAX + 1];
public Plugin myinfo =
{
name = "Couch HUD",
author = "marisakirisame.net",
description = "Shows who is shooting the couch",
version = "2.0",
url = "https://marisakirisame.net"
};
public void OnPluginStart()
{
g_hHudSync = CreateHudSynchronizer();
g_cvEnabled = CreateConVar("sm_couch_hud_enabled", "1", "Show the list of players shooting the couch.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
g_cvTime = CreateConVar("sm_couch_hud_time", "0.5", "How long a player stays on the list after their last hit or +use push, in seconds.", FCVAR_NOTIFY, true, 0.5, true, 30.0);
g_cvGrenadeTime = CreateConVar("sm_couch_hud_time_grenade", "4.0", "How long a player stays on the list, tagged " ... HUD_GRENADE_TAG ... ", after their grenade hits the couch, in seconds.", FCVAR_NOTIFY, true, 0.5, true, 30.0);
g_cvEnabled.AddChangeHook(OnEnabledChanged);
AutoExecConfig(true, "CouchHud");
}
public void OnMapStart()
{
// TIMER_FLAG_NO_MAPCHANGE already killed the timer.
g_hExpireTimer = null;
g_bFlushQueued = false;
g_bHudShown = false;
g_fNextExpiry = 0.0;
g_fLastExpiry = 0.0;
for (int i = 1; i <= MAXPLAYERS; i++)
{
g_fExpire[i] = 0.0;
g_fLastHit[i] = 0.0;
g_fGrenadeUntil[i] = 0.0;
g_fNextUseSend[i] = 0.0;
}
}
public void OnClientPutInServer(int client)
{
g_fExpire[client] = 0.0;
g_fLastHit[client] = 0.0;
g_fGrenadeUntil[client] = 0.0;
g_fNextUseSend[client] = 0.0;
}
public void OnClientDisconnect(int client)
{
bool bListed = (g_fExpire[client] > GetGameTime());
g_fExpire[client] = 0.0;
g_fLastHit[client] = 0.0;
g_fGrenadeUntil[client] = 0.0;
g_fNextUseSend[client] = 0.0;
if (bListed)
QueueFlush();
}
public void OnEnabledChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
if (!convar.BoolValue)
ResetAll();
}
public void OnLibraryRemoved(const char[] name)
{
if (StrEqual(name, "Couch"))
ResetAll();
}
// ---------------------------------------------------------------------------------------------
// Couch forwards
// ---------------------------------------------------------------------------------------------
public void Couch_OnCouchDamaged(int couch, int attacker, float damage)
{
if (!g_cvEnabled.BoolValue)
return;
// Extended, never shortened, so gunfire right after a grenade doesn't cut its time short.
float fNow = GetGameTime();
g_fLastHit[attacker] = fNow;
g_fExpire[attacker] = MaxFloat(g_fExpire[attacker], fNow + g_cvTime.FloatValue);
QueueFlush();
}
// Hits fire just after Couch_OnCouchDamaged for the same hit, so the ordering is already updated.
// +use never goes through damage, so it is listed from here.
public void Couch_OnCouchInteraction(int couch, int client, CouchMethod method, const char[] weapon)
{
if (!g_cvEnabled.BoolValue)
return;
float fNow = GetGameTime();
if (method == CouchMethod_Use)
{
g_fLastHit[client] = fNow;
g_fExpire[client] = MaxFloat(g_fExpire[client], fNow + g_cvTime.FloatValue);
if (fNow < g_fNextUseSend[client])
return;
g_fNextUseSend[client] = fNow + HUD_USE_RESEND;
QueueFlush();
return;
}
if (method != CouchMethod_Grenade)
return;
g_fGrenadeUntil[client] = fNow + g_cvGrenadeTime.FloatValue;
g_fExpire[client] = MaxFloat(g_fExpire[client], g_fGrenadeUntil[client]);
QueueFlush();
}
float MaxFloat(float a, float b)
{
return (a > b) ? a : b;
}
public void Couch_OnCouchRemoved()
{
ResetAll();
}
// ---------------------------------------------------------------------------------------------
// Sending
// ---------------------------------------------------------------------------------------------
// Once per frame: every hit in a frame (all shotgun pellets, several shooters) lands here, but
// only the first schedules a send.
void QueueFlush()
{
if (g_bFlushQueued)
return;
g_bFlushQueued = true;
RequestFrame(Frame_Flush);
}
void Frame_Flush(any data)
{
g_bFlushQueued = false;
Flush();
}
void Flush()
{
if (!g_cvEnabled.BoolValue || Couch_GetCouch() == -1)
{
ResetAll();
return;
}
if (BuildHudText(g_sHudText, sizeof(g_sHudText)) == 0)
{
ClearAll();
return;
}
Send();
}
void Send()
{
// Held until the last name lapses, so the text leaves on its own if no update ever comes.
// Names lapsing sooner are dropped by a resend from the expiry timer.
SetHudTextParams(0.8, 0.2, g_fLastExpiry - GetGameTime(), 255, 255, 255, 255, 0, 0.0, 0.0, 0.0);
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && !IsFakeClient(i))
ShowSyncHudText(i, g_hHudSync, "%s", g_sHudText);
}
g_bHudShown = true;
// Started here rather than from the hit, so a tick between hit and send can't stop the timer.
if (g_hExpireTimer == null)
g_hExpireTimer = CreateTimer(HUD_EXPIRE_INTERVAL, Timer_Expire, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
}
// Most recent shooters first. Returns how many players are listed, shown or not. Names are
// packed until the message is full, then come off the bottom until a "+ N" line fits.
int BuildHudText(char[] text, int maxlen)
{
float fNow = GetGameTime();
bool bTaken[MAXPLAYERS + 1];
int order[MAXPLAYERS + 1];
int total = 0;
g_fNextExpiry = 0.0;
g_fLastExpiry = 0.0;
// Selection sort, most recent hit first. The soonest expiry is tracked over everyone listed,
// since a hidden player lapsing still changes the "+ N" count. A grenade tag dropping off a
// name that stays listed changes the text too, so it counts as an expiry.
for (;;)
{
int best = 0;
for (int i = 1; i <= MaxClients; i++)
{
if (bTaken[i] || g_fExpire[i] <= fNow || !IsClientInGame(i))
continue;
if (best == 0 || g_fLastHit[i] > g_fLastHit[best])
best = i;
}
if (best == 0)
break;
bTaken[best] = true;
order[total++] = best;
float fChange = g_fExpire[best];
if (g_fGrenadeUntil[best] > fNow && g_fGrenadeUntil[best] < fChange)
fChange = g_fGrenadeUntil[best];
if (g_fNextExpiry == 0.0 || fChange < g_fNextExpiry)
g_fNextExpiry = fChange;
if (g_fExpire[best] > g_fLastExpiry)
g_fLastExpiry = g_fExpire[best];
}
if (total == 0)
{
text[0] = '\0';
return 0;
}
strcopy(text, maxlen, "Shooting couch:");
// rowStart[n] is the text length before row n, so a row can be cut back off.
int rowStart[MAXPLAYERS + 1];
int rows = 0;
for (; rows < total; rows++)
{
char sName[MAX_NAME_LENGTH];
GetClientName(order[rows], sName, sizeof(sName));
TruncateUtf8(sName, HUD_NAME_BYTES);
char sLine[HUD_NAME_BYTES + sizeof(HUD_GRENADE_TAG) + 1];
FormatEx(sLine, sizeof(sLine), "\n%s%s", (g_fGrenadeUntil[order[rows]] > fNow) ? HUD_GRENADE_TAG : "", sName);
int len = strlen(text);
if (len + strlen(sLine) >= maxlen)
break;
rowStart[rows] = len;
StrCat(text, maxlen, sLine);
}
// Overflow line. A longer count can need another byte, so the fit is rechecked after every drop.
while (rows < total)
{
char sLine[16];
FormatEx(sLine, sizeof(sLine), "\n+ %d", total - rows);
if (strlen(text) + strlen(sLine) < maxlen)
{
StrCat(text, maxlen, sLine);
break;
}
rows--;
text[rowStart[rows]] = '\0';
}
return total;
}
// Cuts to at most maxBytes without leaving a partial multi-byte character on the end.
void TruncateUtf8(char[] text, int maxBytes)
{
if (strlen(text) <= maxBytes)
return;
int cut = maxBytes;
// Back up over continuation bytes so the whole character is dropped.
while (cut > 0 && (text[cut] & 0xC0) == 0x80)
cut--;
text[cut] = '\0';
}
public Action Timer_Expire(Handle timer)
{
if (!g_bHudShown)
{
g_hExpireTimer = null;
return Plugin_Stop;
}
// Only resend when a name has lapsed.
if (GetGameTime() >= g_fNextExpiry)
Flush();
return Plugin_Continue;
}
void ClearAll()
{
if (!g_bHudShown)
return;
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && !IsFakeClient(i))
ClearSyncHud(i, g_hHudSync);
}
g_bHudShown = false;
}
// The expiry timer sees g_bHudShown clear on its next tick and stops itself, so this is safe
// to call from inside it.
void ResetAll()
{
for (int i = 1; i <= MAXPLAYERS; i++)
{
g_fExpire[i] = 0.0;
g_fLastHit[i] = 0.0;
g_fGrenadeUntil[i] = 0.0;
g_fNextUseSend[i] = 0.0;
}
ClearAll();
}
+187
View File
@@ -0,0 +1,187 @@
#if defined _Couch_included
#endinput
#endif
#define _Couch_included
// How a player moved the couch, passed to Couch_OnCouchInteraction.
enum CouchMethod
{
CouchMethod_Bullet, // Gunfire; weapon is the gun, e.g. "ak47".
CouchMethod_Knife, // weapon is "knife".
CouchMethod_Grenade, // weapon is "hegrenade".
CouchMethod_Use, // +use; weapon is empty.
CouchMethod_Other // Any other damage credited to a player; weapon is the inflictor's classname.
};
// The core defines COUCH_CORE; it creates the forwards and natives itself.
#if !defined COUCH_CORE
/**
* Called after the couch has been created, for admin spawns and round-start respawns.
*
* @param couch Entity index of the new couch.
*/
forward void Couch_OnCouchSpawned(int couch);
/**
* Called once the couch entity is gone, for whatever reason. Couch_IsPlaced() tells whether
* it will come back at the next round start.
*/
forward void Couch_OnCouchRemoved();
/**
* Called for every hit the couch accepts from a player. Shotguns report once per pellet.
*
* @param couch Entity index of the couch.
* @param attacker Client index of the player who dealt the damage.
* @param damage Damage dealt by this hit.
*/
forward void Couch_OnCouchDamaged(int couch, int attacker, float damage);
/**
* Called for every hit or +use the couch accepts from a player, saying how it was moved. Hits
* fire alongside Couch_OnCouchDamaged, so shotguns report once per pellet.
*
* @param couch Entity index of the couch.
* @param client Client index of the player.
* @param method How the couch was moved.
* @param weapon Weapon without its "weapon_" prefix, see CouchMethod.
*/
forward void Couch_OnCouchInteraction(int couch, int client, CouchMethod method, const char[] weapon);
/**
* Called when a hit or +use from a blocked client is refused. Fires per pellet, so rate-limit anything you send.
*
* @param couch Entity index of the couch.
* @param client Client index of the blocked player.
*/
forward void Couch_OnClientBlocked(int couch, int client);
/**
* @return Entity index of the couch, or -1 if there is none.
*/
native int Couch_GetCouch();
/**
* Whether an admin has placed a couch this map. While true the couch respawns at the placed
* spot every round start.
*
* @return True if a placement is remembered.
*/
native bool Couch_IsPlaced();
/**
* Spawns the couch where the client is aiming, replacing any existing couch. The spot becomes
* the respawn point.
*
* @param client Client whose crosshair places the couch. Must be in game.
* @return Entity index of the couch, or -1 on failure.
*/
native int Couch_Spawn(int client);
/**
* Teleports the couch to where the client is aiming. The respawn point is left as it was.
*
* @param client Client whose crosshair places the couch. Must be in game.
* @return True on success, false if there is no couch.
*/
native bool Couch_Bring(int client);
/**
* Teleports the client onto the couch, or beside it when the top is blocked. The client keeps
* their view angles and arrives with no velocity.
*
* @param client Client to teleport. Must be in game.
* @return True on success, false if there is no couch.
*/
native bool Couch_Goto(int client);
/**
* Removes the couch and forgets its placement.
*
* @return True if a couch or a placement was removed.
*/
native bool Couch_Remove();
/**
* Whether the colour cycle and light are on. Mirrors sm_couch_effect.
*/
native bool Couch_IsEffectEnabled();
/**
* Switches the colour cycle and light on or off. Writes sm_couch_effect.
*/
native void Couch_SetEffectEnabled(bool enabled);
/**
* Whether only pistols and knives may move the couch. Mirrors sm_couch_pistol_only.
*/
native bool Couch_IsPistolOnly();
/**
* Restricts couch movement to pistols and knives, or lifts the restriction. Writes sm_couch_pistol_only.
*/
native void Couch_SetPistolOnly(bool enabled);
/**
* Whether players may push the couch by holding +use on it. Mirrors sm_allow_couch_use.
*/
native bool Couch_IsUseAllowed();
/**
* Allows or forbids pushing the couch with +use; when forbidden it can only be shot. Writes sm_allow_couch_use.
*/
native void Couch_SetUseAllowed(bool allowed);
/**
* Blocks or unblocks a client from the couch: their hits are refused and their body can't push
* it. The flag is per slot and cleared on disconnect and map start; the caller owns persistence.
*
* @param client Client to block or unblock. Must be in game.
* @param blocked True to block, false to lift the block.
*/
native void Couch_SetClientBlocked(int client, bool blocked);
/**
* Whether the client is currently blocked from the couch.
*
* @param client Client index.
* @return True if blocked.
*/
native bool Couch_IsClientBlocked(int client);
public SharedPlugin __pl_Couch =
{
name = "Couch",
file = "Couch.smx",
#if defined REQUIRE_PLUGIN
required = 1
#else
required = 0
#endif
};
#if !defined REQUIRE_PLUGIN
public void __pl_Couch_SetNTVOptional()
{
MarkNativeAsOptional("Couch_GetCouch");
MarkNativeAsOptional("Couch_IsPlaced");
MarkNativeAsOptional("Couch_Spawn");
MarkNativeAsOptional("Couch_Bring");
MarkNativeAsOptional("Couch_Goto");
MarkNativeAsOptional("Couch_Remove");
MarkNativeAsOptional("Couch_IsEffectEnabled");
MarkNativeAsOptional("Couch_SetEffectEnabled");
MarkNativeAsOptional("Couch_IsPistolOnly");
MarkNativeAsOptional("Couch_SetPistolOnly");
MarkNativeAsOptional("Couch_IsUseAllowed");
MarkNativeAsOptional("Couch_SetUseAllowed");
MarkNativeAsOptional("Couch_SetClientBlocked");
MarkNativeAsOptional("Couch_IsClientBlocked");
}
#endif
#endif // COUCH_CORE
+138 -224
View File
@@ -2,9 +2,8 @@
#include <basecomm> #include <basecomm>
#include <sourcemod> #include <sourcemod>
#include <SteamWorks> #include <ripext>
#include <regex> #include <regex>
#include <smjansson>
#include <multicolors> #include <multicolors>
#include <sdktools> #include <sdktools>
#include <AFKManager> #include <AFKManager>
@@ -17,7 +16,8 @@
#tryinclude <calladmin> #tryinclude <calladmin>
#tryinclude <oryx> #tryinclude <oryx>
#tryinclude <entWatch_restrictions> #tryinclude <entWatch_restrictions>
#tryinclude <jenz_ban_detector> //#tryinclude <jenz_ban_detector>
#tryinclude <handicap_trouble_maker>
#pragma newdecls required #pragma newdecls required
@@ -28,6 +28,8 @@ ArrayList g_arrQueuedMessages = null;
Handle g_hDataTimer = null; Handle g_hDataTimer = null;
Handle g_hReplaceConfigFile = null; Handle g_hReplaceConfigFile = null;
static Handle g_hForwardDiscord;
UserMsg g_umSayText2 = INVALID_MESSAGE_ID; UserMsg g_umSayText2 = INVALID_MESSAGE_ID;
bool g_bLoadedLate; bool g_bLoadedLate;
@@ -58,7 +60,7 @@ public Plugin myinfo =
public APLRes AskPluginLoad2(Handle hThis, bool bLate, char[] sError, int err_max) public APLRes AskPluginLoad2(Handle hThis, bool bLate, char[] sError, int err_max)
{ {
g_bLoadedLate = bLate; g_bLoadedLate = bLate;
RegPluginLibrary("Discord_UNLOZE");
return APLRes_Success; return APLRes_Success;
} }
@@ -67,6 +69,9 @@ public void OnPluginStart()
char sRegexErr[32]; char sRegexErr[32];
RegexError RegexErr; RegexError RegexErr;
g_hForwardDiscord = CreateGlobalForward("GetDiscordToStoat", ET_Ignore, Param_String, Param_String, Param_Cell);
g_Regex_Clyde = CompileRegex(".*(clyde).*", PCRE_CASELESS, sRegexErr, sizeof(sRegexErr), RegexErr); g_Regex_Clyde = CompileRegex(".*(clyde).*", PCRE_CASELESS, sRegexErr, sizeof(sRegexErr), RegexErr);
if (RegexErr != REGEX_ERROR_NONE) if (RegexErr != REGEX_ERROR_NONE)
@@ -113,6 +118,7 @@ public void OnPluginEnd()
{ {
delete g_arrQueuedMessages; delete g_arrQueuedMessages;
delete g_hDataTimer; delete g_hDataTimer;
CloseHandle(g_hForwardDiscord);
UnhookUserMessage(g_umSayText2, Hook_UserMessage, false); UnhookUserMessage(g_umSayText2, Hook_UserMessage, false);
} }
@@ -132,6 +138,9 @@ public void OnAdminAFKTimeChanged(ConVar convar, const char[] oldValue, const ch
g_iAdminAFKTime = g_cvAdminAFKTime.IntValue; g_iAdminAFKTime = g_cvAdminAFKTime.IntValue;
} }
//----------------------------------------------------------------------------------------------------
// Purpose: Steam player summary lookup (avatar) - now via ripext, JSON instead of VDF
//----------------------------------------------------------------------------------------------------
public void OnClientAuthorized(int client, const char[] sAuthID32) public void OnClientAuthorized(int client, const char[] sAuthID32)
{ {
if (IsFakeClient(client)) if (IsFakeClient(client))
@@ -142,19 +151,44 @@ public void OnClientAuthorized(int client, const char[] sAuthID32)
if (!Steam32IDtoSteam64ID(sAuthID32, sAuthID64, sizeof(sAuthID64))) if (!Steam32IDtoSteam64ID(sAuthID32, sAuthID64, sizeof(sAuthID64)))
return; return;
static char sRequest[256]; char sRequest[256];
FormatEx(sRequest, sizeof(sRequest), "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=%s&steamids=%s&format=vdf", STEAM_API_KEY, sAuthID64); FormatEx(sRequest, sizeof(sRequest), "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=%s&steamids=%s&format=json", STEAM_API_KEY, sAuthID64);
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest); HTTPRequest req = new HTTPRequest(sRequest);
req.Get(OnTransferComplete, GetClientSerial(client));
}
if (!hRequest || public void OnTransferComplete(HTTPResponse response, any serial)
!SteamWorks_SetHTTPRequestContextValue(hRequest, client) || {
!SteamWorks_SetHTTPCallbacks(hRequest, OnTransferComplete) || int client = GetClientFromSerial(serial);
!SteamWorks_SendHTTPRequest(hRequest)) if (!client) //Player disconnected.
return;
if (response.Status != HTTPStatus_OK || response.Data == null)
return;
APIWebResponse(view_as<JSONObject>(response.Data), client);
}
public void APIWebResponse(JSONObject root, int client)
{
if (!root.HasKey("players"))
return;
JSONArray players = view_as<JSONArray>(root.Get("players"));
if (players == null || !players.Length)
{ {
delete hRequest; delete players;
return;
} }
JSONObject player = view_as<JSONObject>(players.Get(0));
player.GetString("avatarfull", g_sAvatarURL[client], sizeof(g_sAvatarURL[]));
delete player;
delete players;
} }
public Action Command_PrintToAdminChat(int args) public Action Command_PrintToAdminChat(int args)
@@ -190,6 +224,9 @@ public Action Command_PrintToAllChat(int args)
return Plugin_Handled; return Plugin_Handled;
} }
//----------------------------------------------------------------------------------------------------
// Purpose: Drains the rate-limit queue - now via ripext
//----------------------------------------------------------------------------------------------------
public Action Timer_DataProcessor(Handle hThis) public Action Timer_DataProcessor(Handle hThis)
{ {
if (!g_bProcessingData) if (!g_bProcessingData)
@@ -198,8 +235,6 @@ public Action Timer_DataProcessor(Handle hThis)
if (g_iRatelimitRemaining == 0 && GetTime() < g_iRatelimitReset) if (g_iRatelimitRemaining == 0 && GetTime() < g_iRatelimitReset)
return Plugin_Handled; return Plugin_Handled;
//PrintToServer("[Timer_DataProcessor] Array Length #1: %d", g_arrQueuedMessages.Length);
char sContent[1024]; char sContent[1024];
g_arrQueuedMessages.GetString(0, sContent, sizeof(sContent)); g_arrQueuedMessages.GetString(0, sContent, sizeof(sContent));
g_arrQueuedMessages.Erase(0); g_arrQueuedMessages.Erase(0);
@@ -211,28 +246,13 @@ public Action Timer_DataProcessor(Handle hThis)
if (g_arrQueuedMessages.Length == 0) if (g_arrQueuedMessages.Length == 0)
g_bProcessingData = false; g_bProcessingData = false;
//PrintToServer("[Timer_DataProcessor] Array Length #2: %d", g_arrQueuedMessages.Length); JSONObject data = JSONObject.FromString(sContent);
//PrintToServer("%s | %s", sURL, sContent); HTTPRequest req = new HTTPRequest(sURL);
req.Post(data, OnHTTPRequestCompleted);
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, sURL); delete data;
JSONObject RequestJSON = view_as<JSONObject>(json_load(sContent));
if (!hRequest ||
!SteamWorks_SetHTTPRequestContextValue(hRequest, RequestJSON) ||
!SteamWorks_SetHTTPCallbacks(hRequest, OnHTTPRequestCompleted) ||
!SteamWorks_SetHTTPRequestRawPostBody(hRequest, "application/json", sContent, strlen(sContent)) ||
!SteamWorks_SetHTTPRequestNetworkActivityTimeout(hRequest, 10) ||
!SteamWorks_SendHTTPRequest(hRequest))
{
LogError("Discord SteamWorks_CreateHTTPRequest failed.");
delete RequestJSON;
delete hRequest;
return Plugin_Handled;
}
return Plugin_Handled; return Plugin_Handled;
} }
@@ -293,27 +313,11 @@ stock void Discord_MakeStringSafe(const char[] sOrigin, char[] sOut, int iOutSiz
{ {
if (sOrigin[i] < 0x20 && sOrigin[i] != 0x0) if (sOrigin[i] < 0x20 && sOrigin[i] != 0x0)
{ {
//sOut[iCurIndex] = 0x20;
//iCurIndex++;
continue; continue;
} }
switch (sOrigin[i]) switch (sOrigin[i])
{ {
// case '"':
// {
// strcopy(sOut[iCurIndex], iOutSize, "\\u0022");
// iCurIndex += 6;
// continue;
// }
// case '\\':
// {
// strcopy(sOut[iCurIndex], iOutSize, "\\u005C");
// iCurIndex += 6;
// continue;
// }
case '@': case '@':
{ {
strcopy(sOut[iCurIndex], iOutSize, "@​"); //@ + zero-width space strcopy(sOut[iCurIndex], iOutSize, "@​"); //@ + zero-width space
@@ -351,102 +355,12 @@ stock void Discord_MakeStringSafe(const char[] sOrigin, char[] sOut, int iOutSiz
} }
} }
stock int OnTransferComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, int client) //----------------------------------------------------------------------------------------------------
{ // Purpose: Webhook POST helper - now via ripext
if (bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK) //----------------------------------------------------------------------------------------------------
{
if (eStatusCode != k_EHTTPStatusCode429TooManyRequests)
{
LogError("SteamAPI HTTP Response failed: %d", eStatusCode);
}
delete hRequest;
return 0;
}
int iBodyLength;
SteamWorks_GetHTTPResponseBodySize(hRequest, iBodyLength);
char[] sData = new char[iBodyLength];
SteamWorks_GetHTTPResponseBodyData(hRequest, sData, iBodyLength);
delete hRequest;
APIWebResponse(sData, client);
return 0;
}
stock void APIWebResponse(const char[] sData, int client)
{
KeyValues kvResponse = new KeyValues("SteamAPIResponse");
if (!kvResponse.ImportFromString(sData, "SteamAPIResponse"))
{
//LogError("kvResponse.ImportFromString(\"SteamAPIResponse\") in APIWebResponse failed.");
delete kvResponse;
return;
}
if (!kvResponse.JumpToKey("players"))
{
//LogError("kvResponse.JumpToKey(\"players\") in APIWebResponse failed.");
delete kvResponse;
return;
}
if (!kvResponse.GotoFirstSubKey())
{
//LogError("kvResponse.GotoFirstSubKey() in APIWebResponse failed.");
delete kvResponse;
return;
}
kvResponse.GetString("avatarfull", g_sAvatarURL[client], sizeof(g_sAvatarURL[]));
delete kvResponse;
}
stock void HTTPPostJSON(const char[] sURL, const char[] sText)
{
// if (g_iRatelimitRemaining > 0 && !g_bProcessingData && GetTime() < g_iRatelimitReset)
// {
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, sURL);
JSONObject RequestJSON = view_as<JSONObject>(json_load(sText));
if (!hRequest ||
!SteamWorks_SetHTTPRequestContextValue(hRequest, RequestJSON) ||
!SteamWorks_SetHTTPCallbacks(hRequest, OnHTTPRequestCompleted) ||
!SteamWorks_SetHTTPRequestRawPostBody(hRequest, "application/json", sText, strlen(sText)) ||
!SteamWorks_SetHTTPRequestNetworkActivityTimeout(hRequest, 15) ||
!SteamWorks_SendHTTPRequest(hRequest))
{
LogError("Discord SteamWorks_CreateHTTPRequest failed.");
delete RequestJSON;
delete hRequest;
return;
}
// }
// else
// {
// g_arrQueuedMessages.PushString(sText);
// g_arrQueuedMessages.PushString(sURL);
// g_bProcessingData = true;
// }
//delete hRequest;
}
stock void Discord_POST(const char[] sURL, char[] sText, bool bUsingUsername=false, char[] sUsername=NULL_STRING, bool bUsingAvatar=false, char[] sAvatarURL=NULL_STRING, bool bSafe=true, bool bTimestamp=true) stock void Discord_POST(const char[] sURL, char[] sText, bool bUsingUsername=false, char[] sUsername=NULL_STRING, bool bUsingAvatar=false, char[] sAvatarURL=NULL_STRING, bool bSafe=true, bool bTimestamp=true)
{ {
//PrintToServer("[Discord_POST] Called with text: %s", sText); JSONObject hJSONRoot = new JSONObject();
JSONRootNode hJSONRoot = new JSONObject();
char sSafeText[4096]; char sSafeText[4096];
char sFinal[4096]; char sFinal[4096];
@@ -456,13 +370,13 @@ stock void Discord_POST(const char[] sURL, char[] sText, bool bUsingUsername=fal
TrimString(sUsername); TrimString(sUsername);
if (g_Regex_Clyde.Match(sUsername) > 0 || strlen(sUsername) < 2) if (g_Regex_Clyde.Match(sUsername) > 0 || strlen(sUsername) < 2)
(view_as<JSONObject>(hJSONRoot)).SetString("username", "Invalid Name"); hJSONRoot.SetString("username", "Invalid Name");
else else
(view_as<JSONObject>(hJSONRoot)).SetString("username", sUsername); hJSONRoot.SetString("username", sUsername);
} }
if (bUsingAvatar) if (bUsingAvatar)
(view_as<JSONObject>(hJSONRoot)).SetString("avatar_url", sAvatarURL); hJSONRoot.SetString("avatar_url", sAvatarURL);
if (bSafe) if (bSafe)
{ {
@@ -481,102 +395,72 @@ stock void Discord_POST(const char[] sURL, char[] sText, bool bUsingUsername=fal
Format(sSafeText, sizeof(sSafeText), "[ *%s* ] %s", sTime, sText); Format(sSafeText, sizeof(sSafeText), "[ *%s* ] %s", sTime, sText);
} }
(view_as<JSONObject>(hJSONRoot)).SetString("content", sSafeText); hJSONRoot.SetString("content", sSafeText);
(view_as<JSONObject>(hJSONRoot)).ToString(sFinal, sizeof(sFinal), 0);
//hJSONRoot.DumpToServer();
delete hJSONRoot;
if ((g_iRatelimitRemaining > 0 || GetTime() >= g_iRatelimitReset) && !g_bProcessingData) if ((g_iRatelimitRemaining > 0 || GetTime() >= g_iRatelimitReset) && !g_bProcessingData)
{ {
//PrintToServer("[Discord_POST] Have allowances and not processing data"); HTTPRequest req = new HTTPRequest(sURL);
req.Post(hJSONRoot, OnHTTPRequestCompleted);
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, sURL); delete hJSONRoot;
JSONObject RequestJSON = view_as<JSONObject>(json_load(sFinal));
if (!hRequest ||
!SteamWorks_SetHTTPRequestContextValue(hRequest, RequestJSON) ||
!SteamWorks_SetHTTPCallbacks(hRequest, OnHTTPRequestCompleted) ||
!SteamWorks_SetHTTPRequestRawPostBody(hRequest, "application/json", sFinal, strlen(sFinal)) ||
!SteamWorks_SetHTTPRequestNetworkActivityTimeout(hRequest, 10) ||
!SteamWorks_SendHTTPRequest(hRequest))
{
LogError("Discord SteamWorks_CreateHTTPRequest failed.");
delete RequestJSON;
delete hRequest;
return;
}
} }
else else
{ {
//PrintToServer("[Discord_POST] Have allowances? [%s] | Is processing data? [%s]", g_iRatelimitRemaining > 0 ? "YES":"NO", g_bProcessingData?"YES":"NO"); hJSONRoot.ToString(sFinal, sizeof(sFinal), 0);
delete hJSONRoot;
g_arrQueuedMessages.PushString(sFinal); g_arrQueuedMessages.PushString(sFinal);
g_arrQueuedMessages.PushString(sURL); g_arrQueuedMessages.PushString(sURL);
g_bProcessingData = true; g_bProcessingData = true;
} }
//delete hRequest; //nonono
} }
public int OnHTTPRequestCompleted(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, JSONObject RequestJSON) public void OnHTTPRequestCompleted(HTTPResponse response, any value)
{ {
if (bFailure || !bRequestSuccessful || (eStatusCode != k_EHTTPStatusCode200OK && eStatusCode != k_EHTTPStatusCode204NoContent)) if (response.Status != HTTPStatus_OK && response.Status != HTTPStatus_NoContent)
{ {
if (eStatusCode != k_EHTTPStatusCode429TooManyRequests) if (response.Status != HTTPStatus_TooManyRequests)
LogError("Discord HTTP request failed: %d", eStatusCode); LogError("Discord HTTP request failed: %d", response.Status);
if (eStatusCode == k_EHTTPStatusCode400BadRequest) if (response.Status == HTTPStatus_BadRequest && response.Data != null)
{ {
char sData[2048]; char sData[2048];
(view_as<JSONRootNode>(RequestJSON)).ToString(sData, sizeof(sData), 0); response.Data.ToString(sData, sizeof(sData), 0);
LogError("Malformed request? Dumping request data:\n%s", sData); LogError("Malformed request? Dumping request data:\n%s", sData);
} }
else if (eStatusCode == k_EHTTPStatusCode429TooManyRequests) else if (response.Status == HTTPStatus_TooManyRequests)
{ {
g_iRatelimitRemaining = 0; g_iRatelimitRemaining = 0;
g_iRatelimitReset = GetTime() + 5; g_iRatelimitReset = GetTime() + 5;
} }
delete RequestJSON; return;
delete hRequest;
return 0;
} }
static int iLastRatelimitRemaining = 0; static int iLastRatelimitRemaining = 0;
static int iLastRatelimitReset = 0; static int iLastRatelimitReset = 0;
char sTmp[32]; char sTmp[32];
bool bHeaderExists = SteamWorks_GetHTTPResponseHeaderValue(hRequest, "x-ratelimit-remaining", sTmp, sizeof(sTmp)); bool bHeaderExists = response.GetHeader("x-ratelimit-remaining", sTmp, sizeof(sTmp));
if (!bHeaderExists) if (!bHeaderExists)
LogError("x-ratelimit-remaining header value could not be retrieved"); LogError("x-ratelimit-remaining header value could not be retrieved");
int iRatelimitRemaining = StringToInt(sTmp); int iRatelimitRemaining = StringToInt(sTmp);
bHeaderExists = SteamWorks_GetHTTPResponseHeaderValue(hRequest, "x-ratelimit-reset", sTmp, sizeof(sTmp)); bHeaderExists = response.GetHeader("x-ratelimit-reset", sTmp, sizeof(sTmp));
if (!bHeaderExists) if (!bHeaderExists)
LogError("x-ratelimit-reset header value could not be retrieved"); LogError("x-ratelimit-reset header value could not be retrieved");
int iRatelimitReset = StringToInt(sTmp); int iRatelimitReset = StringToInt(sTmp);
if (iRatelimitRemaining < iLastRatelimitRemaining || iRatelimitReset >= iLastRatelimitReset) //don't be fooled by different completion times if (iRatelimitRemaining < iLastRatelimitRemaining || iRatelimitReset >= iLastRatelimitReset) //dont be fooled by different completion times
{ {
g_iRatelimitRemaining = iRatelimitRemaining; g_iRatelimitRemaining = iRatelimitRemaining;
g_iRatelimitReset = iRatelimitReset; g_iRatelimitReset = iRatelimitReset;
} }
//PrintToServer("limit: %d | remaining: %d || reset %d - now %d", g_iRatelimitLimit, g_iRatelimitRemaining, g_iRatelimitReset, GetTime());
delete RequestJSON;
delete hRequest;
return 0;
} }
stock bool IsValidClient(int client) stock bool IsValidClient(int client)
@@ -613,6 +497,12 @@ public Action OnLogAction(Handle hSource, Identity ident, int client, int target
else else
Discord_POST(DISCORD_ADMINLOGS_WEBHOOKURL, sFinal, true, sClientName, false, "", false); Discord_POST(DISCORD_ADMINLOGS_WEBHOOKURL, sFinal, true, sClientName, false, "", false);
Call_StartForward(g_hForwardDiscord);
Call_PushString(sClientName);
Call_PushString(sFinal);
Call_PushCell(1);
Call_Finish();
return Plugin_Handled; return Plugin_Handled;
} }
@@ -632,6 +522,11 @@ public Action CommandListener_SmChat(int client, const char[] sCommand, int argc
else else
Discord_POST(DISCORD_ADMINCHAT_WEBHOOKURL, sText, true, sUsername); Discord_POST(DISCORD_ADMINCHAT_WEBHOOKURL, sText, true, sUsername);
Call_StartForward(g_hForwardDiscord);
Call_PushString(sUsername);
Call_PushString(sText);
Call_PushCell(6);
Call_Finish();
return Plugin_Continue; return Plugin_Continue;
} }
@@ -656,6 +551,12 @@ public Action OnClientSayCommand(int client, const char[] sCommand, const char[]
else else
Discord_POST(DISCORD_ADMINCHAT_WEBHOOKURL, sFinal, true, sUsername); Discord_POST(DISCORD_ADMINCHAT_WEBHOOKURL, sFinal, true, sUsername);
Call_StartForward(g_hForwardDiscord);
Call_PushString(sUsername);
Call_PushString(sFinal);
Call_PushCell(6);
Call_Finish();
if (!bAdmin) if (!bAdmin)
{ {
//g_iReplyTargetSerial = GetClientSerial(client); //g_iReplyTargetSerial = GetClientSerial(client);
@@ -674,7 +575,7 @@ public Action OnClientSayCommand(int client, const char[] sCommand, const char[]
return Plugin_Continue; return Plugin_Continue;
} }
public void BanDetectorPost(int client, const char[] detected_sourceban) public void BanDetectorPost1(int client, const char[] detected_sourceban)
{ {
char sClientName[MAX_NAME_LENGTH]; char sClientName[MAX_NAME_LENGTH];
char sClientID[21]; char sClientID[21];
@@ -687,11 +588,17 @@ public void BanDetectorPost(int client, const char[] detected_sourceban)
if (!Steam32IDtoSteam64ID(sClientID, sAuthID64, sizeof(sAuthID64))) if (!Steam32IDtoSteam64ID(sClientID, sAuthID64, sizeof(sAuthID64)))
return; return;
Format(sMessage, sizeof(sMessage), "https://steamcommunity.com/profiles/%s\nName: %s\nSteamID: %s\nClient is ban avoiding the ban: https://bans.unloze.com/index.php?p=banlist&searchText=%s&Submit=Search ", sAuthID64, sClientName, sClientID, detected_sourceban); Format(sMessage, sizeof(sMessage), "https://steamcommunity.com/profiles/%s\nName: %s\nSteamID: %s\nMap: %s\nClient is ban avoiding the ban: https://bans.unloze.com/index.php?p=banlist&searchText=%s&Submit=Search ", sAuthID64, sClientName, sClientID, currentMap, detected_sourceban);
if (g_sAvatarURL[client][0] != '\0') if (g_sAvatarURL[client][0] != '\0')
Discord_POST(DISCORD_BAN_DETECTOR, sMessage, true, sClientName, true, g_sAvatarURL[client], false); Discord_POST(DISCORD_BAN_DETECTOR, sMessage, true, sClientName, true, g_sAvatarURL[client], false);
else else
Discord_POST(DISCORD_BAN_DETECTOR, sMessage, true, sClientName, false, "", false); Discord_POST(DISCORD_BAN_DETECTOR, sMessage, true, sClientName, false, "", false);
Call_StartForward(g_hForwardDiscord);
Call_PushString(sClientName);
Call_PushString(sMessage);
Call_PushCell(4);
Call_Finish();
} }
public void CallAdmin_OnReportPost(int client, int target, const char[] reason) public void CallAdmin_OnReportPost(int client, int target, const char[] reason)
@@ -710,7 +617,7 @@ public void CallAdmin_OnReportPost(int client, int target, const char[] reason)
serverPort = CallAdmin_GetHostPort(); serverPort = CallAdmin_GetHostPort();
CallAdmin_GetHostName(sServerName, sizeof(sServerName)); CallAdmin_GetHostName(sServerName, sizeof(sServerName));
// Reporter wasn't a real client (initiated by a module) // Reporter wasnt a real client (initiated by a module)
if (client == REPORTER_CONSOLE) if (client == REPORTER_CONSOLE)
{ {
strcopy(sClientName, sizeof(sClientName), "Server/Console"); strcopy(sClientName, sizeof(sClientName), "Server/Console");
@@ -753,6 +660,12 @@ public void CallAdmin_OnReportPost(int client, int target, const char[] reason)
Discord_POST(DISCORD_CALLADMIN_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[client], false); Discord_POST(DISCORD_CALLADMIN_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[client], false);
else else
Discord_POST(DISCORD_CALLADMIN_WEBHOOKURL, sMessage, true, sUsername, false, "", false); Discord_POST(DISCORD_CALLADMIN_WEBHOOKURL, sMessage, true, sUsername, false, "", false);
Call_StartForward(g_hForwardDiscord);
Call_PushString(sUsername);
Call_PushString(sMessage);
Call_PushCell(5);
Call_Finish();
} }
public void CallAdmin_OnReportHandled(int client, int id) public void CallAdmin_OnReportHandled(int client, int id)
@@ -772,30 +685,14 @@ public void CallAdmin_OnReportHandled(int client, int id)
Discord_POST(DISCORD_CALLADMIN_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[client], false); Discord_POST(DISCORD_CALLADMIN_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[client], false);
else else
Discord_POST(DISCORD_CALLADMIN_WEBHOOKURL, sMessage, true, sUsername, false, "", false); Discord_POST(DISCORD_CALLADMIN_WEBHOOKURL, sMessage, true, sUsername, false, "", false);
Call_StartForward(g_hForwardDiscord);
Call_PushString(sUsername);
Call_PushString(sMessage);
Call_PushCell(5);
Call_Finish();
} }
//'
/*
public Action Oryx_OnTrigger(int client, int &level, char[] cheat)
{
char sUsername[MAX_NAME_LENGTH];
GetClientName(client, sUsername, sizeof(sUsername));
char currentMap[64];
GetCurrentMap(currentMap, sizeof(currentMap));
char sAuthID[32];
GetClientAuthId(client, AuthId_Steam2, sAuthID, sizeof(sAuthID), false);
char sMessage[1900];
Format(sMessage, sizeof(sMessage), "```%s - Tick: %d``````NAME: %N\nSTEAMID: %s\nTRIGGER LEVEL: %i\n%s```", currentMap, GetGameTickCount(), client, sAuthID, level, cheat);
if (g_sAvatarURL[client][0] != '\0')
Discord_POST(DISCORD_ANTIBHOPCHEAT_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[client], false);
else
Discord_POST(DISCORD_ANTIBHOPCHEAT_WEBHOOKURL, sMessage, true, sUsername, false, "", false);
return Plugin_Continue;
}
*/
public void AntiBhopCheat_OnClientDetected(int client, char[] sReason, char[] sStats) public void AntiBhopCheat_OnClientDetected(int client, char[] sReason, char[] sStats)
{ {
char sCurrentMap[64]; char sCurrentMap[64];
@@ -820,9 +717,15 @@ public void AntiBhopCheat_OnClientDetected(int client, char[] sReason, char[] sS
Discord_POST(DISCORD_ANTIBHOPCHEAT_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[client], false); Discord_POST(DISCORD_ANTIBHOPCHEAT_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[client], false);
else else
Discord_POST(DISCORD_ANTIBHOPCHEAT_WEBHOOKURL, sMessage, true, sUsername, false, "", false); Discord_POST(DISCORD_ANTIBHOPCHEAT_WEBHOOKURL, sMessage, true, sUsername, false, "", false);
Call_StartForward(g_hForwardDiscord);
Call_PushString(sUsername);
Call_PushString(sMessage);
Call_PushCell(2);
Call_Finish();
} }
public void EW_OnClientRestricted(int client, int target, int length) public void EW_OnClientRestricted(int client, int target, int hours, int minutes)
{ {
char sCurrentMap[64]; char sCurrentMap[64];
GetCurrentMap(sCurrentMap, sizeof(sCurrentMap)); GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));
@@ -834,12 +737,12 @@ public void EW_OnClientRestricted(int client, int target, int length)
Format(sDemoInfo, sizeof(sDemoInfo), "Not recording"); Format(sDemoInfo, sizeof(sDemoInfo), "Not recording");
char sMessageTemp[1280]; // 128 x 10 char sMessageTemp[1280]; // 128 x 10
if (length == -1) if (hours == -1)
Format(sMessageTemp, sizeof(sMessageTemp), "%L got temporarily restricted by %L", target, client); Format(sMessageTemp, sizeof(sMessageTemp), "%L got temporarily restricted by %L", target, client);
else if (length == 0) else if (hours == 0 && minutes == 0)
Format(sMessageTemp, sizeof(sMessageTemp), "%L got permanently restricted by %L", target, client); Format(sMessageTemp, sizeof(sMessageTemp), "%L got permanently restricted by %L", target, client);
else else
Format(sMessageTemp, sizeof(sMessageTemp), "%L got restricted by %L for %d minutes", target, client, length); Format(sMessageTemp, sizeof(sMessageTemp), "%L got restricted by %L for %d Hours and %d minutes playtime", target, client, hours, minutes);
char sMessage[1920]; // 128 x 15 char sMessage[1920]; // 128 x 15
Format(sMessage, sizeof(sMessage), "[ %s ][ %s ]```%s```", sCurrentMap, sDemoInfo, sMessageTemp); Format(sMessage, sizeof(sMessage), "[ %s ][ %s ]```%s```", sCurrentMap, sDemoInfo, sMessageTemp);
@@ -851,8 +754,13 @@ public void EW_OnClientRestricted(int client, int target, int length)
Discord_POST(DISCORD_ENTWATCH_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[target], false); Discord_POST(DISCORD_ENTWATCH_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[target], false);
else else
Discord_POST(DISCORD_ENTWATCH_WEBHOOKURL, sMessage, true, sUsername, false, "", false); Discord_POST(DISCORD_ENTWATCH_WEBHOOKURL, sMessage, true, sUsername, false, "", false);
}
Call_StartForward(g_hForwardDiscord);
Call_PushString(sUsername);
Call_PushString(sMessage);
Call_PushCell(3);
Call_Finish();
}
public void EW_OnClientUnrestricted(int client, int target) public void EW_OnClientUnrestricted(int client, int target)
{ {
@@ -878,4 +786,10 @@ public void EW_OnClientUnrestricted(int client, int target)
Discord_POST(DISCORD_ENTWATCH_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[target], false); Discord_POST(DISCORD_ENTWATCH_WEBHOOKURL, sMessage, true, sUsername, true, g_sAvatarURL[target], false);
else else
Discord_POST(DISCORD_ENTWATCH_WEBHOOKURL, sMessage, true, sUsername, false, "", false); Discord_POST(DISCORD_ENTWATCH_WEBHOOKURL, sMessage, true, sUsername, false, "", false);
Call_StartForward(g_hForwardDiscord);
Call_PushString(sUsername);
Call_PushString(sMessage);
Call_PushCell(3);
Call_Finish();
} }
+25
View File
@@ -0,0 +1,25 @@
#if defined _Discord_UNLOZE_Included
#endinput
#endif
#define _Discord_UNLOZE_Included
/**
* @noreturn
*/
forward void GetDiscordToStoat(const char[] username, const char[] message, int type);
/* Do not edit below this line */
public SharedPlugin __pl_Discord_UNLOZE =
{
name = "Discord_UNLOZE",
file = "Discord_UNLOZE.smx",
#if defined REQUIRE_PLUGIN
required = 1,
#else
required = 0,
#endif
};
+296
View File
@@ -0,0 +1,296 @@
#pragma semicolon 1
#include <ripext>
#include <basecomm>
#include <multicolors>
#include <sourcemod>
#include <Discord_UNLOZE>
#include <connect_announce>
#define API_URL ""
#define BOT_TOKEN ""
int i_port = 0;
public Plugin myinfo =
{
name = "livechat stoat",
author = "jenz",
description = "",
version = "1.2.0",
url = "www.unloze.com"
}
public void OnMapStart()
{
CreateTimer(5.0, SendMapNotificationToStoat);
}
public Action SendMapNotificationToStoat(Handle timer)
{
if (i_port == 27015)
{
char sText[1024];
char sCurrentmap[64];
GetCurrentMap(sCurrentmap, sizeof(sCurrentmap));
int NumPlayers = GetClientCount(false);
char serverIp[32];
ConVar ip_cvar = FindConVar("ip");
ip_cvar.GetString(serverIp, sizeof(serverIp));
Format(sText, sizeof(sText), "Map changed to: %s\nPlayerCount: %i/65\nQuick Join: %s:%i", sCurrentmap, NumPlayers, serverIp, i_port);
SendToMapNotifications_endpoint(sText);
}
return Plugin_Handled;
}
void SendToMapNotifications_endpoint(const char[] msg) {
HTTPRequest request = new HTTPRequest("");
request.SetHeader("Content-Type", "application/json");
char content[1024];
Format(content, sizeof(content), "**%s**", msg);
JSONObject data = new JSONObject();
data.SetString("content", content);
request.Post(data, OnHTTPResponse);
delete data;
}
public void OnPluginStart()
{
i_port = GetConVarInt(FindConVar("hostport"));
AddCommandListener(CommandListener_SmChat_stoat, "say");
RegServerCmd("sm_printtoallchat_stoat", Command_PrintToAllChat_stoat, "Stoat Integration");
RegServerCmd("sm_printtoadminchat_stoat", Command_PrintToAdminChat_stoat, "Stoat Integration");
}
public Action CommandListener_SmChat_stoat(int client, const char[] sCommand, int argc)
{
if (client <= 0)
return Plugin_Continue;
if (!IsValidClient(client))
{
return Plugin_Continue;
}
char sText[256];
char sUsername[32];
GetCmdArgString(sText, sizeof(sText));
GetClientName(client, sUsername, sizeof(sUsername));
if (FindCharInString(sText, '!') > 0)
{
return Plugin_Continue;
}
if (FindCharInString(sText, '/') > 0)
{
return Plugin_Continue;
}
if (sText[0] == '/')
{
return Plugin_Continue;
}
if (sText[0] == '!')
{
return Plugin_Continue;
}
//LogMessage("sText: %s. sText[0]: %c", sText, sText[0]);
if (i_port == 27015)
{
SendToStoat(sUsername, sText, "");
}
else if (i_port == 27017)
{
SendToStoat(sUsername, sText, "");
}
return Plugin_Continue;
}
void SendToStoat(const char[] name, const char[] msg, const char[] channel_id) {
char formatted_api[1024];
Format(formatted_api, sizeof(formatted_api), "%s%s%s", API_URL, channel_id, "/messages");
HTTPRequest request = new HTTPRequest(formatted_api);
request.SetHeader("x-bot-token", BOT_TOKEN);
request.SetHeader("Content-Type", "application/json");
char content[1024];
Format(content, sizeof(content), "**%s**: %s", name, msg);
JSONObject data = new JSONObject();
data.SetString("content", content);
request.Post(data, OnHTTPResponse);
delete data;
}
void OnHTTPResponse(HTTPResponse response, any value, const char[] error) {
/*
if (strlen(error) > 0)
{
LogError("HTTP request failed: %s", error);
return;
}
int status = response.Status;
if (status != HTTPStatus_OK && status != HTTPStatus_Created)
{
LogError("Unexpected HTTP status: %d", status);
// Optionally log the response body to see what the server returned
JSONObject body = view_as<JSONObject>(response.Data);
if (body != null)
{
char bodyStr[1024];
body.ToString(bodyStr, sizeof(bodyStr));
LogError("Response body: %s", bodyStr);
}
}
else
{
LogMessage("Message sent successfully, status: %d", status);
}
*/
}
public Action Command_PrintToAllChat_stoat(int args)
{
char sArgs[1024];
char sArgs2[1024];
GetCmdArg(1, sArgs, sizeof(sArgs));
GetCmdArg(2, sArgs2, sizeof(sArgs2));
CPrintToChatAll("{azure}[Stoat] (ALL) %s: {white}%s", sArgs, sArgs2);
return Plugin_Handled;
}
public Action Command_PrintToAdminChat_stoat(int args)
{
char sArgs[1024];
char sArgs2[1024];
GetCmdArg(1, sArgs, sizeof(sArgs));
GetCmdArg(2, sArgs2, sizeof(sArgs2));
for(int i = 0; i < MAXPLAYERS; i++)
{
if (IsValidClient(i))
{
bool bAdmin = CheckCommandAccess(i, "", ADMFLAG_GENERIC, true);
if (bAdmin)
CPrintToChat(i, "{green}(Stoat ADMINS) %s: {yellow}%s", sArgs, sArgs2);
}
}
return Plugin_Handled;
}
//ai SLOP
void StripColorTags(char[] buffer, int maxlen)
{
int len = strlen(buffer);
int writeIndex = 0;
bool inside = false;
for (int i = 0; i < len; i++)
{
if (buffer[i] == '{')
{
inside = true;
continue;
}
else if (buffer[i] == '}')
{
inside = false;
continue;
}
if (!inside && writeIndex < maxlen - 1)
{
buffer[writeIndex] = buffer[i];
writeIndex++;
}
}
buffer[writeIndex] = '\0';
}
public void GetPlayerConnectMessage(char[] message)
{
StripColorTags(message, strlen(message));
if (i_port == 27015)
{
SendToStoat("", message, "");
}
else if (i_port == 27017)
{
SendToStoat("", message, "");
}
}
public void GetDiscordToStoat(const char[] username, const char[] message, int type)
{
//type = 1. ADMINLOGS
//type = 2. ADMIN LOGS BHOPPING ZE
//type = 3. AMDIN LOGS entwatch
//type = 4. admin logs ze ban evasion
//type = 5. Calladmin
//type = 6. admin chat ze
if (i_port == 27017)
{
if (type == 1)
{
SendToStoat(username, message, "");
}
else if (type == 2)
{
SendToStoat(username, message, "");
}
return;
}
if (type == 1)
{
SendToStoat(username, message, "");
}
else if (type == 2)
{
SendToStoat(username, message, "");
}
else if (type == 3)
{
SendToStoat(username, message, "");
}
else if (type == 4)
{
SendToStoat(username, message, "");
}
else if (type == 5)
{
SendToStoat(username, message, "");
}
else if(type == 6)
{
SendToStoat(username, message, "");
}
}
stock bool IsValidClient(int client)
{
return (client > 0 && client <= MaxClients && IsClientInGame(client));
}
File diff suppressed because it is too large Load Diff
+587
View File
@@ -0,0 +1,587 @@
#include <sourcemod>
#include <ripext>
#include <geoip>
#define EVENTNOTIFIER_CORE
#include <EventNotifier>
#pragma semicolon 1
#pragma newdecls required
#define PLUGIN_VERSION "1.1"
#define TIMEZONE_FILE "configs/eventnotifier_timezones.cfg"
#define TIMEZONE_NAME_LEN 64
enum struct TzPeriod
{
int start; // unix timestamp this offset applies from
int offset; // seconds east of UTC
char abbr[12];
}
ConVar g_cvUrl;
ConVar g_cvServer;
ConVar g_cvInterval;
ConVar g_cvFallbackZone;
ArrayList g_Events;
Handle g_hRefreshTimer;
bool g_bFetching;
bool g_bEverFetched;
GlobalForward g_fwdOnEventsUpdated;
// Zone name -> ArrayList of TzPeriod, ascending by start.
StringMap g_Timezones;
char g_sClientZone[MAXPLAYERS + 1][TIMEZONE_NAME_LEN];
public Plugin myinfo =
{
name = "EventNotifier",
author = "marisakirisame.net",
description = "Fetches upcoming Unloze events and exposes them to other plugins",
version = PLUGIN_VERSION,
url = "https://marisakirisame.net"
};
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
CreateNative("EventNotifier_GetEventCount", Native_GetEventCount);
CreateNative("EventNotifier_HasFetched", Native_HasFetched);
CreateNative("EventNotifier_FindEventByNumber", Native_FindEventByNumber);
CreateNative("EventNotifier_GetEvent", Native_GetEvent);
CreateNative("EventNotifier_Refresh", Native_Refresh);
CreateNative("EventNotifier_FormatClientTime", Native_FormatClientTime);
RegPluginLibrary("EventNotifier");
return APLRes_Success;
}
public void OnPluginStart()
{
g_Events = new ArrayList(sizeof(EventInfo));
g_fwdOnEventsUpdated = new GlobalForward("EventNotifier_OnEventsUpdated", ET_Ignore);
g_cvUrl = CreateConVar("sm_event_url", "https://marisakirisame.net/unloze-events.json",
"Base URL of the Unloze events API (query parameters are appended automatically)");
g_cvServer = CreateConVar("sm_event_server", "ze",
"Value of the 'server' query parameter sent to the events API");
g_cvInterval = CreateConVar("sm_event_refresh_interval", "1800",
"How often to re-fetch the event list, in seconds", _, true, 30.0);
g_cvFallbackZone = CreateConVar("sm_event_fallback_timezone", "UTC",
"Timezone (IANA name, e.g. Europe/London) used for players whose timezone cannot be looked up");
g_cvUrl.AddChangeHook(OnSourceChanged);
g_cvServer.AddChangeHook(OnSourceChanged);
g_cvInterval.AddChangeHook(OnIntervalChanged);
AutoExecConfig(true, "EventNotifier");
RegAdminCmd("sm_event_refresh", Command_EventRefresh, ADMFLAG_GENERIC, "Re-fetches the event list now");
LoadTimezones();
// Late load: look up everyone already on the server.
for (int client = 1; client <= MaxClients; client++)
{
if (IsClientConnected(client))
OnClientConnected(client);
}
}
public void OnClientConnected(int client)
{
g_sClientZone[client][0] = '\0';
if (IsFakeClient(client))
return;
char ip[32];
if (GetClientIP(client, ip, sizeof(ip)) && !GeoipTimezone(ip, g_sClientZone[client], sizeof(g_sClientZone[])))
g_sClientZone[client][0] = '\0';
}
public void OnClientDisconnect(int client)
{
g_sClientZone[client][0] = '\0';
}
public void OnConfigsExecuted()
{
RestartRefreshTimer();
FetchEvents();
}
public void OnSourceChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
FetchEvents();
}
public void OnIntervalChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
RestartRefreshTimer();
}
void RestartRefreshTimer()
{
delete g_hRefreshTimer;
g_hRefreshTimer = CreateTimer(g_cvInterval.FloatValue, Timer_Refresh, _, TIMER_REPEAT);
}
public Action Timer_Refresh(Handle timer)
{
FetchEvents();
return Plugin_Continue;
}
// ---------------------------------------------------------------------------
// Natives
// ---------------------------------------------------------------------------
public any Native_GetEventCount(Handle plugin, int numParams)
{
return g_Events.Length;
}
public any Native_HasFetched(Handle plugin, int numParams)
{
return g_bEverFetched;
}
public any Native_FindEventByNumber(Handle plugin, int numParams)
{
return FindEventByNumber(GetNativeCell(1));
}
public any Native_GetEvent(Handle plugin, int numParams)
{
int index = GetNativeCell(1);
int size = GetNativeCell(3);
if (size < sizeof(EventInfo))
return ThrowNativeError(SP_ERROR_NATIVE, "EventInfo buffer too small (%d < %d), recompile against the current EventNotifier.inc", size, sizeof(EventInfo));
if (index < 0 || index >= g_Events.Length)
return false;
EventInfo ev;
g_Events.GetArray(index, ev);
SetNativeArray(2, ev, sizeof(EventInfo));
return true;
}
public any Native_Refresh(Handle plugin, int numParams)
{
FetchEvents();
return 0;
}
public any Native_FormatClientTime(Handle plugin, int numParams)
{
int client = GetNativeCell(1);
if (client < 0 || client > MaxClients)
return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client);
int maxlen = GetNativeCell(4);
char[] buffer = new char[maxlen];
FormatClientTime(client, GetNativeCell(2), buffer, maxlen, GetNativeCell(5));
SetNativeString(3, buffer, maxlen);
return 0;
}
// ---------------------------------------------------------------------------
// Fetching
// ---------------------------------------------------------------------------
void FetchEvents()
{
if (g_bFetching)
return;
char url[256];
g_cvUrl.GetString(url, sizeof(url));
if (!url[0])
{
LogError("sm_event_url is empty, cannot fetch events");
return;
}
char server[64];
g_cvServer.GetString(server, sizeof(server));
HTTPRequest request = new HTTPRequest(url);
request.AppendQueryParam("server", "%s", server);
request.AppendQueryParam("future", "1");
request.SetHeader("Accept", "application/json");
request.Timeout = 20;
g_bFetching = true;
request.Get(OnEventsFetched);
}
public void OnEventsFetched(HTTPResponse response, any value, const char[] error)
{
g_bFetching = false;
if (error[0])
{
LogError("Event fetch failed: %s", error);
return;
}
if (response.Status != HTTPStatus_OK)
{
LogError("Event fetch failed: HTTP %d", response.Status);
return;
}
JSON data = response.Data;
if (data == null)
{
LogError("Event fetch failed: response was not valid JSON");
return;
}
JSONObject root = view_as<JSONObject>(data);
if (!root.HasKey("events") || root.IsNull("events"))
{
LogError("Event fetch failed: response has no 'events' array");
return;
}
JSONArray events = view_as<JSONArray>(root.Get("events"));
ArrayList parsed = new ArrayList(sizeof(EventInfo));
int count = events.Length;
for (int i = 0; i < count; i++)
{
if (events.IsNull(i))
continue;
JSONObject obj = view_as<JSONObject>(events.Get(i));
EventInfo ev;
ParseEvent(obj, ev);
delete obj;
parsed.PushArray(ev);
}
delete events;
// Soonest first so "the next N events" means something regardless of API order.
parsed.SortCustom(SortEventsByStart);
int previous = g_Events.Length;
delete g_Events;
g_Events = parsed;
if (!g_bEverFetched || previous != g_Events.Length)
LogMessage("Fetched %d upcoming event(s)", g_Events.Length);
g_bEverFetched = true;
Call_StartForward(g_fwdOnEventsUpdated);
Call_Finish();
}
// Ascending by start time, with unknown (0) start times last.
int SortEventsByStart(int index1, int index2, Handle array, Handle hndl)
{
ArrayList list = view_as<ArrayList>(array);
int a = list.Get(index1, EventInfo::start);
int b = list.Get(index2, EventInfo::start);
if (a == b)
return 0;
if (a == 0)
return 1;
if (b == 0)
return -1;
return (a < b) ? -1 : 1;
}
void ParseEvent(JSONObject obj, EventInfo ev)
{
char buf[64];
ev.number = (obj.HasKey("eventNumber") && !obj.IsNull("eventNumber")) ? obj.GetInt("eventNumber") : 0;
GetJsonString(obj, "name", ev.name, sizeof(ev.name));
if (!ev.name[0])
GetJsonString(obj, "title", ev.name, sizeof(ev.name));
GetJsonString(obj, "server", ev.server, sizeof(ev.server));
GetJsonString(obj, "rewards", ev.rewards, sizeof(ev.rewards));
GetJsonString(obj, "leader", ev.leader, sizeof(ev.leader));
GetJsonString(obj, "kickers", ev.kickers, sizeof(ev.kickers));
ev.start = 0;
if (GetJsonString(obj, "start", buf, sizeof(buf)))
ev.start = ParseIso8601(buf);
ev.end = 0;
if (GetJsonString(obj, "end", buf, sizeof(buf)))
ev.end = ParseIso8601(buf);
ev.maps[0] = '\0';
if (obj.HasKey("maps") && !obj.IsNull("maps"))
{
JSONArray maps = view_as<JSONArray>(obj.Get("maps"));
int len = maps.Length;
for (int i = 0; i < len; i++)
{
char map[128];
if (!maps.GetString(i, map, sizeof(map)) || !map[0])
continue;
if (ev.maps[0])
StrCat(ev.maps, sizeof(ev.maps), ", ");
StrCat(ev.maps, sizeof(ev.maps), map);
}
delete maps;
}
}
bool GetJsonString(JSONObject obj, const char[] key, char[] buffer, int maxlen)
{
buffer[0] = '\0';
if (!obj.HasKey(key) || obj.IsNull(key))
return false;
return obj.GetString(key, buffer, maxlen) && buffer[0] != '\0';
}
// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------
public Action Command_EventRefresh(int client, int args)
{
if (g_bFetching)
{
ReplyToCommand(client, "[Events] A fetch is already in progress.");
return Plugin_Handled;
}
FetchEvents();
ReplyToCommand(client, "[Events] Refreshing the event list.");
return Plugin_Handled;
}
int FindEventByNumber(int number)
{
int count = g_Events.Length;
for (int i = 0; i < count; i++)
{
if (g_Events.Get(i, EventInfo::number) == number)
return i;
}
return -1;
}
// ---------------------------------------------------------------------------
// Timezones
// ---------------------------------------------------------------------------
// Loads the zone table generated by tools/gen_timezones.py.
void LoadTimezones()
{
g_Timezones = new StringMap();
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), TIMEZONE_FILE);
KeyValues kv = new KeyValues("Timezones");
if (!kv.ImportFromFile(path))
{
LogError("Could not read %s, all times will be shown in UTC", path);
delete kv;
return;
}
if (kv.GotoFirstSubKey())
{
do
{
char zone[TIMEZONE_NAME_LEN];
kv.GetSectionName(zone, sizeof(zone));
ArrayList periods = new ArrayList(sizeof(TzPeriod));
if (kv.GotoFirstSubKey(false))
{
do
{
char key[16], value[32], parts[2][16];
kv.GetSectionName(key, sizeof(key));
kv.GetString(NULL_STRING, value, sizeof(value));
if (ExplodeString(value, " ", parts, sizeof(parts), sizeof(parts[])) != 2)
continue;
TzPeriod period;
period.start = StringToInt(key);
period.offset = StringToInt(parts[0]);
strcopy(period.abbr, sizeof(period.abbr), parts[1]);
periods.PushArray(period);
} while (kv.GotoNextKey(false));
kv.GoBack();
}
if (periods.Length > 0)
{
periods.SortCustom(SortPeriodsByStart);
g_Timezones.SetValue(zone, periods);
}
else
{
delete periods;
}
} while (kv.GotoNextKey());
}
delete kv;
LogMessage("Loaded %d timezone(s)", g_Timezones.Size);
}
int SortPeriodsByStart(int index1, int index2, Handle array, Handle hndl)
{
ArrayList list = view_as<ArrayList>(array);
int a = list.Get(index1, TzPeriod::start);
int b = list.Get(index2, TzPeriod::start);
return (a == b) ? 0 : ((a < b) ? -1 : 1);
}
// Finds the offset and abbreviation in effect at stamp for the client's zone,
// falling back to sm_event_fallback_timezone and then plain UTC.
void GetClientZonePeriod(int client, int stamp, TzPeriod out)
{
ArrayList periods;
if (client <= 0 || !g_sClientZone[client][0] || !g_Timezones.GetValue(g_sClientZone[client], periods))
{
char fallback[TIMEZONE_NAME_LEN];
g_cvFallbackZone.GetString(fallback, sizeof(fallback));
if (!g_Timezones.GetValue(fallback, periods))
periods = null;
}
if (periods == null)
{
out.start = 0;
out.offset = 0;
strcopy(out.abbr, sizeof(out.abbr), "UTC");
return;
}
periods.GetArray(0, out);
int count = periods.Length;
for (int i = 1; i < count && periods.Get(i, TzPeriod::start) <= stamp; i++)
periods.GetArray(i, out);
}
// Formats stamp in the client's timezone as "Thu 01 Oct 2026, 20:00 BST"
// (or without the year). FormatTime can't be used, it only knows the server's zone.
void FormatClientTime(int client, int stamp, char[] buffer, int maxlen, bool withYear)
{
static const char weekdays[][] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static const char months[][] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
TzPeriod period;
GetClientZonePeriod(client, stamp, period);
int local = stamp + period.offset;
int days = local / 86400;
int secs = local % 86400;
int year, month, day;
CivilFromDays(days, year, month, day);
// 1970-01-01 was a Thursday.
int weekday = (days + 4) % 7;
if (withYear)
Format(buffer, maxlen, "%s %02d %s %d, %02d:%02d %s", weekdays[weekday], day, months[month - 1], year, secs / 3600, (secs % 3600) / 60, period.abbr);
else
Format(buffer, maxlen, "%s %02d %s, %02d:%02d %s", weekdays[weekday], day, months[month - 1], secs / 3600, (secs % 3600) / 60, period.abbr);
}
// ---------------------------------------------------------------------------
// Time helpers
// ---------------------------------------------------------------------------
// Parses dates into a UTC unix timestamp.
// Returns 0 when the string cannot be parsed.
int ParseIso8601(const char[] str)
{
int year, month, day, hour, minute, second;
if (!ReadFixedInt(str, 0, 4, year) || str[4] != '-'
|| !ReadFixedInt(str, 5, 2, month) || str[7] != '-'
|| !ReadFixedInt(str, 8, 2, day))
return 0;
int pos = 10;
if (str[pos] == 'T' || str[pos] == ' ')
{
if (!ReadFixedInt(str, 11, 2, hour) || str[13] != ':' || !ReadFixedInt(str, 14, 2, minute))
return 0;
pos = 16;
if (str[pos] == ':')
{
if (!ReadFixedInt(str, 17, 2, second))
return 0;
pos = 19;
}
if (str[pos] == '.')
{
pos++;
while (IsCharNumeric(str[pos]))
pos++;
}
}
int offset = 0;
if (str[pos] == '+' || str[pos] == '-')
{
int sign = (str[pos] == '-') ? -1 : 1;
int offHour, offMinute;
if (!ReadFixedInt(str, pos + 1, 2, offHour))
return 0;
int minPos = (str[pos + 3] == ':') ? pos + 4 : pos + 3;
ReadFixedInt(str, minPos, 2, offMinute);
offset = sign * (offHour * 3600 + offMinute * 60);
}
int days = DaysFromCivil(year, month, day);
return days * 86400 + hour * 3600 + minute * 60 + second - offset;
}
bool ReadFixedInt(const char[] str, int pos, int digits, int &out)
{
out = 0;
for (int i = 0; i < digits; i++)
{
if (!IsCharNumeric(str[pos + i]))
return false;
out = out * 10 + (str[pos + i] - '0');
}
return true;
}
// Days since 1970-01-01 for a proper date.
int DaysFromCivil(int y, int m, int d)
{
y -= (m <= 2) ? 1 : 0;
int era = ((y >= 0) ? y : y - 399) / 400;
int yoe = y - era * 400;
int doy = (153 * (m + ((m > 2) ? -3 : 9)) + 2) / 5 + d - 1;
int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + doe - 719468;
}
// Inverse of DaysFromCivil: date for a day count since 1970-01-01.
void CivilFromDays(int z, int &y, int &m, int &d)
{
z += 719468;
int era = ((z >= 0) ? z : z - 146096) / 146097;
int doe = z - era * 146097;
int yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
int doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
int mp = (5 * doy + 2) / 153;
d = doy - (153 * mp + 2) / 5 + 1;
m = (mp < 10) ? mp + 3 : mp - 9;
y = yoe + era * 400 + ((m <= 2) ? 1 : 0);
}
@@ -0,0 +1,185 @@
#include <sourcemod>
#include <multicolors>
#include <EventNotifier>
#pragma semicolon 1
#pragma newdecls required
#define PLUGIN_VERSION "1.1"
// Shortest automatic announcement interval, in seconds. Values below this are
// raised to it; 0 or below disables the timer entirely.
#define MIN_ANNOUNCE_INTERVAL 60.0
// Visible characters kept from the event name in chat.
#define ANNOUNCE_NAME_MAX 64
// Visible characters allowed for the map list before " +N more" is appended.
#define ANNOUNCE_MAPS_BUDGET 120
ConVar g_cvInterval;
ConVar g_cvCount;
Handle g_hAnnounceTimer;
int g_iRotation;
public Plugin myinfo =
{
name = "EventNotifier Announcements",
author = "marisakirisame.net",
description = "Periodically announces upcoming Unloze events in chat",
version = PLUGIN_VERSION,
url = "https://marisakirisame.net"
};
public void OnPluginStart()
{
g_cvInterval = CreateConVar("sm_event_announce_interval", "120",
"Seconds between event announcements in chat, -1 to disable automatic announcements (sm_event_announce still works)", _, true, -1.0);
g_cvCount = CreateConVar("sm_event_announce_count", "3",
"Rotate announcements through the next N upcoming events, one per announcement", _, true, 1.0, true, 20.0);
// CreateConVar keeps the old bounds when the cvar already exists (plugin reload),
// so apply the lower bound explicitly.
g_cvInterval.SetBounds(ConVarBound_Lower, true, -1.0);
g_cvInterval.AddChangeHook(OnIntervalChanged);
AutoExecConfig(true, "EventNotifierAnnouncements");
RegAdminCmd("sm_event_announce", Command_Announce, ADMFLAG_GENERIC, "Posts the next event announcement in chat now");
}
public void OnConfigsExecuted()
{
RestartAnnounceTimer();
}
public void OnIntervalChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
RestartAnnounceTimer();
}
void RestartAnnounceTimer()
{
delete g_hAnnounceTimer;
float interval = g_cvInterval.FloatValue;
if (interval <= 0.0)
return;
if (interval < MIN_ANNOUNCE_INTERVAL)
interval = MIN_ANNOUNCE_INTERVAL;
g_hAnnounceTimer = CreateTimer(interval, Timer_Announce, _, TIMER_REPEAT);
}
public Action Timer_Announce(Handle timer)
{
PostAnnouncement();
return Plugin_Continue;
}
public Action Command_Announce(int client, int args)
{
if (PostAnnouncement())
ReplyToCommand(client, "[Events] Announcement posted.");
else
ReplyToCommand(client, "[Events] No upcoming events to announce.");
return Plugin_Handled;
}
// ---------------------------------------------------------------------------
// Announcement
// ---------------------------------------------------------------------------
// Posts one event from the rotation window. Returns false when there is nothing to post.
bool PostAnnouncement()
{
int count = EventNotifier_GetEventCount();
if (count <= 0)
return false;
int window = g_cvCount.IntValue;
if (window > count)
window = count;
int index = g_iRotation % window;
g_iRotation = (index + 1) % window;
EventInfo ev;
if (!EventNotifier_GetEvent(index, ev))
return false;
char name[ANNOUNCE_NAME_MAX + 1];
strcopy(name, sizeof(name), ev.name);
if (!name[0])
strcopy(name, sizeof(name), "Unnamed event");
if (ev.start <= 0)
{
CPrintToChatAll("{darkorange}[Events] {yellow}#%d: {lightgreen}%s {grey}date TBA", ev.number, name);
}
else
{
// Each player gets the start time in their own timezone.
bool started = GetTime() >= ev.start;
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client) || IsFakeClient(client))
continue;
char when[48];
EventNotifier_FormatClientTime(client, ev.start, when, sizeof(when), false);
// CPrintToChatEx rather than CPrintToChat: the latter only accepts string format arguments.
CPrintToChatEx(client, client, "{darkorange}[Events] {yellow}#%d: {lightgreen}%s %s %s", ev.number, name,
started ? "{orchid}started" : "{white}starts{lime}", when);
}
}
if (ev.maps[0])
{
char mapsLine[ANNOUNCE_MAPS_BUDGET + 132];
int more = BuildMapsLine(ev.maps, mapsLine, sizeof(mapsLine), ANNOUNCE_MAPS_BUDGET);
if (more > 0)
CPrintToChatAll("{darkorange}[Events] {white}Maps: {lightgreen}%s {grey}+%d more", mapsLine, more);
else
CPrintToChatAll("{darkorange}[Events] {white}Maps: {lightgreen}%s", mapsLine);
}
CPrintToChatAll("{darkorange}[Events] {white}Type {lime}!event %d {white}for details and the full map list.", ev.number);
return true;
}
// Copies ", "-separated items from maps into out while the visible length stays
// within budget. Always emits at least the first item. Returns how many items
// were left out (0 when everything fit).
int BuildMapsLine(const char[] maps, char[] out, int maxlen, int budget)
{
// Count from the raw string so "+N more" is right even past the explode limit.
int total = 1;
for (int i = 0; maps[i]; i++)
{
if (maps[i] == ',' && maps[i + 1] == ' ')
total++;
}
char parts[32][128];
int count = ExplodeString(maps, ", ", parts, sizeof(parts), sizeof(parts[]));
out[0] = '\0';
int used = 0;
int shown = 0;
for (int i = 0; i < count; i++)
{
int add = strlen(parts[i]) + ((shown > 0) ? 2 : 0);
if (shown > 0 && used + add > budget)
break;
if (shown > 0)
StrCat(out, maxlen, ", ");
StrCat(out, maxlen, parts[i]);
used += add;
shown++;
}
return total - shown;
}
@@ -0,0 +1,231 @@
#include <sourcemod>
#include <EventNotifier>
#pragma semicolon 1
#pragma newdecls required
#define PLUGIN_VERSION "1.2"
#define CHAT_PREFIX "\x04[Events]\x01"
#define PANEL_LINE_WIDTH 60
#define KEY_BACK 8
#define KEY_EXIT 10
public Plugin myinfo =
{
name = "EventNotifier Menu",
author = "marisakirisame.net",
description = "Shows upcoming Unloze events in-game via sm_event",
version = PLUGIN_VERSION,
url = "https://marisakirisame.net"
};
public void OnPluginStart()
{
RegConsoleCmd("sm_event", Command_EventMenu, "Shows upcoming Unloze events. Usage: sm_event [event number]");
RegConsoleCmd("sm_events", Command_EventMenu, "Shows upcoming Unloze events. Usage: sm_events [event number]");
}
// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------
public Action Command_EventMenu(int client, int args)
{
if (client <= 0 || !IsClientInGame(client))
{
ReplyToCommand(client, "This command can only be used in-game.");
return Plugin_Handled;
}
if (EventNotifier_GetEventCount() == 0)
{
if (EventNotifier_HasFetched())
PrintToChat(client, "%s There are no upcoming events.", CHAT_PREFIX);
else
PrintToChat(client, "%s The event list is not available right now, try again later.", CHAT_PREFIX);
return Plugin_Handled;
}
if (args >= 1)
{
char arg[16];
GetCmdArg(1, arg, sizeof(arg));
int number = StringToInt(arg);
int index = EventNotifier_FindEventByNumber(number);
if (index == -1)
{
PrintToChat(client, "%s Event #%d is not in the upcoming list.", CHAT_PREFIX, number);
return Plugin_Handled;
}
ShowEventDetails(client, index);
return Plugin_Handled;
}
ShowEventList(client);
return Plugin_Handled;
}
// ---------------------------------------------------------------------------
// Menus
// ---------------------------------------------------------------------------
void ShowEventList(int client)
{
Menu menu = new Menu(EventList_Handler);
menu.SetTitle("Upcoming Unloze events");
int count = EventNotifier_GetEventCount();
for (int i = 0; i < count; i++)
{
EventInfo ev;
if (!EventNotifier_GetEvent(i, ev))
continue;
char info[16], display[EVENT_STR_LEN + 64];
IntToString(ev.number, info, sizeof(info));
Format(display, sizeof(display), "%d: %s", ev.number, ev.name);
if (ev.start > 0)
{
char when[48];
EventNotifier_FormatClientTime(client, ev.start, when, sizeof(when), false);
Format(display, sizeof(display), "%s (%s)", display, when);
}
menu.AddItem(info, display);
}
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
public int EventList_Handler(Menu menu, MenuAction action, int param1, int param2)
{
switch (action)
{
case MenuAction_Select:
{
char info[16];
menu.GetItem(param2, info, sizeof(info));
int index = EventNotifier_FindEventByNumber(StringToInt(info));
if (index == -1)
{
PrintToChat(param1, "%s That event is no longer in the upcoming list.", CHAT_PREFIX);
ShowEventList(param1);
}
else
{
ShowEventDetails(param1, index);
}
}
case MenuAction_End:
{
delete menu;
}
}
return 0;
}
void ShowEventDetails(int client, int index)
{
EventInfo ev;
if (!EventNotifier_GetEvent(index, ev))
{
PrintToChat(client, "%s That event is no longer in the upcoming list.", CHAT_PREFIX);
return;
}
char line[EVENT_STR_LEN + 32];
Panel panel = new Panel();
Format(line, sizeof(line), "Event #%d: %s", ev.number, ev.name);
panel.SetTitle(line);
panel.DrawText(" ");
if (ev.start > 0)
{
char when[64];
EventNotifier_FormatClientTime(client, ev.start, when, sizeof(when));
Format(line, sizeof(line), "Date: %s", when);
panel.DrawText(line);
EventNotifier_FormatRelativeTime(ev.start, ev.end, line, sizeof(line));
panel.DrawText(line);
}
else
{
panel.DrawText("Date: unknown");
}
Format(line, sizeof(line), "Server: %s", ev.server[0] ? ev.server : "unknown");
panel.DrawText(line);
if (ev.maps[0])
DrawWrappedList(panel, "Maps: ", ev.maps);
else
panel.DrawText("Maps: none listed");
Format(line, sizeof(line), "Rewards: %s", ev.rewards[0] ? ev.rewards : "none");
panel.DrawText(line);
Format(line, sizeof(line), "Leader: %s", ev.leader[0] ? ev.leader : "none");
panel.DrawText(line);
Format(line, sizeof(line), "Kickers: %s", ev.kickers[0] ? ev.kickers : "none");
panel.DrawText(line);
panel.DrawText(" ");
panel.CurrentKey = KEY_BACK;
panel.DrawItem("Back");
panel.CurrentKey = KEY_EXIT;
panel.DrawItem("Exit");
panel.Send(client, EventDetails_Handler, MENU_TIME_FOREVER);
delete panel;
}
public int EventDetails_Handler(Menu menu, MenuAction action, int param1, int param2)
{
if (action == MenuAction_Select && param2 == KEY_BACK)
{
if (IsClientInGame(param1))
Command_EventMenu(param1, 0);
}
return 0;
}
// Draws "prefix a, b, c" and continues onto new indented lines when a line
// would exceed PANEL_LINE_WIDTH characters, splitting only at the commas.
void DrawWrappedList(Panel panel, const char[] prefix, const char[] list)
{
char parts[32][128];
int count = ExplodeString(list, ", ", parts, sizeof(parts), sizeof(parts[]));
char line[PANEL_LINE_WIDTH + 160];
strcopy(line, sizeof(line), prefix);
bool lineHasItems = false;
for (int i = 0; i < count; i++)
{
char item[132];
strcopy(item, sizeof(item), parts[i]);
if (i < count - 1)
StrCat(item, sizeof(item), ",");
if (lineHasItems && strlen(line) + 1 + strlen(item) > PANEL_LINE_WIDTH)
{
panel.DrawText(line);
strcopy(line, sizeof(line), " ");
lineHasItems = false;
}
else if (lineHasItems)
{
StrCat(line, sizeof(line), " ");
}
StrCat(line, sizeof(line), item);
lineHasItems = true;
}
if (lineHasItems)
panel.DrawText(line);
}
@@ -0,0 +1,157 @@
#if defined _EventNotifier_included
#endinput
#endif
#define _EventNotifier_included
#define EVENT_STR_LEN 128
#define EVENT_MAPS_LEN 1024
enum struct EventInfo
{
int number;
int start; // unix timestamp (UTC), 0 if unknown
int end; // unix timestamp (UTC), 0 if unknown
char name[EVENT_STR_LEN];
char server[EVENT_STR_LEN];
char rewards[EVENT_STR_LEN];
char leader[EVENT_STR_LEN];
char kickers[EVENT_STR_LEN];
char maps[EVENT_MAPS_LEN]; // ", "-joined list
}
#if !defined EVENTNOTIFIER_CORE
/**
* Number of events in the last successful fetch (0 before the first fetch).
*
* @return Event count.
*/
native int EventNotifier_GetEventCount();
/**
* Whether at least one fetch has succeeded since the plugin loaded.
*
* @return True once the event list has been fetched.
*/
native bool EventNotifier_HasFetched();
/**
* Finds an event by its event number.
*
* @param number Event number.
* @return Index into the event list, or -1 if not found.
*/
native int EventNotifier_FindEventByNumber(int number);
/**
* Copies an event into the caller's EventInfo.
*
* @param index Index into the event list.
* @param ev EventInfo to fill.
* @param size Size of ev, must be sizeof(EventInfo).
* @return True on success, false if index is out of range.
* @error Buffer smaller than sizeof(EventInfo).
*/
native bool EventNotifier_GetEvent(int index, any[] ev, int size = sizeof(EventInfo));
/**
* Starts a fetch of the event list now. No-op if a fetch is already running.
*/
native void EventNotifier_Refresh();
/**
* Formats a timestamp in the client's own timezone (looked up via GeoIP),
* e.g. "Thu 01 Oct 2026, 20:00 BST". Clients whose timezone is unknown get
* sm_event_fallback_timezone.
*
* @param client Client index, 0 for the fallback timezone.
* @param timestamp Unix timestamp (UTC).
* @param buffer Destination buffer.
* @param maxlen Size of buffer.
* @param withYear Whether to include the year.
* @error Invalid client index.
*/
native void EventNotifier_FormatClientTime(int client, int timestamp, char[] buffer, int maxlen, bool withYear = true);
/**
* Called after every successful fetch, once the event list has been replaced.
*/
forward void EventNotifier_OnEventsUpdated();
#endif // EVENTNOTIFIER_CORE
// ---------------------------------------------------------------------------
// Display helpers
// ---------------------------------------------------------------------------
/**
* Formats a duration in seconds as "1d 2h 3m", "2h 3m" or "3m".
*/
stock void EventNotifier_FormatDuration(int seconds, char[] buffer, int maxlen)
{
int days = seconds / 86400;
int hours = (seconds % 86400) / 3600;
int minutes = (seconds % 3600) / 60;
if (days > 0)
Format(buffer, maxlen, "%dd %dh %dm", days, hours, minutes);
else if (hours > 0)
Format(buffer, maxlen, "%dh %dm", hours, minutes);
else
Format(buffer, maxlen, "%dm", minutes);
}
/**
* Formats an event's status relative to now as a "Status: ..." line.
*/
stock void EventNotifier_FormatRelativeTime(int start, int end, char[] buffer, int maxlen)
{
int now = GetTime();
if (end > 0 && now >= start && now < end)
{
strcopy(buffer, maxlen, "Status: In progress now!");
return;
}
char span[32];
if (now >= start)
{
EventNotifier_FormatDuration(now - start, span, sizeof(span));
Format(buffer, maxlen, "Status: Started %s ago", span);
}
else
{
EventNotifier_FormatDuration(start - now, span, sizeof(span));
Format(buffer, maxlen, "Status: Starts in %s", span);
}
}
#if !defined EVENTNOTIFIER_CORE
public SharedPlugin __pl_EventNotifier =
{
name = "EventNotifier",
file = "EventNotifier.smx",
#if defined REQUIRE_PLUGIN
required = 1
#else
required = 0
#endif
};
#if !defined REQUIRE_PLUGIN
public void __pl_EventNotifier_SetNTVOptional()
{
MarkNativeAsOptional("EventNotifier_GetEventCount");
MarkNativeAsOptional("EventNotifier_HasFetched");
MarkNativeAsOptional("EventNotifier_FindEventByNumber");
MarkNativeAsOptional("EventNotifier_GetEvent");
MarkNativeAsOptional("EventNotifier_Refresh");
MarkNativeAsOptional("EventNotifier_FormatClientTime");
}
#endif
#endif // EVENTNOTIFIER_CORE
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""
Generates configs/eventnotifier_timezones.cfg from the system tzdata.
Each zone lists "<utc timestamp>" "<offset seconds> <abbreviation>" pairs; the
"0" entry is the offset in effect before the first transition in range.
Regenerate before END_YEAR runs out (32-bit timestamps end in 2038 anyway).
"""
import re
import zoneinfo
from datetime import datetime, timedelta, timezone
from pathlib import Path
START_YEAR = 2025
END_YEAR = 2037
OUT = Path(__file__).resolve().parent.parent / "configs" / "eventnotifier_timezones.cfg"
def utc(ts):
return datetime.fromtimestamp(ts, timezone.utc)
def state(zone, ts):
local = utc(ts).astimezone(zone)
return int(local.utcoffset().total_seconds()), local.tzname()
def abbreviation(name, offset):
# tzdata uses numeric names like "+03" / "-0330" for zones without a real abbreviation.
if name and re.fullmatch(r"[A-Za-z]+", name):
return name
sign = "+" if offset >= 0 else "-"
hours, rem = divmod(abs(offset), 3600)
minutes = rem // 60
if hours == 0 and minutes == 0:
return "UTC"
return f"UTC{sign}{hours}:{minutes:02d}" if minutes else f"UTC{sign}{hours}"
def transitions(zone):
start = int(datetime(START_YEAR, 1, 1, tzinfo=timezone.utc).timestamp())
end = int(datetime(END_YEAR, 12, 31, tzinfo=timezone.utc).timestamp())
step = int(timedelta(days=1).total_seconds())
periods = [(0, *state(zone, start))]
prev_ts, prev = start, periods[0][1:]
ts = start + step
while ts <= end:
cur = state(zone, ts)
if cur != prev:
# Bisect to the first second of the new state.
lo, hi = prev_ts, ts
while hi - lo > 1:
mid = (lo + hi) // 2
if state(zone, mid) == prev:
lo = mid
else:
hi = mid
periods.append((hi, *state(zone, hi)))
prev = cur
prev_ts = ts
ts += step
return periods
def main():
lines = ["// Generated by tools/gen_timezones.py, do not edit by hand.", '"Timezones"', "{"]
for name in sorted(zoneinfo.available_timezones()):
if name.startswith(("posix/", "right/")) or name in ("Factory", "localtime"):
continue
zone = zoneinfo.ZoneInfo(name)
lines.append(f'\t"{name}"')
lines.append("\t{")
for ts, offset, tzname in transitions(zone):
lines.append(f'\t\t"{ts}"\t"{offset} {abbreviation(tzname, offset)}"')
lines.append("\t}")
lines.append("}")
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text("\n".join(lines) + "\n")
print(f"Wrote {OUT}")
if __name__ == "__main__":
main()
+14 -2
View File
@@ -41,9 +41,21 @@ public void OnConVarQueryFinished(QueryCookie cookie, int client, ConVarQueryRes
return; return;
if (result != ConVarQuery_Okay) if (result != ConVarQuery_Okay)
LogError("Could not query ConVar \"cvarName\" for %N", client); LogError("Could not query ConVar fps_max for %N", client);
int len = strlen(cvarValue);
for (int i = 0; i < len; i++)
{
if (!IsCharNumeric(cvarValue[i]))
{
KickClient(client, "Invalid fps_max Format");
return;
}
}
//fps_max 0 is probably acceptable
if (StringToInt(cvarValue) == 0)
return;
if (StringToInt(cvarValue) < 58) if (StringToInt(cvarValue) < 58)
KickClient(client, "Your fps_max is invalid, please set it to a higher value (>59)"); KickClient(client, "Your fps_max is invalid, please set it to a higher value (>59)");
} }
+80
View File
@@ -0,0 +1,80 @@
HAFL-LIFE_2
NoobDnoRaq
IMBA
ТАЩИР
ebaw-kak-t34
h4rdc0r3
omfg]wiz
GOdlikebAby
SHITSHITSHIT
-Impulse-
SLAVA_UKRAINE
4ik-pyk
BigBoss
lolkalalka
muhahahahaha
GFLClan.ru
nide.gg
vk.com/nide_css
alex481516
alahakbar
HeadShot
HolySHIT
CRAY_ZEE
mlpro
Deadmemories
MaGothic
ILoveYouBaby
nevazhno
Tauren
Calypso
Hawk
(1)HAFL-LIFE_2
(1)NoobDnoRaq
(1)Hawk
(1)Tauren
(1)mlpro
(1)CRAY_ZEE
(1)NF-CSS.RU
(1)Calypso
(1)BigBoss
HAFL-LIFE_2
NoobDnoRaq
IMBA
ТАЩИР
ebaw-kak-t34
h4rdc0r3
omfg]wiz
GOdlikebAby
SHITSHITSHIT
-Impulse-
SLAVA_UKRAINE
4ik-pyk
BigBoss
lolkalalka
muhahahahaha
cp.powerhub.ru
NiDE.GG
vk.com/nide_css
alex481516
alahakbar
HeadShot
HolySHIT
CRAY_ZEE
mlpro
Deadmemories
MaGothic
ILoveYouBaby
nevazhno
Tauren
Calypso
Hawk
(1)HAFL-LIFE_2
(1)NoobDnoRaq
(1)Hawk
(1)Tauren
(1)mlpro
(1)CRAY_ZEE
(1)NF-CSS.RU
(1)Calypso
(1)BigBoss
-114
View File
@@ -1,114 +0,0 @@
Dwelitram
Gwyri
Caredus
Arerawia
Vilali
Astiwiel
Vardonydd
Ybaossa
Umyk
Nico50Pax
Onand
Thelian
Nydaleth
Chomarin
Traedien
Miev
Kaaede
Koamond
TheRottenBenson
BigLegend2017
TRIGGEREDHarambexXx
InPepe2016
xXxMaster2012
InBoixXx
TheKopsing
Cornelius
Gustavo
Bryant
Winfred
Nicolas
Mitchel
Dana
Carrol
Darell
Ruben
Jeromy
Wade
Scotty
Salvatore
Kory
Don
Morgan
Kurtis
Federico
Darin
css-ru
aimbot
lastkraftwagenfahrzeug
edger
clownface
слово
счастливый
kara
puta
meow
uncle sam
FunBun
Counter-Strike.Com.Ua
For-css.Ru
BOBO
Z3r0
ZeTo
Sakharov
Sache11
Mr. Dogenberg
Maus
Magikarpet
Miles
magick
James Lebron
jiZZ
Dobel
THE POPE OF DOPE
The Joker
that guy over there
Be happy :)
KingKong
Figger Nucker
noname
alexdu63
I put babies in the microwave
Tango!!
faggot killer
admin pidaras
MESSI>RONALDO
Gabe Newell
acetylsalicylic acid
Country-Steak: Sauce
chlamydia harvester
kyle_69
Shaggy's dog
rEVERSE
Piment d'espelette
FireHawk
Sgt. Pepper
krix.
imPulse_^_
Lardon
CAPS LOCK
$pussymoneyweed$
Low5
Bonerfart
Shiny Thanos
MARK NUTT
Legless Runner
Banana is a berry
Gabriel
El Moustachio
James Bong
Disco Fever
Acacia
Sugared
ronald goddamn macdonald
pizza is life
+27
View File
@@ -0,0 +1,27 @@
"FakeClientsTiers"
{
"0" "23"
"3" "22"
"6" "21"
"9" "20"
"12" "19"
"15" "18"
"18" "17"
"21" "16"
"24" "15"
"27" "14"
"30" "13"
"33" "12"
"36" "11"
"39" "10"
"42" "9"
"45" "8"
"48" "7"
"51" "6"
"54" "5"
"55" "4"
"57" "3"
"60" "2"
"61" "1"
"62" "0"
}
@@ -0,0 +1,9 @@
"FakeClientsTiersTeam"
{
"0" "0"
"10" "2"
"14" "3"
"18" "4"
"22" "5"
"26" "6"
}
+265 -320
View File
@@ -1,28 +1,18 @@
#include <sourcemod> #include <sourcemod>
#include <sdktools> #include <sdktools>
#include <zombiereloaded>
#include <BotTargeting>
#include <cstrike> #include <cstrike>
#pragma semicolon 1 #pragma semicolon 1
#pragma newdecls required #pragma newdecls required
#define MAXNAMES 500
ArrayList g_hNames; ArrayList g_hNames;
ArrayList g_hClanNames; ArrayList g_hClanNames;
bool g_bFakePopulation[MAXPLAYERS + 1];
bool g_bMapEnded;
int g_iBaseLatency[MAXPLAYERS + 1]; int g_iBaseLatency[MAXPLAYERS + 1];
int g_iLatency[MAXPLAYERS + 1]; int g_iLatency[MAXPLAYERS + 1];
int g_iPlayerCount;
int g_iAdminFakes; bool g_bMapended;
int g_iPopulation;
bool g_bCheckRequested;
bool g_bBlockInstantFakeConnects;
Database g_hDatabase; Database g_hDatabase;
Database g_hDatabase_hlstats; Database g_hDatabase_hlstats;
@@ -32,10 +22,10 @@ Database g_hDatabase_hlstats;
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Plugin myinfo = public Plugin myinfo =
{ {
name = "ImprovedHitboxes", //camouflage name = "fakes",
author = "Neon + Dogan + Botox", author = "Neon + Dogan + Botox",
description = "Handle Hitboxes via Plugin", description = "Handle Hitboxes via Plugin",
version = "5.3.1", version = "5.4.1",
url = "https://steamcommunity.com/id/n3ontm" url = "https://steamcommunity.com/id/n3ontm"
}; };
@@ -46,28 +36,19 @@ public void OnPluginStart()
{ {
RegAdminCmd("sm_debugfakes", Command_DebugFakes, ADMFLAG_GENERIC, "Shows the amount of fake-clients on server"); RegAdminCmd("sm_debugfakes", Command_DebugFakes, ADMFLAG_GENERIC, "Shows the amount of fake-clients on server");
RegAdminCmd("sm_fakes", Command_Fakes, ADMFLAG_GENERIC, "Shows the fake-clients on server"); RegAdminCmd("sm_fakes", Command_Fakes, ADMFLAG_GENERIC, "Shows the fake-clients on server");
RegAdminCmd("sm_setfakes", Command_SetFakes, ADMFLAG_RCON, "Manually sets the amount of fake-clients");
int arraySize = ByteCountToCells(MAX_NAME_LENGTH); int arraySize = ByteCountToCells(MAX_NAME_LENGTH);
g_hNames = CreateArray(arraySize); g_hNames = CreateArray(arraySize);
g_hClanNames = CreateArray(arraySize); g_hClanNames = CreateArray(arraySize);
g_iAdminFakes = -1; CreateTimer(GetRandomFloat(20.0, 40.0), RunPopulationCheck, _, TIMER_REPEAT);
g_iPopulation = GetClientCount(false);
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientConnected(i) && IsFakeClient(i))
g_iPopulation--;
}
g_bMapEnded = false;
g_bBlockInstantFakeConnects = false;
CreateTimer(40.0, BlockInstantFakeConnects, _, TIMER_REPEAT);
CreateTimer(3.0, RandomizePing, _, TIMER_REPEAT); CreateTimer(3.0, RandomizePing, _, TIMER_REPEAT);
CreateTimer(150.0, RandomizeNames, _, TIMER_REPEAT); CreateTimer(600.0, RandomizeNames, _, TIMER_REPEAT);
CreateTimer(80.0, CheckPopulation_interval, _, TIMER_REPEAT);
HookUserMessage(GetUserMessageId("SayText2"), UserMessage_SayText2, true); HookUserMessage(GetUserMessageId("SayText2"), UserMessage_SayText2, true);
RequestFrame(CheckPopulation); g_bMapended = false;
if (!g_hDatabase) if (!g_hDatabase)
{ {
@@ -77,6 +58,7 @@ public void OnPluginStart()
{ {
Database.Connect(SQL_OnDatabaseConnect_hlstats, "hlstatsx"); Database.Connect(SQL_OnDatabaseConnect_hlstats, "hlstatsx");
} }
g_iPlayerCount = 0;
} }
public void SQL_OnDatabaseConnect_hlstats(Database db, const char[] error, any data) public void SQL_OnDatabaseConnect_hlstats(Database db, const char[] error, any data)
@@ -93,7 +75,7 @@ public void SQL_OnDatabaseConnect_hlstats(Database db, const char[] error, any d
public void randomize_clantags() public void randomize_clantags()
{ {
char sQuery[512]; char sQuery[512];
Format(sQuery, sizeof(sQuery), "select DISTINCT tag from unloze_stats.hlstats_Clans ORDER BY RAND() limit 500"); Format(sQuery, sizeof(sQuery), "select DISTINCT tag from unloze_stats.hlstats_Clans ORDER BY RAND() limit 1000");
g_hDatabase_hlstats.Query(SQL_OnQueryCompleted_hlstats, sQuery, DBPrio_High); g_hDatabase_hlstats.Query(SQL_OnQueryCompleted_hlstats, sQuery, DBPrio_High);
} }
@@ -111,7 +93,7 @@ public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
public void randomize_names() public void randomize_names()
{ {
char sQuery[512]; char sQuery[512];
Format(sQuery, sizeof(sQuery), "select name from unloze_racetimer_css.random_names ORDER BY RAND() limit 500"); Format(sQuery, sizeof(sQuery), "select name from unloze_racetimer_css.random_names ORDER BY RAND() limit 1000");
g_hDatabase.Query(SQL_OnQueryCompleted, sQuery, DBPrio_High); g_hDatabase.Query(SQL_OnQueryCompleted, sQuery, DBPrio_High);
} }
@@ -159,20 +141,9 @@ public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[]
delete results; delete results;
} }
//---------------------------------------------------------------------------------------------------- public void OnMapEnd()
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginEnd()
{ {
for(int i = 1; i <= MaxClients; i++) g_bMapended = true;
{
if(g_bFakePopulation[i])
{
g_bFakePopulation[i] = false;
g_iLatency[i] = 0;
KickClient(i, "Disconnect by user.");
}
}
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -180,80 +151,174 @@ public void OnPluginEnd()
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnMapStart() public void OnMapStart()
{ {
g_hNames.Clear(); KeyValues kv = CreateKeyValues("ints_used_already");
g_hClanNames.Clear(); int fakes_needed = getFakesNeeded();
if (!g_hDatabase) for (float i = 0.0; i < fakes_needed;)
{ {
Database.Connect(SQL_OnDatabaseConnect, "racetimercss"); int num = GetRandomInt(5, 45);
} char strnum[16];
else IntToString(num, strnum, sizeof(strnum));
int found = kv.GetNum(strnum, -1);
if (found != -1)
{ {
randomize_names(); continue;
} }
kv.SetNum(strnum, num);
i += 1.0;
if (!g_hDatabase_hlstats) CreateTimer(float(num), addfakes);
{
Database.Connect(SQL_OnDatabaseConnect_hlstats, "hlstatsx");
} }
else delete kv;
{
randomize_clantags();
}
/*
char sFile[PLATFORM_MAX_PATH];
char sLine[MAX_NAME_LENGTH];
BuildPath(Path_SM, sFile, sizeof(sFile), "configs/fakeclients_names.txt");
Handle hFile = OpenFile(sFile, "r");
if(hFile != INVALID_HANDLE)
{
int iLine = 0;
g_hNames.Clear();
while (!IsEndOfFile(hFile))
{
if (!ReadFileLine(hFile, sLine, sizeof(sLine)) || iLine >= MAXNAMES)
break;
TrimString(sLine);
g_hNames.PushString(sLine);
iLine++;
}
delete hFile;
}
else
SetFailState("Could not open file: configs/fakeclients_names.txt");
*/
g_bMapEnded = false;
g_bBlockInstantFakeConnects = false;
} }
//---------------------------------------------------------------------------------------------------- public int getFakesNeededInTeam()
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnMapEnd()
{ {
g_bMapEnded = true; //just moving them with CheckPopulation() over to a team as needed. we dont bother moving them back to spectate, mapchanges do that for us.
g_iAdminFakes = -1; char sPath[PLATFORM_MAX_PATH];
g_iPopulation = GetClientCount(false); BuildPath(Path_SM, sPath, sizeof(sPath), "configs/fakeclients_tiers_team.cfg");
KeyValues kv = new KeyValues("FakeClientsTiersTeam");
if (!kv.ImportFromFile(sPath))
{
LogError("configs/fakeclients_tiers_team.cfg not found");
delete kv;
return 0;
}
if (!kv.GotoFirstSubKey(false))
{
LogError("configs/fakeclients_tiers_team.cfg is empty or malformed");
delete kv;
return 0;
}
int prev_bots = 0;
do
{
char sKey[16], sVal[16];
kv.GetSectionName(sKey, sizeof(sKey));
kv.GetString(NULL_STRING, sVal, sizeof(sVal), "-1");
int bots = StringToInt(sVal);
int players = StringToInt(sKey);
if (players > g_iPlayerCount)
{
break;
}
prev_bots = bots;
} while (kv.GotoNextKey(false));
delete kv;
//LogMessage("prev_bots: %i", prev_bots);
return prev_bots;
}
public int getFakesNeeded()
{
//copied from nide fakeclients plugin.
char sPath[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sPath, sizeof(sPath), "configs/fakeclients_tiers.cfg");
KeyValues kv = new KeyValues("FakeClientsTiers");
if (!kv.ImportFromFile(sPath))
{
LogError("configs/fakeclients_tiers.cfg not found — falling back to sm_fakeclients_players");
delete kv;
return 0;
}
if (!kv.GotoFirstSubKey(false))
{
LogError("configs/fakeclients_tiers.cfg is empty or malformed");
delete kv;
return 0;
}
int prev_bots = 0;
do
{
char sKey[16], sVal[16];
kv.GetSectionName(sKey, sizeof(sKey));
kv.GetString(NULL_STRING, sVal, sizeof(sVal), "-1");
int bots = StringToInt(sVal);
int players = StringToInt(sKey);
if (players > g_iPlayerCount)
{
break;
}
prev_bots = bots;
} while (kv.GotoNextKey(false));
delete kv;
//LogMessage("prev_bots: %i", prev_bots);
return prev_bots;
}
public Action addfakes(Handle timer)
{
int limit = getFakesNeeded();
int fakes = 0;
for(int i = 1; i <= MaxClients; i++) for(int i = 1; i <= MaxClients; i++)
{ {
if(IsClientConnected(i) && IsFakeClient(i)) if (IsClientConnected(i) && IsClientInGame(i) && IsClientAuthorized(i))
g_iPopulation--; {
if (IsFakeClient(i) && !IsClientSourceTV(i))
{
fakes++;
} }
}
}
/*
if (nightTime())
{
limit /= 2;
}
*/
if (fakes < limit)
{
CreateFake();
}
return Plugin_Continue;
}
public Action CheckPopulation_interval(Handle timer)
{
int TimeLeft;
if (GetMapTimeLeft(TimeLeft) && TimeLeft < 0)
{
return Plugin_Continue;
}
if (g_bMapended)
{
g_bMapended = false;
return Plugin_Continue;
}
g_iPlayerCount = 0;
for(int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientInGame(i) && IsClientAuthorized(i))
{
if (!IsFakeClient(i) && !IsClientSourceTV(i))
{
g_iPlayerCount++;
}
}
}
return Plugin_Handled;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action BlockInstantFakeConnects(Handle timer) public Action RunPopulationCheck(Handle timer)
{ {
g_bBlockInstantFakeConnects = false; CheckPopulation();
RequestFrame(CheckPopulation);
return Plugin_Continue; return Plugin_Continue;
} }
@@ -264,7 +329,7 @@ public Action RandomizePing(Handle timer)
{ {
for(int i = 1; i <= MaxClients; i++) for(int i = 1; i <= MaxClients; i++)
{ {
if(g_bFakePopulation[i]) if(IsClientConnected(i) && IsFakeClient(i) && !IsClientSourceTV(i))
g_iLatency[i] = g_iBaseLatency[i] + GetRandomInt(-3, 3); g_iLatency[i] = g_iBaseLatency[i] + GetRandomInt(-3, 3);
} }
return Plugin_Continue; return Plugin_Continue;
@@ -284,7 +349,7 @@ public Action RandomizeNames(Handle timer)
ArrayList hClanNames = g_hClanNames.Clone(); ArrayList hClanNames = g_hClanNames.Clone();
for(int i = 1; i <= MaxClients; i++) for(int i = 1; i <= MaxClients; i++)
{ {
if(g_bFakePopulation[i]) if(IsClientConnected(i) && IsFakeClient(i) && !IsClientSourceTV(i))
{ {
int iRand = GetRandomInt(0, hNames.Length - 1); int iRand = GetRandomInt(0, hNames.Length - 1);
char sName[MAX_NAME_LENGTH]; char sName[MAX_NAME_LENGTH];
@@ -340,7 +405,7 @@ public Action UserMessage_SayText2(UserMsg msg_id, BfRead msg, const int[] playe
return Plugin_Continue; return Plugin_Continue;
} }
if(g_bFakePopulation[client]) if(IsClientConnected(client) && IsFakeClient(client) && !IsClientSourceTV(client))
return Plugin_Handled; return Plugin_Handled;
return Plugin_Continue; return Plugin_Continue;
@@ -357,45 +422,20 @@ public Action Command_DebugFakes(int client, int argc)
for(int i = 1; i <= MaxClients; i++) for(int i = 1; i <= MaxClients; i++)
{ {
if (g_bFakePopulation[i]) if (!IsClientConnected(i))
{
continue;
}
if (IsFakeClient(i) && !IsClientSourceTV(i))
{
iFakes++; iFakes++;
if (g_bFakePopulation[i] && GetClientTeam(i) > 0)
iFakesInTeam++;
if(IsClientConnected(i) && IsFakeClient(i))
iPlayers--; iPlayers--;
} }
if (IsFakeClient(i) && GetClientTeam(i) > CS_TEAM_SPECTATOR)
ReplyToCommand(client, "[SM] There are currently %d Fake-Clients, from which %d are in Spectate.", iFakes, iFakes - iFakesInTeam); iFakesInTeam++;
ReplyToCommand(client, "[SM] Server Population at the end of the previous Map: %d.", g_iPopulation);
ReplyToCommand(client, "[SM] Current Server Population: %d. Difference: %d.", iPlayers, iPlayers - g_iPopulation);
return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_SetFakes(int client, int argc)
{
if (argc < 1)
{
ReplyToCommand(client, "[SM] Usage: sm_setfakes <amount of fakes>");
return Plugin_Handled;
} }
char sArgs[16]; ReplyToCommand(client, "[SM] There are currently %d Fake-Clients, from which %d are in Spectate. Real Clients %d", iFakes, iFakes - iFakesInTeam, iPlayers);
GetCmdArg(1, sArgs, sizeof(sArgs));
if (!StringToIntEx(sArgs, g_iAdminFakes))
{
ReplyToCommand(client, "[SM] Invalid value");
return Plugin_Handled;
}
ReplyToCommand(client, "[SM] You set the amount of Fake-Clients to %d.", g_iAdminFakes);
CheckPopulation();
return Plugin_Handled; return Plugin_Handled;
} }
@@ -411,7 +451,7 @@ public Action Command_Fakes(int client, int args)
{ {
if(IsClientInGame(i)) if(IsClientInGame(i))
{ {
if(g_bFakePopulation[i]) if(IsClientConnected(i) && IsFakeClient(i) && !IsClientSourceTV(i))
{ {
GetClientName(i, aBuf2, sizeof(aBuf2)); GetClientName(i, aBuf2, sizeof(aBuf2));
StrCat(aBuf, sizeof(aBuf), aBuf2); StrCat(aBuf, sizeof(aBuf), aBuf2);
@@ -431,18 +471,6 @@ public Action Command_Fakes(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientConnected(int client)
{
if (!g_bCheckRequested && !IsFakeClient(client))
{
RequestFrame(CheckPopulation);
g_bCheckRequested = true;
}
}
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -450,128 +478,112 @@ public void OnClientDisconnect(int client)
{ {
if (client > 0) if (client > 0)
{ {
if(g_bFakePopulation[client]) if(IsFakeClient(client) && !IsClientSourceTV(client))
{ {
g_bFakePopulation[client] = false;
g_iLatency[client] = 0; g_iLatency[client] = 0;
} }
if (!g_bCheckRequested && !IsFakeClient(client))
{
RequestFrame(CheckPopulation);
g_bCheckRequested = true;
} }
}
}
public Action repeatCheckPopulation(Handle timer)
{
CheckPopulation();
return Plugin_Continue;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void CheckPopulation() public void CheckPopulation()
{
int fakes = 0;
int last_fake = 0;
for(int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientInGame(i) && IsClientAuthorized(i))
{
if (IsFakeClient(i) && !IsClientSourceTV(i))
{
fakes++;
last_fake = i;
}
}
}
int fakes_needed = getFakesNeeded();
/*
if (nightTime())
{
fakes_needed /= 2;
}
*/
if (fakes > fakes_needed && last_fake != 0)
{
g_iLatency[last_fake] = 0;
KickClient(last_fake, "Disconnect by user.");
}
else if (fakes < fakes_needed)
{
CreateFake();
}
//moving fakes to a team
int fakes_in_team = 0;
for(int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientInGame(i) && IsClientAuthorized(i))
{
if (IsFakeClient(i) && !IsClientSourceTV(i) && GetClientTeam(i) > CS_TEAM_SPECTATOR)
{
fakes_in_team++;
}
}
}
if (getFakesNeededInTeam() > fakes_in_team)
{
SetFakeToTeam();
}
}
public bool nightTime()
{
char sTime[8];
FormatTime(sTime, sizeof(sTime), "%H%M");
int CurTime = StringToInt(sTime);
int MinTime = 2300;
int MaxTime = 400;
//Wrap around.
CurTime = (CurTime <= MinTime) ? CurTime + 2400 : CurTime;
MaxTime = (MaxTime <= MinTime) ? MaxTime + 2400 : MaxTime;
if ((MinTime <= CurTime <= MaxTime))
{
return true;
}
return false;
}
public void SetFakeToTeam()
{
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientConnected(i) && IsFakeClient(i) && !IsClientSourceTV(i) && GetClientTeam(i) <= CS_TEAM_SPECTATOR)
{
ChangeClientTeam(i, CS_TEAM_CT);
FakeClientCommandEx(i, "joinclass");
break;
}
}
}
public void CreateFake()
{ {
if (g_hNames.Length == 0 || g_hClanNames.Length == 0) if (g_hNames.Length == 0 || g_hClanNames.Length == 0)
{ {
CreateTimer(5.0, repeatCheckPopulation);
return; return;
} }
g_bCheckRequested = false;
if(g_bMapEnded)
return;
int iPlayers = GetClientCount(false);
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientConnected(i) && IsFakeClient(i))
iPlayers--;
}
int iFakes = 0;
int iFakesInTeam = 0;
for(int i = 1; i <= MaxClients; i++)
{
if (g_bFakePopulation[i])
iFakes++;
}
int iFakesNeeded = 0;
int iFakesInTeamNeeded = 0;
int max = 2;
int min = 1;
if (iPlayers == 1)
{
max = iPlayers * 5;
min = 3;
}
else if (iPlayers < 6)
{
max = iPlayers * 5;
min = iPlayers * 2;
}
else if (iPlayers < 11)
{
max = RoundToFloor(iPlayers * 1.5);
min = iPlayers;
}
else if (iPlayers < 20)
{
max = iPlayers;
min = RoundToFloor(iPlayers * 0.5);
}
else if (iPlayers < 26)
{
max = iPlayers;
min = RoundToFloor(iPlayers * 0.3);
}
else if (iPlayers < 36)
{
max = RoundToFloor(iPlayers * 0.5);
min = RoundToFloor(iPlayers * 0.3);
}
else
{
max = RoundToFloor(iPlayers * 0.3);
min = RoundToFloor(iPlayers * 0.1);
}
iFakesNeeded = GetRandomInt(min, max);
//iFakesNeeded = randomAmount - iPlayers;
if (iFakesNeeded < 0 || iPlayers > 50)
{
iFakesNeeded = 0;
iFakesInTeamNeeded = 0;
}
else
{
iFakesInTeamNeeded = iFakesNeeded / 3;
}
if (iFakesInTeamNeeded < 0)
{
iFakesInTeamNeeded = 0;
}
if(g_iAdminFakes != -1)
iFakesNeeded = g_iAdminFakes;
if (iFakes != iFakesNeeded)
{
while (iFakes < iFakesNeeded && !g_bBlockInstantFakeConnects)
{
ArrayList hNames = g_hNames.Clone(); ArrayList hNames = g_hNames.Clone();
char sName[MAX_NAME_LENGTH]; char sName[MAX_NAME_LENGTH];
for(int i = 1; i <= MaxClients; i++) for(int i = 1; i <= MaxClients; i++)
{ {
if(g_bFakePopulation[i]) if(IsClientConnected(i) && IsFakeClient(i) && !IsClientSourceTV(i))
{ {
GetClientName(i, sName, sizeof(sName)); GetClientName(i, sName, sizeof(sName));
int iPos = hNames.FindString(sName); int iPos = hNames.FindString(sName);
@@ -593,7 +605,6 @@ public void CheckPopulation()
DispatchKeyValue(iIndex, "classname", "player"); DispatchKeyValue(iIndex, "classname", "player");
DispatchSpawn(iIndex); DispatchSpawn(iIndex);
g_bFakePopulation[iIndex] = true;
g_iBaseLatency[iIndex] = GetRandomInt(20, 110); g_iBaseLatency[iIndex] = GetRandomInt(20, 110);
g_iLatency[iIndex] = g_iBaseLatency[iIndex]; g_iLatency[iIndex] = g_iBaseLatency[iIndex];
@@ -612,62 +623,6 @@ public void CheckPopulation()
else else
{ {
CS_SetClientClanTag(iIndex, ""); CS_SetClientClanTag(iIndex, "");
}
iFakes++;
g_bBlockInstantFakeConnects = true;
}
while (iFakes > iFakesNeeded)
{
for(int i = 1; i <= MaxClients; i++)
{
if(g_bFakePopulation[i])
{
g_bFakePopulation[i] = false;
g_iLatency[i] = 0;
KickClient(i, "Disconnect by user.");
iFakes--;
break;
}
}
}
}
for(int i = 1; i <= MaxClients; i++)
{
if (g_bFakePopulation[i] && GetClientTeam(i) >= CS_TEAM_T)
iFakesInTeam++;
}
if (iFakes == iFakesNeeded && iFakesInTeam != iFakesInTeamNeeded && g_iAdminFakes == -1)
{
while (iFakesInTeam < iFakesInTeamNeeded)
{
for(int i = 1; i <= MaxClients; i++)
{
if(g_bFakePopulation[i] && GetClientTeam(i) <= CS_TEAM_SPECTATOR)
{
ChangeClientTeam(i, CS_TEAM_CT);
FakeClientCommandEx(i, "joinclass");
iFakesInTeam++;
break;
}
}
}
while (iFakesInTeam > iFakesInTeamNeeded)
{
for(int i = 1; i <= MaxClients; i++)
{
if(g_bFakePopulation[i] && GetClientTeam(i) >= CS_TEAM_T)
{
ChangeClientTeam(i, CS_TEAM_SPECTATOR);
iFakesInTeam--;
break;
}
}
}
} }
} }
@@ -678,7 +633,7 @@ public void OnGameFrame()
{ {
for(int i = 1; i <= MaxClients; i++) for(int i = 1; i <= MaxClients; i++)
{ {
if(g_bFakePopulation[i]) if(IsClientConnected(i) && IsFakeClient(i) && !IsClientSourceTV(i))
{ {
int iResEnt = GetPlayerResourceEntity(); int iResEnt = GetPlayerResourceEntity();
@@ -689,13 +644,3 @@ public void OnGameFrame()
} }
} }
} }
/*
public Action ZR_OnClientMotherZombieEligible(int client)
{
if (g_bFakePopulation[client])
return Plugin_Handled;
return Plugin_Continue;
}
*/
+480
View File
@@ -0,0 +1,480 @@
#pragma semicolon 1
#pragma newdecls required
#include <sourcemod>
#include <sdktools>
#include <cstrike>
#define MAX_TIERS 32
ConVar g_hCount;
ConVar g_hDelay;
ConVar g_hUseTiers;
ArrayList g_hNames;
int g_iTierThreshold[MAX_TIERS]; // real player count that triggers this tier
int g_iTierMaxBots[MAX_TIERS]; // max bots allowed for this tier
int g_iTierCount = 0;
int g_iPendingBots = 0;
bool g_bUseTiers = false;
public Plugin myinfo = {
name = "FakeClients",
author = "Tsunami, .Rushaway",
description = "Put fake clients in server with tier system",
version = "3.0.0",
url = "https://github.com/srcdslab/sm-plugin-FakeClients"
}
public void OnPluginStart()
{
g_hCount = CreateConVar("sm_fakeclients_players", "8", "Fallback: number of bots when tier system is disabled", _, true, 0.0, true, 64.0);
g_hDelay = CreateConVar("sm_fakeclients_delay", "120", "Delay after map change before fake clients join (seconds)", _, true, 0.0, true, 10000.0);
g_hUseTiers = CreateConVar("sm_fakeclients_tiers", "0", "Use tier system from fakeclients_tiers.cfg (1 = enabled, 0 = disabled)", _, true, 0.0, true, 1.0);
g_bUseTiers = g_hUseTiers.BoolValue;
g_hUseTiers.AddChangeHook(OnConVarChanged);
RegAdminCmd("sm_debugfakes", Command_DebugFakes, ADMFLAG_GENERIC, "Shows the amount of fake-clients on server");
RegAdminCmd("sm_fakes", Command_Fakes, ADMFLAG_GENERIC, "Shows the fake-clients on server");
AutoExecConfig(true);
}
public Action Command_DebugFakes(int client, int argc)
{
int iFakes = 0;
int iFakesInTeam = 0;
int iPlayers = GetClientCount(false);
for(int i = 1; i <= MaxClients; i++)
{
if (!IsClientConnected(i))
{
continue;
}
if (IsFakeClient(i) || IsClientSourceTV(i))
{
iFakes++;
iPlayers--;
}
if (IsFakeClient(i) && GetClientTeam(i) > CS_TEAM_SPECTATOR)
iFakesInTeam++;
}
ReplyToCommand(client, "[SM] There are currently %d Fake-Clients, from which %d are in Spectate. Real Clients %d", iFakes, iFakes - iFakesInTeam, iPlayers);
return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_Fakes(int client, int args)
{
char aBuf[1024];
char aBuf2[MAX_NAME_LENGTH];
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i))
{
if(IsClientConnected(i) && IsFakeClient(i) && !IsClientSourceTV(i))
{
GetClientName(i, aBuf2, sizeof(aBuf2));
StrCat(aBuf, sizeof(aBuf), aBuf2);
StrCat(aBuf, sizeof(aBuf), ", ");
}
}
}
if(strlen(aBuf))
{
aBuf[strlen(aBuf) - 2] = 0;
ReplyToCommand(client, "[SM] Fake-Clients online: %s", aBuf);
}
else
ReplyToCommand(client, "[SM] Fake-Clients online: none");
return Plugin_Handled;
}
public void OnMapStart()
{
ParseNames();
ParseTiers();
CreateTimer(g_hDelay.FloatValue, Timer_CreateFakeClients, _, TIMER_FLAG_NO_MAPCHANGE);
}
public void OnConVarChanged(ConVar hConVar, const char[] sOldValue, const char[] sNewValue)
{
if (hConVar == g_hUseTiers)
{
g_bUseTiers = g_hUseTiers.BoolValue;
LogMessage("Tier system %s", g_bUseTiers ? "enabled" : "disabled");
if (g_bUseTiers)
{
g_iPendingBots = 0;
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientConnected(i) || !IsClientInGame(i))
continue;
if (!IsFakeClient(i) || IsClientSourceTV(i))
continue;
KickClient(i, "Client Disconnect");
}
// Wait a moment for all bots to be kicked before trying to add new ones according to tiers.
CreateTimer(1.0, Timer_CreateFakeClients, _, TIMER_FLAG_NO_MAPCHANGE);
}
else
{
AdjustFakeClientsToTier();
}
}
}
/**
* Returns the target bot count based on the current number of real players.
* Falls back to sm_fakeclients_players if tiers are disabled or not loaded.
*/
int GetTargetBotCount(int iRealPlayers)
{
if (!g_bUseTiers || g_iTierCount == 0)
return g_hCount.IntValue;
// Walk tiers from highest threshold down, pick the first one that applies
for (int t = g_iTierCount - 1; t >= 0; t--)
{
if (iRealPlayers >= g_iTierThreshold[t])
return g_iTierMaxBots[t];
}
// No tier matched (e.g. config missing threshold "0")
LogMessage("Warning: no tier matched for %d real players, defaulting to 0 bots", iRealPlayers);
return 0;
}
/**
* Computes the clamped target bot count, accounting for available slots and
* server over-capacity. Shared by AdjustFakeClientsToTier and OnClientPutInServer.
*/
int ComputeTarget(int iBots, int iRealPlayers, int iReservedSlots)
{
int iTarget = GetTargetBotCount(iRealPlayers);
if (iTarget < 0)
iTarget = 0;
// Bots currently being added already count as occupied slots
int iEffectiveBots = iBots + g_iPendingBots;
int iFreeSlots = MaxClients - (iRealPlayers + iEffectiveBots + iReservedSlots);
if (iFreeSlots < 0)
{
iTarget = iEffectiveBots + iFreeSlots;
if (iTarget < 0)
iTarget = 0;
}
else
{
int iMaxBotsBySlots = iEffectiveBots + iFreeSlots;
if (iTarget > iMaxBotsBySlots)
iTarget = iMaxBotsBySlots;
}
return iTarget;
}
/**
* Kicks excess bots until the bot count reaches iTarget.
* Never kicks real players or SourceTV.
*/
void KickExcessBots(int iBots, int iRealPlayers, int iTarget)
{
int iToKick = iBots - iTarget;
int iKicked = 0;
for (int i = 1; i <= MaxClients && iKicked < iToKick; i++)
{
if (!IsClientConnected(i) || !IsClientInGame(i))
continue;
// Safety: never kick real players or SourceTV
if (!IsFakeClient(i) || IsClientSourceTV(i))
continue;
LogMessage("Kicking fake client '%N' (slot %d) — bots:%d target:%d", i, i, iBots, iTarget);
KickClient(i, "Client Disconnect");
iKicked++;
}
if (iKicked > 0)
LogMessage("Kicked %d bot(s) — real:%d bots:%d target:%d slots:%d", iKicked, iRealPlayers, iBots, iTarget, MaxClients);
}
/**
* Schedules staggered timers to add bots up to iToAdd, respecting free slots.
*/
void ScheduleBotsToAdd(int iToAdd, int iFreeSlots)
{
if (iFreeSlots <= 0 || iToAdd <= 0)
return;
if (iToAdd > iFreeSlots)
iToAdd = iFreeSlots;
// Keep a staggered cadence but add jitter so joins look less scripted.
float fNextDelay = GetRandomFloat(0.4, 1.2);
for (int j = 0; j < iToAdd; j++)
{
g_iPendingBots++;
CreateTimer(fNextDelay, Timer_CreateFakeClient, _, TIMER_FLAG_NO_MAPCHANGE);
fNextDelay += GetRandomFloat(0.7, 1.9);
}
}
/**
* Compares current bot count against the target and either kicks excess bots
* or schedules new ones to fill the gap.
*/
void AdjustFakeClientsToTier()
{
int iBots, iRealPlayers, iReservedSlots;
CollectClientCounts(iBots, iRealPlayers, iReservedSlots);
int iTarget = ComputeTarget(iBots, iRealPlayers, iReservedSlots);
int iFreeSlots = MaxClients - (iRealPlayers + iBots + iReservedSlots);
// Too many active bots -> kick extras
if (iBots > iTarget)
{
g_iPendingBots = 0;
KickExcessBots(iBots, iRealPlayers, iTarget);
}
// Active + pending bots are below target -> add missing bots
else if ((iBots + g_iPendingBots) < iTarget)
ScheduleBotsToAdd(iTarget - iBots - g_iPendingBots, iFreeSlots);
}
/**
* Collects fake bots, real players and reserved slots in a single client loop.
*/
void CollectClientCounts(int &iBots, int &iRealPlayers, int &iReservedSlots)
{
iBots = 0;
iRealPlayers = 0;
bool bHasSourceTV = false;
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientConnected(i))
continue;
if (IsClientSourceTV(i))
{
bHasSourceTV = true;
continue; // SourceTV occupies its own slot in MaxClients — do not count it as a bot
}
if (IsFakeClient(i))
iBots++;
else
iRealPlayers++;
}
// Reserve 1 free slot so a real player can always connect.
// When SourceTV is active, reserve 1 extra (SourceTV's slot is already in MaxClients).
iReservedSlots = bHasSourceTV ? 2 : 1;
}
public void OnClientPutInServer(int client)
{
// Skip fake clients: bot additions are managed via timers.
// Calling AdjustFakeClientsToTier on every bot join would cause cascading timers.
if (!client || IsFakeClient(client))
return;
// A real player joined: only kick excess bots, never schedule additions.
// Scheduling here would race with the staggered timers already in flight.
int iBots, iRealPlayers, iReservedSlots;
CollectClientCounts(iBots, iRealPlayers, iReservedSlots);
int iTarget = ComputeTarget(iBots, iRealPlayers, iReservedSlots);
if (iBots > iTarget)
KickExcessBots(iBots, iRealPlayers, iTarget);
}
public void OnClientDisconnect(int client)
{
// Ignore bot disconnects (caused by our own kicks) to avoid cascading timers.
if (IsFakeClient(client))
return;
CreateTimer(0.5, Timer_CreateFakeClients, _, TIMER_FLAG_NO_MAPCHANGE);
}
public Action Timer_CreateFakeClient(Handle timer)
{
g_iPendingBots--;
if (g_iPendingBots < 0)
g_iPendingBots = 0;
int iBots, iRealPlayers, iReservedSlots;
CollectClientCounts(iBots, iRealPlayers, iReservedSlots);
int iTarget = ComputeTarget(iBots, iRealPlayers, iReservedSlots);
int iFreeSlots = MaxClients - (iRealPlayers + iBots + iReservedSlots);
// Recheck: tier may have changed since this timer was scheduled
if (iFreeSlots <= 0 || iBots >= iTarget)
return Plugin_Handled;
char sName[MAX_NAME_LENGTH];
char sTarget[MAX_TARGET_LENGTH];
int iTargets[MAXPLAYERS];
bool bTN_Is_ML;
// Pick a random name, re-roll if it's already taken by a fake client.
// Stop after trying all available names to prevent an infinite loop when
// the name list is smaller than the number of bots (duplicates are allowed).
// If no names are configured, CreateFakeClient will assign a default name based on the engine.
int iNameCount = g_hNames.Length;
if (iNameCount > 0)
{
g_hNames.GetString(GetRandomInt(0, iNameCount - 1), sName, sizeof(sName));
for (int iAttempt = 1; iAttempt < iNameCount; iAttempt++)
{
if (ProcessTargetString(sName, 0, iTargets, MAXPLAYERS, COMMAND_FILTER_NO_MULTI, sTarget, MAX_TARGET_LENGTH, bTN_Is_ML) != 1 || !IsFakeClient(iTargets[0]))
break;
g_hNames.GetString(GetRandomInt(0, iNameCount - 1), sName, sizeof(sName));
}
}
CreateFakeClient(sName);
return Plugin_Handled;
}
public Action Timer_CreateFakeClients(Handle timer)
{
AdjustFakeClientsToTier();
return Plugin_Continue;
}
/**
* Loads tier thresholds from configs/fakeclients_tiers.cfg. Max 32 tiers.
* Format (KeyValues):
*
* "FakeClientsTiers"
* {
* "0" "12" // 0 real players → up to 12 bots
* "5" "10" // 5+ real players → up to 10 bots
* "15" "8" // And so on...
* }
*
* Entries are sorted by threshold automatically, so order in the file
* does not matter.
*/
stock void ParseTiers()
{
if (!g_bUseTiers)
return;
g_iTierCount = 0;
char sPath[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sPath, sizeof(sPath), "configs/fakeclients_tiers.cfg");
KeyValues kv = new KeyValues("FakeClientsTiers");
if (!kv.ImportFromFile(sPath))
{
LogError("configs/fakeclients_tiers.cfg not found — falling back to sm_fakeclients_players");
g_hUseTiers.BoolValue = false;
delete kv;
return;
}
if (!kv.GotoFirstSubKey(false))
{
LogError("configs/fakeclients_tiers.cfg is empty or malformed");
g_hUseTiers.BoolValue = false;
delete kv;
return;
}
do
{
if (g_iTierCount >= MAX_TIERS)
break;
char sKey[16], sVal[16];
kv.GetSectionName(sKey, sizeof(sKey));
kv.GetString(NULL_STRING, sVal, sizeof(sVal), "-1");
int iMaxBots = StringToInt(sVal);
if (iMaxBots < 0)
continue; // skip malformed lines
g_iTierThreshold[g_iTierCount] = StringToInt(sKey);
g_iTierMaxBots[g_iTierCount] = iMaxBots;
g_iTierCount++;
} while (kv.GotoNextKey(false));
delete kv;
// Bubble sort tiers by threshold (ascending) so GetTargetBotCount() works correctly
for (int i = 0; i < g_iTierCount - 1; i++)
{
for (int j = 0; j < g_iTierCount - 1 - i; j++)
{
if (g_iTierThreshold[j] > g_iTierThreshold[j + 1])
{
int tmp;
tmp = g_iTierThreshold[j]; g_iTierThreshold[j] = g_iTierThreshold[j + 1]; g_iTierThreshold[j + 1] = tmp;
tmp = g_iTierMaxBots[j]; g_iTierMaxBots[j] = g_iTierMaxBots[j + 1]; g_iTierMaxBots[j + 1] = tmp;
}
}
}
LogMessage("Loaded %d tier(s) from fakeclients_tiers.cfg", g_iTierCount);
}
/**
* Loads bot display names from configs/fakeclients.txt (one name per line).
*/
stock void ParseNames()
{
delete g_hNames;
g_hNames = new ArrayList(MAX_NAME_LENGTH);
char sBuffer[256];
BuildPath(Path_SM, sBuffer, sizeof(sBuffer), "configs/fakeclients.txt");
File hConfig = OpenFile(sBuffer, "r");
if (!hConfig)
{
LogError("configs/fakeclients.txt not found — using default engine name");
return;
}
while (hConfig.ReadLine(sBuffer, sizeof(sBuffer)))
{
TrimString(sBuffer);
if (strlen(sBuffer) > 0)
g_hNames.PushString(sBuffer);
}
delete hConfig;
if (!g_hNames.Length)
LogError("configs/fakeclients.txt is empty — using default engine name");
}
+6 -2
View File
@@ -1,5 +1,7 @@
#pragma semicolon 1 #pragma semicolon 1
#pragma newdecls required
#define PLUGIN_AUTHOR "Cloud Strife" #define PLUGIN_AUTHOR "Cloud Strife"
#define PLUGIN_VERSION "1.0" #define PLUGIN_VERSION "1.0"
@@ -21,15 +23,17 @@ public Plugin myinfo =
Handle g_CFuncRotating_StartForward = null; Handle g_CFuncRotating_StartForward = null;
Handle g_CFuncRotating_UpdateSpeed = null; Handle g_CFuncRotating_UpdateSpeed = null;
stock float FloatMod(float num, float denom) /*
float FloatMod(float num, float denom)
{ {
return num - denom * RoundToFloor(num / denom); return num - denom * RoundToFloor(num / denom);
} }
stock float operator%(float oper1, float oper2) float operator%(float oper1, float oper2)
{ {
return FloatMod(oper1, oper2); return FloatMod(oper1, oper2);
} }
*/
// Set m_bStopAtStartPos to false // Set m_bStopAtStartPos to false
public MRESReturn CFuncRotating_InputStartForward(int entity) public MRESReturn CFuncRotating_InputStartForward(int entity)
+3 -1
View File
@@ -373,6 +373,8 @@ public Action Command_Rainbow(int client, int args)
Frequency = StringToFloat(sArg); Frequency = StringToFloat(sArg);
if(Frequency > 10.0) if(Frequency > 10.0)
Frequency = 10.0; Frequency = 10.0;
else if(Frequency < 0.01)
Frequency = 0.01;
} }
if(!Frequency || (args < 1 && g_aRainbowFrequency[client])) if(!Frequency || (args < 1 && g_aRainbowFrequency[client]))
@@ -401,7 +403,7 @@ void DisplayGlowColorMenu(int client)
g_GlowColorsMenu.Display(client, MENU_TIME_FOREVER); g_GlowColorsMenu.Display(client, MENU_TIME_FOREVER);
} }
public int MenuHandler_GlowColorsMenu(Menu menu, MenuAction action, int param1, int param2) public void MenuHandler_GlowColorsMenu(Menu menu, MenuAction action, int param1, int param2)
{ {
switch(action) switch(action)
{ {
@@ -0,0 +1,80 @@
// Angery nut
materials/models/unloze/william/back.vmt
materials/models/unloze/william/back.vtf
materials/models/unloze/william/front.vmt
materials/models/unloze/william/front.vtf
models/unloze/william/angerynut.dx80.vtx
models/unloze/william/angerynut.dx90.vtx
models/unloze/william/angerynut.mdl
models/unloze/william/angerynut.sw.vtx
models/unloze/william/angerynut.vvd
//
//// Eevee
models\unloze\grenades\eevee.dx80.vtx
models\unloze\grenades\eevee.dx90.vtx
models\unloze\grenades\eevee.mdl
models\unloze\grenades\eevee.sw.vtx
models\unloze\grenades\eevee.vvd
materials\models\unloze\grenades\eevee\eevee_body.vtf
materials\models\unloze\grenades\eevee\eevee_body_shiny.vtf
materials\models\unloze\grenades\eevee\eevee_eye.vtf
materials\models\unloze\grenades\eevee\eevee_eye_shiny.vtf
materials\models\unloze\grenades\eevee\eevee_mouth.vtf
materials\models\unloze\grenades\eevee\eevee_mouth_shiny.vtf
materials\models\unloze\grenades\eevee\eievui.vmt
materials\models\unloze\grenades\eevee\eievuieye.vmt
materials\models\unloze\grenades\eevee\eievuieye_shiny.vmt
materials\models\unloze\grenades\eevee\eievuimouth.vmt
materials\models\unloze\grenades\eevee\eievuimouth_shiny.vmt
materials\models\unloze\grenades\eevee\eievui_shiny.vmt
//
//// Holy Grenade
models\unloze\grenades\holy_grenade.dx80.vtx
models\unloze\grenades\holy_grenade.dx90.vtx
models\unloze\grenades\holy_grenade.mdl
models\unloze\grenades\holy_grenade.phy
models\unloze\grenades\holy_grenade.sw.vtx
models\unloze\grenades\holy_grenade.vvd
materials\models\unloze\grenades\holy_grenade\holy_grenade.vmt
materials\models\unloze\grenades\holy_grenade\holy_grenade.vtf
//
//// Lenny
models\unloze\grenades\lenny.dx80.vtx
models\unloze\grenades\lenny.dx90.vtx
models\unloze\grenades\lenny.mdl
models\unloze\grenades\lenny.sw.vtx
models\unloze\grenades\lenny.vvd
models\unloze\grenades\lennysad.dx80.vtx
models\unloze\grenades\lennysad.dx90.vtx
models\unloze\grenades\lennysad.mdl
models\unloze\grenades\lennysad.sw.vtx
models\unloze\grenades\lennysad.vvd
materials\models\unloze\grenades\lenny\black.vmt
//
//// Pokeball
models\unloze\grenades\pokeball.dx80.vtx
models\unloze\grenades\pokeball.dx90.vtx
models\unloze\grenades\pokeball.mdl
models\unloze\grenades\pokeball.phy
models\unloze\grenades\pokeball.sw.vtx
models\unloze\grenades\pokeball.vvd
materials\models\unloze\grenades\pokeball\pokeball.vmt
materials\models\unloze\grenades\pokeball\pokeball.vtf
materials\models\unloze\grenades\pokeball\pokeball_great.vmt
materials\models\unloze\grenades\pokeball\pokeball_great.vtf
materials\models\unloze\grenades\pokeball\pokeball_gs.vmt
materials\models\unloze\grenades\pokeball\pokeball_gs.vtf
materials\models\unloze\grenades\pokeball\pokeball_master.vmt
materials\models\unloze\grenades\pokeball\pokeball_master.vtf
materials\models\unloze\grenades\pokeball\pokeball_ultra.vmt
materials\models\unloze\grenades\pokeball\pokeball_ultra.vtf
//
//// Soccer Ball
materials/models/unloze/soccer/soccerball.vmt
materials/models/unloze/soccer/soccerball_unloze.vtf
models/unloze/soccer/soccerball.dx80.vtx
models/unloze/soccer/soccerball.dx90.vtx
models/unloze/soccer/soccerball.mdl
models/unloze/soccer/soccerball.phy
models/unloze/soccer/soccerball.sw.vtx
models/unloze/soccer/soccerball.vvd
@@ -0,0 +1,193 @@
"Skins"
{
"0"
{
"Name" "Hula Doll"
"ModelPath" "models/props_lab/huladoll.mdl"
"Scale" "1.7"
"Tier" "1"
}
"1"
{
"Name" "Banana"
"ModelPath" "models/props/cs_italy/bananna.mdl"
"Tier" "2"
}
"2"
{
"Name" "Melon"
"ModelPath" "models/props_junk/watermelon01.mdl"
"Scale" "0.7"
"Tier" "2"
}
"3"
{
"Name" "Orange"
"ModelPath" "models/props/cs_italy/orange.mdl"
"Scale" "1.5"
"Tier" "3"
}
"4"
{
"Name" "Creepy Doll"
"ModelPath" "models/props_c17/doll01.mdl"
"Scale" "0.8"
"Tier" "3"
}
"5"
{
"Name" "Turtle"
"ModelPath" "models/props/de_tides/vending_turtle.mdl"
"Tier" "4"
}
"6"
{
"Name" "Goldfish"
"ModelPath" "models/props/de_inferno/goldfish.mdl"
"Tier" "5"
}
"7"
{
"Name" "River Fish"
"ModelPath" "models/props/cs_militia/fishriver01.mdl"
"Tier" "5"
}
"8"
{
"Name" "Snowman Head"
"ModelPath" "models/props/cs_office/snowman_face.mdl"
"Scale" "0.7"
"Tier" "6"
}
"9"
{
"Name" "Glass Bottle"
"ModelPath" "models/props/cs_militia/bottle01.mdl"
"Tier" "7"
}
"10"
{
"Name" "Gift"
"ModelPath" "models/items/cs_gift.mdl"
"Scale" "0.6"
"Tier" "8"
}
"11"
{
"Name" "Rollermine"
"ModelPath" "models/roller_spikes.mdl"
"VIP" "1"
"Scale" "0.7"
}
"12"
{
"Name" "Helicopter Bomb"
"ModelPath" "models/combine_helicopter/helicopter_bomb01.mdl"
"VIP" "1"
"Scale" "0.35"
}
"13"
{
"Name" "Explosive Barrel"
"ModelPath" "models/props_c17/oildrum001_explosive.mdl"
"VIP" "1"
"Scale" "0.25"
}
"14"
{
"Name" "Skull"
"ModelPath" "models/gibs/agibs.mdl"
"VIP" "1"
"Scale" "1.3"
}
"15"
{
"Name" "Angery Nut"
"ModelPath" "models/unloze/william/angerynut.mdl"
"VIP" "1"
"Scale" "0.8"
}
"16"
{
"Name" "Holy Grenade"
"ModelPath" "models/unloze/grenades/holy_grenade.mdl"
"Scale" "0.6"
"VIP" "1"
}
"17"
{
"Name" "Lenny Face"
"ModelPath" "models/unloze/grenades/lenny.mdl"
"VIP" "1"
"Scale" "0.8"
}
"18"
{
"Name" "Lenny Sad"
"ModelPath" "models/unloze/grenades/lennysad.mdl"
"VIP" "1"
"Scale" "0.8"
}
"19"
{
"Name" "Soccer Ball"
"ModelPath" "models/unloze/soccer/soccerball.mdl"
"VIP" "1"
"Scale" "0.4"
}
"20"
{
"Name" "Eevee"
"ModelPath" "models/unloze/grenades/eevee.mdl"
"VIP" "1"
"Scale" "0.5"
}
"21"
{
"Name" "Shiny Eevee"
"ModelPath" "models/unloze/grenades/eevee.mdl"
"VIP" "1"
"Skin" "1"
"Scale" "0.5"
}
"22"
{
"Name" "Poke Ball"
"ModelPath" "models/unloze/grenades/pokeball.mdl"
"VIP" "1"
"Scale" "1.5"
}
"23"
{
"Name" "Great Ball"
"ModelPath" "models/unloze/grenades/pokeball.mdl"
"VIP" "1"
"Skin" "1"
"Scale" "1.5"
}
"24"
{
"Name" "Ultra Ball"
"ModelPath" "models/unloze/grenades/pokeball.mdl"
"VIP" "1"
"Skin" "4"
"Scale" "1.5"
}
"25"
{
"Name" "Master Ball"
"ModelPath" "models/unloze/grenades/pokeball.mdl"
"VIP" "1"
"Skin" "3"
"Scale" "1.5"
}
"26"
{
"Name" "GS Ball"
"ModelPath" "models/unloze/grenades/pokeball.mdl"
"VIP" "1"
"Skin" "2"
"Scale" "1.5"
}
}
@@ -0,0 +1,416 @@
#pragma semicolon 1
#include <sourcemod>
#include <clientprefs>
#include <sdktools>
#include <sdkhooks>
#include <unloze_playtime>
#pragma newdecls required
#define MAX_SKINS 64
enum struct GrenadeSkin
{
char ModelPath[128];
char Name[128];
int VIP;
int Tier;
float Scale;
int Skin;
}
GrenadeSkin g_aGrenadeSkins[MAX_SKINS];
int g_iSkinCount = 0;
int g_iGrenadeSkin[MAXPLAYERS + 1] = { -1, ... };
bool g_bAdminChecked[MAXPLAYERS + 1];
Handle g_hCookie_GrenadeSkin;
public Plugin myinfo =
{
name = "Grenade Skins",
description = "grenade skins but source code actually provided",
author = "redone by claude ai mostly, slight human adjustments",
version = "1.0",
url = ""
};
public void OnPluginStart()
{
RegConsoleCmd("sm_grenadeskin", Cmd_GrenadeSkinSettings);
RegConsoleCmd("sm_grenadeskins", Cmd_GrenadeSkinSettings);
g_hCookie_GrenadeSkin = RegClientCookie("grenade_skin", "Selected grenade skin", CookieAccess_Protected);
SetCookieMenuItem(MenuHandler_CookieMenu, 0, "Grenade Skin");
for (int i = 1; i <= MaxClients; i++)
{
if (IsValidClient(i, true) && AreClientCookiesCached(i))
{
OnClientCookiesCached(i);
}
}
}
public void OnMapStart()
{
LoadDownloads();
LoadSkinsConfig();
}
void LoadDownloads()
{
char sDownloadsFile[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sDownloadsFile, sizeof(sDownloadsFile), "configs/grenade_skins/downloads.txt");
File hFile = OpenFile(sDownloadsFile, "r");
if (!hFile)
{
SetFailState("Could not open file: configs/grenade_skins/downloads.txt");
}
char sLine[1024];
while (hFile.ReadLine(sLine, sizeof(sLine)))
{
TrimString(sLine);
// Skip empty lines and comment lines (lines starting with "//" with no file extension).
if (sLine[0] == '\0')
continue;
if (StrContains(sLine, ".", false) == -1)
continue;
if (FileExists(sLine, false, "GAME"))
{
AddFileToDownloadsTable(sLine);
}
}
delete hFile;
}
void LoadSkinsConfig()
{
// Clear existing skins.
for (int i = 0; i < MAX_SKINS; i++)
{
g_aGrenadeSkins[i].ModelPath[0] = '\0';
g_aGrenadeSkins[i].Name[0] = '\0';
g_aGrenadeSkins[i].VIP = 0;
g_aGrenadeSkins[i].Tier = 0;
g_aGrenadeSkins[i].Scale = 1.0;
g_aGrenadeSkins[i].Skin = 0;
}
g_iSkinCount = 0;
char sConfigFile[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/grenade_skins/skins.cfg");
if (!FileExists(sConfigFile, false, "GAME"))
{
SetFailState("Could not find: \"%s\"", sConfigFile);
}
KeyValues kv = new KeyValues("Skins");
if (!kv.ImportFromFile(sConfigFile))
{
delete kv;
SetFailState("ImportFromFile() failed!");
}
if (!kv.GotoFirstSubKey(true))
{
delete kv;
SetFailState("Unable to goto first sub key in: \"%s\"", sConfigFile);
}
int i = 0;
do
{
if (i >= MAX_SKINS)
{
LogError("grenade_skins: skins.cfg has more than %d entries, ignoring the rest.", MAX_SKINS);
break;
}
kv.GetString("ModelPath", g_aGrenadeSkins[i].ModelPath, sizeof(g_aGrenadeSkins[].ModelPath), "error");
if (StrEqual(g_aGrenadeSkins[i].ModelPath, "error", true))
{
SetFailState("Unable to read Path");
}
kv.GetString("Name", g_aGrenadeSkins[i].Name, sizeof(g_aGrenadeSkins[].Name), "error");
if (StrEqual(g_aGrenadeSkins[i].Name, "error", true))
{
SetFailState("Unable to read Name");
}
g_aGrenadeSkins[i].VIP = kv.GetNum("VIP", 0);
g_aGrenadeSkins[i].Tier = kv.GetNum("tier", 0);
g_aGrenadeSkins[i].Scale = kv.GetFloat("Scale", 1.0);
g_aGrenadeSkins[i].Skin = kv.GetNum("Skin", 0);
PrecacheModel(g_aGrenadeSkins[i].ModelPath, false);
i++;
}
while (kv.GotoNextKey(true));
g_iSkinCount = i;
delete kv;
}
public void OnClientCookiesCached(int client)
{
char sBuffer[256];
GetClientCookie(client, g_hCookie_GrenadeSkin, sBuffer, sizeof(sBuffer));
if (sBuffer[0])
{
g_iGrenadeSkin[client] = -1;
for (int i = 0; i < g_iSkinCount; i++)
{
if (StrEqual(g_aGrenadeSkins[i].ModelPath, sBuffer, true))
{
g_iGrenadeSkin[client] = i;
if (g_bAdminChecked[client] && !ClientHasSkinAccess(client, i))
{
g_iGrenadeSkin[client] = -1;
}
break;
}
}
}
else
{
g_iGrenadeSkin[client] = -1;
}
}
public void OnClientDisconnect(int client)
{
g_iGrenadeSkin[client] = -1;
g_bAdminChecked[client] = false;
}
public void OnClientPostAdminCheck(int client)
{
g_bAdminChecked[client] = true;
if (AreClientCookiesCached(client) && g_iGrenadeSkin[client] != -1)
{
if (!ClientHasSkinAccess(client, g_iGrenadeSkin[client]))
{
g_iGrenadeSkin[client] = -1;
}
}
}
/**
* Checks whether a client has access to a given skin index,
* taking both the legacy VIP flag and the new tier requirement into account.
*/
bool ClientHasSkinAccess(int client, int skinIndex)
{
if (g_aGrenadeSkins[skinIndex].VIP && !CheckCommandAccess(client, "", ADMFLAG_RESERVATION, false))
{
return false;
}
if (g_aGrenadeSkins[skinIndex].Tier > 0 && GetPlayerTier_native(client) < g_aGrenadeSkins[skinIndex].Tier)
{
return false;
}
return true;
}
public void OnEntitySpawned(int entity, const char[] sClassname)
{
if (StrContains(sClassname, "_projectile", false) == -1)
{
return;
}
int iOwner = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity");
if (!IsValidClient(iOwner, true) || g_iGrenadeSkin[iOwner] == -1)
{
return;
}
int skinIndex = g_iGrenadeSkin[iOwner];
SetEntityRenderMode(entity, RENDER_TRANSALPHA);
float fNadeOrigin[3];
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", fNadeOrigin);
int iNadeProp = CreateEntityAtOrigin("prop_dynamic_override", fNadeOrigin);
DispatchKeyValue(iNadeProp, "disableshadows", "1");
DispatchKeyValue(iNadeProp, "disablereceiveshadows", "1");
DispatchKeyValue(iNadeProp, "DisableBoneFollowers", "1");
DispatchKeyValue(iNadeProp, "solid", "0");
DispatchKeyValue(iNadeProp, "model", g_aGrenadeSkins[skinIndex].ModelPath);
char sScale[32];
FloatToString(g_aGrenadeSkins[skinIndex].Scale, sScale, sizeof(sScale));
DispatchKeyValue(iNadeProp, "modelscale", sScale);
SpawnAndActivate(iNadeProp);
ParentToEntity(iNadeProp, entity);
if (g_aGrenadeSkins[skinIndex].Skin > 0)
{
char sSkinBuf[16];
IntToString(g_aGrenadeSkins[skinIndex].Skin, sSkinBuf, sizeof(sSkinBuf));
SetVariantString(sSkinBuf);
AcceptEntityInput(iNadeProp, "Skin");
}
}
public Action Cmd_GrenadeSkinSettings(int client, int args)
{
ShowSettingsMenu(client);
return Plugin_Handled;
}
public void MenuHandler_CookieMenu(int client, CookieMenuAction action, any info, char[] buffer, int maxlen)
{
switch (action)
{
case CookieMenuAction_DisplayOption:
{
Format(buffer, maxlen, "Grenade Skin");
}
case CookieMenuAction_SelectOption:
{
ShowSettingsMenu(client);
}
}
}
void ShowSettingsMenu(int client)
{
Menu menu = new Menu(MenuHandler_MainMenu);
menu.SetTitle("Grenade Skin");
char sBuffer[128];
Format(sBuffer, sizeof(sBuffer), "No Skin%s", (g_iGrenadeSkin[client] == -1) ? " (Selected)" : "");
menu.AddItem("-1", sBuffer, (g_iGrenadeSkin[client] == -1) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
for (int i = 0; i < g_iSkinCount; i++)
{
bool bSelected = (i == g_iGrenadeSkin[client]);
bool bHasAccess = ClientHasSkinAccess(client, i);
bool bUnavailable = bSelected || !bHasAccess;
char sSuffix[32];
sSuffix[0] = '\0';
if (!bHasAccess)
{
if (g_aGrenadeSkins[i].VIP && !CheckCommandAccess(client, "", ADMFLAG_RESERVATION, false))
{
Format(sSuffix, sizeof(sSuffix), " [VIP]");
}
else if (g_aGrenadeSkins[i].Tier > 0 && GetPlayerTier_native(client) < g_aGrenadeSkins[i].Tier)
{
Format(sSuffix, sizeof(sSuffix), " [Tier %i]", g_aGrenadeSkins[i].Tier);
}
}
else if (bSelected)
{
Format(sSuffix, sizeof(sSuffix), " (Selected)");
}
Format(sBuffer, sizeof(sBuffer), "%s%s", g_aGrenadeSkins[i].Name, sSuffix);
char sInfo[8];
IntToString(i, sInfo, sizeof(sInfo));
menu.AddItem(sInfo, sBuffer, bUnavailable ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
}
menu.ExitBackButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int selection)
{
switch (action)
{
case MenuAction_Select:
{
char sInfo[8];
menu.GetItem(selection, sInfo, sizeof(sInfo));
int chosen = StringToInt(sInfo);
g_iGrenadeSkin[client] = chosen;
if (chosen == -1)
{
SetClientCookie(client, g_hCookie_GrenadeSkin, "");
}
else
{
SetClientCookie(client, g_hCookie_GrenadeSkin, g_aGrenadeSkins[chosen].ModelPath);
}
ShowSettingsMenu(client);
}
case MenuAction_Cancel:
{
if (selection == MenuCancel_ExitBack)
{
// Re-open the cookie/settings menu if desired; original plugin
// routed back into the cookie menu here.
}
}
case MenuAction_End:
{
delete menu;
}
}
return 0;
}
// ---------------------------------------------------------------------
// Helper stocks (ported from decompiled output)
// ---------------------------------------------------------------------
stock bool IsValidClient(int client, bool noBots)
{
if (client <= 0 || client > MaxClients || !IsClientConnected(client) || (noBots && IsFakeClient(client)))
{
return false;
}
return IsClientInGame(client);
}
stock int CreateEntityAtOrigin(const char[] classname, const float origin[3])
{
int entity = CreateEntityByName(classname);
TeleportEntity(entity, origin, NULL_VECTOR, NULL_VECTOR);
return entity;
}
stock void SpawnAndActivate(int entity)
{
DispatchSpawn(entity);
ActivateEntity(entity);
}
stock void ParentToEntity(int entity, int parent)
{
SetVariantString("!activator");
AcceptEntityInput(entity, "SetParent", parent, parent);
}
+79 -2
View File
@@ -25,7 +25,7 @@ bool g_bHitmarkerSound[MAXPLAYERS+1];
int g_iHitmarkerSoundVolume[MAXPLAYERS+1] = {100,...}; int g_iHitmarkerSoundVolume[MAXPLAYERS+1] = {100,...};
int g_iHitmarkerSkin[MAXPLAYERS+1]; int g_iHitmarkerSkin[MAXPLAYERS+1];
#define MAXIMUMSKINS 6 #define MAXIMUMSKINS 24
char g_sHitmarkerSkins[MAXIMUMSKINS][PLATFORM_MAX_PATH]; char g_sHitmarkerSkins[MAXIMUMSKINS][PLATFORM_MAX_PATH];
char g_sHitmarkerSkinsNames[MAXIMUMSKINS][PLATFORM_MAX_PATH]; char g_sHitmarkerSkinsNames[MAXIMUMSKINS][PLATFORM_MAX_PATH];
@@ -38,6 +38,7 @@ Handle g_hCookie_HitmarkerSoundVolume;
Handle g_hCookie_HitmarkerSkin; Handle g_hCookie_HitmarkerSkin;
ConVar g_hCVar_Debug; ConVar g_hCVar_Debug;
ConVar g_hCVar_HitmarkerEnabled;
bool g_bLate; bool g_bLate;
@@ -87,7 +88,9 @@ public void OnPluginStart()
g_hCookie_HitmarkerSkin = RegClientCookie("hitmarker_skin", "", CookieAccess_Private); g_hCookie_HitmarkerSkin = RegClientCookie("hitmarker_skin", "", CookieAccess_Private);
g_hCVar_Debug = CreateConVar("sm_hitmarker_debug", "0", "", FCVAR_NONE, true, 0.0, true, 1.0); g_hCVar_Debug = CreateConVar("sm_hitmarker_debug", "0", "", FCVAR_NONE, true, 0.0, true, 1.0);
AutoExecConfig(); g_hCVar_HitmarkerEnabled = CreateConVar("sm_hitmarker_enabled", "1", "", FCVAR_NONE, true, 0.0, true, 1.0);
g_hCVar_HitmarkerEnabled.AddChangeHook(OnConVarChanged);
AutoExecConfig(true);
RegConsoleCmd("sm_hm", OnHitmarkerSettings); RegConsoleCmd("sm_hm", OnHitmarkerSettings);
RegConsoleCmd("sm_hitmarker", OnHitmarkerSettings); RegConsoleCmd("sm_hitmarker", OnHitmarkerSettings);
@@ -109,6 +112,14 @@ public void OnPluginStart()
} }
} }
public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
if(g_hCVar_HitmarkerEnabled.BoolValue)
CPrintToChatAll("{cyan}[Hitmarker] {white}has been allowed.");
else
CPrintToChatAll("{cyan}[Hitmarker] {white}has been disabled.");
}
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -120,6 +131,11 @@ public void OnMapStart()
g_sHitmarkerSkinsNames[i] = ""; g_sHitmarkerSkinsNames[i] = "";
} }
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
char sConfigFile[PLATFORM_MAX_PATH]; char sConfigFile[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/Hitmarkers.cfg"); BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/Hitmarkers.cfg");
@@ -184,6 +200,11 @@ public void OnMapStart()
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnClientCookiesCached(int client) public void OnClientCookiesCached(int client)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
char sBuffer[PLATFORM_MAX_PATH]; char sBuffer[PLATFORM_MAX_PATH];
GetClientCookie(client, g_hCookie_ShowBossHitmarker, sBuffer, sizeof(sBuffer)); GetClientCookie(client, g_hCookie_ShowBossHitmarker, sBuffer, sizeof(sBuffer));
@@ -243,6 +264,10 @@ public void OnClientDisconnect(int client)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action OnHitmarkerSettings(int client, int args) public Action OnHitmarkerSettings(int client, int args)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return Plugin_Handled;
}
ShowSettingsMenu(client); ShowSettingsMenu(client);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -252,6 +277,10 @@ public Action OnHitmarkerSettings(int client, int args)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action OnToggleBossHitmarker(int client, int args) public Action OnToggleBossHitmarker(int client, int args)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return Plugin_Handled;
}
ToggleBossHitmarker(client); ToggleBossHitmarker(client);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -275,6 +304,10 @@ public void ToggleBossHitmarker(int client)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action OnToggleZombieHitmarker(int client, int args) public Action OnToggleZombieHitmarker(int client, int args)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return Plugin_Handled;
}
ToggleZombieHitmarker(client); ToggleZombieHitmarker(client);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -284,6 +317,10 @@ public Action OnToggleZombieHitmarker(int client, int args)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void ToggleZombieHitmarker(int client) public void ToggleZombieHitmarker(int client)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
g_bShowZombieHitmarker[client] = !g_bShowZombieHitmarker[client]; g_bShowZombieHitmarker[client] = !g_bShowZombieHitmarker[client];
SetClientCookie(client, g_hCookie_ShowZombieHitmarker, g_bShowZombieHitmarker[client] ? "1" : ""); SetClientCookie(client, g_hCookie_ShowZombieHitmarker, g_bShowZombieHitmarker[client] ? "1" : "");
@@ -298,6 +335,10 @@ public void ToggleZombieHitmarker(int client)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action OnToggleHitmarkerSound(int client, int args) public Action OnToggleHitmarkerSound(int client, int args)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return Plugin_Handled;
}
ToggleHitmarkerSound(client); ToggleHitmarkerSound(client);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -307,6 +348,10 @@ public Action OnToggleHitmarkerSound(int client, int args)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void ToggleHitmarkerSound(int client) public void ToggleHitmarkerSound(int client)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
g_bHitmarkerSound[client] = !g_bHitmarkerSound[client]; g_bHitmarkerSound[client] = !g_bHitmarkerSound[client];
SetClientCookie(client, g_hCookie_HitmarkerSound, g_bHitmarkerSound[client] ? "1" : ""); SetClientCookie(client, g_hCookie_HitmarkerSound, g_bHitmarkerSound[client] ? "1" : "");
@@ -321,6 +366,10 @@ public void ToggleHitmarkerSound(int client)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void ToggleHitmarkerSoundVolume(int client) public void ToggleHitmarkerSoundVolume(int client)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
g_iHitmarkerSoundVolume[client] += 25; g_iHitmarkerSoundVolume[client] += 25;
if(g_iHitmarkerSoundVolume[client] > 100) if(g_iHitmarkerSoundVolume[client] > 100)
@@ -339,6 +388,10 @@ public void ToggleHitmarkerSoundVolume(int client)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void ToggleHitmarkerSkin(int client) public void ToggleHitmarkerSkin(int client)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
g_iHitmarkerSkin[client]++; g_iHitmarkerSkin[client]++;
if (StrEqual(g_sHitmarkerSkins[g_iHitmarkerSkin[client]], "")) if (StrEqual(g_sHitmarkerSkins[g_iHitmarkerSkin[client]], ""))
@@ -354,6 +407,10 @@ public void ToggleHitmarkerSkin(int client)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void ShowSettingsMenu(int client) public void ShowSettingsMenu(int client)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
Menu menu = new Menu(MenuHandler_MainMenu); Menu menu = new Menu(MenuHandler_MainMenu);
menu.SetTitle("Hitmarker Settings", client); menu.SetTitle("Hitmarker Settings", client);
@@ -434,6 +491,10 @@ public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int se
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnEntitySpawned(int Entity, const char[] sClassname) public void OnEntitySpawned(int Entity, const char[] sClassname)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
int ent = EntRefToEntIndex(Entity); int ent = EntRefToEntIndex(Entity);
if (0 < ent < MAX_EDICTS) if (0 < ent < MAX_EDICTS)
@@ -451,6 +512,10 @@ public void OnEntitySpawned(int Entity, const char[] sClassname)
/* /*
public void OnBossDamaged(CBoss Boss, CConfig Config, int client, float damage) public void OnBossDamaged(CBoss Boss, CConfig Config, int client, float damage)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
if (!IsValidClient(client)) if (!IsValidClient(client))
return; return;
@@ -475,6 +540,10 @@ public void OnBossDamaged(CBoss Boss, CConfig Config, int client, float damage)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public MRESReturn Detour_OnTakeDamage(int entity, Handle hReturn, Handle hParams) public MRESReturn Detour_OnTakeDamage(int entity, Handle hReturn, Handle hParams)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return MRES_Ignored;
}
//https://github.com/alliedmodders/hl2sdk/blob/css/game/shared/takedamageinfo.h#L115 //https://github.com/alliedmodders/hl2sdk/blob/css/game/shared/takedamageinfo.h#L115
int iHealth = GetEntProp(entity, Prop_Data, "m_iHealth"); int iHealth = GetEntProp(entity, Prop_Data, "m_iHealth");
int client = DHookGetParamObjectPtrVar(hParams, 1, 3*(3*4) + 0, ObjectValueType_Ehandle); int client = DHookGetParamObjectPtrVar(hParams, 1, 3*(3*4) + 0, ObjectValueType_Ehandle);
@@ -514,6 +583,10 @@ public MRESReturn Detour_OnTakeDamage(int entity, Handle hReturn, Handle hParams
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnClientHurt(Event hEvent, const char[] sEvent, bool bDontBroadcast) public void OnClientHurt(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
int client = GetClientOfUserId(hEvent.GetInt("attacker")); int client = GetClientOfUserId(hEvent.GetInt("attacker"));
int victim = GetClientOfUserId(hEvent.GetInt("userid")); int victim = GetClientOfUserId(hEvent.GetInt("userid"));
@@ -544,6 +617,10 @@ public void OnClientHurt(Event hEvent, const char[] sEvent, bool bDontBroadcast)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
void ShowOverlay(int client, float fTime = 0.3) void ShowOverlay(int client, float fTime = 0.3)
{ {
if(!g_hCVar_HitmarkerEnabled.BoolValue)
{
return;
}
if (g_bHitmarkerSound[client]) if (g_bHitmarkerSound[client])
{ {
float fVolume = g_iHitmarkerSoundVolume[client] / 100.0; float fVolume = g_iHitmarkerSoundVolume[client] / 100.0;
+5 -2
View File
@@ -177,6 +177,9 @@ public Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam
if(!IsPlayerAlive(victim)) if(!IsPlayerAlive(victim))
return Plugin_Continue; return Plugin_Continue;
if(!IsPlayerAlive(attacker))
return Plugin_Continue;
if(!g_bKnifeBan) if(!g_bKnifeBan)
return Plugin_Continue; return Plugin_Continue;
@@ -552,7 +555,7 @@ public void OpenKnifebanlistMenu(int client)
menu.Display(client, MENU_TIME_FOREVER); menu.Display(client, MENU_TIME_FOREVER);
} }
public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int selection) public void MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int selection)
{ {
switch(action) switch(action)
{ {
@@ -644,7 +647,7 @@ public void OpenKnifebanlistOfTheClient(int client, char sBuffer[32])
menuclient.Display(client, MENU_TIME_FOREVER); menuclient.Display(client, MENU_TIME_FOREVER);
} }
public int MenuHandler_ClientMenu(Menu menuclient, MenuAction action, int client, int selection) public void MenuHandler_ClientMenu(Menu menuclient, MenuAction action, int client, int selection)
{ {
switch(action) switch(action)
{ {
+955
View File
@@ -0,0 +1,955 @@
#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <cstrike>
#include <multicolors>
#include <zombiereloaded>
//2026: add playtime requirements for being unrestricted
#include <unloze_playtime>
char g_sBanListFilePath[256];
Database g_hDatabase;
bool g_bKnifeBanned[MAXPLAYERS + 1];
bool g_bKnifeBan;
ArrayList g_TempBanned;
KeyValues Banlist;
int g_hActiveWeapon;
int g_iClientHours[MAXPLAYERS + 1];
int g_iClientMinutes[MAXPLAYERS + 1];
public Plugin myinfo =
{
name = "KnifeBan",
author = "Dogan",
description = "Tools to handle Zombie Knifers in ZE",
version = "2.0.0",
url = ""
}
public void OnPluginStart()
{
LoadTranslations("common.phrases");
ConVar cvar;
HookConVarChange((cvar = CreateConVar("sm_knifeban_block", "1", "1 = Knifebanned clients are blocked, 0 = Knifebanned clients are not blocked", FCVAR_NONE, true, 0.0, true, 1.0)), g_cvKnifeBan);
g_bKnifeBan = cvar.BoolValue;
delete cvar;
AutoExecConfig(true, "plugin.KnifeBan");
RegAdminCmd("sm_knifeban", Command_Knifeban, ADMFLAG_BAN, "Knifebans a client");
RegAdminCmd("sm_knifeunban", Command_Knifeunban, ADMFLAG_BAN, "Knifeunbans a client");
RegConsoleCmd("sm_knifestatus", Command_Knifestatus, "Returns the knifestatus of a client");
RegConsoleCmd("sm_knifebanlist", Command_Knifebanlist, "Returns the knifebanlist of all clients currently on the server");
g_hActiveWeapon = FindSendPropInfo("CBaseCombatCharacter", "m_hActiveWeapon");
if(g_hActiveWeapon == -1)
SetFailState("Couldnt find CBaseCombatCharacter::m_hActiveWeapon");
CreateTimer(5.0, CheckKnifeBans, _, TIMER_REPEAT);
CreateTimer(300.0, CheckKnifeBansFromWeb, _, TIMER_REPEAT); //check every 5 minutes if something new was done on webpannel
g_TempBanned = new ArrayList();
Banlist = new KeyValues("banlist");
BuildPath(Path_SM, g_sBanListFilePath, sizeof(g_sBanListFilePath), "configs/knifeban/banlist.cfg");
Banlist.ImportFromFile(g_sBanListFilePath);
if(!FileExists(g_sBanListFilePath))
SetFailState("[KnifeBan] Config file missing!");
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i))
OnClientPostAdminCheck(i);
}
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_knifeban_web");
}
}
public void OnMapStart()
{
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_knifeban_web");
}
}
public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
{
if(!db || strlen(error))
{
LogError("Database error: %s", error);
return;
}
g_hDatabase = db;
}
public void OnPluginEnd()
{
Banlist.Rewind();
if(!Banlist.ExportToFile(g_sBanListFilePath))
SetFailState("[KnifeBan] Config file missing!");
}
public void g_cvKnifeBan(ConVar convar, const char[] oldValue, const char[] newValue)
{
g_bKnifeBan = convar.BoolValue;
}
public void OnClientPutInServer(int client)
{
//now knifebanned people cant knife fakeclients forward.
if (IsFakeClient(client))
{
SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamage);
}
}
public void OnClientPostAdminCheck(int client)
{
//autism check
if (!IsFakeClient(client))
{
SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamage);
}
char sAuth[32];
GetClientAuthId(client, AuthId_Steam2, sAuth, sizeof(sAuth));
Banlist.Rewind();
if(Banlist.JumpToKey(sAuth))
{
int length = Banlist.GetNum("duration");
int length_minutes = Banlist.GetNum("duration_minutes");
int time = Banlist.GetNum("time");
int time_minutes = Banlist.GetNum("time_minutes", 0);
if(length == -1)
g_bKnifeBanned[client] = true;
else if(length > 0 || length_minutes > 0)
CheckIfClientIsStillKnifeBanned(sAuth, client, time, time_minutes, length, length_minutes);
}
int accountid = GetSteamAccountID(client);
if(accountid != 0)
{
int index = g_TempBanned.FindValue(accountid);
if(index != -1)
g_bKnifeBanned[client] = true;
}
}
public void OnClientDisconnect(int client)
{
g_iClientHours[client] = 0;
g_iClientMinutes[client] = 0;
g_bKnifeBanned[client] = false;
}
public void OnMapEnd()
{
g_TempBanned.Clear();
}
public Action CheckKnifeBansFromWeb(Handle timer)
{
char sQuery[512];
Format(sQuery, sizeof(sQuery), "select client_steamid, web_addban_required, web_unban_required, web_ban_delete, web_edit_required, time_played_start, length, krcb.admin_name from `KbRestrict_CurrentBans` krcb where krcb.web_addban_required = 1 or krcb.web_unban_required = 1 or krcb.web_ban_delete = 1 or krcb.web_edit_required = 1");
g_hDatabase.Query(SQL_OnQueryCompletedWebKbans, sQuery);
return Plugin_Continue;
}
public void SQL_OnQueryCompletedWebKbans(Database db, DBResultSet results, const char[] error, int iSerial)
{
if (!db || strlen(error))
{
delete results;
LogError("Query error 3: %s", error);
return;
}
while (results.RowCount && results.FetchRow())
{
char sSID[64];
results.FetchString(0, sSID, sizeof(sSID));
int web_addban_required = results.FetchInt(1);
int web_unban_required = results.FetchInt(2);
int web_ban_delete = results.FetchInt(3);
int web_edit_required = results.FetchInt(4);
int time_played_start = results.FetchInt(5);
int length = results.FetchInt(6);
char admin_name[255];
results.FetchString(7, admin_name, sizeof(admin_name));
char sQuery[512];
if (web_addban_required)
{
file_addban(sSID, time_played_start, length, admin_name);
Format(sQuery, sizeof(sQuery), "update `KbRestrict_CurrentBans` set web_addban_required = 0 where client_steamid = '%s' and web_addban_required = 1", sSID);
g_hDatabase.Query(SQL_FinishedQuery, sQuery);
}
else if (web_unban_required)
{
file_unban(sSID);
Format(sQuery, sizeof(sQuery), "update `KbRestrict_CurrentBans` set web_unban_required = 0 where client_steamid = '%s' and web_unban_required = 1", sSID);
g_hDatabase.Query(SQL_FinishedQuery, sQuery);
}
else if (web_ban_delete)
{
file_unban(sSID);
Format(sQuery, sizeof(sQuery), "delete from `KbRestrict_CurrentBans` where client_steamid = '%s' and web_ban_delete = 1", sSID);
g_hDatabase.Query(SQL_FinishedQuery, sQuery);
}
else if (web_edit_required)
{
file_edit_ban(sSID, length);
Format(sQuery, sizeof(sQuery), "update `KbRestrict_CurrentBans` set web_edit_required = 0 where client_steamid = '%s' and web_edit_required = 1", sSID);
g_hDatabase.Query(SQL_FinishedQuery, sQuery);
}
}
delete results;
}
public void file_edit_ban(char sAuth[64], int length)
{
int length_hours = length / 60;
int length_minutes = length % 60;
if(Banlist.JumpToKey(sAuth))
{
Banlist.SetNum("duration", length_hours);
Banlist.SetNum("duration_minutes", length_minutes);
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
}
}
public void file_unban(char sAuth[64])
{
if(Banlist.JumpToKey(sAuth))
{
int history = Banlist.GetNum("history");
Banlist.SetNum("history", history - 1);
Banlist.SetNum("time", 0);
Banlist.SetNum("time_minutes", 0);
Banlist.SetNum("duration", 0);
Banlist.SetNum("duration_minutes", 0);
Banlist.SetString("admin", "");
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
}
}
public void file_addban(char sAuth[64], int time_played_start, int length, char sAdmin[255])
{
int time = time_played_start / 60;
int time_minutes = time_played_start & 60;
int length_hours = length / 60;
int length_minutes = length % 60;
if( length_hours || length_minutes)
{
if(Banlist.JumpToKey(sAuth, true))
{
int history = Banlist.GetNum("history");
Banlist.SetNum("history", history + 1);
Banlist.SetNum("time", time);
Banlist.SetNum("time_minutes", time_minutes);
Banlist.SetNum("duration", length_hours);
Banlist.SetNum("duration_minutes", length_minutes);
Banlist.SetString("admin", sAdmin);
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
}
}
else
{
if(Banlist.JumpToKey(sAuth, true))
{
int history = Banlist.GetNum("history");
Banlist.SetNum("history", history + 1);
Banlist.SetNum("time", time);
Banlist.SetNum("time_minutes", time_minutes);
Banlist.SetNum("duration", -1);
Banlist.SetNum("duration_minutes", -1);
Banlist.SetString("admin", sAdmin);
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
}
}
}
public Action CheckKnifeBans(Handle timer)
{
for(int i = 1; i <= MaxClients; i++)
{
if(!IsClientInGame(i))
continue;
if(g_bKnifeBanned[i])
{
char sAuth[32];
GetClientAuthId(i, AuthId_Steam2, sAuth, sizeof(sAuth));
Banlist.Rewind();
int length;
int length_minutes;
int time;
int time_minutes;
if(Banlist.JumpToKey(sAuth))
{
length = Banlist.GetNum("duration");
length_minutes = Banlist.GetNum("duration_minutes");
time = Banlist.GetNum("time");
time_minutes = Banlist.GetNum("time_minutes", 0);
}
if(length > 0 || length_minutes > 0)
CheckIfClientIsStillKnifeBanned(sAuth, i, time, time_minutes, length, length_minutes);
}
}
return Plugin_Continue;
}
public void CheckIfClientIsStillKnifeBanned(char sAuth[32], int client, int time, int time_minutes, int length, int length_minutes)
{
int timesinceknifeban = g_iClientHours[client] - time;
int timesinceknifeban_minutes = g_iClientMinutes[client] - time_minutes;
if (timesinceknifeban_minutes < 0)
{
timesinceknifeban_minutes += 60;
timesinceknifeban--;
}
//PrintToChatAll("timesinceknifeban: %i. length: %i. timesinceknifeban_minutes: %i. length_minutes: %i", timesinceknifeban, length, timesinceknifeban_minutes, length_minutes);
if (timesinceknifeban < length)
{
g_bKnifeBanned[client] = true;
}
else if (timesinceknifeban <= length && timesinceknifeban_minutes < length_minutes)
{
g_bKnifeBanned[client] = true;
}
else
{
g_bKnifeBanned[client] = false;
Banlist.Rewind();
if(Banlist.JumpToKey(sAuth))
{
Banlist.SetNum("time", 0);
Banlist.SetNum("time_minutes", 0);
Banlist.SetNum("duration", 0);
Banlist.SetNum("duration_minutes", 0);
Banlist.SetString("admin", "");
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
update_knife_unban_mysql(client);
}
}
}
public Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom)
{
if(attacker < 1 || attacker > MaxClients)
return Plugin_Continue;
if(!IsPlayerAlive(victim))
return Plugin_Continue;
if(!IsPlayerAlive(attacker))
return Plugin_Continue;
if(!g_bKnifeBan)
return Plugin_Continue;
if(!g_bKnifeBanned[attacker])
return Plugin_Continue;
if(!ZR_IsClientZombie(victim))
return Plugin_Continue;
if(!ZR_IsClientHuman(attacker))
return Plugin_Continue;
char sWeapon[32];
int iWeapon = GetEntDataEnt2(attacker, g_hActiveWeapon);
if(iWeapon != INVALID_ENT_REFERENCE)
GetEdictClassname(iWeapon, sWeapon, sizeof(sWeapon));
else
return Plugin_Continue;
if(!StrEqual(sWeapon, "weapon_knife") || damagetype & DMG_BLAST)
return Plugin_Continue;
CPrintToChat(attacker, "\x07%s[KnifeBan] \x07%sYou are currently knifebanned.", "E01B5D", "F16767");
damage = 0.0;
return Plugin_Handled;
}
public Action Command_Knifeban(int client, int args)
{
if(!GetCmdArgs())
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sUsage: sm_knifeban <#userid/name> [duration]", "E01B5D", "F16767");
return Plugin_Handled;
}
char sArguments[2][32];
GetCmdArg(1, sArguments[0], sizeof(sArguments[]));
GetCmdArg(2, sArguments[1], sizeof(sArguments[]));
int target;
if((target = FindTarget(client, sArguments[0], true)) == -1)
return Plugin_Handled;
int length;
int length_hours;
int length_minutes;
if(GetCmdArgs() >= 2)
{
length = StringToInt(sArguments[1]);
if(length < 0)
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sThe given knifeban duration is invalid.", "E01B5D", "F16767");
return Plugin_Handled;
}
length_hours = length / 60;
length_minutes = length % 60;
}
if(g_bKnifeBanned[target])
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sThe given client is already knifebanned.", "E01B5D", "F16767");
return Plugin_Handled;
}
g_bKnifeBanned[target] = true;
int time = g_iClientHours[target];
int time_minutes = g_iClientMinutes[target];
char sAdmin[32];
GetClientName(client, sAdmin, sizeof(sAdmin));
if(client == 0)
sAdmin = "Console";
char sAuth[32];
GetClientAuthId(target, AuthId_Steam2, sAuth, sizeof(sAuth));
Banlist.Rewind();
if(GetCmdArgs() >= 2)
{
if(length_hours || length_minutes)
{
CPrintToChatAll("\x07%s[KnifeBan] \x07%s%N\x07%s knifebanned \x07%s%N\x07%s for \x07%s%d\x07%s hours and \x07%s%d\x07%s minutes Playtime.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767", "EDEDED", length_hours, "F16767", "EDEDED", length_minutes, "F16767");
LogAction(client, target, "%L knifebanned %L for %d hours and %d minutes.", client, target, length_hours, length_minutes);
if(Banlist.JumpToKey(sAuth, true))
{
int history = Banlist.GetNum("history");
Banlist.SetNum("history", history + 1);
Banlist.SetNum("time", time);
Banlist.SetNum("time_minutes", time_minutes);
Banlist.SetNum("duration", length_hours);
Banlist.SetNum("duration_minutes", length_minutes);
Banlist.SetString("admin", sAdmin);
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
insert_knifeban_mysql(client, target, length_hours, length_minutes, time, time_minutes);
}
}
else
{
CPrintToChatAll("\x07%s[KnifeBan] \x07%s%N\x07%s knifebanned \x07%s%N\x07%s permanently.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767");
LogAction(client, target, "%L knifebanned %L permanently.", client, target);
if(Banlist.JumpToKey(sAuth, true))
{
int history = Banlist.GetNum("history");
Banlist.SetNum("history", history + 1);
Banlist.SetNum("time", time);
Banlist.SetNum("time_minutes", time_minutes);
Banlist.SetNum("duration", -1);
Banlist.SetNum("duration_minutes", -1);
Banlist.SetString("admin", sAdmin);
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
insert_knifeban_mysql(client, target, -1, -1, time, time_minutes);
}
}
}
else
{
CPrintToChatAll("\x07%s[KnifeBan] \x07%s%N\x07%s knifebanned \x07%s%N\x07%s temporarily.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767");
LogAction(client, target, "%L knifebanned %L temporarily.", client, target);
if(Banlist.JumpToKey(sAuth, true))
{
int history = Banlist.GetNum("history");
Banlist.SetNum("history", history + 1);
Banlist.SetNum("time", time);
Banlist.SetNum("time_minutes", time_minutes);
Banlist.SetString("admin", sAdmin);
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
int accountid = GetSteamAccountID(target);
g_TempBanned.Push(accountid);
}
}
return Plugin_Handled;
}
public void insert_knifeban_mysql(int admin, int knife_banned_player, int length_hours, int length_minutes, int time_start_playertime_hours, int time_start_playertime_minutes)
{
char sNameAdmin[MAX_NAME_LENGTH];
GetClientName(admin, sNameAdmin, sizeof(sNameAdmin));
int size2 = 2 * strlen(sNameAdmin) + 1;
char[] sEscapedNameAdmin = new char[size2 + 1];
g_hDatabase.Escape(sNameAdmin, sEscapedNameAdmin, size2 + 1);
char sSIDAdmin[64];
GetClientAuthId(admin, AuthId_Steam2, sSIDAdmin, sizeof(sSIDAdmin));
char sNameClient[MAX_NAME_LENGTH];
GetClientName(knife_banned_player, sNameClient, sizeof(sNameClient));
size2 = 2 * strlen(sNameClient) + 1;
char[] sEscapedNameClient = new char[size2 + 1];
g_hDatabase.Escape(sNameClient, sEscapedNameClient, size2 + 1);
char sSIDClient[64];
GetClientAuthId(knife_banned_player, AuthId_Steam2, sSIDClient, sizeof(sSIDClient));
char sIP[32];
GetClientIP(knife_banned_player, sIP, sizeof(sIP));
char currentMap[64];
GetCurrentMap(currentMap, sizeof(currentMap));
char sQuery[512];
//length is assumed to be minutes.
int length = (length_hours * 60) + length_minutes;
if (length_hours == -1)
{
length = 0;
}
int playtime_start_client = (time_start_playertime_hours * 60) + time_start_playertime_minutes;
int playtime_end_client = playtime_start_client + length;
if (length_hours == -1)
{
playtime_end_client = -1;
playtime_start_client = -1;
}
Format(sQuery, sizeof(sQuery), "INSERT INTO `KbRestrict_CurrentBans` (`client_name`, `client_steamid`, `client_ip`, `admin_name`, `admin_steamid`, `reason`, `map`, `length`, `time_played_start`, `time_played_end`, `is_expired`, `is_removed`, `admin_name_removed`, `admin_steamid_removed`, `time_stamp_removed`, `reason_removed`) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%i', '%i', '%i', 0, 0, ' ', ' ', 0, ' ')", sEscapedNameClient, sSIDClient, sIP, sEscapedNameAdmin, sSIDAdmin, "get fucked", currentMap, length, playtime_start_client, playtime_end_client);
g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Normal);
}
public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, any data)
{
if (!db || strlen(error))
{
LogError("Query error 3: %s", error);
}
delete results;
}
public Action Command_Knifeunban(int client, int args)
{
if(!GetCmdArgs())
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sUsage: sm_knifeunban <#userid/name>", "E01B5D", "F16767");
return Plugin_Handled;
}
char sArguments[1][32];
GetCmdArg(1, sArguments[0], sizeof(sArguments[]));
int target;
if((target = FindTarget(client, sArguments[0], true)) == -1)
return Plugin_Handled;
if(!g_bKnifeBanned[target])
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sThe given client is not knifebanned.", "E01B5D", "F16767");
return Plugin_Handled;
}
g_bKnifeBanned[target] = false;
Banlist.Rewind();
char sAuth[32];
GetClientAuthId(target, AuthId_Steam2, sAuth, sizeof(sAuth));
if(Banlist.JumpToKey(sAuth))
{
int history = Banlist.GetNum("history");
Banlist.SetNum("history", history - 1);
Banlist.SetNum("time", 0);
Banlist.SetNum("time_minutes", 0);
Banlist.SetNum("duration", 0);
Banlist.SetNum("duration_minutes", 0);
Banlist.SetString("admin", "");
Banlist.Rewind();
Banlist.ExportToFile(g_sBanListFilePath);
update_knife_unban_mysql(target);
}
int accountid = GetSteamAccountID(target);
if(accountid != 0)
{
int index = g_TempBanned.FindValue(accountid);
if(index != -1)
g_TempBanned.Erase(index);
}
CPrintToChatAll("\x07%s[KnifeBan] \x07%s%N\x07%s knifeunbanned \x07%s%N\x07%s.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767");
LogAction(client, target, "%L knifeunbanned %L.", client, target);
return Plugin_Handled;
}
public void update_knife_unban_mysql(int knife_unbanned_client)
{
char sSIDClient[64];
GetClientAuthId(knife_unbanned_client, AuthId_Steam2, sSIDClient, sizeof(sSIDClient));
char sQuery[512];
Format(sQuery, sizeof(sQuery), "update `KbRestrict_CurrentBans` set is_expired = 1 where client_steamid = '%s' and is_expired = 0", sSIDClient);
g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Normal);
}
public Action Command_Knifestatus(int client, int args)
{
if(CheckCommandAccess(client, "", ADMFLAG_BAN) && GetCmdArgs())
{
char sArguments[1][32];
GetCmdArg(1, sArguments[0], sizeof(sArguments[]));
int target;
if((target = FindTarget(client, sArguments[0], true)) == -1)
return Plugin_Handled;
char sAuth[32];
GetClientAuthId(target, AuthId_Steam2, sAuth, sizeof(sAuth));
int accountid = GetSteamAccountID(target);
int index = g_TempBanned.FindValue(accountid);
Banlist.Rewind();
int length_hours;
int length_minutes;
int time;
int time_minutes;
int history;
if(Banlist.JumpToKey(sAuth))
{
length_hours = Banlist.GetNum("duration");
length_minutes = Banlist.GetNum("duration_minutes");
time = Banlist.GetNum("time");
time_minutes = Banlist.GetNum("time_minutes");
history = Banlist.GetNum("history");
}
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%s%N\x07%s has been knifebanned \x07%s%d\x07%s times before.", "E01B5D", "EDEDED", target, "F16767", "EDEDED", history, "F16767");
if(g_bKnifeBanned[target] && index != -1)
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%s%N\x07%s is currently temporarily knifebanned.", "E01B5D", "EDEDED", target, "F16767");
return Plugin_Handled;
}
else if(g_bKnifeBanned[target] && length_hours == -1)
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%s%N\x07%s is currently permanently knifebanned.", "E01B5D", "EDEDED", target, "F16767");
return Plugin_Handled;
}
else if(g_bKnifeBanned[target] && (length_hours > 0 || length_minutes > 0))
{
char sTimeRemaining[64];
int timesinceknifeban = g_iClientHours[target] - time;
int timesinceknifeban_minutes = g_iClientMinutes[target] - time_minutes;
if (timesinceknifeban_minutes < 0)
{
timesinceknifeban_minutes += 60;
timesinceknifeban--;
}
int iTimeRemaining = length_hours - timesinceknifeban;
int iTimeRemaining_minutes = length_minutes - timesinceknifeban_minutes;
if (iTimeRemaining_minutes < 0)
{
iTimeRemaining_minutes += 60;
iTimeRemaining--;
}
Format(sTimeRemaining, sizeof(sTimeRemaining), "%d Hours and %d minutes playtime", iTimeRemaining, iTimeRemaining_minutes);
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%s%N\x07%s is currently knifebanned for another \x07%s%s\x07%s.", "E01B5D", "EDEDED", target, "F16767", "EDEDED", sTimeRemaining, "F16767");
return Plugin_Handled;
}
else
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%s%N\x07%s is currently not knifebanned.", "E01B5D", "EDEDED", target, "F16767");
return Plugin_Handled;
}
}
else
{
char sAuth[32];
GetClientAuthId(client, AuthId_Steam2, sAuth, sizeof(sAuth));
int accountid = GetSteamAccountID(client);
int index = g_TempBanned.FindValue(accountid);
Banlist.Rewind();
int length_hours;
int length_minutes;
int time;
int time_minutes;
if(Banlist.JumpToKey(sAuth))
{
length_hours = Banlist.GetNum("duration");
length_minutes = Banlist.GetNum("duration_minutes");
time = Banlist.GetNum("time");
time_minutes = Banlist.GetNum("time_minutes");
}
if(g_bKnifeBanned[client] && index != -1)
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sYou are currently temporarily knifebanned.", "E01B5D", "F16767");
return Plugin_Handled;
}
else if(g_bKnifeBanned[client] && length_hours == -1)
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sYou are currently permanently knifebanned.", "E01B5D", "F16767");
return Plugin_Handled;
}
else if(g_bKnifeBanned[client] && (length_hours > 0 || length_minutes > 0))
{
char sTimeRemaining[64];
int timesinceknifeban = g_iClientHours[client] - time;
int timesinceknifeban_minutes = g_iClientMinutes[client] - time_minutes;
if (timesinceknifeban_minutes < 0)
{
timesinceknifeban_minutes += 60;
timesinceknifeban--;
}
int iTimeRemaining = length_hours - timesinceknifeban;
int iTimeRemaining_minutes = length_minutes - timesinceknifeban_minutes;
if (iTimeRemaining_minutes < 0)
{
iTimeRemaining_minutes += 60;
iTimeRemaining--;
}
Format(sTimeRemaining, sizeof(sTimeRemaining), "%d Hours and %d minutes playtime", iTimeRemaining, iTimeRemaining_minutes);
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sYou are currently knifebanned for another \x07%s%s\x07%s.", "E01B5D", "F16767", "EDEDED", sTimeRemaining, "F16767");
return Plugin_Handled;
}
else
{
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sYou are currently not knifebanned.", "E01B5D", "F16767");
return Plugin_Handled;
}
}
}
public Action Command_Knifebanlist(int client, int args)
{
char aBuf[1024];
char aBuf2[MAX_NAME_LENGTH];
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i) && !IsFakeClient(i))
{
if(g_bKnifeBanned[i])
{
GetClientName(i, aBuf2, sizeof(aBuf2));
StrCat(aBuf, sizeof(aBuf), aBuf2);
StrCat(aBuf, sizeof(aBuf), ", ");
}
}
}
if(strlen(aBuf))
{
aBuf[strlen(aBuf) - 2] = 0;
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sCurrently knifebanned clients: \x07%s%s", "E01B5D", "F16767", "EDEDED", aBuf);
}
else
CReplyToCommand(client, "\x07%s[KnifeBan] \x07%sCurrently knifebanned clients: \x07%snone", "E01B5D", "F16767", "EDEDED");
if(CheckCommandAccess(client, "", ADMFLAG_BAN))
OpenKnifebanlistMenu(client);
return Plugin_Handled;
}
public void OpenKnifebanlistMenu(int client)
{
if(client == 0)
return;
Menu menu = new Menu(MenuHandler_MainMenu);
menu.SetTitle("Currently knifebanned clients:", client);
char sBuffer[32];
int iMenuItem;
char sMenuItem[8];
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i) && !IsFakeClient(i))
{
if(g_bKnifeBanned[i])
{
GetClientName(i, sBuffer, sizeof(sBuffer));
IntToString(iMenuItem, sMenuItem, sizeof(sMenuItem));
menu.AddItem(sMenuItem, sBuffer);
iMenuItem++;
}
}
}
menu.Display(client, MENU_TIME_FOREVER);
}
public void MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int selection)
{
switch(action)
{
case(MenuAction_Select):
{
char sMenuItem[8];
char sBuffer[32];
int flag;
menu.GetItem(selection, sMenuItem, sizeof(sMenuItem), flag, sBuffer, sizeof(sBuffer));
OpenKnifebanlistOfTheClient(client, sBuffer);
}
case(MenuAction_End):
{
delete menu;
}
}
}
public void OpenKnifebanlistOfTheClient(int client, char sBuffer[32])
{
Menu menuclient = new Menu(MenuHandler_ClientMenu);
char sTitle[64] = "Knifebanned client: ";
StrCat(sTitle, sizeof(sTitle), sBuffer);
int target;
char sName[32];
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i) && !IsFakeClient(i) && g_bKnifeBanned[i])
{
GetClientName(i, sName, sizeof(sName));
if(StrEqual(sName, sBuffer))
{
target = i;
break;
}
}
}
char sAuth[32];
GetClientAuthId(target, AuthId_Steam2, sAuth, sizeof(sAuth));
Banlist.Rewind();
int length;
int length_minutes;
int history;
char sAdmin[32];
if(Banlist.JumpToKey(sAuth))
{
length = Banlist.GetNum("duration");
length_minutes = Banlist.GetNum("duration_minutes");
history = Banlist.GetNum("history");
Banlist.GetString("admin", sAdmin, sizeof(sAdmin));
}
menuclient.SetTitle(sTitle, client);
char sInfo[256];
Format(sInfo, sizeof(sInfo), "Banned by: %s", sAdmin);
menuclient.AddItem("0", sInfo);
if(length > 0)
Format(sInfo, sizeof(sInfo), "Duration: %d Hours and %d minutes playtime", length, length_minutes);
else if(length == -1)
Format(sInfo, sizeof(sInfo), "Duration: Permanently");
else
Format(sInfo, sizeof(sInfo), "Duration: Temporarly");
menuclient.AddItem("1", sInfo);
Format(sInfo, sizeof(sInfo), "Ban History: %d times", history);
menuclient.AddItem("2", sInfo);
menuclient.AddItem("3", "Back to List");
menuclient.Display(client, MENU_TIME_FOREVER);
}
public void MenuHandler_ClientMenu(Menu menuclient, MenuAction action, int client, int selection)
{
switch(action)
{
case(MenuAction_Select):
{
switch(selection)
{
case(4): OpenKnifebanlistMenu(client);
}
}
case(MenuAction_End):
{
delete menuclient;
}
}
}
//----------------------------------------------------------------------------------------------------
// Purpose: 2026 getting the ingame play time of the players. called with OnClientPostAdminCheck() forward and timer every 10 minutes
//----------------------------------------------------------------------------------------------------
public void GetPlayerHoursServer(int client, int hours, int minutes)
{
g_iClientHours[client] = hours;
g_iClientMinutes[client] = minutes;
}
+46
View File
@@ -0,0 +1,46 @@
-- knifeban_web.KbRestrict_CurrentBans definition
CREATE TABLE `KbRestrict_CurrentBans` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`client_name` varchar(64) NOT NULL,
`client_steamid` varchar(64) NOT NULL,
`client_ip` varchar(64) NOT NULL,
`admin_name` varchar(64) NOT NULL,
`admin_steamid` varchar(64) NOT NULL,
`reason` varchar(255) NOT NULL,
`map` varchar(64) NOT NULL,
`length` int(11) NOT NULL,
`time_stamp_start` datetime DEFAULT current_timestamp(),
`time_played_start` int(11) NOT NULL,
`time_played_end` int(11) NOT NULL,
`is_expired` int(11) NOT NULL,
`is_removed` int(11) NOT NULL,
`time_stamp_removed` int(20) NOT NULL,
`admin_name_removed` varchar(255) NOT NULL,
`admin_steamid_removed` varchar(255) NOT NULL,
`reason_removed` varchar(255) NOT NULL,
`web_unban_required` tinyint(1) NOT NULL DEFAULT 0,
`web_addban_required` tinyint(1) NOT NULL DEFAULT 0,
`web_ban_delete` tinyint(1) NOT NULL DEFAULT 0,
`web_edit_required` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_steamid_search` (`client_steamid`),
KEY `idx_ip_search` (`client_ip`),
KEY `idx_admin_steamid` (`admin_steamid`),
KEY `idx_status` (`is_expired`,`is_removed`),
KEY `idx_steamid_ip_status` (`client_steamid`,`client_ip`,`is_expired`,`is_removed`),
KEY `idx_web_unban_required` (`web_unban_required`),
KEY `idx_web_addban_required` (`web_addban_required`),
KEY `idx_web_ban_delete` (`web_ban_delete`),
KEY `idx_web_edit_required` (`web_edit_required`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS knifeban_web.KbRestrict_weblogs (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`message` varchar(200) NOT NULL,
`admin_name` varchar(255) NOT NULL,
`admin_steamid` varchar(64) NOT NULL,
`client_name` varchar(255) NOT NULL,
`client_steamid` varchar(64) NOT NULL,
`time_stamp` int(20) NOT NULL,
PRIMARY KEY(`id`));
@@ -5,6 +5,7 @@
#include <dhooks> #include <dhooks>
#include <clientprefs> #include <clientprefs>
#include <multicolors> #include <multicolors>
#include <mapchooser_extended>
#define SetBit(%1,%2) ((%1)[(%2) >> 5] |= (1 << ((%2) & 31))) #define SetBit(%1,%2) ((%1)[(%2) >> 5] |= (1 << ((%2) & 31)))
#define ClearBit(%1,%2) ((%1)[(%2) >> 5] &= ~(1 << ((%2) & 31))) #define ClearBit(%1,%2) ((%1)[(%2) >> 5] &= ~(1 << ((%2) & 31)))
@@ -18,7 +19,7 @@ public Plugin myinfo =
name = "LagCompensation", name = "LagCompensation",
author = "BotoX, Vauff, .Rushaway, maxime1907", author = "BotoX, Vauff, .Rushaway, maxime1907",
description = "", description = "",
version = "1.0.6", version = "1.2.0",
url = "" url = ""
}; };
@@ -26,9 +27,10 @@ bool g_bLateLoad = false;
bool g_bHasPhysHooks = true; bool g_bHasPhysHooks = true;
bool g_bHasOnEntitySpawned = false; bool g_bHasOnEntitySpawned = false;
bool g_bCheckPing = false; bool g_bCheckPing = false;
bool g_bMapDisabled = false;
int g_iCompenseEntityClass; int g_iCompenseEntityClass;
// Dont change this. // Don't change this.
#define MAX_EDICTS 2048 #define MAX_EDICTS 2048
#define FSOLID_FORCE_WORLD_ALIGNED 0x0040 #define FSOLID_FORCE_WORLD_ALIGNED 0x0040
#define FSOLID_ROOT_PARENT_ALIGNED 0x0100 #define FSOLID_ROOT_PARENT_ALIGNED 0x0100
@@ -104,6 +106,7 @@ enum struct EntityLagData
int iNotMoving; int iNotMoving;
bool bRestore; bool bRestore;
bool bLateKill; bool bLateKill;
char sClassname[64];
} }
LagRecord g_aaLagRecords[MAX_ENTITIES][MAX_RECORDS]; LagRecord g_aaLagRecords[MAX_ENTITIES][MAX_RECORDS];
@@ -111,6 +114,12 @@ EntityLagData g_aEntityLagData[MAX_ENTITIES];
int g_iNumEntities = 0; int g_iNumEntities = 0;
bool g_bCleaningUp = true; bool g_bCleaningUp = true;
// When more than sm_lagcomp_max_entities entities are lag compensation at the same time we switch off lag compensation
//enabled again on next round or through command
bool g_bLagCompSuspended = false; // nothing new gets compensated
bool g_bReleasePending = false; // the tracked entities still have to be released
int g_iMaxEntities = MAX_ENTITIES;
// Cache // Cache
int g_iGameTick; int g_iGameTick;
float g_fTickInterval; float g_fTickInterval;
@@ -159,22 +168,25 @@ int g_aaFilterClientSolidTouch[((MAXPLAYERS + 1) * MAX_EDICTS) / 32];
int g_aBlockTriggerMoved[MAX_EDICTS / 32]; int g_aBlockTriggerMoved[MAX_EDICTS / 32];
int g_aBlacklisted[MAX_EDICTS / 32]; int g_aBlacklisted[MAX_EDICTS / 32];
Handle g_hCookie_EnableLagComp; Handle g_hCookie_LagCompSettings;
Handle g_hCookie_LagCompMessages; bool g_bDisableLagComp[MAXPLAYERS + 1];
bool g_bEnableLagComp[MAXPLAYERS + 1]; bool g_bPlayerLaserCompensated[MAXPLAYERS + 1];
bool g_bLagCompMessages[MAXPLAYERS + 1]; bool g_bLagCompMessages[MAXPLAYERS + 1];
bool g_bSettingsTouched[MAXPLAYERS + 1];
int g_iDisableLagComp[MAXPLAYERS + 1]; int g_iDisableLagComp[MAXPLAYERS + 1];
ConVar g_cvCompenseEntityClass; ConVar g_cvCompenseEntityClass;
ConVar g_cvCheckUserPing; ConVar g_cvCheckUserPing;
ConVar g_cvMinimumPing; ConVar g_cvMinimumPing;
ConVar g_cvMaxEntities;
public void OnPluginStart() public void OnPluginStart()
{ {
LoadTranslations("common.phrases"); LoadTranslations("common.phrases");
g_cvCompenseEntityClass = CreateConVar("sm_lagcomp_entityclass", "0", "Only lagcompensate: [0 = All | 1 = func_physbox only]", _, true, 0.0, true, 1.0); g_cvCompenseEntityClass = CreateConVar("sm_lagcomp_entityclass", "0", "Only lagcompensate: [0 = All | 1 = func_physbox only]", _, true, 0.0, true, 1.0);
g_cvCheckUserPing = CreateConVar("sm_lagcomp_checkuserping", "0", "Check users ping before lagcompensating them [0 = Disabled | 1 = Enabled]", _, true, 0.0, true, 1.0); g_cvCheckUserPing = CreateConVar("sm_lagcomp_checkuserping", "0", "Check user's ping before lagcompensating them [0 = Disabled | 1 = Enabled]", _, true, 0.0, true, 1.0);
g_cvMinimumPing = CreateConVar("sm_lagcomp_ping", "150", "Minimum ping threshold to apply lagcomp on a client", _, true, 0.0); g_cvMinimumPing = CreateConVar("sm_lagcomp_ping", "180", "Minimum ping threshold to apply lagcomp on a client", _, true, 0.0);
g_cvMaxEntities = CreateConVar("sm_lagcomp_max_entities", "30", "Maximum entities lag compensated at the same time. Going over it disables lag compensation for everyone until the next round. [1-256]", _, true, 1.0, true, float(MAX_ENTITIES));
AutoExecConfig(true); AutoExecConfig(true);
@@ -345,19 +357,21 @@ public void OnPluginStart()
HookConVarChange(g_cvCompenseEntityClass, OnConVarChanged); HookConVarChange(g_cvCompenseEntityClass, OnConVarChanged);
HookConVarChange(g_cvCheckUserPing, OnConVarChanged); HookConVarChange(g_cvCheckUserPing, OnConVarChanged);
HookConVarChange(g_cvMinimumPing, OnConVarChanged); HookConVarChange(g_cvMinimumPing, OnConVarChanged);
HookConVarChange(g_cvMaxEntities, OnConVarChanged);
g_hCookie_EnableLagComp = RegClientCookie("enable_lagcomp", "Client LagComp Status", CookieAccess_Private); g_hCookie_LagCompSettings = RegClientCookie("lagcomp_settings", "LagCompensation Settings", CookieAccess_Private);
g_hCookie_LagCompMessages = RegClientCookie("lagcomp_messages", "Print LagComp messages in chat", CookieAccess_Private); RegConsoleCmd("sm_lagcomp", OnLagCompSettings);
RegConsoleCmd("sm_lagcomp", OnToggleLagCompSettings);
RegConsoleCmd("sm_0ping", OnToggleLagCompSettings); RegConsoleCmd("sm_0ping", OnToggleLagCompSettings);
RegConsoleCmd("sm_0pinglasers", OnToggleLagCompLasersSettings);
RegConsoleCmd("sm_checklag", CheckLagComp); RegConsoleCmd("sm_checklag", CheckLagComp);
SetCookieMenuItem(MenuHandler_CookieMenu, 0, "LagCompensation"); SetCookieMenuItem(MenuHandler_CookieMenu, 0, "LagCompensation");
CreateTimer(0.1, DisableLagCompTimer, _, TIMER_REPEAT); CreateTimer(0.1, DisableLagCompTimer, _, TIMER_REPEAT);
RegAdminCmd("sm_unlag", Command_AddLagCompensation, ADMFLAG_ROOT, "sm_unlag <entidx>"); RegAdminCmd("sm_unlag", Command_AddLagCompensation, ADMFLAG_ROOT, "sm_unlag <entidx> [late]");
RegAdminCmd("sm_lagged", Command_CheckLagCompensated, ADMFLAG_ROOT, "sm_lagged"); RegAdminCmd("sm_lagged", Command_CheckLagCompensated, ADMFLAG_ROOT, "sm_lagged");
RegAdminCmd("sm_lagcomp_enable", Command_EnableLagCompensation, ADMFLAG_GENERIC, "Re-enables lag compensation after it was disabled for going over sm_lagcomp_max_entities");
FilterClientSolidTouch(g_aaFilterClientSolidTouch, true); FilterClientSolidTouch(g_aaFilterClientSolidTouch, true);
BlockTriggerMoved(g_aBlockTriggerMoved, true); BlockTriggerMoved(g_aBlockTriggerMoved, true);
@@ -405,6 +419,7 @@ public void OnConfigsExecuted()
g_iCompenseEntityClass = GetConVarInt(g_cvCompenseEntityClass); g_iCompenseEntityClass = GetConVarInt(g_cvCompenseEntityClass);
g_bCheckPing = GetConVarBool(g_cvCheckUserPing); g_bCheckPing = GetConVarBool(g_cvCheckUserPing);
g_iMinPing = GetConVarInt(g_cvMinimumPing); g_iMinPing = GetConVarInt(g_cvMinimumPing);
g_iMaxEntities = GetConVarInt(g_cvMaxEntities);
} }
void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
@@ -415,10 +430,16 @@ void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue
g_bCheckPing = GetConVarBool(convar); g_bCheckPing = GetConVarBool(convar);
else if (convar == g_cvMinimumPing) else if (convar == g_cvMinimumPing)
g_iMinPing = GetConVarInt(convar); g_iMinPing = GetConVarInt(convar);
else if (convar == g_cvMaxEntities)
g_iMaxEntities = GetConVarInt(convar);
} }
public void OnMapStart() public void OnMapStart()
{ {
char map[128];
GetCurrentMap(map, sizeof(map));
g_bMapDisabled = GetMapLagCompensationDisabled(map);
bool bLate = g_bLateLoad; bool bLate = g_bLateLoad;
DHookGamerules(g_hEndGameFrame, true); DHookGamerules(g_hEndGameFrame, true);
@@ -486,18 +507,27 @@ public void OnMapEnd()
public void OnClientConnected(int client) public void OnClientConnected(int client)
{ {
g_bEnableLagComp[client] = false; // Must match the defaults in ParseClientCookie() for clients whose cookies never cache.
g_bDisableLagComp[client] = false;
g_bPlayerLaserCompensated[client] = true;
g_bLagCompMessages[client] = false;
g_bSettingsTouched[client] = false;
g_iDisableLagComp[client] = 0; g_iDisableLagComp[client] = 0;
g_aLerpTicks[client] = 0; g_aLerpTicks[client] = 0;
} }
public void OnClientCookiesCached(int client) public void OnClientCookiesCached(int client)
{ {
char sBuffer[16]; // A toggle made before the cookies arrived wins, so flush it instead of
GetClientCookie(client, g_hCookie_EnableLagComp, sBuffer, sizeof(sBuffer)); // overwriting the client's choice with the stored value.
g_bEnableLagComp[client] = StringToInt(sBuffer) != 0; //this wording should make more sense, if the cookie exists its true to enable it. if(g_bSettingsTouched[client])
GetClientCookie(client, g_hCookie_LagCompMessages, sBuffer, sizeof(sBuffer)); {
g_bLagCompMessages[client] = StringToInt(sBuffer) != 0; SaveLagCompSettings(client);
return;
}
// Load all settings from single cookie
ParseClientCookie(client, g_bDisableLagComp[client], g_bPlayerLaserCompensated[client], g_bLagCompMessages[client]);
} }
public void OnClientSettingsChanged(int client) public void OnClientSettingsChanged(int client)
@@ -511,7 +541,10 @@ public void OnClientSettingsChanged(int client)
public void OnClientDisconnect(int client) public void OnClientDisconnect(int client)
{ {
g_bEnableLagComp[client] = false; g_bDisableLagComp[client] = false;
g_bPlayerLaserCompensated[client] = true;
g_bLagCompMessages[client] = false;
g_bSettingsTouched[client] = false;
g_iDisableLagComp[client] = 0; g_iDisableLagComp[client] = 0;
} }
@@ -529,8 +562,9 @@ public void OnEntityCreated(int entity, const char[] classname)
DHookEntity(g_hAcceptInput, true, entity); DHookEntity(g_hAcceptInput, true, entity);
} }
if (!g_bHasOnEntitySpawned && (!strncmp(classname, "func_physbox", 12, false) || (g_iCompenseEntityClass == 0 && strcmp(classname, "trigger_hurt", false) == 0 || if (!g_bHasOnEntitySpawned && (!strncmp(classname, "func_physbox", 12, false) ||
strcmp(classname, "trigger_push", false) == 0 || strcmp(classname, "trigger_teleport", false) == 0))) (g_iCompenseEntityClass == 0 && (strcmp(classname, "trigger_hurt", false) == 0 ||
strcmp(classname, "trigger_push", false) == 0 || strcmp(classname, "trigger_teleport", false) == 0))))
{ {
SDKHook(entity, SDKHook_SpawnPost, OnSDKHookEntitySpawnPost); SDKHook(entity, SDKHook_SpawnPost, OnSDKHookEntitySpawnPost);
} }
@@ -570,7 +604,7 @@ public MRESReturn Hook_AcceptInput(int entity, Handle hReturn, Handle hParams)
if(iCaller <= 0 || iCaller >= MAX_EDICTS) if(iCaller <= 0 || iCaller >= MAX_EDICTS)
return MRES_Ignored; return MRES_Ignored;
// Dont lagcompensate anything that has a game_ui button in their hierarchy. // Don't lagcompensate anything that has a game_ui button in their hierarchy.
BlacklistFamily(iCaller); BlacklistFamily(iCaller);
return MRES_Ignored; return MRES_Ignored;
@@ -623,12 +657,16 @@ void BlacklistChildren(int parent)
bool CheckEntityForLagComp(int entity, const char[] classname, bool bRecursive=false, bool bGoodParents=false) bool CheckEntityForLagComp(int entity, const char[] classname, bool bRecursive=false, bool bGoodParents=false)
{ {
if(entity < 0 || entity > MAX_EDICTS) if(entity < 0 || entity >= MAX_EDICTS)
return false; return false;
if(!IsValidEntity(entity)) if(!IsValidEntity(entity))
return false; return false;
// Disabled for the rest of the round.
if(g_bLagCompSuspended)
return false;
if(g_aLagCompensated[entity] != -1) if(g_aLagCompensated[entity] != -1)
return false; return false;
@@ -668,7 +706,7 @@ bool CheckEntityForLagComp(int entity, const char[] classname, bool bRecursive=f
return false; return false;
} }
if(CheckBit(g_aBlacklisted, entity)) if(CheckBit(g_aBlacklisted, iParent))
{ {
return false; return false;
} }
@@ -695,6 +733,14 @@ bool CheckEntityForLagComp(int entity, const char[] classname, bool bRecursive=f
if(!bGoodParents) if(!bGoodParents)
return false; return false;
// One more than allowed: switch lag compensation off until the next round.
//switch lag comp off every round if the map is not allowed to use it
if(g_iNumEntities >= g_iMaxEntities || g_bMapDisabled)
{
SuspendLagCompensation();
return false;
}
if(AddEntityForLagCompensation(entity, bTrigger)) if(AddEntityForLagCompensation(entity, bTrigger))
{ {
if(bTrigger) if(bTrigger)
@@ -736,7 +782,7 @@ public void OnEntityDestroyed(int entity)
if(g_bCleaningUp) if(g_bCleaningUp)
return; return;
if(entity < 0 || entity > MAX_EDICTS) if(entity < 0 || entity >= MAX_EDICTS)
return; return;
ClearBit(g_aBlacklisted, entity); ClearBit(g_aBlacklisted, entity);
@@ -758,7 +804,7 @@ public MRESReturn Detour_OnUTIL_Remove(Handle hParams)
return MRES_Ignored; return MRES_Ignored;
int entity = DHookGetParamObjectPtrVar(hParams, 1, g_iNetworkableOuter, ObjectValueType_CBaseEntityPtr); int entity = DHookGetParamObjectPtrVar(hParams, 1, g_iNetworkableOuter, ObjectValueType_CBaseEntityPtr);
if(entity < 0 || entity > MAX_EDICTS) if(entity < 0 || entity >= MAX_EDICTS)
return MRES_Ignored; return MRES_Ignored;
int iIndex = g_aLagCompensated[entity]; int iIndex = g_aLagCompensated[entity];
@@ -785,9 +831,32 @@ public MRESReturn Detour_OnRestartRound()
{ {
g_bCleaningUp = true; g_bCleaningUp = true;
ReleaseAllEntities();
for(int i = 0; i < sizeof(g_aBlacklisted); i++)
g_aBlacklisted[i] = 0;
// New round, lag compensation is back on.
g_bLagCompSuspended = false;
g_bCleaningUp = false;
return MRES_Ignored;
}
// Stops compensating everything that is currently tracked.
void ReleaseAllEntities()
{
g_bReleasePending = false;
for(int i = 0; i < g_iNumEntities; i++) for(int i = 0; i < g_iNumEntities; i++)
{ {
int iEntity = g_aEntityLagData[i].iEntity; int iEntity = g_aEntityLagData[i].iEntity;
bool bValid = IsValidEntity(iEntity);
// Should never be rewound at this point, but never leave an entity in the past.
if(g_aEntityLagData[i].bRestore && bValid)
RestoreEntityFromRecord(iEntity, g_aaLagRecords[i][g_aEntityLagData[i].iRecordIndex]);
g_aEntityLagData[i].bRestore = false;
g_aLagCompensated[iEntity] = -1; g_aLagCompensated[iEntity] = -1;
ClearBit(g_aBlockTriggerTouchPlayers, iEntity); ClearBit(g_aBlockTriggerTouchPlayers, iEntity);
@@ -798,22 +867,39 @@ public MRESReturn Detour_OnRestartRound()
ClearBit(g_aaFilterClientSolidTouch, client * MAX_EDICTS + iEntity); ClearBit(g_aaFilterClientSolidTouch, client * MAX_EDICTS + iEntity);
} }
if(g_aEntityLagData[i].iDeleted) if(g_aEntityLagData[i].iDeleted && bValid)
{ {
if(IsValidEntity(iEntity)) ClearBit(g_aBlacklisted, iEntity);
RemoveEdict(iEntity); RemoveEdict(iEntity);
} }
g_aEntityLagData[i].iEntity = INVALID_ENT_REFERENCE; g_aEntityLagData[i].iEntity = INVALID_ENT_REFERENCE;
} }
for(int i = 0; i < sizeof(g_aBlacklisted); i++)
g_aBlacklisted[i] = 0;
g_iNumEntities = 0; g_iNumEntities = 0;
}
g_bCleaningUp = false; void ReleaseAllEntitiesNow()
return MRES_Ignored; {
bool bWasCleaningUp = g_bCleaningUp;
g_bCleaningUp = true;
ReleaseAllEntities();
g_bCleaningUp = bWasCleaningUp;
}
void SuspendLagCompensation()
{
if(g_bLagCompSuspended)
return;
g_bLagCompSuspended = true;
g_bReleasePending = true;
for(int client = 1; client <= MaxClients; client++)
{
if(IsClientInGame(client) && !IsFakeClient(client) && !g_bDisableLagComp[client])
CPrintToChat(client, "%s LagCompensation has been {red}disabled{default} until the next round, too many entities needed to be compensated.", PREFIX);
}
} }
// https://developer.valvesoftware.com/wiki/Logic_measure_movement // https://developer.valvesoftware.com/wiki/Logic_measure_movement
@@ -858,8 +944,8 @@ public MRESReturn Detour_OnFrameUpdatePostEntityThink()
for(int i = 0; i < g_iNumEntities; i++) for(int i = 0; i < g_iNumEntities; i++)
{ {
// Dont make the entity check untouch in FrameUpdatePostEntityThink. // Dont make the entity check untouch in FrameUpdatePostEntityThink.
// If the player didnt get simulated in the current frame then // If the player didn't get simulated in the current frame then
// they didnt have a chance to touch this entity. // they didn't have a chance to touch this entity.
// Hence the touchlink could be broken and we only let the player check untouch. // Hence the touchlink could be broken and we only let the player check untouch.
int EFlags = GetEntData(g_aEntityLagData[i].iEntity, g_iEFlags); int EFlags = GetEntData(g_aEntityLagData[i].iEntity, g_iEFlags);
EFlags &= ~EFL_CHECK_UNTOUCH; EFlags &= ~EFL_CHECK_UNTOUCH;
@@ -877,7 +963,10 @@ public void OnRunThinkFunctions(bool simulating)
public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2])
{ {
if(!IsPlayerAlive(client) || IsFakeClient(client)) if(!IsClientInGame(client) || !IsPlayerAlive(client) || IsFakeClient(client))
return Plugin_Continue;
if(g_bLagCompSuspended)
return Plugin_Continue; return Plugin_Continue;
int iTargetTick = tickcount - g_aLerpTicks[client]; int iTargetTick = tickcount - g_aLerpTicks[client];
@@ -928,6 +1017,13 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3
continue; continue;
int iRecord = iDelta; int iRecord = iDelta;
if(!g_bPlayerLaserCompensated[client] &&
(strcmp(g_aEntityLagData[i].sClassname, "trigger_hurt", false) == 0 ||
strcmp(g_aEntityLagData[i].sClassname, "trigger_push", false) == 0 ||
strcmp(g_aEntityLagData[i].sClassname, "trigger_teleport", false) == 0))
iRecord = 0;
if(iRecord >= g_aEntityLagData[i].iNumRecords) if(iRecord >= g_aEntityLagData[i].iNumRecords)
iRecord = g_aEntityLagData[i].iNumRecords - 1; iRecord = g_aEntityLagData[i].iNumRecords - 1;
@@ -958,6 +1054,13 @@ public void OnPostPlayerThinkFunctions()
public MRESReturn Hook_EndGameFrame() public MRESReturn Hook_EndGameFrame()
{ {
// Lag compensation got disabled this frame, let go of everything.
if(g_bReleasePending)
{
ReleaseAllEntitiesNow();
return MRES_Ignored;
}
for(int i = 0; i < g_iNumEntities; i++) for(int i = 0; i < g_iNumEntities; i++)
{ {
if(g_aEntityLagData[i].iDeleted) if(g_aEntityLagData[i].iDeleted)
@@ -1112,6 +1215,10 @@ bool AddEntityForLagCompensation(int iEntity, bool bLateKill)
if(g_bCleaningUp) if(g_bCleaningUp)
return false; return false;
// sm_unlag takes an arbitrary index from the console, so validate before indexing anything.
if(iEntity < 0 || iEntity >= MAX_EDICTS || !IsValidEntity(iEntity))
return false;
if(g_aLagCompensated[iEntity] != -1) if(g_aLagCompensated[iEntity] != -1)
return false; return false;
@@ -1144,6 +1251,7 @@ bool AddEntityForLagCompensation(int iEntity, bool bLateKill)
g_aEntityLagData[i].iNotMoving = MAX_RECORDS; g_aEntityLagData[i].iNotMoving = MAX_RECORDS;
g_aEntityLagData[i].bRestore = false; g_aEntityLagData[i].bRestore = false;
g_aEntityLagData[i].bLateKill = bLateKill; g_aEntityLagData[i].bLateKill = bLateKill;
g_aEntityLagData[i].sClassname = sClassname;
if(bLateKill) if(bLateKill)
{ {
@@ -1226,6 +1334,7 @@ void EntityLagData_Copy(EntityLagData obj, const EntityLagData other)
obj.iNotMoving = other.iNotMoving; obj.iNotMoving = other.iNotMoving;
obj.bRestore = other.bRestore; obj.bRestore = other.bRestore;
obj.bLateKill = other.bLateKill; obj.bLateKill = other.bLateKill;
obj.sClassname = other.sClassname;
} }
void LagRecord_Copy(LagRecord obj, const LagRecord other) void LagRecord_Copy(LagRecord obj, const LagRecord other)
@@ -1253,6 +1362,59 @@ bool CompareVectors(const float vec1[3], const float vec2[3])
return vec1[0] == vec2[0] && vec1[1] == vec2[1] && vec1[2] == vec2[2]; return vec1[0] == vec2[0] && vec1[1] == vec2[1] && vec1[2] == vec2[2];
} }
void SaveLagCompSettings(int client)
{
// Remember that this client chose something, so OnClientCookiesCached
// flushes it rather than loading over the top of it.
g_bSettingsTouched[client] = true;
// Cookie reads/writes are meaningless until the cookies arrive; we get called back then.
if(!AreClientCookiesCached(client))
return;
// Read current cookie values
bool currentDisableLagComp, currentPlayerLaserCompensated, currentLagCompMessages;
ParseClientCookie(client, currentDisableLagComp, currentPlayerLaserCompensated, currentLagCompMessages);
// Check if values have changed
bool disableChanged = g_bDisableLagComp[client] != currentDisableLagComp;
bool laserChanged = g_bPlayerLaserCompensated[client] != currentPlayerLaserCompensated;
bool messagesChanged = g_bLagCompMessages[client] != currentLagCompMessages;
// If no values have changed, no need to save
if (!disableChanged && !laserChanged && !messagesChanged) {
return;
}
// Otherwise, save the chain format
char sBuffer[8];
Format(sBuffer, sizeof(sBuffer), "%d%d%d",
g_bDisableLagComp[client] ? 1 : 0,
g_bPlayerLaserCompensated[client] ? 1 : 0,
g_bLagCompMessages[client] ? 1 : 0);
SetClientCookie(client, g_hCookie_LagCompSettings, sBuffer);
}
void ParseClientCookie(int client, bool &disableLagComp, bool &playerLaserCompensated, bool &lagCompMessages)
{
char buffer[16];
GetClientCookie(client, g_hCookie_LagCompSettings, buffer, sizeof(buffer));
// Parse chain format: disable|laser|messages
if (strlen(buffer) >= 3)
{
disableLagComp = (buffer[0] == '1');
playerLaserCompensated = (buffer[1] == '1');
lagCompMessages = (buffer[2] == '1');
}
else
{
// Default values
disableLagComp = false;
playerLaserCompensated = true;
lagCompMessages = false;
}
}
public Action Command_AddLagCompensation(int client, int argc) public Action Command_AddLagCompensation(int client, int argc)
{ {
@@ -1274,11 +1436,43 @@ public Action Command_AddLagCompensation(int client, int argc)
late = view_as<bool>(StringToInt(sArgs)); late = view_as<bool>(StringToInt(sArgs));
} }
if(g_bLagCompSuspended)
{
ReplyToCommand(client, "[SM] LagCompensation is disabled until the next round, use sm_lagcomp_enable first.");
return Plugin_Handled;
}
AddEntityForLagCompensation(entity, late); AddEntityForLagCompensation(entity, late);
return Plugin_Handled; return Plugin_Handled;
} }
public Action Command_EnableLagCompensation(int client, int argc)
{
if(!g_bLagCompSuspended)
{
ReplyToCommand(client, "[SM] LagCompensation is already enabled.");
return Plugin_Handled;
}
// Let go of whatever was tracked before it got disabled, so only
// entities that spawn from now on are compensated.
if(g_bReleasePending)
ReleaseAllEntitiesNow();
g_bLagCompSuspended = false;
ReplyToCommand(client, "[SM] LagCompensation has been re-enabled.");
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i) && !IsFakeClient(i) && !g_bDisableLagComp[i])
CPrintToChat(i, "%s LagCompensation has been {green}re-enabled{default} by an admin.", PREFIX);
}
return Plugin_Handled;
}
public Action Command_CheckLagCompensated(int client, int argc) public Action Command_CheckLagCompensated(int client, int argc)
{ {
if(argc == 0) if(argc == 0)
@@ -1296,6 +1490,9 @@ public Action Command_CheckLagCompensated(int client, int argc)
PrintToConsole(client, "%2d. #%d %s \"%s\" (#%d)", i, g_aEntityLagData[i].iEntity, sClassname, sTargetname, iHammerID); PrintToConsole(client, "%2d. #%d %s \"%s\" (#%d)", i, g_aEntityLagData[i].iEntity, sClassname, sTargetname, iHammerID);
} }
PrintToConsole(client, "Active: %d / %d%s", g_iNumEntities, g_iMaxEntities,
g_bLagCompSuspended ? " | DISABLED until next round (over the limit)" : "");
return Plugin_Handled; return Plugin_Handled;
} }
@@ -1355,9 +1552,9 @@ stock void PrintToBoth(const char[] format, any ...)
for(int client = 1; client <= MaxClients; client++) for(int client = 1; client <= MaxClients; client++)
{ {
if (IsClientInGame(client) && g_bLagCompMessages[client]) if (IsClientInGame(client) && g_bLagCompMessages[client] && CheckCommandAccess(client, "sm_lagged", ADMFLAG_ROOT))
{ {
PrintToChat(client, "%s", buffer); PrintToConsole(client, "%s", buffer);
} }
} }
} }
@@ -1370,22 +1567,25 @@ public Action DisableLagCompTimer(Handle timer)
continue; continue;
// https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/game/server/util.cpp#L747 // https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/game/server/util.cpp#L747
// Lag compensation only kicks in above the ping threshold, so ramp it off below it.
if (g_bCheckPing) if (g_bCheckPing)
{ {
int iAvgPing = RoundFloat(GetClientAvgLatency(client, NetFlow_Outgoing) * 1000); int iAvgPing = RoundFloat(GetClientAvgLatency(client, NetFlow_Outgoing) * 1000);
if (iAvgPing > g_iMinPing) if (iAvgPing <= g_iMinPing)
{ {
if(g_iDisableLagComp[client] < MAX_RECORDS)
g_iDisableLagComp[client]++; g_iDisableLagComp[client]++;
return Plugin_Continue;
continue;
} }
} }
if(!g_bEnableLagComp[client] && g_iDisableLagComp[client] < MAX_RECORDS) if(g_bDisableLagComp[client] && g_iDisableLagComp[client] < MAX_RECORDS)
{ {
g_iDisableLagComp[client]++; g_iDisableLagComp[client]++;
} }
else if(g_bEnableLagComp[client] && g_iDisableLagComp[client] > 0) else if(!g_bDisableLagComp[client] && g_iDisableLagComp[client] > 0)
{ {
g_iDisableLagComp[client]--; g_iDisableLagComp[client]--;
} }
@@ -1406,33 +1606,55 @@ public Action OnToggleLagCompSettings(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
public Action OnToggleLagCompLasersSettings(int client, int args)
{
ToggleLagCompLasers(client);
return Plugin_Handled;
}
public void ToggleLagCompSettings(int client) public void ToggleLagCompSettings(int client)
{ {
if(!client) if(!client || !IsClientInGame(client) || IsFakeClient(client))
return; return;
g_bEnableLagComp[client] = !g_bEnableLagComp[client]; g_bDisableLagComp[client] = !g_bDisableLagComp[client];
SetClientCookie(client, g_hCookie_EnableLagComp, g_bEnableLagComp[client] ? "1" : ""); SaveLagCompSettings(client);
CPrintToChat(client, "%s LagCompensation has been %s.", PREFIX, g_bEnableLagComp[client] ? "enabled" : "disabled"); CPrintToChat(client, "%s LagCompensation has been %s.", PREFIX, g_bDisableLagComp[client] ? "disabled" : "enabled");
if(!g_bDisableLagComp[client] && g_bLagCompSuspended)
CPrintToChat(client, "%s It is currently {red}disabled{default} for everyone until the next round (too many entities).", PREFIX);
}
public void ToggleLagCompLasers(int client)
{
if(!client || !IsClientInGame(client) || IsFakeClient(client))
return;
g_bPlayerLaserCompensated[client] = !g_bPlayerLaserCompensated[client];
SaveLagCompSettings(client);
CPrintToChat(client, "%s LagCompensation for Lasers has been %s.", PREFIX, g_bPlayerLaserCompensated[client] ? "enabled" : "disabled");
if (g_bCheckPing && g_bPlayerLaserCompensated[client])
CPrintToChat(client, "%s It will take effect if your real ping is higher than %dms.", PREFIX, g_iMinPing);
} }
public void ToggleLagCompMessages(int client) public void ToggleLagCompMessages(int client)
{ {
if(!client) if(!client || !IsClientInGame(client) || IsFakeClient(client))
return; return;
g_bLagCompMessages[client] = !g_bLagCompMessages[client]; g_bLagCompMessages[client] = !g_bLagCompMessages[client];
SetClientCookie(client, g_hCookie_LagCompMessages, g_bLagCompMessages[client] ? "1" : ""); SaveLagCompSettings(client);
CPrintToChat(client, "%s LagCompensation messages have been %s.", PREFIX, g_bLagCompMessages[client] ? "enabled" : "disabled"); CPrintToChat(client, "%s LagCompensation messages have been %s.", PREFIX, g_bLagCompMessages[client] ? "enabled" : "disabled");
} }
public Action CheckLagComp(int client, int args) public Action CheckLagComp(int client, int args)
{ {
if (args > 1) if (args > 1 || (args == 0 && !client))
{ {
CPrintToChat(client, "%s Usage sm_checklag <client>", PREFIX); CReplyToCommand(client, "%s Usage: sm_checklag <client>", PREFIX);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -1455,7 +1677,19 @@ public Action CheckLagComp(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
CPrintToChat(client, "%s LagCompensation is %s for {olive}%N", PREFIX, g_bEnableLagComp[target] ? sEnable : "disabled", target); CReplyToCommand(client, "%s LagCompensation is %s for {olive}%N", PREFIX, g_bDisableLagComp[target] ? "disabled" : sEnable, target);
CReplyToCommand(client, "%s Laser compensation: %s", PREFIX, g_bPlayerLaserCompensated[target] ? "enabled" : "disabled");
if(g_bLagCompSuspended)
CReplyToCommand(client, "%s Server status: {red}disabled{default} for everyone until the next round (more than %d entities needed to be compensated).", PREFIX, g_iMaxEntities);
else
CReplyToCommand(client, "%s Server status: {green}active{default}.", PREFIX);
// g_iDisableLagComp also ramps from the ping threshold, so report it when they haven't opted out.
if(!g_bDisableLagComp[target] && g_iDisableLagComp[target] > 0)
CReplyToCommand(client, "%s Currently reduced: %d of %d ticks skipped%s.", PREFIX,
g_iDisableLagComp[target], MAX_RECORDS,
g_bCheckPing ? " (ping at or below the threshold ramps this up)" : "");
return Plugin_Handled; return Plugin_Handled;
} }
@@ -1463,15 +1697,20 @@ public Action CheckLagComp(int client, int args)
public void ShowSettingsMenu(int client) public void ShowSettingsMenu(int client)
{ {
Menu menu = new Menu(MenuHandler_MainMenu); Menu menu = new Menu(MenuHandler_MainMenu);
menu.SetTitle("LagCompensation Settings", client); if(g_bLagCompSuspended)
menu.SetTitle("LagCompensation Settings\nCurrently disabled until the next round (too many entities)");
else
menu.SetTitle("LagCompensation Settings");
menu.ExitBackButton = true; menu.ExitBackButton = true;
char buffer[128], msg_buffer[128]; char buffer[128], buffer_lasers[128], msg_buffer[128];
Format(buffer, sizeof(buffer), "LagCompensation: %s", g_bEnableLagComp[client] ? "Enabled" : "Disabled"); Format(buffer, sizeof(buffer), "LagCompensation: %s", g_bDisableLagComp[client] ? "Disabled" : "Enabled");
Format(buffer_lasers, sizeof(buffer_lasers), "LagCompensation for Lasers: %s", g_bPlayerLaserCompensated[client] ? "Enabled" : "Disabled");
Format(msg_buffer, sizeof(msg_buffer), "LagCompensation Messages: %s", g_bLagCompMessages[client] ? "Enabled" : "Disabled"); Format(msg_buffer, sizeof(msg_buffer), "LagCompensation Messages: %s", g_bLagCompMessages[client] ? "Enabled" : "Disabled");
menu.AddItem("0", buffer); menu.AddItem("0", buffer);
menu.AddItem("1", msg_buffer); menu.AddItem("1", buffer_lasers);
menu.AddItem("2", msg_buffer, CheckCommandAccess(client, "sm_lagged", ADMFLAG_ROOT) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
menu.Display(client, MENU_TIME_FOREVER); menu.Display(client, MENU_TIME_FOREVER);
} }
@@ -1482,7 +1721,7 @@ public void MenuHandler_CookieMenu(int client, CookieMenuAction action, any info
{ {
case(CookieMenuAction_DisplayOption): case(CookieMenuAction_DisplayOption):
{ {
Format(buffer, maxlen, "LagCompensation", client); strcopy(buffer, maxlen, "LagCompensation");
} }
case(CookieMenuAction_SelectOption): case(CookieMenuAction_SelectOption):
{ {
@@ -1500,7 +1739,8 @@ public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int se
switch(selection) switch(selection)
{ {
case(0): ToggleLagCompSettings(client); case(0): ToggleLagCompSettings(client);
case(1): ToggleLagCompMessages(client); case(1): ToggleLagCompLasers(client);
case(2): ToggleLagCompMessages(client);
} }
ShowSettingsMenu(client); ShowSettingsMenu(client);
+288
View File
@@ -1030,6 +1030,13 @@ public void LeaderMenu(int client)
AddMenuItem(menu, "ping", "Ping Menu"); AddMenuItem(menu, "ping", "Ping Menu");
AddMenuItem(menu, "beacon", "Toggle Beacon"); AddMenuItem(menu, "beacon", "Toggle Beacon");
// RCON-only options
if (CheckCommandAccess(client, "", ADMFLAG_RCON, false))
{
AddMenuItem(menu, "addleader", "[RCON] Add Leader to File");
AddMenuItem(menu, "removeleader", "[RCON] Remove Leader from File");
}
SetMenuExitButton(menu, true); SetMenuExitButton(menu, true);
DisplayMenu(menu, client, MENU_TIME_FOREVER); DisplayMenu(menu, client, MENU_TIME_FOREVER);
} }
@@ -1046,6 +1053,23 @@ public int LeaderMenu_Handler(Handle menu, MenuAction action, int client, int po
char info[32]; char info[32];
GetMenuItem(menu, position, info, sizeof(info)); GetMenuItem(menu, position, info, sizeof(info));
if(StrEqual(info, "addleader"))
{
if(CheckCommandAccess(client, "", ADMFLAG_RCON, false))
{
AddLeaderMenu(client);
}
}
if(StrEqual(info, "removeleader"))
{
if(CheckCommandAccess(client, "", ADMFLAG_RCON, false))
{
RemoveLeaderMenu(client);
}
}
if(StrEqual(info, "resign")) if(StrEqual(info, "resign"))
{ {
RemoveLeader(client); RemoveLeader(client);
@@ -1070,7 +1094,260 @@ public int LeaderMenu_Handler(Handle menu, MenuAction action, int client, int po
delete menu; delete menu;
} }
} }
return 0;
}
//----------------------------------------------------------------------------------------------------
// Purpose: RCON - shows connected players to add to the leaders file
//----------------------------------------------------------------------------------------------------
public void AddLeaderMenu(int client)
{
Handle menu = CreateMenu(AddLeaderMenu_Handler);
SetMenuTitle(menu, "Select a player to ADD as Leader");
char info[12], display[MAX_NAME_LENGTH];
bool any = false;
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i) && !IsFakeClient(i))
{
any = true;
IntToString(GetClientUserId(i), info, sizeof(info));
GetClientName(i, display, sizeof(display));
AddMenuItem(menu, info, display);
}
}
if(!any)
{
AddMenuItem(menu, "", "No players connected", ITEMDRAW_DISABLED);
}
SetMenuExitBackButton(menu, true);
SetMenuExitButton(menu, true);
DisplayMenu(menu, client, MENU_TIME_FOREVER);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int AddLeaderMenu_Handler(Handle menu, MenuAction action, int client, int position)
{
if(action == MenuAction_Select)
{
if(!CheckCommandAccess(client, "", ADMFLAG_RCON, false))
{
PrintToChat(client, "[SM] You no longer have access to this.");
return 0;
}
char info[12];
GetMenuItem(menu, position, info, sizeof(info));
int target = GetClientOfUserId(StringToInt(info));
if(target == 0)
{
PrintToChat(client, "[SM] That player is no longer available.");
}
else
{
AddLeaderToFile(client, target);
}
LeaderMenu(client);
}
else if(action == MenuAction_End)
{
delete menu;
}
else if(action == MenuAction_Cancel && position == MenuCancel_ExitBack)
{
LeaderMenu(client);
}
return 0;
}
//AI slop
//----------------------------------------------------------------------------------------------------
// Purpose: RCON - shows connected players to remove from the leaders file
//----------------------------------------------------------------------------------------------------
public void RemoveLeaderMenu(int client)
{
Handle menu = CreateMenu(RemoveLeaderMenu_Handler);
SetMenuTitle(menu, "Select a player to REMOVE as Leader");
char info[12], display[MAX_NAME_LENGTH];
bool any = false;
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i) && !IsFakeClient(i))
{
any = true;
IntToString(GetClientUserId(i), info, sizeof(info));
GetClientName(i, display, sizeof(display));
AddMenuItem(menu, info, display);
}
}
if(!any)
{
AddMenuItem(menu, "", "No players connected", ITEMDRAW_DISABLED);
}
SetMenuExitBackButton(menu, true);
SetMenuExitButton(menu, true);
DisplayMenu(menu, client, MENU_TIME_FOREVER);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int RemoveLeaderMenu_Handler(Handle menu, MenuAction action, int client, int position)
{
if(action == MenuAction_Select)
{
if(!CheckCommandAccess(client, "", ADMFLAG_RCON, false))
{
PrintToChat(client, "[SM] You no longer have access to this.");
return 0;
}
char info[12];
GetMenuItem(menu, position, info, sizeof(info));
int target = GetClientOfUserId(StringToInt(info));
if(target == 0)
{
PrintToChat(client, "[SM] That player is no longer available.");
}
else
{
RemoveLeaderFromFile(client, target);
}
LeaderMenu(client);
}
else if(action == MenuAction_End)
{
delete menu;
}
else if(action == MenuAction_Cancel && position == MenuCancel_ExitBack)
{
LeaderMenu(client);
}
return 0;
}
//----------------------------------------------------------------------------------------------------
// Purpose: Appends a targets SteamID2 to the leaders file with their name as a comment
//----------------------------------------------------------------------------------------------------
public void AddLeaderToFile(int admin, int target)
{
char sAuth[64], sName[MAX_NAME_LENGTH];
if(!GetClientAuthId(target, AuthId_Steam2, sAuth, sizeof(sAuth)))
{
PrintToChat(admin, "[SM] Could not resolve a SteamID for that player yet, try again in a moment.");
return;
}
GetClientName(target, sName, sizeof(sName));
if(IsPossibleLeader(target))
{
PrintToChat(admin, "[SM] %N is already in the leaders file.", target);
return;
}
File fFile = OpenFile(g_sDataFile, "a");
if(!fFile)
{
PrintToChat(admin, "[SM] Failed to open the leaders file for writing.");
return;
}
fFile.WriteLine("\"%s\" //%s", sAuth, sName);
fFile.Close();
UpdateLeaders();
PrintToChatAll("[SM] %N added %N to the leaders list.", admin, target);
LogAction(admin, target, "\"%L\" added \"%L\" to the leaders file", admin, target);
}
//----------------------------------------------------------------------------------------------------
// Purpose: Removes a targets SteamID2 line from the leaders file, if present
//----------------------------------------------------------------------------------------------------
public void RemoveLeaderFromFile(int admin, int target)
{
char sAuth[64];
if(!GetClientAuthId(target, AuthId_Steam2, sAuth, sizeof(sAuth)))
{
PrintToChat(admin, "[SM] Could not resolve a SteamID for that player yet, try again in a moment.");
return;
}
File fRead = OpenFile(g_sDataFile, "rt");
if(!fRead)
{
PrintToChat(admin, "[SM] Failed to open the leaders file for reading.");
return;
}
ArrayList lines = new ArrayList(ByteCountToCells(255));
char line[255];
bool found = false;
while(!fRead.EndOfFile() && fRead.ReadLine(line, sizeof(line)))
{
// Only skip the first line that actually contains this SteamID (inside the quotes)
if(!found && StrContains(line, sAuth) != -1)
{
found = true;
continue;
}
lines.PushString(line);
}
fRead.Close();
if(!found)
{
delete lines;
PrintToChat(admin, "[SM] %N was not found in the leaders file.", target);
return;
}
File fWrite = OpenFile(g_sDataFile, "wt");
if(!fWrite)
{
delete lines;
PrintToChat(admin, "[SM] Failed to open the leaders file for writing.");
return;
}
int count = lines.Length;
for(int i = 0; i < count; i++)
{
lines.GetString(i, line, sizeof(line));
fWrite.WriteLine(line);
}
fWrite.Close();
delete lines;
UpdateLeaders();
PrintToChatAll("[SM] %N removed %N from the leaders list.", admin, target);
LogAction(admin, target, "\"%L\" removed \"%L\" from the leaders file", admin, target);
// If the person removed is the current leader, kick them out of the role too
if(target == leaderClient)
{
RemoveLeader(target);
PrintToChatAll("[SM] %N is no longer a valid leader and has been removed from the leader role.", target);
}
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -1191,6 +1468,7 @@ public int SpriteMenu_Handler(Handle menu, MenuAction action, int client, int po
LeaderMenu(client); LeaderMenu(client);
} }
} }
return 0;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -1320,6 +1598,7 @@ public int PingMenu_Handler(Handle menu, MenuAction action, int client, int posi
LeaderMenu(client); LeaderMenu(client);
} }
} }
return 0;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -1353,6 +1632,7 @@ public Action Event_PlayerDeath(Handle event, char[] name, bool dontBroadcast)
PrintToChatAll("[SM] The leader has died!"); PrintToChatAll("[SM] The leader has died!");
RemoveLeader(client); RemoveLeader(client);
} }
return Plugin_Handled;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -1407,6 +1687,13 @@ public Action Event_RoundEnd(Handle event, char[] name, bool dontBroadcast)
KillAllBeacons(); KillAllBeacons();
//KillAllPings(); //KillAllPings();
//maybe a good idea to reset them on round end to prevent random props from being killed that could have the same index.
pingEntity0 = -1;
pingEntity1 = -1;
pingEntity2 = -1;
pingEntity3 = -1;
return Plugin_Handled;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -1457,6 +1744,7 @@ public int Native_CurrentLeader(Handle plugin, int numParams)
public int Native_SetLeader(Handle plugin, int numParams) public int Native_SetLeader(Handle plugin, int numParams)
{ {
SetLeader(GetNativeCell(1)); SetLeader(GetNativeCell(1));
return 0;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
+101 -15
View File
@@ -7,16 +7,7 @@
#pragma semicolon 1 #pragma semicolon 1
#pragma newdecls required #pragma newdecls required
public Plugin myinfo = #define NUMBEROFSTAGES 7
{
name = "MakoVoteSystem",
author = "Neon",
description = "MakoVoteSystem",
version = "1.1",
url = "https://steamcommunity.com/id/n3ontm"
}
#define NUMBEROFSTAGES 6
bool g_bVoteFinished = true; bool g_bVoteFinished = true;
bool g_bIsRevote = false; bool g_bIsRevote = false;
@@ -24,13 +15,15 @@ bool g_bStartVoteNextRound = false;
bool g_bPlayedZM = false; bool g_bPlayedZM = false;
bool g_bOnCooldown[NUMBEROFSTAGES]; bool g_bOnCooldown[NUMBEROFSTAGES];
static char g_sStageName[NUMBEROFSTAGES][32] = {"Extreme 2", "Extreme 2 (Heal + Ultima)", "Extreme 3 (ZED)", "Extreme 3 (Hellz)", "Race Mode", "Zombie Mode"}; static char g_sStageName[NUMBEROFSTAGES][32] = {"Extreme 2 (Heal + Ultima)", "Extreme 3 (ZED)", "Extreme 3 (Hellz)", "Race Mode", "Zombie Mode", "Extreme 4 RMZS", "Ext2 Special Lasers"};
int g_Winnerstage; int g_Winnerstage;
Handle g_VoteMenu = INVALID_HANDLE; Handle g_VoteMenu = INVALID_HANDLE;
Handle g_StageList = INVALID_HANDLE; Handle g_StageList = INVALID_HANDLE;
Handle g_CountdownTimer = INVALID_HANDLE; Handle g_CountdownTimer = INVALID_HANDLE;
bool g_bCorrectMap = false;
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -57,6 +50,7 @@ public void OnPluginStart()
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnMapStart() public void OnMapStart()
{ {
g_bCorrectMap = false;
VerifyMap(); VerifyMap();
g_bStartVoteNextRound = false; g_bStartVoteNextRound = false;
@@ -76,17 +70,22 @@ public Action VerifyMap()
if (!StrEqual(currentMap, "ze_ffvii_mako_reactor_v5_3", false)) if (!StrEqual(currentMap, "ze_ffvii_mako_reactor_v5_3", false))
{ {
//plugin unloading can make map switches very slow
/*
char sFilename[256]; char sFilename[256];
GetPluginFilename(INVALID_HANDLE, sFilename, sizeof(sFilename)); GetPluginFilename(INVALID_HANDLE, sFilename, sizeof(sFilename));
ServerCommand("sm plugins unload %s", sFilename); ServerCommand("sm plugins unload %s", sFilename);
*/
} }
else else
{ {
g_bCorrectMap = true;
PrecacheSound("#unloze/Pendulum - Witchcraft.mp3", true); PrecacheSound("#unloze/Pendulum - Witchcraft.mp3", true);
AddFileToDownloadsTable("sound/unloze/Pendulum - Witchcraft.mp3"); AddFileToDownloadsTable("sound/unloze/Pendulum - Witchcraft.mp3");
} }
return Plugin_Handled;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -94,6 +93,10 @@ public Action VerifyMap()
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnEntitySpawned(int iEntity, const char[] sClassname) public void OnEntitySpawned(int iEntity, const char[] sClassname)
{ {
if (!g_bCorrectMap)
{
return;
}
if (!IsValidEntity(iEntity) || g_bVoteFinished) if (!IsValidEntity(iEntity) || g_bVoteFinished)
return; return;
@@ -109,6 +112,10 @@ public void OnEntitySpawned(int iEntity, const char[] sClassname)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnRoundStart(Event hEvent, const char[] sEvent, bool bDontBroadcast) public void OnRoundStart(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{ {
if (!g_bCorrectMap)
{
return;
}
if (g_bStartVoteNextRound) if (g_bStartVoteNextRound)
{ {
g_bVoteFinished = false; g_bVoteFinished = false;
@@ -160,9 +167,12 @@ public void OnRoundStart(Event hEvent, const char[] sEvent, bool bDontBroadcast)
if (iButton5 != INVALID_ENT_REFERENCE) if (iButton5 != INVALID_ENT_REFERENCE)
AcceptEntityInput(iButton5, "Lock"); AcceptEntityInput(iButton5, "Lock");
int iCounter = FindEntityByTargetname(INVALID_ENT_REFERENCE, "LevelCounter", "math_counter"); //its now a permanent entity so really dont kill it.
/*
int iCounter = FindEntityByTargetname(INVALID_ENT_REFERENCE, "LevelCounter", "info_target");
if (iCounter != INVALID_ENT_REFERENCE) if (iCounter != INVALID_ENT_REFERENCE)
AcceptEntityInput(iCounter, "Kill"); AcceptEntityInput(iCounter, "Kill");
*/
int iFilter = FindEntityByTargetname(INVALID_ENT_REFERENCE, "humanos", "filter_activator_team"); int iFilter = FindEntityByTargetname(INVALID_ENT_REFERENCE, "humanos", "filter_activator_team");
if (iFilter != INVALID_ENT_REFERENCE) if (iFilter != INVALID_ENT_REFERENCE)
@@ -245,6 +255,10 @@ public void OnRoundStart(Event hEvent, const char[] sEvent, bool bDontBroadcast)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void GenerateArray() public void GenerateArray()
{ {
if (!g_bCorrectMap)
{
return;
}
int iBlockSize = ByteCountToCells(PLATFORM_MAX_PATH); int iBlockSize = ByteCountToCells(PLATFORM_MAX_PATH);
g_StageList = CreateArray(iBlockSize); g_StageList = CreateArray(iBlockSize);
@@ -270,6 +284,11 @@ public void GenerateArray()
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action Command_StartVote(int args) public Action Command_StartVote(int args)
{ {
if (!g_bCorrectMap)
{
return Plugin_Handled;
}
int iCurrentStage = GetCurrentStage(); int iCurrentStage = GetCurrentStage();
if (iCurrentStage > 0) if (iCurrentStage > 0)
@@ -302,6 +321,10 @@ public Action Command_StartVote(int args)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action StartVote(Handle timer) public Action StartVote(Handle timer)
{ {
if (!g_bCorrectMap)
{
return Plugin_Handled;
}
static int iCountDown = 5; static int iCountDown = 5;
PrintCenterTextAll("[MakoVote] Starting Vote in %ds", iCountDown); PrintCenterTextAll("[MakoVote] Starting Vote in %ds", iCountDown);
@@ -312,6 +335,7 @@ public Action StartVote(Handle timer)
g_CountdownTimer = INVALID_HANDLE; g_CountdownTimer = INVALID_HANDLE;
InitiateVote(); InitiateVote();
} }
return Plugin_Handled;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -319,6 +343,10 @@ public Action StartVote(Handle timer)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void InitiateVote() public void InitiateVote()
{ {
if (!g_bCorrectMap)
{
return;
}
if(IsVoteInProgress()) if(IsVoteInProgress())
{ {
CPrintToChatAll("{green}[Mako Vote] {white}Another vote is currently in progress, retrying again in 5s."); CPrintToChatAll("{green}[Mako Vote] {white}Another vote is currently in progress, retrying again in 5s.");
@@ -347,7 +375,7 @@ public void InitiateVote()
} }
} }
SetMenuOptionFlags(g_VoteMenu, MENUFLAG_BUTTON_NOVOTE); //SetMenuOptionFlags(g_VoteMenu, MENUFLAG_BUTTON_NOVOTE);
SetMenuTitle(g_VoteMenu, "What stage to play next?"); SetMenuTitle(g_VoteMenu, "What stage to play next?");
SetVoteResultCallback(g_VoteMenu, Handler_SettingsVoteFinished); SetVoteResultCallback(g_VoteMenu, Handler_SettingsVoteFinished);
VoteMenuToAll(g_VoteMenu, 20); VoteMenuToAll(g_VoteMenu, 20);
@@ -358,6 +386,10 @@ public void InitiateVote()
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public int Handler_MakoVoteMenu(Handle menu, MenuAction action, int param1, int param2) public int Handler_MakoVoteMenu(Handle menu, MenuAction action, int param1, int param2)
{ {
if (!g_bCorrectMap)
{
return 0;
}
switch(action) switch(action)
{ {
case MenuAction_End: case MenuAction_End:
@@ -385,10 +417,15 @@ public int MenuHandler_NotifyPanel(Menu hMenu, MenuAction iAction, int iParam1,
case MenuAction_Select, MenuAction_Cancel: case MenuAction_Select, MenuAction_Cancel:
delete hMenu; delete hMenu;
} }
return 0;
} }
public void Handler_SettingsVoteFinished(Handle menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info) public void Handler_SettingsVoteFinished(Handle menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info)
{ {
if (!g_bCorrectMap)
{
return;
}
int highest_votes = item_info[0][VOTEINFO_ITEM_VOTES]; int highest_votes = item_info[0][VOTEINFO_ITEM_VOTES];
int required_percent = 60; int required_percent = 60;
int required_votes = RoundToCeil(float(num_votes) * float(required_percent) / 100); int required_votes = RoundToCeil(float(num_votes) * float(required_percent) / 100);
@@ -416,6 +453,10 @@ public void Handler_SettingsVoteFinished(Handle menu, int num_votes, int num_cli
public void Handler_VoteFinishedGeneric(Handle menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info) public void Handler_VoteFinishedGeneric(Handle menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info)
{ {
if (!g_bCorrectMap)
{
return;
}
g_bVoteFinished = true; g_bVoteFinished = true;
char sWinner[128]; char sWinner[128];
GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], sWinner, sizeof(sWinner)); GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], sWinner, sizeof(sWinner));
@@ -429,7 +470,42 @@ public void Handler_VoteFinishedGeneric(Handle menu, int num_votes, int num_clie
g_Winnerstage = i; g_Winnerstage = i;
} }
ServerCommand("sm_stage %d", (g_Winnerstage + 4)); //ServerCommand("sm_stage %d", (g_Winnerstage + 4));
char winner_stage[64];
if (StrEqual(sWinner, "Extreme 3 (Hellz)"))
{
Format(winner_stage, sizeof(winner_stage), "hellz");
}
else if (StrEqual(sWinner, "Extreme 2"))
{
Format(winner_stage, sizeof(winner_stage), "extreme2");
}
else if (StrEqual(sWinner, "Extreme 2 (Heal + Ultima)"))
{
Format(winner_stage, sizeof(winner_stage), "ultiheal");
}
else if (StrEqual(sWinner, "Extreme 3 (ZED)"))
{
Format(winner_stage, sizeof(winner_stage), "zombieden");
}
else if (StrEqual(sWinner, "Race Mode"))
{
Format(winner_stage, sizeof(winner_stage), "race");
}
else if (StrEqual(sWinner, "Zombie Mode"))
{
Format(winner_stage, sizeof(winner_stage), "zm");
}
else if (StrEqual(sWinner, "Extreme 4 RMZS"))
{
Format(winner_stage, sizeof(winner_stage), "rmzs");
}
else if (StrEqual(sWinner, "Ext2 Special Lasers"))
{
Format(winner_stage, sizeof(winner_stage), "laser");
}
ServerCommand("sm_stage %s", winner_stage);
float fDelay = 3.0; float fDelay = 3.0;
CS_TerminateRound(fDelay, CSRoundEnd_GameStart, false); CS_TerminateRound(fDelay, CSRoundEnd_GameStart, false);
@@ -437,7 +513,13 @@ public void Handler_VoteFinishedGeneric(Handle menu, int num_votes, int num_clie
public int GetCurrentStage() public int GetCurrentStage()
{ {
int iLevelCounterEnt = FindEntityByTargetname(INVALID_ENT_REFERENCE, "LevelCounter", "math_counter"); if (!g_bCorrectMap)
{
return 0;
}
//int iLevelCounterEnt = FindEntityByTargetname(INVALID_ENT_REFERENCE, "LevelCounter", "math_counter");
//due to the stripper its now an info_target and not math_counter.
int iLevelCounterEnt = FindEntityByTargetname(INVALID_ENT_REFERENCE, "LevelCounter", "info_target");
int offset = FindDataMapInfo(iLevelCounterEnt, "m_OutValue"); int offset = FindDataMapInfo(iLevelCounterEnt, "m_OutValue");
int iCounterVal = RoundFloat(GetEntDataFloat(iLevelCounterEnt, offset)); int iCounterVal = RoundFloat(GetEntDataFloat(iLevelCounterEnt, offset));
@@ -463,6 +545,10 @@ public int GetCurrentStage()
public int FindEntityByTargetname(int entity, const char[] sTargetname, const char[] sClassname) public int FindEntityByTargetname(int entity, const char[] sTargetname, const char[] sClassname)
{ {
if (!g_bCorrectMap)
{
return 0;
}
if(sTargetname[0] == '#') // HammerID if(sTargetname[0] == '#') // HammerID
{ {
int HammerID = StringToInt(sTargetname[1]); int HammerID = StringToInt(sTargetname[1]);
+25 -16
View File
@@ -37,7 +37,7 @@ public void OnPluginStart()
ConVar cvar; ConVar cvar;
HookConVarChange((cvar = CreateConVar("sm_admin_afk_time", "30", "Time in seconds until an admin is considered as AFK.")), Cvar_AdminAFKTime); HookConVarChange((cvar = CreateConVar("sm_admin_afk_time", "30", "Time in seconds until an admin is considered as AFK.")), Cvar_AdminAFKTime);
g_iAdminAFKTime = cvar.IntValue; g_iAdminAFKTime = cvar.IntValue;
HookConVarChange((cvar = CreateConVar("sm_selfextend_amount", "2", "Amount of sm_selfextend's allowed.")), Cvar_SelfExtendsAmount); HookConVarChange((cvar = CreateConVar("sm_selfextend_amount", "2", "Amount of sm_selfextends allowed.")), Cvar_SelfExtendsAmount);
g_iSelfMaxExtendsAmount = cvar.IntValue; g_iSelfMaxExtendsAmount = cvar.IntValue;
HookConVarChange((cvar = CreateConVar("sm_selfextend_time", "10", "How long to extend in minutes when sm_selfextend passes through.")), Cvar_SelfExtendsTime); HookConVarChange((cvar = CreateConVar("sm_selfextend_time", "10", "How long to extend in minutes when sm_selfextend passes through.")), Cvar_SelfExtendsTime);
g_iSelfExtendsTime = cvar.IntValue; g_iSelfExtendsTime = cvar.IntValue;
@@ -175,24 +175,14 @@ public Action Command_DisplayActiveAdmins(int client, int args)
public Action Command_SelfExtend(int client, int args) public Action Command_SelfExtend(int client, int args)
{ {
int iAdminActivity = 0;
for(int i = 1; i <= MaxClients; i++)
if(g_bActiveAdmin[i])
iAdminActivity++;
if(GetExtendsLeft() > 0) if(GetExtendsLeft() > 0)
{ {
ReplyToCommand(client, "[SM] Not available because not all regular extends have been depleted."); ReplyToCommand(client, "[SM] Not available because not all regular extends have been depleted.");
return Plugin_Handled; return Plugin_Handled;
} }
if(iAdminActivity > 0)
{
ReplyToCommand(client, "[SM] Not available because there is atleast one active Admin who can extend. Please ask the Admins.");
return Plugin_Handled;
}
if(!g_bSelfExtendsAllowed) if(!g_bSelfExtendsAllowed)
{ {
ReplyToCommand(client, "[SM] Not available because it's still too early in the map."); ReplyToCommand(client, "[SM] Not available because its still too early in the map.");
return Plugin_Handled; return Plugin_Handled;
} }
if(g_iSelfMaxExtendsAmount <= g_iSelfExtends) if(g_iSelfMaxExtendsAmount <= g_iSelfExtends)
@@ -201,14 +191,34 @@ public Action Command_SelfExtend(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
int iPlayers;
int iSelfExtendsPlayers;
int iPlayersNeeded;
for(int i = 1; i <= MaxClients; i++)
{
if(IsValidClient(i))
iPlayers++;
if(g_bSelfExtends[i])
iSelfExtendsPlayers++;
}
iPlayersNeeded = RoundToFloor(float(iPlayers) * g_fSelfExtendsRatio);
if (iPlayersNeeded == 0)
{
iPlayersNeeded = 1;
}
if(!g_bSelfExtends[client]) if(!g_bSelfExtends[client])
{ {
g_bSelfExtends[client] = true; g_bSelfExtends[client] = true;
PrintToChatAll("[SM] %N wants to self-extend the map.", client); PrintToChatAll("[SM] %N wants to self-extend the map. %i/%i", client, iSelfExtendsPlayers + 1, iPlayersNeeded);
CheckRatio(); CheckRatio();
} }
else else
ReplyToCommand(client, "[SM] You have already voted to self-extend the map."); {
ReplyToCommand(client, "[SM] You have already voted to self-extend the map. %i/%i", iSelfExtendsPlayers, iPlayersNeeded);
}
return Plugin_Handled; return Plugin_Handled;
} }
@@ -233,7 +243,7 @@ public void CheckRatio()
iPlayersNeeded = RoundToFloor(float(iPlayers) * g_fSelfExtendsRatio); iPlayersNeeded = RoundToFloor(float(iPlayers) * g_fSelfExtendsRatio);
if(iSelfExtendsPlayers >= iPlayersNeeded && iPlayersNeeded != 0) if(iSelfExtendsPlayers >= iPlayersNeeded && iSelfExtendsPlayers > 0)
{ {
PrintToChatAll("[SM] Enough Players voted to self-extend the map. Adding %d minutes to the timelimit.", g_iSelfExtendsTime); PrintToChatAll("[SM] Enough Players voted to self-extend the map. Adding %d minutes to the timelimit.", g_iSelfExtendsTime);
LogMessage("Enough Players voted to self-extend the map. Adding %d minutes to the timelimit.", g_iSelfExtendsTime); LogMessage("Enough Players voted to self-extend the map. Adding %d minutes to the timelimit.", g_iSelfExtendsTime);
@@ -243,7 +253,6 @@ public void CheckRatio()
for(int j; j <= MaxClients; j++) for(int j; j <= MaxClients; j++)
g_bSelfExtends[j] = false; g_bSelfExtends[j] = false;
} }
return; return;
} }
@@ -11,7 +11,6 @@
ConVar g_hCvar_BlockSpoof; ConVar g_hCvar_BlockSpoof;
ConVar g_hCvar_BlockAdmin; ConVar g_hCvar_BlockAdmin;
ConVar g_hCvar_BlockVoice; ConVar g_hCvar_BlockVoice;
ConVar g_hCvar_AuthenticationTime;
/* DATABASE */ /* DATABASE */
Database g_hDatabase; Database g_hDatabase;
@@ -53,7 +52,6 @@ public void OnPluginStart()
g_hCvar_BlockSpoof = CreateConVar("sm_manager_block_spoof", "1", "Kick unauthenticated people that join with known steamids.", FCVAR_NONE, true, 0.0, true, 1.0); g_hCvar_BlockSpoof = CreateConVar("sm_manager_block_spoof", "1", "Kick unauthenticated people that join with known steamids.", FCVAR_NONE, true, 0.0, true, 1.0);
g_hCvar_BlockAdmin = CreateConVar("sm_manager_block_admin", "1", "Should unauthenticated people be blocked from admin?", FCVAR_NONE, true, 0.0, true, 1.0); g_hCvar_BlockAdmin = CreateConVar("sm_manager_block_admin", "1", "Should unauthenticated people be blocked from admin?", FCVAR_NONE, true, 0.0, true, 1.0);
g_hCvar_BlockVoice = CreateConVar("sm_manager_block_voice", "1", "Should unauthenticated people be blocked from voice?", FCVAR_NONE, true, 0.0, true, 1.0); g_hCvar_BlockVoice = CreateConVar("sm_manager_block_voice", "1", "Should unauthenticated people be blocked from voice?", FCVAR_NONE, true, 0.0, true, 1.0);
g_hCvar_AuthenticationTime = CreateConVar("sm_manager_authentication_time", "15", "Time in seconds after which a client needs to be assigned to a SteamID", FCVAR_NONE, true, 1.0);
AddMultiTargetFilter("@steam", Filter_Steam, "Steam Players", false); AddMultiTargetFilter("@steam", Filter_Steam, "Steam Players", false);
AddMultiTargetFilter("@nosteam", Filter_NoSteam, "No-Steam Players", false); AddMultiTargetFilter("@nosteam", Filter_NoSteam, "No-Steam Players", false);
@@ -229,6 +227,7 @@ public bool Filter_NoSteam(const char[] sPattern, Handle hClients)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255]) public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255])
{ {
//LogMessage("OnClientPreConnectEx sName: %s. sRejectReason: %s. sSteam32ID: %s", sName, sRejectReason, sSteam32ID);
char sAuthID[32]; char sAuthID[32];
for(int client = 1; client <= MaxClients; client++) for(int client = 1; client <= MaxClients; client++)
{ {
@@ -257,14 +256,6 @@ public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], co
return k_OnClientPreConnectEx_Accept; return k_OnClientPreConnectEx_Accept;
} }
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientPutInServer(int client)
{
//CreateTimer(g_hCvar_AuthenticationTime.FloatValue, CheckAuth, GetClientSerial(client), TIMER_FLAG_NO_MAPCHANGE);
}
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -272,8 +263,22 @@ public void OnClientAuthorized(int client, const char[] sAuthID)
{ {
char sAddress[16]; char sAddress[16];
GetClientIP(client, sAddress, sizeof(sAddress)); GetClientIP(client, sAddress, sizeof(sAddress));
static char sCountry[32]; //if its the webclient its not a malicious steam ID steal, just pure coincidence if it were to hit a real steam players steamID.
char allowed_ips[128];
ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips");
if (sv_set_steam_id_ips != null)
{
sv_set_steam_id_ips.GetString(allowed_ips, sizeof(allowed_ips));
if (StrEqual(sAddress, allowed_ips))
{
return;
}
}
//just kicking all nosteamers from algeria. not very cool but so be it. //just kicking all nosteamers from algeria. not very cool but so be it.
/*
static char sCountry[32];
if (GeoipCountry(sAddress, sCountry, sizeof(sCountry)) && StrEqual(sCountry, "Algeria", false)) if (GeoipCountry(sAddress, sCountry, sizeof(sCountry)) && StrEqual(sCountry, "Algeria", false))
{ {
if (!SteamClientAuthenticatedEx(sAuthID)) if (!SteamClientAuthenticatedEx(sAuthID))
@@ -283,6 +288,7 @@ public void OnClientAuthorized(int client, const char[] sAuthID)
return; return;
} }
} }
*/
if(!g_hCvar_BlockSpoof.BoolValue || !g_hDatabase) if(!g_hCvar_BlockSpoof.BoolValue || !g_hDatabase)
return; return;
@@ -331,61 +337,6 @@ public void OnClientPostAdminCheck(int client)
char sAuthID[32]; char sAuthID[32];
GetClientAuthId(client, AuthId_Steam2, sAuthID, sizeof(sAuthID), false); GetClientAuthId(client, AuthId_Steam2, sAuthID, sizeof(sAuthID), false);
/*
if(!SteamClientAuthenticatedEx(sAuthID))
{
LogMessage("%L was not authenticated with steam, muting client.", client);
BaseComm_SetClientMute(client, true);
return;
}
*/
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action CheckAuth(Handle timer, int data)
{
int client;
if ((client = GetClientFromSerial(data)) == 0)
return Plugin_Stop;
char sAuthID[32];
if(!GetClientAuthId(client, AuthId_Steam2, sAuthID, sizeof(sAuthID), true))
{
LogMessage("%L could not be assigned to a SteamID, kicking client.", client);
//KickClient(client, "Invalid STEAMID");
if (IsValidClient(client))
{
Panel hNotifyPanel = new Panel(GetMenuStyleHandle(MenuStyle_Radio));
hNotifyPanel.DrawItem("WARNING: Your SteamID is Invalid: Reconnecting you to the server in 8 Seconds.", ITEMDRAW_RAWLINE);
hNotifyPanel.DrawItem("", ITEMDRAW_SPACER);
hNotifyPanel.DrawItem("IMPORTANT: You may disconnect, reconnect if necessary!", ITEMDRAW_RAWLINE);
hNotifyPanel.Send(client, MenuHandler_NotifyPanel, 8);
delete hNotifyPanel;
for (int i = 0; i < 8; i++)
{
PrintToChat(client, "WARNING: Your SteamID is Invalid: Reconnecting you to the server in 8 Seconds.");
}
CreateTimer(8.0, forceClientRetry, GetClientSerial(client));
}
}
return Plugin_Stop;
}
public Action forceClientRetry(Handle timer, int data)
{
int client;
if ((client = GetClientFromSerial(data)) == 0)
return Plugin_Stop;
if (IsValidClient(client))
{
ClientCommand(client, "retry");
}
return Plugin_Handled;
} }
stock bool IsValidClient(int client) stock bool IsValidClient(int client)
@@ -395,16 +346,6 @@ stock bool IsValidClient(int client)
return false; return false;
} }
int MenuHandler_NotifyPanel(Menu hMenu, MenuAction iAction, int iParam1, int iParam2)
{
switch (iAction)
{
case MenuAction_Select, MenuAction_Cancel:
delete hMenu;
}
return 0;
}
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -471,18 +412,13 @@ public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[]
{ {
if(StrEqual(sAddress, sResultAddress, false)) if(StrEqual(sAddress, sResultAddress, false))
{ {
LogMessage("%L tried to join with a legitimate steamid while not authenticated with steam. Allowing connection, IPs match. (Known: %s)", client, sAddress); LogMessage("%N tried to join with a legitimate steamid while not authenticated with steam. Allowing connection, IPs match. (Known: %s)", client, sAddress);
return; return;
} }
else else
{ {
LogAction(client, -1, "\"%L\" tried to join with a legitimate steamid while not authenticated with steam. Refusing connection, IPs dont match. (Known: %s | Current: %s)", client, sResultAddress, sAddress); LogMessage("%N tried to join with a legitimate steamid while not authenticated with steam. Refusing connection, IPs dont match. (Known: %s | Current: %s)", client, sResultAddress, sAddress);
static char sCountry[32];
//we were directly got a problem with an algerian. this is just a specific issue with a specific solution.
if (GeoipCountry(sAddress, sCountry, sizeof(sCountry)) && StrEqual(sCountry, "Algeria", false))
{
KickClient(client, "Trying to join with a legitimate steamid while not authenticated with steam."); KickClient(client, "Trying to join with a legitimate steamid while not authenticated with steam.");
}
return; return;
} }
} }
@@ -18,13 +18,13 @@ native bool PM_IsPlayerSteam(int client);
* Retrieve clients usertype. * Retrieve clients usertype.
* *
* @param client The client index. * @param client The client index.
* @param type The buffer to write to. * @param type The integer to write to (1 = Steam, 0 = NoSteam).
* @param maxlength The maximum buffer length.
* *
* @return True on success, false otherwise. * @return True on success, false otherwise.
* @error Invalid client index, not connected or fake client. * @error Invalid client index, not connected or fake client.
*/ */
native bool PM_GetPlayerType(int client, char[] type, int maxlength); //native bool PM_GetPlayerType(int client, int type);
native bool PM_GetPlayerType(int client, char[] buffer, int maxlength);
/** /**
* Retrieve clients globally unique identifier (GUID). * Retrieve clients globally unique identifier (GUID).
@@ -40,7 +40,7 @@ native int PM_GetPlayerGUID(int client);
public SharedPlugin __pl_PlayerManager = public SharedPlugin __pl_PlayerManager =
{ {
name = "PlayerManager", name = "PlayerManager",
file = "PlayerManager_Connect.smx", file = "PlayerManager.smx",
#if defined REQUIRE_PLUGIN #if defined REQUIRE_PLUGIN
required = 1 required = 1
@@ -50,10 +50,10 @@ public SharedPlugin __pl_PlayerManager =
}; };
#if !defined REQUIRE_PLUGIN #if !defined REQUIRE_PLUGIN
public void __pl_PlayerManager_SetNTVOptional() public void __pl_PlayerManager_SetNTVOptional()
{ {
MarkNativeAsOptional("PM_IsPlayerSteam"); MarkNativeAsOptional("PM_IsPlayerSteam");
MarkNativeAsOptional("PM_GetPlayerType"); MarkNativeAsOptional("PM_GetPlayerType");
MarkNativeAsOptional("PM_GetPlayerGUID"); MarkNativeAsOptional("PM_GetPlayerGUID");
} }
#endif #endif
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
"Games"
{
"cstrike"
{
"Offsets"
{
"PlayerUse"
{
"linux" "414"
"windows" "413"
}
"ForceVPhysicsCollide"
{
"linux" "160"
"windows" "159"
}
}
}
}
+527 -1
View File
@@ -1,6 +1,10 @@
#pragma semicolon 1 #pragma semicolon 1
#include <sourcemod> #include <sourcemod>
#include <sdktools> #include <sdktools>
#include <sdkhooks>
#include <dhooks>
#include <cstrike>
#include <unloze_playtime>
public Plugin myinfo = public Plugin myinfo =
{ {
@@ -16,9 +20,67 @@ Menu g_RotationMenu = null;
Menu g_PhysicMenu = null; Menu g_PhysicMenu = null;
Menu g_DynamicMenu = null; Menu g_DynamicMenu = null;
Menu g_NpcMenu = null; Menu g_NpcMenu = null;
Menu g_ColorMenu = null;
KeyValues g_Models; KeyValues g_Models;
ConVar g_cvNotifyShot;
ConVar g_cvNotifyTimer;
ConVar g_cvUseMode;
ConVar g_cvPropShootTier;
ConVar g_cvButtonPressTier;
ArrayList g_aSpawnedProps; // Tracks entity indices of props spawned by this plugin
ArrayList g_aNotifyList;
int g_aPlayerPropColor[MAXPLAYERS + 1][3];
Handle g_PlayerUseSetup = INVALID_HANDLE;
Handle g_ForceVPhysicsCollideSetup = INVALID_HANDLE;
enum struct NotifyListData
{
int client;
Handle timer;
}
enum
{
PropColors_Default,
PropColors_Black,
PropColors_Red,
PropColors_Purple,
PropColors_Blue,
PropColors_Green,
PropColors_Yellow,
PropColors_Orange,
PropColors_Rainbow,
PropColors_Count
}
char ColorToString[PropColors_Count][] = {
"Default",
"Black",
"Red",
"Purple",
"Blue",
"Green",
"Yellow",
"Orange",
"Rainbow"
};
int ColorToRGB[PropColors_Count][3] = {
{255, 255, 255},
{0, 0, 0},
{255, 0, 0},
{128, 0, 128},
{0, 0, 255},
{0, 255, 0},
{255, 255, 0},
{255, 165, 0},
{256, 256, 256}
};
public void OnPluginStart() public void OnPluginStart()
{ {
@@ -41,7 +103,74 @@ public void OnPluginStart()
} }
g_Models.Rewind(); // Go back to the root node g_Models.Rewind(); // Go back to the root node
GameData gd = new GameData("PropSpawner");
if (gd == null)
{
LogError("PropSpawner: Missing gamedata file");
}
else
{
int PlayerUseOffset = gd.GetOffset("PlayerUse");
if (PlayerUseOffset == -1)
{
LogError("PropSpawner: Missing \"PlayerUse\" offset in gamedata file");
}
else
{
g_PlayerUseSetup = DHookCreate(PlayerUseOffset, HookType_Entity, ReturnType_Void, ThisPointer_CBaseEntity, PlayerUse_Hook);
}
int ForceVPhysicsCollideOffset = gd.GetOffset("ForceVPhysicsCollide");
if (ForceVPhysicsCollideOffset == -1)
{
LogError("PropSpawner: Missing \"ForceVPhysicsCollide\" offset in gamedata file");
}
else
{
g_ForceVPhysicsCollideSetup = DHookCreate(ForceVPhysicsCollideOffset, HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity, ForceVPhysicsCollide_Hook);
DHookAddParam(g_ForceVPhysicsCollideSetup, HookParamType_CBaseEntity);
}
delete gd;
}
RegAdminCmd("sm_propspawner", Command_MainMenu, ADMFLAG_CHANGEMAP); //g RegAdminCmd("sm_propspawner", Command_MainMenu, ADMFLAG_CHANGEMAP); //g
RegAdminCmd("sm_setpropcolor", Command_SetPropColor, ADMFLAG_CHANGEMAP, "Set PropSpawner color by RGB value, or \"rainbow\" for that.");
g_cvNotifyShot = CreateConVar("sm_propspawner_notify_shot", "0", "If set to 1, announces in chat when a plugin-spawned prop is shot.", FCVAR_NOTIFY, true, 0.0, true, 2.0);
g_cvNotifyTimer = CreateConVar("sm_propspawner_notify_shot_timer", "0.5", "How long a player stays in the \"shooting couch\" list.");
g_cvUseMode = CreateConVar("sm_propspawner_use_mode", "0", "What should happen when a prop is +use'd. " ...
"0 = Normal. 1 = Disallow CTs moving plugin spawned props. 2 = Add player to notify list.");
g_cvPropShootTier = CreateConVar("sm_propspawner_tier_requirement", "-1", "Tier needed for players to be able to move PropSpawner props.");
g_cvButtonPressTier = CreateConVar("sm_propspawner_button_tier_requirement", "-1", "Tier needed for players to be able to press buttons.");
AutoExecConfig(true, "PropSpawner");
g_aSpawnedProps = new ArrayList();
g_aNotifyList = new ArrayList();
}
public void OnEntityCreated(int entity, const char[] classname)
{
if (StrEqual(classname, "func_clip_vphysics") && g_ForceVPhysicsCollideSetup != INVALID_HANDLE)
{
DHookEntity(g_ForceVPhysicsCollideSetup, false, entity);
}
else if (StrEqual(classname, "func_button") && g_cvButtonPressTier.IntValue > -1)
{
SDKHook(entity, SDKHook_Use, OnButtonUse);
}
}
public void OnEntityDestroyed(int entity)
{
if (entity <= 0 || g_aSpawnedProps == null)
return;
int index = g_aSpawnedProps.FindValue(entity);
if (index != -1)
{
g_aSpawnedProps.Erase(index);
}
} }
public void OnMapStart() public void OnMapStart()
@@ -53,6 +182,7 @@ public void OnMapStart()
g_PhysicMenu = BuildModelsMenu("physic"); g_PhysicMenu = BuildModelsMenu("physic");
g_DynamicMenu = BuildModelsMenu("dynamic"); g_DynamicMenu = BuildModelsMenu("dynamic");
g_NpcMenu = BuildModelsMenu("npc"); g_NpcMenu = BuildModelsMenu("npc");
g_ColorMenu = BuildColorMenu();
} }
public void OnMapEnd() public void OnMapEnd()
@@ -94,6 +224,7 @@ Menu BuildMainMenu(){
menu.AddItem("menu_prop_dynamic", "Dynamic/Static Props"); menu.AddItem("menu_prop_dynamic", "Dynamic/Static Props");
menu.AddItem("menu_prop_physic", "Physic Props"); menu.AddItem("menu_prop_physic", "Physic Props");
menu.AddItem("menu_prop_npc", "NPCs"); menu.AddItem("menu_prop_npc", "NPCs");
menu.AddItem("menu_prop_color", "Choose Color");
menu.ExitButton = true; menu.ExitButton = true;
return menu; return menu;
} }
@@ -144,6 +275,20 @@ Menu BuildModelsMenu(char[] modelType){
return menu; return menu;
} }
Menu BuildColorMenu()
{
Menu menu = new Menu(ColorHandler);
SetMenuTitle(menu, "Choose Prop Color");
for (int i = 0; i < PropColors_Count; i++)
{
menu.AddItem(ColorToString[i], ColorToString[i]);
}
menu.ExitBackButton = true;
menu.ExitButton = true;
return menu;
}
/* ----------------------------------- */ /* ----------------------------------- */
public Action Command_MainMenu(int client, int args) public Action Command_MainMenu(int client, int args)
@@ -152,6 +297,35 @@ public Action Command_MainMenu(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
public Action Command_SetPropColor(int client, int args)
{
if (args < 3)
{
if (args != 0)
{
char buf[32];
GetCmdArg(1, buf, sizeof(buf));
if (StrEqual(buf, "rainbow", false))
{
g_aPlayerPropColor[client][0] = 256;
ReplyToCommand(client, "\x01[SM] Changed color to \x04Rainbow\x01.");
}
}
else
{
ReplyToCommand(client, "Usage: sm_setpropcolor <r> <g> <b>");
}
return Plugin_Handled;
}
int r = g_aPlayerPropColor[client][0] = GetCmdArgInt(1) % 256;
int g = g_aPlayerPropColor[client][1] = GetCmdArgInt(2) % 256;
int b = g_aPlayerPropColor[client][2] = GetCmdArgInt(3) % 256;
ReplyToCommand(client, "\x01[SM] Changed color to \x07%02x%02x%02x%d %d %d\x01.", r, g, b, r, g, b);
return Plugin_Handled;
}
public int MainMenuHandler(Menu menu, MenuAction action, int client, int param2) public int MainMenuHandler(Menu menu, MenuAction action, int client, int param2)
{ {
char info[32]; char info[32];
@@ -175,8 +349,12 @@ public int MainMenuHandler(Menu menu, MenuAction action, int client, int param2)
else if(StrEqual(info, "menu_prop_npc")){ else if(StrEqual(info, "menu_prop_npc")){
g_NpcMenu.Display(client, MENU_TIME_FOREVER); g_NpcMenu.Display(client, MENU_TIME_FOREVER);
} }
else if(StrEqual(info, "menu_prop_color")){
g_ColorMenu.Display(client, MENU_TIME_FOREVER);
} }
} }
}
return 0;
} }
public RotationHandler(Menu menu, MenuAction action, int client, int param2) public RotationHandler(Menu menu, MenuAction action, int client, int param2)
@@ -302,6 +480,37 @@ public NpcHandler(Menu menu, MenuAction action, int client, int param2)
} }
} }
public ColorHandler(Menu menu, MenuAction action, int client, int param2)
{
char info[32];
menu.GetItem(param2, info, sizeof(info));
switch (action)
{
case MenuAction_Select:
{
int colorIdx = -1;
for (int i = 0; i < PropColors_Count; i++)
{
if (StrEqual(ColorToString[i], info))
{
colorIdx = i;
break;
}
}
if (colorIdx != -1)
{
g_aPlayerPropColor[client] = ColorToRGB[colorIdx];
ReplyToCommand(client, "\x01[SM] Changed color to \x04%s\x01.", info);
g_MainMenu.Display(client, MENU_TIME_FOREVER);
}
}
case MenuAction_Cancel:
if (param2 == MenuCancel_ExitBack)
g_MainMenu.Display(client, MENU_TIME_FOREVER);
}
}
bool isAProp(char[] classname){ bool isAProp(char[] classname){
if(StrEqual(classname, "prop_physics") || StrEqual(classname, "prop_physics_override") || StrEqual(classname, "prop_dynamic") || StrEqual(classname, "prop_dynamic_override") || StrEqual(classname, "prop_physics_multiplayer") || StrEqual(classname, "prop_dynamic_ornament") || StrEqual(classname, "prop_static")){ if(StrEqual(classname, "prop_physics") || StrEqual(classname, "prop_physics_override") || StrEqual(classname, "prop_dynamic") || StrEqual(classname, "prop_dynamic_override") || StrEqual(classname, "prop_physics_multiplayer") || StrEqual(classname, "prop_dynamic_ornament") || StrEqual(classname, "prop_static")){
@@ -405,7 +614,7 @@ void prop_any_create(int client)
char sModelExplodeRadius[11]; char sModelExplodeRadius[11];
int iModelExplode = g_Models.GetNum("explode",0); // if "explode" is not set, then it's 0 int iModelExplode = g_Models.GetNum("explode",0); // if "explode" is not set, then it's 0
g_Models.GetString("health", sModelHealth, sizeof(sModelHealth),"0"); // if "health" is not set, then it's 0 g_Models.GetString("health", sModelHealth, sizeof(sModelHealth),"0"); // if "health" is not set, then its 0
g_Models.GetString("explodedamage", sModelExplodeDamage, sizeof(sModelExplodeDamage),"1"); g_Models.GetString("explodedamage", sModelExplodeDamage, sizeof(sModelExplodeDamage),"1");
g_Models.GetString("exploderadius", sModelExplodeRadius, sizeof(sModelExplodeRadius),"1"); g_Models.GetString("exploderadius", sModelExplodeRadius, sizeof(sModelExplodeRadius),"1");
@@ -485,6 +694,323 @@ void prop_any_create(int client)
AcceptEntityInput(prop, "TurnOn", prop, prop, 0); AcceptEntityInput(prop, "TurnOn", prop, prop, 0);
AcceptEntityInput(prop, "EnableCollision"); AcceptEntityInput(prop, "EnableCollision");
// Hook the prop so we can detect when it gets shot/damaged
if (g_cvNotifyShot.BoolValue || g_cvPropShootTier.IntValue > -1)
{
SetEntityFlags(prop, FL_CLIENT); //Now youre thinking with portals
SetEntProp(prop, Prop_Data, "m_iTeamNum", CS_TEAM_CT); // So it works with filtered triggers
SDKHook(prop, SDKHook_OnTakeDamage, OnPropTakeDamage);
}
g_aSpawnedProps.Push(prop);
SetEntityRenderMode(prop, RENDER_TRANSCOLOR);
if (g_aPlayerPropColor[client][0] == 256) // Rainbow
{
CreateTimer(0.2, OnRainbowTimer, EntIndexToEntRef(prop), TIMER_REPEAT);
ApplyRainbow(prop, 1.0);
}
else
{
SetEntityRenderColor(prop, g_aPlayerPropColor[client][0], g_aPlayerPropColor[client][1], g_aPlayerPropColor[client][2], 255);
}
ShowActivity2(client, "\x01[SM] \x04", "\x01Spawned \x04%s\x01.", sModelName); ShowActivity2(client, "\x01[SM] \x04", "\x01Spawned \x04%s\x01.", sModelName);
LogAction(client, -1, "\"%L\" Spawned \"%s\".", client, sModelName); LogAction(client, -1, "\"%L\" Spawned \"%s\".", client, sModelName);
} }
/* ----------------------------------- */
public Action OnPropTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom)
{
if (attacker < 1 || attacker > MaxClients || !IsClientInGame(attacker))
return Plugin_Continue;
if (GetClientTeam(attacker) == CS_TEAM_CT && GetPlayerTier_native(attacker) < g_cvPropShootTier.IntValue)
return Plugin_Handled;
if (!g_cvNotifyShot.BoolValue)
return Plugin_Continue;
AddToNotifyList(attacker);
DisplayNotifyList();
return Plugin_Continue;
}
char g_szNotifyText[80];
void DisplayNotifyList()
{
if (g_cvNotifyShot.IntValue == 2)
return;
static int tick = 0;
strcopy(g_szNotifyText, sizeof(g_szNotifyText), "Shooting Couch:\n");
for (int i = g_aNotifyList.Length - 1; i >= 0; i--)
{
NotifyListData buf;
g_aNotifyList.GetArray(i, buf, sizeof(NotifyListData));
if (!IsClientInGame(buf.client))
{
if (buf.timer != INVALID_HANDLE)
KillTimer(buf.timer);
g_aNotifyList.Erase(i);
continue;
}
char clientName[32];
GetClientName(buf.client, clientName, sizeof(clientName));
StrCat(g_szNotifyText, sizeof(g_szNotifyText), clientName);
StrCat(g_szNotifyText, sizeof(g_szNotifyText), "\n");
}
if (tick != GetGameTickCount())
{
// Wait a frame since other players might still shoot the couch this frame
RequestFrame(DisplayNotifyList2);
tick = GetGameTickCount();
}
}
void OnNotifyTimer(Handle timer, any data)
{
int client = view_as<int>(data);
int idx = FindInNotifyList(client);
if (idx != -1)
g_aNotifyList.Erase(idx);
if (g_aNotifyList.Length)
DisplayNotifyList();
}
int FindInNotifyList(int client)
{
for (int i = 0; i < g_aNotifyList.Length; i++)
{
NotifyListData buf;
g_aNotifyList.GetArray(i, buf, sizeof(NotifyListData));
if (buf.client == client)
return i;
}
return -1;
}
public void OnClientPutInServer(int client)
{
g_aPlayerPropColor[client] = {255, 255, 255};
if (g_PlayerUseSetup != INVALID_HANDLE)
{
DHookEntity(g_PlayerUseSetup, false, client);
DHookEntity(g_PlayerUseSetup, true, client, INVALID_FUNCTION, PlayerUse_HookPost);
}
}
void OnRainbowTimer(Handle timer, any data)
{
int prop = EntRefToEntIndex(view_as<int>(data));
if (prop == -1)
{
KillTimer(timer);
return;
}
ApplyRainbow(prop, 1.0);
}
// Copied from GlowColors
void ApplyRainbow(int ent, float Frequency)
{
float i = GetGameTime();
int Red = RoundFloat(Sine(Frequency * i + 0.0) * 127.0 + 128.0);
int Green = RoundFloat(Sine(Frequency * i + 2.0943951) * 127.0 + 128.0);
int Blue = RoundFloat(Sine(Frequency * i + 4.1887902) * 127.0 + 128.0);
SetEntityRenderColor(ent, Red, Green, Blue, 255);
}
void AddToNotifyList(int client)
{
if (GetClientTeam(client) != CS_TEAM_CT)
return;
// Just in case the buffer overflow in net message issue doesn't go away with the other fixes
if (g_cvNotifyShot.IntValue == 2)
{
char clientName[32];
GetClientName(client, clientName, sizeof(clientName));
PrintToConsoleAll("[SM] %s is shooting the couch.", clientName);
return;
}
Handle timer = CreateTimer(g_cvNotifyTimer.FloatValue, OnNotifyTimer, client);
int idx = FindInNotifyList(client);
if (idx == -1)
{
NotifyListData data;
data.client = client;
data.timer = timer;
g_aNotifyList.PushArray(data, sizeof(NotifyListData));
}
else
{
NotifyListData buf;
g_aNotifyList.GetArray(idx, buf, sizeof(NotifyListData));
if (buf.timer != INVALID_HANDLE)
KillTimer(buf.timer);
buf.timer = timer;
g_aNotifyList.SetArray(idx, buf, sizeof(NotifyListData));
}
}
ConVar sv_pushaway_force = null;
float g_flPushawayRestore = 0.0;
MRESReturn PlayerUse_Hook(int pThis)
{
static ConVar sv_turbophysics = null;
if (!sv_pushaway_force)
sv_pushaway_force = FindConVar("sv_pushaway_force");
if (!sv_turbophysics)
sv_turbophysics = FindConVar("sv_turbophysics");
if (!sv_pushaway_force || !sv_turbophysics)
return MRES_Ignored;
if (!sv_turbophysics.BoolValue)
return MRES_Ignored;
if (g_cvUseMode.IntValue != 1 && g_cvUseMode.IntValue != 2 && g_cvPropShootTier.IntValue <= -1)
return MRES_Ignored;
// ZMs can always push it
if (GetClientTeam(pThis) != CS_TEAM_CT)
return MRES_Ignored;
if ((GetClientButtons(pThis) & IN_USE) == 0)
return MRES_Ignored;
float eyePos[3];
float eyeAng[3];
float eyeFwd[3];
float endPos[3];
GetClientEyePosition(pThis, eyePos);
GetClientEyeAngles(pThis, eyeAng);
GetAngleVectors(eyeAng, eyeFwd, NULL_VECTOR, NULL_VECTOR);
endPos[0] = eyePos[0] + (eyeFwd[0] * 96.0);
endPos[1] = eyePos[1] + (eyeFwd[1] * 96.0);
endPos[2] = eyePos[2] + (eyeFwd[2] * 96.0);
Handle tr = TR_TraceRayFilterEx(eyePos, endPos, MASK_SOLID, RayType_EndPoint, UsePushFilter, 0, TRACE_ENTITIES_ONLY);
if (TR_DidHit(tr))
{
int entHit = TR_GetEntityIndex(tr);
if (GetEntityMoveType(entHit) == MOVETYPE_VPHYSICS && g_aSpawnedProps.FindValue(entHit) != -1)
{
int mode = g_cvUseMode.IntValue;
if (GetPlayerTier_native(pThis) < g_cvPropShootTier.IntValue)
mode = 1;
switch (mode)
{
// Prevent the prop from being moved
case 1:
{
g_flPushawayRestore = sv_pushaway_force.FloatValue;
sv_pushaway_force.FloatValue = 0.0;
}
// Add to the shooting couch list
case 2:
{
if (g_cvNotifyShot.BoolValue)
{
AddToNotifyList(pThis);
DisplayNotifyList();
}
}
}
}
}
CloseHandle(tr);
return MRES_Handled;
}
MRESReturn PlayerUse_HookPost(int pThis)
{
if (sv_pushaway_force && g_flPushawayRestore != 0.0)
{
sv_pushaway_force.FloatValue = g_flPushawayRestore;
g_flPushawayRestore = 0.0;
}
return MRES_Handled;
}
bool UsePushFilter(int entity, int contentsMask)
{
// Checking if the entity has a physics object
int offs = FindDataMapInfo(entity, "m_pPhysicsObject");
int pt1 = LoadFromAddress(GetEntityAddress(entity) + view_as<Address>(offs), NumberType_Int32);
int pt2 = view_as<int>(Address_PointerSize) == 8 ? LoadFromAddress(GetEntityAddress(entity) + view_as<Address>(offs + 4), NumberType_Int32) : 0;
if (pt1 == 0 && pt2 == 0)
return false;
return true;
}
MRESReturn ForceVPhysicsCollide_Hook(int pThis, DHookReturn hReturn, DHookParam hParams)
{
int ent = hParams.Get(1);
if (GetEntityMoveType(ent) == MOVETYPE_VPHYSICS && g_aSpawnedProps.FindValue(ent) != -1)
{
hReturn.Value = false;
return MRES_Supercede;
}
return MRES_Ignored;
}
void DisplayNotifyList2()
{
static int channel = -1;
static float lastDisplayTime = 0.0;
// Change the channel every once in a while, to avoid conflicts with the map's game_text entities
// (not really sure if sourcemod accounts for them when using -1 as channel but hopefully it does)
if ((GetEngineTime() - lastDisplayTime) > (g_cvNotifyTimer.FloatValue * 2.0))
channel = -1;
SetHudTextParams(0.8, 0.2, g_cvNotifyTimer.FloatValue, 255, 255, 255, 255, 0, 0.0, 0.0, 0.0);
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
channel = ShowHudText(i, channel, g_szNotifyText);
}
lastDisplayTime = GetEngineTime();
}
// To prevent people from early triggering on couch events. Not every map uses buttons for triggers however.
Action OnButtonUse(int entity, int activator, int caller, UseType type, float value)
{
if (activator < 1 || activator > MaxClients || !IsClientInGame(activator))
return Plugin_Continue;
if (GetClientTeam(activator) == CS_TEAM_CT && GetPlayerTier_native(activator) < g_cvButtonPressTier.IntValue)
return Plugin_Handled;
return Plugin_Continue;
}
+125 -13
View File
@@ -1,23 +1,38 @@
#pragma semicolon 1 #pragma semicolon 1
//Yes, this should be a convar. No, I cant be bothered to do the convar hooks.
#define DEFAULT_BAN 60
#include <sourcemod> #include <sourcemod>
#include <clientprefs>
#pragma newdecls required #pragma newdecls required
bool g_bLateLoad = false;
bool g_bProtoBuf; bool g_bProtoBuf;
bool g_bBlocked[MAXPLAYERS + 1];
int g_iBanTime[MAXPLAYERS + 1]; //CAREFUL! 0 ON THIS VARIABLE MEANS NOT BANNED, BUT 0 FROM COOKIE OUTPUT IS AN ERROR!
Cookie g_CookieBanTime = null;
//bool g_bBlocked[MAXPLAYERS + 1];
int g_iMessageClient = -1; int g_iMessageClient = -1;
public Plugin myinfo = public Plugin myinfo =
{ {
name = "RadioMute", name = "RadioMute",
description = "Very simple plugin to block players from using the in-game radio in supported games.", description = "(No longer that) Very simple plugin to block players from using the in-game radio in supported games.",
author = "Obus", author = "Obus, Pan32",
version = "1.0.1", version = "1.1.0",
url = "" url = ""
} }
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
g_bLateLoad = late;
return APLRes_Success;
}
public void OnPluginStart() public void OnPluginStart()
{ {
LoadTranslations("common.phrases"); LoadTranslations("common.phrases");
@@ -35,31 +50,46 @@ public void OnPluginStart()
if (SendAudio == INVALID_MESSAGE_ID) if (SendAudio == INVALID_MESSAGE_ID)
SetFailState("This game does not support the \"SendAudio\" UserMessage."); SetFailState("This game does not support the \"SendAudio\" UserMessage.");
g_CookieBanTime = new Cookie("radiomute_ban_time", "Stores the unix epoch time in which a players ban expires", CookieAccess_Private);
RegAdminCmd("sm_radiomute", Command_RadioMute, ADMFLAG_BAN, "Block a client from using the in-game radio."); RegAdminCmd("sm_radiomute", Command_RadioMute, ADMFLAG_BAN, "Block a client from using the in-game radio.");
RegAdminCmd("sm_radiounmute", Command_RadioUnmute, ADMFLAG_BAN, "Unblock a client from using the in-game radio."); RegAdminCmd("sm_radiounmute", Command_RadioUnmute, ADMFLAG_BAN, "Unblock a client from using the in-game radio.");
HookUserMessage(RadioText, _hkRadioText, true); HookUserMessage(RadioText, _hkRadioText, true);
HookUserMessage(SendAudio, _hkSendAudio, true); HookUserMessage(SendAudio, _hkSendAudio, true);
if (g_bLateLoad)
{
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientInGame(i))
{
OnClientCookiesCached(i);
}
}
}
} }
public void OnMapEnd() public void OnMapEnd()
{ {
for (int i = 1; i <= MaxClients; i++) for (int i = 1; i <= MaxClients; i++)
{ {
g_bBlocked[i] = false; //g_bBlocked[i] = false;
g_iBanTime[i] = 0;
} }
} }
public void OnClientDisconnect(int client) public void OnClientDisconnect(int client)
{ {
g_bBlocked[client] = false; //g_bBlocked[client] = false;
g_iBanTime[client] = 0;
} }
public Action Command_RadioMute(int client, int argc) public Action Command_RadioMute(int client, int argc)
{ {
if (argc < 1) if (argc < 1)
{ {
ReplyToCommand(client, "[SM] Usage: sm_radiomute <target>"); ReplyToCommand(client, "[SM] Usage: sm_radiomute <target> <time>");
return Plugin_Handled; return Plugin_Handled;
} }
@@ -79,13 +109,47 @@ public Action Command_RadioMute(int client, int argc)
return Plugin_Handled; return Plugin_Handled;
} }
int iBanTime = DEFAULT_BAN;
if (argc > 1)
{
GetCmdArg(2, sArgs, sizeof(sArgs));
//BUGBUG Not checking for string to int errors, as people are used to 0 for perm and 0 is also the error value
iBanTime = StringToInt(sArgs);
if (iBanTime < 0)
{
ReplyToCommand(client, "[SM] Invalid ban time");
}
if (iBanTime == 0)
{
//Does this technically violate the previous check? Yes, but the banned value is this. Tought.
iBanTime = -1;
}
}
char sBanTimeStamp[64];
int iBanTimeStamp;
if (iBanTime == -1) //Permanent ban
{
iBanTimeStamp = -1;
Format(sBanTimeStamp, sizeof(sBanTimeStamp), "-1");
}
else //Timestamp + time
{
iBanTimeStamp = GetTime() + MinsToSecs(iBanTime);
Format(sBanTimeStamp, sizeof(sBanTimeStamp), "%d", iBanTimeStamp);
}
for (int i = 0; i < iTargetCount; i++) for (int i = 0; i < iTargetCount; i++)
{ {
g_bBlocked[iTargets[i]] = true; g_CookieBanTime.Set(iTargets[i], sBanTimeStamp);
g_iBanTime[iTargets[i]] = iBanTimeStamp;
//g_bBlocked[iTargets[i]] = true;
} }
ShowActivity2(client, "\x01[SM] \x04", "\x01Radio muted \x04%s\x01", sTargetName); ShowActivity2(client, "\x01[SM] \x04", "\x01Radio muted \x04%s\x01", sTargetName);
LogAction(client, -1, "\"%L\" radio muted \"%s\"", client, sTargetName); LogMessage("\"%L\" radio muted \"%s\" for %d minutes.", client, sTargetName, iBanTime);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -116,11 +180,14 @@ public Action Command_RadioUnmute(int client, int argc)
for (int i = 0; i < iTargetCount; i++) for (int i = 0; i < iTargetCount; i++)
{ {
g_bBlocked[iTargets[i]] = false; g_CookieBanTime.Set(iTargets[i], "");
g_iBanTime[iTargets[i]] = 0;
//g_bBlocked[iTargets[i]] = false;
} }
ShowActivity2(client, "\x01[SM] \x04", "\x01Radio unmuted \x04%s\x01", sTargetName); ShowActivity2(client, "\x01[SM] \x04", "\x01Radio unmuted \x04%s\x01", sTargetName);
LogAction(client, -1, "\"%L\" radio unmuted \"%s\"", client, sTargetName); //Originally LogAction, but for some reason its not logging, so LogMessage it is
LogMessage("\"%L\" radio unmuted \"%s\"", client, sTargetName);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -137,7 +204,8 @@ public Action _hkRadioText(UserMsg msg_id, Handle bf, const int[] players, int p
g_iMessageClient = BfReadByte(bf); g_iMessageClient = BfReadByte(bf);
} }
if (g_bBlocked[g_iMessageClient]) //if (g_bBlocked[g_iMessageClient])
if (IsRadioBanned(g_iMessageClient))
{ {
return Plugin_Handled; return Plugin_Handled;
} }
@@ -160,7 +228,8 @@ public Action _hkSendAudio(UserMsg msg_id, Handle bf, const int[] players, int p
if (strncmp(sSound[6], "lock", 4, false) == 0) if (strncmp(sSound[6], "lock", 4, false) == 0)
return Plugin_Continue; return Plugin_Continue;
if (g_bBlocked[g_iMessageClient]) //if (g_bBlocked[g_iMessageClient])
if (IsRadioBanned(g_iMessageClient))
{ {
g_iMessageClient = -1; g_iMessageClient = -1;
@@ -171,3 +240,46 @@ public Action _hkSendAudio(UserMsg msg_id, Handle bf, const int[] players, int p
return Plugin_Continue; return Plugin_Continue;
} }
public void OnClientCookiesCached(int client)
{
//Get cookie
char sBanTime[64];
g_CookieBanTime.Get(client, sBanTime, sizeof(sBanTime));
if (StrEqual(sBanTime, "")) //If string is empty, no ban ongoing.
{
g_iBanTime[client] = 0;
return;
}
int epochTime = StringToInt(sBanTime);
if (epochTime == 0) //0 Means the string to int errored out, log and empty out the string
{
char steamId[32];
GetClientAuthId(client, AuthId_Steam2, steamId, sizeof(steamId));
LogError("Failed to interpret correctly the ban time length from cookie for user %N (%s)", client, steamId);
g_CookieBanTime.Set(client, "");
g_iBanTime[client] = 0;
return;
}
if (epochTime < GetTime()) //Player is no longer banned
{
g_CookieBanTime.Set(client, "");
g_iBanTime[client] = 0;
return;
}
g_iBanTime[client] = epochTime;
}
//If this turns out to be very expensive, we can convert it back to boolean and banned players will only get unbanned on reconnect or mapchange. But this would work as an ongoing thing.
public bool IsRadioBanned(int client)
{
return (g_iBanTime[client] == -1 || g_iBanTime[client] >= GetTime());
}
public int MinsToSecs(int mins)
{
return mins*60;
}
@@ -0,0 +1,129 @@
#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <clientprefs>
#include <multicolors>
#pragma newdecls required
static const char entityList[][] = { "env_entity_igniter", "env_explosion", "env_fade", "env_fire", "env_hudhint", \
"env_physexplosion", "env_sprite", "env_sun", \
"func_breakable", "func_brush", "func_bomb_target", "func_button", "func_door", \
"func_dustmotes", "func_physbox_multiplayer", "info_overlay", "info_particle_system", \
"info_player_counterterrorist", "info_player_terrorist", "infodecal", \
"item_assaultsuit", "item_defuser", "item_kevlar", \
"light", "light_environment", "light_spot", "move_rope", "path_track", "planted_c4", \
"point_viewcontrol", "shadow_control", "vgui_screen", "func_tracktrain", "worldspawn", \
"func_tanktrain"};
bool bEnabled[MAXPLAYERS + 1];
int iDistance[MAXPLAYERS + 1];
float g_fClientOrigin[MAXPLAYERS + 1][3];
public void OnPluginStart()
{
// Run the cache timer every 0.5 seconds globally
CreateTimer(0.5, Timer_CacheOrigins, _, TIMER_REPEAT);
}
public Action Timer_CacheOrigins(Handle timer)
{
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i))
{
GetClientAbsOrigin(i, g_fClientOrigin[i]);
}
}
return Plugin_Continue;
}
public Plugin myinfo =
{
name = "Render Distance Control",
author = "null138 & (ty ZombieFeyk), modified for webclient on unloze",
description = "Sets entities render distance and etc for players",
version = "3.0.5",
url = "https://steamcommunity.com/id/null138/"
}
public void OnClientAuthorized(int client, const char[] auth)
{
bEnabled[client] = false;
iDistance[client] = 800;
char sIP[32];
GetClientIP(client, sIP, sizeof(sIP));
char allowed_ips[128];
ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips");
sv_set_steam_id_ips.GetString(allowed_ips, sizeof(allowed_ips));
if (StrEqual(sIP, allowed_ips))
{
bEnabled[client] = true;
iDistance[client] = 1200;
}
}
public void OnEntityCreated(int entity, const char[] classname)
{
bool hook = false;
if (!strncmp(classname, "prop_", 5))
{
hook = true;
}
else
{
for(int i; i < sizeof(entityList); i++)
{
if(!strcmp(classname, entityList[i]))
{
hook = true;
break;
}
}
}
if (hook)
{
SDKHook(entity, SDKHook_SetTransmit, DoTransmit);
}
}
public Action DoTransmit(int entity, int client)
{
if (IsFakeClient(client) || !bEnabled[client])
return Plugin_Continue;
if (GetEdictFlags(entity) & FL_EDICT_ALWAYS)
SetEdictFlags(entity, GetEdictFlags(entity) ^ FL_EDICT_ALWAYS);
// Get Entity Origin
static float entVec[3];
if (HasEntProp(entity, Prop_Send, "m_vecOrigin"))
{
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", entVec);
}
else
{
return Plugin_Continue; // If it doesnt have an origin, let it render safely
}
// Use GetVectorDistance with UseSquared=true (avoids an expensive Square Root calculation)
float maxDist = float(iDistance[client]);
float distanceSq = ((g_fClientOrigin[client][0] - entVec[0]) * (g_fClientOrigin[client][0] - entVec[0])) +
((g_fClientOrigin[client][1] - entVec[1]) * (g_fClientOrigin[client][1] - entVec[1])) +
((g_fClientOrigin[client][2] - entVec[2]) * (g_fClientOrigin[client][2] - entVec[2]));
if (distanceSq > (maxDist * maxDist))
{
return Plugin_Handled; // Out of range, hide it from the software renderer!
}
return Plugin_Continue;
}
+10 -2
View File
@@ -97,8 +97,10 @@ public void OnClientDisconnect(int client)
public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255]) public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255])
{ {
// Server not full, nothing to do... // Server not full, nothing to do...
if(GetClientCount(false) < MaxClients) if (GetClientCount(false) < MaxClients)
{
return k_OnClientPreConnectEx_Accept; return k_OnClientPreConnectEx_Accept;
}
// Try to get precached admin id. // Try to get precached admin id.
AdminId admin = FindAdminByIdentity(AUTHMETHOD_STEAM, sSteam32ID); AdminId admin = FindAdminByIdentity(AUTHMETHOD_STEAM, sSteam32ID);
@@ -108,7 +110,6 @@ public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], co
if(admin != INVALID_ADMIN_ID && GetAdminFlag(admin, Admin_Reservation)) if(admin != INVALID_ADMIN_ID && GetAdminFlag(admin, Admin_Reservation))
{ {
Immunity = GetAdminImmunityLevel(admin); Immunity = GetAdminImmunityLevel(admin);
if(!KickValidClient(sName, sSteam32ID, admin, Immunity)) if(!KickValidClient(sName, sSteam32ID, admin, Immunity))
{ {
Format(sRejectReason, sizeof(sRejectReason), "No reserved slot available, sorry."); Format(sRejectReason, sizeof(sRejectReason), "No reserved slot available, sorry.");
@@ -131,6 +132,7 @@ public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], co
pack.WriteCell(Immunity); pack.WriteCell(Immunity);
pack.WriteString(sName); pack.WriteString(sName);
LogMessage("here before AsyncHasSteamIDReservedSlot: sName: %s. sSteam32ID: %s", sName, sSteam32ID);
AsyncHasSteamIDReservedSlot(sSteam32ID, AsyncHasSteamIDReservedSlotCallback, pack); AsyncHasSteamIDReservedSlot(sSteam32ID, AsyncHasSteamIDReservedSlotCallback, pack);
return k_OnClientPreConnectEx_Async; return k_OnClientPreConnectEx_Async;
} }
@@ -145,6 +147,7 @@ public void AsyncHasSteamIDReservedSlotCallback(const char[] sSteam32ID, int Res
// Slot free'd up while waiting or doesn't have a reserved slot? // Slot free'd up while waiting or doesn't have a reserved slot?
if(GetClientCount(false) < MaxClients || Result <= 0) if(GetClientCount(false) < MaxClients || Result <= 0)
{ {
LogMessage("here in return: sSteam32ID: %s. Result: %i", sSteam32ID, Result);
delete pack; delete pack;
ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Accept, ""); ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Accept, "");
return; return;
@@ -162,10 +165,15 @@ public void AsyncHasSteamIDReservedSlotCallback(const char[] sSteam32ID, int Res
if(Result > Immunity) if(Result > Immunity)
Immunity = Result; Immunity = Result;
LogMessage("here 2: sName: %s. sSteam32ID: %s. Result: %i", sName, sSteam32ID, Result);
if(!KickValidClient(sName, sSteam32ID, admin, Immunity)) if(!KickValidClient(sName, sSteam32ID, admin, Immunity))
{
ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Reject, "No reserved slot available, sorry."); ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Reject, "No reserved slot available, sorry.");
}
else else
{
ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Accept, ""); ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Accept, "");
}
} }
stock bool KickValidClient(const char[] sName, const char[] sSteam32ID, AdminId admin, int Immunity) stock bool KickValidClient(const char[] sName, const char[] sSteam32ID, AdminId admin, int Immunity)
+63
View File
@@ -0,0 +1,63 @@
#if defined _AsyncSocket_included
#endinput
#endif
#define _AsyncSocket_included
typedef AsyncSocketConnectCallback = function void(AsyncSocket socket);
typedef AsyncSocketErrorCallback = function void(AsyncSocket socket, int error, const char[] errorName);
typedef AsyncSocketDataCallback = function void(AsyncSocket socket, const char[] data, const int size);
methodmap AsyncSocket < Handle {
public native AsyncSocket();
public native bool Connect(const char[] host, const int port);
public native bool Listen(const char[] host, const int port);
public native bool Write(const char[] data, int length = -1);
public bool WriteNull(const char[] data)
{
return this.Write(data, strlen(data) + 1);
}
public native bool SetConnectCallback(AsyncSocketConnectCallback callback);
public native bool SetErrorCallback(AsyncSocketErrorCallback callback);
public native bool SetDataCallback(AsyncSocketDataCallback callback);
}
/**
* Do not edit below this line!
*/
public Extension __ext_AsyncSocket =
{
name = "AsyncSocket",
file = "AsyncSocket.ext",
#if defined AUTOLOAD_EXTENSIONS
autoload = 1,
#else
autoload = 0,
#endif
#if defined REQUIRE_EXTENSIONS
required = 1,
#else
required = 0,
#endif
};
#if !defined REQUIRE_EXTENSIONS
public __ext_AsyncSocket_SetNTVOptional()
{
MarkNativeAsOptional("AsyncSocket.AsyncSocket");
MarkNativeAsOptional("AsyncSocket.Connect");
MarkNativeAsOptional("AsyncSocket.Listen");
MarkNativeAsOptional("AsyncSocket.Write");
MarkNativeAsOptional("AsyncSocket.SetConnectCallback");
MarkNativeAsOptional("AsyncSocket.SetErrorCallback");
MarkNativeAsOptional("AsyncSocket.SetDataCallback");
}
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
#if defined _selfmute_included_
#endinput
#endif
#define _selfmute_included_
native bool SelfMute_IsIgnoring(int client, int target);
+2 -1
View File
@@ -25,7 +25,8 @@ public void SourceTV_OnSpectatorPutInServer(int client)
SourceTV_GetClientName(client, sName, sizeof(sName)); SourceTV_GetClientName(client, sName, sizeof(sName));
if(SourceTV_GetClientIP(client, sIP, sizeof(sIP)) && GeoipCountry(sIP, sCountry, sizeof(sCountry))) SourceTV_GetClientIP(client, sIP, sizeof(sIP));
if (strlen(sIP) != 0 && GeoipCountry(sIP, sCountry, sizeof(sCountry)))
PrintToChatAll("\x04[\x03SourceTV\x04] %s connected from %s", sName, sCountry); PrintToChatAll("\x04[\x03SourceTV\x04] %s connected from %s", sName, sCountry);
else else
PrintToChatAll("\x04[\x03SourceTV\x04] %s connected", sName); PrintToChatAll("\x04[\x03SourceTV\x04] %s connected", sName);
+6 -4
View File
@@ -2072,6 +2072,7 @@ stock CreateBlock(client, targetId = 0, length = -1, type, const String:sReason[
decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml, String:target_name[MAX_NAME_LENGTH]; decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml, String:target_name[MAX_NAME_LENGTH];
new String:reason[256]; new String:reason[256];
new bool:skipped = false; new bool:skipped = false;
new bool:unverified = false;
// checking args // checking args
if (targetId) if (targetId)
@@ -2167,9 +2168,10 @@ stock CreateBlock(client, targetId = 0, length = -1, type, const String:sReason[
if (!g_bPlayerStatus[target]) if (!g_bPlayerStatus[target])
{ {
// The target has not been blocks verify. It must be completed before you can block anyone. // The target has not been blocks verify. It must be completed before you can block anyone.
ReplyToCommand(client, "%s%t", PREFIX, "Player Comms Not Verified"); //ReplyToCommand(client, "%s%t", PREFIX, "Player Comms Not Verified");
skipped = true; //skipped = true;
continue; // skip //continue; // skip
unverified = true;
} }
switch (type) switch (type)
@@ -2251,7 +2253,7 @@ stock CreateBlock(client, targetId = 0, length = -1, type, const String:sReason[
} }
} }
} }
if (target_count == 1 && !skipped) if (target_count == 1 && !skipped && !unverified)
SavePunishment(client, target_list[0], type, length, reason); SavePunishment(client, target_list[0], type, length, reason);
if (target_count > 1 || !skipped) if (target_count > 1 || !skipped)
ShowActivityToServer(client, type, length, reason, target_name, tn_is_ml); ShowActivityToServer(client, type, length, reason, target_name, tn_is_ml);
+18 -3
View File
@@ -5,6 +5,9 @@
#include <clientprefs> #include <clientprefs>
#include <cstrike> #include <cstrike>
//add some hour requirement for being allowed to spray
#include <unloze_playtime>
#undef REQUIRE_PLUGIN #undef REQUIRE_PLUGIN
#include <adminmenu> #include <adminmenu>
#define REQUIRE_PLUGIN #define REQUIRE_PLUGIN
@@ -35,6 +38,7 @@ ConVar g_cvarUseProximityCheck = null;
ConVar g_cvarSendSpraysToConnectingClients = null; ConVar g_cvarSendSpraysToConnectingClients = null;
ConVar g_cvarUsePersistentSprays = null; ConVar g_cvarUsePersistentSprays = null;
ConVar g_cvarMaxSprayLifetime = null; ConVar g_cvarMaxSprayLifetime = null;
ConVar g_cvarSprayTierRequired = null;
int g_iNSFWDecalIndex; int g_iNSFWDecalIndex;
int g_iHiddenDecalIndex; int g_iHiddenDecalIndex;
@@ -85,7 +89,7 @@ public Plugin myinfo =
{ {
name = "Spray Manager", name = "Spray Manager",
description = "A plugin to help manage player sprays.", description = "A plugin to help manage player sprays.",
author = "Obus", author = "Obus blabla",
version = "2.1.1", version = "2.1.1",
url = "" url = ""
} }
@@ -156,6 +160,8 @@ public void OnPluginStart()
g_cvarMaxSprayLifetime = CreateConVar("sm_spraymanager_maxspraylifetime", "2", "If not using persistent sprays, remove sprays after their global lifetime (in rounds) exceeds this number"); g_cvarMaxSprayLifetime = CreateConVar("sm_spraymanager_maxspraylifetime", "2", "If not using persistent sprays, remove sprays after their global lifetime (in rounds) exceeds this number");
g_cvarSprayTierRequired = CreateConVar("sm_spraymanager_tier_required", "1", "Using the unloze play time plugin to set a minimum tier for allowing players access to spraying.");
AutoExecConfig(true, "plugin.SprayManager"); AutoExecConfig(true, "plugin.SprayManager");
g_hTraceTimer = CreateTimer(0.25, Timer_PerformPlayerTraces, _, TIMER_REPEAT); g_hTraceTimer = CreateTimer(0.25, Timer_PerformPlayerTraces, _, TIMER_REPEAT);
@@ -2150,7 +2156,7 @@ public Action Command_ForceSFW(int client, int argc)
return Plugin_Handled; return Plugin_Handled;
AdminForceSpraySFW(iTarget); AdminForceSpraySFW(iTarget);
PrintToChat(client, "\x01\x04[SprayManager]\x01 Marked \x04%N\x01's spray as SFW.", iTarget); PrintToChat(client, "\x01\x04[SprayManager]\x01 Marked \x04%N\x01 spray as SFW.", iTarget);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -2165,7 +2171,7 @@ public Action Command_ForceSFW(int client, int argc)
continue; continue;
AdminForceSpraySFW(i); AdminForceSpraySFW(i);
PrintToChat(client, "\x01\x04[SprayManager]\x01 Marked \x04%N\x01's spray as SFW.", i); PrintToChat(client, "\x01\x04[SprayManager]\x01 Marked \x04%N\x01s spray as SFW.", i);
return Plugin_Handled; return Plugin_Handled;
} }
@@ -2260,6 +2266,15 @@ public Action HookDecal(const char[] sTEName, const int[] iClients, int iNumClie
return Plugin_Handled; return Plugin_Handled;
} }
//check for an arbitrary amount of hours before allowing spraying
int clientTier = GetPlayerTier_native(client);
int TierRequired = g_cvarSprayTierRequired.IntValue;
if (TierRequired > clientTier)
{
PrintToChat(client, "\x01\x04[SprayManager]\x01 You cannot spray because you dont have reached tier %i yet. (You have tier \x04%i)", TierRequired, clientTier);
return Plugin_Handled;
}
if (g_fNextSprayTime[client] > GetGameTime()) if (g_fNextSprayTime[client] > GetGameTime())
return Plugin_Handled; return Plugin_Handled;
+4 -2
View File
@@ -115,7 +115,8 @@ public void CheckStage()
if(StrEqual(sMethod, "counter")) if(StrEqual(sMethod, "counter"))
{ {
char sCounter[64]; char sCounter[64];
if(!g_Config.GetString("counter", sCounter, sizeof(sCounter))) g_Config.GetString("counter", sCounter, sizeof(sCounter), "not found");
if (StrEqual(sCounter, "not found"))
{ {
LogError("Could not find \"counter\""); LogError("Could not find \"counter\"");
return; return;
@@ -172,7 +173,8 @@ public void CheckStage()
bHasDiffCounter = true; bHasDiffCounter = true;
char sDiffCounter[64]; char sDiffCounter[64];
if(!g_Config.GetString("counter", sDiffCounter, sizeof(sDiffCounter))) g_Config.GetString("counter", sDiffCounter, sizeof(sDiffCounter), "not found");
if (StrEqual(sDiffCounter, "not found"))
{ {
LogError("Could not find \"diffcounter\""); LogError("Could not find \"diffcounter\"");
return; return;
@@ -6,7 +6,7 @@
// //
//==================================================================================================== //====================================================================================================
#include <sourcemod> #include <sourcemod>
#include <SteamWorks> #include <ripext>
#include <unloze> #include <unloze>
#include <UNLOZE.secret> //#define UNLOZE_APIKEY here #include <UNLOZE.secret> //#define UNLOZE_APIKEY here
#include <cstrike> #include <cstrike>
@@ -131,68 +131,71 @@ public void OnClientAuthorized(int client, const char[] sSteamID32)
FormatEx(sRequest, sizeof(sRequest), "https://unloze.com/api/private_api.php?api_key=%s&steam_id=%s", UNLOZE_APIKEY, sSteamID64); FormatEx(sRequest, sizeof(sRequest), "https://unloze.com/api/private_api.php?api_key=%s&steam_id=%s", UNLOZE_APIKEY, sSteamID64);
int iSerial = GetClientSerial(client); int iSerial = GetClientSerial(client);
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest);
if (!hRequest || char sPath[PLATFORM_MAX_PATH];
!SteamWorks_SetHTTPCallbacks(hRequest, OnClientAuthorized_OnTransferComplete) || BuildPath(Path_SM, sPath, sizeof(sPath), "data/unloze_forum_%d.tmp", iSerial);
!SteamWorks_SetHTTPRequestContextValue(hRequest, iSerial) ||
!SteamWorks_SendHTTPRequest(hRequest)) HTTPRequest hRequest = new HTTPRequest(sRequest);
{ hRequest.DownloadFile(sPath, OnClientAuthorized_OnDownloaded, iSerial);
delete hRequest;
}
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose: ripexts HTTPResponse only exposes the body as decoded JSON, unlike
// SteamWorks' GetHTTPResponseBodyCallback which handed back a plain char[].
// Rather than change private_api.php's response format, we use
// HTTPRequest.DownloadFile() to write the raw "GROUP\r\nNAME" body to a temp
// file and parse it back exactly like the original SteamWorks version did.
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public int OnClientAuthorized_OnTransferComplete(Handle hRequest, bool bFailure, bool bSuccessful, EHTTPStatusCode eStatusCode, int iSerial) public void OnClientAuthorized_OnDownloaded(HTTPStatus status, any iSerial)
{ {
char sPath[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sPath, sizeof(sPath), "data/unloze_forum_%d.tmp", iSerial);
int client = GetClientFromSerial(iSerial); int client = GetClientFromSerial(iSerial);
if (!client) //Player disconnected. if (!client) //Player disconnected.
{ {
delete hRequest; DeleteFile(sPath);
return 0; return;
} }
if (bFailure || !bSuccessful || eStatusCode != k_EHTTPStatusCode200OK) if (status != HTTPStatus_OK)
{ {
DeleteFile(sPath);
G_bResponseFailed[client] = true; G_bResponseFailed[client] = true;
if (G_bPreAdminChecked[client]) if (G_bPreAdminChecked[client])
NotifyPostAdminCheck(client); NotifyPostAdminCheck(client);
delete hRequest; return;
return 0;
} }
SteamWorks_GetHTTPResponseBodyCallback(hRequest, OnClientAuthorized_OnTransferResponse, iSerial); char sGroup[64], sName[32];
return 0; sGroup[0] = '\0';
} sName[0] = '\0';
//---------------------------------------------------------------------------------------------------- File hFile = OpenFile(sPath, "r");
// Purpose: if (hFile != null)
//----------------------------------------------------------------------------------------------------
public int OnClientAuthorized_OnTransferResponse(char[] sData, int iSerial)
{
char splitData[2][32];
int client = GetClientFromSerial(iSerial);
if (!client) //Player disconnected.
return 0;
TrimString(sData);
StripQuotes(sData);
LogMessage("reached sData with status 200: %s", sData);
ExplodeString(sData, "\r\n", splitData, 2, sizeof(splitData[]));
if(strlen(splitData[1]) > 0)
strcopy(G_sName[client], sizeof(G_sName[]), splitData[1]);
if(!StrEqual(splitData[0], "NOGROUP"))
{ {
strcopy(G_sGroup[client], sizeof(G_sGroup[]), splitData[0]); hFile.ReadLine(sGroup, sizeof(sGroup));
hFile.ReadLine(sName, sizeof(sName));
delete hFile;
}
DeleteFile(sPath);
TrimString(sGroup);
StripQuotes(sGroup);
TrimString(sName);
StripQuotes(sName);
LogMessage("reached sData with status 200: %s", sGroup);
if (strlen(sName) > 0)
strcopy(G_sName[client], sizeof(G_sName[]), sName);
if (!StrEqual(sGroup, "NOGROUP"))
{
strcopy(G_sGroup[client], sizeof(G_sGroup[]), sGroup);
G_bResponsePassed[client] = true; G_bResponsePassed[client] = true;
@@ -201,7 +204,6 @@ public int OnClientAuthorized_OnTransferResponse(char[] sData, int iSerial)
} }
else else
G_bResponseFailed[client] = true; //users with just a forum name did not pass the VIP check! so the response "failed" (but we store their forum name for later!) G_bResponseFailed[client] = true; //users with just a forum name did not pass the VIP check! so the response "failed" (but we store their forum name for later!)
return 0;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -295,40 +297,39 @@ public int Native_AsyncHasSteamIDReservedSlot(Handle plugin, int numParams)
hDataPack.WriteCell(plugin); hDataPack.WriteCell(plugin);
hDataPack.WriteCell(data); hDataPack.WriteCell(data);
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest); // The DataPack handle value is unique among currently-open handles, so it
if (!hRequest || // doubles as a collision-free key for the temp file this specific
!SteamWorks_SetHTTPCallbacks(hRequest, Native_AsyncHasSteamIDReservedSlot_OnTransferComplete) || // in-flight request will use.
!SteamWorks_SetHTTPRequestContextValue(hRequest, hDataPack) || char sPath[PLATFORM_MAX_PATH];
!SteamWorks_SendHTTPRequest(hRequest)) BuildPath(Path_SM, sPath, sizeof(sPath), "data/unloze_reslot_%d.tmp", view_as<int>(hDataPack));
{
delete hRequest; HTTPRequest hRequest = new HTTPRequest(sRequest);
} hRequest.DownloadFile(sPath, Native_AsyncHasSteamIDReservedSlot_OnDownloaded, hDataPack);
return 0; return 0;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose: same DownloadFile-to-tempfile approach as OnClientAuthorized_OnDownloaded
// above - keeps private_api.php's raw "GROUP\r\nNAME" text response untouched.
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public int Native_AsyncHasSteamIDReservedSlot_OnTransferComplete(Handle hRequest, bool bFailure, bool bSuccessful, EHTTPStatusCode eStatusCode, DataPack hDataPack) public void Native_AsyncHasSteamIDReservedSlot_OnDownloaded(HTTPStatus status, DataPack hDataPack)
{ {
if (bFailure || !bSuccessful || eStatusCode != k_EHTTPStatusCode200OK) char sPath[PLATFORM_MAX_PATH];
{ BuildPath(Path_SM, sPath, sizeof(sPath), "data/unloze_reslot_%d.tmp", view_as<int>(hDataPack));
char sData[32] = "NOGROUP"; char sData[32] = "NOGROUP";
Native_AsyncHasSteamIDReservedSlot_OnTransferResponse(sData, hDataPack);
delete hRequest; if (status == HTTPStatus_OK)
return 0; {
File hFile = OpenFile(sPath, "r");
if (hFile != null)
{
hFile.ReadLine(sData, sizeof(sData));
delete hFile;
} }
}
DeleteFile(sPath);
SteamWorks_GetHTTPResponseBodyCallback(hRequest, Native_AsyncHasSteamIDReservedSlot_OnTransferResponse, hDataPack);
return 0;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int Native_AsyncHasSteamIDReservedSlot_OnTransferResponse(char[] sData, DataPack hDataPack)
{
hDataPack.Reset(); hDataPack.Reset();
char sSteamID32[32]; char sSteamID32[32];
@@ -343,7 +344,7 @@ public int Native_AsyncHasSteamIDReservedSlot_OnTransferResponse(char[] sData, D
any data; any data;
data = hDataPack.ReadCell(); data = hDataPack.ReadCell();
static char splitData[32]; char splitData[32];
TrimString(sData); TrimString(sData);
StripQuotes(sData); StripQuotes(sData);
@@ -363,7 +364,6 @@ public int Native_AsyncHasSteamIDReservedSlot_OnTransferResponse(char[] sData, D
Call_Finish(); Call_Finish();
delete hDataPack; delete hDataPack;
return 0;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -2,13 +2,13 @@
#include <sdktools> #include <sdktools>
#include <multicolors> #include <multicolors>
#include <zombiereloaded> #include <zombiereloaded>
#include <loghelper>
#pragma semicolon 1 #pragma semicolon 1
#pragma newdecls required #pragma newdecls required
/* CONVARS */ /* CONVARS */
ConVar g_hCVar_Delay; ConVar g_hCVar_Delay;
ConVar g_hCVar_KnifemadnessEnabled;
/* BOOLS */ /* BOOLS */
bool g_bClientKnifed[MAXPLAYERS+1]; bool g_bClientKnifed[MAXPLAYERS+1];
@@ -32,6 +32,9 @@ public Plugin myinfo =
public void OnPluginStart() public void OnPluginStart()
{ {
g_hCVar_Delay = CreateConVar("sm_knife_madness_kill_delay", "3", "Delay before ZMs die after being knifed by a human.", 0, true, 0.0); g_hCVar_Delay = CreateConVar("sm_knife_madness_kill_delay", "3", "Delay before ZMs die after being knifed by a human.", 0, true, 0.0);
g_hCVar_KnifemadnessEnabled = CreateConVar("sm_knifemadness_enabled", "0", "", FCVAR_NONE, true, 0.0, true, 1.0);
g_hCVar_KnifemadnessEnabled.AddChangeHook(OnConVarChanged);
AutoExecConfig(true);
HookEvent("player_spawn", OnClientSpawn, EventHookMode_Post); HookEvent("player_spawn", OnClientSpawn, EventHookMode_Post);
HookEvent("player_death", OnClientDeath, EventHookMode_Pre); HookEvent("player_death", OnClientDeath, EventHookMode_Pre);
@@ -39,11 +42,23 @@ public void OnPluginStart()
HookEvent("player_team", OnClientTeam, EventHookMode_Pre); HookEvent("player_team", OnClientTeam, EventHookMode_Pre);
} }
public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
if(g_hCVar_KnifemadnessEnabled.BoolValue)
CPrintToChatAll("{cyan}[Knifemadness] {white}has been allowed.");
else
CPrintToChatAll("{cyan}[Knifemadness] {white}has been disabled.");
}
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnClientSpawn(Event hEvent, const char[] sEvent, bool bDontBroadcast) public void OnClientSpawn(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{ {
if(!g_hCVar_KnifemadnessEnabled.BoolValue)
{
return;
}
int client = GetClientOfUserId(hEvent.GetInt("userid")); int client = GetClientOfUserId(hEvent.GetInt("userid"));
g_bClientKnifed[client] = false; g_bClientKnifed[client] = false;
@@ -58,6 +73,10 @@ public void OnClientSpawn(Event hEvent, const char[] sEvent, bool bDontBroadcast
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action OnClientDeath(Event hEvent, const char[] sEvent, bool bDontBroadcast) public Action OnClientDeath(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{ {
if(!g_hCVar_KnifemadnessEnabled.BoolValue)
{
return Plugin_Continue;
}
int client = GetClientOfUserId(hEvent.GetInt("userid")); int client = GetClientOfUserId(hEvent.GetInt("userid"));
if (g_bSupressDeath[client]) if (g_bSupressDeath[client])
@@ -74,6 +93,10 @@ public Action OnClientDeath(Event hEvent, const char[] sEvent, bool bDontBroadca
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnClientHurt(Event hEvent, const char[] sEvent, bool bDontBroadcast) public void OnClientHurt(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{ {
if(!g_hCVar_KnifemadnessEnabled.BoolValue)
{
return;
}
int attacker = GetClientOfUserId(hEvent.GetInt("attacker")); int attacker = GetClientOfUserId(hEvent.GetInt("attacker"));
int victim = GetClientOfUserId(hEvent.GetInt("userid")); int victim = GetClientOfUserId(hEvent.GetInt("userid"));
@@ -107,12 +130,16 @@ public void OnClientHurt(Event hEvent, const char[] sEvent, bool bDontBroadcast)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnClientTeam(Event hEvent, const char[] sEvent, bool bDontBroadcast) public void OnClientTeam(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{ {
if(!g_hCVar_KnifemadnessEnabled.BoolValue)
{
return;
}
int client = GetClientOfUserId(hEvent.GetInt("userid")); int client = GetClientOfUserId(hEvent.GetInt("userid"));
if (g_bClientKnifed[client]) if (g_bClientKnifed[client])
{ {
g_bClientKnifed[client] = false; g_bClientKnifed[client] = false;
LogPlayerEvent(client, "triggered", "switch_to_spec"); //LogPlayerEvent(client, "triggered", "switch_to_spec");
} }
} }
@@ -121,6 +148,10 @@ public void OnClientTeam(Event hEvent, const char[] sEvent, bool bDontBroadcast)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void ZR_OnClientInfected(int client, int attacker, bool motherInfect, bool respawnOverride, bool respawn) public void ZR_OnClientInfected(int client, int attacker, bool motherInfect, bool respawnOverride, bool respawn)
{ {
if(!g_hCVar_KnifemadnessEnabled.BoolValue)
{
return;
}
if (!IsValidClient(attacker)) if (!IsValidClient(attacker))
return; return;
@@ -137,6 +168,10 @@ public void ZR_OnClientInfected(int client, int attacker, bool motherInfect, boo
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public Action KillZM(Handle timer, DataPack pack) public Action KillZM(Handle timer, DataPack pack)
{ {
if(!g_hCVar_KnifemadnessEnabled.BoolValue)
{
return Plugin_Continue;
}
int attacker = 0; int attacker = 0;
int client = 0; int client = 0;
@@ -145,21 +180,21 @@ public Action KillZM(Handle timer, DataPack pack)
client = GetClientOfUserId(pack.ReadCell()); client = GetClientOfUserId(pack.ReadCell());
if (client == 0) if (client == 0)
return; return Plugin_Continue;
if (!(IsValidClient(client, false) && IsPlayerAlive(client) && ZR_IsClientZombie(client) && g_bClientKnifed[client])) if (!(IsValidClient(client, false) && IsPlayerAlive(client) && ZR_IsClientZombie(client) && g_bClientKnifed[client]))
return; return Plugin_Continue;
g_bSupressDeath[client] = true; g_bSupressDeath[client] = true;
ForcePlayerSuicide(client); ForcePlayerSuicide(client);
g_bClientKnifed[client] = false; g_bClientKnifed[client] = false;
if (!(IsValidClient(attacker, false))) if (!(IsValidClient(attacker, false)))
return; return Plugin_Continue;
Event hEvent = CreateEvent("player_death"); Event hEvent = CreateEvent("player_death");
if (hEvent == null) if (hEvent == null)
return; return Plugin_Continue;
hEvent.SetInt("userid", GetClientUserId(client)); hEvent.SetInt("userid", GetClientUserId(client));
hEvent.SetInt("attacker", GetClientUserId(attacker)); hEvent.SetInt("attacker", GetClientUserId(attacker));
@@ -168,6 +203,7 @@ public Action KillZM(Handle timer, DataPack pack)
hEvent.SetInt("dominated", 0); hEvent.SetInt("dominated", 0);
hEvent.SetInt("revenge", 0); hEvent.SetInt("revenge", 0);
hEvent.Fire(); hEvent.Fire();
return Plugin_Handled;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -190,6 +226,10 @@ public void QueryClient(int client)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public void OnConVarQueryFinished(QueryCookie cookie, int client, ConVarQueryResult result, const char[] cvarName, const char[] cvarValue, int iSerial) public void OnConVarQueryFinished(QueryCookie cookie, int client, ConVarQueryResult result, const char[] cvarName, const char[] cvarValue, int iSerial)
{ {
if(!g_hCVar_KnifemadnessEnabled.BoolValue)
{
return;
}
if (GetClientFromSerial(iSerial) != client) if (GetClientFromSerial(iSerial) != client)
return; return;
+43 -44
View File
@@ -1,7 +1,6 @@
#include <sourcemod> #include <sourcemod>
#include <SteamWorks> #include <ripext>
#include <multicolors> #include <multicolors>
#include <json>
#undef REQUIRE_PLUGIN #undef REQUIRE_PLUGIN
#tryinclude <PlayerManager> #tryinclude <PlayerManager>
@@ -103,9 +102,19 @@ public void OnClientAuthorized(int client, const char[] auth)
char sIP[32]; char sIP[32];
GetClientIP(client, sIP, sizeof(sIP)); GetClientIP(client, sIP, sizeof(sIP));
char allowed_ips[128];
ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips");
if (sv_set_steam_id_ips != null)
{
sv_set_steam_id_ips.GetString(allowed_ips, sizeof(allowed_ips));
if (StrEqual(sIP, allowed_ips))
{
return; //its the webclient, the internal docker IP obviously cannot be found by VPN checker.
}
}
char sQuery[512]; char sQuery[512];
Format(sQuery, sizeof(sQuery), "SELECT * FROM ip_table WHERE ip='%s'", sIP); Format(sQuery, sizeof(sQuery), "SELECT * FROM ip_table WHERE ip='%s'", sIP);
g_hDatabase.Query(SQL_OnQueryCompleted, sQuery, GetClientSerial(client), DBPrio_Low); g_hDatabase.Query(SQL_OnQueryCompleted, sQuery, GetClientSerial(client), DBPrio_Low);
} }
@@ -154,69 +163,60 @@ public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[]
FormatEx(sRequest, sizeof(sRequest), "use a url here", sIP, APIKEY); FormatEx(sRequest, sizeof(sRequest), "use a url here", sIP, APIKEY);
//PrintToConsoleAll(sRequest); //PrintToConsoleAll(sRequest);
Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest); HTTPRequest hRequest = new HTTPRequest(sRequest);
if (!hRequest || hRequest.Get(OnTransferComplete, iSerial);
!SteamWorks_SetHTTPCallbacks(hRequest, OnTransferComplete) ||
!SteamWorks_SetHTTPRequestContextValue(hRequest, iSerial) ||
!SteamWorks_SendHTTPRequest(hRequest))
{
delete hRequest;
}
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// Purpose: // Purpose:
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
public int OnTransferComplete(Handle hRequest, bool bFailure, bool bSuccessful, EHTTPStatusCode eStatusCode, int iSerial) public void OnTransferComplete(HTTPResponse response, any iSerial)
{ {
int client = GetClientFromSerial(iSerial); int client = GetClientFromSerial(iSerial);
if (!client) //Player disconnected. if (!client) //Player disconnected.
{ return;
delete hRequest;
return 0;
}
if (bFailure || !bSuccessful || eStatusCode != k_EHTTPStatusCode200OK) if (response.Status != HTTPStatus_OK || response.Data == null)
{ {
delete hRequest;
g_bStatus[client] = STATUS_ERROR; g_bStatus[client] = STATUS_ERROR;
LogError("Request-Error: %d", eStatusCode); LogError("Request-Error: %d", response.Status);
return 0; return;
} }
SteamWorks_GetHTTPResponseBodyCallback(hRequest, OnTransferResponse, iSerial);
return 0;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int OnTransferResponse(char[] sData, int iSerial)
{
int client = GetClientFromSerial(iSerial);
if (!client) //Player disconnected.
return 0;
char sIP[32]; char sIP[32];
GetClientIP(client, sIP, sizeof(sIP)); GetClientIP(client, sIP, sizeof(sIP));
JSON_Object obj = json_decode(sData); // ripext already decodes the JSON body for us as a native JSONObject; no
JSONObject obj = view_as<JSONObject>(response.Data);
char sStatus[32]; char sStatus[32];
obj.GetString("status", sStatus, sizeof(sStatus)); if (!obj.GetString("status", sStatus, sizeof(sStatus)))
sStatus[0] = '\0';
if (!StrEqual(sStatus, "ok") && !StrEqual(sStatus, "warning")) if (!StrEqual(sStatus, "ok") && !StrEqual(sStatus, "warning"))
{ {
char sMessage[256]; char sMessage[256];
obj.GetString("message", sMessage, sizeof(sMessage)); if (!obj.GetString("message", sMessage, sizeof(sMessage)))
sMessage[0] = '\0';
LogError("API-Response: %s: %s", sStatus, sMessage); LogError("API-Response: %s: %s", sStatus, sMessage);
g_bStatus[client] = STATUS_ERROR; g_bStatus[client] = STATUS_ERROR;
json_cleanup_and_delete(obj); delete obj;
return 0; return;
} }
JSON_Object ipobj = obj.GetObject(sIP); if (!obj.HasKey(sIP))
{
LogError("API-Response: no entry for IP %s", sIP);
g_bStatus[client] = STATUS_ERROR;
delete obj;
return;
}
JSONObject ipobj = view_as<JSONObject>(obj.Get(sIP));
char sProxy[16]; char sProxy[16];
ipobj.GetString("proxy", sProxy, sizeof(sProxy)); if (!ipobj.GetString("proxy", sProxy, sizeof(sProxy)))
sProxy[0] = '\0';
if (StrEqual(sProxy, "no")) if (StrEqual(sProxy, "no"))
g_bStatus[client] = STATUS_SAFE; g_bStatus[client] = STATUS_SAFE;
else else
@@ -238,10 +238,8 @@ public int OnTransferResponse(char[] sData, int iSerial)
Format(sQuery, sizeof(sQuery), "INSERT INTO ip_table (ip, type, last_check) VALUES ('%s', '%d', '%d') ON DUPLICATE KEY UPDATE type='%d', last_check='%d';", sIP, g_bStatus[client], iCurrentTime, g_bStatus[client], iCurrentTime); Format(sQuery, sizeof(sQuery), "INSERT INTO ip_table (ip, type, last_check) VALUES ('%s', '%d', '%d') ON DUPLICATE KEY UPDATE type='%d', last_check='%d';", sIP, g_bStatus[client], iCurrentTime, g_bStatus[client], iCurrentTime);
g_hDatabase.Query(SQL_OnQueryCompleted, sQuery, _, DBPrio_Low); g_hDatabase.Query(SQL_OnQueryCompleted, sQuery, _, DBPrio_Low);
//https://github.com/clugg/sm-json/blob/master/addons/sourcemod/scripting/json_test.sp#L446 delete ipobj;
//as far as i can tell i just need to call json_cleanup_and_delete() on the most outer json object, all its children should get cleaned and deleted as well. delete obj;
json_cleanup_and_delete(obj);
return 0;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@@ -405,3 +403,4 @@ public void OnLibraryRemoved(const char[] sName)
if (strcmp(sName, "PlayerManager", false) == 0) if (strcmp(sName, "PlayerManager", false) == 0)
g_bPMLoaded = false; g_bPMLoaded = false;
} }
+8 -3
View File
@@ -87,16 +87,21 @@ public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] n
public void OnMapStart() public void OnMapStart()
{ {
if(g_hTimer != INVALID_HANDLE && CloseHandle(g_hTimer)) if(g_hTimer != INVALID_HANDLE)
{
CloseHandle(g_hTimer);
g_hTimer = INVALID_HANDLE; g_hTimer = INVALID_HANDLE;
}
g_hTimer = CreateTimer(TIMER_INTERVAL, Timer_CleanupWeapons, INVALID_HANDLE, TIMER_REPEAT); g_hTimer = CreateTimer(TIMER_INTERVAL, Timer_CleanupWeapons, INVALID_HANDLE, TIMER_REPEAT);
} }
public void OnMapEnd() public void OnMapEnd()
{ {
if(g_hTimer != INVALID_HANDLE && CloseHandle(g_hTimer)) if(g_hTimer != INVALID_HANDLE)
{
CloseHandle(g_hTimer);
g_hTimer = INVALID_HANDLE; g_hTimer = INVALID_HANDLE;
}
} }
public void OnClientPutInServer(int client) public void OnClientPutInServer(int client)
+458
View File
@@ -0,0 +1,458 @@
#include <clientprefs>
#include <multicolors>
#include <sdkhooks>
#include <sourcemod>
#include <zombiereloaded>
#include <unloze_playtime>
#include <TransmitManager>
#include <leader>
#include <entWatch_core>
/* BOOLS */
bool g_bHasSomebodyToHide[MAXPLAYERS + 1];
bool g_bHasTobeVisible[MAXPLAYERS + 1];
bool g_bHidePlayers[MAXPLAYERS + 1][MAXPLAYERS + 1];
/* INTEGERS */
int g_iHideRange[MAXPLAYERS+1];
int g_iLeader = 0;
int g_iTierHide = 0;
/* HANDLES */
Handle g_hTimer;
/* CONVARS */
ConVar g_hCVar_HideEnabled;
/* COOKIES */
Handle g_hCookie_HideRange;
#define DISABLED -1
#define SHORT_RANGE 120
#define MEDIUM_RANGE 250
#define LONG_RANGE 500
int SHORT = SHORT_RANGE * SHORT_RANGE;
int MEDIUM = MEDIUM_RANGE * MEDIUM_RANGE;
int LONG = LONG_RANGE * LONG_RANGE;
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Plugin myinfo =
{
name = "Hide Teammates with Transmit extension",
author = "jenz, a bunch of re-used code from Neon. credits to pan for fixing it. and thx to zacade",
description = "A plugin that can !hide teammates with individual distances",
version = "1.0.0",
url = "www.unloze.com"
};
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
g_hCVar_HideEnabled = CreateConVar("sm_hide_enabled", "1", "", FCVAR_NONE, true, 0.0, true, 1.0);
g_hCVar_HideEnabled.AddChangeHook(OnConVarChanged);
AutoExecConfig(true);
g_hCookie_HideRange = RegClientCookie("hide_range_transmitext", "", CookieAccess_Private);
RegConsoleCmd("sm_hide", OnHideSettings, "Hiding near humans");
for(int client = 1; client <= MaxClients; client++)
{
if(IsValidClient(client))
{
OnClientPutInServer(client);
if(AreClientCookiesCached(client))
OnClientCookiesCached(client);
}
}
SetCookieMenuItem(MenuHandler_CookieMenu, 0, "Hide");
g_hTimer = CreateTimer(1.0, UpdateHide, INVALID_HANDLE, TIMER_REPEAT);
g_iTierHide = FindTierForCommandOverride("sm_hide");
}
public void OnPluginEnd()
{
if (g_hTimer != INVALID_HANDLE)
delete g_hTimer;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
for(int client = 1; client <= MaxClients; client++)
{
g_bHasSomebodyToHide[client] = false;
for(int target = 1; target <= MaxClients; target++)
{
g_bHidePlayers[client][target] = false;
}
}
if(g_hCVar_HideEnabled.BoolValue)
CPrintToChatAll("{cyan}[Hide] {white}has been allowed.");
else
CPrintToChatAll("{cyan}[Hide] {white}has been disabled.");
}
public Action UpdateHide(Handle timer)
{
if (!g_hCVar_HideEnabled.BoolValue)
return Plugin_Continue;
g_iLeader = Leader_CurrentLeader();
for (int client = 1; client <= MaxClients; client++)
{
if (!IsValidClient(client) || IsClientSourceTV(client) || IsFakeClient(client))
{
continue;
}
if (g_iHideRange[client] == DISABLED) //client just disabled hide right now. the bool will pass once until they enabled hide again.
{
if (g_bHasSomebodyToHide[client] && TransmitManager_IsEntityHooked(client))
{
for (int target = 1; target <= MaxClients; target++)
{
if (IsValidClient(target) && client != target && TransmitManager_IsEntityHooked(target))
{
TransmitManager_SetEntityState(target, client, true); //client can see all players again
g_bHidePlayers[client][target] = false;
}
}
g_bHasSomebodyToHide[client] = false;
}
continue;
}
//lazy fix for invisible zm
if (TransmitManager_IsEntityHooked(client))
{
for (int target = 1; target <= MaxClients; target++)
{
if (IsValidClient(target) && client != target && g_bHidePlayers[client][target]
&& IsPlayerAlive(target) && ZR_IsClientZombie(target) && TransmitManager_IsEntityHooked(target))
{
g_bHidePlayers[client][target] = false;
TransmitManager_SetEntityState(target, client, true); //client can see player again because the player is a zm
}
}
}
bool ClientIsAlive = IsPlayerAlive(client);
bool ClientIsHuman = false;
if (ClientIsAlive)
{
ClientIsHuman = ZR_IsClientHuman(client);
}
bool ClientHasItem = EW_ClientHasItem(client);
//player is leader, has an item, is not alive or is not human, set the transmit state once via the bool so all can see the player.
if (g_iLeader == client || ClientHasItem || !ClientIsAlive || !ClientIsHuman)
{
if (!g_bHasTobeVisible[client] && TransmitManager_IsEntityHooked(client))
{
for (int target = 1; target <= MaxClients; target++)
{
if (IsValidClient(target) && client != target && TransmitManager_IsEntityHooked(target))
{
//all must see the leader, the zombie and the item user.
TransmitManager_SetEntityState(client, target, true); //all can see the player again
//if the player is dead or not human they must see all players again
if (!ClientIsAlive || !ClientIsHuman)
{
TransmitManager_SetEntityState(target, client, true); //client can see all players again
}
}
}
g_bHasTobeVisible[client] = true;
}
//but the human leader and human item users may still want to hide the remaining human players.
if (ClientIsAlive && ClientIsHuman && (g_iLeader == client || ClientHasItem))
{
DoTheTargetCheck(client);
}
continue;
}
g_bHasTobeVisible[client] = false;
//all the remaining checks for clients with hide enabled that are alive humans and not leaders or item users.
DoTheTargetCheck(client);
}
return Plugin_Continue;
}
public void DoTheTargetCheck(int client)
{
float fOriginClient[3];
float fOriginTarget[3];
GetClientAbsOrigin(client, fOriginClient);
g_bHasSomebodyToHide[client] = false;
for (int target = 1; target <= MaxClients; target++)
{
if (!IsValidClient(target) || IsClientSourceTV(target))
{
continue;
}
//the target is not the leader, has no item, is alive and is a human. so consider him for the distance check.
if (g_iLeader != target && !EW_ClientHasItem(target) && IsPlayerAlive(target) && ZR_IsClientHuman(target) && target != client)
{
GetClientAbsOrigin(target, fOriginTarget);
if (GetVectorDistance(fOriginTarget, fOriginClient, true) <= float(g_iHideRange[client]))
{
//we only have to check the hook when the state of the hidePlayers boolean changed
if (!g_bHidePlayers[client][target] && TransmitManager_IsEntityHooked(client) && TransmitManager_IsEntityHooked(target))
{
//the client here cannot be a fakeclient. thats already checked.
TransmitManager_SetEntityState(target, client, false); //the player is hiding the teammate due to distance
g_bHidePlayers[client][target] = true;
//LogMessage("client: %N is %s. target: %N is %s", client, ZR_IsClientHuman(client) ? "Human" : "Zombie", target, ZR_IsClientHuman(target) ? "Human" : "Zombie");
}
g_bHasSomebodyToHide[client] = true; //theres at least one target to hide for the player, the teammate
}
else
{
//we only have to check the hook when the state of the hidePlayers boolean changed
if (g_bHidePlayers[client][target] && TransmitManager_IsEntityHooked(client) && TransmitManager_IsEntityHooked(target))
{
TransmitManager_SetEntityState(target, client, true); //the player is seeing the teammate due to distance
g_bHidePlayers[client][target] = false;
}
}
}
}
}
public Action OnHideSettings(int client, int args)
{
ShowSettingsMenu(client);
return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void ShowSettingsMenu(int client)
{
Menu menu = new Menu(MenuHandler_MainMenu);
char titlemsg[256];
Format(titlemsg, sizeof(titlemsg), "Hide");
menu.SetTitle(titlemsg, client);
char sBuffer[128];
Format(sBuffer, sizeof(sBuffer), "Disabled%s", (g_iHideRange[client] == DISABLED) ? " [Selected]" : "");
menu.AddItem("", sBuffer, (g_iHideRange[client] == DISABLED));
Format(sBuffer, sizeof(sBuffer), "Short Range%s", (g_iHideRange[client] == SHORT) ? " [Selected]" : "");
menu.AddItem("", sBuffer, (g_iHideRange[client] == SHORT));
Format(sBuffer, sizeof(sBuffer), "Medium Range%s", (g_iHideRange[client] == MEDIUM) ? " [Selected]" : "");
menu.AddItem("", sBuffer, (g_iHideRange[client] == MEDIUM));
Format(sBuffer, sizeof(sBuffer), "Long Range%s", (g_iHideRange[client] == LONG) ? " [Selected]" : "");
menu.AddItem("", sBuffer, (g_iHideRange[client] == LONG));
if(!g_hCVar_HideEnabled.BoolValue)
menu.AddItem("", "Warning: Hide is currently disabled on the server", ITEMDRAW_DISABLED);
menu.ExitBackButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int selection)
{
switch(action)
{
case(MenuAction_Select):
{
switch(selection)
{
case(0):
{
g_iHideRange[client] = DISABLED
}
case(1):
{
g_iHideRange[client] = SHORT
}
case(2):
{
g_iHideRange[client] = MEDIUM
}
case(3):
{
g_iHideRange[client] = LONG
}
}
char sBuffer[16];
Format(sBuffer, sizeof(sBuffer), "%d", g_iHideRange[client]);
SetClientCookie(client, g_hCookie_HideRange, sBuffer);
ShowSettingsMenu(client);
}
case(MenuAction_Cancel):
{
ShowCookieMenu(client);
}
case(MenuAction_End):
{
delete menu;
}
}
return 0;
}
stock int FindTierForCommandOverride(const char[] command)
{
char sPath[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sPath, sizeof(sPath), "configs/admin_groups.cfg");
KeyValues kv = new KeyValues("Groups");
if (!kv.ImportFromFile(sPath))
{
delete kv;
return -1;
}
// collect tier group names first
char groupNames[32][64];
int groupCount = 0;
if (kv.GotoFirstSubKey())
{
do
{
char sName[64];
kv.GetSectionName(sName, sizeof(sName));
if (StrContains(sName, "tier", false) == 0)
{
strcopy(groupNames[groupCount], 64, sName);
groupCount++;
}
}
while (kv.GotoNextKey() && groupCount < 32);
}
int foundTier = -1;
for (int i = 0; i < groupCount; i++)
{
kv.Rewind();
if (!kv.JumpToKey(groupNames[i]))
continue;
if (!kv.JumpToKey("Overrides"))
continue;
if (!kv.GotoFirstSubKey(false))
continue;
do
{
char overrideCmd[64];
kv.GetSectionName(overrideCmd, sizeof(overrideCmd));
if (StrEqual(overrideCmd, command, false))
{
char tierStr[8];
strcopy(tierStr, sizeof(tierStr), groupNames[i][4]); // skip "tier"
foundTier = StringToInt(tierStr);
break;
}
}
while (kv.GotoNextKey(false));
if (foundTier != -1)
break;
}
delete kv;
return foundTier;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void MenuHandler_CookieMenu(int client, CookieMenuAction action, any info, char[] buffer, int maxlen)
{
int client_tier = GetPlayerTier_native(client);
if (g_iTierHide > client_tier)
{
return;
}
switch(action)
{
case(CookieMenuAction_DisplayOption):
{
Format(buffer, maxlen, "Hide", client);
}
case(CookieMenuAction_SelectOption):
{
ShowSettingsMenu(client);
}
}
}
public void OnClientDisconnect(int client)
{
g_iHideRange[client] = 0;
g_bHasSomebodyToHide[client] = false;
g_bHasTobeVisible[client] = false;
for(int target = 1; target <= MaxClients; target++)
{
g_bHidePlayers[client][target] = false;
}
}
public void OnClientPutInServer(int client)
{
if(!g_hCVar_HideEnabled.BoolValue)
return;
g_bHasTobeVisible[client] = false;
if (!IsClientSourceTV(client))
{
TransmitManager_AddEntityHooks(client);
}
}
public void OnClientCookiesCached(int client)
{
char sBuffer[16];
GetClientCookie(client, g_hCookie_HideRange, sBuffer, sizeof(sBuffer));
if (sBuffer[0])
{
g_iHideRange[client] = StringToInt(sBuffer);
if (g_iHideRange[client] != SHORT && g_iHideRange[client] != MEDIUM && g_iHideRange[client] != LONG)
g_iHideRange[client] = DISABLED;
}
else
g_iHideRange[client] = DISABLED;
}
stock bool IsValidClient(int client)
{
if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
return true;
return false;
}
@@ -101,7 +101,7 @@ public void OnPluginStart()
if (SayText2 == INVALID_MESSAGE_ID) if (SayText2 == INVALID_MESSAGE_ID)
{ {
SetFailState("This game doesn't support SayText2 user messages."); SetFailState("This game doesnt support SayText2 user messages.");
} }
HookUserMessage(SayText2, Hook_UserMessage, true); HookUserMessage(SayText2, Hook_UserMessage, true);
@@ -333,11 +333,11 @@ bool ForceColor(int client, char Key[64])
SetColor(SID, Key, sCol, -1, true); SetColor(SID, Key, sCol, -1, true);
if (!strcmp(Key, "namecolor")) if (!strcmp(Key, "namecolor"))
CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%N's{default} name color to: \x07%s#%s{default}!", iTarget, sCol[1], sCol[1]); CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%Ns{default} name color to: \x07%s#%s{default}!", iTarget, sCol[1], sCol[1]);
else if (!strcmp(Key, "tagcolor")) else if (!strcmp(Key, "tagcolor"))
CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%N's{default} tag color to: \x07%s#%s{default}!", iTarget, sCol[1], sCol[1]); CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%Ns{default} tag color to: \x07%s#%s{default}!", iTarget, sCol[1], sCol[1]);
else else
CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%N's{default} text color to: \x07%s#%s{default}!", iTarget, sCol[1], sCol[1]); CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%Ns{default} text color to: \x07%s#%s{default}!", iTarget, sCol[1], sCol[1]);
} }
else else
{ {
@@ -2543,7 +2543,7 @@ public void OnClientPostAdminCheck(int client)
Call_Finish(); Call_Finish();
} }
public Action Hook_UserMessage(UserMsg msg_id, Handle bf, const players[], int playersNum, bool reliable, bool init) public Action Hook_UserMessage(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init)
{ {
char sAuthorTag[64]; char sAuthorTag[64];
g_msgAuthor = BfReadByte(bf); g_msgAuthor = BfReadByte(bf);
@@ -2701,11 +2701,11 @@ public Action Event_PlayerSay(Handle event, const char[] name, bool dontBroadcas
{ {
if (g_msgAuthor == -1 || GetClientOfUserId(GetEventInt(event, "userid")) != g_msgAuthor) if (g_msgAuthor == -1 || GetClientOfUserId(GetEventInt(event, "userid")) != g_msgAuthor)
{ {
return; return Plugin_Handled;
} }
if (strlen(g_msgText) == 0) if (strlen(g_msgText) == 0)
return; return Plugin_Handled;
int[] players = new int[MaxClients + 1]; int[] players = new int[MaxClients + 1];
int playersNum = 0; int playersNum = 0;
@@ -2718,7 +2718,7 @@ public Action Event_PlayerSay(Handle event, const char[] name, bool dontBroadcas
{ {
if (IsClientInGame(client) && GetClientTeam(client) == team) if (IsClientInGame(client) && GetClientTeam(client) == team)
{ {
if(!g_Ignored[client * (MAXPLAYERS + 1) + g_msgAuthor] && MessageForward(client, g_msgAuthor, g_msgText)) if(!g_Ignored[client * (MAXPLAYERS + 1) + g_msgAuthor] && MessageForward(client, g_msgAuthor, g_msgText) )
players[playersNum++] = client; players[playersNum++] = client;
} }
} }
@@ -2729,7 +2729,7 @@ public Action Event_PlayerSay(Handle event, const char[] name, bool dontBroadcas
{ {
if (IsClientInGame(client)) if (IsClientInGame(client))
{ {
if(!g_Ignored[client * (MAXPLAYERS + 1) + g_msgAuthor] && MessageForward(client, g_msgAuthor, g_msgText)) if(!g_Ignored[client * (MAXPLAYERS + 1) + g_msgAuthor] && MessageForward(client, g_msgAuthor, g_msgText) )
players[playersNum++] = client; players[playersNum++] = client;
} }
} }
@@ -2738,7 +2738,7 @@ public Action Event_PlayerSay(Handle event, const char[] name, bool dontBroadcas
if (!playersNum) if (!playersNum)
{ {
g_msgAuthor = -1; g_msgAuthor = -1;
return; return Plugin_Handled;
} }
Handle SayText2 = StartMessage("SayText2", players, playersNum, USERMSG_RELIABLE | USERMSG_BLOCKHOOKS); Handle SayText2 = StartMessage("SayText2", players, playersNum, USERMSG_RELIABLE | USERMSG_BLOCKHOOKS);
@@ -2759,6 +2759,7 @@ public Action Event_PlayerSay(Handle event, const char[] name, bool dontBroadcas
} }
g_msgAuthor = -1; g_msgAuthor = -1;
return Plugin_Handled;
} }
// 888b 888 d8888 88888888888 8888888 888 888 8888888888 .d8888b. // 888b 888 d8888 88888888888 8888888 888 888 8888888888 .d8888b.
@@ -0,0 +1,800 @@
//====================================================================================================
//
// Name: [entWatch] Restrictions
// Author: zaCade & Prometheum
// Description: Handle the restrictions of [entWatch]
//
//====================================================================================================
#include <multicolors>
#pragma newdecls required
#include <sdktools>
#include <sourcemod>
#include <clientprefs>
#include <entWatch_core>
#include <entWatch_helpers>
//2026: add playtime requirements for being unrestricted
#include <unloze_playtime>
Database g_hDatabase;
/* FORWARDS */
Handle g_hFwd_OnClientRestricted;
Handle g_hFwd_OnClientUnrestricted;
/* COOKIES */
Handle g_hCookie_RestrictIssued;
Handle g_hCookie_RestrictIssued_minutes;
Handle g_hCookie_RestrictExpire;
Handle g_hCookie_RestrictExpire_minutes;
Handle g_hCookie_RestrictLength;
Handle g_hCookie_RestrictLength_minutes;
/* BOOLEANS */
bool g_bRestrictedTemp[MAXPLAYERS+1];
bool g_bDontSpamMsg[MAXPLAYERS+1];
bool g_bActiveEban[MAXPLAYERS+1];
/* INTERGERS */
int g_iRestrictIssued[MAXPLAYERS+1];
int g_iRestrictIssued_minutes[MAXPLAYERS+1];
int g_iRestrictLength[MAXPLAYERS+1];
int g_iRestrictLength_minutes[MAXPLAYERS+1];
int g_iRestrictExpire[MAXPLAYERS+1];
int g_iRestrictExpire_minutes[MAXPLAYERS+1];
int g_iClientHours[MAXPLAYERS + 1];
int g_iClientMinutes[MAXPLAYERS + 1];
/* STRINGMAPS */
StringMap g_hTrie_Storage;
/* CONVARS */
ConVar g_cvarItemTierRequirement = null;
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Plugin myinfo =
{
name = "[entWatch] Restrictions",
author = "zaCade & Prometheum",
description = "Handle the restrictions of [entWatch]",
version = "4.0.1"
};
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] sError, int errorSize)
{
CreateNative("EW_ClientRestrict", Native_ClientRestrict);
CreateNative("EW_ClientUnrestrict", Native_ClientUnrestrict);
CreateNative("EW_ClientRestricted", Native_ClientRestricted);
RegPluginLibrary("entWatch-restrictions");
return APLRes_Success;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
LoadTranslations("common.phrases");
LoadTranslations("entWatch.restrictions.phrases");
g_hFwd_OnClientRestricted = CreateGlobalForward("EW_OnClientRestricted", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
g_hFwd_OnClientUnrestricted = CreateGlobalForward("EW_OnClientUnrestricted", ET_Ignore, Param_Cell, Param_Cell);
g_hCookie_RestrictIssued = RegClientCookie("EW_RestrictIssued", "", CookieAccess_Private);
g_hCookie_RestrictIssued_minutes = RegClientCookie("EW_RestrictIssued_minutes", "", CookieAccess_Private);
g_hCookie_RestrictExpire = RegClientCookie("EW_RestrictExpire", "", CookieAccess_Private);
g_hCookie_RestrictExpire_minutes = RegClientCookie("EW_RestrictExpire_minutes", "", CookieAccess_Private);
g_hCookie_RestrictLength = RegClientCookie("EW_RestrictLength", "", CookieAccess_Private);
g_hCookie_RestrictLength_minutes = RegClientCookie("EW_RestrictLength_minutes", "", CookieAccess_Private);
g_hTrie_Storage = new StringMap();
RegAdminCmd("sm_eban", Command_ClientRestrict, ADMFLAG_BAN);
RegAdminCmd("sm_eunban", Command_ClientUnrestrict, ADMFLAG_UNBAN);
RegConsoleCmd("sm_restrictions", Command_DisplayRestrictions);
RegConsoleCmd("sm_ebanlist", Command_DisplayRestrictions);
RegConsoleCmd("sm_status", Command_DisplayStatus);
for (int client = 1; client <= MaxClients; client++)
{
if (IsClientConnected(client))
OnClientPutInServer(client);
if (AreClientCookiesCached(client))
OnClientCookiesCached(client);
}
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_entwatch_web");
}
CreateTimer(5.0, CheckIfEntwatchBanExpired, _, TIMER_REPEAT);
g_cvarItemTierRequirement = CreateConVar("sm_entwatch_tier_requirement", "1", "Using the unloze play time plugin to set a tier for allowing players access to items.");
}
//could probably fail at detecting when a client finally gets entwatch unbanned but risk is pretty small as they need the ingame playtime to accomplish it.
public Action CheckIfEntwatchBanExpired(Handle timer)
{
for (int client = 1; client <= MaxClients; client++)
{
if (Client_IsValid(client) && IsClientInGame(client))
{
//we only need to check for bans with durations. temporary and permanent bans dont need to be updated as expired obviusly
if (g_iRestrictIssued[client] && g_iRestrictExpire[client] >= g_iClientHours[client] && g_iClientHours[client] > 0) //assuring playtime is set for client
{
//example: client restricted until 1335 hours and currently has 1320
if (g_iRestrictExpire[client] > g_iClientHours[client])
{
g_bActiveEban[client] = true;
continue;
}
//example client restricted until 1335 hours and 45 minutes. currently has 1335 hours and 20 minutes.
if (g_iRestrictExpire_minutes[client] > g_iClientMinutes[client])
{
g_bActiveEban[client] = true;
continue;
}
if (g_bActiveEban[client])
{
g_bActiveEban[client] = false;
update_entwatchban_mysql_is_expired(client);
}
}
}
}
return Plugin_Continue;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnMapStart()
{
g_hTrie_Storage.Clear();
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_entwatch_web");
}
}
public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
{
if(!db || strlen(error))
{
LogError("Database error: %s", error);
return;
}
g_hDatabase = db;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientPutInServer(int client)
{
char sAddress[32];
GetClientIP(client, sAddress, sizeof(sAddress));
g_bDontSpamMsg[client] = false;
g_bActiveEban[client] = false;
bool bRestrictedTemp;
if (g_hTrie_Storage.GetValue(sAddress, bRestrictedTemp))
{
g_bRestrictedTemp[client] = bRestrictedTemp;
}
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientCookiesCached(int client)
{
g_iRestrictIssued[client] = GetClientCookieInt(client, g_hCookie_RestrictIssued);
g_iRestrictIssued_minutes[client] = GetClientCookieInt(client, g_hCookie_RestrictIssued_minutes);
g_iRestrictExpire[client] = GetClientCookieInt(client, g_hCookie_RestrictExpire);
g_iRestrictExpire_minutes[client] = GetClientCookieInt(client, g_hCookie_RestrictExpire_minutes);
g_iRestrictLength[client] = GetClientCookieInt(client, g_hCookie_RestrictLength);
g_iRestrictLength_minutes[client] = GetClientCookieInt(client, g_hCookie_RestrictLength_minutes);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientDisconnect(int client)
{
if (g_bRestrictedTemp[client])
{
char sAddress[32];
GetClientIP(client, sAddress, sizeof(sAddress));
g_hTrie_Storage.SetArray(sAddress, g_bRestrictedTemp[client], true);
}
g_bDontSpamMsg[client] = false;
g_bRestrictedTemp[client] = false;
g_bActiveEban[client] = false;
g_iRestrictIssued[client] = 0;
g_iRestrictIssued_minutes[client] = 0;
g_iRestrictExpire[client] = 0;
g_iRestrictExpire_minutes[client] = 0;
g_iRestrictLength[client] = 0;
g_iRestrictLength_minutes[client] = 0;
g_iClientHours[client] = 0;
g_iClientMinutes[client] = 0;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_ClientRestrict(int client, int args)
{
if (!GetCmdArgs())
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%sUsage: sm_eban <#userid/name> [duration]", "E01B5D", "F16767");
return Plugin_Handled;
}
char sArguments[2][32];
GetCmdArg(1, sArguments[0], sizeof(sArguments[]));
GetCmdArg(2, sArguments[1], sizeof(sArguments[]));
int target;
if ((target = FindTarget(client, sArguments[0], true)) == -1)
return Plugin_Handled;
if (GetCmdArgs() >= 2)
{
int length = StringToInt(sArguments[1]);
if (ClientRestrict(client, target, length))
{
if (length == -1)
{
CPrintToChatAll("\x07%s[entWatch] \x07%s%N\x07%s restricted \x07%s%N\x07%s temporarily.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767");
LogAction(client, target, "%L restricted %L temporarily.", client, target);
}
else if (length > 0)
{
int hours = length / 60;
int minutes = length % 60;
CPrintToChatAll("\x07%s[entWatch] \x07%s%N\x07%s restricted \x07%s%N\x07%s for \x07%s%d\x07%s hours and \x07%s%d\x07%s minutes Playtime.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767", "EDEDED", hours, "F16767", "EDEDED", minutes, "F16767");
LogAction(client, target, "%L restricted %L for %d hours and %d minutes playtime.", client, target, hours, minutes);
}
else
{
CPrintToChatAll("\x07%s[entWatch] \x07%s%N\x07%s restricted \x07%s%N\x07%s permanently.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767");
LogAction(client, target, "%L restricted %L permanently.", client, target);
}
insert_entwatchban_mysql(client, target);
}
}
else
{
if (ClientRestrict(client, target, -1))
{
CPrintToChatAll("\x07%s[entWatch] \x07%s%N\x07%s restricted \x07%s%N\x07%s temporarily.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767");
LogAction(client, target, "%L restricted %L temporarily.", client, target);
}
}
return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_ClientUnrestrict(int client, int args)
{
if (!GetCmdArgs())
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%sUsage: sm_eunban <#userid/name>", "E01B5D", "F16767");
return Plugin_Handled;
}
char sArguments[1][32];
GetCmdArg(1, sArguments[0], sizeof(sArguments[]));
int target;
if ((target = FindTarget(client, sArguments[0], true)) == -1)
return Plugin_Handled;
if (ClientUnrestrict(client, target))
{
CPrintToChatAll("\x07%s[entWatch] \x07%s%N\x07%s unrestricted \x07%s%N\x07%s.", "E01B5D", "EDEDED", client, "F16767", "EDEDED", target, "F16767");
LogAction(client, target, "%L unrestricted %L.", client, target);
}
return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_DisplayRestrictions(int client, int args)
{
char aBuf[1024];
char aBuf_time_requirement[1024];
char aBuf2[MAX_NAME_LENGTH];
char aBuf2_time_requirement[MAX_NAME_LENGTH];
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && !IsFakeClient(i))
{
if (ClientRestricted(i))
{
int client_tier = GetPlayerTier_native(i);
if (client_tier < g_cvarItemTierRequirement.IntValue)
{
GetClientName(i, aBuf2_time_requirement, sizeof(aBuf2_time_requirement));
StrCat(aBuf_time_requirement, sizeof(aBuf_time_requirement), aBuf2_time_requirement);
StrCat(aBuf_time_requirement, sizeof(aBuf_time_requirement), ", ");
}
else
{
GetClientName(i, aBuf2, sizeof(aBuf2));
StrCat(aBuf, sizeof(aBuf), aBuf2);
StrCat(aBuf, sizeof(aBuf), ", ");
}
}
}
}
if (strlen(aBuf))
{
aBuf[strlen(aBuf) - 2] = 0;
CReplyToCommand(client, "\x07%s[entWatch] \x07%sCurrently restricted clients: \x07%s%s", "E01B5D", "F16767", "EDEDED", aBuf);
}
else
CReplyToCommand(client, "\x07%s[entWatch] \x07%sCurrently restricted clients: \x07%snone", "E01B5D", "F16767", "EDEDED");
if (strlen(aBuf_time_requirement))
{
aBuf_time_requirement[strlen(aBuf_time_requirement) - 2] = 0;
CReplyToCommand(client, "\x07%s[entWatch] \x07%sCurrent clients with not high enough Tier: \x07%s%s", "E01B5D", "F16767", "EDEDED", aBuf_time_requirement);
}
else
CReplyToCommand(client, "\x07%s[entWatch] \x07%sCurrent clients with not high enough Tier: \x07%snone", "E01B5D", "F16767", "EDEDED");
return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_DisplayStatus(int client, int args)
{
if (CheckCommandAccess(client, "", ADMFLAG_BAN) && GetCmdArgs())
{
char sArguments[1][32];
GetCmdArg(1, sArguments[0], sizeof(sArguments[]));
int target;
if ((target = FindTarget(client, sArguments[0], true)) == -1)
return Plugin_Handled;
if (!AreClientCookiesCached(target))
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%s%N\x07%s their cookies are still loading.", "E01B5D", "EDEDED", target, "F16767");
return Plugin_Handled;
}
else if (g_bRestrictedTemp[target])
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%s%N\x07%s is currently temporarily restricted.", "E01B5D", "EDEDED", target, "F16767");
return Plugin_Handled;
}
else if (g_iRestrictIssued[target] && g_iRestrictExpire[target] == 0)
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%s%N\x07%s is currently permanently restricted.", "E01B5D", "EDEDED", target, "F16767");
return Plugin_Handled;
}
else if (g_iRestrictIssued[target] && g_iRestrictExpire[target] >= g_iClientHours[target])
{
//example: 1335 hours and 40 minutes restriction expires. player at 1335 hours and 50 minutes is no longer restricted.
if (g_iRestrictExpire[target] == g_iClientHours[target] && g_iRestrictExpire_minutes[target] < g_iClientMinutes[target])
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%s%N\x07%s is currently not restricted.", "E01B5D", "EDEDED", target, "F16767");
return Plugin_Handled;
}
//its just hours and minutes now
char sTimeRemaining[64];
int iTimeRemaining_hours = g_iRestrictExpire[target] - g_iClientHours[target];
int iTimeRemaining_minutes = g_iRestrictExpire_minutes[target] - g_iClientMinutes[target];
if (iTimeRemaining_minutes < 0)
{
iTimeRemaining_minutes += 60;
iTimeRemaining_hours--;
}
Format(sTimeRemaining, sizeof(sTimeRemaining), "%d Hours and %d minutes Playtime", iTimeRemaining_hours, iTimeRemaining_minutes);
CReplyToCommand(client, "\x07%s[entWatch] \x07%s%N\x07%s is currently restricted for another \x07%s%s\x07%s.", "E01B5D", "EDEDED", target, "F16767", "EDEDED", sTimeRemaining, "F16767");
return Plugin_Handled;
}
else
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%s%N\x07%s is currently not restricted.", "E01B5D", "EDEDED", target, "F16767");
return Plugin_Handled;
}
}
else
{
if (!AreClientCookiesCached(client))
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%sYour cookies are still loading.", "E01B5D", "F16767");
return Plugin_Handled;
}
else if (g_bRestrictedTemp[client])
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%sYou are currently temporarily restricted.", "E01B5D", "F16767");
return Plugin_Handled;
}
else if (g_iRestrictIssued[client] && g_iRestrictExpire[client] == 0)
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%sYou are currently permanently restricted.", "E01B5D", "F16767");
return Plugin_Handled;
}
else if (g_iRestrictIssued[client] && g_iRestrictExpire[client] >= g_iClientHours[client])
{
//example: 1335 hours and 40 minutes restriction expires. player at 1335 hours and 50 minutes is no longer restricted.
if (g_iRestrictExpire[client] == g_iClientHours[client] && g_iRestrictExpire_minutes[client] < g_iClientMinutes[client])
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%sYou are currently not restricted.", "E01B5D", "F16767");
return Plugin_Handled;
}
//its just hours and minutes now
char sTimeRemaining[64];
int iTimeRemaining_hours = g_iRestrictExpire[client] - g_iClientHours[client];
int iTimeRemaining_minutes = g_iRestrictExpire_minutes[client] - g_iClientMinutes[client];
if (iTimeRemaining_minutes < 0)
{
iTimeRemaining_minutes += 60;
iTimeRemaining_hours--;
}
Format(sTimeRemaining, sizeof(sTimeRemaining), "%d Hours and %d minutes Playtime", iTimeRemaining_hours, iTimeRemaining_minutes);
CReplyToCommand(client, "\x07%s[entWatch] \x07%sYou are currently restricted for another \x07%s%s\x07%s.", "E01B5D", "F16767", "EDEDED", sTimeRemaining, "F16767");
return Plugin_Handled;
}
else
{
CReplyToCommand(client, "\x07%s[entWatch] \x07%sYou are currently not restricted.", "E01B5D", "F16767");
return Plugin_Handled;
}
}
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action EW_OnClientItemCanPickup(int client, int index)
{
return ClientRestricted(client)?Plugin_Handled:Plugin_Continue;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action EW_OnClientItemCanActivate(int client, int index)
{
return ClientRestricted(client)?Plugin_Handled:Plugin_Continue;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock bool ClientRestrict(int client, int target, int length)
{
if (!Client_IsValid(client) || !Client_IsValid(target) || !AreClientCookiesCached(target) || ClientRestricted(target))
{
if (Client_IsValid(client) && Client_IsValid(target) && ClientRestricted(target))
{
CPrintToChat(client, "\x07%s[entWatch] \x07%s The player \x07%s%N\x07%s is already restricted.", "E01B5D", "EDEDED", "EDEDED", target, "F16767");
}
return false;
}
if (length == -1)
{
g_bRestrictedTemp[target] = true;
}
else if (length == 0)
{
//permanently
g_bRestrictedTemp[target] = false;
g_iRestrictIssued[target] = g_iClientHours[target];
g_iRestrictExpire[target] = 0;
g_iRestrictLength[target] = 0;
g_iRestrictLength_minutes[target] = 0;
SetClientCookieInt(target, g_hCookie_RestrictIssued, GetTime());
SetClientCookieInt(target, g_hCookie_RestrictExpire, 0);
SetClientCookieInt(target, g_hCookie_RestrictLength, 0);
}
else
{
//2026 edit. we now use ingame play time of the client instead of real time.
int hours_length = length / 60;
int minutes_length = length % 60;
g_bRestrictedTemp[target] = false;
g_iRestrictIssued[target] = g_iClientHours[target];
g_iRestrictIssued_minutes[target] = g_iClientMinutes[target];
g_iRestrictExpire[target] = g_iClientHours[target] + hours_length;
int rounded_minutes = g_iClientMinutes[target] + minutes_length;
if (rounded_minutes > 60)
{
rounded_minutes -= 60;
g_iRestrictExpire[target]++;
}
g_iRestrictExpire_minutes[target] = rounded_minutes;
g_iRestrictLength[target] = hours_length;
g_iRestrictLength_minutes[target] = minutes_length;
SetClientCookieInt(target, g_hCookie_RestrictIssued, g_iRestrictIssued[target]);
SetClientCookieInt(target, g_hCookie_RestrictIssued_minutes, g_iRestrictIssued_minutes[target]);
SetClientCookieInt(target, g_hCookie_RestrictExpire, g_iRestrictExpire[target]);
SetClientCookieInt(target, g_hCookie_RestrictExpire_minutes, g_iRestrictExpire_minutes[target]);
SetClientCookieInt(target, g_hCookie_RestrictLength, g_iRestrictLength[target]);
SetClientCookieInt(target, g_hCookie_RestrictLength_minutes, g_iRestrictLength_minutes[target]);
}
Call_StartForward(g_hFwd_OnClientRestricted);
Call_PushCell(client);
Call_PushCell(target);
if (length == -1 || length == 0)
{
//its temporarily or permanently
Call_PushCell(length);
Call_PushCell(0);
}
else
{
//its certain amount of hours and minutes
Call_PushCell(g_iRestrictLength[target]);
Call_PushCell(g_iRestrictLength_minutes[target]);
}
Call_Finish();
return true;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock bool ClientUnrestrict(int client, int target)
{
if (!Client_IsValid(client) || !Client_IsValid(target) || !AreClientCookiesCached(target) || !ClientRestricted(target))
return false;
g_bRestrictedTemp[target] = false;
g_iRestrictIssued[target] = 0;
g_iRestrictIssued_minutes[target] = 0;
g_iRestrictExpire[target] = 0;
g_iRestrictExpire_minutes[target] = 0;
g_iRestrictLength[target] = 0;
g_iRestrictLength_minutes[target] = 0;
SetClientCookieInt(target, g_hCookie_RestrictIssued, 0);
SetClientCookieInt(target, g_hCookie_RestrictIssued_minutes, 0);
SetClientCookieInt(target, g_hCookie_RestrictExpire, 0);
SetClientCookieInt(target, g_hCookie_RestrictExpire_minutes, 0);
SetClientCookieInt(target, g_hCookie_RestrictLength, 0);
SetClientCookieInt(target, g_hCookie_RestrictLength_minutes, 0);
update_entwatchban_mysql_is_expired(target);
Call_StartForward(g_hFwd_OnClientUnrestricted);
Call_PushCell(client);
Call_PushCell(target);
Call_Finish();
return true;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock bool ClientRestricted(int client)
{
if (!Client_IsValid(client))
return false;
//Block them when loading cookies..
if (!AreClientCookiesCached(client))
return true;
//Temporary restriction.
if (g_bRestrictedTemp[client])
return true;
//Permanent restriction.
if (g_iRestrictIssued[client] && g_iRestrictExpire[client] == 0)
return true;
//Normal restriction.
if (g_iRestrictIssued[client] && g_iRestrictExpire[client] >= g_iClientHours[client])
{
//example: client restricted until 1335 hours and currently has 1320
if (g_iRestrictExpire[client] > g_iClientHours[client])
{
return true;
}
//example client restricted until 1335 hours and 45 minutes. currently has 1335 hours and 20 minutes.
if (g_iRestrictExpire_minutes[client] > g_iClientMinutes[client])
{
return true;
}
}
//tier requirement for picking up items
int client_tier = GetPlayerTier_native(client);
if (client_tier < g_cvarItemTierRequirement.IntValue)
{
if (!g_bDontSpamMsg[client])
{
CPrintToChat(client, "\x07%s[entWatch] \x07%s You need to reach Tier \x07%s%i\x07%s for picking up items.", "E01B5D", "EDEDED", "EDEDED", g_cvarItemTierRequirement.IntValue, "EDEDED");
g_bDontSpamMsg[client] = true;
CreateTimer(1.0, allow_message_again, GetClientSerial(client));
}
return true;
}
return false;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int Native_ClientRestrict(Handle hPlugin, int numParams)
{
return ClientRestrict(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3));
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int Native_ClientUnrestrict(Handle hPlugin, int numParams)
{
return ClientUnrestrict(GetNativeCell(1), GetNativeCell(2));
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int Native_ClientRestricted(Handle hPlugin, int numParams)
{
return ClientRestricted(GetNativeCell(1));
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock void SetClientCookieInt(int client, Handle hCookie, int value)
{
char sValue[32];
IntToString(value, sValue, sizeof(sValue));
SetClientCookie(client, hCookie, sValue);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock int GetClientCookieInt(int client, Handle hCookie)
{
char sValue[32];
GetClientCookie(client, hCookie, sValue, sizeof(sValue));
return StringToInt(sValue);
}
//----------------------------------------------------------------------------------------------------
// Purpose: 2026 getting the ingame play time of the players. called with OnClientPostAdminCheck() forward and timer every 10 minutes
//----------------------------------------------------------------------------------------------------
public void GetPlayerHoursServer(int client, int hours, int minutes)
{
g_iClientHours[client] = hours;
g_iClientMinutes[client] = minutes;
}
public void update_entwatchban_mysql_is_expired(int entwatch_banned_player)
{
char sSIDClient[64];
GetClientAuthId(entwatch_banned_player, AuthId_Steam2, sSIDClient, sizeof(sSIDClient));
char sQuery[512];
Format(sQuery, sizeof(sQuery), "update `EntWatch_Current_Eban` set is_expired = 1 where client_steamid = '%s' and is_expired = 0", sSIDClient);
g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Normal);
}
public void insert_entwatchban_mysql(int admin, int entwatch_banned_player)
{
char sNameAdmin[MAX_NAME_LENGTH];
GetClientName(admin, sNameAdmin, sizeof(sNameAdmin));
int size2 = 2 * strlen(sNameAdmin) + 1;
char[] sEscapedNameAdmin = new char[size2 + 1];
g_hDatabase.Escape(sNameAdmin, sEscapedNameAdmin, size2 + 1);
char sSIDAdmin[64];
GetClientAuthId(admin, AuthId_Steam2, sSIDAdmin, sizeof(sSIDAdmin));
char sNameClient[MAX_NAME_LENGTH];
GetClientName(entwatch_banned_player, sNameClient, sizeof(sNameClient));
size2 = 2 * strlen(sNameClient) + 1;
char[] sEscapedNameClient = new char[size2 + 1];
g_hDatabase.Escape(sNameClient, sEscapedNameClient, size2 + 1);
char sSIDClient[64];
GetClientAuthId(entwatch_banned_player, AuthId_Steam2, sSIDClient, sizeof(sSIDClient));
int playtime_start_client = (g_iRestrictIssued[entwatch_banned_player] * 60) + g_iRestrictIssued_minutes[entwatch_banned_player];
int playtime_end_client = (g_iRestrictExpire[entwatch_banned_player] * 60) + g_iRestrictExpire_minutes[entwatch_banned_player];
int length = (g_iRestrictLength[entwatch_banned_player] * 60) + g_iRestrictLength_minutes[entwatch_banned_player];
char sQuery[512];
Format(sQuery, sizeof(sQuery), "INSERT INTO `EntWatch_Current_Eban` (`client_name`, `client_steamid`, `admin_name`, `admin_steamid`, `duration`, `reason`, `time_played_start`, `time_played_end`) VALUES ('%s', '%s', '%s', '%s', '%i', '%s', '%i', '%i')", sEscapedNameClient, sSIDClient, sEscapedNameAdmin, sSIDAdmin, length, "get fucked", playtime_start_client, playtime_end_client);
g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Normal);
}
public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, any data)
{
if (!db || strlen(error))
{
LogError("Query error 3: %s", error);
}
delete results;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action allow_message_again(Handle hTimer, int Serial)
{
int client;
if ((client = GetClientFromSerial(Serial)) == 0)
{
return Plugin_Handled;
}
if (Client_IsValid(client))
{
g_bDontSpamMsg[client] = false;
}
return Plugin_Handled;
}
+203
View File
@@ -0,0 +1,203 @@
//====================================================================================================
//
// Name: [entWatch] UseAnywhere
// Author: marisakirisame.net
// Description: Allow the owner of an item to activate it with their USE key anywhere
//
//====================================================================================================
#include <multicolors>
#pragma newdecls required
#include <sourcemod>
#include <sdkhooks>
#include <sdktools>
#include <clientprefs>
#include <entWatch_core>
#include <entWatch_helpers>
/* BOOLS */
bool g_bDisabled[MAXPLAYERS+1];
/* COOKIES */
Handle g_hCookie_Disabled;
/* INTEGERS */
int g_iLastButtons[MAXPLAYERS+1];
int g_iActivateTick[MAXPLAYERS+1];
int g_iActivateButton[MAXPLAYERS+1];
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Plugin myinfo =
{
name = "[entWatch] UseAnywhere",
author = "marisakirisame.net",
description = "Allow the owner of an item to activate it with their USE key anywhere",
version = "4.0.0"
};
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
g_hCookie_Disabled = RegClientCookie("EW_UseAnywhereDisabled", "", CookieAccess_Private);
RegConsoleCmd("sm_euseanywhere", Command_ToggleUseAnywhere);
for (int client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client) || IsFakeClient(client))
continue;
OnClientPutInServer(client);
if (AreClientCookiesCached(client))
OnClientCookiesCached(client);
}
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientPutInServer(int client)
{
g_bDisabled[client] = false;
g_iLastButtons[client] = 0;
g_iActivateTick[client] = 0;
g_iActivateButton[client] = INVALID_ENT_REFERENCE;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientCookiesCached(int client)
{
g_bDisabled[client] = GetClientCookieBool(client, g_hCookie_Disabled);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action Command_ToggleUseAnywhere(int client, int args)
{
if (!Client_IsValid(client))
return Plugin_Handled;
g_bDisabled[client] = !g_bDisabled[client];
if (g_bDisabled[client])
{
SetClientCookieBool(client, g_hCookie_Disabled, true);
CReplyToCommand(client, "\x07%s[entWatch] \x07%sYou will now have to aim at the item's trigger to use it.", "E01B5D", "F16767");
return Plugin_Handled;
}
else
{
SetClientCookieBool(client, g_hCookie_Disabled, false);
CReplyToCommand(client, "\x07%s[entWatch] \x07%sYou can now use your item by pressing your USE key anywhere.", "E01B5D", "F16767");
return Plugin_Handled;
}
}
//----------------------------------------------------------------------------------------------------
// Purpose: Activate the clients item on +use, without having to aim at its button.
//----------------------------------------------------------------------------------------------------
public Action OnPlayerRunCmd(int client, int &buttons)
{
bool bPressed = view_as<bool>((buttons & IN_USE) && !(g_iLastButtons[client] & IN_USE));
g_iLastButtons[client] = buttons;
if (!bPressed || g_bDisabled[client] || !IsPlayerAlive(client))
return Plugin_Continue;
int button = FindClientItemButton(client);
if (button == INVALID_ENT_REFERENCE)
return Plugin_Continue;
// Fires the buttons Use input, which runs the core's SDKHook_Use handler as if the
// client had aimed at it, so every cooldown, restriction and message is handled there.
AcceptEntityInput(button, "Use", client, client);
// Set after the input, so the press above is not blocked by its own guard.
g_iActivateTick[client] = GetGameTickCount();
g_iActivateButton[client] = button;
return Plugin_Continue;
}
//----------------------------------------------------------------------------------------------------
// Purpose: Ignore the engines own +use if we already fired this button for the client this tick,
// so aiming at the button while pressing USE cannot activate the item twice.
//----------------------------------------------------------------------------------------------------
public Action EW_OnClientItemCanActivate(int client, int index)
{
if (g_iActivateTick[client] != GetGameTickCount())
return Plugin_Continue;
CItem item = EW_GetItemData(index);
if (item.bButton && item.iButton == g_iActivateButton[client])
return Plugin_Handled;
return Plugin_Continue;
}
//----------------------------------------------------------------------------------------------------
// Purpose: Get the button of the clients item, but only if they carry exactly one.
//----------------------------------------------------------------------------------------------------
stock int FindClientItemButton(int client)
{
int iFound = -1;
for (int index; index < EW_GetItemCount(); index++)
{
CItem item = EW_GetItemData(index);
if (!(item.bClient && item.iClient == client))
continue;
if (iFound != -1)
return INVALID_ENT_REFERENCE;
iFound = index;
}
if (iFound != -1)
{
CItem itemFound = EW_GetItemData(iFound);
if (itemFound.bButton && Entity_IsValid(itemFound.iButton))
return itemFound.iButton;
}
return INVALID_ENT_REFERENCE;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock void SetClientCookieBool(int client, Handle hCookie, bool value)
{
char sValue[32];
IntToString(view_as<int>(value), sValue, sizeof(sValue));
SetClientCookie(client, hCookie, sValue);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock bool GetClientCookieBool(int client, Handle hCookie)
{
char sValue[32];
GetClientCookie(client, hCookie, sValue, sizeof(sValue));
return view_as<bool>(StringToInt(sValue));
}
@@ -0,0 +1,33 @@
#if defined entWatch_restrictions_included
#endinput
#endif
#define entWatch_restrictions_included
public SharedPlugin __pl_entWatch_restrictions =
{
name = "entWatch-restrictions",
file = "entWatch-restrictions.smx",
#if defined REQUIRE_PLUGIN
required = 1
#else
required = 0
#endif
};
#if !defined REQUIRE_PLUGIN
public void __pl_entWatch_restrictions_SetNTVOptional()
{
MarkNativeAsOptional("EW_ClientRestrict");
MarkNativeAsOptional("EW_ClientUnrestrict");
MarkNativeAsOptional("EW_ClientRestricted");
}
#endif
native bool EW_ClientRestrict(int client, int target, int length);
native bool EW_ClientUnrestrict(int client, int target);
native bool EW_ClientRestricted(int client);
forward void EW_OnClientRestricted(int client, int target, int hours, int minutes);
forward void EW_OnClientUnrestricted(int client, int target);
+38
View File
@@ -0,0 +1,38 @@
-- entwatchban_web.EntWatch_Current_Eban definition
CREATE TABLE `EntWatch_Current_Eban` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`client_name` varchar(32) NOT NULL,
`client_steamid` varchar(64) NOT NULL,
`admin_name` varchar(32) NOT NULL,
`admin_steamid` varchar(64) NOT NULL,
`duration` int(10) unsigned NOT NULL,
`time_stamp_start` datetime DEFAULT current_timestamp(),
`reason` varchar(64) DEFAULT NULL,
`reason_unban` varchar(64) DEFAULT NULL,
`admin_name_unban` varchar(32) DEFAULT NULL,
`admin_steamid_unban` varchar(64) DEFAULT NULL,
`timestamp_unban` int(11) DEFAULT NULL,
`time_played_start` int(11) NOT NULL DEFAULT -1,
`time_played_end` int(11) NOT NULL DEFAULT -1,
`is_expired` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_steamid_search` (`client_steamid`,`admin_steamid`),
KEY `idx_expiry_sort` (`time_stamp_start`,`duration`),
KEY `idx_server_expiry` (`duration`,`time_stamp_start`),
KEY `idx_is_expired` (`is_expired`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- entwatchban_web.web_logs definition
CREATE TABLE `web_logs` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`message` varchar(255) NOT NULL,
`admin_name` varchar(32) NOT NULL,
`admin_steamid` varchar(64) NOT NULL,
`client_name` varchar(32) NOT NULL,
`client_steamid` varchar(64) NOT NULL,
`time_stamp` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
@@ -0,0 +1,33 @@
"Games"
{
"cstrike"
{
"Signatures"
{
"CBasePlayer::InputSetFogController"
{
"library" "server"
"windows" "\x55\x8B\xEC\x56\x8B\xF1\x8B\x4D\x08\x83\xC1\x08\x83\x79\x10\x02\x75\x2A\x8B\x01\xBA\x2A\x2A\x2A\x2A\x85\xC0\x0F\x45\xD0\xEB\x2A\xE8\x2A\x2A\x2A\x2A\x8B\xD0\x6A\x00"
"linux" "@_ZN11CBasePlayer21InputSetFogControllerER11inputdata_t"
}
}
"Functions"
{
"CBasePlayer::InputSetFogController"
{
"signature" "CBasePlayer::InputSetFogController"
"callconv" "thiscall"
"return" "void"
"this" "entity"
"arguments"
{
"inputdata"
{
"type" "objectptr"
}
}
}
}
}
}
@@ -0,0 +1,278 @@
#include <sourcemod>
#include <sdktools>
#include <dhooks>
public Plugin myinfo =
{
name = "Render Distance Limiter",
author = "Dysphie, repurposed for unloze webclient using some AI",
description = "",
version = "",
url = ""
};
int g_iClientFogEntity[MAXPLAYERS + 1] = { INVALID_ENT_REFERENCE, ... };
float g_OverrideDist[MAXPLAYERS + 1] = { -1.0, ... };
bool bEnabled[MAXPLAYERS + 1];
float g_fClientEstimatedFPS[MAXPLAYERS + 1];
// New tracking arrays for your global variables section
float g_fLastStableDist[MAXPLAYERS + 1] = { -1.0, ... };
float g_fDistCooldown[MAXPLAYERS + 1] = { 0.0, ... };
int g_iLastCmdNum[MAXPLAYERS + 1];
float g_fFPSIntervalStart[MAXPLAYERS + 1];
int g_iCmdsInInterval[MAXPLAYERS + 1];
public void OnPluginStart()
{
SetupDetours();
CreateTimer(0.5, Timer_printFPS, _, TIMER_REPEAT);
}
//ai generated
public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2])
{
if (IsFakeClient(client) || !bEnabled[client])
return Plugin_Continue;
// Check if this is a brand new user command execution frame
if (cmdnum != g_iLastCmdNum[client])
{
g_iLastCmdNum[client] = cmdnum;
g_iCmdsInInterval[client]++;
}
float fCurrentTime = GetEngineTime();
float fTimeElapsed = fCurrentTime - g_fFPSIntervalStart[client];
// Evaluate the true input frequency every 0.5 seconds
if (fTimeElapsed >= 0.5)
{
if (g_fFPSIntervalStart[client] > 0.0)
{
// Raw calculated input framerate based on sequential command progression
float fCalculatedFPS = float(g_iCmdsInInterval[client]) / fTimeElapsed;
// FAST-DROP BYPASS: If the client just lost more than 25% of their frame rate instantly
// skip the dampening math and force the estimation down immediately.
if (fCalculatedFPS < (g_fClientEstimatedFPS[client] * 0.75))
{
g_fClientEstimatedFPS[client] = fCalculatedFPS; // Hard-drop the value
}
else
{
// Smoothly recover/climb when frame rates are stable or increasing
g_fClientEstimatedFPS[client] = (g_fClientEstimatedFPS[client] * 0.8) + (fCalculatedFPS * 0.2);
}
}
// Reset interval buckets
g_fFPSIntervalStart[client] = fCurrentTime;
g_iCmdsInInterval[client] = 0;
}
return Plugin_Continue;
}
//ai generated
public Action Timer_printFPS(Handle timer)
{
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i) && bEnabled[i])
{
float fCurrentFPS = g_fClientEstimatedFPS[i] - 4.0;
float currentDist = g_OverrideDist[i];
float targetDist = currentDist;
// 1. HARD CRASH: FPS has tanked down to unplayable levels.
if (fCurrentFPS <= 25.0)
{
// If we have a cached, proven stable distance, use it immediately!
if (g_fLastStableDist[i] != -1.0 && g_fLastStableDist[i] < currentDist)
{
targetDist = g_fLastStableDist[i];
}
else
{
// Emergency fallback steps if we dont have a cached history yet
if (currentDist == -1.0 || currentDist > 1200.0)
{
targetDist = 1200.0;
}
else
{
targetDist = 600.0;
}
}
// Penalize the cooldown heavily because we just choked on rendering
g_fDistCooldown[i] = GetEngineTime() + 15.0;
}
// 2. WARNING ZONE: Framerate is beginning to dip
else if (fCurrentFPS <= 38.0)
{
if (currentDist == -1.0 || currentDist > 2000.0)
{
targetDist = 2000.0;
g_fDistCooldown[i] = GetEngineTime() + 10.0;
}
}
// 3. RECOVERY ZONE: Performance is solid. Lets see if we can open up the view.
else if (fCurrentFPS >= 48.0)
{
if (currentDist != -1.0)
{
// Since the game is running flawlessly at this specific distance,
// we update our safety cache to remember this specific float.
g_fLastStableDist[i] = currentDist;
// Only venture out if our time barrier has expired
if (GetEngineTime() >= g_fDistCooldown[i])
{
if (currentDist == 600.0) targetDist = 1200.0;
else if (currentDist == 1200.0) targetDist = 2000.0;
else if (currentDist == 2000.0) targetDist = 4000.0;
else if (currentDist == 4000.0) targetDist = -1.0;
// Give the client a test window to sample the new distance
g_fDistCooldown[i] = GetEngineTime() + 8.0;
}
}
else
{
// If the client is running fine at infinite distance (-1.0),
// clear the history out so we can recalculate from a clean slate later
g_fLastStableDist[i] = -1.0;
}
}
if (targetDist != currentDist)
{
ApplyFogValues(i, targetDist);
}
}
}
return Plugin_Continue;
}
void ApplyFogValues(int client, float dist)
{
g_OverrideDist[client] = dist;
int fog = EntRefToEntIndex(g_iClientFogEntity[client]);
// Create the persistent entity once if it vanished or hasnt been created yet
if (fog == -1 || !IsValidEntity(fog))
{
fog = CreateEntityByName("env_fog_controller");
if (fog != -1)
{
DispatchSpawn(fog);
g_iClientFogEntity[client] = EntIndexToEntRef(fog);
}
}
if (fog != -1 && IsValidEntity(fog))
{
if (dist == -1.0)
{
// Back to map defaults safely
SetEntProp(fog, Prop_Send, "m_fog.enable", 0);
SetEntPropFloat(fog, Prop_Send, "m_fog.farz", 0.0);
}
else
{
// Apply strict culling boundaries explicitly
SetEntProp(fog, Prop_Send, "m_fog.enable", 1);
SetEntPropFloat(fog, Prop_Send, "m_fog.farz", dist);
SetEntPropFloat(fog, Prop_Send, "m_fog.end", dist);
SetEntPropFloat(fog, Prop_Send, "m_fog.maxdensity", 1.0);
}
// Keep the entity assigned cleanly inside data tables
SetFogController(client, fog);
// Single fire state changes to replicate out smoothly
ChangeEdictState(client, FindSendPropInfo("CBasePlayer", "m_hCtrl"));
}
}
public void OnClientDisconnect(int client)
{
// Clean up entity allocation to prevent lingering map errors
int fog = EntRefToEntIndex(g_iClientFogEntity[client]);
if (fog != -1 && IsValidEntity(fog))
{
RemoveEntity(fog);
}
g_iClientFogEntity[client] = INVALID_ENT_REFERENCE;
g_OverrideDist[client] = -1.0;
bEnabled[client] = false;
}
void SetupDetours()
{
GameData gamedata = new GameData("player-fog.games");
if (!gamedata) {
SetFailState("Missing gamedata file");
}
DynamicDetour detour = DynamicDetour.FromConf(gamedata, "CBasePlayer::InputSetFogController");
if (!detour)
SetFailState("Failed to find signature CBasePlayer::InputSetFogController");
detour.Enable(Hook_Pre, Detour_InputSetFogController);
delete detour;
}
MRESReturn Detour_InputSetFogController(int client, DHookParam params)
{
RequestFrame(Frame_FogControllerChanged, GetClientSerial(client));
return MRES_Ignored;
}
void FogControllerChanged(int client)
{
if (g_OverrideDist[client] != -1.0) {
ApplyFogValues(client, g_OverrideDist[client]);
}
}
void Frame_FogControllerChanged(int clientSerial)
{
int client = GetClientFromSerial(clientSerial);
if (client && IsClientInGame(client))
{
FogControllerChanged(client);
}
}
public void OnClientAuthorized(int client, const char[] auth)
{
bEnabled[client] = false;
g_OverrideDist[client] = -1.0;
g_iClientFogEntity[client] = INVALID_ENT_REFERENCE;
char sIP[32];
GetClientIP(client, sIP, sizeof(sIP));
char allowed_ips[128];
ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips");
if (sv_set_steam_id_ips != null)
{
sv_set_steam_id_ips.GetString(allowed_ips, sizeof(allowed_ips));
if (StrEqual(sIP, allowed_ips))
{
bEnabled[client] = true;
}
}
}
void SetFogController(int client, int fog)
{
SetEntPropEnt(client, Prop_Data, "m_hCtrl", fog);
}
+1 -1
View File
@@ -20,7 +20,7 @@ methodmap AsyncSocket < Handle {
public bool WriteNull(const char[] data) public bool WriteNull(const char[] data)
{ {
this.Write(data, strlen(data) + 1); return this.Write(data, strlen(data) + 1);
} }
public native bool SetConnectCallback(AsyncSocketConnectCallback callback); public native bool SetConnectCallback(AsyncSocketConnectCallback callback);
+1 -1
View File
@@ -1053,7 +1053,7 @@ methodmap JSONRootNode < JSONValue {
} }
public static JSONRootNode Pack(const char[] packString, ArrayList params) { public static JSONRootNode Pack(const char[] packString, ArrayList params) {
return JSONValue_ByPack(packString, params); return view_as<JSONRootNode>(JSONValue_ByPack(packString, params));
} }
public void DumpToServer() { public void DumpToServer() {
@@ -78,6 +78,11 @@ forward void OnMapVoteStart();
*/ */
forward void OnMapVoteEnd(const char[] map); forward void OnMapVoteEnd(const char[] map);
/**
just send the steamID, vote choice and voteweight of the client as a forward
**/
forward void OnMapVoteClientResult(char[] steamID, char[] vote_choice, int voteweight);
/** /**
* Is a map on the current game's official list? * Is a map on the current game's official list?
* This should be treated as informative only. * This should be treated as informative only.
@@ -85,6 +90,7 @@ forward void OnMapVoteEnd(const char[] map);
* @param map Name of map to check * @param map Name of map to check
* @return true if it's on the list of official maps for this game * @return true if it's on the list of official maps for this game
*/ */
native bool IsMapOfficial(const char[] map); native bool IsMapOfficial(const char[] map);
/** /**
@@ -108,13 +114,23 @@ native CanNominateResult CanNominate();
native bool ExcludeMap(const char[] map, int cooldown = 0, int mode = 0); native bool ExcludeMap(const char[] map, int cooldown = 0, int mode = 0);
// Cooldown in seconds // Cooldown in seconds
native bool ExcludeMapTime(const char[] map, int cooldown = 0, int mode = 0); native bool ExcludeMapTime(const char[] map, int cooldown = 0, int mode = 0);
// cooldown based on mysql table rows listing
native bool ExcludeMapListingPriority(const char[] map, int index);
native int GetMapCooldown(const char[] map); native int GetMapCooldown(const char[] map);
native int GetMapCooldownTime(const char[] map); // in unix time native int GetMapCooldownTime(const char[] map); // in unix time
native int GetMapCooldownTime2(const char[] map); // in unix time
native int GetMapLowPopHelpCount(const char[] map);
native int GetMapLaserFag(const char[] map);
native int GetMapTrails(const char[] map);
native int GetMapTickRate(const char[] map);
native int GetMapLagCompensationDisabled(const char[] map);
native int GetMapMinTime(const char[] map); native int GetMapMinTime(const char[] map);
native int GetMapMaxTime(const char[] map); native int GetMapMaxTime(const char[] map);
native int GetMapMinPlayers(const char[] map); native int GetMapMinPlayers(const char[] map);
native int GetMapMaxPlayers(const char[] map); native int GetMapMaxPlayers(const char[] map);
native int GetMapMinHoursAvg(const char[] map);
native int GetExcludedMapListingPriority(const char[] map);
// 0 = Okay // 0 = Okay
// >0 = Minutes till Okay // >0 = Minutes till Okay
@@ -0,0 +1,195 @@
#pragma semicolon 1
#define PLUGIN_AUTHOR "jenz"
#define PLUGIN_VERSION "1.0"
#include <sourcemod>
#include <cstrike>
#include <mapchooser_extended>
int g_iPlayerCount_time_track;
int g_iTopMapsToMarkWithCooldown;
int g_iUnmarkCooldowns;
int g_iCurrentPlayerCount;
int g_iMapStartTime;
Database g_hDatabase;
public Plugin myinfo =
{
name = "map_tracker_cooldown",
author = PLUGIN_AUTHOR,
description = "assigns cooldowns on maps and tracks their played time on the server",
version = PLUGIN_VERSION,
url = "www.unloze.com"
};
public void OnPluginStart()
{
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats");
}
ConVar cvar;
HookConVarChange((cvar = CreateConVar("mce_min_players_track_time", "20", "Playercount in full number required for tracking minutes spend on a map.")), Cvar_playersCount);
g_iPlayerCount_time_track = cvar.IntValue;
delete cvar;
ConVar cvar1;
HookConVarChange((cvar1 = CreateConVar("mce_top_ordered_maps_on_cooldown", "30", "These highest listed maps will receive the cooldown boolean")), Cvar_TopMarkedWithCooldown);
g_iTopMapsToMarkWithCooldown = cvar1.IntValue;
delete cvar1;
ConVar cvar2;
HookConVarChange((cvar2 = CreateConVar("mce_UnmarkCooldowns", "120", "How far down the list does a map need to be for getting unmarked again")), Cvar_UnmarkedCooldown);
g_iUnmarkCooldowns = cvar2.IntValue;
delete cvar2;
g_iCurrentPlayerCount = GetClientCount(false);
CreateTimer(30.0, CheckPlayerCount, _, TIMER_REPEAT);
}
public Action CheckPlayerCount(Handle timer)
{
g_iCurrentPlayerCount = GetClientCount(false);
return Plugin_Continue;
}
public void Cvar_playersCount(ConVar convar, const char[] oldValue, const char[] newValue)
{
g_iPlayerCount_time_track = convar.IntValue;
}
public void Cvar_TopMarkedWithCooldown(ConVar convar, const char[] oldValue, const char[] newValue)
{
g_iTopMapsToMarkWithCooldown = convar.IntValue;
}
public void Cvar_UnmarkedCooldown(ConVar convar, const char[] oldValue, const char[] newValue)
{
g_iUnmarkCooldowns = convar.IntValue;
}
public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
{
if(!db || strlen(error))
{
LogError("Database error: %s", error);
return;
}
g_hDatabase = db;
UpdateTopListedMapsWithCooldown();
}
public void OnMapStart()
{
int i_port = GetConVarInt(FindConVar("hostport"));
if (i_port != 27019 && i_port != 27015)
{
return;
}
g_iMapStartTime = GetTime();
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats");
}
else
{
UpdateTopListedMapsWithCooldown();
}
}
public void UpdateTopListedMapsWithCooldown()
{
//just set every map in top g_iTopMapsToMarkWithCooldown on cooldown
char sQuery[512];
Format(sQuery, sizeof(sQuery), "update unloze_playtimestats.map_minutes_played set on_cooldown = true where mapname in ( SELECT mapname FROM (select mmp.mapname from unloze_playtimestats.map_minutes_played mmp order by mmp.minutes desc limit %i) as top) and on_cooldown is false", g_iTopMapsToMarkWithCooldown);
g_hDatabase.Query(SQL_FinishedQuery1, sQuery, _, DBPrio_High);
}
public void SQL_FinishedQuery1(Database db, DBResultSet results, const char[] error, DataPack data)
{
delete results;
if (!db || strlen(error))
{
LogError("Query error: %s", error);
}
delete data;
UpdateUnmarkCooldown();
}
public void UpdateUnmarkCooldown()
{
//if map in listing fell below g_iUnmarkCooldowns then set the bool to false again.
char sQuery[512];
Format(sQuery, sizeof(sQuery), "update unloze_playtimestats.map_minutes_played set on_cooldown = false where mapname not in ( SELECT mapname FROM (select mmp.mapname from unloze_playtimestats.map_minutes_played mmp order by mmp.minutes desc limit %i) as top) and on_cooldown is true", g_iUnmarkCooldowns);
g_hDatabase.Query(SQL_FinishedQuery2, sQuery, _, DBPrio_High);
}
public void SQL_FinishedQuery2(Database db, DBResultSet results, const char[] error, DataPack data)
{
delete results;
if (!db || strlen(error))
{
LogError("Query error: %s", error);
}
delete data;
SelectMapsToCooldownForMapChooser();
}
public void SelectMapsToCooldownForMapChooser()
{
char sQuery[512];
Format(sQuery, sizeof(sQuery), "select mapname, map_list_index from (select mapname, ROW_NUMBER() OVER(ORDER BY minutes DESC) as map_list_index, mmp.on_cooldown from unloze_playtimestats.map_minutes_played mmp) as targets where on_cooldown");
g_hDatabase.Query(SQL_FinishedQuerySelectMaps, sQuery, _, DBPrio_High);
}
public void SQL_FinishedQuerySelectMaps(Database db, DBResultSet results, const char[] error, DataPack data)
{
if (!db || strlen(error))
{
LogError("Query error in SQL_FinishedQuerySelectMaps: %s", error);
delete results;
return;
}
while (results.RowCount && results.FetchRow())
{
char mapname[256];
results.FetchString(0, mapname, sizeof(mapname));
int map_list_index = results.FetchInt(1);
ExcludeMapListingPriority(mapname, g_iUnmarkCooldowns - map_list_index);
}
delete results;
}
public void OnMapEnd()
{
int i_port = GetConVarInt(FindConVar("hostport"));
if (i_port != 27019 && i_port != 27015)
{
return;
}
int elapsed = GetTime() - g_iMapStartTime;
int minutes = RoundToFloor(elapsed / 60.0);
char Mapname[128];
GetCurrentMap(Mapname, sizeof(Mapname));
//LogMessage("minutes: %i. map: %s", minutes, Mapname);
if (g_iCurrentPlayerCount > g_iPlayerCount_time_track)
{
char sQuery[512];
Format(sQuery, sizeof(sQuery), "INSERT INTO `map_minutes_played` (`mapname`, `minutes`, `updated_on`) VALUES ('%s', '%i', NOW()) ON DUPLICATE KEY UPDATE `minutes` = `minutes` + '%i', `updated_on` = NOW()", Mapname, minutes, minutes);
g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Low);
}
}
public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, DataPack data)
{
delete results;
if (!db || strlen(error))
{
LogError("Query error: %s", error);
}
delete data;
}
File diff suppressed because it is too large Load Diff
@@ -54,9 +54,6 @@ public Plugin myinfo =
url = "https://forums.alliedmods.net/showthread.php?t=156974" url = "https://forums.alliedmods.net/showthread.php?t=156974"
}; };
Handle g_Cvar_ExcludeOld = INVALID_HANDLE;
Handle g_Cvar_ExcludeCurrent = INVALID_HANDLE;
Handle g_MapList = INVALID_HANDLE; Handle g_MapList = INVALID_HANDLE;
Handle g_AdminMapList = INVALID_HANDLE; Handle g_AdminMapList = INVALID_HANDLE;
Menu g_MapMenu; Menu g_MapMenu;
@@ -64,14 +61,6 @@ Menu g_AdminMapMenu;
int g_mapFileSerial = -1; int g_mapFileSerial = -1;
int g_AdminMapFileSerial = -1; int g_AdminMapFileSerial = -1;
#define MAPSTATUS_ENABLED (1<<0)
#define MAPSTATUS_DISABLED (1<<1)
#define MAPSTATUS_EXCLUDE_CURRENT (1<<2)
#define MAPSTATUS_EXCLUDE_PREVIOUS (1<<3)
#define MAPSTATUS_EXCLUDE_NOMINATED (1<<4)
Handle g_mapTrie;
// Nominations Extended Convars // Nominations Extended Convars
Handle g_Cvar_MarkCustomMaps = INVALID_HANDLE; Handle g_Cvar_MarkCustomMaps = INVALID_HANDLE;
Handle g_Cvar_NominateDelay = INVALID_HANDLE; Handle g_Cvar_NominateDelay = INVALID_HANDLE;
@@ -82,6 +71,9 @@ Handle g_Cvar_VIPTimeframe = INVALID_HANDLE;
Handle g_Cvar_VIPTimeframeMinTime = INVALID_HANDLE; Handle g_Cvar_VIPTimeframeMinTime = INVALID_HANDLE;
Handle g_Cvar_VIPTimeframeMaxTime = INVALID_HANDLE; Handle g_Cvar_VIPTimeframeMaxTime = INVALID_HANDLE;
KeyValues g_Config;
int g_Player_NominationDelay[MAXPLAYERS+1]; int g_Player_NominationDelay[MAXPLAYERS+1];
int g_NominationDelay; int g_NominationDelay;
@@ -100,8 +92,6 @@ public void OnPluginStart()
g_MapList = CreateArray(arraySize); g_MapList = CreateArray(arraySize);
g_AdminMapList = CreateArray(arraySize); g_AdminMapList = CreateArray(arraySize);
g_Cvar_ExcludeOld = CreateConVar("sm_nominate_excludeold", "1", "Specifies if the current map should be excluded from the Nominations list", 0, true, 0.00, true, 1.0);
g_Cvar_ExcludeCurrent = CreateConVar("sm_nominate_excludecurrent", "1", "Specifies if the MapChooser excluded maps should also be excluded from Nominations", 0, true, 0.00, true, 1.0);
g_Cvar_InitialDelay = CreateConVar("sm_nominate_initialdelay", "60.0", "Time in seconds before first Nomination can be made", 0, true, 0.00); g_Cvar_InitialDelay = CreateConVar("sm_nominate_initialdelay", "60.0", "Time in seconds before first Nomination can be made", 0, true, 0.00);
g_Cvar_NominateDelay = CreateConVar("sm_nominate_delay", "3.0", "Delay between nominations", 0, true, 0.00, true, 60.00); g_Cvar_NominateDelay = CreateConVar("sm_nominate_delay", "3.0", "Delay between nominations", 0, true, 0.00, true, 60.00);
@@ -125,8 +115,6 @@ public void OnPluginStart()
// Nominations Extended cvars // Nominations Extended cvars
CreateConVar("ne_version", MCE_VERSION, "Nominations Extended Version", FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD); CreateConVar("ne_version", MCE_VERSION, "Nominations Extended Version", FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD);
g_mapTrie = CreateTrie();
//DB //DB
if (!g_dDatabase) if (!g_dDatabase)
{ {
@@ -142,6 +130,26 @@ public void OnMapStart()
//we have too many dbs so i am just re-using racetimercss //we have too many dbs so i am just re-using racetimercss
Database.Connect(SQL_OnDatabaseConnect, "racetimercss"); Database.Connect(SQL_OnDatabaseConnect, "racetimercss");
} }
if(g_Config)
delete g_Config;
char sConfigFile[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/mapchooser_extended.cfg");
if(!FileExists(sConfigFile))
{
LogMessage("Could not find config: \"%s\"", sConfigFile);
return;
}
g_Config = new KeyValues("mapchooser_extended");
if(!g_Config.ImportFromFile(sConfigFile))
{
delete g_Config;
LogMessage("ImportFromFile() failed!");
return;
}
g_Config.Rewind();
} }
public void SQL_OnDatabaseConnect(Database db, const char[] error, any data) public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
@@ -310,7 +318,6 @@ public void OnConfigsExecuted()
g_NominationDelay = GetTime() + GetConVarInt(g_Cvar_InitialDelay); g_NominationDelay = GetTime() + GetConVarInt(g_Cvar_InitialDelay);
UpdateMapTrie();
UpdateMapMenus(); UpdateMapMenus();
} }
@@ -320,61 +327,6 @@ void UpdateMapMenus()
g_AdminMapMenu = BuildAdminMapMenu(""); g_AdminMapMenu = BuildAdminMapMenu("");
} }
void UpdateMapTrie()
{
static char map[PLATFORM_MAX_PATH];
static char currentMap[PLATFORM_MAX_PATH];
ArrayList excludeMaps;
if(GetConVarBool(g_Cvar_ExcludeOld))
{
excludeMaps = CreateArray(ByteCountToCells(PLATFORM_MAX_PATH));
GetExcludeMapList(excludeMaps);
}
if(GetConVarBool(g_Cvar_ExcludeCurrent))
GetCurrentMap(currentMap, sizeof(currentMap));
ClearTrie(g_mapTrie);
for(int i = 0; i < GetArraySize(g_MapList); i++)
{
int status = MAPSTATUS_ENABLED;
GetArrayString(g_MapList, i, map, sizeof(map));
if(GetConVarBool(g_Cvar_ExcludeCurrent))
{
if(StrEqual(map, currentMap))
status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_CURRENT;
}
/* Dont bother with this check if the current map check passed */
if(GetConVarBool(g_Cvar_ExcludeOld) && status == MAPSTATUS_ENABLED)
{
if(FindStringInArray(excludeMaps, map) != -1)
status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_PREVIOUS;
}
SetTrieValue(g_mapTrie, map, status);
}
if(excludeMaps)
delete excludeMaps;
}
public void OnNominationRemoved(const char[] map, int client)
{
int status;
/* Is the map in our list? */
if(!GetTrieValue(g_mapTrie, map, status))
return;
/* Was the map disabled due to being nominated */
if((status & MAPSTATUS_EXCLUDE_NOMINATED) != MAPSTATUS_EXCLUDE_NOMINATED)
return;
SetTrieValue(g_mapTrie, map, MAPSTATUS_ENABLED);
}
public Action Command_Addmap(int client, int args) public Action Command_Addmap(int client, int args)
{ {
if(args == 0) if(args == 0)
@@ -398,69 +350,6 @@ public Action Command_Addmap(int client, int args)
AttemptAdminNominate(client, mapname); AttemptAdminNominate(client, mapname);
return Plugin_Handled; return Plugin_Handled;
} }
/*
int Cooldown1 = GetMapCooldownTime(mapname);
if(Cooldown1 > GetTime())
{
int Seconds = Cooldown1 - GetTime();
CPrintToChat(client, "[NE] %t", "Map Cooldown Time Error", Seconds / 3600, (Seconds % 3600) / 60);
return Plugin_Handled;
}
if(!CheckCommandAccess(client, "sm_nominate_ignore", ADMFLAG_KICK, true) )
{
bool RestrictionsActive = AreRestrictionsActive();
int status;
if(GetTrieValue(g_mapTrie, mapname, status))
{
if((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED)
{
if((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT)
CPrintToChat(client, "[NE] %t", "Cant Nominate Current Map");
if(RestrictionsActive && (status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS)
{
int Cooldown = GetMapCooldown(mapname);
CPrintToChat(client, "[NE] %t (%d)", "Map in Exclude List", Cooldown);
}
if((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED)
CPrintToChat(client, "[NE] %t", "Map Already Nominated");
return Plugin_Handled;
}
}
int TimeRestriction = GetMapTimeRestriction(mapname);
if(RestrictionsActive && TimeRestriction)
{
CPrintToChat(client, "[NE] %t", "Map Nominate Time Error", TimeRestriction / 60, TimeRestriction % 60);
return Plugin_Handled;
}
int AverageHourRestricted = GetAveragePlayerTimeOnServerMapRestriction(mapname);
if (RestrictionsActive && AverageHourRestricted > 0)
{
PrintToChat(client, "%s requires +%i hours average. Use sm_houravg to check average.", mapname, AverageHourRestricted);
return Plugin_Handled;
}
int PlayerRestriction = GetMapPlayerRestriction(mapname);
if(RestrictionsActive && PlayerRestriction)
{
if(PlayerRestriction < 0)
CPrintToChat(client, "[NE] %t", "Map Nominate MinPlayers Error", PlayerRestriction * -1);
else
CPrintToChat(client, "[NE] %t", "Map Nominate MaxPlayers Error", PlayerRestriction);
return Plugin_Handled;
}
}
*/
NominateResult result = NominateMap(mapname, true, 0); NominateResult result = NominateMap(mapname, true, 0);
@@ -472,8 +361,6 @@ public Action Command_Addmap(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
SetTrieValue(g_mapTrie, mapname, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED);
CReplyToCommand(client, "%t", "Map Inserted", mapname); CReplyToCommand(client, "%t", "Map Inserted", mapname);
LogAction(client, -1, "\"%L\" inserted map \"%s\".", client, mapname); LogAction(client, -1, "\"%L\" inserted map \"%s\".", client, mapname);
@@ -548,18 +435,9 @@ public Action Command_AddExclude(int client, int args)
mode = StringToInt(buffer); mode = StringToInt(buffer);
} }
int status;
if(!GetTrieValue(g_mapTrie, mapname, status))
{
ReplyToCommand(client, "[NE] %t", "Map was not found", mapname);
return Plugin_Handled;
}
ShowActivity(client, "Excluded map \"%s\" from nomination", mapname); ShowActivity(client, "Excluded map \"%s\" from nomination", mapname);
LogAction(client, -1, "\"%L\" excluded map \"%s\" from nomination", client, mapname); LogAction(client, -1, "\"%L\" excluded map \"%s\" from nomination", client, mapname);
SetTrieValue(g_mapTrie, mapname, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_PREVIOUS);
// native call to mapchooser_extended // native call to mapchooser_extended
ExcludeMap(mapname, cooldown, mode); ExcludeMap(mapname, cooldown, mode);
@@ -592,8 +470,7 @@ public Action Command_AddExcludeTime(int client, int args)
mode = StringToInt(buffer); mode = StringToInt(buffer);
} }
int status; if (FindStringInArray(g_MapList, mapname) == -1)
if(!GetTrieValue(g_mapTrie, mapname, status))
{ {
ReplyToCommand(client, "[NE] %t", "Map was not found", mapname); ReplyToCommand(client, "[NE] %t", "Map was not found", mapname);
return Plugin_Handled; return Plugin_Handled;
@@ -668,17 +545,29 @@ public Action Command_Nominate(int client, int args)
static char mapname[PLATFORM_MAX_PATH]; static char mapname[PLATFORM_MAX_PATH];
GetCmdArg(1, mapname, sizeof(mapname)); GetCmdArg(1, mapname, sizeof(mapname));
int status; if (FindStringInArray(g_MapList, mapname) == -1)
if(!GetTrieValue(g_mapTrie, mapname, status))
{ {
//CPrintToChat(client, "%t", "Map was not found", mapname);
ReplyToCommand(client, "Map was not found: %s", mapname); ReplyToCommand(client, "Map was not found: %s", mapname);
AttemptNominate(client, mapname); AttemptNominate(client, mapname);
return Plugin_Handled; return Plugin_Handled;
} }
//yeah admins and leaders cant bypass these restrictions, maybe the admins really are the problem sometimes for skipping restrictions.
int Index_Cooldown = GetExcludedMapListingPriority(mapname);
if (Index_Cooldown > 0)
{
CPrintToChat(client, "[NE] Map is on Cooldown for being overplayed. List Posistion: %i", Index_Cooldown);
return Plugin_Handled;
}
//October 2026: admins can just skip all cooldowns and map restrictions
if(!CheckCommandAccess(client, "sm_nominate_ignore", ADMFLAG_KICK, true) /* && !Leader_Is(client)*/ )
{
bool RestrictionsActive = AreRestrictionsActive();
//restrictions not being active now also skips all the cooldowns.
int Cooldown1 = GetMapCooldownTime(mapname); int Cooldown1 = GetMapCooldownTime(mapname);
if(Cooldown1 > GetTime()) if(RestrictionsActive && Cooldown1 > GetTime())
{ {
int Seconds = Cooldown1 - GetTime(); int Seconds = Cooldown1 - GetTime();
CPrintToChat(client, "[NE] %t", "Map Cooldown Time Error", Seconds / 3600, (Seconds % 3600) / 60); CPrintToChat(client, "[NE] %t", "Map Cooldown Time Error", Seconds / 3600, (Seconds % 3600) / 60);
@@ -686,30 +575,6 @@ public Action Command_Nominate(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
//July 2024 edit: any person who is potential leader can just skip all cooldowns and map restrictions. same for admins.
if(!CheckCommandAccess(client, "sm_nominate_ignore", ADMFLAG_KICK, true) && !Leader_Is(client))
{
bool RestrictionsActive = AreRestrictionsActive();
if((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED)
{
if((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT)
ReplyToCommand(client, "Cant Nominate Current Map.");
//CPrintToChat(client, "[NE] %t", "Cant Nominate Current Map");
if(RestrictionsActive && (status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS)
{
int Cooldown = GetMapCooldown(mapname);
CPrintToChat(client, "[NE] %t (%d)", "Map in Exclude List", Cooldown);
}
if((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED)
CPrintToChat(client, "[NE] %t", "Map Already Nominated");
return Plugin_Handled;
}
bool VIPRestriction = GetMapVIPRestriction(mapname, client); bool VIPRestriction = GetMapVIPRestriction(mapname, client);
if(RestrictionsActive && VIPRestriction) if(RestrictionsActive && VIPRestriction)
{ {
@@ -744,6 +609,32 @@ public Action Command_Nominate(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
} }
//2026 march 20th: if map already is admin or leader nominated we dont allow it being nominated again
int arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
ArrayList MapList = CreateArray(arraySize);
ArrayList OwnerList = CreateArray();
GetNominatedMapList(MapList, OwnerList);
for (int j = 0; j < GetArraySize(MapList); j++)
{
char mapIterated[128];
GetArrayString(MapList, j, mapIterated, sizeof(mapIterated));
if (StrEqual(mapname, mapIterated))
{
int owner = GetArrayCell(OwnerList, j);
if (!owner || Leader_Is(owner))
{
delete MapList;
delete OwnerList;
PrintToChat(client, "The map is already nominated by admin or leader.");
return Plugin_Handled;
}
}
}
delete MapList;
delete OwnerList;
NominateResult result = NominateMap(mapname, false, client); NominateResult result = NominateMap(mapname, false, client);
if (result == Nominate_InvalidMap) if (result == Nominate_InvalidMap)
@@ -762,21 +653,17 @@ public Action Command_Nominate(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
/* Map was nominated! - Disable the menu item and update the trie */
//SetTrieValue(g_mapTrie, mapname, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED);
static char name[MAX_NAME_LENGTH]; static char name[MAX_NAME_LENGTH];
GetClientName(client, name, sizeof(name)); GetClientName(client, name, sizeof(name));
int nominate_worth = GetPlayerWorthRTV_boost_(client);
if(result == Nominate_Added) if(result == Nominate_Added)
{ {
int nominate_worth = GetPlayerWorthRTV_boost_(client); PrintToChatAll("[NE] %N has nominated %s (%i Nomination Boost).", client, mapname, nominate_worth);
PrintToChatAll("[NE] %t", "Map Nominated", name, mapname, nominate_worth);
} }
else if(result == Nominate_Replaced) else if(result == Nominate_Replaced)
{ {
PrintToChatAll("[NE] %t", "Map Nomination Changed", name, mapname); PrintToChatAll("[NE] %N has changed their nomination to %s (%i Nomination Boost).", client, mapname, nominate_worth);
} }
LogMessage("%s nominated %s", name, mapname); LogMessage("%s nominated %s", name, mapname);
@@ -836,19 +723,45 @@ public int Handler_NominateListMenu(Menu menu, MenuAction action, int param1, in
return 0; return 0;
} }
void SetNominateMenuTitle(Menu menu, int client)
{
int nominate_worth = GetPlayerWorthRTV_boost_(client);
if (Leader_Is(client))
SetMenuTitle(menu, "Nominate Map: (%i Nomination Boost Leader)", 999);
else
SetMenuTitle(menu, "Nominate Map: (%i Nomination Boost)", nominate_worth);
}
void AttemptNominate(int client, const char[] filter = "") void AttemptNominate(int client, const char[] filter = "")
{ {
if(!client) if(!client)
return; return;
Menu menu = g_MapMenu; Menu menu = g_MapMenu;
if (filter[0])
{
// preserves the existing behavior for the "map wasnt found, try a substring search" fallback
menu = BuildMapMenu(filter, client); menu = BuildMapMenu(filter, client);
}
else
{
menu = BuildCategoryMenu(client);
}
int nominate_worth = GetPlayerWorthRTV_boost_(client); SetNominateMenuTitle(menu, client);
SetMenuTitle(menu, "%T", "Nominate Title", client, nominate_worth);
DisplayMenu(menu, client, MENU_TIME_FOREVER); DisplayMenu(menu, client, MENU_TIME_FOREVER);
} }
public void GetMapCategory(const char[] mapname, char[] output_category, int maxlen)
{
strcopy(output_category, maxlen, "Uncategorized");
if (g_Config && g_Config.JumpToKey(mapname))
{
g_Config.GetString("MapCategory", output_category, maxlen, "Uncategorized");
g_Config.Rewind();
}
}
void AttemptAdminNominate(int client, const char[] filter = "") void AttemptAdminNominate(int client, const char[] filter = "")
{ {
if(!client) if(!client)
@@ -858,7 +771,7 @@ void AttemptAdminNominate(int client, const char[] filter = "")
if(filter[0]) if(filter[0])
menu = BuildAdminMapMenu(filter); menu = BuildAdminMapMenu(filter);
SetMenuTitle(menu, "%T", "Nominate Title", client, 999); SetMenuTitle(menu, "Nominate Map: (%i Nomination Boost Admin)", 999);
DisplayMenu(menu, client, MENU_TIME_FOREVER); DisplayMenu(menu, client, MENU_TIME_FOREVER);
} }
@@ -915,12 +828,12 @@ bool PopulateNominateListMenu(Menu menu, int client, const char[] filter = "")
} }
StringMap sm = new StringMap(); StringMap sm = new StringMap();
StringMap sm_special_noms = new StringMap(); //storing leaders and admin noms.
static char map[PLATFORM_MAX_PATH]; static char map[PLATFORM_MAX_PATH];
static char display[PLATFORM_MAX_PATH]; static char display[PLATFORM_MAX_PATH];
for(int i = 0; i < GetArraySize(MapList); i++) for(int i = 0; i < GetArraySize(MapList); i++)
{ {
GetArrayString(MapList, i, map, sizeof(map)); GetArrayString(MapList, i, map, sizeof(map));
if(!filter[0] || StrContains(map, filter, false) != -1) if(!filter[0] || StrContains(map, filter, false) != -1)
{ {
//admin nominations must have most votes shrug //admin nominations must have most votes shrug
@@ -929,14 +842,15 @@ bool PopulateNominateListMenu(Menu menu, int client, const char[] filter = "")
int nominate_count_for_particular_map = 0; int nominate_count_for_particular_map = 0;
sm.GetValue(map, nominate_count_for_particular_map); sm.GetValue(map, nominate_count_for_particular_map);
//if its console its admin nomination. if its nominated by leader it will also go to the vote just. //if its console its admin nomination. if its nominated by leader it will also go to the vote just.
if (!owner) if (!owner || Leader_Is(owner))
{ {
nominate_count_for_particular_map = 1999; nominate_count_for_particular_map = 1999;
sm_special_noms.SetValue(map, owner); //admin noms and leaders are unique per each map, so cant be overwritten.
} }
else else
{ {
int nominate_worth = GetPlayerWorthRTV_boost_(owner); int nominate_worth = GetPlayerWorthRTV_boost_(owner);
//atm everybody just has equal voting again, so this is just += 1 //2026 march: the boosting can now range from 1 to 2. so for most its +=1 and for a few +=2.
nominate_count_for_particular_map += nominate_worth; nominate_count_for_particular_map += nominate_worth;
} }
sm.SetValue(map, nominate_count_for_particular_map, true); sm.SetValue(map, nominate_count_for_particular_map, true);
@@ -962,22 +876,26 @@ bool PopulateNominateListMenu(Menu menu, int client, const char[] filter = "")
{ {
int nominate_count_for_particular_map = 0; int nominate_count_for_particular_map = 0;
sm.GetValue(map, nominate_count_for_particular_map); sm.GetValue(map, nominate_count_for_particular_map);
int leader_or_admin_map_owner = -1;
sm_special_noms.GetValue(map, leader_or_admin_map_owner);
strcopy(display, sizeof(display), map); strcopy(display, sizeof(display), map);
bool VIPRestriction = GetMapVIPRestriction(map); bool VIPRestriction = GetMapVIPRestriction(map);
if((VIPRestriction) && AreRestrictionsActive()) if((VIPRestriction) && AreRestrictionsActive())
Format(display, sizeof(display), "%s (%T)", display, "VIP Nomination", client); Format(display, sizeof(display), "%s (%T)", display, "VIP Nomination", client);
int owner = GetArrayCell(OwnerList, i);
char spelling[8]; char spelling[8];
Format(spelling, sizeof(spelling), nominate_count_for_particular_map == 1 ? "Vote" : "Votes"); Format(spelling, sizeof(spelling), nominate_count_for_particular_map == 1 ? "Vote" : "Votes");
if (leader_or_admin_map_owner != -1)
if(!owner) {
if(!leader_or_admin_map_owner)
Format(display, sizeof(display), "%s (Admin)", display); Format(display, sizeof(display), "%s (Admin)", display);
else
Format(display, sizeof(display), "%s (Leader)", display);
}
else else
Format(display, sizeof(display), "%s (%i %s)", display, nominate_count_for_particular_map, spelling); Format(display, sizeof(display), "%s (%i %s)", display, nominate_count_for_particular_map, spelling);
AddMenuItem(menu, map, display); AddMenuItem(menu, map, display);
} }
} }
@@ -986,15 +904,87 @@ bool PopulateNominateListMenu(Menu menu, int client, const char[] filter = "")
delete OwnerList; delete OwnerList;
delete SortedList; delete SortedList;
delete sm; delete sm;
delete sm_special_noms;
delete sm_snapshot; delete sm_snapshot;
return true; return true;
} }
Menu BuildMapMenu(const char[] filter, int client) Menu BuildCategoryMenu(int client)
{
Menu menu = CreateMenu(Handler_CategorySelectMenu, MENU_ACTIONS_DEFAULT);
//2023 edit to allow hiding/unhiding unavailable maps
char hiding_unavailable_maps[MAX_NAME_LENGTH];
if (g_bClientsIgnoring[client])
Format(hiding_unavailable_maps, sizeof(hiding_unavailable_maps), "Show all unavailable maps");
else
Format(hiding_unavailable_maps, sizeof(hiding_unavailable_maps), "Hide all unavailable maps");
AddMenuItem(menu, hiding_unavailable_maps, hiding_unavailable_maps);
ArrayList categories = CreateArray(ByteCountToCells(64));
static char map[PLATFORM_MAX_PATH];
static char category[64];
for (int i = 0; i < GetArraySize(g_MapList); i++)
{
GetArrayString(g_MapList, i, map, sizeof(map));
GetMapCategory(map, category, sizeof(category));
if (FindStringInArray(categories, category) == -1)
PushArrayString(categories, category);
}
SortADTArray(categories, Sort_Ascending, Sort_String);
for (int i = 0; i < GetArraySize(categories); i++)
{
GetArrayString(categories, i, category, sizeof(category));
AddMenuItem(menu, category, category);
}
AddMenuItem(menu, "Full Map list", "Full Map list");
delete categories;
SetMenuExitButton(menu, true);
return menu;
}
public int Handler_CategorySelectMenu(Menu menu, MenuAction action, int param1, int param2)
{
switch(action)
{
case MenuAction_End:
{
delete menu;
}
case MenuAction_Select:
{
static char item[64];
GetMenuItem(menu, param2, item, sizeof(item));
if (StrEqual(item, "Hide all unavailable maps") || StrEqual(item, "Show all unavailable maps"))
{
sql_insert_update_hiding_unavailable(param1);
Menu refreshed = BuildCategoryMenu(param1);
SetNominateMenuTitle(refreshed, param1);
DisplayMenu(refreshed, param1, MENU_TIME_FOREVER);
return 0;
}
Menu submenu = BuildMapMenu("", param1, item);
SetNominateMenuTitle(submenu, param1);
DisplayMenu(submenu, param1, MENU_TIME_FOREVER);
}
}
return 0;
}
Menu BuildMapMenu(const char[] filter, int client, const char[] category = "")
{ {
Menu menu = CreateMenu(Handler_MapSelectMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem); Menu menu = CreateMenu(Handler_MapSelectMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem);
static char map[PLATFORM_MAX_PATH * 2]; static char map[PLATFORM_MAX_PATH * 2];
static char mapCategory[64];
int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); int arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
ArrayList MapList = CreateArray(arraySize); ArrayList MapList = CreateArray(arraySize);
@@ -1013,7 +1003,18 @@ Menu BuildMapMenu(const char[] filter, int client)
for(int j = 0; j < GetArraySize(MapList); j++) for(int j = 0; j < GetArraySize(MapList); j++)
{ {
int owner = GetArrayCell(OwnerList, j); int owner = GetArrayCell(OwnerList, j);
if (client == owner) //display if normal nomination, leader nomination or admin nomination
if (!owner)
{
GetArrayString(MapList, j, map, sizeof(map));
sm.SetValue(map, 2);
}
else if (Leader_Is(owner))
{
GetArrayString(MapList, j, map, sizeof(map));
sm.SetValue(map, 3);
}
else if (client == owner)
{ {
GetArrayString(MapList, j, map, sizeof(map)); GetArrayString(MapList, j, map, sizeof(map));
sm.SetValue(map, 1); sm.SetValue(map, 1);
@@ -1021,41 +1022,44 @@ Menu BuildMapMenu(const char[] filter, int client)
} }
} }
//2023 edit to allow hiding/unhiding unavailable maps
char hiding_unavailable_maps[MAX_NAME_LENGTH];
if (g_bClientsIgnoring[client])
{
Format(hiding_unavailable_maps, sizeof(hiding_unavailable_maps), "Show all unavailable maps");
}
else
{
Format(hiding_unavailable_maps, sizeof(hiding_unavailable_maps), "Hide all unavailable maps");
}
AddMenuItem(menu, hiding_unavailable_maps, hiding_unavailable_maps);
for(int i = 0; i < GetArraySize(g_MapList); i++) for(int i = 0; i < GetArraySize(g_MapList); i++)
{ {
GetArrayString(g_MapList, i, map, sizeof(map)); GetArrayString(g_MapList, i, map, sizeof(map));
if (category[0] && !StrEqual(category, "Full Map list"))
{
//only shows maps that are in this actual category.
GetMapCategory(map, mapCategory, sizeof(mapCategory));
if (!StrEqual(mapCategory, category))
continue;
}
if(!filter[0] || StrContains(map, filter, false) != -1) if(!filter[0] || StrContains(map, filter, false) != -1)
{ {
if (client != 0 && nominated_maps) if (client != 0 && nominated_maps)
{ {
int map_present = 0; int map_present = 0;
sm.GetValue(map, map_present); sm.GetValue(map, map_present);
// 1 -> nominated by client. 2 -> nominated by admin. 3 -> nominated by leader
if (map_present == 1) if (map_present == 1)
{ {
//PrintToChatAll("client %N here. map: %s", client, map);
StrCat(map, sizeof(map), " (Nominated)"); StrCat(map, sizeof(map), " (Nominated)");
} }
else if (map_present == 2)
{
StrCat(map, sizeof(map), " (Admin Nominated)");
}
else if (map_present == 3)
{
StrCat(map, sizeof(map), " (Leader Nominated)");
}
} }
if (g_bClientsIgnoring[client]) if (g_bClientsIgnoring[client])
{ {
if (GetMapCooldownTime(map) > GetTime())
{
continue;
}
if(AreRestrictionsActive() && ( if(AreRestrictionsActive() && (
GetMapCooldownTime(map) > GetTime() ||
GetExcludedMapListingPriority(map) > 0 ||
GetMapTimeRestriction(map) || GetMapTimeRestriction(map) ||
GetMapPlayerRestriction(map) || GetMapPlayerRestriction(map) ||
GetAveragePlayerTimeOnServerMapRestriction(map) > 0 || GetAveragePlayerTimeOnServerMapRestriction(map) > 0 ||
@@ -1072,6 +1076,9 @@ Menu BuildMapMenu(const char[] filter, int client)
delete OwnerList; delete OwnerList;
delete sm; delete sm;
if (category[0])
SetMenuExitBackButton(menu, true);
else
SetMenuExitButton(menu, true); SetMenuExitButton(menu, true);
return menu; return menu;
@@ -1119,46 +1126,42 @@ public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int p
delete menu; delete menu;
} }
} }
case MenuAction_Cancel:
{
if (param2 == MenuCancel_ExitBack)
{
Menu back = BuildCategoryMenu(param1);
SetNominateMenuTitle(back, param1);
DisplayMenu(back, param1, MENU_TIME_FOREVER);
}
}
case MenuAction_Select: case MenuAction_Select:
{ {
//2023-03-22 edit since client sometimes is 0 which fucks up mapchooser //2023-03-22 edit since client sometimes is 0 which fucks up mapchooser
if (param1 > 0 && param1 <= MaxClients && IsClientConnected(param1) && IsClientInGame(param1)) if (param1 > 0 && param1 <= MaxClients && IsClientConnected(param1) && IsClientInGame(param1))
{ {
if(g_Player_NominationDelay[param1] > GetTime()) if (g_Player_NominationDelay[param1] > GetTime())
{ {
PrintToChat(param1, "[NE] Please wait %d seconds before you can nominate again", g_Player_NominationDelay[param1] - GetTime()); PrintToChat(param1, "[NE] Please wait %d seconds before you can nominate again", g_Player_NominationDelay[param1] - GetTime());
DisplayMenuAtItem(menu, param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER); DisplayMenuAtItem(menu, param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER);
return 0; return 0;
} }
static char map[PLATFORM_MAX_PATH]; static char map[PLATFORM_MAX_PATH];
char name[MAX_NAME_LENGTH]; char name[MAX_NAME_LENGTH];
GetMenuItem(menu, param2, map, sizeof(map)); GetMenuItem(menu, param2, map, sizeof(map));
if (StrEqual(map, "Hide all unavailable maps"))
{
PrintToChat(param1, "Hiding all unavailable maps from the nomination list");
sql_insert_update_hiding_unavailable(param1);
return 0;
}
if (StrEqual(map, "Show all unavailable maps"))
{
PrintToChat(param1, "Displaying all unavailable maps from the nomination list");
sql_insert_update_hiding_unavailable(param1);
return 0;
}
GetClientName(param1, name, MAX_NAME_LENGTH); GetClientName(param1, name, MAX_NAME_LENGTH);
int Index_Cooldown = GetExcludedMapListingPriority(map);
if (GetMapCooldownTime(map) > GetTime()) if (Index_Cooldown > 0)
{ {
PrintToChat(param1, "[NE] You cant nominate this map right now."); PrintToChat(param1, "[NE] You cant nominate this map right now. Overplayed Position: %i", Index_Cooldown);
return 0; return 0;
} }
if(!CheckCommandAccess(param1, "sm_nominate_ignore", ADMFLAG_KICK, true) && !Leader_Is(param1)) if (!CheckCommandAccess(param1, "sm_nominate_ignore", ADMFLAG_KICK, true) /*&& !Leader_Is(param1)*/ )
{ {
if(AreRestrictionsActive() && ( if(AreRestrictionsActive() && (
GetMapCooldownTime(map) > GetTime() ||
GetMapTimeRestriction(map) || GetMapTimeRestriction(map) ||
GetMapPlayerRestriction(map) || GetMapPlayerRestriction(map) ||
GetAveragePlayerTimeOnServerMapRestriction(map) > 0 || GetAveragePlayerTimeOnServerMapRestriction(map) > 0 ||
@@ -1188,59 +1191,79 @@ public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int p
PrintToChat(param1, "[NE] %t", "Max Nominations"); PrintToChat(param1, "[NE] %t", "Max Nominations");
return 0; return 0;
} }
//SetTrieValue(g_mapTrie, map, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED);
if(result == Nominate_Added)
{
int nominate_worth = GetPlayerWorthRTV_boost_(param1); int nominate_worth = GetPlayerWorthRTV_boost_(param1);
PrintToChatAll("[NE] %t", "Map Nominated", name, map, nominate_worth);
if (result == Nominate_Added)
{
if (Leader_Is(param1))
{
PrintToChatAll("[NE] %N has nominated %s (%i Nomination Boost Leader).", param1, map, 999);
}
else
{
PrintToChatAll("[NE] %N has nominated %s (%i Nomination Boost).", param1, map, nominate_worth);
}
} }
else if(result == Nominate_Replaced) else if(result == Nominate_Replaced)
{ {
PrintToChatAll("[NE] %t", "Map Nomination Changed", name, map); if (Leader_Is(param1))
{
PrintToChatAll("[NE] %N has changed their nomination to %s (%i Nomination Boost Leader).", param1, map, 999);
} }
else
LogMessage("%s nominated %s", name, map); {
PrintToChatAll("[NE] %N has changed their nomination to %s (%i Nomination Boost).", param1, map, nominate_worth);
}
}
//LogMessage("%s nominated %s", name, map);
g_Player_NominationDelay[param1] = GetTime() + GetConVarInt(g_Cvar_NominateDelay); g_Player_NominationDelay[param1] = GetTime() + GetConVarInt(g_Cvar_NominateDelay);
} }
} }
case MenuAction_DrawItem: case MenuAction_DrawItem:
{ {
static char map[PLATFORM_MAX_PATH]; char map_before_fix[PLATFORM_MAX_PATH];
GetMenuItem(menu, param2, map, sizeof(map)); GetMenuItem(menu, param2, map_before_fix, sizeof(map_before_fix));
int status; char map[PLATFORM_MAX_PATH];
if(GetTrieValue(g_mapTrie, map, status)) if (SplitString(map_before_fix, " ", map, sizeof(map)) == -1)
{ {
if((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) Format(map, sizeof(map), map_before_fix);
{
if((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT)
{
return ITEMDRAW_DISABLED;
} }
if(AreRestrictionsActive() && (status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS) //2026 march 20th: if map already is admin or leader nominated we itemdraw disable
{ int arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
return ITEMDRAW_DISABLED; ArrayList MapList = CreateArray(arraySize);
} ArrayList OwnerList = CreateArray();
GetNominatedMapList(MapList, OwnerList);
if((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED) for (int j = 0; j < GetArraySize(MapList); j++)
{ {
char mapIterated[128];
GetArrayString(MapList, j, mapIterated, sizeof(mapIterated));
if (StrEqual(map, mapIterated))
{
int owner = GetArrayCell(OwnerList, j);
if (!owner || Leader_Is(owner))
{
delete MapList;
delete OwnerList;
return ITEMDRAW_DISABLED; return ITEMDRAW_DISABLED;
} }
} }
} }
delete MapList;
delete OwnerList;
if (GetMapCooldownTime(map) > GetTime()) int Index_Cooldown = GetExcludedMapListingPriority(map);
if (Index_Cooldown > 0)
{ {
return ITEMDRAW_DISABLED; return ITEMDRAW_DISABLED;
} }
if(!CheckCommandAccess(param1, "sm_nominate_ignore", ADMFLAG_KICK, true) && !Leader_Is(param1)) if(!CheckCommandAccess(param1, "sm_nominate_ignore", ADMFLAG_KICK, true) && !Leader_Is(param1))
{ {
if(AreRestrictionsActive() && ( if(AreRestrictionsActive() && (
GetMapCooldownTime(map) > GetTime() ||
GetMapTimeRestriction(map) || GetMapTimeRestriction(map) ||
GetMapPlayerRestriction(map) || GetMapPlayerRestriction(map) ||
GetAveragePlayerTimeOnServerMapRestriction(map) > 0 || GetAveragePlayerTimeOnServerMapRestriction(map) > 0 ||
@@ -1292,34 +1315,15 @@ public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int p
Format(buffer, sizeof(buffer), "%s (%T)", buffer, "VIP Restriction", param1); Format(buffer, sizeof(buffer), "%s (%T)", buffer, "VIP Restriction", param1);
} }
int status; int Index_Cooldown = GetExcludedMapListingPriority(map);
if(GetTrieValue(g_mapTrie, map, status)) if (Index_Cooldown > 0)
{ {
if((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) Format(display, sizeof(display), "%s (Overplayed pos: %i)", map, Index_Cooldown);
{
if((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT)
{
Format(display, sizeof(display), "%s (%T)", buffer, "Current Map", param1);
return RedrawMenuItem(display); return RedrawMenuItem(display);
} }
if(RestrictionsActive && (status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS)
{
int Cooldown = GetMapCooldown(map);
Format(display, sizeof(display), "%s (%T %d)", buffer, "Recently Played", param1, Cooldown);
return RedrawMenuItem(display);
}
if((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED)
{
Format(display, sizeof(display), "%s (%T)", buffer, "Nominated", param1);
return RedrawMenuItem(display);
}
}
}
int Cooldown = GetMapCooldownTime(map); int Cooldown = GetMapCooldownTime(map);
if(Cooldown > GetTime()) if(RestrictionsActive && Cooldown > GetTime())
{ {
int Seconds = Cooldown - GetTime(); int Seconds = Cooldown - GetTime();
char time[16]; char time[16];
@@ -1404,13 +1408,6 @@ stock bool IsNominateAllowed(int client)
CReplyToCommand(client, "[NE] %t", "Next Map", map); CReplyToCommand(client, "[NE] %t", "Next Map", map);
return false; return false;
} }
/*
case CanNominate_No_VoteFull:
{
CReplyToCommand(client, "[NE] %t", "Max Nominations");
return false;
}
*/
} }
return true; return true;
@@ -1434,6 +1431,7 @@ public int Handler_AdminMapSelectMenu(Menu menu, MenuAction action, int param1,
{ {
if(AreRestrictionsActive() && ( if(AreRestrictionsActive() && (
GetMapCooldownTime(map) > GetTime() || //this one is fine cause its admin nomination. GetMapCooldownTime(map) > GetTime() || //this one is fine cause its admin nomination.
GetExcludedMapListingPriority(map) > 0 ||
GetMapTimeRestriction(map) || GetMapTimeRestriction(map) ||
GetMapPlayerRestriction(map) || GetMapPlayerRestriction(map) ||
GetAveragePlayerTimeOnServerMapRestriction(map) > 0 || GetAveragePlayerTimeOnServerMapRestriction(map) > 0 ||
@@ -1453,8 +1451,6 @@ public int Handler_AdminMapSelectMenu(Menu menu, MenuAction action, int param1,
return 0; return 0;
} }
SetTrieValue(g_mapTrie, map, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED);
PrintToChat(param1, "[NE] %t", "Map Inserted", map); PrintToChat(param1, "[NE] %t", "Map Inserted", map);
LogAction(param1, -1, "\"%L\" inserted map \"%s\".", param1, map); LogAction(param1, -1, "\"%L\" inserted map \"%s\".", param1, map);
@@ -1525,7 +1521,6 @@ public int Native_PushMapIntoNominationPool(Handle plugin, int numArgs)
ShiftArrayUp(g_MapList, 0); ShiftArrayUp(g_MapList, 0);
SetArrayString(g_MapList, 0, map); SetArrayString(g_MapList, 0, map);
UpdateMapTrie();
UpdateMapMenus(); UpdateMapMenus();
return 0; return 0;
@@ -1549,7 +1544,6 @@ public int Native_PushMapsIntoNominationPool(Handle plugin, int numArgs)
delete maps; delete maps;
UpdateMapTrie();
UpdateMapMenus(); UpdateMapMenus();
return 0; return 0;
@@ -1566,7 +1560,6 @@ public int Native_RemoveMapFromNominationPool(Handle plugin, int numArgs)
if ((idx = FindStringInArray(g_MapList, map)) != -1) if ((idx = FindStringInArray(g_MapList, map)) != -1)
RemoveFromArray(g_MapList, idx); RemoveFromArray(g_MapList, idx);
UpdateMapTrie();
UpdateMapMenus(); UpdateMapMenus();
return 0; return 0;
@@ -1589,7 +1582,6 @@ public int Native_RemoveMapsFromNominationPool(Handle plugin, int numArgs)
delete maps; delete maps;
UpdateMapTrie();
UpdateMapMenus(); UpdateMapMenus();
return 0; return 0;
@@ -201,6 +201,10 @@ public int Native_GetRtvPercentageForClient(Handle plugin, int numParams)
UpdateRTV(); UpdateRTV();
int player_worth_rtv = GetPlayerWorthRTV_boost_(client); int player_worth_rtv = GetPlayerWorthRTV_boost_(client);
if (player_worth_rtv < 1)
{
player_worth_rtv = 1;
}
return player_worth_rtv; return player_worth_rtv;
} }
@@ -209,7 +213,7 @@ void update_g_VotesNeeded()
int iVoters = 0; int iVoters = 0;
for (int i=1; i<=MaxClients; i++) for (int i=1; i<=MaxClients; i++)
{ {
if (IsClientInGame(i) && !IsFakeClient(i) && !is_bot_player[i]) if (IsClientInGame(i) && !IsFakeClient(i) && !is_bot_player[i] && PM_IsPlayerSteam(i))
{ {
if (GetClientIdleTime(i) >= g_Cvar_AFKTime.IntValue) if (GetClientIdleTime(i) >= g_Cvar_AFKTime.IntValue)
continue; continue;
@@ -245,11 +249,15 @@ void UpdateRTV()
public int get_voted_rtv() public int get_voted_rtv()
{ {
int Votes = 0; int Votes = 0;
for (int i = 0; i < MaxClients; i++) for (int i = 0; i <= MaxClients; i++)
{ {
if (IsValidClient(i) && g_Voted[i]) if (IsValidClient(i) && g_Voted[i])
{ {
int player_worth_boost = GetPlayerWorthRTV_boost_(i); int player_worth_boost = GetPlayerWorthRTV_boost_(i);
if (player_worth_boost < 1)
{
player_worth_boost = 1;
}
Votes += player_worth_boost; Votes += player_worth_boost;
} }
} }
@@ -311,11 +319,11 @@ void AttemptRTV(int client)
return; return;
} }
update_g_VotesNeeded(); update_g_VotesNeeded();
int Votes = get_voted_rtv();
if (g_Voted[client]) if (g_Voted[client])
{ {
ReplyToCommand(client, "[RTVE] %t", "Already Voted", Votes, g_VotesNeeded, GetPlayerWorthRTV_boost_(client)); int Votes = get_voted_rtv();
ReplyToCommand(client, "[RTVE] You have already voted to Rock the Vote. (%i/%i Votes. (%i RTV boost))", Votes, g_VotesNeeded, GetPlayerWorthRTV_boost_(client));
return; return;
} }
@@ -324,9 +332,9 @@ void AttemptRTV(int client)
g_Voted[client] = true; g_Voted[client] = true;
Votes = get_voted_rtv(); int Votes = get_voted_rtv();
PrintToChatAll("[RTVE] %t", "RTV Requested", name, Votes, g_VotesNeeded, GetPlayerWorthRTV_boost_(client)); PrintToChatAll("[RTVE] %s wants to rock the vote. (%i/%i RTV reached) (%i RTV boost).", name, Votes, g_VotesNeeded, GetPlayerWorthRTV_boost_(client));
if (Votes >= g_VotesNeeded) if (Votes >= g_VotesNeeded)
{ {
@@ -481,7 +489,7 @@ public Action Command_DebugRTV(int client, int args)
for (int i=1; i<=MaxClients; i++) for (int i=1; i<=MaxClients; i++)
{ {
if (IsClientInGame(i) && !IsFakeClient(i) && !is_bot_player[i]) if (IsClientInGame(i) && !IsFakeClient(i) && !is_bot_player[i] && PM_IsPlayerSteam(i))
{ {
if (GetClientIdleTime(i) >= g_Cvar_AFKTime.IntValue) if (GetClientIdleTime(i) >= g_Cvar_AFKTime.IntValue)
continue; continue;
@@ -234,9 +234,10 @@ public MRESReturn TryPlayerMove_Dhook(Address pThis, Handle hReturn, Handle hPar
Address pFirstTrace = DHookGetParam(hParams, 2); Address pFirstTrace = DHookGetParam(hParams, 2);
DHookSetReturn(hReturn, TryPlayerMove(view_as<CGameMovement>(pThis), view_as<Vector>(pFirstDest), view_as<CGameTrace>(pFirstTrace))); DHookSetReturn(hReturn, TryPlayerMove(view_as<CGameMovement>(pThis), view_as<Vector>(pFirstDest), view_as<CGameTrace>(pFirstTrace)));
}
return MRES_Supercede; return MRES_Supercede;
}
return MRES_Ignored;
} }
int TryPlayerMove(CGameMovement pThis, Vector pFirstDest, CGameTrace pFirstTrace) int TryPlayerMove(CGameMovement pThis, Vector pFirstDest, CGameTrace pFirstTrace)
+189
View File
@@ -0,0 +1,189 @@
#pragma semicolon 1
#define PLUGIN_AUTHOR "jenz"
#define PLUGIN_VERSION "1.0"
#include <sourcemod>
Database g_hDatabase;
char g_cTimeRecords[18][256];
public Plugin myinfo =
{
name = "unloze top activity",
author = PLUGIN_AUTHOR,
description = "listing top activity on ze",
version = PLUGIN_VERSION,
url = "www.unloze.com"
};
public void OnPluginStart()
{
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats");
}
RegConsoleCmd("sm_activity", Command_topactivity, "retrieve top activity on ze for the week");
RegConsoleCmd("sm_myactivity", Command_myactivity, "retrieve your own activity on ze for the week");
CreateTimer(1800.0, update_top_activity, _, TIMER_REPEAT);
update_top_activity_ze();
}
public Action update_top_activity(Handle timer)
{
update_top_activity_ze();
return Plugin_Handled;
}
public void update_top_activity_ze()
{
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats");
return;
}
char sQuery[1024];
Format(sQuery, sizeof(sQuery), "select pt.steam_id, pt.player_name, SUM(pt.ze_time) - pa.ze_time_start_week AS time_this_week from unloze_playtimestats.player_activity_ze pa inner join unloze_playtimestats.player_time pt on pa.steamid = pt.steam_id where pa.steamid not in ('STEAM_0:0:518094602', 'STEAM_0:0:204398871', 'STEAM_0:1:60189040', 'STEAM_0:0:610560766') GROUP BY pt.steam_id ORDER BY time_this_week DESC limit 15");
g_hDatabase.Query(SQL_top_activity, sQuery, DBPrio_Low);
}
public void SQL_top_activity(Database db, DBResultSet results, const char[] error, int iSerial)
{
if (!db || strlen(error))
{
delete results;
LogError("Query error: %s", error);
return;
}
int counter = 0;
int counter_menu = 1;
Format(g_cTimeRecords[counter], sizeof(g_cTimeRecords[]), "Top activity: 5 days VIP");
counter++;
while (results.RowCount && results.FetchRow())
{
char sBuffer[528];
char sName[MAX_NAME_LENGTH];
results.FetchString(1, sName, sizeof(sName));
float time_this_week = results.FetchFloat(2);
int hours_server = RoundToFloor((time_this_week / 60) / 60);
int minutes_server = RoundToFloor((time_this_week / 60) % 60);
Format(sBuffer, sizeof(sBuffer), "%i %s %d Hours %d Minutes", counter_menu, sName, hours_server, minutes_server);
Format(g_cTimeRecords[counter], sizeof(g_cTimeRecords[]), sBuffer);
counter++;
counter_menu++;
if (counter == 6)
{
Format(g_cTimeRecords[counter], sizeof(g_cTimeRecords[]), "Top activity: 3 days VIP");
counter++;
}
if (counter == 11)
{
Format(g_cTimeRecords[counter], sizeof(g_cTimeRecords[]), "Top activity: 1 day VIP");
counter++;
}
}
delete results;
}
public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
{
if(!db || strlen(error))
{
LogError("Database error: %s", error);
return;
}
g_hDatabase = db;
char sQuery[1024];
Format(sQuery, sizeof(sQuery), "select pt.steam_id, pt.player_name, SUM(pt.ze_time) - pa.ze_time_start_week AS time_this_week from unloze_playtimestats.player_activity_ze pa inner join unloze_playtimestats.player_time pt on pa.steamid = pt.steam_id where pa.steamid not in ('STEAM_0:0:518094602', 'STEAM_0:0:204398871', 'STEAM_0:1:60189040', 'STEAM_0:0:610560766') GROUP BY pt.steam_id ORDER BY time_this_week DESC limit 15");
g_hDatabase.Query(SQL_top_activity, sQuery, DBPrio_Low);
}
public Action Command_myactivity(int client, int args)
{
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats");
return Plugin_Handled;
}
char sSID[64];
GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID));
char sQuery[1024];
Format(sQuery, sizeof(sQuery), "select pt.steam_id, pt.player_name, SUM(pt.ze_time) - pa.ze_time_start_week AS time_this_week from unloze_playtimestats.player_activity_ze pa inner join unloze_playtimestats.player_time pt on pa.steamid = pt.steam_id where pa.steamid = '%s' GROUP BY pt.steam_id ORDER BY time_this_week DESC limit 15", sSID);
g_hDatabase.Query(SQL_my_activity, sQuery, GetClientSerial(client));
return Plugin_Handled;
}
public void SQL_my_activity(Database db, DBResultSet results, const char[] error, int iSerial)
{
if (!db || strlen(error))
{
delete results;
LogError("Query error: %s", error);
return;
}
int client;
if ((client = GetClientFromSerial(iSerial)) == 0)
{
delete results;
return;
}
char sTitle[64];
Format(sTitle, sizeof(sTitle), "[UNLOZE your weekly activity ZE]");
Menu menu = new Menu(MenuHandler1);
menu.SetTitle(sTitle);
if (results.RowCount && results.FetchRow())
{
char sBuffer[528];
char sName[MAX_NAME_LENGTH];
results.FetchString(1, sName, sizeof(sName));
float time_this_week = results.FetchFloat(2);
int hours_server = RoundToFloor((time_this_week / 60) / 60);
int minutes_server = RoundToFloor((time_this_week / 60) % 60);
Format(sBuffer, sizeof(sBuffer), "%s %d Hours %d Minutes", sName, hours_server, minutes_server);
menu.AddItem("-1", sBuffer, ITEMDRAW_DISABLED);
}
delete results;
menu.ExitButton = true;
menu.Display(client, 0);
}
public Action Command_topactivity(int client, int args)
{
if (!g_hDatabase)
{
Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats");
return Plugin_Handled;
}
char sTitle[64];
Format(sTitle, sizeof(sTitle), "[UNLOZE weekly activity ZE]");
Menu menu = new Menu(MenuHandler1);
menu.SetTitle(sTitle);
for (int i = 0; i < sizeof(g_cTimeRecords); i++)
{
menu.AddItem("-1", g_cTimeRecords[i], ITEMDRAW_DISABLED);
}
menu.ExitButton = true;
menu.Display(client, 0);
return Plugin_Handled;
}
public int MenuHandler1(Menu menu, MenuAction action, int param1, int param2)
{
switch(action)
{
case MenuAction_End:
{
delete menu;
}
}
return 0;
}
+215
View File
@@ -0,0 +1,215 @@
"Trails"
{
"Dollars"
{
"material" "materials/sprites/store/trails/dollars.vmt"
"tier" "9"
}
"Donuts"
{
"material" "materials/sprites/store/trails/donuts.vmt"
"tier" "9"
}
"Double Rainbow"
{
"material" "materials/sprites/store/trails/doublerainbow.vmt"
"tier" "10"
}
"Dragon"
{
"material" "materials/sprites/store/trails/dragon.vmt"
"tier" "10"
}
"8Bit Mushroom"
{
"material" "materials/sprites/store/trails/8bitmushroom.vmt"
"tier" "11"
}
"Arrow Rainbow"
{
"material" "materials/sprites/store/trails/arrowrainbow.vmt"
"tier" "11"
}
"Arrows"
{
"material" "materials/sprites/store/trails/arrows.vmt"
"tier" "11"
}
"Assasin"
{
"material" "materials/sprites/store/trails/assasin.vmt"
"tier" "11"
}
"Awesome"
{
"material" "materials/sprites/store/trails/awesome.vmt"
"tier" "11"
"VIP" "1"
}
"8Bit Pow"
{
"material" "materials/sprites/store/trails/8bitpow.vmt"
"tier" "11"
"VIP" "1"
}
"Banknote"
{
"material" "materials/sprites/store/trails/banknote.vmt"
"tier" "12"
}
"Barcalona"
{
"material" "materials/sprites/store/trails/barcalona.vmt"
"tier" "12"
}
"Batman"
{
"material" "materials/sprites/store/trails/batman.vmt"
"tier" "12"
}
"Battlefield"
{
"material" "materials/sprites/store/trails/battlefield.vmt"
"tier" "12"
}
"Beer Mug"
{
"material" "materials/sprites/store/trails/beermug.vmt"
"tier" "12"
"VIP" "1"
}
"Blue Lightning"
{
"material" "materials/sprites/store/trails/bluelightning.vmt"
"tier" "13"
}
"BMW Logo"
{
"material" "materials/sprites/store/trails/bmwlogo.vmt"
"tier" "13"
}
"Bom Bomb"
{
"material" "materials/sprites/store/trails/bombomb.vmt"
"tier" "13"
}
"Boo"
{
"material" "materials/sprites/store/trails/boo.vmt"
"tier" "13"
"VIP" "1"
}
"Canada Flag"
{
"material" "materials/sprites/store/trails/canadaflag.vmt"
"tier" "14"
}
"Cereal-guy"
{
"material" "materials/sprites/store/trails/cereal-guy.vmt"
"tier" "14"
}
"Challenge"
{
"material" "materials/sprites/store/trails/challenge.vmt"
"tier" "14"
}
"Coca-Cola"
{
"material" "materials/sprites/store/trails/cocacola.vmt"
"tier" "14"
}
"Cookies"
{
"material" "materials/sprites/store/trails/cookies.vmt"
"tier" "15"
}
"Cracked Beam"
{
"material" "materials/sprites/store/trails/crackedbeam.vmt"
"tier" "15"
}
"Creed"
{
"material" "materials/sprites/store/trails/creed.vmt"
"tier" "15"
}
"Creeper"
{
"material" "materials/sprites/store/trails/creeper.vmt"
"tier" "15"
}
"Crown"
{
"material" "materials/sprites/store/trails/crown.vmt"
"tier" "15"
}
"Danish Flag"
{
"material" "materials/sprites/store/trails/danishflag.vmt"
"VIP" "1"
}
"Dark Knight"
{
"material" "materials/sprites/store/trails/darkknight.vmt"
"VIP" "1"
}
"Coca-Cola 2"
{
"material" "materials/sprites/store/trails/cocacola2.vmt"
"VIP" "1"
}
"Bullet Bill"
{
"material" "materials/sprites/store/trails/bulletbill.vmt"
"VIP" "1"
}
"Biohazard"
{
"material" "materials/sprites/store/trails/biohazard.vmt"
"VIP" "1"
}
"Apple"
{
"material" "materials/sprites/store/trails/apple.vmt"
"VIP" "1"
}
"Airforce"
{
"material" "materials/sprites/store/trails/airforce.vmt"
"VIP" "1"
}
"Airforce 2"
{
"material" "materials/sprites/store/trails/airforce2.vmt"
"VIP" "1"
}
}
+478
View File
@@ -0,0 +1,478 @@
#pragma semicolon 1
#define PLUGIN_AUTHOR "jenz"
#define PLUGIN_VERSION "1.0"
#include <unloze_playtime>
#include <clientprefs>
#include <mapchooser_extended>
#include <zombiereloaded>
#include <sdkhooks>
#include <sdktools>
bool g_bTrailsEnabled;
bool g_bRoundStartSpawnTrails;
int g_iSpriteModel[MAXPLAYERS + 1] = {-1, ...};
int g_iClientTrail[MAXPLAYERS + 1] = { -1, ... }; // index into g_hTrails, -1 = none
float g_fTrailsLifeTime = 1.0;
int g_iTrailsStartWidth = 10;
int g_iTrailsEndWidth = 6;
Handle g_hCookie_trailName;
Handle g_hCookie_trailHide;
bool g_bHideTrails[MAXPLAYERS + 1];
public Plugin myinfo =
{
name = "unloze trails",
author = PLUGIN_AUTHOR,
description = "adds trails based on tier, vip and maps",
version = PLUGIN_VERSION,
url = "www.unloze.com"
};
enum struct TrailData
{
char name[128];
char material[512];
int tier;
int vip;
}
ArrayList g_hTrails;
public void OnPluginStart()
{
RegConsoleCmd("sm_trails", Command_Trail, "The Trails menu");
RegConsoleCmd("sm_trail", Command_Trail, "The Trails menu");
HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy);
HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);
HookEvent("player_death", PlayerDeath);
g_hCookie_trailName = RegClientCookie("unloze_trail", "Select your trail", CookieAccess_Protected);
g_hCookie_trailHide = RegClientCookie("unloze_trail_hide", "Hide trails", CookieAccess_Protected);
SetCookieMenuItem(MenuHandler_CookieMenu, 0, "Unloze Trails");
g_hTrails = new ArrayList(sizeof(TrailData));
ConVar cvar;
HookConVarChange((cvar = CreateConVar("sm_unloze_trails_lifetime", "1.0", "Lifetime of trails")), Cvar_TrailsLifeTime);
g_fTrailsLifeTime = cvar.FloatValue;
delete cvar;
ConVar cvar1;
HookConVarChange((cvar1 = CreateConVar("sm_unloze_trails_start_width", "10", "Start width of trails")), Cvar_TrailsStartWidth);
g_iTrailsStartWidth = cvar1.IntValue;
delete cvar1;
ConVar cvar2;
HookConVarChange((cvar2 = CreateConVar("sm_unloze_trails_end_width", "6", "End width of trails")), Cvar_TrailsEndWidth);
g_iTrailsEndWidth = cvar2.IntValue;
delete cvar2;
}
public void Cvar_TrailsEndWidth(ConVar convar, const char[] oldValue, const char[] newValue)
{
g_iTrailsEndWidth = convar.IntValue;
}
public void Cvar_TrailsStartWidth(ConVar convar, const char[] oldValue, const char[] newValue)
{
g_iTrailsStartWidth = convar.IntValue;
}
public void Cvar_TrailsLifeTime(ConVar convar, const char[] oldValue, const char[] newValue)
{
g_fTrailsLifeTime = convar.FloatValue;
}
public void MenuHandler_CookieMenu(int client, CookieMenuAction action, any info, char[] buffer, int maxlen)
{
switch (action)
{
case CookieMenuAction_DisplayOption:
{
Format(buffer, maxlen, "Trails");
}
case CookieMenuAction_SelectOption:
{
if (!g_bTrailsEnabled)
{
PrintToChat(client, "[Trails] Trails are disabled on this map.");
return;
}
OpenTrailsMenu(client);
}
}
}
public void PlayerDeath(Handle:event,const String:name[],bool:dontBroadcast)
{
int client = GetClientOfUserId(GetEventInt(event, "userid"));
KillTrail(client);
}
public void Event_RoundEnd(Handle event, const char[] name, bool dontBroadcast)
{
if (!g_bTrailsEnabled)
{
return;
}
for (int client = 1; client <= MAXPLAYERS; client++)
{
KillTrail(client);
}
}
public void Event_RoundStart(Handle event, const char[] name, bool dontBroadcast)
{
if (!g_bTrailsEnabled)
{
return;
}
g_bRoundStartSpawnTrails = false;
//From Round start we wait 5 seconds before spawning trails so map has time to load its edicts for the round.
CreateTimer(5.0, allow_spawning_trails);
}
public Action allow_spawning_trails(Handle hTimer)
{
g_bRoundStartSpawnTrails = true;
for (int client = 1; client <= MAXPLAYERS; client++)
{
if (IsValidClient(client) && g_iClientTrail[client] != -1 && IsPlayerAlive(client) && ZR_IsClientHuman(client))
{
SpawnTrail(client);
}
}
return Plugin_Handled;
}
public Action Command_Trail(int client, int args)
{
if (!g_bTrailsEnabled)
{
PrintToChat(client, "[Trails] Trails are disabled on this map.");
return Plugin_Handled;
}
OpenTrailsMenu(client);
return Plugin_Handled;
}
void OpenTrailsMenu(int client)
{
Menu menu = new Menu(MenuHandler_Trails);
menu.SetTitle("Trails Menu");
if (g_bHideTrails[client])
{
menu.AddItem("-1", "Unhide Trails");
}
else
{
menu.AddItem("-1", "Hide Trails");
}
int count = g_hTrails.Length;
int clientTier = GetPlayerTier_native(client);
bool vipAccess = CheckCommandAccess(client, "", ADMFLAG_RESERVATION, false);
for (int i = 0; i < count; i++)
{
TrailData trail;
g_hTrails.GetArray(i, trail);
char info[128];
IntToString(i, info, sizeof(info));
if (trail.tier > clientTier)
{
char displayName[128];
Format(displayName, sizeof(displayName), "%s (Tier %d)", trail.name, trail.tier);
menu.AddItem(info, displayName, ITEMDRAW_DISABLED);
}
else if (trail.vip && !vipAccess)
{
char displayName[128];
Format(displayName, sizeof(displayName), "%s (VIP)", trail.name);
menu.AddItem(info, displayName, ITEMDRAW_DISABLED);
}
else
{
menu.AddItem(info, trail.name);
}
}
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
public int MenuHandler_Trails(Menu menu, MenuAction action, int param1, int param2)
{
switch (action)
{
case MenuAction_Select:
{
char info[8];
menu.GetItem(param2, info, sizeof(info));
int index = StringToInt(info);
g_iClientTrail[param1] = index;
if (index == -1)
{
g_bHideTrails[param1] = !g_bHideTrails[param1];
PrintToChat(param1, "Trails are now %s", g_bHideTrails[param1]? "Hidden": "Unhidden");
SetClientCookie(param1, g_hCookie_trailName, "");
KillTrail(param1);
return 0;
}
else
{
TrailData trail;
g_hTrails.GetArray(index, trail);
PrintToChat(param1, "[Trails] Trail set to: %s", trail.name);
SetClientCookie(param1, g_hCookie_trailName, trail.name);
if (IsPlayerAlive(param1) && ZR_IsClientHuman(param1))
{
SpawnTrail(param1);
}
else
{
PrintToChat(param1, "Not Applying trail right now since you are not alive as human.");
}
}
//OpenTrailsMenu(param1); // reopen
}
case MenuAction_End:
{
delete menu;
}
}
return 0;
}
public OnMapEnd()
{
for (int client = 1; client <= MAXPLAYERS; client++)
{
KillTrail(client);
}
for (int client = 1; client <= MAXPLAYERS; client++)
{
g_iSpriteModel[client] = -1;
}
}
public void OnClientDisconnect(client)
{
g_bHideTrails[client] = false;
KillTrail(client);
g_iClientTrail[client] = -1;
}
public void OnClientCookiesCached(int client)
{
char sBuffer[512];
GetClientCookie(client, g_hCookie_trailHide, sBuffer, sizeof(sBuffer));
if (sBuffer[0])
{
g_bHideTrails[client] = StringToInt(sBuffer) != 0 ? true : false;
}
else
{
g_bHideTrails[client] = false;
}
GetClientCookie(client, g_hCookie_trailName, sBuffer, sizeof(sBuffer));
if (sBuffer[0])
{
g_iClientTrail[client] = -1;
int count = g_hTrails.Length;
for (int i = 0; i < count; i++)
{
TrailData trail;
g_hTrails.GetArray(i, trail);
if (StrEqual(trail.name, sBuffer, true))
{
g_iClientTrail[client] = i;
break;
}
}
}
else
{
g_iClientTrail[client] = -1;
}
}
public void OnClientPostAdminCheck(int client)
{
g_iSpriteModel[client] = -1;
}
public void OnMapStart()
{
char Mapname[128];
GetCurrentMap(Mapname, sizeof(Mapname));
g_bTrailsEnabled = GetMapTrails(Mapname) != 0 ? true : false;
LoadTrails();
}
public void LoadTrails()
{
g_hTrails.Clear();
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "configs/unloze_trails.cfg");
KeyValues kv = new KeyValues("Trails");
if (!kv.ImportFromFile(path))
{
LogError("[trails] Could not load %s", path);
delete kv;
return;
}
// Enter the root "Trails" section, then iterate its children
if (kv.GotoFirstSubKey())
{
do
{
TrailData trail;
kv.GetSectionName(trail.name, sizeof(trail.name));
kv.GetString("material", trail.material, sizeof(trail.material), "");
trail.tier = kv.GetNum("tier", 0);
trail.vip = kv.GetNum("VIP", 0);
g_hTrails.PushArray(trail);
PrecacheModel(trail.material, false);
AddFileToDownloadsTable(trail.material);
char vtf_download[512];
strcopy(vtf_download, sizeof(vtf_download), trail.material);
//modifying last two characters so its .vtf
int len = strlen(vtf_download);
vtf_download[len - 2] = 't';
vtf_download[len - 1] = 'f';
AddFileToDownloadsTable(vtf_download);
}
while (kv.GotoNextKey());
}
delete kv;
}
public void ZR_OnClientInfected(int client, int attacker, bool motherInfect, bool respawnOverride, bool respawn)
{
KillTrail(client);
}
public void KillTrail(int client)
{
if (g_iSpriteModel[client] > MaxClients && IsValidEdict(g_iSpriteModel[client]))
{
SDKUnhook(g_iSpriteModel[client], SDKHook_SetTransmit, Hook_TrailShouldHide);
AcceptEntityInput(g_iSpriteModel[client], "kill");
}
g_iSpriteModel[client] = -1;
}
public void SpawnTrail(int client)
{
KillTrail(client);
if (!g_bRoundStartSpawnTrails)
{
PrintToChat(client, "Cant spawn trails this early in the round.");
return;
}
//we got 2048 edicts in total
int edict_limit = 1700;
int edict_count = GetEntityCount();
if (edict_count > edict_limit)
{
PrintToChat(client, "Not spawning your trail due to edict limitations");
return;
}
g_iSpriteModel[client] = CreateEntityByName("env_spritetrail");
if (g_iSpriteModel[client] != -1)
{
DispatchKeyValueFloat(g_iSpriteModel[client], "lifetime", g_fTrailsLifeTime);
char startwidth[16];
Format(startwidth, sizeof(startwidth), "%i", g_iTrailsStartWidth);
DispatchKeyValue(g_iSpriteModel[client], "startwidth", startwidth);
char endwidth[16];
Format(endwidth, sizeof(endwidth), "%i", g_iTrailsEndWidth);
DispatchKeyValue(g_iSpriteModel[client], "endwidth", endwidth);
TrailData trail;
g_hTrails.GetArray(g_iClientTrail[client], trail);
DispatchKeyValue(g_iSpriteModel[client], "spritename", trail.material);
DispatchKeyValue(g_iSpriteModel[client], "renderamt", "255");
DispatchKeyValue(g_iSpriteModel[client], "rendercolor", "255 255 255 255");
DispatchKeyValue(g_iSpriteModel[client], "rendermode", "1");
// We give the name for our entities here
char buffer[1024];
Format(buffer, sizeof(buffer), "unloze_trails_%d", g_iSpriteModel[client]);
DispatchKeyValue(g_iSpriteModel[client], "targetname", buffer);
DispatchSpawn(g_iSpriteModel[client]);
float dest_vector[3];
dest_vector[0] = 0.0;
dest_vector[1] = 0.0;
dest_vector[2] = 5.0;
float or[3];
float ang[3];
float fForward[3];
float fRight[3];
float fUp[3];
GetClientAbsOrigin(client, or);
GetClientAbsAngles(client, ang);
GetAngleVectors(ang, fForward, fRight, fUp);
or[0] += fRight[0]*dest_vector[0] + fForward[0]*dest_vector[1] + fUp[0]*dest_vector[2];
or[1] += fRight[1]*dest_vector[0] + fForward[1]*dest_vector[1] + fUp[1]*dest_vector[2];
or[2] += fRight[2]*dest_vector[0] + fForward[2]*dest_vector[1] + fUp[2]*dest_vector[2];
TeleportEntity(g_iSpriteModel[client], or, NULL_VECTOR, NULL_VECTOR);
SetVariantString("!activator");
AcceptEntityInput(g_iSpriteModel[client], "SetParent", client);
SetEntPropFloat(g_iSpriteModel[client], Prop_Send, "m_flTextureRes", 0.05);
SetEntPropEnt(g_iSpriteModel[client], Prop_Send, "m_hOwnerEntity", client);
SDKHook(g_iSpriteModel[client], SDKHook_SetTransmit, Hook_TrailShouldHide);
}
}
public Action Hook_TrailShouldHide(int entity, int client)
{
if (g_bHideTrails[client])
{
return Plugin_Handled;
}
return Plugin_Continue;
}
stock bool IsValidClient(int client)
{
if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
return true;
return false;
}