mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-05 11:43:22 +00:00
Updates
This commit is contained in:
parent
fd2367f41f
commit
79d37bdd34
45 changed files with 5587 additions and 3877 deletions
72
.gitignore
vendored
72
.gitignore
vendored
|
@ -1,36 +1,36 @@
|
|||
sm_*
|
||||
.*/
|
||||
scripting_ext/
|
||||
!scripting/include/guesswho/*
|
||||
!scripting/include/feedthetrolls
|
||||
!scripting/include/hideandseek
|
||||
!scripting/include/guessswho/*
|
||||
!scripting/hideandseek.sp
|
||||
!scripting/guessswho.sp
|
||||
!scripting/sm_namespamblock.sp
|
||||
!plugins/sm_namespamblock.smx
|
||||
.push.settings.jsonc*
|
||||
template.config.js
|
||||
scripting/sm_give.sp
|
||||
scripting/include/steamtools.inc
|
||||
scripting/L4D2Testing.sp
|
||||
plugins/L4D2Testing.smx
|
||||
plugins/sm_give.smx
|
||||
plugins/l4d2_stats_recorder.smx
|
||||
scripting/l4d2_stats_recorder.sp
|
||||
scripting/include/smlib.inc
|
||||
scripting/l4d2_custom.sp
|
||||
plugins/l4d2_custom.smx
|
||||
plugins/disable_cameras.smx
|
||||
plugins/l4d_esfp.smx
|
||||
plugins/customstatus.smx
|
||||
scripting/l4d_esfp.sp
|
||||
scripting/customstatus.sp
|
||||
plugins/ssh.smx
|
||||
scripting/include/ssh.inc
|
||||
scripting/include/stats
|
||||
scripting/ssh.sp
|
||||
scripting/l4d2_witch_force_attack_cmd.sp
|
||||
l4d2_stats_plugin/
|
||||
data
|
||||
!sql/*
|
||||
sm_*
|
||||
.*/
|
||||
scripting_ext/
|
||||
!scripting/include/guesswho/*
|
||||
!scripting/include/feedthetrolls
|
||||
!scripting/include/hideandseek
|
||||
!scripting/include/guessswho/*
|
||||
!scripting/hideandseek.sp
|
||||
!scripting/guessswho.sp
|
||||
!scripting/sm_namespamblock.sp
|
||||
!plugins/sm_namespamblock.smx
|
||||
.push.settings.jsonc*
|
||||
template.config.js
|
||||
scripting/sm_give.sp
|
||||
scripting/include/steamtools.inc
|
||||
scripting/L4D2Testing.sp
|
||||
plugins/L4D2Testing.smx
|
||||
plugins/sm_give.smx
|
||||
plugins/l4d2_stats_recorder.smx
|
||||
scripting/l4d2_stats_recorder.sp
|
||||
scripting/include/smlib.inc
|
||||
scripting/l4d2_custom.sp
|
||||
plugins/l4d2_custom.smx
|
||||
plugins/disable_cameras.smx
|
||||
plugins/l4d_esfp.smx
|
||||
plugins/customstatus.smx
|
||||
scripting/l4d_esfp.sp
|
||||
scripting/customstatus.sp
|
||||
plugins/ssh.smx
|
||||
scripting/include/ssh.inc
|
||||
scripting/include/stats
|
||||
scripting/ssh.sp
|
||||
scripting/l4d2_witch_force_attack_cmd.sp
|
||||
l4d2_stats_plugin/
|
||||
data
|
||||
!sql/*
|
||||
|
|
932
README.md
932
README.md
|
@ -1,466 +1,466 @@
|
|||
# Sourcemod L4D2 Plugins
|
||||
This is a collection of sourcemod plugins, most are used on my servers. The majority of the plugins are created by me, but some are modifications of other plugins.
|
||||
Some of the plugins / changes are very specific, but may be useful to someone.
|
||||
|
||||
Not always the latest versions. If you have any interest with a plugin, I can make sure to upload the latest.
|
||||
|
||||
Useful things:
|
||||
1. **Netprop Viewer** [L4D2 Netprops](https://jackz.me/netprops/l4d2) and [L4D2 Datamaps](https://jackz.me/netprops/l4d2-data)
|
||||
|
||||
## Plugin List
|
||||
|
||||
### Created by Me
|
||||
* [l4d2-manual-director](#l4d2-manual-director) - Spawn specials on demand via director or at your cursor
|
||||
* [l4d2-info-cmd](#l4d2-info-cmd) - Prints a full state of all survivors, useful for external information
|
||||
* [AutoWarpBot](#autowarpbot) - Abandoned
|
||||
* [L4D2FFKickProtection](#l4d2ffkickprotection) - Prevents players being voted off from friendly firing and prevents admins from being kicked
|
||||
* [l4d2_avoid_minigun](#l4d2_avoid_minigun) - Makes bots avoid being infront of any in-use miniguns. Useful for spawned miniguns
|
||||
* [l4d2_ai_minigun](#l4d2_ai_minigun) - Based off [Silver's Survivor Bot Holdout plugin](https://forums.alliedmods.net/showthread.php?p=1741099), allows you to spawn survivor bots but with no limit.
|
||||
* [L4D2Tools](#l4d2tools) - A collection of utilities, mostly just used with [l4d_survivor_identity_fix](#l4d_survivor_identity_fix) and the /model command
|
||||
* [l4d2_swarm](#l4d2_swarm) - Uses vscript RushVictim to make all zombies target a player, like a more subtle vomitplayer
|
||||
* [l4d2_feedthetrolls](#l4d2_feedthetrolls) - Full collection of tools to troll your friends or troll the trolls
|
||||
* [l4d2_autobotcrown](#l4d2_autobotcrown) - Bots will auto crown
|
||||
* [l4d2_extraplayeritems](#l4d2_extraplayeritems) - Includes tons of utilities for 5+ games, such as 5+ player hud, extra kit / item spawning, and more
|
||||
* [l4d2_population_control](#l4d2_population_control) - Allows you to custom the type of zombies that spawn (% of clowns, mud men, etc..)
|
||||
* [globalbans](#globalbans) - Bans synced via mysql, way lighter than the sourcebans cesspool.
|
||||
* [l4d2_rollback](#l4d2_rollback) - Abandoned and broken, but makes periodic backup of all player's items
|
||||
* [l4d2_autorestart](#l4d2_autorestart) - Restarts server if it's been on for a certain uptime or when empty with just bots.
|
||||
* [l4d2_TKStopper](#l4d2_tkstopper) - All the teamkiller and shitty-aim player punishments. Auto increasing reverse ff and teamkill detection
|
||||
* [l4d2_crescendo_control](#l4d2_crescendo_control) - Prevents players from running far ahead and starting events, and logs button presses
|
||||
* [l4d2_vocalize_control](#l4d2_vocalize_control) - Allows you to locally mute someone from vocalizing
|
||||
* [l4d2_guesswho](#l4d2_guesswho) - Garry's Mod's Guess Who in l4d2, inspired by hide and seek
|
||||
* [l4d2_hideandseek](#l4d2_hideandseek) - An enhancement to the base hide and seek mutation
|
||||
* [l4d2_hats](#l4d2_hats) - Entity Hats & Entity editing
|
||||
* [l4d2_prophunt](#l4d2_prophunt) - Garry's Mod inspired prop hunt, inspired by hide and seek
|
||||
* [sm_namespamblock](#sm_namespamblock) - Basic plugin that bans players if they change their name in rapid succession
|
||||
* [l4d2-stats-plugin](https://github.com/jackzmc/l4d2-stats-plugin) - Custom stats recorder, see https://stats.jackz.me
|
||||
* [l4d2-ai-tweaks](#l4d2_ai_tweaks) - Very minor tweaks to survivor bots' behavior
|
||||
* [sm_player_notes](#sm_player_notes) - Add notes to players
|
||||
|
||||
### Modified Others
|
||||
* [200IQBots_FlyYouFools](#200iqbots_flyyoufools) - Improved code to make it support multiple tanks and work better
|
||||
* [l4d_survivor_identity_fix](#l4d_survivor_identity_fix) - Use with [L4D2Tools](#l4d2tools) to change models, some fixes
|
||||
* [BetterWitchAvoidance](#betterwitchavoidance)
|
||||
* l4d_anti_rush - Modified plugin to add a forward, so other plugins (like feedthetrolls) can do something. In addition, use highest flow value achieved for players (fixes issue when admins go back and players who haven't moved suddenly get punished)
|
||||
* [l4d2_sb_fix](#l4d2_sb_fix) - Updated to 1.11 & latest sourcepawn syntax & removed the FCVAR_NOTIFY from all cvars (why is that added?)
|
||||
* GrabEnt - Improved version that prevents moving certain entities (such as invisual walls, ragdolls, etc) and improved some code
|
||||
|
||||
## Dependencies
|
||||
This is a list of most common dependencies, independent if they are used for a certain plugin.
|
||||
Check the plugin info for an exact list.
|
||||
|
||||
* [Left 4 Dhooks Direct](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
|
||||
### Development Dependencies
|
||||
Most L4D2 plugins use my own include: jutils.inc, it's provided in this repo.
|
||||
Some do require newer includes for my modified plugins (such as my improved survivor identity fix)
|
||||
|
||||
## Descriptions
|
||||
|
||||
### l4d2-manual-director
|
||||
~~Probably going to be posted publicly sometime.~~ Allows you to spawn specials on cursor, or via director, forcefully, bypassing limits
|
||||
* **Convars:**
|
||||
* `manual_director_version|mandirector_version` - ... gets version
|
||||
* `mandirector_notify_spawn <1/0>` - Should spawning specials notify on use?
|
||||
* `mandirector_announce_level <0/1/2/3>` - Announcement types. 0 - None, 1 - Only bosses, 2 - Only specials+, 3 - Everything
|
||||
* `mandirector_enable_tank <0/1>` - Should tanks be allowed to be spawned?
|
||||
* `mandirector_enable_witch <0/1>` - Should witches be allowed to be spawned?
|
||||
* `mandirector_enable_mob <0/1>` - Should mobs be allowed to be spawned
|
||||
* **Commands:**
|
||||
* `sm_spawnspecial <special> [amount]` - Spawn a special via director
|
||||
* `sm_forcespecial <special> [amount]` - Force spawn a special via director, bypassing spawn limits
|
||||
* `sm_forcecursor <special> [amount]` - Force spawn a special at cursor, bypassing spawn limits
|
||||
* `sm_cursormenu` - Show the spawn menu for cursor spawning
|
||||
* `sm_specialmenu` - Show the spawn menu for director spawning
|
||||
* `sm_directormenu` (Same as sm_specialmenu for now)
|
||||
|
||||
|
||||
### l4d2-info-cmd
|
||||
Technically 'l4d2 game info', haven't changed name. Just prints general information, used for a project
|
||||
* **Commands:**
|
||||
* `sm_gameinfo`
|
||||
* Example Response:
|
||||
```
|
||||
>map,diff,mode,tempoState,totalSeconds
|
||||
c1m1_hotel,1,coop,3,1622
|
||||
>id,name,bot,health,status,throwSlot,kitSlot,pillSlot,survivorType,velocity,primaryWpn,secondaryWpn
|
||||
1,Jackz,0,80,alive,0,,first_aid_kit,,Bill,0,,pistol
|
||||
3,Zoey,1,75,alive,0,,first_aid_kit,,Zoey,0,,pistol
|
||||
4,Francis,1,76,alive,0,,,,Francis,0,,pistol
|
||||
5,Louis,1,90,alive,0,,first_aid_kit,,Louis,0,,pistol
|
||||
```
|
||||
|
||||
|
||||
### AutoWarpBot
|
||||
Simple l4d2 plugin that will auto teleport bots to checkpoint once all real players have reached the saferoom.
|
||||
Doesn't really work well. Abandoned.
|
||||
|
||||
|
||||
### 200IQBots_FlyYouFools
|
||||
Updated version of ConnerRia's plugin. Improves bots avoidance of tanks. Change from original is updated sourcepawn syntax, some optimizations/cleanup, and fixes such as bots avoiding tank that has not been activated, or not escaping in vehicle due to presence of tank.
|
||||
Latest version now has support for multiple tanks, the bots might not avoid them as effectively as they would with one tank but they still try their best.
|
||||
* **Convars:**
|
||||
* `FlyYouFools_Version` - Prints the version of plugin
|
||||
|
||||
|
||||
### BetterWitchAvoidance
|
||||
Inspired by the 200IQBots_FlyYouFools. Bots avoid witch if its over 40% anger when close, or a little bigger range at 60% or more. Not recommended to use, normal behavior seems fine.
|
||||
|
||||
|
||||
### L4D2FFKickProtection
|
||||
Simple plugin that prevents a player that is being vote-kicked from doing any ff damage to teammates.
|
||||
It also prevents vote kicking of admins, instead will notify admins.
|
||||
It also makes any vote kicks created by an admin to instantly be accepted by all players
|
||||
|
||||
* **Convars:**
|
||||
* `sm_votekick_force_threshold <#>` - The threshold of damage where the offending player is just immediately kicked. 0 -> Any attempted damage, -1 -> No auto kick.
|
||||
|
||||
### l4d2_avoid_minigun
|
||||
Makes the bots avoid standing in front of or on top of the player that is using a minigun. It checks every 2.0 seconds if they are in the way, then forces them to move to behind you. There is no configuration, all automatic.
|
||||
|
||||
|
||||
### l4d2_ai_minigun
|
||||
Requires: [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
Spawn the holdout bots used in the passing. This supports all 8 characters, including with the minigun. They can spawn with any weapon or default to ak47.
|
||||
|
||||
**Notes:**
|
||||
* The minigun holdout bot has to internally be Louis, so it will be Louis making sounds, with whatever model specified being shown. This doesn't apply for normal holdout bot.
|
||||
* \<survivor name> can be "bill" or their numeric id (4).
|
||||
|
||||
Code modified from https://forums.alliedmods.net/showthread.php?p=1741099
|
||||
|
||||
* **Commands:**
|
||||
* `sm_ai_minigun <survivor name>` - Spawns an ai bot with minigun infront of wherever you are looking. Can also use numbers (0-7).
|
||||
* `sm_ai_holdout <survivor name> [wpn]` - Spawns a normal ai holdout bot (no minigun), with any weapon w/ laser sight (default is ak).
|
||||
* `sm_ai_remove_far` - Removes any holdout or minigun bots that are 750 units or more from any player.
|
||||
|
||||
|
||||
### L4D2Tools
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
* [Modified L4D Survivor Identity Fix](#l4d_survivor_identity_fix)
|
||||
|
||||
A collection of small tools:
|
||||
* Notification of when someone picks up laser sights (only the first user, includes bots),
|
||||
* Record time it takes for a finale or gauntlet run to be completed.
|
||||
* Record the amount of friendly fire damage done
|
||||
* Set the survivor models of any survivor with updated [l4d_survivor_identity_fix](#l4d_survivor_identity_fix)
|
||||
* Automatically returns melee weapons that an idle bot dropped once no longer idle
|
||||
* Automatically make players go idle when ping spikes
|
||||
* Slowly kill any zombies attacking survivor bot's blind spots (Fixes brain dead bots stuck taking damage and not killing them)
|
||||
|
||||
* **Convars:**
|
||||
* `sm_laser_use_notice <0/1>` - Enable notification of when a laser box was used first
|
||||
* `sm_time_finale <0/1/2>` - Record the time it takes to complete finale. 0 -> OFF, 1 -> Gauntlets Only, 2 -> All finales
|
||||
* `sm_ff_notice <0/1/2>` - Should we record FF damages? 0: OFF, 1: To chat, 2: To HUD text.
|
||||
* `sm_autoidle_ping_max <30.0...>` - "The highest ping a player can have until they will automatically go idle.\n0=OFF, Min is 30
|
||||
* **Commands:**
|
||||
* `sm_model <player> <character>` - Sets the survivor model of the target player(s). 'character' is name or ID of character.
|
||||
* `sm_surv <player> <character>` - Sets the m_survivorCharacter prop only of the target player(s). 'character' is name or ID of character.
|
||||
|
||||
|
||||
### l4d2_swarm
|
||||
This plugin is used to counter trolls and otherwise bad players. It simply uses the new script function RushVictim() to make all zombies in X radius attack Y target. It's that simple.
|
||||
|
||||
This really only affects wandering zombies, mobs and panic events, but it may work slightly when bile or pipes are thrown. It does not and can't change the targets of zombies.
|
||||
|
||||
* **Convars:**
|
||||
* `sm_swarm_default_range <20-Infinity>` - The default range for commands & menus. Defaults to 7,500
|
||||
* **Commands:**
|
||||
* `sm_swarm [player] [range]` - Swarm a player, or random if none."
|
||||
* Aliases: `sm_rush`
|
||||
* `sm_rushmenu` - Opens a menu to quickly rush any player. Can be bound to a key to quickly rush as well
|
||||
* Aliases: `sm_rmenu`
|
||||
* `sm_swarmtoggle <player> [range]` - Will continuously run the swarm method on the player at the range. Use the command again or type "disable" for player to disable. Switching players will not disable, just switches target.
|
||||
* Aliases: `sm_rushtoggle`, `sm_rt`
|
||||
* `sm_rushtogglemenu` - Will open a menu to quickly select a player to continuously rush.
|
||||
* Aliases: `sm_rtmenu`
|
||||
|
||||
|
||||
### l4d2_feedthetrolls
|
||||
Requires:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* (Optional) [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
* (Optional) [Actions](https://forums.alliedmods.net/showthread.php?t=336374)
|
||||
* (Optional) [Modified L4D Antirush](#l4d_anti_rush)
|
||||
|
||||
This plugin allows you to enact certain troll modes on any player, some are subtle some are less so. Either way, it works great to deal with a rusher, an asshole or even your friends.
|
||||
|
||||
See the up-to-date list of trolls and their descriptions:
|
||||
https://admin.jackz.me/docs/ftt
|
||||
|
||||
* **Convars:**
|
||||
* `sm_ftt_autopunish_mode <#>` - (Not used, WIP) Sets the modes that auto punish will activate for. 1 -> Early crescendo activations
|
||||
* `sm_ftt_autopunish_action <#>` - Which actions will autopunish activate? Add bits together. 0=None, 1=TankMagnet 2=SpecialMagnet 4=Swarm 8=VomitPlayer
|
||||
* `sm_ftt_autopunish_expires <0...>` - How many minutes (in gameticks) until autopunish trolls are removed. 0 for never.
|
||||
* **Commands:**
|
||||
* `sm_fta [player]` - The main command, opens a menu to select a troll to apply, with modifiers and flags
|
||||
* `sm_ftr [player]` - Removes all active trolls from a player
|
||||
* `sm_ftc [player]` - Opens a menu to select a combo of trolls
|
||||
* `sm_ftl` - Lists all players that have a mode applied.
|
||||
* `sm_ftm` - Lists all troll options & their descriptions
|
||||
* `sm_mark` - Toggles marking a player to be banned when they fully disconnect
|
||||
* `sm_insta [player] [special]` - (No arguments opens menu) - Spawns a special via the director that will only target the victim
|
||||
* `sm_inface [player] [special]` - Identical to above, but special will be spawned as close as possible to survivor. Boomers auto explode, jockeys on their head, etc.
|
||||
* `sm_bots_attack <player> [target health]` - Slightly broken, but makes all bots shoot at player until they hit X health or a timeout is reached. Turn on `sb_friendlyfire` for it to be effective.
|
||||
* `sm_stagger <player>` - Makes a player stagger, shortcut to the Stagger troll
|
||||
* `sm_witch_attack <player>` - Makes all witches agro on the player
|
||||
* `sm_scharge <player> [timeout seconds]` - Will wait till there's no obstructions and players in the way, then spawns a charger behind them to charge them.
|
||||
* `sm_healbots <player> [# bots or 0 default]` - Makes n amount of bots chase a player down to heal them. Won't stop until they are healed, they die, or you run command again.
|
||||
* `sm_csplat <player> <top/front/back> - Shortcut to Car Splat. Spawns car in top/front/back that hits the player`
|
||||
|
||||
### l4d2_autobotcrown
|
||||
Makes any suitable bot (> 40 hp, has shotgun) automatically crown a witch. Supports multiple bots and witches, but only one bot can crown one witch at a time. Plugin is disabled in realism, and is really on suitable for coop or versus. Even works with idle players.
|
||||
|
||||
Bots do sometimes miss, but sometimes still manage to kill witch. They also don't care if there is danger in the way (fire, acid, angry witch).
|
||||
|
||||
* **Convars:**
|
||||
* `l4d2_autocrown_allowed_difficulty <default: 7>` - The difficulties the plugin is active on. 1=Easy, 2=Normal 4=Advanced 8=Expert. Add numbers together.
|
||||
* `l4d2_autocrown_modes_tog <default: 7>` - (Not implemented) - Turn on the plugin in these game modes. 0=All, 1=Coop, 2=Survival, 4=Versus, 8=Scavenge. Add numbers together
|
||||
|
||||
|
||||
### l4d2_extraplayeritems
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [L4D Info Editor](https://forums.alliedmods.net/showthread.php?p=2614626)
|
||||
* (Development dependency) Updated l4d2_weapon_stocks.inc
|
||||
|
||||
A well rounded tool that provides extra utilities to a 5+ co-op campaign.
|
||||
|
||||
Features:
|
||||
* Automatically giving extra kits for each extra player in saferooms
|
||||
* Increasing item count for items randomly depending on player count
|
||||
* Fix same-models survivors having to fight over ammo pack usage
|
||||
* Automatically lock the first saferoom door for every chapter, until a threshold of players or time has passed
|
||||
* Includes a HUD that shows all the survivors and their items, and optionally their ping (breaks randomly not sure why)
|
||||
* Includes a new 5+ special spawning director
|
||||
* Spawn an extra tank that scales with player count
|
||||
|
||||
* **Convars:**
|
||||
* `epi_item_chance` - The base chance (multiplied by player count) of an extra item being spawned. Default: 0.056
|
||||
* `epi_kitmode` - Decides how extra kits should be added. Default is 0
|
||||
* 0 -> Overwrites previous extra kits
|
||||
* 1 -> Adds onto previous extra kits
|
||||
* `epi_updateminplayers` - Should the plugin update abm's cvar min_players convar to the player count? (0 no, 1 yes)
|
||||
* `epi_doorunlock_percent` - The percent of players that need to be loaded in before saferoom door is opened.
|
||||
* Default is 0.8, set to 0 to disable door locking
|
||||
* `epi_doorunlock_wait` - How many seconds after to unlock saferoom door. 0 to disable timer
|
||||
* `epi_doorunlock_open` - Controls when or if the door automatically opens after unlocked. Add bits together.
|
||||
* 0 = Never, 1 = When timer expires, 2 = When all players loaded in
|
||||
* `epi_hudstate` - Controls when the extra player hud shows.
|
||||
* 0 = Never, 1 = When 5+ players, 2 = Always on
|
||||
* `epi_sp_spawning` - Determines what specials are spawned. Add bits together.
|
||||
* 1 = Specials, 2 = Witches, 4 = Tanks
|
||||
* `epi_enabled` - When should epi be enabled?
|
||||
* 0 = OFF, 1 = Only when 5+ and official map, 2 = Only when 5+, 3 = Always
|
||||
* `epi_tank_chunkhp` - The amount of health to add to a tank per extra player
|
||||
* `epi_gamemodes` - Comma separated list of allowed gamemodes.
|
||||
|
||||
|
||||
### l4d_survivor_identity_fix
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
A fork of [Survivor Identity Fix plugin](https://forums.alliedmods.net/showthread.php?t=280539) that adds support for other plugins to update the model cache. This is used by [L4D2Tools](#L4D2Tools) to update the identity when someone changes their model with `sm_model`. It also will clear the memory of model when a player disconnects entirely or on a new map.
|
||||
|
||||
In addition, has a fix for the passing finale, and will automatically temporarily change L4D characters to L4D2 until finale starts preventing game messing up their characters.
|
||||
|
||||
|
||||
### l4d2_population_control
|
||||
Allows you to set the chances that a common spawns as a certain uncommon. The order of the cvars is the order the percentages are ran
|
||||
* **Convars:**
|
||||
* `l4d2_population_chance <0.0-1.0>` Default: 1.0, the chance that the code runs on a spawn (basically if 0.0, none of the % chances will run for all types)
|
||||
* `l4d2_population_clowns <0.0-1.0>` The chance that on a common spawn that the special will be a clown.
|
||||
* `l4d2_population_mud <0.0-1.0>` The chance that on a common spawn that the special will be a mud common.
|
||||
* `l4d2_population_ceda <0.0-1.0>` The chance that on a common spawn that the special will be a ceda common.
|
||||
* `l4d2_population_worker <0.0-1.0>` The chance that on a common spawn that the special will be a worker common.
|
||||
* `l4d2_population_riot <0.0-1.0>` The chance that on a common spawn that the special will be a riot common.
|
||||
* `l4d2_population_jimmy <0.0-1.0>` The chance that on a common spawn that the special will be a jimmy common
|
||||
* `l4d2_population_common <#>` - The maximum amount of commons that can spawn.
|
||||
* 0 will turn off,
|
||||
* value > 0 will enforce the exact value
|
||||
* value < 0 will enforce z_common_limit + | value |
|
||||
* **Commands:**
|
||||
* `sm_populations` or `sm_population_list` - Lists all the cvar values
|
||||
|
||||
### globalbans
|
||||
This plugin will store bans in a database and read from it on connect. This allows you to easily have bans global between servers.
|
||||
It will automatically intercept any ban that calls OnBanIdentity or OnBanClient (so sm_ban will work normally)
|
||||
Note: All admin players are ignored
|
||||
|
||||
[Database File](sql/globalbans.sql)
|
||||
|
||||
* **Convars:**
|
||||
* `sm_globalbans_kick_type <0/1/2>`
|
||||
* 0 = Do not kick, just notify
|
||||
* 1 = Kick if banned
|
||||
* 2 = Kick if cannot reach database
|
||||
|
||||
|
||||
### l4d2_rollback
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
An idea that you can either manually or have events (friendly fire, new player joining) trigger saving all the player's states. Then if say, a troll comes and kills you and/or incaps your team, you can just quick restore to exactly the point you were at with the same items, health, etc.
|
||||
|
||||
Currently **abandoned.**
|
||||
|
||||
Currently auto triggers:
|
||||
|
||||
1. On any recent friendly fire (only triggers once per 100 game ticks)
|
||||
2. Any new player joins (only triggers once per 100 game ticks)
|
||||
|
||||
* **Commands:**
|
||||
* `sm_save` - Initiates a manual save of all player's states
|
||||
* `sm_state` - Lists all the states
|
||||
* `sm_restore <player(s)>` - Restores the selected player's state. @all for all
|
||||
|
||||
### l4d2_autorestart
|
||||
Plugin that automatically restarts server when the server is NOT hibernating, with bots around and no players.
|
||||
This fixes an issue with custom maps that force sb_all_bot_game to 1 and disable hibernation.
|
||||
|
||||
### l4d2_TKStopper
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
Plugin that prevents team killers by checking multiple criterias. Default system is as:
|
||||
Any survivor that attacks another survivor
|
||||
|
||||
1. If within first 2 minutes of joining, no damage is dealt to either victim or attacker. This prevents the next person to join being punished.
|
||||
2. If during the finale vehicle arrival, they do 0x damage to victim and take 2x reverse friendly fire
|
||||
3. If neither #1 or #2, both the victim and the attacker take 1/2 the original damage
|
||||
4. If victim is in a saferoom, no damage is dealt.
|
||||
|
||||
See https://admin.jackz.me/docs/plugins#tkstopper for some more implementation information
|
||||
|
||||
|
||||
During any of the above three conditions, if they deal (or attempt to deal) over 75 HP in 15 seconds (configurable) they will be instantly banned for a set period of time (60 minutes). If they are for sure a team killer, it can be extended to a permanent ban.
|
||||
|
||||
* **Cvars:**
|
||||
* `l4d2_tk_forgiveness_time <#>` - The minimum amount of seconds to pass (in seconds) where a player's previous accumulated FF is forgive. Default is 15s
|
||||
* `l4d2_tk_bantime` - How long in minutes should a player be banned for? 0 for permanently. Default is 60
|
||||
* `l4d2_tk_ban_ff_threshold` - How much damage does a player need to do before being instantly banned. Default 75 HP
|
||||
* `l4d2_tk_ban_join_time` - Upto how many minutes should any new player's FF be ignored. Default is 2 Minutes
|
||||
|
||||
|
||||
|
||||
### l4d2_crescendo_control
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
This plugin prevents the activation of buttons ahead of the team. It will prevent players from starting crescendos (and some small other activities as a side effect) until a certain threshold of the team has reached the area.
|
||||
|
||||
_This plugin is not perfect, sometimes it may trigger early, or not trigger at all depending on the map. Sometimes you need to as admins, move forward to allow non-admins to activate events._
|
||||
|
||||
|
||||
* **Cvars:**
|
||||
* `l4d2_crescendo_percent`
|
||||
* `l4d2_crescendo_range`
|
||||
|
||||
|
||||
### l4d2_vocalize_control
|
||||
A very small plugin that simply allows a player to mute another player's vocalizations only for them.
|
||||
|
||||
* **Commands:**
|
||||
* `sm_vgag <player(s)>` - Vocalize gag or ungags selected player(s) for the command activator only
|
||||
|
||||
### l4d2_sb_fix
|
||||
A fork of https://forums.alliedmods.net/showthread.php?p=2757330
|
||||
- Updated to latest sourcepawn syntax (now 1.11)
|
||||
- Fixed some stupid things (all cvars being FCVAR_NOTIFY)
|
||||
|
||||
|
||||
### l4d2_hideandseek
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
|
||||
A sourcemod extenstion of the vscript gamemode (https://steamcommunity.com/sharedfiles/filedetails/?id=2467133506)
|
||||
- Custom map boundaries, portals (tunnel through walls), and extra map props to spice up the maps
|
||||
- Optional climbable infected ladders
|
||||
- Heart beat sounds when a seeker is nearby
|
||||
- Quality of life improvements (winner messages, change seeker mid-game, change round time)
|
||||
- and a lot more that I've forgotten
|
||||
|
||||
> [!TIP]
|
||||
> **Note:** Starting the gamemode is very tricky, sometimes you need to slay all players multiple times and sometimes reload the plugin for it to properly start. Once it works, it should be stable.
|
||||
|
||||
|
||||
### l4d2_guesswho
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
|
||||
Based off gmod guess who game, find the real players amongst a group of bots.
|
||||
All logic is written in this plugin, thus is required.
|
||||
Vscript required for hud & mutation
|
||||
|
||||
Gamemode: https://steamcommunity.com/sharedfiles/filedetails/?id=2823719841
|
||||
|
||||
Requires l4dtoolz and left4dhooks, and optionally skip intro cutscene
|
||||
|
||||
### l4d2_prophunt
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
|
||||
Based off gmod prop hunt, find the real players amongst a group of props.
|
||||
All logic is written in this plugin, thus is required.
|
||||
Vscript required for hud & mutation
|
||||
|
||||
* Gamemode: https://steamcommunity.com/sharedfiles/filedetails/?id=2850550331
|
||||
|
||||
* Demo Map: https://steamcommunity.com/sharedfiles/filedetails/?id=2855027013 (makes most prop_static -> prop_dynamic)
|
||||
|
||||
Requires l4dtoolz and left4dhooks, and optionally skip intro cutscene
|
||||
|
||||
### l4d2_hats (Hats & Editor)
|
||||
|
||||
Lets you hat any entity (has blacklist support), including players. Yeet, place, restore, and more with the hats. Also includes an entire prop editor that replaces any other prop spawner. You can create custom invisible walls and spawn props (In the future, may become a separate plugin.)
|
||||
|
||||
[Prop Spawner Cheatsheat](https://admin.jackz.me/docs/props)
|
||||
|
||||
The prop spawner includes:
|
||||
|
||||
* Recents (recently spawned props)
|
||||
* Spawn Previews
|
||||
* Manual Placement (Move prop with cursor before spawning)
|
||||
* Prop Search
|
||||
* Better prop categories
|
||||
* Item spawning
|
||||
* Schematics (in early development)
|
||||
* Favorited props (in alpha)
|
||||
|
||||
> [!WARNING]
|
||||
> **Be warned, hatting can cause server crashes.** It's a LOT more stable now, but some features are disabled by default (reverse hats, visible hats) as they caused too many crashes. Most crashes are caused by hats and ladders, but the default settings prevent it.
|
||||
|
||||
### sm_namespamblock
|
||||
|
||||
If a user changes their name 3 times within 10 seconds, they will be temp banned for 10 minutes.
|
||||
Requires recompile to change.
|
||||
|
||||
* **Commands:**
|
||||
* `status2` - Shitty name, but shows all non-admin players, sorted by last joined ascending (up top). Shows steamid and the first name they joined the server as
|
||||
* `sm_status2` - Same command, but allows /status2 in chat
|
||||
|
||||
### l4d2_ai_tweaks
|
||||
|
||||
Simply, prevents an idle bot (that is a bot for an idle player) from healing another player unless:
|
||||
1. The target is black and white
|
||||
2. The player has been idle for over **ALLOW_HEALING_MIN_IDLE_TIME** (a \#define) seconds (default is 3 minutes)
|
||||
|
||||
Requires recompile to change.
|
||||
|
||||
### sm_player_notes
|
||||
|
||||
Simply lets you add notes to any player, and includes reputation summary and automatic actions (for my other plugins). When a player joins, all admins will see their notes.
|
||||
|
||||
[Database File](sql/sm_player_notes.sql) | [Docs for Automatic Actions](https://admin.jackz.me/docs/notes)
|
||||
|
||||
* **Commands:**
|
||||
* `sm_note <player> <message>` - Add notes to player, message does not need to be surrounded with quotes
|
||||
* `sm_notedisconnected` - Shows menu of all players that have disconnected
|
||||
* `sm_notes <player>` - View notes for player
|
||||
# Sourcemod L4D2 Plugins
|
||||
This is a collection of sourcemod plugins, most are used on my servers. The majority of the plugins are created by me, but some are modifications of other plugins.
|
||||
Some of the plugins / changes are very specific, but may be useful to someone.
|
||||
|
||||
Not always the latest versions. If you have any interest with a plugin, I can make sure to upload the latest.
|
||||
|
||||
Useful things:
|
||||
1. **Netprop Viewer** [L4D2 Netprops](https://jackz.me/netprops/l4d2) and [L4D2 Datamaps](https://jackz.me/netprops/l4d2-data)
|
||||
|
||||
## Plugin List
|
||||
|
||||
### Created by Me
|
||||
* [l4d2-manual-director](#l4d2-manual-director) - Spawn specials on demand via director or at your cursor
|
||||
* [l4d2-info-cmd](#l4d2-info-cmd) - Prints a full state of all survivors, useful for external information
|
||||
* [AutoWarpBot](#autowarpbot) - Abandoned
|
||||
* [L4D2FFKickProtection](#l4d2ffkickprotection) - Prevents players being voted off from friendly firing and prevents admins from being kicked
|
||||
* [l4d2_avoid_minigun](#l4d2_avoid_minigun) - Makes bots avoid being infront of any in-use miniguns. Useful for spawned miniguns
|
||||
* [l4d2_ai_minigun](#l4d2_ai_minigun) - Based off [Silver's Survivor Bot Holdout plugin](https://forums.alliedmods.net/showthread.php?p=1741099), allows you to spawn survivor bots but with no limit.
|
||||
* [L4D2Tools](#l4d2tools) - A collection of utilities, mostly just used with [l4d_survivor_identity_fix](#l4d_survivor_identity_fix) and the /model command
|
||||
* [l4d2_swarm](#l4d2_swarm) - Uses vscript RushVictim to make all zombies target a player, like a more subtle vomitplayer
|
||||
* [l4d2_feedthetrolls](#l4d2_feedthetrolls) - Full collection of tools to troll your friends or troll the trolls
|
||||
* [l4d2_autobotcrown](#l4d2_autobotcrown) - Bots will auto crown
|
||||
* [l4d2_extraplayeritems](#l4d2_extraplayeritems) - Includes tons of utilities for 5+ games, such as 5+ player hud, extra kit / item spawning, and more
|
||||
* [l4d2_population_control](#l4d2_population_control) - Allows you to custom the type of zombies that spawn (% of clowns, mud men, etc..)
|
||||
* [globalbans](#globalbans) - Bans synced via mysql, way lighter than the sourcebans cesspool.
|
||||
* [l4d2_rollback](#l4d2_rollback) - Abandoned and broken, but makes periodic backup of all player's items
|
||||
* [l4d2_autorestart](#l4d2_autorestart) - Restarts server if it's been on for a certain uptime or when empty with just bots.
|
||||
* [l4d2_TKStopper](#l4d2_tkstopper) - All the teamkiller and shitty-aim player punishments. Auto increasing reverse ff and teamkill detection
|
||||
* [l4d2_crescendo_control](#l4d2_crescendo_control) - Prevents players from running far ahead and starting events, and logs button presses
|
||||
* [l4d2_vocalize_control](#l4d2_vocalize_control) - Allows you to locally mute someone from vocalizing
|
||||
* [l4d2_guesswho](#l4d2_guesswho) - Garry's Mod's Guess Who in l4d2, inspired by hide and seek
|
||||
* [l4d2_hideandseek](#l4d2_hideandseek) - An enhancement to the base hide and seek mutation
|
||||
* [l4d2_hats](#l4d2_hats) - Entity Hats & Entity editing
|
||||
* [l4d2_prophunt](#l4d2_prophunt) - Garry's Mod inspired prop hunt, inspired by hide and seek
|
||||
* [sm_namespamblock](#sm_namespamblock) - Basic plugin that bans players if they change their name in rapid succession
|
||||
* [l4d2-stats-plugin](https://github.com/jackzmc/l4d2-stats-plugin) - Custom stats recorder, see https://stats.jackz.me
|
||||
* [l4d2-ai-tweaks](#l4d2_ai_tweaks) - Very minor tweaks to survivor bots' behavior
|
||||
* [sm_player_notes](#sm_player_notes) - Add notes to players
|
||||
|
||||
### Modified Others
|
||||
* [200IQBots_FlyYouFools](#200iqbots_flyyoufools) - Improved code to make it support multiple tanks and work better
|
||||
* [l4d_survivor_identity_fix](#l4d_survivor_identity_fix) - Use with [L4D2Tools](#l4d2tools) to change models, some fixes
|
||||
* [BetterWitchAvoidance](#betterwitchavoidance)
|
||||
* l4d_anti_rush - Modified plugin to add a forward, so other plugins (like feedthetrolls) can do something. In addition, use highest flow value achieved for players (fixes issue when admins go back and players who haven't moved suddenly get punished)
|
||||
* [l4d2_sb_fix](#l4d2_sb_fix) - Updated to 1.11 & latest sourcepawn syntax & removed the FCVAR_NOTIFY from all cvars (why is that added?)
|
||||
* GrabEnt - Improved version that prevents moving certain entities (such as invisual walls, ragdolls, etc) and improved some code
|
||||
|
||||
## Dependencies
|
||||
This is a list of most common dependencies, independent if they are used for a certain plugin.
|
||||
Check the plugin info for an exact list.
|
||||
|
||||
* [Left 4 Dhooks Direct](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
|
||||
### Development Dependencies
|
||||
Most L4D2 plugins use my own include: jutils.inc, it's provided in this repo.
|
||||
Some do require newer includes for my modified plugins (such as my improved survivor identity fix)
|
||||
|
||||
## Descriptions
|
||||
|
||||
### l4d2-manual-director
|
||||
~~Probably going to be posted publicly sometime.~~ Allows you to spawn specials on cursor, or via director, forcefully, bypassing limits
|
||||
* **Convars:**
|
||||
* `manual_director_version|mandirector_version` - ... gets version
|
||||
* `mandirector_notify_spawn <1/0>` - Should spawning specials notify on use?
|
||||
* `mandirector_announce_level <0/1/2/3>` - Announcement types. 0 - None, 1 - Only bosses, 2 - Only specials+, 3 - Everything
|
||||
* `mandirector_enable_tank <0/1>` - Should tanks be allowed to be spawned?
|
||||
* `mandirector_enable_witch <0/1>` - Should witches be allowed to be spawned?
|
||||
* `mandirector_enable_mob <0/1>` - Should mobs be allowed to be spawned
|
||||
* **Commands:**
|
||||
* `sm_spawnspecial <special> [amount]` - Spawn a special via director
|
||||
* `sm_forcespecial <special> [amount]` - Force spawn a special via director, bypassing spawn limits
|
||||
* `sm_forcecursor <special> [amount]` - Force spawn a special at cursor, bypassing spawn limits
|
||||
* `sm_cursormenu` - Show the spawn menu for cursor spawning
|
||||
* `sm_specialmenu` - Show the spawn menu for director spawning
|
||||
* `sm_directormenu` (Same as sm_specialmenu for now)
|
||||
|
||||
|
||||
### l4d2-info-cmd
|
||||
Technically 'l4d2 game info', haven't changed name. Just prints general information, used for a project
|
||||
* **Commands:**
|
||||
* `sm_gameinfo`
|
||||
* Example Response:
|
||||
```
|
||||
>map,diff,mode,tempoState,totalSeconds
|
||||
c1m1_hotel,1,coop,3,1622
|
||||
>id,name,bot,health,status,throwSlot,kitSlot,pillSlot,survivorType,velocity,primaryWpn,secondaryWpn
|
||||
1,Jackz,0,80,alive,0,,first_aid_kit,,Bill,0,,pistol
|
||||
3,Zoey,1,75,alive,0,,first_aid_kit,,Zoey,0,,pistol
|
||||
4,Francis,1,76,alive,0,,,,Francis,0,,pistol
|
||||
5,Louis,1,90,alive,0,,first_aid_kit,,Louis,0,,pistol
|
||||
```
|
||||
|
||||
|
||||
### AutoWarpBot
|
||||
Simple l4d2 plugin that will auto teleport bots to checkpoint once all real players have reached the saferoom.
|
||||
Doesn't really work well. Abandoned.
|
||||
|
||||
|
||||
### 200IQBots_FlyYouFools
|
||||
Updated version of ConnerRia's plugin. Improves bots avoidance of tanks. Change from original is updated sourcepawn syntax, some optimizations/cleanup, and fixes such as bots avoiding tank that has not been activated, or not escaping in vehicle due to presence of tank.
|
||||
Latest version now has support for multiple tanks, the bots might not avoid them as effectively as they would with one tank but they still try their best.
|
||||
* **Convars:**
|
||||
* `FlyYouFools_Version` - Prints the version of plugin
|
||||
|
||||
|
||||
### BetterWitchAvoidance
|
||||
Inspired by the 200IQBots_FlyYouFools. Bots avoid witch if its over 40% anger when close, or a little bigger range at 60% or more. Not recommended to use, normal behavior seems fine.
|
||||
|
||||
|
||||
### L4D2FFKickProtection
|
||||
Simple plugin that prevents a player that is being vote-kicked from doing any ff damage to teammates.
|
||||
It also prevents vote kicking of admins, instead will notify admins.
|
||||
It also makes any vote kicks created by an admin to instantly be accepted by all players
|
||||
|
||||
* **Convars:**
|
||||
* `sm_votekick_force_threshold <#>` - The threshold of damage where the offending player is just immediately kicked. 0 -> Any attempted damage, -1 -> No auto kick.
|
||||
|
||||
### l4d2_avoid_minigun
|
||||
Makes the bots avoid standing in front of or on top of the player that is using a minigun. It checks every 2.0 seconds if they are in the way, then forces them to move to behind you. There is no configuration, all automatic.
|
||||
|
||||
|
||||
### l4d2_ai_minigun
|
||||
Requires: [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
Spawn the holdout bots used in the passing. This supports all 8 characters, including with the minigun. They can spawn with any weapon or default to ak47.
|
||||
|
||||
**Notes:**
|
||||
* The minigun holdout bot has to internally be Louis, so it will be Louis making sounds, with whatever model specified being shown. This doesn't apply for normal holdout bot.
|
||||
* \<survivor name> can be "bill" or their numeric id (4).
|
||||
|
||||
Code modified from https://forums.alliedmods.net/showthread.php?p=1741099
|
||||
|
||||
* **Commands:**
|
||||
* `sm_ai_minigun <survivor name>` - Spawns an ai bot with minigun infront of wherever you are looking. Can also use numbers (0-7).
|
||||
* `sm_ai_holdout <survivor name> [wpn]` - Spawns a normal ai holdout bot (no minigun), with any weapon w/ laser sight (default is ak).
|
||||
* `sm_ai_remove_far` - Removes any holdout or minigun bots that are 750 units or more from any player.
|
||||
|
||||
|
||||
### L4D2Tools
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
* [Modified L4D Survivor Identity Fix](#l4d_survivor_identity_fix)
|
||||
|
||||
A collection of small tools:
|
||||
* Notification of when someone picks up laser sights (only the first user, includes bots),
|
||||
* Record time it takes for a finale or gauntlet run to be completed.
|
||||
* Record the amount of friendly fire damage done
|
||||
* Set the survivor models of any survivor with updated [l4d_survivor_identity_fix](#l4d_survivor_identity_fix)
|
||||
* Automatically returns melee weapons that an idle bot dropped once no longer idle
|
||||
* Automatically make players go idle when ping spikes
|
||||
* Slowly kill any zombies attacking survivor bot's blind spots (Fixes brain dead bots stuck taking damage and not killing them)
|
||||
|
||||
* **Convars:**
|
||||
* `sm_laser_use_notice <0/1>` - Enable notification of when a laser box was used first
|
||||
* `sm_time_finale <0/1/2>` - Record the time it takes to complete finale. 0 -> OFF, 1 -> Gauntlets Only, 2 -> All finales
|
||||
* `sm_ff_notice <0/1/2>` - Should we record FF damages? 0: OFF, 1: To chat, 2: To HUD text.
|
||||
* `sm_autoidle_ping_max <30.0...>` - "The highest ping a player can have until they will automatically go idle.\n0=OFF, Min is 30
|
||||
* **Commands:**
|
||||
* `sm_model <player> <character>` - Sets the survivor model of the target player(s). 'character' is name or ID of character.
|
||||
* `sm_surv <player> <character>` - Sets the m_survivorCharacter prop only of the target player(s). 'character' is name or ID of character.
|
||||
|
||||
|
||||
### l4d2_swarm
|
||||
This plugin is used to counter trolls and otherwise bad players. It simply uses the new script function RushVictim() to make all zombies in X radius attack Y target. It's that simple.
|
||||
|
||||
This really only affects wandering zombies, mobs and panic events, but it may work slightly when bile or pipes are thrown. It does not and can't change the targets of zombies.
|
||||
|
||||
* **Convars:**
|
||||
* `sm_swarm_default_range <20-Infinity>` - The default range for commands & menus. Defaults to 7,500
|
||||
* **Commands:**
|
||||
* `sm_swarm [player] [range]` - Swarm a player, or random if none."
|
||||
* Aliases: `sm_rush`
|
||||
* `sm_rushmenu` - Opens a menu to quickly rush any player. Can be bound to a key to quickly rush as well
|
||||
* Aliases: `sm_rmenu`
|
||||
* `sm_swarmtoggle <player> [range]` - Will continuously run the swarm method on the player at the range. Use the command again or type "disable" for player to disable. Switching players will not disable, just switches target.
|
||||
* Aliases: `sm_rushtoggle`, `sm_rt`
|
||||
* `sm_rushtogglemenu` - Will open a menu to quickly select a player to continuously rush.
|
||||
* Aliases: `sm_rtmenu`
|
||||
|
||||
|
||||
### l4d2_feedthetrolls
|
||||
Requires:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* (Optional) [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
* (Optional) [Actions](https://forums.alliedmods.net/showthread.php?t=336374)
|
||||
* (Optional) [Modified L4D Antirush](#l4d_anti_rush)
|
||||
|
||||
This plugin allows you to enact certain troll modes on any player, some are subtle some are less so. Either way, it works great to deal with a rusher, an asshole or even your friends.
|
||||
|
||||
See the up-to-date list of trolls and their descriptions:
|
||||
https://admin.jackz.me/docs/ftt
|
||||
|
||||
* **Convars:**
|
||||
* `sm_ftt_autopunish_mode <#>` - (Not used, WIP) Sets the modes that auto punish will activate for. 1 -> Early crescendo activations
|
||||
* `sm_ftt_autopunish_action <#>` - Which actions will autopunish activate? Add bits together. 0=None, 1=TankMagnet 2=SpecialMagnet 4=Swarm 8=VomitPlayer
|
||||
* `sm_ftt_autopunish_expires <0...>` - How many minutes (in gameticks) until autopunish trolls are removed. 0 for never.
|
||||
* **Commands:**
|
||||
* `sm_fta [player]` - The main command, opens a menu to select a troll to apply, with modifiers and flags
|
||||
* `sm_ftr [player]` - Removes all active trolls from a player
|
||||
* `sm_ftc [player]` - Opens a menu to select a combo of trolls
|
||||
* `sm_ftl` - Lists all players that have a mode applied.
|
||||
* `sm_ftm` - Lists all troll options & their descriptions
|
||||
* `sm_mark` - Toggles marking a player to be banned when they fully disconnect
|
||||
* `sm_insta [player] [special]` - (No arguments opens menu) - Spawns a special via the director that will only target the victim
|
||||
* `sm_inface [player] [special]` - Identical to above, but special will be spawned as close as possible to survivor. Boomers auto explode, jockeys on their head, etc.
|
||||
* `sm_bots_attack <player> [target health]` - Slightly broken, but makes all bots shoot at player until they hit X health or a timeout is reached. Turn on `sb_friendlyfire` for it to be effective.
|
||||
* `sm_stagger <player>` - Makes a player stagger, shortcut to the Stagger troll
|
||||
* `sm_witch_attack <player>` - Makes all witches agro on the player
|
||||
* `sm_scharge <player> [timeout seconds]` - Will wait till there's no obstructions and players in the way, then spawns a charger behind them to charge them.
|
||||
* `sm_healbots <player> [# bots or 0 default]` - Makes n amount of bots chase a player down to heal them. Won't stop until they are healed, they die, or you run command again.
|
||||
* `sm_csplat <player> <top/front/back> - Shortcut to Car Splat. Spawns car in top/front/back that hits the player`
|
||||
|
||||
### l4d2_autobotcrown
|
||||
Makes any suitable bot (> 40 hp, has shotgun) automatically crown a witch. Supports multiple bots and witches, but only one bot can crown one witch at a time. Plugin is disabled in realism, and is really on suitable for coop or versus. Even works with idle players.
|
||||
|
||||
Bots do sometimes miss, but sometimes still manage to kill witch. They also don't care if there is danger in the way (fire, acid, angry witch).
|
||||
|
||||
* **Convars:**
|
||||
* `l4d2_autocrown_allowed_difficulty <default: 7>` - The difficulties the plugin is active on. 1=Easy, 2=Normal 4=Advanced 8=Expert. Add numbers together.
|
||||
* `l4d2_autocrown_modes_tog <default: 7>` - (Not implemented) - Turn on the plugin in these game modes. 0=All, 1=Coop, 2=Survival, 4=Versus, 8=Scavenge. Add numbers together
|
||||
|
||||
|
||||
### l4d2_extraplayeritems
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [L4D Info Editor](https://forums.alliedmods.net/showthread.php?p=2614626)
|
||||
* (Development dependency) Updated l4d2_weapon_stocks.inc
|
||||
|
||||
A well rounded tool that provides extra utilities to a 5+ co-op campaign.
|
||||
|
||||
Features:
|
||||
* Automatically giving extra kits for each extra player in saferooms
|
||||
* Increasing item count for items randomly depending on player count
|
||||
* Fix same-models survivors having to fight over ammo pack usage
|
||||
* Automatically lock the first saferoom door for every chapter, until a threshold of players or time has passed
|
||||
* Includes a HUD that shows all the survivors and their items, and optionally their ping (breaks randomly not sure why)
|
||||
* Includes a new 5+ special spawning director
|
||||
* Spawn an extra tank that scales with player count
|
||||
|
||||
* **Convars:**
|
||||
* `epi_item_chance` - The base chance (multiplied by player count) of an extra item being spawned. Default: 0.056
|
||||
* `epi_kitmode` - Decides how extra kits should be added. Default is 0
|
||||
* 0 -> Overwrites previous extra kits
|
||||
* 1 -> Adds onto previous extra kits
|
||||
* `epi_updateminplayers` - Should the plugin update abm's cvar min_players convar to the player count? (0 no, 1 yes)
|
||||
* `epi_doorunlock_percent` - The percent of players that need to be loaded in before saferoom door is opened.
|
||||
* Default is 0.8, set to 0 to disable door locking
|
||||
* `epi_doorunlock_wait` - How many seconds after to unlock saferoom door. 0 to disable timer
|
||||
* `epi_doorunlock_open` - Controls when or if the door automatically opens after unlocked. Add bits together.
|
||||
* 0 = Never, 1 = When timer expires, 2 = When all players loaded in
|
||||
* `epi_hudstate` - Controls when the extra player hud shows.
|
||||
* 0 = Never, 1 = When 5+ players, 2 = Always on
|
||||
* `epi_sp_spawning` - Determines what specials are spawned. Add bits together.
|
||||
* 1 = Specials, 2 = Witches, 4 = Tanks
|
||||
* `epi_enabled` - When should epi be enabled?
|
||||
* 0 = OFF, 1 = Only when 5+ and official map, 2 = Only when 5+, 3 = Always
|
||||
* `epi_tank_chunkhp` - The amount of health to add to a tank per extra player
|
||||
* `epi_gamemodes` - Comma separated list of allowed gamemodes.
|
||||
|
||||
|
||||
### l4d_survivor_identity_fix
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
A fork of [Survivor Identity Fix plugin](https://forums.alliedmods.net/showthread.php?t=280539) that adds support for other plugins to update the model cache. This is used by [L4D2Tools](#L4D2Tools) to update the identity when someone changes their model with `sm_model`. It also will clear the memory of model when a player disconnects entirely or on a new map.
|
||||
|
||||
In addition, has a fix for the passing finale, and will automatically temporarily change L4D characters to L4D2 until finale starts preventing game messing up their characters.
|
||||
|
||||
|
||||
### l4d2_population_control
|
||||
Allows you to set the chances that a common spawns as a certain uncommon. The order of the cvars is the order the percentages are ran
|
||||
* **Convars:**
|
||||
* `l4d2_population_chance <0.0-1.0>` Default: 1.0, the chance that the code runs on a spawn (basically if 0.0, none of the % chances will run for all types)
|
||||
* `l4d2_population_clowns <0.0-1.0>` The chance that on a common spawn that the special will be a clown.
|
||||
* `l4d2_population_mud <0.0-1.0>` The chance that on a common spawn that the special will be a mud common.
|
||||
* `l4d2_population_ceda <0.0-1.0>` The chance that on a common spawn that the special will be a ceda common.
|
||||
* `l4d2_population_worker <0.0-1.0>` The chance that on a common spawn that the special will be a worker common.
|
||||
* `l4d2_population_riot <0.0-1.0>` The chance that on a common spawn that the special will be a riot common.
|
||||
* `l4d2_population_jimmy <0.0-1.0>` The chance that on a common spawn that the special will be a jimmy common
|
||||
* `l4d2_population_common <#>` - The maximum amount of commons that can spawn.
|
||||
* 0 will turn off,
|
||||
* value > 0 will enforce the exact value
|
||||
* value < 0 will enforce z_common_limit + | value |
|
||||
* **Commands:**
|
||||
* `sm_populations` or `sm_population_list` - Lists all the cvar values
|
||||
|
||||
### globalbans
|
||||
This plugin will store bans in a database and read from it on connect. This allows you to easily have bans global between servers.
|
||||
It will automatically intercept any ban that calls OnBanIdentity or OnBanClient (so sm_ban will work normally)
|
||||
Note: All admin players are ignored
|
||||
|
||||
[Database File](sql/globalbans.sql)
|
||||
|
||||
* **Convars:**
|
||||
* `sm_globalbans_kick_type <0/1/2>`
|
||||
* 0 = Do not kick, just notify
|
||||
* 1 = Kick if banned
|
||||
* 2 = Kick if cannot reach database
|
||||
|
||||
|
||||
### l4d2_rollback
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
An idea that you can either manually or have events (friendly fire, new player joining) trigger saving all the player's states. Then if say, a troll comes and kills you and/or incaps your team, you can just quick restore to exactly the point you were at with the same items, health, etc.
|
||||
|
||||
Currently **abandoned.**
|
||||
|
||||
Currently auto triggers:
|
||||
|
||||
1. On any recent friendly fire (only triggers once per 100 game ticks)
|
||||
2. Any new player joins (only triggers once per 100 game ticks)
|
||||
|
||||
* **Commands:**
|
||||
* `sm_save` - Initiates a manual save of all player's states
|
||||
* `sm_state` - Lists all the states
|
||||
* `sm_restore <player(s)>` - Restores the selected player's state. @all for all
|
||||
|
||||
### l4d2_autorestart
|
||||
Plugin that automatically restarts server when the server is NOT hibernating, with bots around and no players.
|
||||
This fixes an issue with custom maps that force sb_all_bot_game to 1 and disable hibernation.
|
||||
|
||||
### l4d2_TKStopper
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
Plugin that prevents team killers by checking multiple criterias. Default system is as:
|
||||
Any survivor that attacks another survivor
|
||||
|
||||
1. If within first 2 minutes of joining, no damage is dealt to either victim or attacker. This prevents the next person to join being punished.
|
||||
2. If during the finale vehicle arrival, they do 0x damage to victim and take 2x reverse friendly fire
|
||||
3. If neither #1 or #2, both the victim and the attacker take 1/2 the original damage
|
||||
4. If victim is in a saferoom, no damage is dealt.
|
||||
|
||||
See https://admin.jackz.me/docs/plugins#tkstopper for some more implementation information
|
||||
|
||||
|
||||
During any of the above three conditions, if they deal (or attempt to deal) over 75 HP in 15 seconds (configurable) they will be instantly banned for a set period of time (60 minutes). If they are for sure a team killer, it can be extended to a permanent ban.
|
||||
|
||||
* **Cvars:**
|
||||
* `l4d2_tk_forgiveness_time <#>` - The minimum amount of seconds to pass (in seconds) where a player's previous accumulated FF is forgive. Default is 15s
|
||||
* `l4d2_tk_bantime` - How long in minutes should a player be banned for? 0 for permanently. Default is 60
|
||||
* `l4d2_tk_ban_ff_threshold` - How much damage does a player need to do before being instantly banned. Default 75 HP
|
||||
* `l4d2_tk_ban_join_time` - Upto how many minutes should any new player's FF be ignored. Default is 2 Minutes
|
||||
|
||||
|
||||
|
||||
### l4d2_crescendo_control
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
|
||||
This plugin prevents the activation of buttons ahead of the team. It will prevent players from starting crescendos (and some small other activities as a side effect) until a certain threshold of the team has reached the area.
|
||||
|
||||
_This plugin is not perfect, sometimes it may trigger early, or not trigger at all depending on the map. Sometimes you need to as admins, move forward to allow non-admins to activate events._
|
||||
|
||||
|
||||
* **Cvars:**
|
||||
* `l4d2_crescendo_percent`
|
||||
* `l4d2_crescendo_range`
|
||||
|
||||
|
||||
### l4d2_vocalize_control
|
||||
A very small plugin that simply allows a player to mute another player's vocalizations only for them.
|
||||
|
||||
* **Commands:**
|
||||
* `sm_vgag <player(s)>` - Vocalize gag or ungags selected player(s) for the command activator only
|
||||
|
||||
### l4d2_sb_fix
|
||||
A fork of https://forums.alliedmods.net/showthread.php?p=2757330
|
||||
- Updated to latest sourcepawn syntax (now 1.11)
|
||||
- Fixed some stupid things (all cvars being FCVAR_NOTIFY)
|
||||
|
||||
|
||||
### l4d2_hideandseek
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
|
||||
A sourcemod extenstion of the vscript gamemode (https://steamcommunity.com/sharedfiles/filedetails/?id=2467133506)
|
||||
- Custom map boundaries, portals (tunnel through walls), and extra map props to spice up the maps
|
||||
- Optional climbable infected ladders
|
||||
- Heart beat sounds when a seeker is nearby
|
||||
- Quality of life improvements (winner messages, change seeker mid-game, change round time)
|
||||
- and a lot more that I've forgotten
|
||||
|
||||
> [!TIP]
|
||||
> **Note:** Starting the gamemode is very tricky, sometimes you need to slay all players multiple times and sometimes reload the plugin for it to properly start. Once it works, it should be stable.
|
||||
|
||||
|
||||
### l4d2_guesswho
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
|
||||
Based off gmod guess who game, find the real players amongst a group of bots.
|
||||
All logic is written in this plugin, thus is required.
|
||||
Vscript required for hud & mutation
|
||||
|
||||
Gamemode: https://steamcommunity.com/sharedfiles/filedetails/?id=2823719841
|
||||
|
||||
Requires l4dtoolz and left4dhooks, and optionally skip intro cutscene
|
||||
|
||||
### l4d2_prophunt
|
||||
Plugin requirements:
|
||||
* [Left4DHooks](https://forums.alliedmods.net/showthread.php?t=321696)
|
||||
* [Scene Processor](https://forums.alliedmods.net/showthread.php?p=2147410)
|
||||
|
||||
Based off gmod prop hunt, find the real players amongst a group of props.
|
||||
All logic is written in this plugin, thus is required.
|
||||
Vscript required for hud & mutation
|
||||
|
||||
* Gamemode: https://steamcommunity.com/sharedfiles/filedetails/?id=2850550331
|
||||
|
||||
* Demo Map: https://steamcommunity.com/sharedfiles/filedetails/?id=2855027013 (makes most prop_static -> prop_dynamic)
|
||||
|
||||
Requires l4dtoolz and left4dhooks, and optionally skip intro cutscene
|
||||
|
||||
### l4d2_hats (Hats & Editor)
|
||||
|
||||
Lets you hat any entity (has blacklist support), including players. Yeet, place, restore, and more with the hats. Also includes an entire prop editor that replaces any other prop spawner. You can create custom invisible walls and spawn props (In the future, may become a separate plugin.)
|
||||
|
||||
[Prop Spawner Cheatsheat](https://admin.jackz.me/docs/props)
|
||||
|
||||
The prop spawner includes:
|
||||
|
||||
* Recents (recently spawned props)
|
||||
* Spawn Previews
|
||||
* Manual Placement (Move prop with cursor before spawning)
|
||||
* Prop Search
|
||||
* Better prop categories
|
||||
* Item spawning
|
||||
* Schematics (in early development)
|
||||
* Favorited props (in alpha)
|
||||
|
||||
> [!WARNING]
|
||||
> **Be warned, hatting can cause server crashes.** It's a LOT more stable now, but some features are disabled by default (reverse hats, visible hats) as they caused too many crashes. Most crashes are caused by hats and ladders, but the default settings prevent it.
|
||||
|
||||
### sm_namespamblock
|
||||
|
||||
If a user changes their name 3 times within 10 seconds, they will be temp banned for 10 minutes.
|
||||
Requires recompile to change.
|
||||
|
||||
* **Commands:**
|
||||
* `status2` - Shitty name, but shows all non-admin players, sorted by last joined ascending (up top). Shows steamid and the first name they joined the server as
|
||||
* `sm_status2` - Same command, but allows /status2 in chat
|
||||
|
||||
### l4d2_ai_tweaks
|
||||
|
||||
Simply, prevents an idle bot (that is a bot for an idle player) from healing another player unless:
|
||||
1. The target is black and white
|
||||
2. The player has been idle for over **ALLOW_HEALING_MIN_IDLE_TIME** (a \#define) seconds (default is 3 minutes)
|
||||
|
||||
Requires recompile to change.
|
||||
|
||||
### sm_player_notes
|
||||
|
||||
Simply lets you add notes to any player, and includes reputation summary and automatic actions (for my other plugins). When a player joins, all admins will see their notes.
|
||||
|
||||
[Database File](sql/sm_player_notes.sql) | [Docs for Automatic Actions](https://admin.jackz.me/docs/notes)
|
||||
|
||||
* **Commands:**
|
||||
* `sm_note <player> <message>` - Add notes to player, message does not need to be surrounded with quotes
|
||||
* `sm_notedisconnected` - Shows menu of all players that have disconnected
|
||||
* `sm_notes <player>` - View notes for player
|
||||
|
|
|
@ -1,87 +1,87 @@
|
|||
"guesswho"
|
||||
{
|
||||
"c1m1_hotel"
|
||||
{
|
||||
"spawnpoint" "442.905334 5640.576660 2656.031250"
|
||||
"ents"
|
||||
{
|
||||
"FENCE"
|
||||
{
|
||||
"origin" "1602.161499 5618.440917 2656.031250"
|
||||
"rotation" "0.000000 -178.836151 0.000000"
|
||||
"type" "prop_dynamic"
|
||||
"model" "props_fortifications\barricade001_128_reference.mdl"
|
||||
}
|
||||
"VENDING"
|
||||
{
|
||||
"origin" "406.428405 5625.409667 2656.031250"
|
||||
"rotation" "0.000000 90.238922 0.000000"
|
||||
"type" "prop_dynamic"
|
||||
"model" "props\cs_office\vending_machine.mdl"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
"c8m5_rooftop"
|
||||
{
|
||||
"spawnpoint" "5386.052246 8413.505859 5536.031250"
|
||||
}
|
||||
"c2m2_fairgrounds"
|
||||
{
|
||||
"spawnpoint" "-3451.616943 -818.726989 128.031250"
|
||||
"ents"
|
||||
{
|
||||
"DROP_BLOCK"
|
||||
{
|
||||
"origin" "-3073.630371 -910.963195 192.623626"
|
||||
"scale" "30 260 100"
|
||||
}
|
||||
"ENTRANCE_BLOCK"
|
||||
{
|
||||
"origin" "-2826.833251 -1978.528808 -127.915451"
|
||||
"rotation" "0.000000 -1.978949 0.000000"
|
||||
"type" "prop_dynamic"
|
||||
"model" "props_urban\wood_fence001_128.mdl"
|
||||
}
|
||||
"ENTRANCE_BLOCK_PROP"
|
||||
{
|
||||
"origin" "-2826.833251 -1978.528808 -127.915451"
|
||||
"scale" "20 100 500"
|
||||
}
|
||||
"STAIR_BLOCK_PROP"
|
||||
{
|
||||
"origin" "-3241.681884 -1817.223266 256.031250"
|
||||
"rotation" "0.000000 92.011253 0.000000"
|
||||
"type" "prop_dynamic"
|
||||
"model" "props_crates/static_crate_40.mdl"
|
||||
}
|
||||
"STAIR_BLOCK"
|
||||
{
|
||||
"origin" "-3321.681884 -1817.223266 300.031250"
|
||||
"scale" "140 20 60"
|
||||
}
|
||||
}
|
||||
}
|
||||
"c11m4_terminal"
|
||||
{
|
||||
"spawnpoint" "3081.824951 4571.367187 152.031250"
|
||||
"ents"
|
||||
{
|
||||
"ENTRANCE_BLOCKER"
|
||||
{
|
||||
"origin" "2700.844482 1865.502807 177.313140"
|
||||
"scale" "200 100 200"
|
||||
}
|
||||
"fire"
|
||||
{
|
||||
"origin" "2700.844482 1865.502807 179.313140"
|
||||
"type" "env_fire"
|
||||
"scale" "1.0 256 8"
|
||||
}
|
||||
}
|
||||
"inputs"
|
||||
{
|
||||
"checkpoint_entrance" "Kill"
|
||||
}
|
||||
}
|
||||
}
|
||||
"guesswho"
|
||||
{
|
||||
"c1m1_hotel"
|
||||
{
|
||||
"spawnpoint" "442.905334 5640.576660 2656.031250"
|
||||
"ents"
|
||||
{
|
||||
"FENCE"
|
||||
{
|
||||
"origin" "1602.161499 5618.440917 2656.031250"
|
||||
"rotation" "0.000000 -178.836151 0.000000"
|
||||
"type" "prop_dynamic"
|
||||
"model" "props_fortifications\barricade001_128_reference.mdl"
|
||||
}
|
||||
"VENDING"
|
||||
{
|
||||
"origin" "406.428405 5625.409667 2656.031250"
|
||||
"rotation" "0.000000 90.238922 0.000000"
|
||||
"type" "prop_dynamic"
|
||||
"model" "props\cs_office\vending_machine.mdl"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
"c8m5_rooftop"
|
||||
{
|
||||
"spawnpoint" "5386.052246 8413.505859 5536.031250"
|
||||
}
|
||||
"c2m2_fairgrounds"
|
||||
{
|
||||
"spawnpoint" "-3451.616943 -818.726989 128.031250"
|
||||
"ents"
|
||||
{
|
||||
"DROP_BLOCK"
|
||||
{
|
||||
"origin" "-3073.630371 -910.963195 192.623626"
|
||||
"scale" "30 260 100"
|
||||
}
|
||||
"ENTRANCE_BLOCK"
|
||||
{
|
||||
"origin" "-2826.833251 -1978.528808 -127.915451"
|
||||
"rotation" "0.000000 -1.978949 0.000000"
|
||||
"type" "prop_dynamic"
|
||||
"model" "props_urban\wood_fence001_128.mdl"
|
||||
}
|
||||
"ENTRANCE_BLOCK_PROP"
|
||||
{
|
||||
"origin" "-2826.833251 -1978.528808 -127.915451"
|
||||
"scale" "20 100 500"
|
||||
}
|
||||
"STAIR_BLOCK_PROP"
|
||||
{
|
||||
"origin" "-3241.681884 -1817.223266 256.031250"
|
||||
"rotation" "0.000000 92.011253 0.000000"
|
||||
"type" "prop_dynamic"
|
||||
"model" "props_crates/static_crate_40.mdl"
|
||||
}
|
||||
"STAIR_BLOCK"
|
||||
{
|
||||
"origin" "-3321.681884 -1817.223266 300.031250"
|
||||
"scale" "140 20 60"
|
||||
}
|
||||
}
|
||||
}
|
||||
"c11m4_terminal"
|
||||
{
|
||||
"spawnpoint" "3081.824951 4571.367187 152.031250"
|
||||
"ents"
|
||||
{
|
||||
"ENTRANCE_BLOCKER"
|
||||
{
|
||||
"origin" "2700.844482 1865.502807 177.313140"
|
||||
"scale" "200 100 200"
|
||||
}
|
||||
"fire"
|
||||
{
|
||||
"origin" "2700.844482 1865.502807 179.313140"
|
||||
"type" "env_fire"
|
||||
"scale" "1.0 256 8"
|
||||
}
|
||||
}
|
||||
"inputs"
|
||||
{
|
||||
"checkpoint_entrance" "Kill"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
4164
data/hideandseek.cfg
4164
data/hideandseek.cfg
File diff suppressed because it is too large
Load diff
|
@ -1,28 +1,28 @@
|
|||
"prophunt"
|
||||
{
|
||||
"c1m1_hotel"
|
||||
{
|
||||
"props"
|
||||
{
|
||||
"SOFA_1"
|
||||
{
|
||||
"model" "props_urban/hotel_chair001.mdl"
|
||||
"origin" "1532.456787 4609 1184"
|
||||
"rotation" "0 323.5 0"
|
||||
}
|
||||
"SOFA_2"
|
||||
{
|
||||
"origin" "1649 4529 1184"
|
||||
"rotation" "0 170 0"
|
||||
"model" "props_urban/hotel_chair001.mdl"
|
||||
}
|
||||
}
|
||||
}
|
||||
"c8m1_apartments"
|
||||
{
|
||||
"props"
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
"prophunt"
|
||||
{
|
||||
"c1m1_hotel"
|
||||
{
|
||||
"props"
|
||||
{
|
||||
"SOFA_1"
|
||||
{
|
||||
"model" "props_urban/hotel_chair001.mdl"
|
||||
"origin" "1532.456787 4609 1184"
|
||||
"rotation" "0 323.5 0"
|
||||
}
|
||||
"SOFA_2"
|
||||
{
|
||||
"origin" "1649 4529 1184"
|
||||
"rotation" "0 170 0"
|
||||
"model" "props_urban/hotel_chair001.mdl"
|
||||
}
|
||||
}
|
||||
}
|
||||
"c8m1_apartments"
|
||||
{
|
||||
"props"
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +1,23 @@
|
|||
"props"
|
||||
{
|
||||
"props/cs_office/Fire_Extinguisher.mdl" "5"
|
||||
"props_urban/ashtray_stand001.mdl" "5"
|
||||
"props_furniture/hotel_chair.mdl" "10"
|
||||
"props_urban/hotel_chair001.mdl" "20"
|
||||
"props_interiors/tv.mdl" "10"
|
||||
"props_downtown/ironing_board.mdl" "15"
|
||||
"props_urban/hotel_lamp001.mdl" "10"
|
||||
"props_windows/hotel_window_glass001.mdl" "30"
|
||||
"props/cs_office/shelves_metal1.mdl" "50"
|
||||
"props/cs_office/shelves_metal.mdl" "50"
|
||||
"props_downtown/dresser.mdl" "50"
|
||||
"props_interiors_Hotel_Cart.mdl" "40"
|
||||
"props_downtown/side_table.mdl" "10"
|
||||
"props_interiors/ac_wallunit.mdl" "30"
|
||||
"props_downtown/bed_motel01.mdl" "50"
|
||||
"props_interiors/coffee_table_oval.mdl" "20"
|
||||
"props_downtown/mini_fridge.mdl" "10"
|
||||
"props_interiors/fridge_mini.mdl" "10"
|
||||
"props/cs_office/vending_machine.mdl" "50"
|
||||
"props_interiors/toilet.mdl" "30"
|
||||
"props"
|
||||
{
|
||||
"props/cs_office/Fire_Extinguisher.mdl" "5"
|
||||
"props_urban/ashtray_stand001.mdl" "5"
|
||||
"props_furniture/hotel_chair.mdl" "10"
|
||||
"props_urban/hotel_chair001.mdl" "20"
|
||||
"props_interiors/tv.mdl" "10"
|
||||
"props_downtown/ironing_board.mdl" "15"
|
||||
"props_urban/hotel_lamp001.mdl" "10"
|
||||
"props_windows/hotel_window_glass001.mdl" "30"
|
||||
"props/cs_office/shelves_metal1.mdl" "50"
|
||||
"props/cs_office/shelves_metal.mdl" "50"
|
||||
"props_downtown/dresser.mdl" "50"
|
||||
"props_interiors_Hotel_Cart.mdl" "40"
|
||||
"props_downtown/side_table.mdl" "10"
|
||||
"props_interiors/ac_wallunit.mdl" "30"
|
||||
"props_downtown/bed_motel01.mdl" "50"
|
||||
"props_interiors/coffee_table_oval.mdl" "20"
|
||||
"props_downtown/mini_fridge.mdl" "10"
|
||||
"props_interiors/fridge_mini.mdl" "10"
|
||||
"props/cs_office/vending_machine.mdl" "50"
|
||||
"props_interiors/toilet.mdl" "30"
|
||||
}
|
|
@ -1,26 +1,26 @@
|
|||
{
|
||||
"group1": {
|
||||
"chance": 0.1,
|
||||
"exclusions": ["group2"],
|
||||
"variants": [
|
||||
{
|
||||
"weight": 1,
|
||||
"entities": [
|
||||
|
||||
{
|
||||
"origin": [1421.24, 5780.23, 2881.16],
|
||||
"angles": [0.00, 0.00, 0.00],
|
||||
"size": [5.00, 5.00, 5.00],
|
||||
"model": "models/props/cs_assault/forklift.mdl"
|
||||
},
|
||||
{
|
||||
"origin": [1139.59, 5685.23, 2883.25],
|
||||
"angles": [0.00, 0.00, 0.00],
|
||||
"size": [5.00, 5.00, 5.00],
|
||||
"model": "models/props_unique/airportdeparturescreen01.mdl"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"group1": {
|
||||
"chance": 0.1,
|
||||
"exclusions": ["group2"],
|
||||
"variants": [
|
||||
{
|
||||
"weight": 1,
|
||||
"entities": [
|
||||
|
||||
{
|
||||
"origin": [1421.24, 5780.23, 2881.16],
|
||||
"angles": [0.00, 0.00, 0.00],
|
||||
"size": [5.00, 5.00, 5.00],
|
||||
"model": "models/props/cs_assault/forklift.mdl"
|
||||
},
|
||||
{
|
||||
"origin": [1139.59, 5685.23, 2883.25],
|
||||
"angles": [0.00, 0.00, 0.00],
|
||||
"size": [5.00, 5.00, 5.00],
|
||||
"model": "models/props_unique/airportdeparturescreen01.mdl"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,123 +1,123 @@
|
|||
{
|
||||
"alleyskip": {
|
||||
"chance": 0.30,
|
||||
"variants": [
|
||||
{
|
||||
"weight": 1,
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_urban/fire_escape_wide_lower.mdl",
|
||||
"origin": [2203.81, 2381.96, 212.84],
|
||||
"angles": [0.00, 1.07, 0.00],
|
||||
"size": [181.27, 2.22, 132.22]
|
||||
},
|
||||
{
|
||||
"model": "models/props_vehicles/ambulance.mdl",
|
||||
"origin": [2317.06, 2282.30, 16.03],
|
||||
"angles": [0.00, 270.39, 0.00],
|
||||
"size": [90.09, 115.60, 109.48]
|
||||
},
|
||||
{
|
||||
"model": "models/props/cs_militia/boxes_garage_lower.mdl",
|
||||
"origin": [2265.46, 2308.65, 124.13],
|
||||
"angles": [0.00, 92.14, 0.00],
|
||||
"size": [37.36, 35.15, 65.06]
|
||||
},
|
||||
{
|
||||
"model": "models/props_fortifications/barricade001_128_reference.mdl",
|
||||
"origin": [2013.47, 2254.53, 15.83],
|
||||
"angles": [0.00, 178.81, 0.00],
|
||||
"size": [21.75, 48.25, 144.25]
|
||||
},
|
||||
{
|
||||
"model": "models/props_fortifications/barricade001_64_reference.mdl",
|
||||
"origin": [2001.55, 2345.92, 15.51],
|
||||
"angles": [0.00, 177.42, 0.00],
|
||||
"size": [24.25, 24.25, 144.25]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"mainblock": {
|
||||
"chance": 0.5,
|
||||
"variants": [
|
||||
{
|
||||
"weight": 1,
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_street/police_barricade2.mdl",
|
||||
"origin": [2744.79, 3975.41, 16.03],
|
||||
"angles": [0.00, 105.00, 0.00],
|
||||
"size": [28.70, 71.14, 119.31]
|
||||
},
|
||||
{
|
||||
"model": "models/props_street/police_barricade_496in.mdl",
|
||||
"origin": [2428.55, 3960.12, 8.03],
|
||||
"angles": [0.00, 89.61, 0.00],
|
||||
"size": [28.77, 245.37, 119.31]
|
||||
},
|
||||
{
|
||||
"model": "models/props_street/police_barricade2.mdl",
|
||||
"origin": [2120.48, 3963.20, 16.03],
|
||||
"angles": [0.00, 87.27, 0.00],
|
||||
"size": [28.70, 71.14, 119.31]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"subwayblock": {
|
||||
"chance": 0.2,
|
||||
"variants": [
|
||||
{
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_vehicles/hmmwv.mdl",
|
||||
"origin": [2755.60, 4139.53, 12.72],
|
||||
"angles": [0.00, -180.00, 0.00],
|
||||
"size": [57.97, 105.86, 124.61]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_vehicles/pickup_truck_78.mdl",
|
||||
"origin": [2755.60, 4139.53, 12.72],
|
||||
"angles": [0.00, -180.00, 0.00],
|
||||
"size": [57.97, 105.86, 124.61]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"doorblock": {
|
||||
"chance": 0.2,
|
||||
"variants": [
|
||||
{
|
||||
"weight": 2,
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_junk/dumpster.mdl",
|
||||
"origin": [1519.27, 2626.97, 55.79],
|
||||
"angles": [0.00, -86.23, 90.00],
|
||||
"size": [26.37, 40.17, 54.13]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"weight": 1,
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_junk/dumpster.mdl",
|
||||
"origin": [1498.78, 2630.72, 16.03],
|
||||
"angles": [0.00, 3.76, 0.00],
|
||||
"size": [26.37, 40.17, 54.13]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"alleyskip": {
|
||||
"chance": 0.30,
|
||||
"variants": [
|
||||
{
|
||||
"weight": 1,
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_urban/fire_escape_wide_lower.mdl",
|
||||
"origin": [2203.81, 2381.96, 212.84],
|
||||
"angles": [0.00, 1.07, 0.00],
|
||||
"size": [181.27, 2.22, 132.22]
|
||||
},
|
||||
{
|
||||
"model": "models/props_vehicles/ambulance.mdl",
|
||||
"origin": [2317.06, 2282.30, 16.03],
|
||||
"angles": [0.00, 270.39, 0.00],
|
||||
"size": [90.09, 115.60, 109.48]
|
||||
},
|
||||
{
|
||||
"model": "models/props/cs_militia/boxes_garage_lower.mdl",
|
||||
"origin": [2265.46, 2308.65, 124.13],
|
||||
"angles": [0.00, 92.14, 0.00],
|
||||
"size": [37.36, 35.15, 65.06]
|
||||
},
|
||||
{
|
||||
"model": "models/props_fortifications/barricade001_128_reference.mdl",
|
||||
"origin": [2013.47, 2254.53, 15.83],
|
||||
"angles": [0.00, 178.81, 0.00],
|
||||
"size": [21.75, 48.25, 144.25]
|
||||
},
|
||||
{
|
||||
"model": "models/props_fortifications/barricade001_64_reference.mdl",
|
||||
"origin": [2001.55, 2345.92, 15.51],
|
||||
"angles": [0.00, 177.42, 0.00],
|
||||
"size": [24.25, 24.25, 144.25]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"mainblock": {
|
||||
"chance": 0.5,
|
||||
"variants": [
|
||||
{
|
||||
"weight": 1,
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_street/police_barricade2.mdl",
|
||||
"origin": [2744.79, 3975.41, 16.03],
|
||||
"angles": [0.00, 105.00, 0.00],
|
||||
"size": [28.70, 71.14, 119.31]
|
||||
},
|
||||
{
|
||||
"model": "models/props_street/police_barricade_496in.mdl",
|
||||
"origin": [2428.55, 3960.12, 8.03],
|
||||
"angles": [0.00, 89.61, 0.00],
|
||||
"size": [28.77, 245.37, 119.31]
|
||||
},
|
||||
{
|
||||
"model": "models/props_street/police_barricade2.mdl",
|
||||
"origin": [2120.48, 3963.20, 16.03],
|
||||
"angles": [0.00, 87.27, 0.00],
|
||||
"size": [28.70, 71.14, 119.31]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"subwayblock": {
|
||||
"chance": 0.2,
|
||||
"variants": [
|
||||
{
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_vehicles/hmmwv.mdl",
|
||||
"origin": [2755.60, 4139.53, 12.72],
|
||||
"angles": [0.00, -180.00, 0.00],
|
||||
"size": [57.97, 105.86, 124.61]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_vehicles/pickup_truck_78.mdl",
|
||||
"origin": [2755.60, 4139.53, 12.72],
|
||||
"angles": [0.00, -180.00, 0.00],
|
||||
"size": [57.97, 105.86, 124.61]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"doorblock": {
|
||||
"chance": 0.2,
|
||||
"variants": [
|
||||
{
|
||||
"weight": 2,
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_junk/dumpster.mdl",
|
||||
"origin": [1519.27, 2626.97, 55.79],
|
||||
"angles": [0.00, -86.23, 90.00],
|
||||
"size": [26.37, 40.17, 54.13]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"weight": 1,
|
||||
"entities": [
|
||||
{
|
||||
"model": "models/props_junk/dumpster.mdl",
|
||||
"origin": [1498.78, 2630.72, 16.03],
|
||||
"angles": [0.00, 3.76, 0.00],
|
||||
"size": [26.37, 40.17, 54.13]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -36,7 +36,7 @@ int g_BeamSprite;
|
|||
int g_HaloSprite;
|
||||
int g_iLaserIndex;
|
||||
|
||||
#define MAX_FORBIDDEN_CLASSNAMES 9
|
||||
#define MAX_FORBIDDEN_CLASSNAMES 10
|
||||
static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
|
||||
// "env_physics_blocker",
|
||||
// "env_player_blocker",
|
||||
|
@ -49,7 +49,8 @@ static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
|
|||
"func_tracktrain",
|
||||
// "infected",
|
||||
"func_lod",
|
||||
"prop_ragdoll"
|
||||
"prop_ragdoll",
|
||||
"move_rope"
|
||||
};
|
||||
|
||||
#define MAX_FORBIDDEN_MODELS 2
|
||||
|
@ -65,9 +66,11 @@ static char HIGHLIGHTED_CLASSNAMES[MAX_HIGHLIGHTED_CLASSNAMES][] = {
|
|||
"func_brush"
|
||||
}
|
||||
|
||||
ConVar g_cvarEnabled;
|
||||
|
||||
public void OnPluginStart()
|
||||
{
|
||||
g_cvarEnabled = CreateConVar("sm_grabent_allow", "1", "Is grabent allowed", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
RegAdminCmd("sm_grabent_freeze", Cmd_ReleaseFreeze, ADMFLAG_CHEATS, "<0/1> - Toggle entity freeze/unfreeze on release.");
|
||||
RegAdminCmd("sm_grab", Cmd_Grab, ADMFLAG_CHEATS, "Toggle Grab the entity in your crosshair.");
|
||||
RegAdminCmd("+grabent", Cmd_Grab, ADMFLAG_CHEATS, "Grab the entity in your crosshair.");
|
||||
|
@ -131,11 +134,13 @@ public Action Cmd_ReleaseFreeze(client, args)
|
|||
//============================================================================
|
||||
// GRAB ENTITY COMMAND //
|
||||
//============================================================================
|
||||
public Action Cmd_Grab(client, args) {
|
||||
if (client < 1 || client > MaxClients || !IsClientInGame(client))
|
||||
Action Cmd_Grab(int client, int args) {
|
||||
if(!g_cvarEnabled.BoolValue) {
|
||||
ReplyToCommand(client, "[SM] Grabent is disabled");
|
||||
return Plugin_Handled;
|
||||
|
||||
if (g_pGrabbedEnt[client] > 0 && IsValidEntity(g_pGrabbedEnt[client])) {
|
||||
} else if (client < 1 || client > MaxClients || !IsClientInGame(client)) {
|
||||
return Plugin_Handled;
|
||||
} else if (g_pGrabbedEnt[client] > 0 && IsValidEntity(g_pGrabbedEnt[client])) {
|
||||
Cmd_Release(client, 0);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
@ -572,6 +577,7 @@ bool Filter_IgnoreForbidden(int entity, int mask, int data) {
|
|||
}
|
||||
|
||||
bool CheckBlacklist(int entity) {
|
||||
if(entity == 0) return false;
|
||||
static char buffer[64];
|
||||
GetEntityClassname(entity, buffer, sizeof(buffer));
|
||||
for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) {
|
||||
|
@ -591,5 +597,6 @@ bool CheckBlacklist(int entity) {
|
|||
if(StrEqual(buffer, "l4d2_randomizer")) {
|
||||
return false;
|
||||
}
|
||||
GetEntityClassname(entity, buffer, sizeof(buffer));
|
||||
return true;
|
||||
}
|
|
@ -461,6 +461,10 @@ Action Command_SetClientModel(int client, int args) {
|
|||
|
||||
void SetCharacter(int target, int survivorIndex, L4DModelId modelIndex, bool keepModel) {
|
||||
SetEntProp(target, Prop_Send, "m_survivorCharacter", survivorIndex);
|
||||
if(!PrecacheModel(MODELS[view_as<int>(modelIndex)])) {
|
||||
LogError("SetCharacter: INVALID MODEL: %s", MODELS[view_as<int>(modelIndex)]);
|
||||
return;
|
||||
}
|
||||
SetEntityModel(target, MODELS[view_as<int>(modelIndex)]);
|
||||
if (IsFakeClient(target)) {
|
||||
char name[32];
|
||||
|
@ -629,6 +633,9 @@ public void OnMapStart() {
|
|||
PrecacheSound(PRECACHE_SOUNDS[i]);
|
||||
}
|
||||
#endif
|
||||
for(int i = 0; i < 8; i++) {
|
||||
PrecacheModel(MODELS[i]);
|
||||
}
|
||||
|
||||
HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
|
||||
HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
#include <multicolors>
|
||||
#include <jutils>
|
||||
#include <socket>
|
||||
#include <SteamWorks>
|
||||
#undef REQUIRE_PLUGIN
|
||||
#tryinclude <SteamWorks>
|
||||
|
||||
#pragma newdecls required
|
||||
|
||||
|
@ -85,7 +86,7 @@ public void OnPluginStart() {
|
|||
g_socket.SetOption(SocketSendBuffer, BUFFER_SIZE);
|
||||
|
||||
uptime = GetTime();
|
||||
cvar_debug = CreateConVar("sm_adminpanel_debug", "1", "Turn on debug mode", FCVAR_DONTRECORD, true, 0.0, true, 1.0);
|
||||
cvar_debug = CreateConVar("sm_adminpanel_debug", "0", "Turn on debug mode", FCVAR_DONTRECORD, true, 0.0, true, 1.0);
|
||||
|
||||
cvar_authToken = CreateConVar("sm_adminpanel_authtoken", "", "The token for authentication", FCVAR_PROTECTED);
|
||||
cvar_authToken.AddChangeHook(OnCvarChanged);
|
||||
|
@ -172,10 +173,7 @@ void OnSocketError(Socket socket, int errorType, int errorNumber, int any) {
|
|||
}
|
||||
|
||||
void SendFullSync() {
|
||||
PrintToServer("SendFullSync");
|
||||
if(StartPayload(true)) {
|
||||
PrintToServer("SendFullSync : Started");
|
||||
|
||||
AddGameRecord();
|
||||
int stage = L4D2_GetCurrentFinaleStage();
|
||||
if(stage != 0)
|
||||
|
@ -254,15 +252,19 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int
|
|||
}
|
||||
|
||||
void ProcessCommand(int id, const char[] command, const char[] cmdNamespace = "") {
|
||||
char output[128];
|
||||
char output[1024];
|
||||
if(!StartPayload(true)) return;
|
||||
if(cmdNamespace[0] == '\0' || StrEqual(cmdNamespace, "default")) {
|
||||
SplitString(command, " ", output, sizeof(output));
|
||||
// If command has no spaces, we need to manually copy the command to the split part
|
||||
if(SplitString(command, " ", output, sizeof(output)) == -1) {
|
||||
strcopy(output, sizeof(output), command);
|
||||
}
|
||||
if(CommandExists(output)) {
|
||||
ServerCommandEx(output, sizeof(output), "%s", command);
|
||||
AddCommandResponseRecord(id, Result_Boolean, 1, output);
|
||||
} else {
|
||||
AddCommandResponseRecord(id, Result_Error, -1, "Command does not exist");
|
||||
Format(output, sizeof(output), "Command \"%s\" does not exist", output);
|
||||
AddCommandResponseRecord(id, Result_Error, -1, output);
|
||||
}
|
||||
} else if(StrEqual(cmdNamespace, "builtin")) {
|
||||
CommandResultType type;
|
||||
|
|
|
@ -50,7 +50,9 @@ stock void EntFire(const char[] name, const char[] input) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void CreateCoinEntity(float pos[3]) {
|
||||
// int ent = CreateProp("prop_dynamic", "")
|
||||
}
|
||||
|
||||
void SetupEntities(bool blockers = true, bool props = true, bool portals = true) {
|
||||
#if defined DEBUG_BLOCKERS
|
||||
|
|
|
@ -125,6 +125,12 @@ methodmap GuessWhoGame < BaseGame {
|
|||
}
|
||||
}
|
||||
|
||||
property int TargetCoinCount {
|
||||
public get() {
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start() {
|
||||
|
||||
}
|
||||
|
@ -301,6 +307,14 @@ methodmap GuessWhoGame < BaseGame {
|
|||
SDKUnhook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
|
||||
SDKUnhook(client, SDKHook_WeaponEquip, OnWeaponEquip);
|
||||
}
|
||||
|
||||
public void PopulateCoins() {
|
||||
float pos[3];
|
||||
for(int i = 0; i < this.TargetCoinCount; i++) {
|
||||
movePoints.GetRandomPoint(pos);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stock bool ArePlayersJoining() {
|
||||
|
|
|
@ -13,13 +13,18 @@ Action Timer_RecordPoints(Handle h, int i) {
|
|||
vecLastLocation[i] = meta.pos;
|
||||
}
|
||||
}
|
||||
Game.MapTime++;
|
||||
PrintHintText(i, "Points: %d / %d", movePoints.Length, MAX_VALID_LOCATIONS);
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
|
||||
bool firstCheckDone = false;
|
||||
Action Timer_WaitForPlayers(Handle h) {
|
||||
if(!isEnabled) return Plugin_Stop;
|
||||
if(!isEnabled) {
|
||||
waitTimer = null;
|
||||
return Plugin_Stop;
|
||||
}
|
||||
if(!ArePlayersJoining()) {
|
||||
Game.Debug("No players pending, ready to go");
|
||||
if(!firstCheckDone) {
|
||||
|
@ -28,6 +33,7 @@ Action Timer_WaitForPlayers(Handle h) {
|
|||
} else {
|
||||
firstCheckDone = false;
|
||||
InitGamemode();
|
||||
waitTimer = null;
|
||||
return Plugin_Stop;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
native bool SpawnSchematic(const char name[32], const float pos[3], const float angles[3] = NULL_VECTOR);
|
|
@ -1,7 +1,6 @@
|
|||
int BUILDER_COLOR[4] = { 0, 255, 0, 235 };
|
||||
int GLOW_BLUE[4] = { 3, 148, 252 };
|
||||
int GLOW_RED_ALPHA[4] = { 255, 0, 0, 235 };
|
||||
int GLOW_RED[3] = { 255, 0, 0};
|
||||
int GLOW_WHITE[4] = { 255, 255, 255, 255 };
|
||||
int GLOW_GREEN[4] = { 3, 252, 53 };
|
||||
float ORIGIN_SIZE[3] = { 2.0, 2.0, 2.0 };
|
||||
|
@ -27,26 +26,21 @@ char MODE_NAME[5][] = {
|
|||
"Freelook"
|
||||
}
|
||||
|
||||
enum editFlag {
|
||||
enum {
|
||||
Edit_None,
|
||||
Edit_Copy = 1,
|
||||
Edit_Preview = 2,
|
||||
Edit_WallCreator = 4
|
||||
Edit_WallCreator = 4,
|
||||
Edit_Manager = 8
|
||||
}
|
||||
|
||||
enum buildType {
|
||||
Build_Solid,
|
||||
Build_Physics,
|
||||
Build_NonSolid,
|
||||
// TODO: Build_Weapon (spawn as weapon?)
|
||||
}
|
||||
|
||||
enum CompleteType {
|
||||
Complete_WallSuccess,
|
||||
Complete_WallError,
|
||||
Complete_PropSpawned,
|
||||
Complete_PropError,
|
||||
Complete_EditSuccess
|
||||
}
|
||||
|
||||
enum StackerDirection {
|
||||
Stack_Off,
|
||||
|
@ -88,6 +82,7 @@ enum struct EditorData {
|
|||
int colorIndex;
|
||||
int axis;
|
||||
int snapAngle;
|
||||
float rotateSpeed;
|
||||
int moveSpeed;
|
||||
float moveDistance;
|
||||
int entity;
|
||||
|
@ -97,11 +92,14 @@ enum struct EditorData {
|
|||
|
||||
editMode mode;
|
||||
buildType buildType;
|
||||
editFlag flags;
|
||||
int flags;
|
||||
|
||||
PrivateForward callback;
|
||||
bool isEditCallback;
|
||||
|
||||
void Reset(bool initial = false) {
|
||||
// Clear preview entity
|
||||
if(this.entity != INVALID_ENT_REFERENCE && this.flags & Edit_Preview && IsValidEntity(this.entity)) {
|
||||
if(this.entity != INVALID_ENT_REFERENCE && (this.flags & Edit_Preview) && IsValidEntity(this.entity)) {
|
||||
RemoveEntity(this.entity);
|
||||
}
|
||||
this.stackerDirection = Stack_Off;
|
||||
|
@ -111,12 +109,13 @@ enum struct EditorData {
|
|||
this.size[0] = this.size[1] = this.size[2] = 5.0;
|
||||
this.angles[0] = this.angles[1] = this.angles[2] = 0.0;
|
||||
this.colorIndex = 0;
|
||||
this.axis = 1;
|
||||
this.axis = 0;
|
||||
this.moveDistance = 200.0;
|
||||
this.flags = Edit_None;
|
||||
this.classname[0] = '\0';
|
||||
this.CalculateMins();
|
||||
this.SetMode(INACTIVE);
|
||||
this.rotateSpeed = 0.1;
|
||||
// Settings that don't get reset on new spawns:
|
||||
if(initial) {
|
||||
this.color[0] = this.color[1] = this.color[2] = this.color[3] = 255;
|
||||
|
@ -138,6 +137,12 @@ enum struct EditorData {
|
|||
if(this.flags & Edit_WallCreator || this.entity == INVALID_ENT_REFERENCE) {
|
||||
Effect_DrawBeamBoxRotatableToAll(this.origin, this.mins, this.size, this.angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, amplitude, color, 0);
|
||||
} else {
|
||||
if(this.snapAngle != 1) {
|
||||
this.angles[0] = RoundToNearestInterval(this.angles[0], this.snapAngle);
|
||||
this.angles[1] = RoundToNearestInterval(this.angles[1], this.snapAngle);
|
||||
this.angles[2] = RoundToNearestInterval(this.angles[2], this.snapAngle);
|
||||
|
||||
}
|
||||
TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR);
|
||||
}
|
||||
Effect_DrawAxisOfRotationToAll(this.origin, this.angles, ORIGIN_SIZE, g_iLaserIndex, 0, 0, 30, 0.2, 0.1, 0.1, 0, 0.0, 0);
|
||||
|
@ -147,9 +152,6 @@ enum struct EditorData {
|
|||
void UpdateEntity() {
|
||||
int alpha = this.color[3];
|
||||
// Keep previews transparent
|
||||
if(this.flags & Edit_Preview) {
|
||||
alpha = 200;
|
||||
}
|
||||
SetEntityRenderColor(this.entity, this.color[0], this.color[1], this.color[2], alpha);
|
||||
}
|
||||
|
||||
|
@ -178,6 +180,10 @@ enum struct EditorData {
|
|||
void SetName(const char[] name) {
|
||||
strcopy(this.name, sizeof(this.name), name);
|
||||
}
|
||||
void SetCallback(PrivateForward callback, bool isEditCallback) {
|
||||
this.callback = callback;
|
||||
this.isEditCallback = isEditCallback;
|
||||
}
|
||||
|
||||
void CycleMode() {
|
||||
// Remove frozen state when cycling
|
||||
|
@ -210,43 +216,43 @@ enum struct EditorData {
|
|||
PrintToChat(this.client, "\x04[Editor]\x01 Mode: \x05%s\x01 (Press \x04RELOAD\x01 to change)", MODE_NAME[this.mode]);
|
||||
}
|
||||
|
||||
void CycleStacker(float tick) {
|
||||
if(tick - cmdThrottle[this.client] <= 0.10) return;
|
||||
void CycleStacker() {
|
||||
int newDirection = view_as<int>(this.stackerDirection) + 1;
|
||||
if(newDirection == view_as<int>(Stack_Down)) newDirection = 0;
|
||||
this.stackerDirection = view_as<StackerDirection>(newDirection);
|
||||
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Stacker: %s\x01", STACK_DIRECTION_NAME[this.stackerDirection]);
|
||||
cmdThrottle[this.client] = tick;
|
||||
}
|
||||
|
||||
void ToggleCollision(float tick) {
|
||||
if(tick - cmdThrottle[this.client] <= 0.25) return;
|
||||
void ToggleCollision() {
|
||||
this.hasCollision = !this.hasCollision
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Collision: %s", ON_OFF_STRING[view_as<int>(this.hasCollision)]);
|
||||
cmdThrottle[this.client] = tick;
|
||||
}
|
||||
|
||||
void ToggleCollisionRotate(float tick) {
|
||||
if(tick - cmdThrottle[this.client] <= 0.20) return;
|
||||
void ToggleCollisionRotate() {
|
||||
this.hasCollisionRotate = !this.hasCollisionRotate
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Rotate with Collision: %s", ON_OFF_STRING[view_as<int>(this.hasCollisionRotate)]);
|
||||
cmdThrottle[this.client] = tick;
|
||||
}
|
||||
|
||||
void CycleAxis(float tick) {
|
||||
if(tick - cmdThrottle[this.client] <= 0.1) return;
|
||||
void CycleAxis() {
|
||||
// if(tick - cmdThrottle[this.client] <= 0.1) return;
|
||||
if(this.axis == 0) {
|
||||
this.axis = 1;
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05HEADING (Y)\x01");
|
||||
} else if(this.axis == 1) {
|
||||
this.axis = 2;
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05PITCH (X)\x01");
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05ROLL (Z)\x01");
|
||||
} else {
|
||||
this.axis = 0;
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05ROLL (Z)\x01");
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05PITCH AND HEADING (X, Y)\x01");
|
||||
}
|
||||
// cmdThrottle[this.client] = tick;
|
||||
}
|
||||
|
||||
void IncrementAxis(int axis, int mouse) {
|
||||
if(this.snapAngle == 1) {
|
||||
this.angles[axis] += mouse * this.rotateSpeed;
|
||||
} else {
|
||||
if(mouse > 0) this.angles[axis] += this.snapAngle;
|
||||
else if(mouse < 0) this.angles[axis] -= this.snapAngle;
|
||||
}
|
||||
cmdThrottle[this.client] = tick;
|
||||
}
|
||||
|
||||
void CycleSnapAngle(float tick) {
|
||||
|
@ -328,18 +334,38 @@ enum struct EditorData {
|
|||
|
||||
// Complete the edit, wall creation, or spawning
|
||||
CompleteType Done(int& entity) {
|
||||
CompleteType type;
|
||||
if(this.flags & Edit_WallCreator) {
|
||||
return this._FinishWall(entity) ? Complete_WallSuccess : Complete_WallError;
|
||||
type = this._FinishWall(entity) ? Complete_WallSuccess : Complete_WallError;
|
||||
} else if(this.flags & Edit_Preview) {
|
||||
return this._FinishPreview(entity) ? Complete_PropSpawned : Complete_PropError;
|
||||
type = this._FinishPreview(entity) ? Complete_PropSpawned : Complete_PropError;
|
||||
} else {
|
||||
// Is edit, do nothing, just reset
|
||||
PrintHintText(this.client, "Edit Complete");
|
||||
this.Reset();
|
||||
entity = 0;
|
||||
|
||||
return Complete_EditSuccess;
|
||||
type = Complete_EditSuccess;
|
||||
}
|
||||
if(this.callback) {
|
||||
Call_StartForward(this.callback);
|
||||
Call_PushCell(this.client);
|
||||
Call_PushCell(entity);
|
||||
Call_PushCell(type);
|
||||
bool result;
|
||||
Call_Finish(result);
|
||||
// Cancel menu:
|
||||
if(this.isEditCallback) delete this.callback;
|
||||
if(this.isEditCallback || !result) {
|
||||
// No native way to close a menu, so open a dummy menu and close it:
|
||||
// Handler doesn't matter, no options are added
|
||||
Menu menu = new Menu(Spawn_RootHandler);
|
||||
menu.Display(this.client, 1);
|
||||
} else {
|
||||
delete this.callback;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
bool _FinishWall(int& id) {
|
||||
|
@ -569,7 +595,7 @@ enum struct EditorData {
|
|||
DispatchKeyValue(entity, "targetname", "prop_preview");
|
||||
DispatchKeyValue(entity, "solid", "0");
|
||||
DispatchKeyValue(entity, "rendercolor", "255 128 255");
|
||||
DispatchKeyValue(entity, "renderamt", "200");
|
||||
DispatchKeyValue(entity, "renderamt", "255");
|
||||
DispatchKeyValue(entity, "rendermode", "1");
|
||||
TeleportEntity(entity, this.origin, NULL_VECTOR, NULL_VECTOR);
|
||||
if(!DispatchSpawn(entity)) {
|
||||
|
@ -583,8 +609,11 @@ enum struct EditorData {
|
|||
return IsValidEntity(entity);
|
||||
}
|
||||
|
||||
// Adds an existing entity to the editor, to move it.
|
||||
// asWallCopy: to instead copy the wall's size and position (walls only)
|
||||
/**
|
||||
* Adds an existing entity to the editor, to move it.
|
||||
* asWallCopy: to instead copy the wall's size and position (walls only)
|
||||
* @deprecated
|
||||
*/
|
||||
void Import(int entity, bool asWallCopy = false, editMode mode = SCALE) {
|
||||
this.Reset();
|
||||
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin);
|
||||
|
@ -599,6 +628,22 @@ enum struct EditorData {
|
|||
this.SetMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports an entity
|
||||
*/
|
||||
void ImportEntity(int entity, int flags = 0, editMode mode = SCALE) {
|
||||
this.Reset();
|
||||
this.flags = flags;
|
||||
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin);
|
||||
GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles);
|
||||
this.prevOrigin = this.origin;
|
||||
this.prevAngles = this.angles;
|
||||
GetEntPropVector(entity, Prop_Send, "m_vecMins", this.mins);
|
||||
GetEntPropVector(entity, Prop_Send, "m_vecMaxs", this.size);
|
||||
this.entity = entity;
|
||||
this.SetMode(mode);
|
||||
}
|
||||
|
||||
// Cancels the current placement. If the edit is a copy/preview, the entity is also deleted
|
||||
// If entity is not a wall, it will be returned
|
||||
void Cancel() {
|
||||
|
@ -611,9 +656,22 @@ enum struct EditorData {
|
|||
}
|
||||
this.SetMode(INACTIVE);
|
||||
PrintHintText(this.client, "Cancelled");
|
||||
if(this.callback) {
|
||||
delete this.callback;
|
||||
}
|
||||
// CPrintToChat(this.client, "\x04[Editor]\x01 Cancelled.");
|
||||
}
|
||||
}
|
||||
|
||||
void SendEditorMessage(int client, const char[] format, any ...) {
|
||||
char message[256];
|
||||
VFormat(message, sizeof(message), format, 3);
|
||||
CPrintToChat(client, "\x04`[Editor]\x01 %s", message);
|
||||
}
|
||||
|
||||
stock float RoundToNearestInterval(float value, int interval) {
|
||||
return float(RoundFloat(value / float(interval)) * interval);
|
||||
}
|
||||
EditorData Editor[MAXPLAYERS+1];
|
||||
|
||||
Action OnWallClicked(int entity, int activator, int caller, UseType type, float value) {
|
||||
|
|
|
@ -43,7 +43,7 @@ int lastHatRequestTime[MAXPLAYERS+1];
|
|||
HatInstance hatData[MAXPLAYERS+1];
|
||||
StringMap g_HatPresets;
|
||||
|
||||
#define MAX_FORBIDDEN_CLASSNAMES 14
|
||||
#define MAX_FORBIDDEN_CLASSNAMES 15
|
||||
char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
|
||||
"prop_door_rotating_checkpoint",
|
||||
"env_physics_blocker",
|
||||
|
@ -59,7 +59,8 @@ char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
|
|||
// "infected",
|
||||
"func_lod",
|
||||
"func_door",
|
||||
"prop_ragdoll"
|
||||
"prop_ragdoll",
|
||||
"move_rope"
|
||||
};
|
||||
|
||||
#define MAX_FORBIDDEN_MODELS 2
|
||||
|
@ -73,7 +74,7 @@ char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = {
|
|||
// Classnames that should automatically trigger reverse infected
|
||||
static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = {
|
||||
"infected",
|
||||
"func_movelinear"
|
||||
"func_movelinear",
|
||||
};
|
||||
|
||||
Action Command_DoAHat(int client, int args) {
|
||||
|
|
122
scripting/include/hats/natives.sp
Normal file
122
scripting/include/hats/natives.sp
Normal file
|
@ -0,0 +1,122 @@
|
|||
|
||||
int Native_StartEdit(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
int entity = GetNativeCell(2);
|
||||
Editor[client].Import(entity, false);
|
||||
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
|
||||
fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(3));
|
||||
Editor[client].SetCallback(fwd, true);
|
||||
return 0;
|
||||
}
|
||||
int Native_StartSpawner(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
g_PropData[client].Selector.Cancel();
|
||||
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
|
||||
fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(2));
|
||||
Editor[client].SetCallback(fwd, false);
|
||||
ShowCategoryList(client, ROOT_CATEGORY);
|
||||
return 0;
|
||||
}
|
||||
int Native_CancelEdit(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
Editor[client].Cancel();
|
||||
return 0;
|
||||
}
|
||||
int Native_IsEditorActive(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
Editor[client].IsActive();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Native_StartSelector(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
int color[3] = { 0, 255, 0 };
|
||||
PrivateForward fwd = new PrivateForward(ET_Single, Param_Cell, Param_Cell);
|
||||
fwd.AddFunction(plugin, GetNativeFunction(2));
|
||||
GetNativeArray(3, color, 3);
|
||||
int limit = GetNativeCell(4);
|
||||
g_PropData[client].Selector.Start(color, 0, limit);
|
||||
g_PropData[client].Selector.SetOnEnd(fwd);
|
||||
return 0;
|
||||
}
|
||||
int Native_CancelSelector(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
g_PropData[client].Selector.Cancel();
|
||||
return 0;
|
||||
}
|
||||
int Native_IsSelectorActive(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
g_PropData[client].Selector.IsActive();
|
||||
return 0;
|
||||
}
|
||||
int Native_Selector_Start(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
int color[3] = { 0, 255, 0 };
|
||||
GetNativeArray(2, color, 3);
|
||||
int flags = GetNativeCell(3);
|
||||
int limit = GetNativeCell(4);
|
||||
g_PropData[client].Selector.Start(color, flags, limit);
|
||||
return 0;
|
||||
}
|
||||
int Native_Selector_GetCount(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
if(!g_PropData[client].Selector.IsActive()) {
|
||||
return -1;
|
||||
} else {
|
||||
return g_PropData[client].Selector.list.Length;
|
||||
}
|
||||
}
|
||||
int Native_Selector_GetActive(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
return g_PropData[client].Selector.IsActive();
|
||||
}
|
||||
int Native_Selector_SetOnEnd(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell);
|
||||
fwd.AddFunction(plugin, GetNativeFunction(2));
|
||||
g_PropData[client].Selector.SetOnEnd(fwd);
|
||||
return 0;
|
||||
}
|
||||
int Native_Selector_SetOnPreSelect(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
PrivateForward fwd = new PrivateForward(ET_Single, Param_Cell, Param_Cell);
|
||||
if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0;
|
||||
g_PropData[client].Selector.SetOnPreSelect(fwd);
|
||||
return 1;
|
||||
}
|
||||
int Native_Selector_SetOnPostSelect(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell);
|
||||
if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0;
|
||||
g_PropData[client].Selector.SetOnPostSelect(fwd);
|
||||
return 1;
|
||||
}
|
||||
int Native_Selector_SetOnUnselect(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell);
|
||||
if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0;
|
||||
g_PropData[client].Selector.SetOnUnselect(fwd);
|
||||
return 1;
|
||||
}
|
||||
int Native_Selector_AddEntity(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
int entity = GetNativeCell(2);
|
||||
g_PropData[client].Selector.AddEntity(entity, false);
|
||||
return 0;
|
||||
}
|
||||
int Native_Selector_RemoveEntity(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
int entity = GetNativeCell(2);
|
||||
g_PropData[client].Selector.RemoveEntity(entity);
|
||||
return 0;
|
||||
}
|
||||
int Native_Selector_Cancel(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
g_PropData[client].Selector.Cancel();
|
||||
return 0;
|
||||
}
|
||||
int Native_Selector_End(Handle plugin, int numParams) {
|
||||
int client = GetNativeCell(1);
|
||||
g_PropData[client].Selector.End();
|
||||
return 0;
|
||||
}
|
|
@ -25,6 +25,8 @@ enum SaveType {
|
|||
Save_Schematic
|
||||
}
|
||||
|
||||
int GLOW_MANAGER[3] = { 52, 174, 235 };
|
||||
|
||||
enum struct Schematic {
|
||||
char name[64];
|
||||
char creatorSteamid[32];
|
||||
|
@ -134,6 +136,197 @@ public any Native_SpawnSchematic(Handle plugin, int numParams) {
|
|||
delete list;
|
||||
return true;
|
||||
}
|
||||
|
||||
enum struct PropSelectorIterator {
|
||||
ArrayList _list;
|
||||
int _index;
|
||||
int Entity;
|
||||
|
||||
void _Init(ArrayList list) {
|
||||
this._list = list;
|
||||
this._index = -1;
|
||||
}
|
||||
|
||||
bool Next() {
|
||||
this._index++;
|
||||
return this._index + 1 < this._list.Length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
enum struct PropSelector {
|
||||
int selectColor[3];
|
||||
int limit;
|
||||
ArrayList list;
|
||||
PrivateForward endCallback;
|
||||
PrivateForward selectPreCallback;
|
||||
PrivateForward selectPostCallback;
|
||||
PrivateForward unSelectCallback;
|
||||
int _client;
|
||||
|
||||
PropSelectorIterator Iter() {
|
||||
PropSelectorIterator iter;
|
||||
iter._Init(this.list);
|
||||
return iter;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
if(this.endCallback) delete this.endCallback;
|
||||
if(this.selectPreCallback) delete this.selectPreCallback;
|
||||
if(this.selectPostCallback) delete this.selectPostCallback;
|
||||
if(this.unSelectCallback) delete this.unSelectCallback;
|
||||
if(this.list) delete this.list;
|
||||
}
|
||||
|
||||
void Start(int color[3], int flags = 0, int limit = 0) {
|
||||
this.selectColor = color;
|
||||
this.limit = 0;
|
||||
this.list = new ArrayList();
|
||||
SendEditorMessage(this._client, "Left click to select, right click to unselect");
|
||||
SendEditorMessage(this._client, "Press WALK+USE to confirm, DUCK+USE to cancel");
|
||||
}
|
||||
|
||||
void SetOnEnd(PrivateForward callback) {
|
||||
this.endCallback = callback;
|
||||
}
|
||||
void SetOnPreSelect(PrivateForward callback) {
|
||||
this.selectPreCallback = callback;
|
||||
}
|
||||
void SetOnPostSelect(PrivateForward callback) {
|
||||
this.selectPostCallback = callback;
|
||||
}
|
||||
void SetOnUnselect(PrivateForward callback) {
|
||||
this.unSelectCallback = callback;
|
||||
}
|
||||
|
||||
void StartDirect(int color[3], SelectDoneCallback callback, int limit = 0) {
|
||||
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell);
|
||||
fwd.AddFunction(INVALID_HANDLE, callback);
|
||||
this.Start(color, 0, limit);
|
||||
this.SetOnEnd(fwd);
|
||||
}
|
||||
|
||||
bool IsActive() {
|
||||
return this.list != null;
|
||||
}
|
||||
|
||||
void End() {
|
||||
if(this.list == null) return;
|
||||
SendEditorMessage(this._client, "Selection completed");
|
||||
// Reset glows, remove selection from our spawned props
|
||||
for(int i = 0; i < this.list.Length; i++) {
|
||||
int ref = this.list.Get(i);
|
||||
if(IsValidEntity(ref)) {
|
||||
L4D2_RemoveEntityGlow(ref);
|
||||
RemoveSpawnedProp(ref);
|
||||
}
|
||||
}
|
||||
if(this.endCallback) {
|
||||
if(GetForwardFunctionCount(this.endCallback) == 0) {
|
||||
PrintToServer("[Editor] Warn: Selector.End(): callback has no functions assigned to it.");
|
||||
}
|
||||
Call_StartForward(this.endCallback);
|
||||
Call_PushCell(this._client);
|
||||
Call_PushCell(this.list.Clone());
|
||||
int result = Call_Finish();
|
||||
if(result != SP_ERROR_NONE) {
|
||||
PrintToServer("[Editor] Warn: Selector.End() forward error: %d", result);
|
||||
}
|
||||
} else {
|
||||
PrintToServer("[Editor] Warn: Selector.End() called but no callback assigned, voiding list");
|
||||
}
|
||||
this.Reset();
|
||||
}
|
||||
|
||||
void Cancel() {
|
||||
if(this.endCallback) {
|
||||
Call_StartForward(this.endCallback);
|
||||
Call_PushCell(this._client);
|
||||
Call_PushCell(INVALID_HANDLE);
|
||||
Call_Finish();
|
||||
}
|
||||
if(this.list) {
|
||||
for(int i = 0; i < this.list.Length; i++) {
|
||||
int ref = this.list.Get(i);
|
||||
L4D2_RemoveEntityGlow(ref);
|
||||
}
|
||||
}
|
||||
PrintToChat(this._client, "\x04[Editor]\x01 Selection cancelled.");
|
||||
this.Reset();
|
||||
}
|
||||
|
||||
int GetEntityRefIndex(int ref) {
|
||||
int index = this.list.FindValue(ref);
|
||||
if(index > -1) {
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Removes entity from list
|
||||
* @return returns entity ref of entity removed
|
||||
*/
|
||||
int RemoveEntity(int entity) {
|
||||
if(this.list == null) return -2;
|
||||
|
||||
L4D2_RemoveEntityGlow(entity);
|
||||
int ref = EntIndexToEntRef(entity);
|
||||
int index = this.GetEntityRefIndex(ref);
|
||||
if(index > -1) {
|
||||
this.list.Erase(index);
|
||||
if(this.unSelectCallback != null) {
|
||||
Call_StartForward(this.unSelectCallback)
|
||||
Call_PushCell(this._client);
|
||||
Call_PushCell(EntRefToEntIndex(ref));
|
||||
Call_Finish();
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
return INVALID_ENT_REFERENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds entity to list
|
||||
* @return index into list of entity
|
||||
* @return -1 if already added
|
||||
* @return -2 if callback rejected
|
||||
*/
|
||||
int AddEntity(int entity, bool useCallback = true) {
|
||||
if(this.list == null) return -2;
|
||||
|
||||
int ref = EntIndexToEntRef(entity);
|
||||
if(this.GetEntityRefIndex(ref) == -1) {
|
||||
PrintToServer("Selector.AddEntity: PRE CALLBACK");
|
||||
// FIXME: crashes server, sourcemod bug
|
||||
/*if(this.selectPreCallback != null && useCallback) {
|
||||
Call_StartForward(this.selectPreCallback)
|
||||
Call_PushCell(this._client);
|
||||
Call_PushCell(entity);
|
||||
bool allowed = true;
|
||||
PrintToServer("Selector.AddEntity: PRE CALLBACK pre finish");
|
||||
Call_Finish(allowed);
|
||||
PrintToServer("Selector.AddEntity: PRE CALLBACK pre result %b", allowed);
|
||||
if(!allowed) return -2;
|
||||
}*/
|
||||
|
||||
L4D2_SetEntityGlow(entity, L4D2Glow_Constant, 10000, 0, this.selectColor, false);
|
||||
int index = this.list.Push(ref);
|
||||
PrintToServer("Selector.AddEntity: post CALLBACK pre");
|
||||
//FIXME: crashes server, sourcemod bug
|
||||
/*if(this.selectPostCallback != null && useCallback) {
|
||||
Call_StartForward(this.selectPostCallback)
|
||||
Call_PushCell(this._client);
|
||||
Call_PushCell(entity);
|
||||
//Call_PushCell(index);
|
||||
Call_Finish();
|
||||
}*/
|
||||
PrintToServer("Selector.AddEntity: post CALLBACK post");
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
enum struct PlayerPropData {
|
||||
ArrayList categoryStack;
|
||||
ArrayList itemBuffer;
|
||||
|
@ -144,14 +337,19 @@ enum struct PlayerPropData {
|
|||
int lastActiveTime;
|
||||
char classnameOverride[64];
|
||||
ChatPrompt chatPrompt;
|
||||
ArrayList markedProps;
|
||||
PropSelector Selector;
|
||||
SaveType pendingSaveType;
|
||||
|
||||
Schematic schematic;
|
||||
int highlightedEntityRef;
|
||||
int managerEntityRef;
|
||||
|
||||
void Init(int client) {
|
||||
this.Selector._client = client;
|
||||
}
|
||||
// Called on PlayerDisconnect
|
||||
void Reset() {
|
||||
if(this.markedProps != null) delete this.markedProps;
|
||||
if(this.Selector.IsActive()) this.Selector.Cancel();
|
||||
this.chatPrompt = Prompt_None;
|
||||
this.clearListBuffer = false;
|
||||
this.lastCategoryIndex = 0;
|
||||
|
@ -161,6 +359,19 @@ enum struct PlayerPropData {
|
|||
this.CleanupBuffers();
|
||||
this.pendingSaveType = Save_None;
|
||||
this.schematic.Reset();
|
||||
this.managerEntityRef = INVALID_ENT_REFERENCE;
|
||||
this.StopHighlight();
|
||||
}
|
||||
|
||||
void StartHighlight(int entity) {
|
||||
this.highlightedEntityRef = EntIndexToEntRef(entity);
|
||||
L4D2_SetEntityGlow(entity, L4D2Glow_Constant, 10000, 0, GLOW_MANAGER, false);
|
||||
}
|
||||
void StopHighlight() {
|
||||
if(IsValidEntity(this.highlightedEntityRef)) {
|
||||
L4D2_RemoveEntityGlow(this.highlightedEntityRef);
|
||||
}
|
||||
this.highlightedEntityRef = INVALID_ENT_REFERENCE;
|
||||
}
|
||||
|
||||
void StartSchematic(int client, const char[] name) {
|
||||
|
|
|
@ -8,6 +8,8 @@ public void OnAdminMenuReady(Handle topMenuHandle) {
|
|||
topMenu.AddItem("editor_edit", AdminMenu_Edit, g_propSpawnerCategory, "sm_prop");
|
||||
topMenu.AddItem("editor_delete", AdminMenu_Delete, g_propSpawnerCategory, "sm_prop");
|
||||
topMenu.AddItem("editor_saveload", AdminMenu_SaveLoad, g_propSpawnerCategory, "sm_prop");
|
||||
topMenu.AddItem("editor_manager", AdminMenu_Manager, g_propSpawnerCategory, "sm_prop");
|
||||
topMenu.AddItem("editor_selector", AdminMenu_Selector, g_propSpawnerCategory, "sm_prop");
|
||||
}
|
||||
g_topMenu = topMenu;
|
||||
}
|
||||
|
@ -24,6 +26,15 @@ void Category_Handler(TopMenu topmenu, TopMenuAction action, TopMenuObject topob
|
|||
}
|
||||
}
|
||||
|
||||
void AdminMenu_Selector(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
|
||||
if(action == TopMenuAction_DisplayOption) {
|
||||
Format(buffer, maxlength, "Selector");
|
||||
} else if(action == TopMenuAction_SelectOption) {
|
||||
ShowManagerSelectorMenu(param);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AdminMenu_Spawn(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
|
||||
if(action == TopMenuAction_DisplayOption) {
|
||||
Format(buffer, maxlength, "Spawn Props");
|
||||
|
@ -79,6 +90,14 @@ void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject obj
|
|||
}
|
||||
}
|
||||
|
||||
void AdminMenu_Manager(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
|
||||
if(action == TopMenuAction_DisplayOption) {
|
||||
Format(buffer, maxlength, "Manager (ALPHA)");
|
||||
} else if(action == TopMenuAction_SelectOption) {
|
||||
Spawn_ShowManagerMainMenu(param);
|
||||
}
|
||||
}
|
||||
|
||||
int SaveLoadMainMenuHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
char info[2];
|
||||
|
@ -87,7 +106,7 @@ int SaveLoadMainMenuHandler(Menu menu, MenuAction action, int client, int param2
|
|||
ShowSaves(client, type);
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
Spawn_ShowSaveLoadMainMenu(client);
|
||||
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||
}
|
||||
} else if (action == MenuAction_End)
|
||||
delete menu;
|
||||
|
@ -127,6 +146,7 @@ int SaveLoadSceneHandler(Menu menu, MenuAction action, int client, int param2) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int SaveLoadSchematicHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
char saveName[64];
|
||||
|
@ -189,40 +209,160 @@ int SaveLoadConfirmHandler(Menu menu, MenuAction action, int client, int param2)
|
|||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DeleteHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||
int ManagerHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
char info[8];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
int index = StringToInt(info);
|
||||
if(index == -1) {
|
||||
if(info[0] != '\0') {
|
||||
int index = StringToInt(info);
|
||||
int ref = g_spawnedItems.Get(index);
|
||||
// TODO: add delete confirm
|
||||
if(!IsValidEntity(ref)) {
|
||||
SendEditorMessage(client, "Entity has disappeared");
|
||||
} else {
|
||||
int entity = EntRefToEntIndex(ref);
|
||||
g_PropData[client].managerEntityRef = ref;
|
||||
g_PropData[client].StartHighlight(entity);
|
||||
ShowManagerEntityMenu(client, entity);
|
||||
}
|
||||
}
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
Spawn_ShowSaveLoadMainMenu(client);
|
||||
}
|
||||
} else if (action == MenuAction_End)
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
int ManagerEntityHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
g_PropData[client].StopHighlight();
|
||||
char info[32];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
int ref = g_PropData[client].managerEntityRef;
|
||||
if(!IsValidEntity(ref)) {
|
||||
SendEditorMessage(client, "Entity disappeared");
|
||||
return 0;
|
||||
}
|
||||
if(StrEqual(info, "edit")) {
|
||||
Editor[client].ImportEntity(EntRefToEntIndex(ref), Edit_Manager);
|
||||
Spawn_ShowManagerMainMenu(client);
|
||||
} else if(StrEqual(info, "delete")) {
|
||||
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||
int spawnedRef = g_spawnedItems.Get(i);
|
||||
if(spawnedRef == ref) {
|
||||
g_spawnedItems.Erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(IsValidEntity(ref)) {
|
||||
RemoveEntity(ref);
|
||||
}
|
||||
Spawn_ShowManagerMainMenu(client);
|
||||
} else if(StrEqual(info, "view")) {
|
||||
ReplyToCommand(client, "Maybe soon.");
|
||||
} else if(StrEqual(info, "select")) {
|
||||
ShowManagerSelectorMenu(client);
|
||||
int entity = EntRefToEntIndex(ref);
|
||||
g_PropData[client].Selector.AddEntity(entity);
|
||||
} else {
|
||||
SendEditorMessage(client, "Unknown option / not implemented");
|
||||
}
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
g_PropData[client].StopHighlight();
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
Spawn_ShowManagerMainMenu(client);
|
||||
}
|
||||
} else if (action == MenuAction_End)
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
int ManagerSelectorMainMenuHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
if(!EntitySelector.FromClient(client).Active) {
|
||||
return 0;
|
||||
}
|
||||
char info[32];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
if(StrEqual(info, "list")) {
|
||||
SendEditorMessage(client, "Not implemented");
|
||||
} else if(StrEqual(info, "actions")) {
|
||||
ShowManagerSelectorActionsMenu(client);
|
||||
} else if(StrEqual(info, "cancel")) {
|
||||
g_PropData[client].Selector.Cancel();
|
||||
}
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
g_PropData[client].Selector.Cancel();
|
||||
} else if (action == MenuAction_End)
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
int ManagerSelectorActionHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
if(!g_PropData[client].Selector.IsActive()) {
|
||||
return 0;
|
||||
}
|
||||
char info[32];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
if(StrEqual(info, "delete")) {
|
||||
for(int i = 0; i < g_PropData[client].Selector.list.Length; i++) {
|
||||
int ref = g_PropData[client].Selector.list.Get(i);
|
||||
if(IsValidEntity(ref)) {
|
||||
RemoveEntity(ref);
|
||||
}
|
||||
}
|
||||
g_PropData[client].Selector.End();
|
||||
Spawn_ShowManagerMainMenu(client);
|
||||
} else if(StrEqual(info, "save")) {
|
||||
// TODO: implement
|
||||
SendEditorMessage(client, "Not implemented");
|
||||
} else {
|
||||
SendEditorMessage(client, "Unknown option / not implemented");
|
||||
}
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
Spawn_ShowSaveLoadMainMenu(client);
|
||||
}
|
||||
} else if (action == MenuAction_End)
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
int COLOR_DELETE[3] = { 255, 0, 0 }
|
||||
|
||||
int DeleteHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
char info[128];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
int ref = StringToInt(info[2]);
|
||||
int option = StringToInt(info);
|
||||
if(option == -1) {
|
||||
// Delete all (everyone)
|
||||
int count = DeleteAll();
|
||||
PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count);
|
||||
ShowDeleteList(client);
|
||||
} else if(index == -2) {
|
||||
} else if(option == -2) {
|
||||
// Delete all (mine only)
|
||||
int count = DeleteAll(client);
|
||||
PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count);
|
||||
ShowDeleteList(client);
|
||||
} else if(index == -3) {
|
||||
if(g_PropData[client].markedProps != null) {
|
||||
EndDeleteTool(client, false);
|
||||
} else if(option == -3) {
|
||||
if(g_PropData[client].Selector.IsActive()) {
|
||||
g_PropData[client].Selector.End();
|
||||
PrintToChat(client, "\x04[Editor]\x01 Delete tool cancelled");
|
||||
} else {
|
||||
g_PropData[client].markedProps = new ArrayList();
|
||||
g_PropData[client].Selector.StartDirect(COLOR_DELETE, OnDeleteToolEnd);
|
||||
PrintToChat(client, "\x04[Editor]\x01 Delete tool active. Press \x05Left Mouse\x01 to mark props, \x05Right Mouse\x01 to undo. SHIFT+USE to spawn, CTRL+USE to cancel");
|
||||
}
|
||||
ShowDeleteList(client);
|
||||
} else {
|
||||
int ref = g_spawnedItems.Get(index);
|
||||
// TODO: add delete confirm
|
||||
int index = g_spawnedItems.FindValue(ref);
|
||||
if(IsValidEntity(ref)) {
|
||||
RemoveEntity(ref);
|
||||
}
|
||||
g_spawnedItems.Erase(index);
|
||||
if(index > 0) {
|
||||
if(index > -1) {
|
||||
g_spawnedItems.Erase(index);
|
||||
index--;
|
||||
}
|
||||
} else { index = 0; }
|
||||
ShowDeleteList(client, index);
|
||||
}
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
|
@ -317,16 +457,18 @@ int EditHandler(Menu menu, MenuAction action, int client, int param2) {
|
|||
if (action == MenuAction_Select) {
|
||||
char info[8];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
int index = StringToInt(info);
|
||||
int ref = g_spawnedItems.Get(index);
|
||||
int ref = StringToInt(info);
|
||||
int index = g_spawnedItems.FindValue(ref);
|
||||
int entity = EntRefToEntIndex(ref);
|
||||
if(entity > 0) {
|
||||
Editor[client].Import(entity, false);
|
||||
PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d", entity);
|
||||
} else {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Entity disappeared.");
|
||||
g_spawnedItems.Erase(index);
|
||||
index--;
|
||||
if(index > -1) {
|
||||
g_spawnedItems.Erase(index);
|
||||
index--;
|
||||
} else { index = 0; }
|
||||
}
|
||||
ShowEditList(client, index);
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
/////////////
|
||||
void ShowSpawnRoot(int client) {
|
||||
Menu menu = new Menu(Spawn_RootHandler);
|
||||
menu.SetTitle("Choose list:");
|
||||
menu.AddItem("f", "Favorites (WIP)");
|
||||
menu.AddItem("r", "Recents");
|
||||
menu.AddItem("s", "Search");
|
||||
menu.AddItem("n", "Prop List");
|
||||
menu.SetTitle("Choose spawn list:");
|
||||
menu.AddItem("f", "Favorites (Broken :D)");
|
||||
menu.AddItem("r", "Recently Spawned Props");
|
||||
menu.AddItem("s", "Search for Props");
|
||||
menu.AddItem("n", "Browse Props");
|
||||
menu.ExitBackButton = true;
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
|
@ -17,6 +17,7 @@ void Spawn_ShowRecents(int client) {
|
|||
ArrayList items = GetRecentsItemList();
|
||||
if(items.Length == 0) {
|
||||
CReplyToCommand(client, "\x04[Editor] \x01No recent props spawned.");
|
||||
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||
return;
|
||||
}
|
||||
ShowTempItemMenu(client, items, "Recents");
|
||||
|
@ -26,6 +27,11 @@ void Spawn_ShowSearch(int client) {
|
|||
CReplyToCommand(client, "\x04[Editor] \x01Please enter search query in chat:");
|
||||
}
|
||||
void ShowDeleteList(int client, int index = -3) {
|
||||
if(g_spawnedItems.Length == 0) {
|
||||
SendEditorMessage(client, "No spawned items to delete");
|
||||
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||
return;
|
||||
}
|
||||
Menu menu = new Menu(DeleteHandler);
|
||||
menu.SetTitle("Delete Props");
|
||||
|
||||
|
@ -38,7 +44,7 @@ void ShowDeleteList(int client, int index = -3) {
|
|||
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||
int ref = GetSpawnedItem(i);
|
||||
if(ref == -1) continue;
|
||||
IntToString(i, info, sizeof(info));
|
||||
Format(info, sizeof(info), "0|%d", ref);
|
||||
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
|
||||
index = FindCharInString(buffer, '/', true);
|
||||
if(index != -1)
|
||||
|
@ -52,6 +58,11 @@ void ShowDeleteList(int client, int index = -3) {
|
|||
menu.DisplayAt(client, 0, MENU_TIME_FOREVER);
|
||||
}
|
||||
void ShowEditList(int client, int index = 0) {
|
||||
if(g_spawnedItems.Length == 0) {
|
||||
SendEditorMessage(client, "No spawned items to edit");
|
||||
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||
return;
|
||||
}
|
||||
Menu menu = new Menu(EditHandler);
|
||||
menu.SetTitle("Edit Prop");
|
||||
|
||||
|
@ -60,7 +71,7 @@ void ShowEditList(int client, int index = 0) {
|
|||
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||
int ref = GetSpawnedItem(i);
|
||||
if(ref == -1) continue;
|
||||
IntToString(i, info, sizeof(info));
|
||||
Format(info, sizeof(info), "%d", ref);
|
||||
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
|
||||
index = FindCharInString(buffer, '/', true);
|
||||
if(index != -1)
|
||||
|
@ -104,6 +115,8 @@ void _showItemMenu(int client, ArrayList items, const char[] title = "", bool cl
|
|||
items = g_PropData[client].itemBuffer;
|
||||
if(items == null) {
|
||||
LogError("Previous list does not exist and no new list was provided ShowItemMenu(%N)", client);
|
||||
PrintToChat(client, "\x04[Editor]\x01 An error occurred (no list)");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Populate the buffer with this list
|
||||
|
@ -194,6 +207,81 @@ void Spawn_ShowSaveLoadMainMenu(int client) {
|
|||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
}
|
||||
|
||||
void Spawn_ShowManagerMainMenu(int client, int index = 0) {
|
||||
if(g_spawnedItems.Length == 0) {
|
||||
SendEditorMessage(client, "No spawned items to manage");
|
||||
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||
return;
|
||||
}
|
||||
Menu menu = new Menu(ManagerHandler);
|
||||
menu.SetTitle("Manager");
|
||||
// Id is SaveType
|
||||
char info[8];
|
||||
char buffer[128];
|
||||
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||
int ref = GetSpawnedItem(i);
|
||||
if(ref == -1) continue;
|
||||
IntToString(i, info, sizeof(info));
|
||||
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
|
||||
index = FindCharInString(buffer, '/', true);
|
||||
if(index != -1)
|
||||
menu.AddItem(info, buffer[index + 1]);
|
||||
}
|
||||
|
||||
menu.ExitBackButton = true;
|
||||
menu.ExitButton = true;
|
||||
menu.DisplayAt(client, index, MENU_TIME_FOREVER);
|
||||
}
|
||||
void ShowManagerEntityMenu(int client, int entity) {
|
||||
if(!IsValidEntity(entity)) {
|
||||
SendEditorMessage(client, "Item has vanished");
|
||||
Spawn_ShowManagerMainMenu(client);
|
||||
return;
|
||||
}
|
||||
Menu menu = new Menu(ManagerEntityHandler);
|
||||
menu.SetTitle("Manage %d", entity);
|
||||
menu.AddItem("edit", "Edit");
|
||||
menu.AddItem("delete", "Delete");
|
||||
menu.AddItem("select", "Select");
|
||||
menu.AddItem("view", "View");
|
||||
menu.ExitBackButton = true;
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
}
|
||||
void ShowManagerSelectorMenu(int client) {
|
||||
EntitySelector sel = EntitySelector.FromClient(client);
|
||||
if(!sel.Active) {
|
||||
sel.Start(GLOW_MANAGER);
|
||||
sel.SetOnEnd(OnManagerSelectorEnd);
|
||||
sel.SetOnPostSelect(OnManagerSelectorSelect);
|
||||
sel.SetOnUnselect(OnManagerSelectorSelect);
|
||||
}
|
||||
Menu menu = new Menu(ManagerSelectorMainMenuHandler);
|
||||
menu.SetTitle("Selector");
|
||||
menu.AddItem("list", "> List Entities");
|
||||
menu.AddItem("actions", "> Actions");
|
||||
menu.AddItem("add-self", "Add All Self-Spawned");
|
||||
menu.AddItem("add-all", "Add All Spawned");
|
||||
menu.ExitBackButton = false;
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
}
|
||||
void ShowManagerSelectorActionsMenu(int client) {
|
||||
Menu menu = new Menu(ManagerSelectorActionHandler);
|
||||
menu.SetTitle("Selector: Select action");
|
||||
char display[32];
|
||||
Format(display, sizeof(display), "Entities: %d", g_PropData[client].Selector.list.Length);
|
||||
menu.AddItem("", display, ITEMDRAW_DISABLED);
|
||||
|
||||
// menu.AddItem("edit", "Edit");
|
||||
menu.AddItem("delete", "Delete");
|
||||
// menu.AddItem("select", "Select");
|
||||
menu.AddItem("save", "Save");
|
||||
menu.ExitBackButton = true;
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
}
|
||||
|
||||
void ShowSaves(int client, SaveType type) {
|
||||
ArrayList saves;
|
||||
Menu newMenu;
|
||||
|
|
|
@ -378,9 +378,7 @@ ArrayList SearchItems(const char[] query) {
|
|||
SearchData data;
|
||||
for(int i = 0; i < results.Length; i++) {
|
||||
results.GetArray(i, data);
|
||||
PrintToConsoleAll("%d | data=\"%s\"", i, data.model);
|
||||
item.FromSearchData(data);
|
||||
PrintToConsoleAll("%d | item=\"%s\"", i, item.model);
|
||||
items.PushArray(item);
|
||||
}
|
||||
delete results;
|
||||
|
@ -446,26 +444,28 @@ bool RemoveSpawnedProp(int ref) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void EndDeleteTool(int client, bool deleteEntities = false) {
|
||||
if(g_PropData[client].markedProps != null) {
|
||||
int count;
|
||||
for(int i = 0; i < g_PropData[client].markedProps.Length; i++) {
|
||||
int ref = g_PropData[client].markedProps.Get(i);
|
||||
if(IsValidEntity(ref)) {
|
||||
count++;
|
||||
if(deleteEntities) {
|
||||
RemoveSpawnedProp(ref);
|
||||
RemoveEntity(ref);
|
||||
}
|
||||
else L4D2_RemoveEntityGlow(EntRefToEntIndex(ref));
|
||||
}
|
||||
void OnDeleteToolEnd(int client, ArrayList entities) {
|
||||
int count;
|
||||
for(int i = 0; i < entities.Length; i++) {
|
||||
int ref = entities.Get(i);
|
||||
if(IsValidEntity(ref)) {
|
||||
count++;
|
||||
RemoveSpawnedProp(ref);
|
||||
RemoveEntity(ref);
|
||||
}
|
||||
delete g_PropData[client].markedProps;
|
||||
if(deleteEntities)
|
||||
PrintToChat(client, "\x04[Editor]\x01 \x05%d\x01 entities deleted", count);
|
||||
else
|
||||
PrintToChat(client, "\x04[Editor]\x01 Delete tool cancelled");
|
||||
}
|
||||
delete entities;
|
||||
PrintToChat(client, "\x04[Editor]\x01 \x05%d\x01 entities deleted", count);
|
||||
}
|
||||
|
||||
void OnManagerSelectorEnd(int client, ArrayList entities) {
|
||||
// TODO: implement manager selector cb
|
||||
ReplyToCommand(client, "Not Implemented");
|
||||
delete entities;
|
||||
}
|
||||
void OnManagerSelectorSelect(int client, int entity) {
|
||||
// update entity count
|
||||
ShowManagerSelectorMenu(client);
|
||||
}
|
||||
|
||||
int DeleteAll(int onlyPlayer = 0) {
|
||||
|
|
155
scripting/include/hats_editor.inc
Normal file
155
scripting/include/hats_editor.inc
Normal file
|
@ -0,0 +1,155 @@
|
|||
#if defined _editor_included_
|
||||
#endinput
|
||||
#endif
|
||||
#define _editor_included_
|
||||
|
||||
public SharedPlugin __pl_editor_ = {
|
||||
name = "editor",
|
||||
file = "hats.smx",
|
||||
#if defined REQUIRE_PLUGIN
|
||||
required = 1,
|
||||
#else
|
||||
required = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
#if !defined REQUIRE_PLUGIN
|
||||
public void __pl_editor__SetNTVOptional()
|
||||
{
|
||||
MarkNativeAsOptional("SpawnSchematic");
|
||||
MarkNativeAsOptional("StartEdit");
|
||||
MarkNativeAsOptional("StartSpawner");
|
||||
MarkNativeAsOptional("CancelEdit");
|
||||
MarkNativeAsOptional("IsEditorActive");
|
||||
|
||||
MarkNativeAsOptional("StartSelector");
|
||||
MarkNativeAsOptional("CancelSelector");
|
||||
MarkNativeAsOptional("IsSelectorActive");
|
||||
|
||||
MarkNativeAsOptional("Selector.Count.get");
|
||||
MarkNativeAsOptional("Selector.Active.get");
|
||||
MarkNativeAsOptional("Selector.Start");
|
||||
MarkNativeAsOptional("Selector.SetOnEnd");
|
||||
MarkNativeAsOptional("Selector.SetOnPreSelect");
|
||||
MarkNativeAsOptional("Selector.SetOnPostSelect");
|
||||
MarkNativeAsOptional("Selector.SetOnUnselect");
|
||||
MarkNativeAsOptional("Selector.AddEntity");
|
||||
MarkNativeAsOptional("Selector.RemoveEntity");
|
||||
MarkNativeAsOptional("Selector.Cancel");
|
||||
MarkNativeAsOptional("Selector.End");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
native bool SpawnSchematic(const char name[32], const float pos[3], const float angles[3] = NULL_VECTOR);
|
||||
|
||||
/** Called when edit is done or cancelled
|
||||
* @param client - client doing the edit
|
||||
* @param entity - The entity edited
|
||||
* @param result - Result of the edit, or cancelled
|
||||
* @return boolean - only for StartSpawner, true to continue, false to end spawning
|
||||
*/
|
||||
typeset EditorDoneCallback {
|
||||
function void (int client, int entity, CompleteType result);
|
||||
function bool (int client, int entity, CompleteType result);
|
||||
}
|
||||
|
||||
/** Called when an item is to be selected.
|
||||
* @return boolean - TRUE to allow item to be selected, FALSE to reject
|
||||
*/
|
||||
typedef SelectPreAddCallback = function bool (int client, int entity);
|
||||
/** Called when an item has been selected */
|
||||
typedef SelectPostAddCallback = function void (int client, int entity);
|
||||
|
||||
/** Called when an item is to be unselected. */
|
||||
typedef SelectRemoveCallback = function void (int client, int entity);
|
||||
/** Called when a user is done selecting items
|
||||
* @param client - client doing the selection
|
||||
* @param entities - if null, selection was cancelled. if not null, contains list of entity references, must be deleted.
|
||||
*/
|
||||
typedef SelectDoneCallback = function void (int client, ArrayList entities);
|
||||
|
||||
/** Starts editing an entity
|
||||
* @param client - The client that is editing
|
||||
* @param entity - The entity to edit
|
||||
* @param doneCallback - Called when edit is done
|
||||
*/
|
||||
native void StartEdit(int client, int entity, EditorDoneCallback doneCallback);
|
||||
/** Let client pick prop(s) to spawn
|
||||
* @param client - The client that is editing
|
||||
* @param entity - The entity to edit
|
||||
* @param doneCallback - Called when edit is done
|
||||
*/
|
||||
native void StartSpawner(int client, EditorDoneCallback doneCallback);
|
||||
native void CancelEdit(int client);
|
||||
// Includes non-plugin started edits
|
||||
native bool IsEditorActive(int client);
|
||||
|
||||
/** Starts a selection, where the client can click on entities to select or deselect them.
|
||||
* @param client - the client that can select
|
||||
* @param callback - called when user is done seleting or cancelled
|
||||
* @param highlightColor - the color to highlight selected items, default solid green
|
||||
* @param maxEntities - the max number of selections, 0 for infinite
|
||||
*/
|
||||
native void StartSelector(int client, SelectDoneCallback callback, int highlightColor[3] = { 0, 255, 0 }, int maxEntities = 0);
|
||||
|
||||
methodmap EntitySelector {
|
||||
public EntitySelector(int client) {
|
||||
return view_as<EntitySelector>(client);
|
||||
}
|
||||
|
||||
public static EntitySelector FromClient(int client) {
|
||||
return view_as<EntitySelector>(client);
|
||||
}
|
||||
|
||||
/** Starts a new selector for client
|
||||
* @param highlightColor - the color to highlight selected items, default solid green
|
||||
* @param flags - not used.
|
||||
* @param maxEntities - the max number of selections, 0 for infinite
|
||||
*/
|
||||
public native EntitySelector Start(int highlightColor[3], int flags = 0, int maxEntities = 0);
|
||||
|
||||
|
||||
property int Count {
|
||||
/** Returns the number of entities in selector. Returns -1 if not active */
|
||||
public native get();
|
||||
}
|
||||
|
||||
property bool Active {
|
||||
public native get();
|
||||
}
|
||||
|
||||
/** Sets the callback for when the selector is ended (or cancelled) */
|
||||
public native void SetOnEnd(SelectDoneCallback callback);
|
||||
|
||||
/** Sets the callback for when an item is to be added to the selector. */
|
||||
public native void SetOnPreSelect(SelectPreAddCallback callback);
|
||||
|
||||
/** Sets the callback for when an item has been added to the selector. */
|
||||
public native void SetOnPostSelect(SelectPostAddCallback callback);
|
||||
|
||||
/** Sets the callback for when an item is removed from selector. */
|
||||
public native void SetOnUnselect(SelectRemoveCallback callback);
|
||||
|
||||
/** Adds an entity to selection. Does not call SelectAddCallback */
|
||||
public native void AddEntity(int entity);
|
||||
|
||||
/** Removes an entity from selection. Does not call SelectAddCallback */
|
||||
public native void RemoveEntity(int entity);
|
||||
|
||||
public native void Cancel();
|
||||
|
||||
public native void End();
|
||||
}
|
||||
|
||||
|
||||
native void CancelSelector(int client);
|
||||
native bool IsSelectorActive(int client);
|
||||
|
||||
enum CompleteType {
|
||||
Complete_WallSuccess,
|
||||
Complete_WallError,
|
||||
Complete_PropSpawned,
|
||||
Complete_PropError,
|
||||
Complete_EditSuccess
|
||||
}
|
|
@ -2,21 +2,50 @@
|
|||
#endinput
|
||||
#endif
|
||||
#define _overlay_included
|
||||
#include <ripext>
|
||||
|
||||
native bool SendTempUI(int client, const char[] id, int lifetime, JSONObject element);
|
||||
public SharedPlugin __pl_overlay = {
|
||||
name = "overlay",
|
||||
file = "overlay.smx",
|
||||
#if defined REQUIRE_PLUGIN
|
||||
required = 1,
|
||||
#else
|
||||
required = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
native bool ShowUI(int client, const char[] elemNamespace, const char[] elemId, JSONObject variables);
|
||||
#define ACTION_ARG_LENGTH 128 // The length each arg (separated by space) can be
|
||||
|
||||
native bool HideUI(int client, const char[] elemNamespace, const char[] elemId);
|
||||
// typedef ActionFallbackHandlerCallback = function void (const char[] actionName, const char[][] args, int numArgs);
|
||||
// typedef ActionHandlerCallback = function void (const char[][] args, int numArgs);
|
||||
typedef ActionFallbackHandlerCallback = function void (const char[] actionName, UIActionEvent event, int client);
|
||||
typedef ActionHandlerCallback = function void (UIActionEvent event, int client);
|
||||
|
||||
native bool PlayAudio(int client, const char[] url);
|
||||
|
||||
native bool IsOverlayConnected();
|
||||
|
||||
forward void OnUIAction(const char[] elemNamespace, const char[] elemId, const char[] action);
|
||||
// myplugin:action_name
|
||||
// Handles any action for actionNamespace and actionName
|
||||
native void RegisterActionHandler(const char[] actionNamespace, const char[] actionName, ActionFallbackHandlerCallback cb);
|
||||
// Handles all actions for namespace that were not caught by RegisterActionHandler
|
||||
native void RegisterActionAnyHandler(const char[] actionNamespace, ActionHandlerCallback cb);
|
||||
|
||||
typedef UIActionCallback = function void (const char[][] args, int numArgs);
|
||||
methodmap UIActionEvent {
|
||||
public UIActionEvent(ArrayList list) {
|
||||
return view_as<UIActionEvent>(list);
|
||||
}
|
||||
|
||||
public void GetArg(int argNum, char[] output, int maxlen) {
|
||||
view_as<ArrayList>(this).GetString(argNum, output, maxlen);
|
||||
}
|
||||
|
||||
public void _Delete() {
|
||||
delete view_as<ArrayList>(this);
|
||||
}
|
||||
|
||||
property int Args {
|
||||
public get() { return view_as<ArrayList>(this).Length; }
|
||||
}
|
||||
}
|
||||
|
||||
methodmap UIElement < JSONObject {
|
||||
public UIElement(const char[] elemNamespace, const char[] elemId) {
|
||||
|
@ -24,6 +53,7 @@ methodmap UIElement < JSONObject {
|
|||
obj.SetString("namespace", elemNamespace);
|
||||
obj.SetString("elem_id", elemId);
|
||||
obj.SetBool("visibility", false);
|
||||
obj.Set("steamids", new JSONArray());
|
||||
obj.Set("variables", new JSONObject());
|
||||
|
||||
return view_as<UIElement>(obj);
|
||||
|
@ -35,16 +65,6 @@ methodmap UIElement < JSONObject {
|
|||
}
|
||||
public set(bool value) {
|
||||
view_as<JSONObject>(this).SetBool("visibility", value);
|
||||
this.Send();
|
||||
}
|
||||
}
|
||||
|
||||
/** Is the UI element globally sent to all connected players?
|
||||
* Specify players with .AddClient() or clear with .ClearClients()
|
||||
*/
|
||||
property bool Global {
|
||||
public get() {
|
||||
return !view_as<JSONObject>(this).HasKey("steamids")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,35 +88,13 @@ methodmap UIElement < JSONObject {
|
|||
view_as<JSONObject>(this).SetBool(id, value);
|
||||
}
|
||||
|
||||
public void SetActionCallback(UIActionCallback callback) {}
|
||||
|
||||
public void AddClient(const char[] steamid) {
|
||||
// if(!IsClientInGame(client) || steamidCache[client][0] == '\0') ThrowError("Client %d is not connected, ingame, or authorized");
|
||||
JSONObject obj = view_as<JSONObject>(this);
|
||||
JSONArray steamids = view_as<JSONArray>(obj.Get("steamids"));
|
||||
if(steamids == null) {
|
||||
steamids = new JSONArray();
|
||||
obj.Set("steamids", steamids)
|
||||
public native bool SendAll();
|
||||
public native bool SendTo(int client);
|
||||
public bool SendToMultiple(int[] clientIds, int numClients) {
|
||||
for(int i = 0; i < numClients; i++) {
|
||||
this.SendTo(clientIds[i]);
|
||||
}
|
||||
steamids.PushString(steamid);
|
||||
}
|
||||
|
||||
public void ClearClients() {
|
||||
view_as<JSONObject>(this).Remove("steamids");
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
view_as<JSONObject>(this).Clear();
|
||||
}
|
||||
|
||||
public void Hide() {
|
||||
this.Visibility = false;
|
||||
}
|
||||
public void Show() {
|
||||
this.Visibility = true;
|
||||
}
|
||||
|
||||
public native bool Send();
|
||||
}
|
||||
|
||||
methodmap UIPosition < JSONObject {
|
||||
|
@ -118,6 +116,25 @@ methodmap UIPosition < JSONObject {
|
|||
}
|
||||
}
|
||||
|
||||
methodmap UISize < JSONObject {
|
||||
public UISize(int width, int height) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.SetInt("width", width);
|
||||
obj.SetInt("height", height);
|
||||
return view_as<UISize>(obj);
|
||||
}
|
||||
|
||||
property int Width {
|
||||
public get() { return view_as<JSONObject>(this).GetInt("width"); }
|
||||
public set(int value) { view_as<JSONObject>(this).SetInt("height", value); }
|
||||
}
|
||||
|
||||
property int Height {
|
||||
public get() { return view_as<JSONObject>(this).GetInt("height"); }
|
||||
public set(int value) { view_as<JSONObject>(this).SetInt("height", value); }
|
||||
}
|
||||
}
|
||||
|
||||
methodmap UIColor < JSONObject {
|
||||
/// Creates a new UIColor with RGB between 0-255, alpha is normalized 0.0-1.0
|
||||
public UIColor(int r = 255, int g = 255, int b = 255) {
|
||||
|
@ -168,11 +185,15 @@ methodmap TempUIElementDefaults < JSONObject {
|
|||
}
|
||||
property UIPosition Position {
|
||||
public get() { return view_as<UIPosition>(view_as<JSONObject>(this).Get("position")); }
|
||||
public set(UIPosition pos) { view_as<JSONObject>(this).Set("position", view_as<JSON>(pos)); }
|
||||
// public set(UIPosition pos) { view_as<JSONObject>(this).Set("position", view_as<JSON>(pos)); }
|
||||
}
|
||||
property UIColor BackgroundColor {
|
||||
public get() { return view_as<UIColor>(view_as<JSONObject>(this).Get("bgColor")); }
|
||||
public set(UIColor color) { view_as<JSONObject>(this).Set("bgColor", view_as<JSON>(color)); }
|
||||
// public set(UIColor color) { view_as<JSONObject>(this).Set("bgColor", view_as<JSON>(color)); }
|
||||
}
|
||||
property UISize Size {
|
||||
public get() { return view_as<UISize>(view_as<JSONObject>(this).Get("size")); }
|
||||
// public set(UISize size) { view_as<JSONObject>(this).Set("size", view_as<JSON>(size)); }
|
||||
}
|
||||
/// Returns or sets opacity, -1 is not set
|
||||
property int Opacity {
|
||||
|
@ -203,6 +224,7 @@ enum UIType {
|
|||
Element_Unknown = -1,
|
||||
Element_Text,
|
||||
Element_List,
|
||||
Element_Audio
|
||||
}
|
||||
enum UIFlags {
|
||||
Element_None
|
||||
|
@ -287,27 +309,29 @@ methodmap TempUI {
|
|||
public TempUI(const char[] elemId, const char[] type, int lifetime = 0) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.SetString("elem_id", elemId);
|
||||
obj.Set("steamids", new JSONArray());
|
||||
obj.SetInt("expires_seconds", 0);
|
||||
TempUIElement element = new TempUIElement(type);
|
||||
obj.Set("element", element);
|
||||
return view_as<TempUI>(obj);
|
||||
}
|
||||
|
||||
/// How long the temp UI lasts, 0 for never.
|
||||
property int Duration {
|
||||
public get() {
|
||||
return view_as<JSONObject>(this).GetInt("expires_seconds");
|
||||
}
|
||||
public set(int value) {
|
||||
view_as<JSONObject>(this).SetInt("expires_seconds", value);
|
||||
}
|
||||
}
|
||||
|
||||
property bool Visible {
|
||||
public get() {
|
||||
return view_as<JSONObject>(this).GetBool("visibility");
|
||||
}
|
||||
public set(bool value) {
|
||||
view_as<JSONObject>(this).SetBool("visibility", value);
|
||||
this.Send();
|
||||
}
|
||||
}
|
||||
|
||||
/** Is the UI element globally sent to all connected players?
|
||||
* Specify players with .AddClient() or clear with .ClearClients()
|
||||
*/
|
||||
property bool Global {
|
||||
public get() {
|
||||
return !view_as<JSONObject>(this).HasKey("steamids")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,38 +340,127 @@ methodmap TempUI {
|
|||
return view_as<TempUIElement>(view_as<JSONObject>(this).Get("element"));
|
||||
}
|
||||
public set(TempUIElement newElement) {
|
||||
// Delete old element
|
||||
JSON elem = view_as<JSONObject>(this).Get("element");
|
||||
if(elem != null) delete elem;
|
||||
|
||||
view_as<JSONObject>(this).Set("element", view_as<JSON>(newElement));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetActionCallback(UIActionCallback callback) {}
|
||||
|
||||
public void AddClient(const char[] steamid) {
|
||||
// if(!IsClientInGame(client) || steamidCache[client][0] == '\0') ThrowError("Client %d is not connected, ingame, or authorized");
|
||||
JSONObject obj = view_as<JSONObject>(this);
|
||||
JSONArray steamids = view_as<JSONArray>(obj.Get("steamids"));
|
||||
if(steamids == null) {
|
||||
steamids = new JSONArray();
|
||||
obj.Set("steamids", steamids)
|
||||
}
|
||||
steamids.PushString(steamid);
|
||||
}
|
||||
|
||||
public void ClearClients() {
|
||||
view_as<JSONObject>(this).Remove("steamids");
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
view_as<JSONObject>(this).Clear();
|
||||
}
|
||||
|
||||
public void Hide() {
|
||||
this.Visibility = false;
|
||||
public void Hide() {
|
||||
this.Visible = false;
|
||||
}
|
||||
public void Show() {
|
||||
this.Visibility = true;
|
||||
this.Visible = true;
|
||||
}
|
||||
|
||||
public native bool Send();
|
||||
public native bool SendAll();
|
||||
public native bool SendTo(int client);
|
||||
public bool SendToMultiple(int[] clientIds, int numClients) {
|
||||
for(int i = 0; i < numClients; i++) {
|
||||
this.SendTo(clientIds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
enum AudioState {
|
||||
// Audio stopped, reset to startTime
|
||||
Audio_Stopped,
|
||||
// Pauses audio at current time
|
||||
Audio_Paused,
|
||||
Audio_Play
|
||||
}
|
||||
|
||||
methodmap ClientList < JSONArray {
|
||||
public ClientList() {
|
||||
return view_as<ClientList>(new JSONArray());
|
||||
}
|
||||
|
||||
property int Length {
|
||||
public get() { return view_as<JSONArray>(this).Length; }
|
||||
}
|
||||
|
||||
public native void AddClient(int client);
|
||||
|
||||
public native bool HasClient(int client);
|
||||
|
||||
public void Clear() {
|
||||
view_as<JSONArray>(this).Clear();
|
||||
}
|
||||
}
|
||||
|
||||
methodmap AudioResource < JSONObject {
|
||||
public AudioResource(const char[] url, float volume = 0.5) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.SetString("source", url);
|
||||
obj.SetFloat("volume", volume);
|
||||
obj.SetInt("state", 0);
|
||||
obj.Set("steamids", new JSONArray());
|
||||
obj.SetBool("repeat", false)
|
||||
return view_as<AudioResource>(obj);
|
||||
}
|
||||
|
||||
property AudioState State {
|
||||
public get() {
|
||||
return view_as<AudioState>(view_as<JSONObject>(this).GetInt("state"));
|
||||
}
|
||||
public set(AudioState state) {
|
||||
view_as<JSONObject>(this).SetInt("state", view_as<int>(state));
|
||||
}
|
||||
}
|
||||
|
||||
property float Volume {
|
||||
public get() {
|
||||
return view_as<JSONObject>(this).GetFloat("volume");
|
||||
}
|
||||
public set(float volume) {
|
||||
view_as<JSONObject>(this).SetFloat("volume", volume);
|
||||
}
|
||||
}
|
||||
|
||||
property bool Repeat {
|
||||
public get() {
|
||||
return view_as<JSONObject>(this).GetBool("repeat");
|
||||
}
|
||||
public set(bool repeat) {
|
||||
view_as<JSONObject>(this).SetBool("repeat", repeat);
|
||||
}
|
||||
}
|
||||
|
||||
property ClientList Clients {
|
||||
public get() {
|
||||
return view_as<ClientList>(view_as<JSONObject>(this).Get("steamids"));
|
||||
}
|
||||
}
|
||||
/// Plays or resumes playing
|
||||
public native void Play();
|
||||
/// Stops playing audio, clients will reset to beginning
|
||||
public native void Stop();
|
||||
/// Pauses audio, resuming to current play duration
|
||||
public native void Pause();
|
||||
|
||||
public void Clear() {
|
||||
view_as<JSONObject>(this).Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined REQUIRE_PLUGIN
|
||||
public void __pl_overlay_SetNTVOptional() {
|
||||
MarkNativeAsOptional("IsOverlayConnected");
|
||||
MarkNativeAsOptional("RegisterActionAnyHandler");
|
||||
MarkNativeAsOptional("RegisterActionHandler");
|
||||
|
||||
MarkNativeAsOptional("UIElement.SendAll");
|
||||
MarkNativeAsOptional("UIElement.SendTo");
|
||||
MarkNativeAsOptional("TempUI.SendAll");
|
||||
MarkNativeAsOptional("TempUI.SendTo");
|
||||
MarkNativeAsOptional("AudioResource.Play");
|
||||
MarkNativeAsOptional("AudioResource.Stop");
|
||||
MarkNativeAsOptional("AudioResource.Pause");
|
||||
}
|
||||
#endif
|
135
scripting/include/randomizer/rbuild.sp
Normal file
135
scripting/include/randomizer/rbuild.sp
Normal file
|
@ -0,0 +1,135 @@
|
|||
/// MENUS
|
||||
public void OpenMainMenu(int client) {
|
||||
Menu menu = new Menu(BuilderHandler_MainMenu);
|
||||
menu.SetTitle("Randomizer Builder");
|
||||
if(g_builder.mapData == null) {
|
||||
menu.AddItem("load", "Load Map Data");
|
||||
menu.AddItem("new", "New Map Data");
|
||||
} else {
|
||||
menu.AddItem("save", "Save Map Data");
|
||||
menu.AddItem("selector", "Start Selector");
|
||||
menu.AddItem("spawner", "Start Spawner");
|
||||
menu.AddItem("cursor", "Add Entity At Cursor");
|
||||
menu.AddItem("scenes", "Scenes");
|
||||
}
|
||||
menu.Display(client, 0);
|
||||
}
|
||||
|
||||
void OpenScenesMenu(int client) {
|
||||
Menu menu = new Menu(BuilderHandler_ScenesMenu);
|
||||
menu.SetTitle("Select a scene");
|
||||
char id[64], display[32];
|
||||
JSONObjectKeys iterator = g_builder.mapData.Keys();
|
||||
while(iterator.ReadKey(id, sizeof(id))) {
|
||||
if(StrEqual(id, g_builder.selectedSceneId)) {
|
||||
Format(display, sizeof(display), "%s (selected)", id);
|
||||
} else {
|
||||
Format(display, sizeof(display), "%s", id);
|
||||
}
|
||||
menu.AddItem(id, display);
|
||||
}
|
||||
menu.Display(client, 0);
|
||||
}
|
||||
|
||||
void OpenVariantsMenu(int client) {
|
||||
Menu menu = new Menu(BuilderHandler_VariantsMenu);
|
||||
menu.SetTitle("%s > Variants", g_builder.selectedSceneId);
|
||||
char id[8], display[32];
|
||||
menu.AddItem("new", "New");
|
||||
menu.AddItem("-1", "None (Shared Scene)");
|
||||
|
||||
JSONArray variants = view_as<JSONArray>(g_builder.selectedSceneData.Get("variants"));
|
||||
JSONObject varObj;
|
||||
JSONArray entities;
|
||||
for(int i = 0; i < variants.Length; i++) {
|
||||
varObj = view_as<JSONObject>(variants.Get(i));
|
||||
entities = view_as<JSONArray>(varObj.Get("entities"));
|
||||
if(i == g_builder.selectedVariantIndex) {
|
||||
Format(display, sizeof(display), "%d entities (selected)", entities.Length);
|
||||
} else {
|
||||
Format(display, sizeof(display), "%d entities", entities.Length);
|
||||
}
|
||||
IntToString(i, id, sizeof(id));
|
||||
menu.AddItem(id, display);
|
||||
}
|
||||
menu.Display(client, 0);
|
||||
}
|
||||
|
||||
/// HANDLERS
|
||||
|
||||
int BuilderHandler_MainMenu(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
char info[32];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
if(StrEqual(info, "new")) {
|
||||
JSONObject temp = LoadMapJson(currentMap);
|
||||
GetCmdArg(2, info, sizeof(info));
|
||||
if(temp != null) {
|
||||
Menu nMenu = new Menu(BuilderHandler_MainMenu);
|
||||
nMenu.SetTitle("Existing map data exists");
|
||||
nMenu.AddItem("new_confirm", "Overwrite");
|
||||
nMenu.Display(client, 0);
|
||||
delete temp;
|
||||
return 0;
|
||||
} else {
|
||||
FakeClientCommand(client, "sm_rbuild new");
|
||||
}
|
||||
} else if(StrEqual(info, "new_confirm")) {
|
||||
FakeClientCommand(client, "sm_rbuild new confirm");
|
||||
} else if(StrEqual(info, "scenes")) {
|
||||
OpenScenesMenu(client);
|
||||
return 0;
|
||||
} else {
|
||||
FakeClientCommand(client, "sm_rbuild %s", info);
|
||||
} /*else if(StrEqual(info, "cursor")) {
|
||||
Menu menu = new Menu(BuilderHandler_)
|
||||
}*/
|
||||
OpenMainMenu(client);
|
||||
} else if(action == MenuAction_Cancel) {
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
}
|
||||
} else if (action == MenuAction_End) {
|
||||
delete menu;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int BuilderHandler_ScenesMenu(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
char info[64];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
if(StrEqual(info, "new")) {
|
||||
FakeClientCommand(client, "sm_rbuild scenes new");
|
||||
OpenScenesMenu(client);
|
||||
} else {
|
||||
FakeClientCommand(client, "sm_rbuild scenes select %s", info);
|
||||
OpenVariantsMenu(client);
|
||||
}
|
||||
} else if(action == MenuAction_Cancel) {
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
}
|
||||
} else if (action == MenuAction_End) {
|
||||
delete menu;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int BuilderHandler_VariantsMenu(Menu menu, MenuAction action, int client, int param2) {
|
||||
if (action == MenuAction_Select) {
|
||||
char info[64];
|
||||
menu.GetItem(param2, info, sizeof(info));
|
||||
if(StrEqual(info, "new")) {
|
||||
FakeClientCommand(client, "sm_rbuild scenes variants new");
|
||||
} else {
|
||||
FakeClientCommand(client, "sm_rbuild scenes variants select %s", info);
|
||||
}
|
||||
OpenVariantsMenu(client);
|
||||
} else if(action == MenuAction_Cancel) {
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
}
|
||||
} else if (action == MenuAction_End) {
|
||||
delete menu;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -249,8 +249,8 @@ void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast)
|
|||
CPrintChatToAdmins("{olive}%N{default} has been banned for %d minutes (marked as troll). If this was a mistake, you can discard their ban from the admin panel at {yellow}https://admin.jackz.me", client, hBanTime.IntValue);
|
||||
else
|
||||
CPrintChatToAdmins("{olive}%N{default} has been permanently banned (marked as troll). If this was a mistake, you can discard their ban from the admin panel at {yellow}https://admin.jackz.me", client);
|
||||
pData[client].isTroll = false;
|
||||
}
|
||||
pData[client].isTroll = false;
|
||||
|
||||
if(!IsFakeClient(client)) {
|
||||
float minutesSinceiLastFFTime = GetLastFFMinutes(client);
|
||||
|
|
|
@ -90,7 +90,7 @@ ConVar cvEPICommonCountScale, cvEPICommonCountScaleMax;
|
|||
ConVar g_ffFactorCvar, hExtraTankThreshold;
|
||||
|
||||
|
||||
ConVar cvZCommonLimit; int zCommonLimitPrevValue;
|
||||
ConVar cvZCommonLimit; int commonLimitBase; bool isSettingLimit;
|
||||
|
||||
int g_extraKitsAmount, g_extraKitsStart, g_saferoomDoorEnt, g_prevPlayerCount;
|
||||
bool g_forcedSurvivorCount, g_extraKitsSpawnedFinale;
|
||||
|
@ -306,25 +306,29 @@ public void OnPluginStart() {
|
|||
|
||||
hExtraItemBasePercentage = CreateConVar("epi_item_chance", "0.034", "The base chance (multiplied by player count) of an extra item being spawned.", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
hExtraSpawnBasePercentage = CreateConVar("epi_spawn_chance", "0.01", "The base chance (multiplied by player count) of an extra item spawner being created.", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
hAddExtraKits = CreateConVar("epi_kitmode", "0", "Decides how extra kits should be added.\n0 -> Overwrites previous extra kits\n1 -> Adds onto previous extra kits", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
hUpdateMinPlayers = CreateConVar("epi_updateminplayers", "1", "Should the plugin update abm\'s cvar min_players convar to the player count?\n 0 -> NO\n1 -> YES", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
hMinPlayersSaferoomDoor = CreateConVar("epi_doorunlock_percent", "0.75", "The percent of players that need to be loaded in before saferoom door is opened.\n 0 to disable", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
hSaferoomDoorWaitSeconds = CreateConVar("epi_doorunlock_wait", "25", "How many seconds after to unlock saferoom door. 0 to disable", FCVAR_NONE, true, 0.0);
|
||||
hSaferoomDoorAutoOpen = CreateConVar("epi_doorunlock_open", "0", "Controls when the door automatically opens after unlocked. Add bits together.\n0 = Never, 1 = When timer expires, 2 = When all players loaded in", FCVAR_NONE, true, 0.0);
|
||||
hEPIHudState = CreateConVar("epi_hudstate", "1", "Controls when the hud displays.\n0 -> OFF, 1 = When 5+ players, 2 = ALWAYS", FCVAR_NONE, true, 0.0, true, 3.0);
|
||||
hExtraFinaleTank = CreateConVar("epi_extra_tanks", "3", "Add bits together. 0 = Normal tank spawning, 1 = 50% tank split on non-finale (half health), 2 = Tank split (full health) on finale ", FCVAR_NONE, true, 0.0, true, 3.0);
|
||||
hExtraTankThreshold = CreateConVar("epi_extra_tanks_min_players", "6", "The minimum number of players for extra tanks to spawn. When disabled, normal 5+ tank health applies", FCVAR_NONE, true, 0.0);
|
||||
hSplitTankChance = CreateConVar("epi_splittank_chance", "0.65", "The % chance of a split tank occurring in non-finales", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
cvDropDisconnectTime = CreateConVar("epi_disconnect_time", "120.0", "The amount of seconds after a player has actually disconnected, where their character slot will be void. 0 to disable", FCVAR_NONE, true, 0.0);
|
||||
cvFFDecreaseRate = CreateConVar("epi_ff_decrease_rate", "0.3", "The friendly fire factor is subtracted from the formula (playerCount-4) * this rate. Effectively reduces ff penalty when more players. 0.0 to subtract none", FCVAR_NONE, true, 0.0);
|
||||
cvEPIHudFlags = CreateConVar("epi_hud_flags", "3", "Add together.\n1 = Scrolling hud, 2 = Show ping", FCVAR_NONE, true, 0.0);
|
||||
cvEPISpecialSpawning = CreateConVar("epi_sp_spawning", "2", "Determines what specials are spawned. Add bits together.\n1 = Normal specials\n2 = Witches\n4 = Tanks", FCVAR_NONE, true, 0.0);
|
||||
cvEPITankHealth = CreateConVar("epi_tank_chunkhp", "2500", "The amount of health added to tank, for each extra player", FCVAR_NONE, true, 0.0);
|
||||
cvEPIGamemodes = CreateConVar("epi_gamemodes", "coop,realism,versus", "Gamemodes where plugin is active. Comma-separated", FCVAR_NONE);
|
||||
cvEPIEnabledMode = CreateConVar("epi_enabled", "1", "Is EPI enabled?\n0=OFF\n1=Auto (Official Maps Only)(5+)\n2=Auto (Any map) (5+)\n3=Forced on", FCVAR_NONE, true, 0.0, true, 3.0);
|
||||
cvEPICommonCountScale = CreateConVar("epi_commons_scale_multiplier", "0", "This value is multiplied by the number of extra players playing. It's then added to z_common_limit. 5 players with value 5 would be z_common_limit + ", FCVAR_NONE, true, 0.0);
|
||||
cvEPICommonCountScaleMax = CreateConVar("epi_commons_scale_max", "60", "The maximum amount that z_common_limit can be scaled to.", FCVAR_NONE, true, 0.0);
|
||||
hAddExtraKits = CreateConVar("epi_kitmode", "0", "Decides how extra kits should be added.\n0 -> Overwrites previous extra kits\n1 -> Adds onto previous extra kits", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
hUpdateMinPlayers = CreateConVar("epi_updateminplayers", "1", "Should the plugin update abm\'s cvar min_players convar to the player count?\n 0 -> NO\n1 -> YES", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
hMinPlayersSaferoomDoor = CreateConVar("epi_doorunlock_percent", "0.75", "The percent of players that need to be loaded in before saferoom door is opened.\n 0 to disable", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
hSaferoomDoorWaitSeconds = CreateConVar("epi_doorunlock_wait", "25", "How many seconds after to unlock saferoom door. 0 to disable", FCVAR_NONE, true, 0.0);
|
||||
hSaferoomDoorAutoOpen = CreateConVar("epi_doorunlock_open", "0", "Controls when the door automatically opens after unlocked. Add bits together.\n0 = Never, 1 = When timer expires, 2 = When all players loaded in", FCVAR_NONE, true, 0.0);
|
||||
hEPIHudState = CreateConVar("epi_hudstate", "1", "Controls when the hud displays.\n0 -> OFF, 1 = When 5+ players, 2 = ALWAYS", FCVAR_NONE, true, 0.0, true, 3.0);
|
||||
hExtraFinaleTank = CreateConVar("epi_extra_tanks", "3", "Add bits together. 0 = Normal tank spawning, 1 = 50% tank split on non-finale (half health), 2 = Tank split (full health) on finale ", FCVAR_NONE, true, 0.0, true, 3.0);
|
||||
hExtraTankThreshold = CreateConVar("epi_extra_tanks_min_players", "6", "The minimum number of players for extra tanks to spawn. When disabled, normal 5+ tank health applies", FCVAR_NONE, true, 0.0);
|
||||
hSplitTankChance = CreateConVar("epi_splittank_chance", "0.65", "The % chance of a split tank occurring in non-finales", FCVAR_NONE, true, 0.0, true, 1.0);
|
||||
cvDropDisconnectTime = CreateConVar("epi_disconnect_time", "120.0", "The amount of seconds after a player has actually disconnected, where their character slot will be void. 0 to disable", FCVAR_NONE, true, 0.0);
|
||||
cvFFDecreaseRate = CreateConVar("epi_ff_decrease_rate", "0.3", "The friendly fire factor is subtracted from the formula (playerCount-4) * this rate. Effectively reduces ff penalty when more players. 0.0 to subtract none", FCVAR_NONE, true, 0.0);
|
||||
cvEPIHudFlags = CreateConVar("epi_hud_flags", "3", "Add together.\n1 = Scrolling hud, 2 = Show ping", FCVAR_NONE, true, 0.0);
|
||||
cvEPISpecialSpawning = CreateConVar("epi_sp_spawning", "2", "Determines what specials are spawned. Add bits together.\n1 = Normal specials\n2 = Witches\n4 = Tanks", FCVAR_NONE, true, 0.0);
|
||||
cvEPITankHealth = CreateConVar("epi_tank_chunkhp", "2500", "The amount of health added to tank, for each extra player", FCVAR_NONE, true, 0.0);
|
||||
cvEPIGamemodes = CreateConVar("epi_gamemodes", "coop,realism,versus", "Gamemodes where plugin is active. Comma-separated", FCVAR_NONE);
|
||||
cvEPIEnabledMode = CreateConVar("epi_enabled", "1", "Is EPI enabled?\n0=OFF\n1=Auto (Official Maps Only)(5+)\n2=Auto (Any map) (5+)\n3=Forced on", FCVAR_NONE, true, 0.0, true, 3.0);
|
||||
cvEPICommonCountScale = CreateConVar("epi_commons_scale_multiplier", "0", "This value is multiplied by the number of extra players playing. It's then added to z_common_limit. 5 players with value 5 would be z_common_limit + ", FCVAR_NONE, true, 0.0);
|
||||
cvEPICommonCountScaleMax = CreateConVar("epi_commons_scale_max", "60", "The maximum amount that z_common_limit can be scaled to.", FCVAR_NONE, true, 0.0);
|
||||
cvZCommonLimit = FindConVar("z_common_limit");
|
||||
|
||||
cvEPICommonCountScale.AddChangeHook(Cvar_CommonScaleChange);
|
||||
cvEPICommonCountScaleMax.AddChangeHook(Cvar_CommonScaleChange);
|
||||
cvZCommonLimit.AddChangeHook(Cvar_CommonScaleChange);
|
||||
|
||||
// TODO: hook flags, reset name index / ping mode
|
||||
cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange);
|
||||
|
@ -428,6 +432,7 @@ public void OnPluginEnd() {
|
|||
delete weaponMaxClipSizes;
|
||||
delete g_ammoPacks;
|
||||
L4D2_ExecVScriptCode(HUD_SCRIPT_CLEAR);
|
||||
_UnsetCommonLimit();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -482,6 +487,19 @@ void Cvar_HudStateChange(ConVar convar, const char[] oldValue, const char[] newV
|
|||
TryStartHud();
|
||||
}
|
||||
}
|
||||
void Cvar_CommonScaleChange(ConVar convar, const char[] oldValue, const char[] newValue) {
|
||||
if(convar == cvZCommonLimit) {
|
||||
PrintToServer("z_common_limit changed [value=%d] [isSettingLimit=%b]", convar.IntValue, isSettingLimit);
|
||||
// Ignore our own changes:
|
||||
if(isSettingLimit) {
|
||||
isSettingLimit = false;
|
||||
return;
|
||||
}
|
||||
commonLimitBase = convar.IntValue;
|
||||
}
|
||||
_SetCommonLimit();
|
||||
|
||||
}
|
||||
void TryStartHud() {
|
||||
int threshold = 0;
|
||||
// Default to 0 for state == 2 (force)
|
||||
|
@ -572,6 +590,7 @@ Action Command_EpiVal(int client, int args) {
|
|||
PrintToConsole(client, "restCount = %d", g_restCount);
|
||||
PrintToConsole(client, "extraFinaleTankEnabled = %b", g_extraFinaleTankEnabled);
|
||||
PrintToConsole(client, "g_areItemsPopulated = %b", g_areItemsPopulated);
|
||||
PrintToConsole(client, "commonLimitBase = %d", commonLimitBase);
|
||||
ReplyToCommand(client, "Values printed to console");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
@ -1343,7 +1362,7 @@ public void OnMapStart() {
|
|||
if(L4D_IsMissionFinalMap()) {
|
||||
// Disable tank split on hard rain finale
|
||||
g_extraFinaleTankEnabled = true;
|
||||
if(StrEqual(map, "c4m5_milltown_escape")) {
|
||||
if(StrEqual(map, "c4m5_milltown_escape") || StrEqual(map, "c14m2_lighthouse")) {
|
||||
g_extraFinaleTankEnabled = false;
|
||||
}
|
||||
}
|
||||
|
@ -1454,6 +1473,7 @@ public void OnConfigsExecuted() {
|
|||
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
|
||||
hMinPlayers.IntValue = g_realSurvivorCount;
|
||||
}
|
||||
_SetCommonLimit();
|
||||
}
|
||||
|
||||
public void OnMapEnd() {
|
||||
|
@ -2102,29 +2122,42 @@ void UpdateSurvivorCount() {
|
|||
} else if(wasActive) {
|
||||
OnEPIInactive();
|
||||
}
|
||||
|
||||
if(isActive)
|
||||
SetFFFactor(g_epiEnabled);
|
||||
if(isActive) {
|
||||
SetFFFactor(g_epiEnabled);
|
||||
_SetCommonLimit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnEPIActive() {
|
||||
zCommonLimitPrevValue = cvZCommonLimit.IntValue;
|
||||
_SetCommonLimit();
|
||||
}
|
||||
|
||||
void OnEPIInactive() {
|
||||
_UnsetCommonLimit();
|
||||
}
|
||||
|
||||
void _UnsetCommonLimit() {
|
||||
if(commonLimitBase > 0) {
|
||||
cvZCommonLimit.IntValue = commonLimitBase;
|
||||
}
|
||||
commonLimitBase = 0;
|
||||
}
|
||||
void _SetCommonLimit() {
|
||||
if(!g_epiEnabled || commonLimitBase <= 0) return;
|
||||
// TODO: lag check for common limit
|
||||
if(cvEPICommonCountScale.IntValue > 0) {
|
||||
int newLimit = zCommonLimitPrevValue + RoundFloat(cvEPICommonCountScale.FloatValue * float(g_realSurvivorCount));
|
||||
if(cvEPICommonCountScale.IntValue > 0 && commonLimitBase > 0) {
|
||||
int newLimit = commonLimitBase + RoundFloat(cvEPICommonCountScale.FloatValue * float(g_realSurvivorCount - 4));
|
||||
PrintDebug(DEBUG_INFO, "Setting common scale: %d + (%f * %d) [max=%d] = %d", commonLimitBase, cvEPICommonCountScale.FloatValue, g_realSurvivorCount - 4, cvEPICommonCountScaleMax.IntValue, newLimit);
|
||||
if(newLimit > 0) {
|
||||
if(newLimit > cvEPICommonCountScaleMax.IntValue) {
|
||||
newLimit = cvEPICommonCountScaleMax.IntValue;
|
||||
}
|
||||
isSettingLimit = true;
|
||||
cvZCommonLimit.IntValue = newLimit;
|
||||
}
|
||||
cvZCommonLimit.IntValue = newLimit;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEPIInactive() {
|
||||
cvZCommonLimit.IntValue = zCommonLimitPrevValue;
|
||||
}
|
||||
void SetFFFactor(bool enabled) {
|
||||
static float prevValue;
|
||||
// Restore the previous value (we use the value for the calculations of new value)
|
||||
|
|
|
@ -213,6 +213,7 @@ public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[
|
|||
HookEvent("player_bot_replace", Event_PlayerToBot);
|
||||
HookEvent("player_ledge_grab", Event_LedgeGrab);
|
||||
AddCommandListener(OnGoAwayFromKeyboard, "go_away_from_keyboard");
|
||||
InitGamemode();
|
||||
} else if(!lateLoaded) {
|
||||
cvarStorage.Restore();
|
||||
delete cvarStorage;
|
||||
|
@ -462,7 +463,8 @@ Action Timer_SpawnBots(Handle h, int max) {
|
|||
if(AddSurvivor()) {
|
||||
count++;
|
||||
return Plugin_Continue;
|
||||
} else {
|
||||
} else if(count < 0) {
|
||||
// Fail if we couldnt make enough bots
|
||||
PrintToChatAll("GUESS WHO: FATAL ERROR: AddSurvivor() failed");
|
||||
LogError("Guess Who: Fatal Error: AddSurvivor() failed");
|
||||
count = 0;
|
||||
|
@ -561,6 +563,7 @@ Action Timer_WaitForStart(Handle h) {
|
|||
Game.State = State_Starting;
|
||||
Game.Tick = 0;
|
||||
Game.MapTime = RoundFloat(seedTime);
|
||||
Game.PopulateCoins();
|
||||
CreateTimer(seedTime, Timer_StartSeeker);
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
@ -585,6 +588,7 @@ Action Timer_StartSeeker(Handle h) {
|
|||
Action Timer_TimesUp(Handle h) {
|
||||
Game.Broadcast("The seeker ran out of time. Hiders win!");
|
||||
Game.End(State_HidersWin);
|
||||
timesUpTimer = null;
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,8 @@ char g_currentMap[64];
|
|||
#include <hats/hats.sp>
|
||||
#include <hats/hat_presets.sp>
|
||||
#include <hats/props/base.sp>
|
||||
#include <hats/natives.sp>
|
||||
#include <hats_editor>
|
||||
|
||||
public Plugin myinfo = {
|
||||
name = "L4D2 Hats & Editor",
|
||||
|
@ -56,6 +58,32 @@ public Plugin myinfo = {
|
|||
};
|
||||
|
||||
ArrayList NavAreas;
|
||||
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
|
||||
RegPluginLibrary("editor");
|
||||
// CreateNative("SpawnSchematic", Native_SpawnSchematic);
|
||||
CreateNative("StartEdit", Native_StartEdit);
|
||||
CreateNative("StartSpawner", Native_StartSpawner);
|
||||
CreateNative("CancelEdit", Native_CancelEdit);
|
||||
CreateNative("IsEditorActive", Native_IsEditorActive);
|
||||
|
||||
|
||||
CreateNative("StartSelector", Native_StartSelector);
|
||||
CreateNative("CancelSelector", Native_CancelSelector);
|
||||
CreateNative("IsSelectorActive", Native_IsSelectorActive);
|
||||
|
||||
CreateNative("EntitySelector.Start", Native_Selector_Start);
|
||||
CreateNative("EntitySelector.Count.get", Native_Selector_GetCount);
|
||||
CreateNative("EntitySelector.Active.get", Native_Selector_GetActive);
|
||||
CreateNative("EntitySelector.SetOnEnd", Native_Selector_SetOnEnd);
|
||||
CreateNative("EntitySelector.SetOnPreSelect", Native_Selector_SetOnPreSelect);
|
||||
CreateNative("EntitySelector.SetOnPostSelect", Native_Selector_SetOnPostSelect);
|
||||
CreateNative("EntitySelector.SetOnUnselect", Native_Selector_SetOnUnselect);
|
||||
CreateNative("EntitySelector.AddEntity", Native_Selector_AddEntity);
|
||||
CreateNative("EntitySelector.RemoveEntity", Native_Selector_RemoveEntity);
|
||||
CreateNative("EntitySelector.Cancel", Native_Selector_Cancel);
|
||||
CreateNative("EntitySelector.End", Native_Selector_End);
|
||||
return APLRes_Success;
|
||||
}
|
||||
|
||||
|
||||
public void OnPluginStart() {
|
||||
|
@ -116,6 +144,7 @@ public void OnPluginStart() {
|
|||
for(int i = 1; i <= MaxClients; i++) {
|
||||
Editor[i].client = i;
|
||||
Editor[i].Reset(true);
|
||||
g_PropData[i].Init(i);
|
||||
hatData[i].yeetGroundTimer = null;
|
||||
}
|
||||
|
||||
|
@ -456,6 +485,7 @@ ArrayList GetSpawnLocations() {
|
|||
return newList;
|
||||
}
|
||||
|
||||
|
||||
void ChooseRandomPosition(float pos[3], int ignoreClient = 0) {
|
||||
if(NavAreas.Length > 0 && GetURandomFloat() > 0.5) {
|
||||
int nav = NavAreas.Get(GetURandomInt() % (NavAreas.Length - 1));
|
||||
|
@ -578,34 +608,34 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
if(g_PropData[client].pendingSaveType == Save_Schematic) {
|
||||
// move cursor? or should be editor anyway
|
||||
}
|
||||
} else if(g_PropData[client].markedProps != null) {
|
||||
} else if(g_PropData[client].Selector.IsActive()) {
|
||||
SetWeaponDelay(client, 0.5);
|
||||
if(tick - cmdThrottle[client] >= 0.20) {
|
||||
if(buttons & IN_ATTACK) {
|
||||
int entity = GetLookingEntity(client, Filter_ValidHats);
|
||||
if(entity > 0) {
|
||||
g_PropData[client].markedProps.Push(EntIndexToEntRef(entity));
|
||||
GlowEntity(entity, 0.0, GLOW_RED);
|
||||
if(g_PropData[client].Selector.AddEntity(entity) != -1) {
|
||||
PrecacheSound("ui/beep07.wav");
|
||||
EmitSoundToClient(client, "ui/beep07.wav", entity, SND_CHANGEVOL, .volume = 0.5);
|
||||
}
|
||||
} else {
|
||||
PrintHintText(client, "No entity found");
|
||||
}
|
||||
} else if(buttons & IN_ATTACK2) {
|
||||
int entity = GetLookingEntity(client, Filter_ValidHats);
|
||||
if(entity > 0) {
|
||||
int ref = EntIndexToEntRef(entity);
|
||||
int index = g_PropData[client].markedProps.FindValue(ref);
|
||||
if(index > -1) {
|
||||
g_PropData[client].markedProps.Erase(index);
|
||||
L4D2_RemoveEntityGlow(entity);
|
||||
if(g_PropData[client].Selector.RemoveEntity(entity)) {
|
||||
PrecacheSound("ui/beep22.wav");
|
||||
EmitSoundToClient(client, "ui/beep22.wav", entity, SND_CHANGEVOL, .volume = 0.5);
|
||||
}
|
||||
}
|
||||
} else if(buttons & IN_USE) {
|
||||
if(buttons & IN_SPEED) {
|
||||
//Delete
|
||||
EndDeleteTool(client, true);
|
||||
g_PropData[client].Selector.End();
|
||||
} else if(buttons & IN_DUCK) {
|
||||
//Cancel
|
||||
EndDeleteTool(client, false);
|
||||
g_PropData[client].Selector.Cancel();
|
||||
}
|
||||
}
|
||||
cmdThrottle[client] = tick;
|
||||
|
@ -628,31 +658,37 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
}
|
||||
if(!(oldButtons & IN_JUMP) && (buttons & IN_JUMP)) {
|
||||
buttons &= ~IN_JUMP;
|
||||
Editor[client].CycleStacker(tick);
|
||||
Editor[client].CycleStacker();
|
||||
} else if(!(oldButtons & IN_SPEED) && (buttons & IN_SPEED)) {
|
||||
Editor[client].ToggleCollision(tick);
|
||||
Editor[client].ToggleCollision();
|
||||
return Plugin_Handled;
|
||||
} else if(!(oldButtons & IN_DUCK) && (buttons & IN_DUCK)) {
|
||||
Editor[client].ToggleCollisionRotate(tick);
|
||||
Editor[client].ToggleCollisionRotate();
|
||||
return Plugin_Handled;
|
||||
} else {
|
||||
PrintCenterText(client, "%.1f %.1f %.1f", Editor[client].angles[0], Editor[client].angles[1], Editor[client].angles[2]);
|
||||
isRotate = true;
|
||||
SetEntityFlags(client, flags |= FL_FROZEN);
|
||||
if(!(oldButtons & IN_ATTACK) && (buttons & IN_ATTACK)) Editor[client].CycleAxis(tick);
|
||||
else if(buttons & IN_ATTACK2) Editor[client].CycleSnapAngle(tick);
|
||||
if(!(oldButtons & IN_ATTACK) && (buttons & IN_ATTACK)) Editor[client].CycleAxis();
|
||||
else if(!(oldButtons & IN_ATTACK2) && (buttons & IN_ATTACK2)) Editor[client].CycleSnapAngle(tick);
|
||||
|
||||
// Rotation control:
|
||||
// Turn off rotate when player wants rotate
|
||||
Editor[client].hasCollisionRotate = false;
|
||||
if(tick - cmdThrottle[client] > 0.1) {
|
||||
if(Editor[client].axis == 2) {
|
||||
if(mouse[1] > 10) Editor[client].angles[2] += Editor[client].snapAngle;
|
||||
else if(mouse[1] < -10) Editor[client].angles[2] -= Editor[client].snapAngle;
|
||||
} else {
|
||||
if(mouse[0] > 10) Editor[client].angles[Editor[client].axis] += Editor[client].snapAngle;
|
||||
else if(mouse[0] < -10) Editor[client].angles[Editor[client].axis] -= Editor[client].snapAngle;
|
||||
|
||||
if(Editor[client].axis == 0) {
|
||||
int mouseXAbs = IntAbs(mouse[0]);
|
||||
int mouseYAbs = IntAbs(mouse[1]);
|
||||
bool XOverY = mouseXAbs > mouseYAbs;
|
||||
if(mouseYAbs > 10 && !XOverY) {
|
||||
Editor[client].IncrementAxis(0, mouse[1]);
|
||||
} else if(mouseXAbs > 10 && XOverY) {
|
||||
Editor[client].IncrementAxis(1, mouse[0]);
|
||||
}
|
||||
}
|
||||
else if(Editor[client].axis == 1) {
|
||||
if(mouse[0] > 10) Editor[client].angles[2] += Editor[client].snapAngle;
|
||||
else if(mouse[0] < -10) Editor[client].angles[2] -= Editor[client].snapAngle;
|
||||
}
|
||||
cmdThrottle[client] = tick;
|
||||
}
|
||||
|
@ -701,24 +737,27 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
}
|
||||
case COLOR: {
|
||||
SetWeaponDelay(client, 0.5);
|
||||
PrintCenterText(client, "%d %d %d %d", Editor[client].color[0], Editor[client].color[1], Editor[client].color[2], Editor[client].color[3]);
|
||||
PrintHintText(client, "%d %d %d %d", Editor[client].color[0], Editor[client].color[1], Editor[client].color[2], Editor[client].color[3]);
|
||||
if(buttons & IN_USE) {
|
||||
Editor[client].CycleColorComponent(tick);
|
||||
} else if(buttons & IN_ATTACK) {
|
||||
} else if(buttons & IN_ATTACK2) {
|
||||
Editor[client].IncreaseColor(1);
|
||||
allowMove = false;
|
||||
} else if(buttons & IN_ATTACK2) {
|
||||
} else if(buttons & IN_ATTACK) {
|
||||
Editor[client].IncreaseColor(-1);
|
||||
allowMove = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!(oldButtons & IN_USE) && buttons & IN_USE) {
|
||||
if(buttons & IN_DUCK) {
|
||||
|
||||
}
|
||||
if(Editor[client].mode != COLOR && !(oldButtons & IN_USE) && buttons & IN_USE) {
|
||||
if(buttons & IN_SPEED) {
|
||||
Editor[client].Cancel();
|
||||
} else if(buttons & IN_DUCK) {
|
||||
if(Editor[client].flags & Edit_Preview)
|
||||
Editor[client].CycleBuildType();
|
||||
Editor[client].CycleBuildType();
|
||||
// Editor[client].ShowExtraOptions();
|
||||
} else {
|
||||
int entity;
|
||||
Editor[client].Done(entity);
|
||||
|
@ -735,6 +774,12 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
int IntAbs(int a) {
|
||||
if(a < 0) {
|
||||
return a * -1;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// Don't show real entity to hat wearer (Show for ALL but hat wearer)
|
||||
Action OnRealTransmit(int entity, int client) {
|
||||
|
@ -914,6 +959,7 @@ bool Filter_ValidHats(int entity, int mask, int data) {
|
|||
}
|
||||
|
||||
bool CheckBlacklist(int entity) {
|
||||
if(entity == 0) return false;
|
||||
if(cvar_sm_hats_blacklist_enabled.BoolValue) {
|
||||
static char buffer[64];
|
||||
GetEntityClassname(entity, buffer, sizeof(buffer));
|
||||
|
@ -1024,4 +1070,4 @@ stock bool CalculateEditorPosition(int client, TraceEntityFilter filter) {
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -11,9 +11,12 @@
|
|||
#include <sdktools>
|
||||
//#include <sdkhooks>
|
||||
#include <profiler>
|
||||
#include <json>
|
||||
// #include <json>
|
||||
#include <ripext>
|
||||
#include <jutils>
|
||||
#include <entitylump>
|
||||
#undef REQUIRE_PLUGIN
|
||||
#include <hats_editor>
|
||||
|
||||
int g_iLaserIndex;
|
||||
#if defined DEBUG_BLOCKERS
|
||||
|
@ -27,6 +30,77 @@ int g_iLaserIndex;
|
|||
#define MAX_SCENE_NAME_LENGTH 32
|
||||
#define MAX_INPUTS_CLASSNAME_LENGTH 64
|
||||
|
||||
|
||||
ConVar cvarEnabled;
|
||||
enum struct ActiveSceneData {
|
||||
char name[MAX_SCENE_NAME_LENGTH];
|
||||
int variantIndex;
|
||||
}
|
||||
MapData g_MapData;
|
||||
BuilderData g_builder;
|
||||
char currentMap[64];
|
||||
|
||||
enum struct BuilderData {
|
||||
JSONObject mapData;
|
||||
|
||||
JSONObject selectedSceneData;
|
||||
char selectedSceneId[64];
|
||||
|
||||
JSONObject selectedVariantData;
|
||||
int selectedVariantIndex;
|
||||
|
||||
void Cleanup() {
|
||||
this.selectedSceneData = null;
|
||||
this.selectedVariantData = null;
|
||||
this.selectedVariantIndex = -1;
|
||||
this.selectedSceneId[0] = '\0';
|
||||
if(this.mapData != null)
|
||||
delete this.mapData;
|
||||
// JSONcleanup_and_delete(this.mapData);
|
||||
}
|
||||
|
||||
bool SelectScene(const char[] group) {
|
||||
if(!g_builder.mapData.HasKey(group)) return false;
|
||||
this.selectedSceneData = view_as<JSONObject>(g_builder.mapData.Get(group));
|
||||
strcopy(this.selectedSceneId, sizeof(this.selectedSceneId), group);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a variant, enter -1 to not select any (scene's entities)
|
||||
*/
|
||||
bool SelectVariant(int index = -1) {
|
||||
if(this.selectedSceneData == null) LogError("SelectVariant called, but no group selected");
|
||||
JSONArray variants = view_as<JSONArray>(this.selectedSceneData.Get("variants"));
|
||||
if(index >= variants.Length) return false;
|
||||
else if(index < -1) return false;
|
||||
else if(index > -1) {
|
||||
this.selectedVariantData = view_as<JSONObject>(variants.Get(index));
|
||||
} else {
|
||||
this.selectedVariantData = null;
|
||||
}
|
||||
this.selectedVariantIndex = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddEntity(int entity, ExportType exportType = Export_Model) {
|
||||
JSONArray entities;
|
||||
if(g_builder.selectedVariantData == null) {
|
||||
// Create <scene>.entities if doesn't exist:
|
||||
if(!g_builder.selectedSceneData.HasKey("entities")) {
|
||||
g_builder.selectedSceneData.Set("entities", new JSONArray());
|
||||
}
|
||||
entities = view_as<JSONArray>(g_builder.selectedSceneData.Get("entities"));
|
||||
} else {
|
||||
entities = view_as<JSONArray>(g_builder.selectedVariantData.Get("entities"));
|
||||
}
|
||||
JSONObject entityData = ExportEntity(entity, Export_Model);
|
||||
entities.Push(entityData);
|
||||
}
|
||||
}
|
||||
|
||||
#include <randomizer/rbuild.sp>
|
||||
|
||||
public Plugin myinfo =
|
||||
{
|
||||
name = "L4D2 Randomizer",
|
||||
|
@ -36,13 +110,6 @@ public Plugin myinfo =
|
|||
url = "https://github.com/Jackzmc/sourcemod-plugins"
|
||||
};
|
||||
|
||||
ConVar cvarEnabled;
|
||||
enum struct ActiveSceneData {
|
||||
char name[MAX_SCENE_NAME_LENGTH];
|
||||
int variantIndex;
|
||||
}
|
||||
MapData g_MapData;
|
||||
|
||||
public void OnPluginStart() {
|
||||
EngineVersion g_Game = GetEngineVersion();
|
||||
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
|
||||
|
@ -51,15 +118,14 @@ public void OnPluginStart() {
|
|||
|
||||
RegAdminCmd("sm_rcycle", Command_CycleRandom, ADMFLAG_CHEATS);
|
||||
RegAdminCmd("sm_expent", Command_ExportEnt, ADMFLAG_GENERIC);
|
||||
RegAdminCmd("sm_rbuild", Command_RandomizerBuild, ADMFLAG_CHEATS);
|
||||
|
||||
cvarEnabled = CreateConVar("sm_randomizer_enabled", "0");
|
||||
|
||||
g_MapData.activeScenes = new ArrayList(sizeof(ActiveSceneData));
|
||||
}
|
||||
|
||||
char currentMap[64];
|
||||
|
||||
bool hasRan;
|
||||
// TODO: on round start
|
||||
public void OnMapStart() {
|
||||
g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true);
|
||||
|
@ -69,6 +135,7 @@ public void OnMapStart() {
|
|||
}
|
||||
|
||||
public void OnMapEnd() {
|
||||
g_builder.Cleanup();
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
|
@ -124,9 +191,7 @@ public Action Command_CycleRandom(int client, int args) {
|
|||
if(args > 0) {
|
||||
DeleteCustomEnts();
|
||||
|
||||
char arg1[8];
|
||||
GetCmdArg(1, arg1, sizeof(arg1));
|
||||
int flags = StringToInt(arg1) | view_as<int>(FLAG_REFRESH);
|
||||
int flags = GetCmdArgInt(1) | view_as<int>(FLAG_REFRESH);
|
||||
RunMap(currentMap, flags);
|
||||
if(client > 0)
|
||||
PrintCenterText(client, "Cycled flags=%d", flags);
|
||||
|
@ -141,7 +206,7 @@ public Action Command_CycleRandom(int client, int args) {
|
|||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public Action Command_ExportEnt(int client, int args) {
|
||||
Action Command_ExportEnt(int client, int args) {
|
||||
float origin[3];
|
||||
int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin);
|
||||
float angles[3];
|
||||
|
@ -188,6 +253,271 @@ public Action Command_ExportEnt(int client, int args) {
|
|||
}
|
||||
return Plugin_Handled;
|
||||
}
|
||||
Action Command_RandomizerBuild(int client, int args) {
|
||||
char arg[64];
|
||||
GetCmdArg(1, arg, sizeof(arg));
|
||||
if(StrEqual(arg, "new")) {
|
||||
JSONObject temp = LoadMapJson(currentMap);
|
||||
GetCmdArg(2, arg, sizeof(arg));
|
||||
if(temp != null && !StrEqual(arg, "confirm")) {
|
||||
delete temp;
|
||||
ReplyToCommand(client, "Existing map data found, enter /rbuild new confirm to overwrite.");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
g_builder.Cleanup();
|
||||
g_builder.mapData = new JSONObject();
|
||||
SaveMapJson(currentMap, g_builder.mapData);
|
||||
ReplyToCommand(client, "Started new map data for %s", currentMap);
|
||||
} else if(StrEqual(arg, "load")) {
|
||||
if(args >= 2) {
|
||||
GetCmdArg(2, arg, sizeof(arg));
|
||||
} else {
|
||||
strcopy(arg, sizeof(arg), currentMap);
|
||||
}
|
||||
g_builder.Cleanup();
|
||||
g_builder.mapData = LoadMapJson(arg);
|
||||
if(g_builder.mapData != null) {
|
||||
ReplyToCommand(client, "Loaded map data for %s", arg);
|
||||
} else {
|
||||
ReplyToCommand(client, "No map data found for %s", arg);
|
||||
}
|
||||
} else if(StrEqual(arg, "menu")) {
|
||||
OpenMainMenu(client);
|
||||
} else if(g_builder.mapData == null) {
|
||||
ReplyToCommand(client, "No map data for %s, either load with /rbuild load, or start new /rbuild new", currentMap);
|
||||
return Plugin_Handled;
|
||||
} else if(StrEqual(arg, "save")) {
|
||||
SaveMapJson(currentMap, g_builder.mapData);
|
||||
ReplyToCommand(client, "Saved %s", currentMap);
|
||||
} else if(StrEqual(arg, "scenes")) {
|
||||
Command_RandomizerBuild_Scenes(client, args);
|
||||
} else if(StrEqual(arg, "sel") || StrEqual(arg, "selector")) {
|
||||
if(g_builder.selectedVariantData == null) {
|
||||
ReplyToCommand(client, "Please load map data, select a scene and a variant.");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
StartSelector(client, OnSelectorDone);
|
||||
} else if(StrEqual(arg, "spawner")) {
|
||||
if(g_builder.selectedVariantData == null) {
|
||||
ReplyToCommand(client, "Please load map data, select a scene and a variant.");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
StartSpawner(client, OnSpawnerDone);
|
||||
ReplyToCommand(client, "Spawn props to add to variant");
|
||||
} else if(StrEqual(arg, "cursor")) {
|
||||
if(g_builder.selectedVariantData == null) {
|
||||
ReplyToCommand(client, "Please load map data, select a scene and a variant.");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
float origin[3];
|
||||
char arg1[32];
|
||||
int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin);
|
||||
GetCmdArg(2, arg1, sizeof(arg1));
|
||||
ExportType exportType = Export_Model;
|
||||
if(StrEqual(arg1, "hammerid")) {
|
||||
exportType = Export_HammerId;
|
||||
} else if(StrEqual(arg1, "targetname")) {
|
||||
exportType = Export_TargetName;
|
||||
}
|
||||
if(entity > 0) {
|
||||
g_builder.AddEntity(entity, exportType);
|
||||
ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex);
|
||||
} else {
|
||||
ReplyToCommand(client, "No entity found");
|
||||
}
|
||||
} else if(StrEqual(arg, "entityid")) {
|
||||
char arg1[32];
|
||||
int entity = GetCmdArgInt(2);
|
||||
GetCmdArg(3, arg1, sizeof(arg));
|
||||
ExportType exportType = Export_Model;
|
||||
if(StrEqual(arg1, "hammerid")) {
|
||||
exportType = Export_HammerId;
|
||||
} else if(StrEqual(arg1, "targetname")) {
|
||||
exportType = Export_TargetName;
|
||||
}
|
||||
if(entity > 0) {
|
||||
g_builder.AddEntity(entity, exportType);
|
||||
ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex);
|
||||
} else {
|
||||
ReplyToCommand(client, "No entity found");
|
||||
}
|
||||
} else {
|
||||
ReplyToCommand(client, "Unknown arg. Try: new, load, save, scenes, cursor");
|
||||
}
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
enum ExportType {
|
||||
Export_HammerId,
|
||||
Export_TargetName,
|
||||
Export_Model
|
||||
}
|
||||
JSONObject ExportEntity(int entity, ExportType exportType = Export_Model) {
|
||||
float origin[3], angles[3], size[3];
|
||||
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin);
|
||||
GetEntPropVector(entity, Prop_Send, "m_angRotation", angles);
|
||||
GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size);
|
||||
|
||||
char model[64];
|
||||
JSONObject entityData = new JSONObject();
|
||||
GetEntityClassname(entity, model, sizeof(model));
|
||||
if(StrContains(model, "prop_") == -1) {
|
||||
entityData.Set("scale", VecToArray(size));
|
||||
}
|
||||
if(exportType == Export_HammerId) {
|
||||
int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID");
|
||||
entityData.SetString("type", "hammerid");
|
||||
char id[16];
|
||||
IntToString(hammerid, id, sizeof(id));
|
||||
entityData.SetString("model", id);
|
||||
} else if(exportType == Export_TargetName) {
|
||||
GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model));
|
||||
entityData.SetString("type", "targetname");
|
||||
entityData.SetString("model", model);
|
||||
} else {
|
||||
GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||
entityData.SetString("model", model);
|
||||
}
|
||||
entityData.Set("origin", VecToArray(origin));
|
||||
entityData.Set("angles", VecToArray(angles));
|
||||
return entityData;
|
||||
}
|
||||
|
||||
bool OnSpawnerDone(int client, int entity, CompleteType result) {
|
||||
PrintToServer("Randomizer OnSpawnerDone");
|
||||
if(result == Complete_PropSpawned && entity > 0) {
|
||||
JSONObject entityData = ExportEntity(entity, Export_Model);
|
||||
JSONArray entities = view_as<JSONArray>(g_builder.selectedVariantData.Get("entities"));
|
||||
entities.Push(entityData);
|
||||
ReplyToCommand(client, "Added entity to variant");
|
||||
RemoveEntity(entity);
|
||||
}
|
||||
return result == Complete_PropSpawned;
|
||||
}
|
||||
void OnSelectorDone(int client, ArrayList entities) {
|
||||
JSONArray entArray = view_as<JSONArray>(g_builder.selectedVariantData.Get("entities"));
|
||||
if(entities != null) {
|
||||
JSONObject entityData;
|
||||
for(int i = 0; i < entities.Length; i++) {
|
||||
int ref = entities.Get(i);
|
||||
entityData = ExportEntity(ref, Export_Model);
|
||||
entArray.Push(entityData);
|
||||
delete entityData; //?
|
||||
RemoveEntity(ref);
|
||||
}
|
||||
PrintToChat(client, "Added %d entities to variant", entities.Length);
|
||||
delete entities;
|
||||
}
|
||||
}
|
||||
|
||||
JSONArray VecToArray(float vec[3]) {
|
||||
JSONArray arr = new JSONArray();
|
||||
arr.PushFloat(vec[0]);
|
||||
arr.PushFloat(vec[1]);
|
||||
arr.PushFloat(vec[2]);
|
||||
return arr;
|
||||
}
|
||||
|
||||
void Command_RandomizerBuild_Scenes(int client, int args) {
|
||||
char arg[16];
|
||||
GetCmdArg(2, arg, sizeof(arg));
|
||||
if(StrEqual(arg, "new")) {
|
||||
if(args < 4) {
|
||||
ReplyToCommand(client, "Syntax: /rbuild scenes new <name> <chance 0.0-1.0>");
|
||||
} else {
|
||||
char name[64];
|
||||
GetCmdArg(3, name, sizeof(name));
|
||||
GetCmdArg(4, arg, sizeof(arg));
|
||||
float chance = StringToFloat(arg);
|
||||
JSONObject scene = new JSONObject();
|
||||
scene.SetFloat("chance", chance);
|
||||
scene.Set("variants", new JSONArray());
|
||||
g_builder.mapData.Set(name, scene);
|
||||
g_builder.SelectScene(name);
|
||||
JSONArray variants = view_as<JSONArray>(g_builder.selectedSceneData.Get("variants"));
|
||||
|
||||
JSONObject variantObj = new JSONObject();
|
||||
variantObj.SetInt("weight", 1);
|
||||
variantObj.Set("entities", new JSONArray());
|
||||
variants.Push(variantObj);
|
||||
g_builder.SelectVariant(0);
|
||||
ReplyToCommand(client, "Created & selected scene & variant %s#0", name);
|
||||
StartSelector(client, OnSelectorDone);
|
||||
}
|
||||
} else if(StrEqual(arg, "select") || StrEqual(arg, "load") || StrEqual(arg, "choose")) {
|
||||
GetCmdArg(3, arg, sizeof(arg));
|
||||
if(g_builder.SelectScene(arg)) {
|
||||
int variantIndex;
|
||||
if(GetCmdArgIntEx(4, variantIndex)) {
|
||||
if(g_builder.SelectVariant(variantIndex)) {
|
||||
ReplyToCommand(client, "Selected scene: %s#%d", arg, variantIndex);
|
||||
} else {
|
||||
ReplyToCommand(client, "Unknown variant for scene");
|
||||
}
|
||||
} else {
|
||||
ReplyToCommand(client, "Selected scene: %s", arg);
|
||||
}
|
||||
} else {
|
||||
ReplyToCommand(client, "No scene found");
|
||||
}
|
||||
} else if(StrEqual(arg, "variants")) {
|
||||
Command_RandomizerBuild_Variants(client, args);
|
||||
} else if(args > 1) {
|
||||
ReplyToCommand(client, "Unknown argument, try: new, select, variants");
|
||||
} else {
|
||||
ReplyToCommand(client, "Scenes:");
|
||||
JSONObjectKeys iterator = g_builder.mapData.Keys();
|
||||
while(iterator.ReadKey(arg, sizeof(arg))) {
|
||||
if(StrEqual(arg, g_builder.selectedSceneId)) {
|
||||
ReplyToCommand(client, "\t%s (selected)", arg);
|
||||
} else {
|
||||
ReplyToCommand(client, "\t%s", arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Command_RandomizerBuild_Variants(int client, int args) {
|
||||
if(g_builder.selectedSceneId[0] == '\0') {
|
||||
ReplyToCommand(client, "No scene selected, select with /rbuild groups select <group>");
|
||||
return;
|
||||
}
|
||||
char arg[16];
|
||||
GetCmdArg(3, arg, sizeof(arg));
|
||||
if(StrEqual(arg, "new")) {
|
||||
// /rbuild group variants new [weight]
|
||||
int weight;
|
||||
if(!GetCmdArgIntEx(4, weight)) {
|
||||
weight = 1;
|
||||
}
|
||||
JSONArray variants = view_as<JSONArray>(g_builder.selectedSceneData.Get("variants"));
|
||||
JSONObject variantObj = new JSONObject();
|
||||
variantObj.SetInt("weight", weight);
|
||||
variantObj.Set("entities", new JSONArray());
|
||||
int index = variants.Push(variantObj);
|
||||
g_builder.SelectVariant(index);
|
||||
ReplyToCommand(client, "Created variant #%d", index);
|
||||
} else if(StrEqual(arg, "select")) {
|
||||
int index = GetCmdArgInt(4);
|
||||
if(g_builder.SelectVariant(index)) {
|
||||
ReplyToCommand(client, "Selected variant: %s#%d", g_builder.selectedSceneId, index);
|
||||
} else {
|
||||
ReplyToCommand(client, "No variant found");
|
||||
}
|
||||
} else {
|
||||
ReplyToCommand(client, "Variants:");
|
||||
JSONObject variantObj;
|
||||
JSONArray variants = view_as<JSONArray>(g_builder.selectedSceneData.Get("variants"));
|
||||
for(int i = 0; i < variants.Length; i++) {
|
||||
variantObj = view_as<JSONObject>(variants.Get(i));
|
||||
int weight = 1;
|
||||
if(variantObj.HasKey("weight"))
|
||||
weight = variantObj.GetInt("weight");
|
||||
JSONArray entities = view_as<JSONArray>(variantObj.Get("entities"));
|
||||
ReplyToCommand(client, " #%d. [W:%d] [#E:%d]", i, weight, entities.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum struct SceneData {
|
||||
|
@ -211,10 +541,12 @@ enum struct SceneVariantData {
|
|||
int weight;
|
||||
ArrayList inputsList;
|
||||
ArrayList entities;
|
||||
ArrayList forcedScenes;
|
||||
|
||||
void Cleanup() {
|
||||
delete this.inputsList;
|
||||
delete this.entities;
|
||||
delete this.forcedScenes;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,6 +698,7 @@ enum struct LumpEditData {
|
|||
}
|
||||
|
||||
enum struct MapData {
|
||||
StringMap scenesKv;
|
||||
ArrayList scenes;
|
||||
ArrayList lumpEdits;
|
||||
ArrayList activeScenes;
|
||||
|
@ -376,30 +709,40 @@ enum loadFlags {
|
|||
FLAG_ALL_SCENES = 1, // Pick all scenes, no random chance
|
||||
FLAG_ALL_VARIANTS = 2, // Pick all variants (for debug purposes),
|
||||
FLAG_REFRESH = 4, // Load data bypassing cache
|
||||
FLAG_FORCE_ACTIVE = 8 // Similar to ALL_SCENES, bypasses % chance
|
||||
}
|
||||
|
||||
// Reads (mapname).json file and parses it
|
||||
public bool LoadMapData(const char[] map, int flags) {
|
||||
public JSONObject LoadMapJson(const char[] map) {
|
||||
Debug("Loading config for %s", map);
|
||||
char filePath[PLATFORM_MAX_PATH];
|
||||
BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map);
|
||||
if(!FileExists(filePath)) {
|
||||
Log("[Randomizer] No map config file (data/randomizer/%s.json), not loading", map);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
char buffer[65536];
|
||||
File file = OpenFile(filePath, "r");
|
||||
if(file == null) {
|
||||
LogError("Could not open map config file (data/randomizer/%s.json)", map);
|
||||
return false;
|
||||
}
|
||||
file.ReadString(buffer, sizeof(buffer));
|
||||
JSON_Object data = json_decode(buffer);
|
||||
JSONObject data = JSONObject.FromFile(filePath);
|
||||
if(data == null) {
|
||||
LogError("Could not parse map config file (data/randomizer/%s.json)", map);
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
public void SaveMapJson(const char[] map, JSONObject json) {
|
||||
Debug("Saving config for %s", map);
|
||||
char filePath[PLATFORM_MAX_PATH], filePathTemp[PLATFORM_MAX_PATH];
|
||||
BuildPath(Path_SM, filePathTemp, sizeof(filePath), "data/randomizer/%s.json.tmp", map);
|
||||
BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map);
|
||||
|
||||
json.ToFile(filePathTemp, JSON_INDENT(4));
|
||||
RenameFile(filePath, filePathTemp);
|
||||
SetFilePermissions(filePath, FPERM_U_WRITE | FPERM_U_READ | FPERM_G_WRITE | FPERM_G_READ | FPERM_O_READ);
|
||||
}
|
||||
|
||||
public bool LoadMapData(const char[] map, int flags) {
|
||||
JSONObject data = LoadMapJson(map);
|
||||
if(data == null) {
|
||||
json_get_last_error(buffer, sizeof(buffer));
|
||||
LogError("Could not parse map config file (data/randomizer/%s.json): %s", map, buffer);
|
||||
delete file;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -407,51 +750,51 @@ public bool LoadMapData(const char[] map, int flags) {
|
|||
|
||||
Cleanup();
|
||||
g_MapData.scenes = new ArrayList(sizeof(SceneData));
|
||||
g_MapData.scenesKv = new StringMap();
|
||||
g_MapData.lumpEdits = new ArrayList(sizeof(LumpEditData));
|
||||
g_MapData.activeScenes.Clear();
|
||||
|
||||
Profiler profiler = new Profiler();
|
||||
profiler.Start();
|
||||
|
||||
int length = data.Length;
|
||||
JSONObjectKeys iterator = data.Keys();
|
||||
char key[32];
|
||||
for (int i = 0; i < length; i += 1) {
|
||||
data.GetKey(i, key, sizeof(key));
|
||||
|
||||
while(iterator.ReadKey(key, sizeof(key))) {
|
||||
if(key[0] == '_') {
|
||||
if(StrEqual(key, "_lumps")) {
|
||||
JSON_Array lumpsList = view_as<JSON_Array>(data.GetObject(key));
|
||||
JSONArray lumpsList = view_as<JSONArray>(data.Get(key));
|
||||
if(lumpsList != null) {
|
||||
for(int l = 0; l < lumpsList.Length; l++) {
|
||||
loadLumpData(g_MapData.lumpEdits, lumpsList.GetObject(l));
|
||||
loadLumpData(g_MapData.lumpEdits, view_as<JSONObject>(lumpsList.Get(l)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Debug("Unknown special entry \"%s\", skipping", key);
|
||||
}
|
||||
} else {
|
||||
if(data.GetType(key) != JSON_Type_Object) {
|
||||
Debug("Invalid normal entry \"%s\" (not an object), skipping", key);
|
||||
continue;
|
||||
}
|
||||
JSON_Object scene = data.GetObject(key);
|
||||
// if(data.GetType(key) != JSONType_Object) {
|
||||
// Debug("Invalid normal entry \"%s\" (not an object), skipping", key);
|
||||
// continue;
|
||||
// }
|
||||
JSONObject scene = view_as<JSONObject>(data.Get(key));
|
||||
// Parses scene data and inserts to scenes
|
||||
loadScene(key, scene);
|
||||
}
|
||||
}
|
||||
|
||||
json_cleanup_and_delete(data);
|
||||
delete data;
|
||||
profiler.Stop();
|
||||
Log("Parsed map file for %s(%d) and found %d scenes in %.4f seconds", map, flags, g_MapData.scenes.Length, profiler.Time);
|
||||
delete profiler;
|
||||
delete file;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calls LoadMapData (read&parse (mapname).json) then select scenes
|
||||
public bool RunMap(const char[] map, int flags) {
|
||||
if(g_MapData.scenes == null || flags & view_as<int>(FLAG_REFRESH)) {
|
||||
LoadMapData(map, flags);
|
||||
if(!LoadMapData(map, flags)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Profiler profiler = new Profiler();
|
||||
|
||||
|
@ -463,7 +806,7 @@ public bool RunMap(const char[] map, int flags) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSON_Object sceneData) {
|
||||
void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSONObject sceneData) {
|
||||
SceneData scene;
|
||||
scene.name = key;
|
||||
scene.chance = sceneData.GetFloat("chance");
|
||||
|
@ -471,38 +814,73 @@ void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSON_Object sceneData) {
|
|||
LogError("Scene \"%s\" has invalid chance (%f)", scene.name, scene.chance);
|
||||
return;
|
||||
}
|
||||
// TODO: load "entities", merge with choice.entities
|
||||
sceneData.GetString("group", scene.group, sizeof(scene.group));
|
||||
scene.variants = new ArrayList(sizeof(SceneVariantData));
|
||||
JSON_Array variants = view_as<JSON_Array>(sceneData.GetObject("variants"));
|
||||
if(!sceneData.HasKey("variants")) {
|
||||
ThrowError("Failed to load: Scene \"%s\" has missing \"variants\" array", scene.name);
|
||||
return;
|
||||
}
|
||||
JSONArray entities;
|
||||
if(sceneData.HasKey("entities")) {
|
||||
entities = view_as<JSONArray>(sceneData.Get("entities"));
|
||||
}
|
||||
|
||||
JSONArray variants = view_as<JSONArray>(sceneData.Get("variants"));
|
||||
for(int i = 0; i < variants.Length; i++) {
|
||||
// Parses choice and loads to scene.choices
|
||||
loadChoice(scene, variants.GetObject(i));
|
||||
loadChoice(scene, view_as<JSONObject>(variants.Get(i)), entities);
|
||||
}
|
||||
g_MapData.scenes.PushArray(scene);
|
||||
g_MapData.scenesKv.SetArray(scene.name, scene, sizeof(scene));
|
||||
}
|
||||
|
||||
void loadChoice(SceneData scene, JSON_Object choiceData) {
|
||||
void loadChoice(SceneData scene, JSONObject choiceData, JSONArray extraEntities) {
|
||||
SceneVariantData choice;
|
||||
choice.weight = choiceData.GetInt("weight", 1);
|
||||
choice.weight = 1;
|
||||
if(choiceData.HasKey("weight"))
|
||||
choice.weight = choiceData.GetInt("weight");
|
||||
choice.entities = new ArrayList(sizeof(VariantEntityData));
|
||||
choice.inputsList = new ArrayList(sizeof(VariantInputData));
|
||||
JSON_Array entities = view_as<JSON_Array>(choiceData.GetObject("entities"));
|
||||
if(entities != null) {
|
||||
choice.forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH));
|
||||
// Load in any variant-based entities
|
||||
if(choiceData.HasKey("entities")) {
|
||||
JSONArray entities = view_as<JSONArray>(choiceData.Get("entities"));
|
||||
for(int i = 0; i < entities.Length; i++) {
|
||||
// Parses entities and loads to choice.entities
|
||||
loadChoiceEntity(choice.entities, entities.GetObject(i));
|
||||
loadChoiceEntity(choice.entities, view_as<JSONObject>(entities.Get(i)));
|
||||
}
|
||||
delete entities;
|
||||
}
|
||||
JSON_Array inputsList = view_as<JSON_Array>(choiceData.GetObject("inputs"));
|
||||
if(inputsList != null) {
|
||||
for(int i = 0; i < inputsList.Length; i++) {
|
||||
loadChoiceInput(choice.inputsList, inputsList.GetObject(i));
|
||||
// Load in any entities that the scene has
|
||||
if(extraEntities != null) {
|
||||
for(int i = 0; i < extraEntities.Length; i++) {
|
||||
// Parses entities and loads to choice.entities
|
||||
loadChoiceEntity(choice.entities, view_as<JSONObject>(extraEntities.Get(i)));
|
||||
}
|
||||
delete extraEntities;
|
||||
}
|
||||
// Load all inputs
|
||||
if(choiceData.HasKey("inputs")) {
|
||||
JSONArray inputsList = view_as<JSONArray>(choiceData.Get("inputs"));
|
||||
for(int i = 0; i < inputsList.Length; i++) {
|
||||
loadChoiceInput(choice.inputsList, view_as<JSONObject>(inputsList.Get(i)));
|
||||
}
|
||||
delete inputsList;
|
||||
}
|
||||
if(choiceData.HasKey("force_scenes")) {
|
||||
JSONArray scenes = view_as<JSONArray>(choiceData.Get("force_scenes"));
|
||||
char sceneId[32];
|
||||
for(int i = 0; i < scenes.Length; i++) {
|
||||
scenes.GetString(i, sceneId, sizeof(sceneId));
|
||||
choice.forcedScenes.PushString(sceneId);
|
||||
}
|
||||
delete scenes;
|
||||
}
|
||||
scene.variants.PushArray(choice);
|
||||
}
|
||||
|
||||
void loadChoiceInput(ArrayList list, JSON_Object inputData) {
|
||||
void loadChoiceInput(ArrayList list, JSONObject inputData) {
|
||||
VariantInputData input;
|
||||
// Check classname -> targetname -> hammerid
|
||||
if(!inputData.GetString("classname", input.name, sizeof(input.name))) {
|
||||
|
@ -527,7 +905,7 @@ void loadChoiceInput(ArrayList list, JSON_Object inputData) {
|
|||
list.PushArray(input);
|
||||
}
|
||||
|
||||
void loadLumpData(ArrayList list, JSON_Object inputData) {
|
||||
void loadLumpData(ArrayList list, JSONObject inputData) {
|
||||
LumpEditData input;
|
||||
// Check classname -> targetname -> hammerid
|
||||
if(!inputData.GetString("classname", input.name, sizeof(input.name))) {
|
||||
|
@ -553,7 +931,7 @@ void loadLumpData(ArrayList list, JSON_Object inputData) {
|
|||
list.PushArray(input);
|
||||
}
|
||||
|
||||
void loadChoiceEntity(ArrayList list, JSON_Object entityData) {
|
||||
void loadChoiceEntity(ArrayList list, JSONObject entityData) {
|
||||
VariantEntityData entity;
|
||||
entityData.GetString("model", entity.model, sizeof(entity.model));
|
||||
if(!entityData.GetString("type", entity.type, sizeof(entity.type))) {
|
||||
|
@ -569,18 +947,20 @@ void loadChoiceEntity(ArrayList list, JSON_Object entityData) {
|
|||
list.PushArray(entity);
|
||||
}
|
||||
|
||||
void GetVector(JSON_Object obj, const char[] key, float out[3]) {
|
||||
JSON_Array vecArray = view_as<JSON_Array>(obj.GetObject(key));
|
||||
bool GetVector(JSONObject obj, const char[] key, float out[3]) {
|
||||
if(!obj.HasKey(key)) return false;
|
||||
JSONArray vecArray = view_as<JSONArray>(obj.Get(key));
|
||||
if(vecArray != null) {
|
||||
out[0] = vecArray.GetFloat(0);
|
||||
out[1] = vecArray.GetFloat(1);
|
||||
out[2] = vecArray.GetFloat(2);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetColor(JSON_Object obj, const char[] key, int out[4]) {
|
||||
JSON_Array vecArray = view_as<JSON_Array>(obj.GetObject(key));
|
||||
if(vecArray != null) {
|
||||
void GetColor(JSONObject obj, const char[] key, int out[4], int defaultColor[4] = { 255, 255, 255, 255 }) {
|
||||
if(obj.HasKey(key)) {
|
||||
JSONArray vecArray = view_as<JSONArray>(obj.Get(key));
|
||||
out[0] = vecArray.GetInt(0);
|
||||
out[1] = vecArray.GetInt(1);
|
||||
out[2] = vecArray.GetInt(2);
|
||||
|
@ -589,10 +969,7 @@ void GetColor(JSON_Object obj, const char[] key, int out[4]) {
|
|||
else
|
||||
out[3] = 255;
|
||||
} else {
|
||||
out[0] = 255;
|
||||
out[1] = 255;
|
||||
out[2] = 255;
|
||||
out[3] = 255;
|
||||
out = defaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -600,6 +977,8 @@ void selectScenes(int flags = 0) {
|
|||
SceneData scene;
|
||||
StringMap groups = new StringMap();
|
||||
ArrayList list;
|
||||
// Select and spawn non-group scenes
|
||||
// TODO: refactor to use .scenesKv
|
||||
for(int i = 0; i < g_MapData.scenes.Length; i++) {
|
||||
g_MapData.scenes.GetArray(i, scene);
|
||||
// TODO: Exclusions
|
||||
|
@ -607,6 +986,7 @@ void selectScenes(int flags = 0) {
|
|||
if(scene.group[0] == '\0') {
|
||||
selectScene(scene, flags);
|
||||
} else {
|
||||
// Load it into group list
|
||||
if(!groups.GetValue(scene.group, list)) {
|
||||
list = new ArrayList();
|
||||
}
|
||||
|
@ -615,25 +995,61 @@ void selectScenes(int flags = 0) {
|
|||
}
|
||||
}
|
||||
|
||||
// Iterate through groups and select a random scene:
|
||||
StringMapSnapshot snapshot = groups.Snapshot();
|
||||
char key[MAX_SCENE_NAME_LENGTH];
|
||||
for(int i = 0; i < snapshot.Length; i++) {
|
||||
snapshot.GetKey(i, key, sizeof(key));
|
||||
groups.GetValue(key, list);
|
||||
// Select a random scene from the group:
|
||||
int index = GetURandomInt() % list.Length;
|
||||
index = list.Get(index);
|
||||
g_MapData.scenes.GetArray(index, scene);
|
||||
|
||||
Debug("Selected scene \"%s\" for group %s (%d members)", scene.name, key, list.Length);
|
||||
selectScene(scene, flags);
|
||||
delete list;
|
||||
}
|
||||
// Traverse active scenes, loading any other scene it requires (via .force_scenes)
|
||||
ActiveSceneData aScene;
|
||||
SceneVariantData choice;
|
||||
ArrayList forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH));
|
||||
for(int i = 0; i < g_MapData.activeScenes.Length; i++) {
|
||||
g_MapData.activeScenes.GetArray(i, aScene);
|
||||
g_MapData.scenes.GetArray(i, scene);
|
||||
scene.variants.GetArray(aScene.variantIndex, choice);
|
||||
if(choice.forcedScenes != null) {
|
||||
for(int j = 0; j < choice.forcedScenes.Length; j++) {
|
||||
choice.forcedScenes.GetString(j, key, sizeof(key));
|
||||
forcedScenes.PushString(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Iterate and activate any forced scenes
|
||||
for(int i = 0; i < forcedScenes.Length; i++) {
|
||||
forcedScenes.GetString(i, key, sizeof(key));
|
||||
// Check if scene was already loaded
|
||||
bool isSceneAlreadyLoaded = false;
|
||||
for(int j = 0; j < g_MapData.activeScenes.Length; i++) {
|
||||
g_MapData.activeScenes.GetArray(j, aScene);
|
||||
if(StrEqual(aScene.name, key)) {
|
||||
isSceneAlreadyLoaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isSceneAlreadyLoaded) continue;
|
||||
g_MapData.scenesKv.GetArray(key, scene, sizeof(scene));
|
||||
selectScene(scene, flags | view_as<int>(FLAG_FORCE_ACTIVE));
|
||||
}
|
||||
|
||||
delete forcedScenes;
|
||||
delete snapshot;
|
||||
delete groups;
|
||||
}
|
||||
|
||||
void selectScene(SceneData scene, int flags) {
|
||||
// Use the .chance field unless FLAG_ALL_SCENES is set
|
||||
if(~flags & view_as<int>(FLAG_ALL_SCENES) && GetURandomFloat() > scene.chance) {
|
||||
// Use the .chance field unless FLAG_ALL_SCENES or FLAG_FORCE_ACTIVE is set
|
||||
if(~flags & view_as<int>(FLAG_ALL_SCENES) && ~flags & view_as<int>(FLAG_FORCE_ACTIVE) && GetURandomFloat() > scene.chance) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -645,20 +1061,26 @@ void selectScene(SceneData scene, int flags) {
|
|||
ArrayList choices = new ArrayList();
|
||||
SceneVariantData choice;
|
||||
int index;
|
||||
Debug("Scene %s has %d variants", scene.name, scene.variants.Length);
|
||||
// Weighted random: Push N times dependent on weight
|
||||
for(int i = 0; i < scene.variants.Length; i++) {
|
||||
scene.variants.GetArray(i, choice);
|
||||
if(flags & view_as<int>(FLAG_ALL_VARIANTS)) {
|
||||
spawnVariant(choice);
|
||||
} else {
|
||||
if(choice.weight <= 0) {
|
||||
PrintToServer("Warn: Variant %d in scene %s has invalid weight", i, scene.name);
|
||||
continue;
|
||||
}
|
||||
for(int c = 0; c < choice.weight; c++) {
|
||||
choices.Push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Debug("Total choices: %d", choices.Length);
|
||||
if(flags & view_as<int>(FLAG_ALL_VARIANTS)) {
|
||||
delete choices;
|
||||
} else {
|
||||
} else if(choices.Length > 0) {
|
||||
index = GetURandomInt() % choices.Length;
|
||||
index = choices.Get(index);
|
||||
delete choices;
|
||||
|
@ -666,7 +1088,6 @@ void selectScene(SceneData scene, int flags) {
|
|||
scene.variants.GetArray(index, choice);
|
||||
spawnVariant(choice);
|
||||
}
|
||||
|
||||
ActiveSceneData aScene;
|
||||
strcopy(aScene.name, sizeof(aScene.name), scene.name);
|
||||
aScene.variantIndex = index;
|
||||
|
|
|
@ -20,6 +20,9 @@ int connectAttempts;
|
|||
authState g_authState;
|
||||
JSONObject g_globalVars;
|
||||
|
||||
StringMap actionFallbackHandlers; // namespace -> action name has no handler, falls to this.
|
||||
StringMap actionNamespaceHandlers; // { namespace: { [action name] -> handler } }
|
||||
|
||||
enum authState {
|
||||
AuthError = -1,
|
||||
NotAuthorized,
|
||||
|
@ -53,8 +56,16 @@ char OUT_EVENT_IDS[view_as<int>(Event_Invalid)][] = {
|
|||
char steamidCache[MAXPLAYERS+1][32];
|
||||
|
||||
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
|
||||
CreateNative("UIElement.Send", Native_UpdateUI);
|
||||
CreateNative("TempUI.Send", Native_UpdateTempUI);
|
||||
RegPluginLibrary("overlay");
|
||||
|
||||
CreateNative("IsOverlayConnected", Native_IsOverlayConnected);
|
||||
CreateNative("RegisterActionAnyHandler", Native_ActionHandler);
|
||||
CreateNative("RegisterActionHandler", Native_ActionHandler);
|
||||
|
||||
CreateNative("UIElement.SendAll", Native_UpdateUI);
|
||||
CreateNative("UIElement.SendTo", Native_UpdateUI);
|
||||
CreateNative("TempUI.SendAll", Native_UpdateTempUI);
|
||||
CreateNative("TempUI.SendTo", Native_UpdateTempUI);
|
||||
return APLRes_Success;
|
||||
}
|
||||
|
||||
|
@ -64,6 +75,9 @@ public void OnPluginStart() {
|
|||
SetFailState("This plugin is for L4D/L4D2 only.");
|
||||
}
|
||||
|
||||
actionFallbackHandlers = new StringMap();
|
||||
actionNamespaceHandlers = new StringMap();
|
||||
|
||||
g_globalVars = new JSONObject();
|
||||
|
||||
cvarManagerUrl = CreateConVar("sm_overlay_manager_url", "ws://localhost:3011/socket", "");
|
||||
|
@ -171,6 +185,7 @@ void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast)
|
|||
if(GetClientCount(false) == 0) {
|
||||
DisconnectManager();
|
||||
}
|
||||
steamidCache[client][0] = '\0';
|
||||
}
|
||||
|
||||
void OnWSConnect(WebSocket ws, any arg) {
|
||||
|
@ -210,12 +225,78 @@ void OnWSJson(WebSocket ws, JSON message, any data) {
|
|||
message.ToString(buffer, sizeof(buffer));
|
||||
PrintToServer("[Overlay] Error: %s", buffer);
|
||||
} else {
|
||||
char type[32];
|
||||
obj.GetString("type", type, sizeof(type));
|
||||
if(StrEqual(type, "action")) {
|
||||
OnAction(obj);
|
||||
}
|
||||
|
||||
char buffer[2048];
|
||||
message.ToString(buffer, sizeof(buffer));
|
||||
PrintToServer("[Overlay] Got JSON: %s", buffer);
|
||||
}
|
||||
}
|
||||
|
||||
stock int ExplodeStringToArrayList(const char[] text, const char[] split, ArrayList buffers, int maxStringLength) {
|
||||
int reloc_idx, idx, total;
|
||||
|
||||
if (buffers == null || !split[0]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char[] item = new char[maxStringLength];
|
||||
while ((idx = SplitString(text[reloc_idx], split, item, maxStringLength)) != -1) {
|
||||
reloc_idx += idx;
|
||||
++total;
|
||||
buffers.PushString(item);
|
||||
}
|
||||
++total;
|
||||
buffers.PushString(text[reloc_idx]);
|
||||
|
||||
return buffers.Length;
|
||||
}
|
||||
|
||||
void OnAction(JSONObject obj) {
|
||||
char steamid[32];
|
||||
obj.GetString("steamid", steamid, sizeof(steamid));
|
||||
char ns[64];
|
||||
obj.GetString("namespace", ns, sizeof(ns));
|
||||
char id[64];
|
||||
obj.GetString("elem_id", id, sizeof(id));
|
||||
char action[256];
|
||||
obj.GetString("action", action, sizeof(action));
|
||||
|
||||
int client = FindClientBySteamId2(steamid);
|
||||
|
||||
StringMap nsHandler;
|
||||
PrivateForward fwd;
|
||||
if(!actionNamespaceHandlers.GetValue(ns, nsHandler) || !nsHandler.GetValue(id, fwd)) {
|
||||
if(!actionFallbackHandlers.GetValue(ns, fwd)) {
|
||||
// No handler or catch all namespace handler
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList args = new ArrayList(ACTION_ARG_LENGTH);
|
||||
ExplodeStringToArrayList(action, " ", args, ACTION_ARG_LENGTH);
|
||||
UIActionEvent event = UIActionEvent(args);
|
||||
|
||||
Call_StartForward(fwd);
|
||||
Call_PushCell(event);
|
||||
Call_PushCell(client);
|
||||
Call_Finish();
|
||||
event._Delete();
|
||||
}
|
||||
|
||||
int FindClientBySteamId2(const char[] steamid) {
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
if(StrEqual(steamidCache[i], steamid)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void ConnectManager() {
|
||||
DisconnectManager();
|
||||
|
@ -281,11 +362,15 @@ void SendEvent_UpdateUI(const char[] elemNamespace, const char[] elemId, bool vi
|
|||
}
|
||||
|
||||
//SendTempUI(int client, const char[] id, int lifetime, JSONObject element);
|
||||
bool Native_SendTempUI(Handle plugin, int numParams) {
|
||||
any Native_SendTempUI(Handle plugin, int numParams) {
|
||||
if(!isManagerReady()) return false;
|
||||
|
||||
int client = GetNativeCell(1);
|
||||
if(steamidCache[client][0] == '\0') return false;
|
||||
if (client <= 0 || client > MaxClients) {
|
||||
return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client);
|
||||
} else if (!IsClientConnected(client) || steamidCache[client][0] == '\0') {
|
||||
return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected/authorized yet", client);
|
||||
}
|
||||
|
||||
char id[64];
|
||||
GetNativeString(2, id, sizeof(id));
|
||||
|
@ -299,7 +384,7 @@ bool Native_SendTempUI(Handle plugin, int numParams) {
|
|||
}
|
||||
|
||||
//ShowUI(int client, const char[] elemNamespace, const char[] elemId, JSONObject variables);
|
||||
bool Native_ShowUI(Handle plugin, int numParams) {
|
||||
any Native_ShowUI(Handle plugin, int numParams) {
|
||||
if(!isManagerReady()) return false;
|
||||
|
||||
char elemNamespace[64], id[64];
|
||||
|
@ -313,7 +398,7 @@ bool Native_ShowUI(Handle plugin, int numParams) {
|
|||
}
|
||||
|
||||
//HideUI(int client, const char[] elemNamespace, const char[] elemId);
|
||||
bool Native_HideUI(Handle plugin, int numParams) {
|
||||
any Native_HideUI(Handle plugin, int numParams) {
|
||||
if(!isManagerReady()) return false;
|
||||
|
||||
char elemNamespace[64], id[64];
|
||||
|
@ -325,7 +410,7 @@ bool Native_HideUI(Handle plugin, int numParams) {
|
|||
}
|
||||
|
||||
//PlayAudio(int client, const char[] url);
|
||||
bool Native_PlayAudio(Handle plugin, int numParams) {
|
||||
any Native_PlayAudio(Handle plugin, int numParams) {
|
||||
if(!isManagerReady()) return false;
|
||||
|
||||
char url[256];
|
||||
|
@ -335,27 +420,92 @@ bool Native_PlayAudio(Handle plugin, int numParams) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Native_UpdateUI(Handle plugin, int numParams) {
|
||||
any Native_UpdateUI(Handle plugin, int numParams) {
|
||||
if(!isManagerReady()) return false;
|
||||
|
||||
UIElement elem = GetNativeCell(1);
|
||||
JSONObject obj = view_as<JSONObject>(elem);
|
||||
|
||||
JSONArray arr = view_as<JSONArray>(obj.Get("steamids"));
|
||||
if(numParams == 0) {
|
||||
arr.Clear();
|
||||
} else if(numParams == 1) {
|
||||
int client = GetNativeCell(2);
|
||||
if (client <= 0 || client > MaxClients) {
|
||||
return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client);
|
||||
} else if (!IsClientConnected(client) || steamidCache[client][0] == '\0') {
|
||||
return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected/authorized yet", client);
|
||||
}
|
||||
arr.PushString(steamidCache[client]);
|
||||
}
|
||||
|
||||
g_ws.Write(view_as<JSON>(elem));
|
||||
arr.Clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Native_UpdateTempUI(Handle plugin, int numParams) {
|
||||
any Native_UpdateTempUI(Handle plugin, int numParams) {
|
||||
if(!isManagerReady()) return false;
|
||||
|
||||
TempUI elem = GetNativeCell(1);
|
||||
JSONObject obj = view_as<JSONObject>(elem);
|
||||
|
||||
JSONArray arr = view_as<JSONArray>(obj.Get("steamids"));
|
||||
if(numParams == 0) {
|
||||
arr.Clear();
|
||||
} else if(numParams == 1) {
|
||||
int client = GetNativeCell(2);
|
||||
if (client <= 0 || client > MaxClients) {
|
||||
return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client);
|
||||
} else if (!IsClientConnected(client) || steamidCache[client][0] == '\0') {
|
||||
return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected/authorized yet", client);
|
||||
}
|
||||
arr.PushString(steamidCache[client]);
|
||||
}
|
||||
|
||||
g_ws.Write(view_as<JSON>(elem));
|
||||
arr.Clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//IsOverlayConnected();
|
||||
bool Native_IsOverlayConnected(Handle plugin, int numParams) {
|
||||
any Native_IsOverlayConnected(Handle plugin, int numParams) {
|
||||
return isManagerReady();
|
||||
}
|
||||
|
||||
//RegisterActionHandler
|
||||
//RegisterActionAnyHandler
|
||||
any Native_ActionHandler(Handle plugin, int numParams) {
|
||||
char ns[64];
|
||||
GetNativeString(1, ns, sizeof(ns));
|
||||
|
||||
if(numParams == 3) {
|
||||
// RegisterActionHandler
|
||||
StringMap nsHandlers;
|
||||
if(!actionNamespaceHandlers.GetValue(ns, nsHandlers)) {
|
||||
nsHandlers = new StringMap();
|
||||
}
|
||||
|
||||
char actionId[64];
|
||||
GetNativeString(2, actionId, sizeof(actionId));
|
||||
|
||||
PrivateForward fwd;
|
||||
if(!nsHandlers.GetValue(actionId, fwd)) {
|
||||
fwd = new PrivateForward(ET_Ignore, Param_Cell);
|
||||
}
|
||||
fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(3));
|
||||
nsHandlers.SetValue(actionId, fwd);
|
||||
} else {
|
||||
// RegisterActionAnyHandler
|
||||
|
||||
PrivateForward fwd;
|
||||
if(!actionFallbackHandlers.GetValue(ns, fwd)) {
|
||||
fwd = new PrivateForward(ET_Ignore, Param_Cell);
|
||||
}
|
||||
fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(2));
|
||||
actionFallbackHandlers.SetValue(ns, fwd);
|
||||
}
|
||||
return 1;
|
||||
}
|
|
@ -242,15 +242,12 @@ public Action OnClientSayCommand(int client, const char[] command, const char[]
|
|||
PrintToChat(client, "Note cancelled.");
|
||||
} else {
|
||||
int size = 2 * strlen(sArgs) + 1;
|
||||
char[] sArgsTrimmed = new char[size];
|
||||
DB.Escape(sArgs, sArgsTrimmed, size);
|
||||
TrimString(sArgsTrimmed);
|
||||
char buffer[32];
|
||||
GetClientAuthId(client, AuthId_Steam2, buffer, sizeof(buffer));
|
||||
// TODO: escape content
|
||||
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", menuNoteTarget, buffer, sArgsTrimmed);
|
||||
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", menuNoteTarget, buffer, sArgs);
|
||||
DB.Query(DB_AddNote, query);
|
||||
LogAction(client, -1, "added note for \"%s\" (%s): \"%s\"", client, menuNoteTargetName, menuNoteTarget, sArgsTrimmed);
|
||||
LogAction(client, -1, "added note for \"%s\" (%s): \"%s\"", client, menuNoteTargetName, menuNoteTarget, sArgs);
|
||||
Format(buffer, sizeof(buffer), "%N: ", client);
|
||||
CShowActivity2(client, buffer, "added a note for {green}%s: {default}\"%s\"", menuNoteTargetName, sArgs);
|
||||
}
|
||||
|
@ -310,10 +307,7 @@ public Action Command_AddNote(int client, int args) {
|
|||
char authMarker[32];
|
||||
if(client > 0)
|
||||
GetClientAuthId(client, AuthId_Steam2, authMarker, sizeof(authMarker));
|
||||
int size = 2 * strlen(reason) + 1;
|
||||
char[] content = new char[size];
|
||||
DB.Escape(reason, content, size);
|
||||
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", auth, authMarker, content);
|
||||
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", auth, authMarker, reason);
|
||||
DB.Query(DB_AddNote, query);
|
||||
LogAction(client, target_list[0], "\"%L\" added note for \"%L\": \"%s\"", client, target_list[0], reason);
|
||||
CShowActivity(client, "added a note for {green}%N: {default}\"%s\"", target_list[0], reason);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue