diff --git a/ULib_readme.txt b/ULib_readme.txt
index 0661132..3143a11 100644
--- a/ULib_readme.txt
+++ b/ULib_readme.txt
@@ -1,279 +1,279 @@
-Title: ULib Readme
-
-*ULib v2.60 (released 00/00/00)*
-
-ULib is a developer library for GMod 10 ().
-
-ULib provides such features as universal physics, user access lists, and much, much more!
-
-Visit our homepage at .
-
-You can talk to us on our forums at .
-
-Group: Author
-
-ULib is brought to you by..
-
-* Brett "Megiddo" Smith - Contact:
-* JamminR - Contact:
-* Stickly Man! - Contact:
-* MrPresident - Contact:
-
-Group: Requirements
-
-ULib requires a working copy of the latest garrysmod, and that's it!
-
-Group: Installation
-
-To install ULib, simply extract the files from the archive to your garrysmod/addons folder.
-When you've done this, you should have a file structure like this--
-/addons/ulib/lua/ULib/init.lua
-/addons/ulib/lua/ULib/server/util.lua
-/addons/ulib/lua/autorun/ulib_init.lua
-/addons/ulib/data/ULib/users.txt
-etc..
-
-Please note that installation is the same on dedicated servers.
-
-You absolutely, positively have to do a full server restart after installing the files. A simple map
-change will not cut it!
-
-Group: Usage
-
-Server admins do not "use" ULib, they simply enjoy the benefits it has to offer.
-After installing ULib correctly, scripts that take advantage of ULib will take care of the rest.
-Rest easy!
-
-Group: Changelog
-v2.60 - *(00/00/00)*
- * [ADD] ULib.ucl.getUserInfoFromID for getting user info from an ID.
- * [ADD] CAMI support.
- * [ADD] "noMount" parameter to file-related APIs.
- * [ADD] ULibGetUser(s)CustomKeyword hooks (Thanks, LuaTenshi).
- * [FIX] The usual random slew of Garry-breakages (Thanks, Fuzzik).
- * [FIX] An assumption regarding player authentication that led to a player's group being reset to user sometimes.
- * [FIX] Garry API change for ULib.findinDir (Thanks, ascentechit).
- * [FIX] Workaround for Garry's odd handling of bot UIDs (Thanks, BurgerLUA).
- * [FIX] Improved how well ULib files handle being autorefreshed.
- * [FIX] Exploitable console command could potentially cause errors and/or crashes.
- * [FIX] Exploit involving file system mounting.
- * [CHANGE] Hook system. It's much faster (CPU-wise) and a little easier to use now (Many thanks for input from darkjacky and Divran).
-
-v2.52 - *(03/09/15)*
- * [ADD] Admin parameter to ULib.unban for overriding purposes (Thanks for the idea, MStruntze).
- * [ADD] A list of players is presented when a target string for getUser matches more than one player (Thanks, RhapsodySL).
- * [FIX] ULib.ucl.registerAccess not allowing an access tag to be registered to no groups.
- * [FIX] Several incorrect file I/O calls (Thanks, Q4-Bi).
- * [FIX] Hook priority being messed up for parent hook when hooks are called recursively (Thanks, NoBrainCZ).
- * [FIX] Some fiddly-bits with group case-sensitivity (Thanks, BryanFlannery).
- * [CHANGE] hook.Run to match Garry's changes.
- * [CHANGE] ULib.HOOK_LOCALPLAYERREADY is now called on InitPostEntity instead of OnEntCreate.
-
-v2.51 - *(08/30/13)*
- * [FIX] ULib.ucl.userAllow not working on disconnected players (Thanks, JackYack13).
- * [FIX] Issue with setting groups with capitals in the group name (Thanks, FPtje!).
- * [FIX] Calling SetUserGroup not passing on information to clients (Thanks, Bo98).
- * [FIX] Garry's File I/O bugs by wrapping all his I/O.
- * [FIX] A user group lower casing that no longer belonged in the code (Thanks, iamalexer).
- * [FIX] Some issues with casing in ULib commands (Thanks, TheSpy7).
- * [FIX] Invalid time restrictions throwing an error (Thanks, Scratch).
- * [FIX] A problem with targeting in single player (Effected XGUI. Thanks, bender180).
- * [FIX] A problem with self-target restrictions breaking commands under certain conditions (Thanks, iSnipeu).
- * [FIX] A bug with being able to update replicated variables after running a listen server and then joining another server.
- * [REMOVED] Temp garry-patch for reading from the data directory that appears to be fixed now.
-
-v2.50 - *(01/27/13)*
- * [ADD] ULib.pcallError -- Does what global PCallError used to do before it was removed.
- * [ADD] Shows reasons to kicked person upon kick or ban (Thanks FPtje!).
- * [ADD] Operator to target only a specific group, ignoring inheritance ('#').
- * [ADD] Operator to target a specific id ('$').
- * [ADD] ULib.namedQueueFunctionCall to allow scripts to create their own queues separate of the main one.
- * [ADD] The ability to have aliased chat commands.
- * [FIX] The usual assortment of garry breakages.
- * [FIX] Changed away from our custom implementation of datastream to use Garry's new net library.
- * [FIX] Error with returning from invisibility when the player has no weapons (Thanks HellFox).
- * [FIX] "ULibCommandCalled" hook not being called on chat commands (Thanks Adult).
- * [CHANGE] Replicated cvars aren't actually relying on source replication anymore since Garry broke it (but they function the same).
- * [CHANGE] Lots of changes to match GM13.
- * [CHANGE] NumArg now allows for time string format.
- * [CHANGE] Hook library to match garry's. hook.isInHook was removed, no longer able to support with garry's changes.
-
-v2.42 - *(01/01/12)*
- * [FIX] Garry breakages.
-
-v2.41 - *(09/22/11)*
- * [ADD] ULib.ucl.getUserRegisteredID.
- * [ADD] ULib.stringTimeToSeconds (Thanks lavacano201014).
- * [FIX] Now properly kicks users who are banned while joining (Thanks Willdy).
-
-v2.40 - *(05/13/11)*
- * [ADD] ULib.tsayColor and Ulib.tsayError
- * [ADD] Replicated cvars. Nearly a direct port from the UPS implementation, with a few improvements.
- * [ADD] queueFunctionCall, ported from UPS.
- * [ADD] Player:GetUserGroup().
- * [ADD] Player:CheckGroup(), ability to check if a user in a group via inheritance.
- * [ADD] ULib.getPlyByUID().
- * [ADD] ULib.clientRPC(), send massive amounts of data to a client with ease.
- * [ADD] Upgrade script.
- * [ADD] hook.getCurrentHooks(), returns all currently processing hooks.
- * [ADD] hook.isInHook( name ), returns if you're in the specified hook or not.
- * [ADD] ULib.splitPort(), ULib.isValidSteamID(), ULib.isValidIP().
- * [ADD] ULib.backupFile().
- * [ADD] ULib.throwBadArg(), useful for argument checking.
- * [ADD] ULib.checkArg(), useful for argument checking.
- * [ADD] ULib.getPicker(), returns a user directly in front of another user.
- * [ADD] Utilities for table inheritance.
- * [ADD] New 'translation' command system that acts as a wrapper between a user and lua.
- * [ADD] New (and very different) command system.
- * [ADD] Lots of new hooks.
- * [ADD] Support for gatekeeper in ULib.kick.
- * [ADD] Our own optimized version of datastream, since garry's implementation is always broken.
- * [ADD] ULib.getAllReadyPlayers(), useful for sending usermessages to everyone.
- * [ADD] Basic spam detection system for ULib commands.
- * [FIX] ULib.filesInDir, was completely broken.
- * [FIX] The usual assortment of garry breakages.
- * [FIX] Some case-sensitive issues with the ULib add-command functions.
- * [FIX] Attempting to delete misc_registered.txt when it didn't exist.
- * [FIX] ULib.splitArgs now really properly handles escaped quotes and now unescapes them.
- * [FIX] Concommands created by ULib removing empty args.
- * [FIX] Overflowing command buffer when executing large config files.
- * [FIX] Optimized various functions to support up to 4000 bans (at least!).
- * [FIX] Bug where reloading ban information when a temp ban had less than a minute left made the ban permanent.
- * [FIX] Bug where ULib was reading in bad characters from source bans (Thanks edk141).
- * [CHANGE] Chat hooks are now a high priority due to other aggressive admin mods overriding ULX.
- * [CHANGE] Rewrote UCL entirely. The upgrade script should take care of bringing over old data into the new system.
- * [CHANGE] Added the ability to have access tags for each access string. These allow the accesses to have customizable behavior.
- * [CHANGE] Access tags now have comments attached to them (for the "what is it?" among us).
- * [CHANGE] Added lots of keywords (and keyword negation!) to ULib.getUsers and ULib.getUser.
- * [CHANGE] Invisible gets rid of shadows now.
- * [CHANGE] Garry's hook table spec is now more closely followed. (Thanks aVoN!)
- * [CHANGE] Moved the hook changes to the shared portion so clients can use the enhanced hooks as well.
- * [CHANGE] Updated the hooks file to match garry's recent changes. Also increased efficiency in hooks (faster than garry's!)
- * [CHANGE] Slaps now do a view punch as well.
- * [CHANGE] Allow nil access on ULib.addSayCommand so that you can create a command you always have access to.
- * [CHANGE] ULib.ucl.query always returns true when a nil access string is passed in.
- * [REMOVE] Ability to have passwords in UCL, don't think it worked anymore and it was never really used.
- * [REMOVE] Immunity no longer exists, since the new UCL has a much better method of doing the same thing.
- * [REMOVE] Some hooks due to garry breakage.
- * [REMOVE] Chat sounds on tsay, engine no longer makes sounds so neither should tsay.
-
-v2.30 - *(06/20/09)*
- * [FIX] Umsgs being sent too early in certain circumstances.
- * [FIX] Some issues garry introduced in the Jan09 update regarding player initialization.
- * [FIX] ParseKeyValues not unescaping backslashes.
- * [CHANGE] Rewrote splitArgs and parseKeyValues.
- * [CHANGE] misc_registered.txt now self-destructs on missing or empty groups.txt.
- * [CHANGE] All gamemode.Call refs to hook.Call, thanks aVoN!
- * [CHANGE] SetUserGroup now REMOVES any other groups and sets an exclusive group. Sorry about this, but this is for the better.
-
-v2.21 - *(06/08/08)*
- * [ADD] Support for client/server-side only modules.
- * [FIX] Bug in ULib.tsay that would incorrectly print to console if the target player was disconnecting.
- * [FIX] Makes sure that prop protectors don't take ownership of props using physgun reload while a prop is unmovable.
- * [CHANGE] ULib.getUsers now returns multiple users on an asterisk "*" when enable_keywords is true. "" can still be used. (Thanks Kyzer)
-
-
-v2.20 - *(01/26/08)*
- * [ADD] ULib now has three shiny new hooks to let you know about client initialization and a new hook to signal a player name change.
- * [FIX] A possible bug in the physics helpers.
- * [CHANGE] Various things to bring ULib into new engine compatibility.
- * [CHANGE] Removed all timers dealing with initialization and now rely on flags from the client. This makes the ULib initialization much more dependable.
- * [CHANGE] Converted all calls from ULib.consoleCommand( "exec ..." ) to ULib.execFile() to avoid running into the block on "exec" without our module.
- * [REMOVE] Removing the module for now, might re-appear in the next version
-
-
-v2.10 - *(09/23/07)*
- * [ADD] New hook library. Completely backwards compatible, but can now do priorities. (Server-side only)
- * [ADD] ULib.parseKeyValues, ULib.makeKeyValues
- * [ADD] ULib.getSpawnInfo, ULib.Spawn - Enhanced Spawn... will replace original health/armor when called if getSpawnInfo called first.
- * [ADD] READDED hexing system to get around garry's ConCommand() blocks. So much is now blocked that it's interferring with normal ULX operations.
- * [ADD] Our server module again. This time with only console-executing abilities. This is because garry has blocked much of what we need. Source is included.
- * [ADD] Custom ban list to store temp bans and additional ban info. Permanent bans are still stored in banned_user.cfg, and the two lists are synchronized.
- * [FIX] Can now query players from client side.
- * [FIX] An exploit in DisallowDelete() that allowed players to still remove the props
- * [FIX] Various initialization functions trying to access a disconnected player
- * [FIX] ULib.csay() sending umsgs to invalid players.
- * [FIX] UCL by clantag not working.
- * [CHANGE] Big changes in ucl.query() and concommand functions. Probably won't be backwards compatible.
- * [CHANGE] UCL now uses our new keyvalues functions. It should be backwards compatible with your old data, but we make no promises. If you're having trouble with it, try starting from scratch.
- * [CHANGE] ULib.tsay has a wait parameter to send on next frame
- * [CHANGE] subconcommands are now case insensitive
- * [CHANGE] Csay's now have fade.
- * [CHANGE] DisallowSpawning() now implements SpawnObject. For example, people can't sit and precache props while in the ulx jail.
- * [CHANGE] Say commands are now case insensitive and default to needing a space between command and arg (can flag to use old behavior though)
- * [CHANGE] ULib.ban, and ULib.kickban now accept additional information and pass data to ULib.addBan.
- * [CHANGE] Immunity is now an access string instead of a group
- * [CHANGE] Overcoming immunity is no longer bound to superadmins
- * [CHANGE] Increased performance of UCL.
- * [REMOVED] The vgui panels, derma is the vgui of choice now.
-
-v2.05 - *(06/19/07)*
- * [ADD] ply:SetUserGroup() -- Thanks aVoN!
- * [ADD] ply:DisallowVehicles( bool )
- * [FIX] A timer error in UCL, was messing up scoreboard sometimes.
- * [FIX] Security hole where exploiters could gain superadmin access
- * [CHANGE] You can assign allow/denies to the default user group, "user" now. (IE, allow guests to slap)
- * [CHANGE] DisallowSpawning now disallows tools that can spawn things.
- * [REMOVED] Old settings/users.txt stuff, handled by SetUserGroup now
-
-v2.04 - *(05/05/07)*
- * [ADD] ULib.isSandbox
- * [ADD] Player/ent hooks DisallowMoving, DisallowDeleting, DisallowSpawning, DisallowNoclip
- * [ADD] Some vgui libs (URoundButton, URoundMenu)
- * [FIX] Double printing in console.
- * [CHANGE] Implemented garry's "proper" way of including c-side files.
- * [CHANGE] Implemented client side UCL
- * [CHANGE] Now in addon format
- * [CHANGE] Slapping noclipped players will take them out of noclip to prevent them flying very far out of the world
- * [CHANGE] Improved the umsg send/receive functions
- * [REMOVED] Hexing system to get around garry's ConCommand() blocks. Very little is blocked now.
- * [REMOVED] Dll, MOTD functionality is handled by ULX now.
-
-v2.03 - *(01/10/07)*
- * [ADD] ULib module, has functions for motd, concommands, and downloading files. SOURCE CODE!
- * [FIX] Player slap after dead problem.
-
-v2.02 - *(01/07/07)*
- * [ADD] New system for giving files to clients. Strips comments and puts them in a separate folder.
- * [FIX] Autocompletes aren't handled so hackishly now. This should fix some occasional errors.
- * [FIX] Lots of general fixes.
-
-v2.01 - *(01/02/07)*
- * [FIX] Importing from garry's default user file.
- * [FIX] All users receiving "you do not have access" message.
-
-v2.0 - *(01/01/07)*
- * Initial version for GM10
-
-Group: Developers
-
-To all developers, I sincerely hope you enjoy what ULib has to offer!
-If you have any suggestions, comments, or complaints, please tell us at .
-
-If you want an overview of what's in ULib, please visit the documentation at .
-If you find any bugs, you can report them at .
-
-All ULib's functions are kept in the table "ULib" to prevent conflicts.
-
-Revisions are kept in the function/variable documentation. If you don't see revisions listed, it hasn't changed since v2.0
-
-If you write a script taking advantage of ULib, stick the init script inside ULib/modules. ULib will load your script after
-ULib loads, and will send it to and load it on clients as well.
-
-Some important quirks developers should know about --
-* autocomplete - You have to define the autocomplete on the client, so if you pass a string for autocomplete to ULib.concommand,
-it will assume you mean a client function. There's also a delay in the sending of these to the client.
-
-Group: Credits
-
-Thanks to JamminR, who is always there to offer help and advice to those who need it.
-
-Group: License
-
-This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License.
-To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to
-Creative Commons
-543 Howard Street
-5th Floor
-San Francisco, California 94105
-USA
+Title: ULib Readme
+
+*ULib v2.60 (released 00/00/00)*
+
+ULib is a developer library for GMod 10 ().
+
+ULib provides such features as universal physics, user access lists, and much, much more!
+
+Visit our homepage at .
+
+You can talk to us on our forums at .
+
+Group: Author
+
+ULib is brought to you by..
+
+* Brett "Megiddo" Smith - Contact:
+* JamminR - Contact:
+* Stickly Man! - Contact:
+* MrPresident - Contact:
+
+Group: Requirements
+
+ULib requires a working copy of the latest garrysmod, and that's it!
+
+Group: Installation
+
+To install ULib, simply extract the files from the archive to your garrysmod/addons folder.
+When you've done this, you should have a file structure like this--
+/addons/ulib/lua/ULib/init.lua
+/addons/ulib/lua/ULib/server/util.lua
+/addons/ulib/lua/autorun/ulib_init.lua
+/addons/ulib/data/ULib/users.txt
+etc..
+
+Please note that installation is the same on dedicated servers.
+
+You absolutely, positively have to do a full server restart after installing the files. A simple map
+change will not cut it!
+
+Group: Usage
+
+Server admins do not "use" ULib, they simply enjoy the benefits it has to offer.
+After installing ULib correctly, scripts that take advantage of ULib will take care of the rest.
+Rest easy!
+
+Group: Changelog
+v2.60 - *(00/00/00)*
+ * [ADD] ULib.ucl.getUserInfoFromID for getting user info from an ID.
+ * [ADD] CAMI support.
+ * [ADD] "noMount" parameter to file-related APIs.
+ * [ADD] ULibGetUser(s)CustomKeyword hooks (Thanks, LuaTenshi).
+ * [FIX] The usual random slew of Garry-breakages (Thanks, Fuzzik).
+ * [FIX] An assumption regarding player authentication that led to a player's group being reset to user sometimes.
+ * [FIX] Garry API change for ULib.findinDir (Thanks, ascentechit).
+ * [FIX] Workaround for Garry's odd handling of bot UIDs (Thanks, BurgerLUA).
+ * [FIX] Improved how well ULib files handle being autorefreshed.
+ * [FIX] Exploitable console command could potentially cause errors and/or crashes.
+ * [FIX] Exploit involving file system mounting.
+ * [CHANGE] Hook system. It's much faster (CPU-wise) and a little easier to use now (Many thanks for input from darkjacky and Divran).
+
+v2.52 - *(03/09/15)*
+ * [ADD] Admin parameter to ULib.unban for overriding purposes (Thanks for the idea, MStruntze).
+ * [ADD] A list of players is presented when a target string for getUser matches more than one player (Thanks, RhapsodySL).
+ * [FIX] ULib.ucl.registerAccess not allowing an access tag to be registered to no groups.
+ * [FIX] Several incorrect file I/O calls (Thanks, Q4-Bi).
+ * [FIX] Hook priority being messed up for parent hook when hooks are called recursively (Thanks, NoBrainCZ).
+ * [FIX] Some fiddly-bits with group case-sensitivity (Thanks, BryanFlannery).
+ * [CHANGE] hook.Run to match Garry's changes.
+ * [CHANGE] ULib.HOOK_LOCALPLAYERREADY is now called on InitPostEntity instead of OnEntCreate.
+
+v2.51 - *(08/30/13)*
+ * [FIX] ULib.ucl.userAllow not working on disconnected players (Thanks, JackYack13).
+ * [FIX] Issue with setting groups with capitals in the group name (Thanks, FPtje!).
+ * [FIX] Calling SetUserGroup not passing on information to clients (Thanks, Bo98).
+ * [FIX] Garry's File I/O bugs by wrapping all his I/O.
+ * [FIX] A user group lower casing that no longer belonged in the code (Thanks, iamalexer).
+ * [FIX] Some issues with casing in ULib commands (Thanks, TheSpy7).
+ * [FIX] Invalid time restrictions throwing an error (Thanks, Scratch).
+ * [FIX] A problem with targeting in single player (Effected XGUI. Thanks, bender180).
+ * [FIX] A problem with self-target restrictions breaking commands under certain conditions (Thanks, iSnipeu).
+ * [FIX] A bug with being able to update replicated variables after running a listen server and then joining another server.
+ * [REMOVED] Temp garry-patch for reading from the data directory that appears to be fixed now.
+
+v2.50 - *(01/27/13)*
+ * [ADD] ULib.pcallError -- Does what global PCallError used to do before it was removed.
+ * [ADD] Shows reasons to kicked person upon kick or ban (Thanks FPtje!).
+ * [ADD] Operator to target only a specific group, ignoring inheritance ('#').
+ * [ADD] Operator to target a specific id ('$').
+ * [ADD] ULib.namedQueueFunctionCall to allow scripts to create their own queues separate of the main one.
+ * [ADD] The ability to have aliased chat commands.
+ * [FIX] The usual assortment of garry breakages.
+ * [FIX] Changed away from our custom implementation of datastream to use Garry's new net library.
+ * [FIX] Error with returning from invisibility when the player has no weapons (Thanks HellFox).
+ * [FIX] "ULibCommandCalled" hook not being called on chat commands (Thanks Adult).
+ * [CHANGE] Replicated cvars aren't actually relying on source replication anymore since Garry broke it (but they function the same).
+ * [CHANGE] Lots of changes to match GM13.
+ * [CHANGE] NumArg now allows for time string format.
+ * [CHANGE] Hook library to match garry's. hook.isInHook was removed, no longer able to support with garry's changes.
+
+v2.42 - *(01/01/12)*
+ * [FIX] Garry breakages.
+
+v2.41 - *(09/22/11)*
+ * [ADD] ULib.ucl.getUserRegisteredID.
+ * [ADD] ULib.stringTimeToSeconds (Thanks lavacano201014).
+ * [FIX] Now properly kicks users who are banned while joining (Thanks Willdy).
+
+v2.40 - *(05/13/11)*
+ * [ADD] ULib.tsayColor and Ulib.tsayError
+ * [ADD] Replicated cvars. Nearly a direct port from the UPS implementation, with a few improvements.
+ * [ADD] queueFunctionCall, ported from UPS.
+ * [ADD] Player:GetUserGroup().
+ * [ADD] Player:CheckGroup(), ability to check if a user in a group via inheritance.
+ * [ADD] ULib.getPlyByUID().
+ * [ADD] ULib.clientRPC(), send massive amounts of data to a client with ease.
+ * [ADD] Upgrade script.
+ * [ADD] hook.getCurrentHooks(), returns all currently processing hooks.
+ * [ADD] hook.isInHook( name ), returns if you're in the specified hook or not.
+ * [ADD] ULib.splitPort(), ULib.isValidSteamID(), ULib.isValidIP().
+ * [ADD] ULib.backupFile().
+ * [ADD] ULib.throwBadArg(), useful for argument checking.
+ * [ADD] ULib.checkArg(), useful for argument checking.
+ * [ADD] ULib.getPicker(), returns a user directly in front of another user.
+ * [ADD] Utilities for table inheritance.
+ * [ADD] New 'translation' command system that acts as a wrapper between a user and lua.
+ * [ADD] New (and very different) command system.
+ * [ADD] Lots of new hooks.
+ * [ADD] Support for gatekeeper in ULib.kick.
+ * [ADD] Our own optimized version of datastream, since garry's implementation is always broken.
+ * [ADD] ULib.getAllReadyPlayers(), useful for sending usermessages to everyone.
+ * [ADD] Basic spam detection system for ULib commands.
+ * [FIX] ULib.filesInDir, was completely broken.
+ * [FIX] The usual assortment of garry breakages.
+ * [FIX] Some case-sensitive issues with the ULib add-command functions.
+ * [FIX] Attempting to delete misc_registered.txt when it didn't exist.
+ * [FIX] ULib.splitArgs now really properly handles escaped quotes and now unescapes them.
+ * [FIX] Concommands created by ULib removing empty args.
+ * [FIX] Overflowing command buffer when executing large config files.
+ * [FIX] Optimized various functions to support up to 4000 bans (at least!).
+ * [FIX] Bug where reloading ban information when a temp ban had less than a minute left made the ban permanent.
+ * [FIX] Bug where ULib was reading in bad characters from source bans (Thanks edk141).
+ * [CHANGE] Chat hooks are now a high priority due to other aggressive admin mods overriding ULX.
+ * [CHANGE] Rewrote UCL entirely. The upgrade script should take care of bringing over old data into the new system.
+ * [CHANGE] Added the ability to have access tags for each access string. These allow the accesses to have customizable behavior.
+ * [CHANGE] Access tags now have comments attached to them (for the "what is it?" among us).
+ * [CHANGE] Added lots of keywords (and keyword negation!) to ULib.getUsers and ULib.getUser.
+ * [CHANGE] Invisible gets rid of shadows now.
+ * [CHANGE] Garry's hook table spec is now more closely followed. (Thanks aVoN!)
+ * [CHANGE] Moved the hook changes to the shared portion so clients can use the enhanced hooks as well.
+ * [CHANGE] Updated the hooks file to match garry's recent changes. Also increased efficiency in hooks (faster than garry's!)
+ * [CHANGE] Slaps now do a view punch as well.
+ * [CHANGE] Allow nil access on ULib.addSayCommand so that you can create a command you always have access to.
+ * [CHANGE] ULib.ucl.query always returns true when a nil access string is passed in.
+ * [REMOVE] Ability to have passwords in UCL, don't think it worked anymore and it was never really used.
+ * [REMOVE] Immunity no longer exists, since the new UCL has a much better method of doing the same thing.
+ * [REMOVE] Some hooks due to garry breakage.
+ * [REMOVE] Chat sounds on tsay, engine no longer makes sounds so neither should tsay.
+
+v2.30 - *(06/20/09)*
+ * [FIX] Umsgs being sent too early in certain circumstances.
+ * [FIX] Some issues garry introduced in the Jan09 update regarding player initialization.
+ * [FIX] ParseKeyValues not unescaping backslashes.
+ * [CHANGE] Rewrote splitArgs and parseKeyValues.
+ * [CHANGE] misc_registered.txt now self-destructs on missing or empty groups.txt.
+ * [CHANGE] All gamemode.Call refs to hook.Call, thanks aVoN!
+ * [CHANGE] SetUserGroup now REMOVES any other groups and sets an exclusive group. Sorry about this, but this is for the better.
+
+v2.21 - *(06/08/08)*
+ * [ADD] Support for client/server-side only modules.
+ * [FIX] Bug in ULib.tsay that would incorrectly print to console if the target player was disconnecting.
+ * [FIX] Makes sure that prop protectors don't take ownership of props using physgun reload while a prop is unmovable.
+ * [CHANGE] ULib.getUsers now returns multiple users on an asterisk "*" when enable_keywords is true. "" can still be used. (Thanks Kyzer)
+
+
+v2.20 - *(01/26/08)*
+ * [ADD] ULib now has three shiny new hooks to let you know about client initialization and a new hook to signal a player name change.
+ * [FIX] A possible bug in the physics helpers.
+ * [CHANGE] Various things to bring ULib into new engine compatibility.
+ * [CHANGE] Removed all timers dealing with initialization and now rely on flags from the client. This makes the ULib initialization much more dependable.
+ * [CHANGE] Converted all calls from ULib.consoleCommand( "exec ..." ) to ULib.execFile() to avoid running into the block on "exec" without our module.
+ * [REMOVE] Removing the module for now, might re-appear in the next version
+
+
+v2.10 - *(09/23/07)*
+ * [ADD] New hook library. Completely backwards compatible, but can now do priorities. (Server-side only)
+ * [ADD] ULib.parseKeyValues, ULib.makeKeyValues
+ * [ADD] ULib.getSpawnInfo, ULib.Spawn - Enhanced Spawn... will replace original health/armor when called if getSpawnInfo called first.
+ * [ADD] READDED hexing system to get around garry's ConCommand() blocks. So much is now blocked that it's interferring with normal ULX operations.
+ * [ADD] Our server module again. This time with only console-executing abilities. This is because garry has blocked much of what we need. Source is included.
+ * [ADD] Custom ban list to store temp bans and additional ban info. Permanent bans are still stored in banned_user.cfg, and the two lists are synchronized.
+ * [FIX] Can now query players from client side.
+ * [FIX] An exploit in DisallowDelete() that allowed players to still remove the props
+ * [FIX] Various initialization functions trying to access a disconnected player
+ * [FIX] ULib.csay() sending umsgs to invalid players.
+ * [FIX] UCL by clantag not working.
+ * [CHANGE] Big changes in ucl.query() and concommand functions. Probably won't be backwards compatible.
+ * [CHANGE] UCL now uses our new keyvalues functions. It should be backwards compatible with your old data, but we make no promises. If you're having trouble with it, try starting from scratch.
+ * [CHANGE] ULib.tsay has a wait parameter to send on next frame
+ * [CHANGE] subconcommands are now case insensitive
+ * [CHANGE] Csay's now have fade.
+ * [CHANGE] DisallowSpawning() now implements SpawnObject. For example, people can't sit and precache props while in the ulx jail.
+ * [CHANGE] Say commands are now case insensitive and default to needing a space between command and arg (can flag to use old behavior though)
+ * [CHANGE] ULib.ban, and ULib.kickban now accept additional information and pass data to ULib.addBan.
+ * [CHANGE] Immunity is now an access string instead of a group
+ * [CHANGE] Overcoming immunity is no longer bound to superadmins
+ * [CHANGE] Increased performance of UCL.
+ * [REMOVED] The vgui panels, derma is the vgui of choice now.
+
+v2.05 - *(06/19/07)*
+ * [ADD] ply:SetUserGroup() -- Thanks aVoN!
+ * [ADD] ply:DisallowVehicles( bool )
+ * [FIX] A timer error in UCL, was messing up scoreboard sometimes.
+ * [FIX] Security hole where exploiters could gain superadmin access
+ * [CHANGE] You can assign allow/denies to the default user group, "user" now. (IE, allow guests to slap)
+ * [CHANGE] DisallowSpawning now disallows tools that can spawn things.
+ * [REMOVED] Old settings/users.txt stuff, handled by SetUserGroup now
+
+v2.04 - *(05/05/07)*
+ * [ADD] ULib.isSandbox
+ * [ADD] Player/ent hooks DisallowMoving, DisallowDeleting, DisallowSpawning, DisallowNoclip
+ * [ADD] Some vgui libs (URoundButton, URoundMenu)
+ * [FIX] Double printing in console.
+ * [CHANGE] Implemented garry's "proper" way of including c-side files.
+ * [CHANGE] Implemented client side UCL
+ * [CHANGE] Now in addon format
+ * [CHANGE] Slapping noclipped players will take them out of noclip to prevent them flying very far out of the world
+ * [CHANGE] Improved the umsg send/receive functions
+ * [REMOVED] Hexing system to get around garry's ConCommand() blocks. Very little is blocked now.
+ * [REMOVED] Dll, MOTD functionality is handled by ULX now.
+
+v2.03 - *(01/10/07)*
+ * [ADD] ULib module, has functions for motd, concommands, and downloading files. SOURCE CODE!
+ * [FIX] Player slap after dead problem.
+
+v2.02 - *(01/07/07)*
+ * [ADD] New system for giving files to clients. Strips comments and puts them in a separate folder.
+ * [FIX] Autocompletes aren't handled so hackishly now. This should fix some occasional errors.
+ * [FIX] Lots of general fixes.
+
+v2.01 - *(01/02/07)*
+ * [FIX] Importing from garry's default user file.
+ * [FIX] All users receiving "you do not have access" message.
+
+v2.0 - *(01/01/07)*
+ * Initial version for GM10
+
+Group: Developers
+
+To all developers, I sincerely hope you enjoy what ULib has to offer!
+If you have any suggestions, comments, or complaints, please tell us at .
+
+If you want an overview of what's in ULib, please visit the documentation at .
+If you find any bugs, you can report them at .
+
+All ULib's functions are kept in the table "ULib" to prevent conflicts.
+
+Revisions are kept in the function/variable documentation. If you don't see revisions listed, it hasn't changed since v2.0
+
+If you write a script taking advantage of ULib, stick the init script inside ULib/modules. ULib will load your script after
+ULib loads, and will send it to and load it on clients as well.
+
+Some important quirks developers should know about --
+* autocomplete - You have to define the autocomplete on the client, so if you pass a string for autocomplete to ULib.concommand,
+it will assume you mean a client function. There's also a delay in the sending of these to the client.
+
+Group: Credits
+
+Thanks to JamminR, who is always there to offer help and advice to those who need it.
+
+Group: License
+
+This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License.
+To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to
+Creative Commons
+543 Howard Street
+5th Floor
+San Francisco, California 94105
+USA
diff --git a/addon.txt b/addon.txt
index e282a31..dfe831d 100644
--- a/addon.txt
+++ b/addon.txt
@@ -1,11 +1,11 @@
-"AddonInfo"
-{
- "name" "ULib"
- "version" "2.60"
- "up_date" "00/00/00"
- "author_name" "Team Ulysses"
- "author_email" "teamulysses@ulyssesmod.net"
- "author_url" "http://www.ulyssesmod.net/"
- "info" "Lua Library"
- "override" "0"
-}
+"AddonInfo"
+{
+ "name" "ULib"
+ "version" "2.60"
+ "up_date" "00/00/00"
+ "author_name" "Team Ulysses"
+ "author_email" "teamulysses@ulyssesmod.net"
+ "author_url" "http://www.ulyssesmod.net/"
+ "info" "Lua Library"
+ "override" "0"
+}
diff --git a/data/ulib/format.txt b/data/ulib/format.txt
index 379a947..1c38d93 100644
--- a/data/ulib/format.txt
+++ b/data/ulib/format.txt
@@ -1,39 +1,39 @@
-Format of admin account in users.txt--
-""
-{
- "group" "superadmin"
- "allow"
- {
- "ulx kick"
- "ulx ban"
- }
- "deny"
- {
- "ulx cexec"
- }
-}
-
-Example of a superadmin:
-"STEAM_0:1:123456"
-{
- "group" "superadmin"
- "allow"
- {
- }
- "deny"
- {
- }
-}
-
-
-
-Format of group that gets the same allows as a superadmin in groups.txt--
-""
-{
- "allow"
- {
- "ulx kick"
- "ulx ban"
- }
- "inherit_from" "superadmin"
+Format of admin account in users.txt--
+""
+{
+ "group" "superadmin"
+ "allow"
+ {
+ "ulx kick"
+ "ulx ban"
+ }
+ "deny"
+ {
+ "ulx cexec"
+ }
+}
+
+Example of a superadmin:
+"STEAM_0:1:123456"
+{
+ "group" "superadmin"
+ "allow"
+ {
+ }
+ "deny"
+ {
+ }
+}
+
+
+
+Format of group that gets the same allows as a superadmin in groups.txt--
+""
+{
+ "allow"
+ {
+ "ulx kick"
+ "ulx ban"
+ }
+ "inherit_from" "superadmin"
}
\ No newline at end of file
diff --git a/data/ulib/groups.txt b/data/ulib/groups.txt
index 0f78089..98d4bc5 100644
--- a/data/ulib/groups.txt
+++ b/data/ulib/groups.txt
@@ -1,35 +1,35 @@
-//ATTENTION! This is a default file. DO NOT EDIT THIS FILE!!!
-//Instead, edit the version in /data/ULib/groups.txt if it exists. If it doesn't, copy this file over to create it.
-//IF YOU MAKE CHANGES TO THIS FILE THEY WILL BE ERASED!
-
-"operator"
-{
- "allow"
- {
- }
- "can_target" "!%admin"
-}
-
-"admin"
-{
- "allow"
- {
- }
- "inherit_from" "operator"
- "can_target" "!%superadmin"
-}
-
-"superadmin"
-{
- "allow"
- {
- }
- "inherit_from" "admin"
-}
-
-"user"
-{
- "allow"
- {
- }
-}
+//ATTENTION! This is a default file. DO NOT EDIT THIS FILE!!!
+//Instead, edit the version in /data/ULib/groups.txt if it exists. If it doesn't, copy this file over to create it.
+//IF YOU MAKE CHANGES TO THIS FILE THEY WILL BE ERASED!
+
+"operator"
+{
+ "allow"
+ {
+ }
+ "can_target" "!%admin"
+}
+
+"admin"
+{
+ "allow"
+ {
+ }
+ "inherit_from" "operator"
+ "can_target" "!%superadmin"
+}
+
+"superadmin"
+{
+ "allow"
+ {
+ }
+ "inherit_from" "admin"
+}
+
+"user"
+{
+ "allow"
+ {
+ }
+}
diff --git a/data/ulib/users.txt b/data/ulib/users.txt
index 09b19e2..07faf13 100644
--- a/data/ulib/users.txt
+++ b/data/ulib/users.txt
@@ -1,3 +1,3 @@
-//ATTENTION! This is a default file. DO NOT EDIT THIS FILE!!!
-//Instead, edit the version in /data/ULib/users.txt if it exists. If it doesn't, copy this file over to create it.
-//IF YOU MAKE CHANGES TO THIS FILE THEY WILL BE ERASED!
+//ATTENTION! This is a default file. DO NOT EDIT THIS FILE!!!
+//Instead, edit the version in /data/ULib/users.txt if it exists. If it doesn't, copy this file over to create it.
+//IF YOU MAKE CHANGES TO THIS FILE THEY WILL BE ERASED!
diff --git a/lua/autorun/ulib_init.lua b/lua/autorun/ulib_init.lua
index e96041a..4779650 100644
--- a/lua/autorun/ulib_init.lua
+++ b/lua/autorun/ulib_init.lua
@@ -1,6 +1,6 @@
--- Short and sweet
-if SERVER then
- include( "ulib/init.lua" )
-else
- include( "ulib/cl_init.lua" )
-end
+-- Short and sweet
+if SERVER then
+ include( "ulib/init.lua" )
+else
+ include( "ulib/cl_init.lua" )
+end
diff --git a/lua/ulib/cl_init.lua b/lua/ulib/cl_init.lua
index ef4a96c..a73a980 100644
--- a/lua/ulib/cl_init.lua
+++ b/lua/ulib/cl_init.lua
@@ -1,63 +1,63 @@
-ULib = ULib or {} -- Init table
-
-include( "ulib/shared/defines.lua" )
-include( "ulib/shared/misc.lua" )
-include( "ulib/shared/util.lua" )
-include( "ulib/shared/hook.lua" )
-include( "ulib/shared/tables.lua" )
-include( "ulib/client/commands.lua" )
-include( "ulib/shared/messages.lua" )
-include( "ulib/shared/player.lua" )
-include( "ulib/client/cl_util.lua" )
-include( "ulib/client/draw.lua" )
-include( "ulib/shared/commands.lua" )
-include( "ulib/shared/sh_ucl.lua" )
-include( "ulib/shared/cami_global.lua" )
-include( "ulib/shared/cami_ulib.lua" )
-
-Msg( string.format( "You are running ULib version %.2f.\n", ULib.VERSION ) )
-
---Shared modules
-local files = file.Find( "ulib/modules/*.lua", "LUA" )
-if #files > 0 then
- for _, file in ipairs( files ) do
- Msg( "[ULIB] Loading SHARED module: " .. file .. "\n" )
- include( "ulib/modules/" .. file )
- end
-end
-
---Client modules
-local files = file.Find( "ulib/modules/client/*.lua", "LUA" )
-if #files > 0 then
- for _, file in ipairs( files ) do
- Msg( "[ULIB] Loading CLIENT module: " .. file .. "\n" )
- include( "ulib/modules/client/" .. file )
- end
-end
-
-local needs_auth = {}
-
-local function onEntCreated( ent )
- if ent:IsPlayer() and needs_auth[ ent:UserID() ] then
- hook.Call( ULib.HOOK_UCLAUTH, _, ent ) -- Because otherwise the server might call this before the player is created
- needs_auth[ ent:UserID() ] = nil
- end
-end
-hook.Add( "OnEntityCreated", "ULibPlayerAuthCheck", onEntCreated, HOOK_MONITOR_HIGH ) -- Listen for player creations
-
-local function onInitPostEntity()
- if LocalPlayer():IsValid() then
- hook.Call( ULib.HOOK_LOCALPLAYERREADY, _, LocalPlayer() )
- RunConsoleCommand( "ulib_cl_ready" )
- end
-end
-hook.Add( "InitPostEntity", "ULibLocalPlayerReady", onInitPostEntity, HOOK_MONITOR_HIGH ) -- Flag server when LocalPlayer() should be valid
-
--- We're trying to make sure that the player auths after the player object is created, this function is part of that check
-function authPlayerIfReady( ply, userid )
- if ply and ply:IsValid() then
- hook.Call( ULib.HOOK_UCLAUTH, _, ply ) -- Call hook
- else
- needs_auth[ userid ] = true
- end
-end
+ULib = ULib or {} -- Init table
+
+include( "ulib/shared/defines.lua" )
+include( "ulib/shared/misc.lua" )
+include( "ulib/shared/util.lua" )
+include( "ulib/shared/hook.lua" )
+include( "ulib/shared/tables.lua" )
+include( "ulib/client/commands.lua" )
+include( "ulib/shared/messages.lua" )
+include( "ulib/shared/player.lua" )
+include( "ulib/client/cl_util.lua" )
+include( "ulib/client/draw.lua" )
+include( "ulib/shared/commands.lua" )
+include( "ulib/shared/sh_ucl.lua" )
+include( "ulib/shared/cami_global.lua" )
+include( "ulib/shared/cami_ulib.lua" )
+
+Msg( string.format( "You are running ULib version %.2f.\n", ULib.VERSION ) )
+
+--Shared modules
+local files = file.Find( "ulib/modules/*.lua", "LUA" )
+if #files > 0 then
+ for _, file in ipairs( files ) do
+ Msg( "[ULIB] Loading SHARED module: " .. file .. "\n" )
+ include( "ulib/modules/" .. file )
+ end
+end
+
+--Client modules
+local files = file.Find( "ulib/modules/client/*.lua", "LUA" )
+if #files > 0 then
+ for _, file in ipairs( files ) do
+ Msg( "[ULIB] Loading CLIENT module: " .. file .. "\n" )
+ include( "ulib/modules/client/" .. file )
+ end
+end
+
+local needs_auth = {}
+
+local function onEntCreated( ent )
+ if ent:IsPlayer() and needs_auth[ ent:UserID() ] then
+ hook.Call( ULib.HOOK_UCLAUTH, _, ent ) -- Because otherwise the server might call this before the player is created
+ needs_auth[ ent:UserID() ] = nil
+ end
+end
+hook.Add( "OnEntityCreated", "ULibPlayerAuthCheck", onEntCreated, HOOK_MONITOR_HIGH ) -- Listen for player creations
+
+local function onInitPostEntity()
+ if LocalPlayer():IsValid() then
+ hook.Call( ULib.HOOK_LOCALPLAYERREADY, _, LocalPlayer() )
+ RunConsoleCommand( "ulib_cl_ready" )
+ end
+end
+hook.Add( "InitPostEntity", "ULibLocalPlayerReady", onInitPostEntity, HOOK_MONITOR_HIGH ) -- Flag server when LocalPlayer() should be valid
+
+-- We're trying to make sure that the player auths after the player object is created, this function is part of that check
+function authPlayerIfReady( ply, userid )
+ if ply and ply:IsValid() then
+ hook.Call( ULib.HOOK_UCLAUTH, _, ply ) -- Call hook
+ else
+ needs_auth[ userid ] = true
+ end
+end
diff --git a/lua/ulib/client/cl_util.lua b/lua/ulib/client/cl_util.lua
index dd96d2d..04a2d30 100644
--- a/lua/ulib/client/cl_util.lua
+++ b/lua/ulib/client/cl_util.lua
@@ -1,156 +1,156 @@
---[[
- Title: Utilities
-
- Some client-side utilties
-]]
-
-local function ULibRPC()
- local fn_string = net.ReadString()
- local args = net.ReadTable()
- local fn = ULib.findVar( fn_string )
- if type( fn ) ~= "function" then return error( "Received bad RPC, invalid function (" .. tostring( fn_string ) .. ")!" ) end
-
- -- Since the table length operator can't always be trusted if there are holes in it, find the length by ourself
- local max = 0
- for k, v in pairs( args ) do
- local n = tonumber( k )
- if n and n > max then
- max = n
- end
- end
-
- fn( unpack( args, 1, max ) )
-end
-net.Receive( "URPC", ULibRPC )
-
---[[
- Function: umsgRcv
-
- Receive a umsg sent by ULib.umsgSend
-
- Parameters:
-
- um - The user message object
-
- Returns:
-
- The variable from the umsg.
-]]
-function ULib.umsgRcv( um, control )
- local tv = control or um:ReadChar()
-
- local ret -- Our return value
- if tv == ULib.TYPE_STRING then
- ret = um:ReadString()
- elseif tv == ULib.TYPE_FLOAT then
- ret = um:ReadFloat()
- elseif tv == ULib.TYPE_SHORT then
- ret = um:ReadShort()
- elseif tv == ULib.TYPE_LONG then
- ret = um:ReadLong()
- elseif tv == ULib.TYPE_BOOLEAN then
- ret = um:ReadBool()
- elseif tv == ULib.TYPE_ENTITY then
- ret = um:ReadEntity()
- elseif tv == ULib.TYPE_VECTOR then
- ret = um:ReadVector()
- elseif tv == ULib.TYPE_ANGLE then
- ret = um:ReadAngle()
- elseif tv == ULib.TYPE_CHAR then
- ret = um:ReadChar()
- elseif tv == ULib.TYPE_TABLE_BEGIN then
- ret = {}
- while true do -- Yes an infite loop. We have a break inside.
- local key = ULib.umsgRcv( um )
- if key == nil then break end -- Here's our break
- ret[ key ] = ULib.umsgRcv( um )
- end
- elseif tv == ULib.TYPE_TABLE_END then
- return nil
- elseif tv == ULib.TYPE_NIL then
- return nil
- else
- ULib.error( "Unknown type passed to umsgRcv - " .. tv )
- end
-
- return ret
-end
-
--- This will play sounds client side
-local function rcvSound( um )
- local str = um:ReadString()
- if not ULib.fileExists( "sound/" .. str ) then
- Msg( "[LC ULib ERROR] Received invalid sound\n" )
- return
- end
-
- if LocalPlayer():IsValid() then
- LocalPlayer():EmitSound( Sound( str ) )
- end
-end
-usermessage.Hook( "ulib_sound", rcvSound )
-
-local cvarinfo = {} -- Stores the client cvar object indexed by name of the server cvar
-local reversecvar = {} -- Stores the name of server cvars indexed by the client cvar
-
--- When our client side cvar is changed, notify the server to change it's cvar too.
-local function clCvarChanged( cl_cvar, oldvalue, newvalue )
- if not reversecvar[ cl_cvar ] then -- Error
- return
- elseif reversecvar[ cl_cvar ].ignore then -- ignore
- reversecvar[ cl_cvar ].ignore = nil
- return
- end
-
- local sv_cvar = reversecvar[ cl_cvar ].sv_cvar
- RunConsoleCommand( "ulib_update_cvar", sv_cvar, newvalue )
-end
-
--- This is the counterpart to . See that function for more info. We also add callbacks from here.
-local function readCvar( um )
- local sv_cvar = um:ReadString()
- local cl_cvar = um:ReadString()
- local default_value = um:ReadString()
- local current_value = um:ReadString()
-
- cvarinfo[ sv_cvar ] = GetConVar( cl_cvar ) or CreateClientConVar( cl_cvar, default_value, false, false ) -- Make sure it's created one way or another (second case is most common)
- reversecvar[ cl_cvar ] = { sv_cvar=sv_cvar }
-
- ULib.queueFunctionCall( function() -- Queued to ensure we don't overload the client console
- hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, cl_cvar, nil, nil, current_value )
- if cvarinfo[ sv_cvar ]:GetString() ~= current_value then
- reversecvar[ cl_cvar ].ignore = true -- Flag so hook doesn't do anything. Flag is removed at hook.
- RunConsoleCommand( cl_cvar, current_value )
- end
- end )
-
- cvars.AddChangeCallback( cl_cvar, clCvarChanged )
-end
-usermessage.Hook( "ulib_repWriteCvar", readCvar )
-
--- This is called when they've attempted to change a cvar they don't have access to.
-local function changeCvar( um )
- local ply = um:ReadEntity()
- local cl_cvar = um:ReadString()
- local oldvalue = um:ReadString()
- local newvalue = um:ReadString()
- local changed = oldvalue ~= newvalue
-
- if not reversecvar[ cl_cvar ] then -- Error!
- return
- end
-
- local sv_cvar = reversecvar[ cl_cvar ].sv_cvar
-
- ULib.queueFunctionCall( function() -- Queued so we won't overload the client console and so that changes are always going to be called via the hook AFTER the initial hook is called
- if changed then
- hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, cl_cvar, ply, oldvalue, newvalue )
- end
-
- if GetConVarString( cl_cvar ) ~= newvalue then
- reversecvar[ cl_cvar ].ignore = true -- Flag so hook doesn't do anything. Flag is removed at hook.
- RunConsoleCommand( cl_cvar, newvalue)
- end
- end )
-end
-usermessage.Hook( "ulib_repChangeCvar", changeCvar )
+--[[
+ Title: Utilities
+
+ Some client-side utilties
+]]
+
+local function ULibRPC()
+ local fn_string = net.ReadString()
+ local args = net.ReadTable()
+ local fn = ULib.findVar( fn_string )
+ if type( fn ) ~= "function" then return error( "Received bad RPC, invalid function (" .. tostring( fn_string ) .. ")!" ) end
+
+ -- Since the table length operator can't always be trusted if there are holes in it, find the length by ourself
+ local max = 0
+ for k, v in pairs( args ) do
+ local n = tonumber( k )
+ if n and n > max then
+ max = n
+ end
+ end
+
+ fn( unpack( args, 1, max ) )
+end
+net.Receive( "URPC", ULibRPC )
+
+--[[
+ Function: umsgRcv
+
+ Receive a umsg sent by ULib.umsgSend
+
+ Parameters:
+
+ um - The user message object
+
+ Returns:
+
+ The variable from the umsg.
+]]
+function ULib.umsgRcv( um, control )
+ local tv = control or um:ReadChar()
+
+ local ret -- Our return value
+ if tv == ULib.TYPE_STRING then
+ ret = um:ReadString()
+ elseif tv == ULib.TYPE_FLOAT then
+ ret = um:ReadFloat()
+ elseif tv == ULib.TYPE_SHORT then
+ ret = um:ReadShort()
+ elseif tv == ULib.TYPE_LONG then
+ ret = um:ReadLong()
+ elseif tv == ULib.TYPE_BOOLEAN then
+ ret = um:ReadBool()
+ elseif tv == ULib.TYPE_ENTITY then
+ ret = um:ReadEntity()
+ elseif tv == ULib.TYPE_VECTOR then
+ ret = um:ReadVector()
+ elseif tv == ULib.TYPE_ANGLE then
+ ret = um:ReadAngle()
+ elseif tv == ULib.TYPE_CHAR then
+ ret = um:ReadChar()
+ elseif tv == ULib.TYPE_TABLE_BEGIN then
+ ret = {}
+ while true do -- Yes an infite loop. We have a break inside.
+ local key = ULib.umsgRcv( um )
+ if key == nil then break end -- Here's our break
+ ret[ key ] = ULib.umsgRcv( um )
+ end
+ elseif tv == ULib.TYPE_TABLE_END then
+ return nil
+ elseif tv == ULib.TYPE_NIL then
+ return nil
+ else
+ ULib.error( "Unknown type passed to umsgRcv - " .. tv )
+ end
+
+ return ret
+end
+
+-- This will play sounds client side
+local function rcvSound( um )
+ local str = um:ReadString()
+ if not ULib.fileExists( "sound/" .. str ) then
+ Msg( "[LC ULib ERROR] Received invalid sound\n" )
+ return
+ end
+
+ if LocalPlayer():IsValid() then
+ LocalPlayer():EmitSound( Sound( str ) )
+ end
+end
+usermessage.Hook( "ulib_sound", rcvSound )
+
+local cvarinfo = {} -- Stores the client cvar object indexed by name of the server cvar
+local reversecvar = {} -- Stores the name of server cvars indexed by the client cvar
+
+-- When our client side cvar is changed, notify the server to change it's cvar too.
+local function clCvarChanged( cl_cvar, oldvalue, newvalue )
+ if not reversecvar[ cl_cvar ] then -- Error
+ return
+ elseif reversecvar[ cl_cvar ].ignore then -- ignore
+ reversecvar[ cl_cvar ].ignore = nil
+ return
+ end
+
+ local sv_cvar = reversecvar[ cl_cvar ].sv_cvar
+ RunConsoleCommand( "ulib_update_cvar", sv_cvar, newvalue )
+end
+
+-- This is the counterpart to . See that function for more info. We also add callbacks from here.
+local function readCvar( um )
+ local sv_cvar = um:ReadString()
+ local cl_cvar = um:ReadString()
+ local default_value = um:ReadString()
+ local current_value = um:ReadString()
+
+ cvarinfo[ sv_cvar ] = GetConVar( cl_cvar ) or CreateClientConVar( cl_cvar, default_value, false, false ) -- Make sure it's created one way or another (second case is most common)
+ reversecvar[ cl_cvar ] = { sv_cvar=sv_cvar }
+
+ ULib.queueFunctionCall( function() -- Queued to ensure we don't overload the client console
+ hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, cl_cvar, nil, nil, current_value )
+ if cvarinfo[ sv_cvar ]:GetString() ~= current_value then
+ reversecvar[ cl_cvar ].ignore = true -- Flag so hook doesn't do anything. Flag is removed at hook.
+ RunConsoleCommand( cl_cvar, current_value )
+ end
+ end )
+
+ cvars.AddChangeCallback( cl_cvar, clCvarChanged )
+end
+usermessage.Hook( "ulib_repWriteCvar", readCvar )
+
+-- This is called when they've attempted to change a cvar they don't have access to.
+local function changeCvar( um )
+ local ply = um:ReadEntity()
+ local cl_cvar = um:ReadString()
+ local oldvalue = um:ReadString()
+ local newvalue = um:ReadString()
+ local changed = oldvalue ~= newvalue
+
+ if not reversecvar[ cl_cvar ] then -- Error!
+ return
+ end
+
+ local sv_cvar = reversecvar[ cl_cvar ].sv_cvar
+
+ ULib.queueFunctionCall( function() -- Queued so we won't overload the client console and so that changes are always going to be called via the hook AFTER the initial hook is called
+ if changed then
+ hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, cl_cvar, ply, oldvalue, newvalue )
+ end
+
+ if GetConVarString( cl_cvar ) ~= newvalue then
+ reversecvar[ cl_cvar ].ignore = true -- Flag so hook doesn't do anything. Flag is removed at hook.
+ RunConsoleCommand( cl_cvar, newvalue)
+ end
+ end )
+end
+usermessage.Hook( "ulib_repChangeCvar", changeCvar )
diff --git a/lua/ulib/client/draw.lua b/lua/ulib/client/draw.lua
index cc688ee..41336b7 100644
--- a/lua/ulib/client/draw.lua
+++ b/lua/ulib/client/draw.lua
@@ -1,54 +1,54 @@
---[[
- Title: Draw
-
- Our client-side draw functions
-]]
-
---[[
- Function: csayDraw
-
- Draws a csay text on the screen.
-
- Parameters:
-
- msg - The message to draw.
- color - *(Optional, defaults to 255, 255, 255, 255)* The color of the text
- duration - *(Optional, defaults to 5)* The length of the text
- fade - *(Optional, defaults to 0.5)* The length of fade time
-
- Revisions:
-
- v2.10 - Added fade parameter
-]]
-function ULib.csayDraw( msg, color, duration, fade )
- color = color or Color( 255, 255, 255, 255 )
- duration = duration or 5
- fade = fade or 0.5
- local start = CurTime()
-
- local function drawToScreen()
- local alpha = 255
- local dtime = CurTime() - start
-
- if dtime > duration then -- Our time has come :'(
- hook.Remove( "HUDPaint", "CSayHelperDraw" )
- return
- end
-
- if fade - dtime > 0 then -- beginning fade
- alpha = (fade - dtime) / fade -- 0 to 1
- alpha = 1 - alpha -- Reverse
- alpha = alpha * 255
- end
-
- if duration - dtime < fade then -- ending fade
- alpha = (duration - dtime) / fade -- 0 to 1
- alpha = alpha * 255
- end
- color.a = alpha
-
- draw.DrawText( msg, "TargetID", ScrW() * 0.5, ScrH() * 0.25, color, TEXT_ALIGN_CENTER )
- end
-
- hook.Add( "HUDPaint", "CSayHelperDraw", drawToScreen )
-end
+--[[
+ Title: Draw
+
+ Our client-side draw functions
+]]
+
+--[[
+ Function: csayDraw
+
+ Draws a csay text on the screen.
+
+ Parameters:
+
+ msg - The message to draw.
+ color - *(Optional, defaults to 255, 255, 255, 255)* The color of the text
+ duration - *(Optional, defaults to 5)* The length of the text
+ fade - *(Optional, defaults to 0.5)* The length of fade time
+
+ Revisions:
+
+ v2.10 - Added fade parameter
+]]
+function ULib.csayDraw( msg, color, duration, fade )
+ color = color or Color( 255, 255, 255, 255 )
+ duration = duration or 5
+ fade = fade or 0.5
+ local start = CurTime()
+
+ local function drawToScreen()
+ local alpha = 255
+ local dtime = CurTime() - start
+
+ if dtime > duration then -- Our time has come :'(
+ hook.Remove( "HUDPaint", "CSayHelperDraw" )
+ return
+ end
+
+ if fade - dtime > 0 then -- beginning fade
+ alpha = (fade - dtime) / fade -- 0 to 1
+ alpha = 1 - alpha -- Reverse
+ alpha = alpha * 255
+ end
+
+ if duration - dtime < fade then -- ending fade
+ alpha = (duration - dtime) / fade -- 0 to 1
+ alpha = alpha * 255
+ end
+ color.a = alpha
+
+ draw.DrawText( msg, "TargetID", ScrW() * 0.5, ScrH() * 0.25, color, TEXT_ALIGN_CENTER )
+ end
+
+ hook.Add( "HUDPaint", "CSayHelperDraw", drawToScreen )
+end
diff --git a/lua/ulib/init.lua b/lua/ulib/init.lua
index 4f1f3b7..b967363 100644
--- a/lua/ulib/init.lua
+++ b/lua/ulib/init.lua
@@ -1,100 +1,100 @@
-if not ULib then
- ULib = {}
-
- -- For historical purposes
- if not ULib.consoleCommand then ULib.consoleCommand = game.ConsoleCommand end
-
- file.CreateDir( "ulib" )
-
- Msg( "///////////////////////////////\n" )
- Msg( "// Ulysses Library //\n" )
- Msg( "///////////////////////////////\n" )
- Msg( "// Loading... //\n" )
-
- Msg( "// shared/defines.lua //\n" )
- include( "ulib/shared/defines.lua" )
- Msg( "// shared/misc.lua //\n" )
- include( "ulib/shared/misc.lua" )
- Msg( "// shared/util.lua //\n" )
- include( "ulib/shared/util.lua" )
- Msg( "// shared/hook.lua //\n" )
- include( "ulib/shared/hook.lua" )
- Msg( "// shared/table.lua //\n" )
- include( "ulib/shared/tables.lua" )
- Msg( "// shared/player.lua //\n" )
- include( "ulib/shared/player.lua" )
- Msg( "// server/player.lua //\n" )
- include( "ulib/server/player.lua" )
- Msg( "// shared/messages.lua //\n" )
- include( "ulib/shared/messages.lua" )
- Msg( "// shared/commands.lua //\n" )
- include( "ulib/shared/commands.lua" )
- Msg( "// server/concommand.lua //\n" )
- include( "ulib/server/concommand.lua" )
- Msg( "// server/util.lua //\n" )
- include( "ulib/server/util.lua" )
- Msg( "// shared/sh_ucl.lua //\n" )
- include( "ulib/shared/sh_ucl.lua" )
- Msg( "// server/ucl.lua //\n" )
- include( "ulib/server/ucl.lua" )
- Msg( "// server/phys.lua //\n" )
- include( "ulib/server/phys.lua" )
- Msg( "// server/player_ext.lua //\n" )
- include( "server/player_ext.lua" )
- Msg( "// server/entity_ext.lua //\n" )
- include( "server/entity_ext.lua" )
- Msg( "// shared/cami_global.lua //\n" )
- include( "shared/cami_global.lua" )
- Msg( "// shared/cami_ulib.lua //\n" )
- include( "shared/cami_ulib.lua" )
- Msg( "// Load Complete! //\n" )
- Msg( "///////////////////////////////\n" )
-
- AddCSLuaFile( "ulib/cl_init.lua" )
- AddCSLuaFile( "autorun/ulib_init.lua" )
- local folder = "ulib/shared"
- local files = file.Find( folder .. "/" .. "*.lua", "LUA" )
- for _, file in ipairs( files ) do
- AddCSLuaFile( folder .. "/" .. file )
- end
-
- folder = "ulib/client"
- files = file.Find( folder .. "/" .. "*.lua", "LUA" )
- for _, file in ipairs( files ) do
- AddCSLuaFile( folder .. "/" .. file )
- end
-
- --Shared modules
- local files = file.Find( "ulib/modules/*.lua", "LUA" )
- if #files > 0 then
- for _, file in ipairs( files ) do
- Msg( "[ULIB] Loading SHARED module: " .. file .. "\n" )
- include( "ulib/modules/" .. file )
- AddCSLuaFile( "ulib/modules/" .. file )
- end
- end
-
- --Server modules
- local files = file.Find( "ulib/modules/server/*.lua", "LUA" )
- if #files > 0 then
- for _, file in ipairs( files ) do
- Msg( "[ULIB] Loading SERVER module: " .. file .. "\n" )
- include( "ulib/modules/server/" .. file )
- end
- end
-
- --Client modules
- local files = file.Find( "ulib/modules/client/*.lua", "LUA" )
- if #files > 0 then
- for _, file in ipairs( files ) do
- Msg( "[ULIB] Loading CLIENT module: " .. file .. "\n" )
- AddCSLuaFile( "ulib/modules/client/" .. file )
- end
- end
-
- local function clReady( ply )
- ply.ulib_ready = true
- hook.Call( ULib.HOOK_LOCALPLAYERREADY, _, ply )
- end
- concommand.Add( "ulib_cl_ready", clReady ) -- Called when the c-side player object is ready
-end
+if not ULib then
+ ULib = {}
+
+ -- For historical purposes
+ if not ULib.consoleCommand then ULib.consoleCommand = game.ConsoleCommand end
+
+ file.CreateDir( "ulib" )
+
+ Msg( "///////////////////////////////\n" )
+ Msg( "// Ulysses Library //\n" )
+ Msg( "///////////////////////////////\n" )
+ Msg( "// Loading... //\n" )
+
+ Msg( "// shared/defines.lua //\n" )
+ include( "ulib/shared/defines.lua" )
+ Msg( "// shared/misc.lua //\n" )
+ include( "ulib/shared/misc.lua" )
+ Msg( "// shared/util.lua //\n" )
+ include( "ulib/shared/util.lua" )
+ Msg( "// shared/hook.lua //\n" )
+ include( "ulib/shared/hook.lua" )
+ Msg( "// shared/table.lua //\n" )
+ include( "ulib/shared/tables.lua" )
+ Msg( "// shared/player.lua //\n" )
+ include( "ulib/shared/player.lua" )
+ Msg( "// server/player.lua //\n" )
+ include( "ulib/server/player.lua" )
+ Msg( "// shared/messages.lua //\n" )
+ include( "ulib/shared/messages.lua" )
+ Msg( "// shared/commands.lua //\n" )
+ include( "ulib/shared/commands.lua" )
+ Msg( "// server/concommand.lua //\n" )
+ include( "ulib/server/concommand.lua" )
+ Msg( "// server/util.lua //\n" )
+ include( "ulib/server/util.lua" )
+ Msg( "// shared/sh_ucl.lua //\n" )
+ include( "ulib/shared/sh_ucl.lua" )
+ Msg( "// server/ucl.lua //\n" )
+ include( "ulib/server/ucl.lua" )
+ Msg( "// server/phys.lua //\n" )
+ include( "ulib/server/phys.lua" )
+ Msg( "// server/player_ext.lua //\n" )
+ include( "server/player_ext.lua" )
+ Msg( "// server/entity_ext.lua //\n" )
+ include( "server/entity_ext.lua" )
+ Msg( "// shared/cami_global.lua //\n" )
+ include( "shared/cami_global.lua" )
+ Msg( "// shared/cami_ulib.lua //\n" )
+ include( "shared/cami_ulib.lua" )
+ Msg( "// Load Complete! //\n" )
+ Msg( "///////////////////////////////\n" )
+
+ AddCSLuaFile( "ulib/cl_init.lua" )
+ AddCSLuaFile( "autorun/ulib_init.lua" )
+ local folder = "ulib/shared"
+ local files = file.Find( folder .. "/" .. "*.lua", "LUA" )
+ for _, file in ipairs( files ) do
+ AddCSLuaFile( folder .. "/" .. file )
+ end
+
+ folder = "ulib/client"
+ files = file.Find( folder .. "/" .. "*.lua", "LUA" )
+ for _, file in ipairs( files ) do
+ AddCSLuaFile( folder .. "/" .. file )
+ end
+
+ --Shared modules
+ local files = file.Find( "ulib/modules/*.lua", "LUA" )
+ if #files > 0 then
+ for _, file in ipairs( files ) do
+ Msg( "[ULIB] Loading SHARED module: " .. file .. "\n" )
+ include( "ulib/modules/" .. file )
+ AddCSLuaFile( "ulib/modules/" .. file )
+ end
+ end
+
+ --Server modules
+ local files = file.Find( "ulib/modules/server/*.lua", "LUA" )
+ if #files > 0 then
+ for _, file in ipairs( files ) do
+ Msg( "[ULIB] Loading SERVER module: " .. file .. "\n" )
+ include( "ulib/modules/server/" .. file )
+ end
+ end
+
+ --Client modules
+ local files = file.Find( "ulib/modules/client/*.lua", "LUA" )
+ if #files > 0 then
+ for _, file in ipairs( files ) do
+ Msg( "[ULIB] Loading CLIENT module: " .. file .. "\n" )
+ AddCSLuaFile( "ulib/modules/client/" .. file )
+ end
+ end
+
+ local function clReady( ply )
+ ply.ulib_ready = true
+ hook.Call( ULib.HOOK_LOCALPLAYERREADY, _, ply )
+ end
+ concommand.Add( "ulib_cl_ready", clReady ) -- Called when the c-side player object is ready
+end
diff --git a/lua/ulib/modules/what_is_this.txt b/lua/ulib/modules/what_is_this.txt
index 9a33db4..ac82979 100644
--- a/lua/ulib/modules/what_is_this.txt
+++ b/lua/ulib/modules/what_is_this.txt
@@ -1,3 +1,3 @@
-This folder is similar to the lua/autorun folder, except all scripts in this folder are loaded after ULib.
-Scripts in this directory are shared ( loaded both server and client side ).
+This folder is similar to the lua/autorun folder, except all scripts in this folder are loaded after ULib.
+Scripts in this directory are shared ( loaded both server and client side ).
Scripts in the client and server sub-folders are loaded only on the client and server respectively.
\ No newline at end of file
diff --git a/lua/ulib/server/concommand.lua b/lua/ulib/server/concommand.lua
index 852793e..05899c7 100644
--- a/lua/ulib/server/concommand.lua
+++ b/lua/ulib/server/concommand.lua
@@ -1,117 +1,117 @@
---[[
- Title: Concommand Helpers
-
- Server-side compliment of the shared commands.lua
-]]
-
---[[
- Table: sayCmds
-
- This table holds our say commands.
-]]
-ULib.sayCmds = ULib.sayCmds or {}
-
---[[
- Function: sayCmdCheck
-
- Say callback which will check to see if there's a say command being used. *DO NOT CALL DIRECTLY*
-
- Parameters:
-
- ply - The player.
- strText - The text.
- bTeam - Team say.
-
- Revisions:
-
- v2.10 - Made case-insensitive
-]]
-local function sayCmdCheck( ply, strText, bTeam )
- local match
- for str, data in pairs( ULib.sayCmds ) do
- local str2 = str
- if strText:len() < str:len() then -- Go ahead and allow commands w/o spaces
- str2 = string.Trim( str )
- end
-
- if strText:sub( 1, str2:len() ):lower() == str2 then
- if not match or match:len() <= str:len() then -- Don't rematch if there's a more specific one already.
- match = str
- end
- end
- end
-
- if match then -- We've got a winner!
- local data = ULib.sayCmds[ match ]
-
- local args = string.Trim( strText:sub( match:len() + 1 ) ) -- Strip the caller command out
- local argv = ULib.splitArgs( args )
-
- -- ULib command callback
- if data.__cmd then
- local return_value = hook.Call( ULib.HOOK_COMMAND_CALLED, _, ply, data.__cmd, argv )
- if return_value == false then
- return nil
- end
- end
-
- if not ULib.ucl.query( ply, data.access ) then
- ULib.tsay( ply, "You do not have access to this command, " .. ply:Nick() .. "." )
- -- Print their name to intimidate them :)
- return "" -- Block from appearing
- end
-
- local fn = data.fn
- local hide = data.hide
-
- ULib.pcallError( fn, ply, match:Trim(), argv, args )
- if hide then return "" end
- end
-
- return nil
-end
-hook.Add( "PlayerSay", "ULib_saycmd", sayCmdCheck, HOOK_HIGH ) -- High-priority
-
-
---[[
- Function: addSayCommand
-
- Just like ULib's except that the callback is called when the command is said in chat instead of typed in the console.
-
- Parameters:
-
- say_cmd - A command string for says. IE: "!kick", then when someone says "!kick", it'll call the callback.
- fn_call - The function to call when the command's called.
- access - The access string to associate access with this say command. (IE: "ulx kick"). Remember to call if the access string isn't being used in a command.
- hide_say - *(Optional, defaults to false)* If true, will hide the chat message. Use this if you don't want other people to see the command.
- nospace - *(Optional, defaults to false)* If true, a space won't be required after the command "IE: !slapbob" vs "!slap bob".
-
- Revisions:
-
- v2.10 - Added nospace parameter, made case insensitive
- v2.40 - Removed the command help parameter, now accepts nil as access (for always allowed)
-]]
-function ULib.addSayCommand( say_cmd, fn_call, access, hide_say, nospace )
- say_cmd = string.Trim( say_cmd:lower() )
-
- if not nospace then
- say_cmd = say_cmd .. " "
- end
-
- ULib.sayCmds[ say_cmd ] = { fn=fn_call, hide=hide_say, access=access }
-end
-
-
---[[
- Function: removeSayCommand
-
- Removes a say command.
-
- Parameters:
-
- say_cmd - The command string for says to remove.
-]]
-function ULib.removeSayCommand( say_cmd )
- ULib.sayCmds[ say_cmd ] = nil -- Remove both forms
- ULib.sayCmds[ say_cmd .. " " ] = nil
-end
+--[[
+ Title: Concommand Helpers
+
+ Server-side compliment of the shared commands.lua
+]]
+
+--[[
+ Table: sayCmds
+
+ This table holds our say commands.
+]]
+ULib.sayCmds = ULib.sayCmds or {}
+
+--[[
+ Function: sayCmdCheck
+
+ Say callback which will check to see if there's a say command being used. *DO NOT CALL DIRECTLY*
+
+ Parameters:
+
+ ply - The player.
+ strText - The text.
+ bTeam - Team say.
+
+ Revisions:
+
+ v2.10 - Made case-insensitive
+]]
+local function sayCmdCheck( ply, strText, bTeam )
+ local match
+ for str, data in pairs( ULib.sayCmds ) do
+ local str2 = str
+ if strText:len() < str:len() then -- Go ahead and allow commands w/o spaces
+ str2 = string.Trim( str )
+ end
+
+ if strText:sub( 1, str2:len() ):lower() == str2 then
+ if not match or match:len() <= str:len() then -- Don't rematch if there's a more specific one already.
+ match = str
+ end
+ end
+ end
+
+ if match then -- We've got a winner!
+ local data = ULib.sayCmds[ match ]
+
+ local args = string.Trim( strText:sub( match:len() + 1 ) ) -- Strip the caller command out
+ local argv = ULib.splitArgs( args )
+
+ -- ULib command callback
+ if data.__cmd then
+ local return_value = hook.Call( ULib.HOOK_COMMAND_CALLED, _, ply, data.__cmd, argv )
+ if return_value == false then
+ return nil
+ end
+ end
+
+ if not ULib.ucl.query( ply, data.access ) then
+ ULib.tsay( ply, "You do not have access to this command, " .. ply:Nick() .. "." )
+ -- Print their name to intimidate them :)
+ return "" -- Block from appearing
+ end
+
+ local fn = data.fn
+ local hide = data.hide
+
+ ULib.pcallError( fn, ply, match:Trim(), argv, args )
+ if hide then return "" end
+ end
+
+ return nil
+end
+hook.Add( "PlayerSay", "ULib_saycmd", sayCmdCheck, HOOK_HIGH ) -- High-priority
+
+
+--[[
+ Function: addSayCommand
+
+ Just like ULib's except that the callback is called when the command is said in chat instead of typed in the console.
+
+ Parameters:
+
+ say_cmd - A command string for says. IE: "!kick", then when someone says "!kick", it'll call the callback.
+ fn_call - The function to call when the command's called.
+ access - The access string to associate access with this say command. (IE: "ulx kick"). Remember to call if the access string isn't being used in a command.
+ hide_say - *(Optional, defaults to false)* If true, will hide the chat message. Use this if you don't want other people to see the command.
+ nospace - *(Optional, defaults to false)* If true, a space won't be required after the command "IE: !slapbob" vs "!slap bob".
+
+ Revisions:
+
+ v2.10 - Added nospace parameter, made case insensitive
+ v2.40 - Removed the command help parameter, now accepts nil as access (for always allowed)
+]]
+function ULib.addSayCommand( say_cmd, fn_call, access, hide_say, nospace )
+ say_cmd = string.Trim( say_cmd:lower() )
+
+ if not nospace then
+ say_cmd = say_cmd .. " "
+ end
+
+ ULib.sayCmds[ say_cmd ] = { fn=fn_call, hide=hide_say, access=access }
+end
+
+
+--[[
+ Function: removeSayCommand
+
+ Removes a say command.
+
+ Parameters:
+
+ say_cmd - The command string for says to remove.
+]]
+function ULib.removeSayCommand( say_cmd )
+ ULib.sayCmds[ say_cmd ] = nil -- Remove both forms
+ ULib.sayCmds[ say_cmd .. " " ] = nil
+end
diff --git a/lua/ulib/server/entity_ext.lua b/lua/ulib/server/entity_ext.lua
index 7e407a9..9271a06 100644
--- a/lua/ulib/server/entity_ext.lua
+++ b/lua/ulib/server/entity_ext.lua
@@ -1,192 +1,192 @@
-local meta = FindMetaTable( "Entity" )
-
--- Return if there's nothing to add on to
-if not meta then return end
-
-
--- Are you a STOOL author who's angry that your tool isn't on this list?
--- Just add this to your code:
--- if ULib then table.insert( ULib.delWhiteList, "my_stool" ) end
-ULib.delWhitelist = -- White list for objects that can't be deleted
-{
- "colour",
- "material",
- "paint",
- "hoverball",
- "emitter",
- "elastic",
- "hydraulic",
- "muscle",
- "nail",
- "ballsocket",
- "ballsocket_adv",
- "pulley",
- "rope",
- "slider",
- "weld",
- "winch",
- "balloon",
- "button",
- "duplicator",
- "dynamite",
- "keepupright",
- "lamp",
- "nocollide",
- "thruster",
- "turret",
- "wheel",
- "eyeposer",
- "faceposer",
- "statue",
- "weld_ez",
- "axis",
-
- -- Properties
- "gravity",
- "collision",
- --"keepupright", -- Already above
- "persist",
-}
-
--- Are you a STOOL author who's angry that your tool isn't on this list?
--- Just add this to your code:
--- if ULib then table.insert( ULib.moveWhiteList, "my_stool" ) end
-ULib.moveWhitelist = -- White list for objects that can't be moved
-{
- "colour",
- "material",
- "paint",
- "duplicator",
- "eyeposer",
- "faceposer",
- "remover",
-
- -- Properties
- --"remover", -- Already above
- "persist",
-}
-
-function meta:DisallowMoving( bool )
- self.NoMoving = bool
-end
-
-function meta:DisallowDeleting( bool, callback, no_replication )
- self.NoDeleting = bool
- self.NoDeletingCallback = callback
- self.NoReplication = no_replication
-end
-
-local function tool( ply, tr, toolmode, second )
- -- In the case of the nail gun, let's check the entity they're nailing TO first.
- if toolmode == "nail" and not second then
- local tr2 = {}
- tr2.start = tr.HitPos
- tr2.endpos = tr.HitPos + ply:GetAimVector() * 16
- tr2.filter = { ply, tr.Entity }
- local trace = util.TraceLine( tr2 )
-
- if trace.Entity and trace.Entity:IsValid() and not trace.Entity:IsPlayer() then
- local ret = tool( ply, trace, toolmode, true )
- if ret ~= nil then
- return ret
- end
- end
- end
-
- -- In the case of the remover, we have to make sure they're not trying to right click remove one of no delete ents
- if toolmode == "remover" and ply:KeyDown( IN_ATTACK2 ) and not ply:KeyDownLast( IN_ATTACK2 ) then
- local ConstrainedEntities = constraint.GetAllConstrainedEntities( tr.Entity )
- if ConstrainedEntities then -- If we have anything to worry about
- -- Loop through all the entities in the system
- for _, ent in pairs( ConstrainedEntities ) do
- if ent.NoDeleting then
- ULib.tsay( ply, "You cannot use a right click delete on this ent because it is constrained to a non-deleteable entity." )
- return false
- end
- end
- end
- end
-
- if tr.Entity.NoMoving then
- if not table.HasValue( ULib.moveWhitelist, toolmode ) then
- return false
- end
- end
-
- if tr.Entity.NoDeleting then
- if not table.HasValue( ULib.delWhitelist, toolmode ) then
- return false
- end
- end
-end
-hook.Add( "CanTool", "ULibEntToolCheck", tool, HOOK_HIGH )
-
-local function property( ply, propertymode, ent )
- if ent.NoMoving then
- if not table.HasValue( ULib.moveWhitelist, toolmode ) then
- return false
- end
- end
-
- if ent.NoDeleting then
- if not table.HasValue( ULib.delWhitelist, toolmode ) then
- return false
- end
- end
-end
-hook.Add( "CanProperty", "ULibEntPropertyCheck", property, HOOK_HIGH )
-
-local function physgun( ply, ent )
- if ent.NoMoving then return false end
-end
-hook.Add( "PhysgunPickup", "ULibEntPhysCheck", physgun, HOOK_HIGH )
-hook.Add( "CanPlayerUnfreeze", "ULibEntUnfreezeCheck", physgun, HOOK_HIGH )
-
-local function physgunReload( weapon, ply )
- local trace = util.GetPlayerTrace( ply )
- local tr = util.TraceLine( trace )
-
- local ent = tr.Entity
- if not ent or not ent:IsValid() or ent:IsWorld() then return end -- Invalid or not interested
- if ent.NoMoving then return false end
-end
-hook.Add( "OnPhysgunReload", "ULibEntPhysReloadCheck", physgunReload, HOOK_HIGH )
-
-local function damageCheck( ent )
- if ent.NoDeleting then
- -- return false
- end
-end
-hook.Add( "EntityTakeDamage", "ULibEntDamagedCheck", damageCheck, HOOK_MONITOR_HIGH )
-
--- This is just in case we have some horribly programmed addon that goes rampant in deleting things
-local function removedCheck( ent )
- if ent.NoDeleting and not ent.NoReplication then
- local class = ent:GetClass()
- local pos = ent:GetPos()
- local ang = ent:GetAngles()
- local model = ent:GetModel()
- local frozen = false
- if ent:GetPhysicsObject():IsValid() and not ent:GetPhysicsObject():IsMoveable() then
- frozen = true
- end
- local t = ent:GetTable()
-
- ULib.queueFunctionCall( function() -- Create it next frame because 1. Old ent won't be in way and 2. We won't overflow the server while shutting down
- local ent2 = ents.Create( class )
- table.Merge( ent2:GetTable(), t )
- ent2:SetModel( model )
- ent2:SetPos( pos )
- ent2:SetAngles( ang )
- ent2:Spawn()
- if frozen then
- ent2:GetPhysicsObject():EnableMotion( false )
- end
-
- if ent2.NoDeletingCallback then
- ent2.NoDeletingCallback( ent, ent2 )
- end
- end )
- end
-end
-hook.Add( "EntityRemoved", "ULibEntRemovedCheck", removedCheck, HOOK_MONITOR_HIGH )
+local meta = FindMetaTable( "Entity" )
+
+-- Return if there's nothing to add on to
+if not meta then return end
+
+
+-- Are you a STOOL author who's angry that your tool isn't on this list?
+-- Just add this to your code:
+-- if ULib then table.insert( ULib.delWhiteList, "my_stool" ) end
+ULib.delWhitelist = -- White list for objects that can't be deleted
+{
+ "colour",
+ "material",
+ "paint",
+ "hoverball",
+ "emitter",
+ "elastic",
+ "hydraulic",
+ "muscle",
+ "nail",
+ "ballsocket",
+ "ballsocket_adv",
+ "pulley",
+ "rope",
+ "slider",
+ "weld",
+ "winch",
+ "balloon",
+ "button",
+ "duplicator",
+ "dynamite",
+ "keepupright",
+ "lamp",
+ "nocollide",
+ "thruster",
+ "turret",
+ "wheel",
+ "eyeposer",
+ "faceposer",
+ "statue",
+ "weld_ez",
+ "axis",
+
+ -- Properties
+ "gravity",
+ "collision",
+ --"keepupright", -- Already above
+ "persist",
+}
+
+-- Are you a STOOL author who's angry that your tool isn't on this list?
+-- Just add this to your code:
+-- if ULib then table.insert( ULib.moveWhiteList, "my_stool" ) end
+ULib.moveWhitelist = -- White list for objects that can't be moved
+{
+ "colour",
+ "material",
+ "paint",
+ "duplicator",
+ "eyeposer",
+ "faceposer",
+ "remover",
+
+ -- Properties
+ --"remover", -- Already above
+ "persist",
+}
+
+function meta:DisallowMoving( bool )
+ self.NoMoving = bool
+end
+
+function meta:DisallowDeleting( bool, callback, no_replication )
+ self.NoDeleting = bool
+ self.NoDeletingCallback = callback
+ self.NoReplication = no_replication
+end
+
+local function tool( ply, tr, toolmode, second )
+ -- In the case of the nail gun, let's check the entity they're nailing TO first.
+ if toolmode == "nail" and not second then
+ local tr2 = {}
+ tr2.start = tr.HitPos
+ tr2.endpos = tr.HitPos + ply:GetAimVector() * 16
+ tr2.filter = { ply, tr.Entity }
+ local trace = util.TraceLine( tr2 )
+
+ if trace.Entity and trace.Entity:IsValid() and not trace.Entity:IsPlayer() then
+ local ret = tool( ply, trace, toolmode, true )
+ if ret ~= nil then
+ return ret
+ end
+ end
+ end
+
+ -- In the case of the remover, we have to make sure they're not trying to right click remove one of no delete ents
+ if toolmode == "remover" and ply:KeyDown( IN_ATTACK2 ) and not ply:KeyDownLast( IN_ATTACK2 ) then
+ local ConstrainedEntities = constraint.GetAllConstrainedEntities( tr.Entity )
+ if ConstrainedEntities then -- If we have anything to worry about
+ -- Loop through all the entities in the system
+ for _, ent in pairs( ConstrainedEntities ) do
+ if ent.NoDeleting then
+ ULib.tsay( ply, "You cannot use a right click delete on this ent because it is constrained to a non-deleteable entity." )
+ return false
+ end
+ end
+ end
+ end
+
+ if tr.Entity.NoMoving then
+ if not table.HasValue( ULib.moveWhitelist, toolmode ) then
+ return false
+ end
+ end
+
+ if tr.Entity.NoDeleting then
+ if not table.HasValue( ULib.delWhitelist, toolmode ) then
+ return false
+ end
+ end
+end
+hook.Add( "CanTool", "ULibEntToolCheck", tool, HOOK_HIGH )
+
+local function property( ply, propertymode, ent )
+ if ent.NoMoving then
+ if not table.HasValue( ULib.moveWhitelist, toolmode ) then
+ return false
+ end
+ end
+
+ if ent.NoDeleting then
+ if not table.HasValue( ULib.delWhitelist, toolmode ) then
+ return false
+ end
+ end
+end
+hook.Add( "CanProperty", "ULibEntPropertyCheck", property, HOOK_HIGH )
+
+local function physgun( ply, ent )
+ if ent.NoMoving then return false end
+end
+hook.Add( "PhysgunPickup", "ULibEntPhysCheck", physgun, HOOK_HIGH )
+hook.Add( "CanPlayerUnfreeze", "ULibEntUnfreezeCheck", physgun, HOOK_HIGH )
+
+local function physgunReload( weapon, ply )
+ local trace = util.GetPlayerTrace( ply )
+ local tr = util.TraceLine( trace )
+
+ local ent = tr.Entity
+ if not ent or not ent:IsValid() or ent:IsWorld() then return end -- Invalid or not interested
+ if ent.NoMoving then return false end
+end
+hook.Add( "OnPhysgunReload", "ULibEntPhysReloadCheck", physgunReload, HOOK_HIGH )
+
+local function damageCheck( ent )
+ if ent.NoDeleting then
+ -- return false
+ end
+end
+hook.Add( "EntityTakeDamage", "ULibEntDamagedCheck", damageCheck, HOOK_MONITOR_HIGH )
+
+-- This is just in case we have some horribly programmed addon that goes rampant in deleting things
+local function removedCheck( ent )
+ if ent.NoDeleting and not ent.NoReplication then
+ local class = ent:GetClass()
+ local pos = ent:GetPos()
+ local ang = ent:GetAngles()
+ local model = ent:GetModel()
+ local frozen = false
+ if ent:GetPhysicsObject():IsValid() and not ent:GetPhysicsObject():IsMoveable() then
+ frozen = true
+ end
+ local t = ent:GetTable()
+
+ ULib.queueFunctionCall( function() -- Create it next frame because 1. Old ent won't be in way and 2. We won't overflow the server while shutting down
+ local ent2 = ents.Create( class )
+ table.Merge( ent2:GetTable(), t )
+ ent2:SetModel( model )
+ ent2:SetPos( pos )
+ ent2:SetAngles( ang )
+ ent2:Spawn()
+ if frozen then
+ ent2:GetPhysicsObject():EnableMotion( false )
+ end
+
+ if ent2.NoDeletingCallback then
+ ent2.NoDeletingCallback( ent, ent2 )
+ end
+ end )
+ end
+end
+hook.Add( "EntityRemoved", "ULibEntRemovedCheck", removedCheck, HOOK_MONITOR_HIGH )
diff --git a/lua/ulib/server/phys.lua b/lua/ulib/server/phys.lua
index 94bcbf8..9920753 100644
--- a/lua/ulib/server/phys.lua
+++ b/lua/ulib/server/phys.lua
@@ -1,115 +1,115 @@
---[[
- Title: Physics Helpers
-
- Various functions to make dealing with the HL2 physics engine a little easier.
-]]
-
-
---[[
- Function: applyAccel
-
- Parameters:
-
- ent - The entity to apply the acceleration to
- magnitude - The amount of acceleration ( Use nil if the magnitude is specified in the direction )
- direction - The direction to apply the acceleration in ( if the magnitude is part of the direction, specify nil for the magnitude )
- dTime - *(Optional, defaults to 1)* The time passed since the last update in seconds ( IE: 0.5 for dTime would only apply half the acceleration )
-]]
-function ULib.applyAccel( ent, magnitude, direction, dTime )
- if dTime == nil then dTime = 1 end
-
- if magnitude ~= nil then
- direction:Normalize()
- else
- magnitude = 1
- end
-
- -- Times it by the time elapsed since the last update.
- local accel = magnitude * dTime
- -- Convert our scalar accel to a vector accel
- accel = direction * accel
-
- if ent:GetMoveType() == MOVETYPE_VPHYSICS then
- -- a = f/m , so times by mass to get the force.
- local force = accel * ent:GetPhysicsObject():GetMass()
- ent:GetPhysicsObject():ApplyForceCenter( force )
- else
- ent:SetVelocity( accel ) -- As it turns out, SetVelocity() is actually SetAccel() in GM10
- end
-end
-
-
---[[
- Function: applyForce
-
- Parameters:
-
- ent - The entity to apply the force to
- magnitude - The amount of force ( Use nil if the magnitude is specified in the direction )
- direction - The direction to apply the force in ( if the magnitude is part of the direction, specify nil for the magnitude )
- dTime - *(Optional, defaults to 1)* The time passed since the last update in seconds ( IE: 0.5 for dTime would only apply half the force )
-]]
-function ULib.applyForce( ent, magnitude, direction, dTime )
- if dTime == nil then dTime = 1 end
-
- if magnitude ~= nil then
- direction:Normalize()
- else
- magnitude = 1
- end
-
- -- Times it by the time elapsed since the last update.
- local force = magnitude * dTime
- -- Convert our scalar force to a vector force
- force = direction * force
-
- if ent:GetMoveType() == MOVETYPE_VPHYSICS then
- ent:GetPhysicsObject():ApplyForceCenter( force )
- else
- -- Because we're not dealing with objects that have vphysics, they might not have a mass. This would cause errors, let's catch them here.
- local mass = ent:GetPhysicsObject():GetMass()
- if not mass then
- mass = 1
- Msg( "applyForce was called with a non-physics entity that doesn't have a mass. To continue calculations, we're assuming it has a mass of one. This could very well produce unrealistic looking physics!\n")
- end
- -- f = m*a, so divide it by mass to get the accel
- local accel = force * 1/mass
- ent:SetVelocity( accel ) -- As it turns out, SetVelocity() is actually SetAccel() in GM10
- end
-end
-
-
---[[
- Function: applyAccelInCurDirection
-
- Applies an acceleration in the entities current *velocity* direction ( not the entity's heading ). See .
- Basically makes the entity go faster or slower ( if a negative magnitude is passed ).
-
- Parameters:
-
- ent - The entity to apply the force to
- magnitude - The amount of acceleration
- dTime - *(Optional, defaults to 1)* The time passed since the last update in seconds ( IE: 0.5 for dTime would only apply half the acceleration )
-]]
-function ULib.applyAccelInCurDirection( ent, magnitude, dTime )
- local direction = ent:GetVelocity( entid ):GetNormalized()
- ULib.applyAccel( entid, magnitude, direction, dTime )
-end
-
-
---[[
- Function: applyForceInCurDirection
-
- Applies a force in the entities current *velocity* direction ( not the entity's heading ). See .
- Basically makes the entity go faster or slower ( if a negative magnitude is passed ).
-
- Parameters:
-
- ent - The entity to apply the force to
- magnitude - The amount of force
- dTime - *(Optional, defaults to 1)* The time passed since the last update in seconds ( IE: 0.5 for dTime would only apply half the force )
-]]
-function ULib.applyForceInCurDirection( ent, magnitude, dTime )
- local direction = ent:GetVelocity( entid ):GetNormalized()
- ULib.applyForce( entid, magnitude, direction, dTime )
+--[[
+ Title: Physics Helpers
+
+ Various functions to make dealing with the HL2 physics engine a little easier.
+]]
+
+
+--[[
+ Function: applyAccel
+
+ Parameters:
+
+ ent - The entity to apply the acceleration to
+ magnitude - The amount of acceleration ( Use nil if the magnitude is specified in the direction )
+ direction - The direction to apply the acceleration in ( if the magnitude is part of the direction, specify nil for the magnitude )
+ dTime - *(Optional, defaults to 1)* The time passed since the last update in seconds ( IE: 0.5 for dTime would only apply half the acceleration )
+]]
+function ULib.applyAccel( ent, magnitude, direction, dTime )
+ if dTime == nil then dTime = 1 end
+
+ if magnitude ~= nil then
+ direction:Normalize()
+ else
+ magnitude = 1
+ end
+
+ -- Times it by the time elapsed since the last update.
+ local accel = magnitude * dTime
+ -- Convert our scalar accel to a vector accel
+ accel = direction * accel
+
+ if ent:GetMoveType() == MOVETYPE_VPHYSICS then
+ -- a = f/m , so times by mass to get the force.
+ local force = accel * ent:GetPhysicsObject():GetMass()
+ ent:GetPhysicsObject():ApplyForceCenter( force )
+ else
+ ent:SetVelocity( accel ) -- As it turns out, SetVelocity() is actually SetAccel() in GM10
+ end
+end
+
+
+--[[
+ Function: applyForce
+
+ Parameters:
+
+ ent - The entity to apply the force to
+ magnitude - The amount of force ( Use nil if the magnitude is specified in the direction )
+ direction - The direction to apply the force in ( if the magnitude is part of the direction, specify nil for the magnitude )
+ dTime - *(Optional, defaults to 1)* The time passed since the last update in seconds ( IE: 0.5 for dTime would only apply half the force )
+]]
+function ULib.applyForce( ent, magnitude, direction, dTime )
+ if dTime == nil then dTime = 1 end
+
+ if magnitude ~= nil then
+ direction:Normalize()
+ else
+ magnitude = 1
+ end
+
+ -- Times it by the time elapsed since the last update.
+ local force = magnitude * dTime
+ -- Convert our scalar force to a vector force
+ force = direction * force
+
+ if ent:GetMoveType() == MOVETYPE_VPHYSICS then
+ ent:GetPhysicsObject():ApplyForceCenter( force )
+ else
+ -- Because we're not dealing with objects that have vphysics, they might not have a mass. This would cause errors, let's catch them here.
+ local mass = ent:GetPhysicsObject():GetMass()
+ if not mass then
+ mass = 1
+ Msg( "applyForce was called with a non-physics entity that doesn't have a mass. To continue calculations, we're assuming it has a mass of one. This could very well produce unrealistic looking physics!\n")
+ end
+ -- f = m*a, so divide it by mass to get the accel
+ local accel = force * 1/mass
+ ent:SetVelocity( accel ) -- As it turns out, SetVelocity() is actually SetAccel() in GM10
+ end
+end
+
+
+--[[
+ Function: applyAccelInCurDirection
+
+ Applies an acceleration in the entities current *velocity* direction ( not the entity's heading ). See .
+ Basically makes the entity go faster or slower ( if a negative magnitude is passed ).
+
+ Parameters:
+
+ ent - The entity to apply the force to
+ magnitude - The amount of acceleration
+ dTime - *(Optional, defaults to 1)* The time passed since the last update in seconds ( IE: 0.5 for dTime would only apply half the acceleration )
+]]
+function ULib.applyAccelInCurDirection( ent, magnitude, dTime )
+ local direction = ent:GetVelocity( entid ):GetNormalized()
+ ULib.applyAccel( entid, magnitude, direction, dTime )
+end
+
+
+--[[
+ Function: applyForceInCurDirection
+
+ Applies a force in the entities current *velocity* direction ( not the entity's heading ). See .
+ Basically makes the entity go faster or slower ( if a negative magnitude is passed ).
+
+ Parameters:
+
+ ent - The entity to apply the force to
+ magnitude - The amount of force
+ dTime - *(Optional, defaults to 1)* The time passed since the last update in seconds ( IE: 0.5 for dTime would only apply half the force )
+]]
+function ULib.applyForceInCurDirection( ent, magnitude, dTime )
+ local direction = ent:GetVelocity( entid ):GetNormalized()
+ ULib.applyForce( entid, magnitude, direction, dTime )
end
\ No newline at end of file
diff --git a/lua/ulib/server/player.lua b/lua/ulib/server/player.lua
index 8901046..3b83cae 100644
--- a/lua/ulib/server/player.lua
+++ b/lua/ulib/server/player.lua
@@ -1,491 +1,491 @@
---[[
- Title: Player
-
- Holds some helpful player functions.
-]]
-
---[[
- Table: slapSounds
-
- These are the sounds used for slaps.
-]]
-local slapSounds = {
- "physics/body/body_medium_impact_hard1.wav",
- "physics/body/body_medium_impact_hard2.wav",
- "physics/body/body_medium_impact_hard3.wav",
- "physics/body/body_medium_impact_hard5.wav",
- "physics/body/body_medium_impact_hard6.wav",
- "physics/body/body_medium_impact_soft5.wav",
- "physics/body/body_medium_impact_soft6.wav",
- "physics/body/body_medium_impact_soft7.wav",
-}
-
-
---[[
- Function: slap
-
- Slaps an entity, can be a user or any entity.
-
- Parameters:
-
- ent - The target ent.
- damage - *(Optional, defaults to 0)* The amount of damage to inflict on the entity.
- power - *(Optional, defaults to 30)* The power of the slap.
- nosound - *(Optional, defaults to false)* If true, no sound will be played.
-]]
-function ULib.slap( ent, damage, power, nosound )
- if ent:GetMoveType() == MOVETYPE_OBSERVER then return end -- Nothing we can do.
-
- damage = damage or 0
- power = power or 500
-
- if ent:IsPlayer() then
- if not ent:Alive() then
- return -- Nothing we can do.
- end
-
- if ent:InVehicle() then
- ent:ExitVehicle()
- end
-
- if ent:GetMoveType() == MOVETYPE_NOCLIP then
- ent:SetMoveType( MOVETYPE_WALK )
- end
- end
-
- if not nosound then -- Play a slap sound
- local sound_num = math.random( #slapSounds ) -- Choose at random
- ent:EmitSound( slapSounds[ sound_num ] )
- end
-
- local direction = Vector( math.random( 20 )-10, math.random( 20 )-10, math.random( 20 )-5 ) -- Make it random, slightly biased to go up.
- ULib.applyAccel( ent, power, direction )
-
- local angle_punch_pitch = math.Rand( -20, 20 )
- local angle_punch_yaw = math.sqrt( 20*20 - angle_punch_pitch * angle_punch_pitch )
- if math.random( 0, 1 ) == 1 then
- angle_punch_yaw = angle_punch_yaw * -1
- end
- ent:ViewPunch( Angle( angle_punch_pitch, angle_punch_yaw, 0 ) )
-
- local newHp = ent:Health() - damage
- if newHp <= 0 then
- if ent:IsPlayer() then
- ent:Kill()
- else
- ent:Fire( "break", 1, 0 )
- end
- return
- end
- ent:SetHealth( newHp )
-end
-
---[[
- Function: kick
-
- Kicks a user.
-
- Parameters:
-
- ply - The player to kick.
- reason - *(Optional)* The reason to give for kicking.
-]]
-function ULib.kick( ply, reason, calling_ply )
- if reason and calling_ply ~= nil then
- local nick = calling_ply:IsValid() and string.format( "%s(%s)", calling_ply:Nick(), calling_ply:SteamID() ) or "Console"
- ply:Kick( string.format( "Kicked by %s (%s)", nick, reason or "[ULX] Kicked from server" ) )
- else
- ply:Kick( reason or "[ULX] Kicked from server" )
- end
-end
-
-
---[[
- Function: ban
-
- Bans a user.
-
- Parameters:
-
- ply - The player to ban.
- time - *(Optional)* The time in minutes to ban the person for, leave nil or 0 for permaban.
- reason - *(Optional)* The reason for banning
- admin - *(Optional)* Admin player enacting ban
-
- Revisions:
-
- v2.10 - Added support for custom ban list
-]]
-function ULib.ban( ply, time, reason, admin )
- if not time or type( time ) ~= "number" then
- time = 0
- end
-
- ULib.addBan( ply:SteamID(), time, reason, ply:Name(), admin )
-
- -- Load our currently banned users so we don't overwrite them
- if ULib.fileExists( "cfg/banned_user.cfg" ) then
- ULib.execFile( "cfg/banned_user.cfg" )
- end
-end
-
-
---[[
- Function: kickban
-
- Kicks and bans a user.
-
- Parameters:
-
- ply - The player to ban.
- time - *(Optional)* The time in minutes to ban the person for, leave nil or 0 for permaban.
- reason - *(Optional)* The reason for banning
- admin - *(Optional)* Admin player enacting ban
-
- Revisions:
-
- v2.10 - Added support for custom ban list
-]]
-function ULib.kickban( ply, time, reason, admin )
- if not time or type( time ) ~= "number" then
- time = 0
- end
-
- ULib.addBan( ply:SteamID(), time, reason, ply:Name(), admin )
-
- -- Load our currently banned users so we don't overwrite them
- if ULib.fileExists( "cfg/banned_user.cfg" ) then
- ULib.execFile( "cfg/banned_user.cfg" )
- end
-end
-
---[[
- Function: addBan
-
- Helper function to store additional data about bans.
-
- Parameters:
-
- steamid - Banned player's steamid
- time - Length of ban
- reason - *(Optional)* Reason for banning
- name - *(Optional)* Name of player banned
- admin - *(Optional)* Admin player enacting the ban
-
- Revisions:
-
- 2.10 - Initial
- 2.40 - If the steamid is connected, kicks them with the reason given
-]]
-function ULib.addBan( steamid, time, reason, name, admin )
- local strTime = time ~= 0 and string.format( "for %s minute(s)", time ) or "permanently"
- local showReason = string.format( "Banned %s: %s", strTime, reason )
-
- local players = player.GetAll()
- for i=1, #players do
- if players[ i ]:SteamID() == steamid then
- ULib.kick( players[ i ], showReason, admin )
- end
- end
-
- -- Remove all semicolons from the reason to prevent command injection
- showReason = string.gsub(showReason, ";", "")
-
- -- This redundant kick code is to ensure they're kicked -- even if they're joining
- game.ConsoleCommand( string.format( "kickid %s %s\n", steamid, showReason or "" ) )
- game.ConsoleCommand( string.format( "banid %f %s kick\n", time, steamid ) )
- game.ConsoleCommand( "writeid\n" )
-
- local admin_name
- if admin then
- admin_name = "(Console)"
- if admin:IsValid() then
- admin_name = string.format( "%s(%s)", admin:Name(), admin:SteamID() )
- end
- end
-
- local t = {}
- if ULib.bans[ steamid ] then
- t = ULib.bans[ steamid ]
- t.modified_admin = admin_name
- t.modified_time = os.time()
- else
- t.admin = admin_name
- end
- t.time = t.time or os.time()
- if time > 0 then
- t.unban = ( ( time * 60 ) + os.time() )
- else
- t.unban = 0
- end
- if reason then
- t.reason = reason
- end
- if name then
- t.name = name
- end
- ULib.bans[ steamid ] = t
- ULib.fileWrite( ULib.BANS_FILE, ULib.makeKeyValues( ULib.bans ) )
-end
-
---[[
- Function: unban
-
- Unbans the given steamid.
-
- Parameters:
-
- steamid - The steamid to unban.
- admin - *(Optional)* Admin player unbanning steamid
-
- Revisions:
-
- v2.10 - Initial
-]]
-function ULib.unban( steamid, admin )
-
- --Default banlist
- if ULib.fileExists( "cfg/banned_user.cfg" ) then
- ULib.execFile( "cfg/banned_user.cfg" )
- end
- ULib.queueFunctionCall( game.ConsoleCommand, "removeid " .. steamid .. ";writeid\n" ) -- Execute after done loading bans
-
- --ULib banlist
- ULib.bans[ steamid ] = nil
- ULib.fileWrite( ULib.BANS_FILE, ULib.makeKeyValues( ULib.bans ) )
-end
-
-local function doInvis()
- local players = player.GetAll()
- local remove = true
- for _, player in ipairs( players ) do
- local t = player:GetTable()
- if t.invis then
- remove = false
- if player:Alive() and player:GetActiveWeapon():IsValid() then
- if player:GetActiveWeapon() ~= t.invis.wep then
-
- if t.invis.wep and IsValid( t.invis.wep ) then -- If changed weapon, set the old weapon to be visible.
- t.invis.wep:SetRenderMode( RENDERMODE_NORMAL )
- t.invis.wep:Fire( "alpha", 255, 0 )
- t.invis.wep:SetMaterial( "" )
- end
-
- t.invis.wep = player:GetActiveWeapon()
- ULib.invisible( player, true, t.invis.vis )
- end
- end
- end
- end
-
- if remove then
- hook.Remove( "Think", "InvisThink" )
- end
-end
-
---[[
- Function: invisible
-
- Makes a user invisible
-
- Parameters:
-
- ply - The player to affect.
- bool - Whether they're invisible or not
- visibility - *(Optional, defaults to 0)* A number from 0 to 255 for their visibility.
-
- Revisions:
-
- v2.40 - Removes shadow when invisible
-]]
-function ULib.invisible( ply, bool, visibility )
- if not ply:IsValid() then return end -- This is called on a timer so we need to verify they're still connected
-
- if bool then
- visibility = visibility or 0
- ply:DrawShadow( false )
- ply:SetMaterial( "models/effects/vol_light001" )
- ply:SetRenderMode( RENDERMODE_TRANSALPHA )
- ply:Fire( "alpha", visibility, 0 )
- ply:GetTable().invis = { vis=visibility, wep=ply:GetActiveWeapon() }
-
- if IsValid( ply:GetActiveWeapon() ) then
- ply:GetActiveWeapon():SetRenderMode( RENDERMODE_TRANSALPHA )
- ply:GetActiveWeapon():Fire( "alpha", visibility, 0 )
- ply:GetActiveWeapon():SetMaterial( "models/effects/vol_light001" )
- if ply:GetActiveWeapon():GetClass() == "gmod_tool" then
- ply:DrawWorldModel( false ) -- tool gun has problems
- else
- ply:DrawWorldModel( true )
- end
- end
-
- hook.Add( "Think", "InvisThink", doInvis )
- else
- ply:DrawShadow( true )
- ply:SetMaterial( "" )
- ply:SetRenderMode( RENDERMODE_NORMAL )
- ply:Fire( "alpha", 255, 0 )
- local activeWeapon = ply:GetActiveWeapon()
- if IsValid( activeWeapon ) then
- activeWeapon:SetRenderMode( RENDERMODE_NORMAL )
- activeWeapon:Fire( "alpha", 255, 0 )
- activeWeapon:SetMaterial( "" )
- end
- ply:GetTable().invis = nil
- end
-end
-
-
---[[
- Function: refreshBans
-
- Refreshes the ULib bans.
-]]
-function ULib.refreshBans()
- local err
- if not ULib.fileExists( ULib.BANS_FILE ) then
- ULib.bans = {}
- else
- ULib.bans, err = ULib.parseKeyValues( ULib.fileRead( ULib.BANS_FILE ) )
- end
-
- if err then
- Msg( "Bans file was not formatted correctly. Attempting to fix and backing up original\n" )
- if err then
- Msg( "Error while reading bans file was: " .. err .. "\n" )
- end
- Msg( "Original file was backed up to " .. ULib.backupFile( ULib.BANS_FILE ) .. "\n" )
- ULib.bans = {}
- end
-
- local default_bans = ""
- if ULib.fileExists( "cfg/banned_user.cfg" ) then
- ULib.execFile( "cfg/banned_user.cfg" )
- ULib.queueFunctionCall( game.ConsoleCommand, "writeid\n" )
- default_bans = ULib.fileRead( "cfg/banned_user.cfg" )
- end
-
- --default_bans = ULib.makePatternSafe( default_bans )
- default_bans = string.gsub( default_bans, "banid %d+ ", "" )
- default_bans = string.Explode( "\n", default_bans:gsub( "\r", "" ) )
- local ban_set = {}
- for _, v in pairs( default_bans ) do
- if v ~= "" then
- ban_set[ v ] = true
- if not ULib.bans[ v ] then
- ULib.bans[ v ] = { unban = 0 }
- end
- end
- end
-
- for k, v in pairs( ULib.bans ) do
- if type( v ) == "table" and type( k ) == "string" then
- local time = ( v.unban - os.time() ) / 60
- if time > 0 then
- game.ConsoleCommand( string.format( "banid %f %s\n", time, k ) )
- elseif math.floor( v.unban ) == 0 then -- We floor it because GM10 has floating point errors that might make it be 0.1e-20 or something dumb.
- if not ban_set[ k ] then
- ULib.bans[ k ] = nil
- end
- else
- ULib.bans[ k ] = nil
- end
- else
- Msg( "Warning: Bad ban data is being ignored, key = " .. tostring( k ) .. "\n" )
- ULib.bans[ k ] = nil
- end
- end
-
- -- We're queueing this because it will split the load out for VERY large ban files
- ULib.queueFunctionCall( function() ULib.fileWrite( ULib.BANS_FILE, ULib.makeKeyValues( ULib.bans ) ) end )
-end
-ULib.pcallError( ULib.refreshBans )
-
-
---[[
- Function: getSpawnInfo
-
- Grabs and returns player information that can be used to respawn player with same health/armor as before the spawn.
-
- Parameters:
-
- ply - The player to grab information for.
-
-
- Returns:
-
- Updates player object to store health and armor. Has no effect unless ULib.Spawn is used later.
-]]
-function ULib.getSpawnInfo( player )
- local result = {}
-
- local t = {}
- player.ULibSpawnInfo = t
- t.health = player:Health()
- t.armor = player:Armor()
- if player:GetActiveWeapon():IsValid() then
- t.curweapon = player:GetActiveWeapon():GetClass()
- end
-
- local weapons = player:GetWeapons()
- local data = {}
- for _, weapon in ipairs( weapons ) do
- printname = weapon:GetClass()
- data[ printname ] = {}
- data[ printname ].clip1 = weapon:Clip1()
- data[ printname ].clip2 = weapon:Clip2()
- data[ printname ].ammo1 = player:GetAmmoCount( weapon:GetPrimaryAmmoType() )
- data[ printname ].ammo2 = player:GetAmmoCount( weapon:GetSecondaryAmmoType() )
- end
- t.data = data
-end
-
--- Helper function for ULib.spawn()
-local function doWeapons( player, t )
- if not player:IsValid() then return end -- Drat, missed 'em.
-
- player:StripAmmo()
- player:StripWeapons()
-
- for printname, data in pairs( t.data ) do
- player:Give( printname )
- local weapon = player:GetWeapon( printname )
- weapon:SetClip1( data.clip1 )
- weapon:SetClip2( data.clip2 )
- player:SetAmmo( data.ammo1, weapon:GetPrimaryAmmoType() )
- player:SetAmmo( data.ammo2, weapon:GetSecondaryAmmoType() )
- end
-
- if t.curweapon then
- player:SelectWeapon( t.curweapon )
- end
-end
-
-
---[[
- Function: spawn
-
- Enhanced spawn player. Can spawn player and return health/armor to status before the spawn. (Only IF ULib.getSpawnInfo was used previously.)
- Clears previously set values that were stored from ULib.getSpawnInfo.
-
- Parameters:
-
- ply - The player to grab information for.
- bool - If true, spawn will set player information to values stored using ULib.SpawnInfo
-
- Returns:
-
- Spawns player. Sets health/armor to stored defaults if ULib.getSpawnInfo was used previously. Clears SpawnInfo table afterwards.
-]]
-function ULib.spawn( player, bool )
- player:Spawn()
-
- if bool and player.ULibSpawnInfo then
- local t = player.ULibSpawnInfo
- player:SetHealth( t.health )
- player:SetArmor( t.armor )
- timer.Simple( 0.1, function() doWeapons( player, t ) end )
- player.ULibSpawnInfo = nil
- end
-end
+--[[
+ Title: Player
+
+ Holds some helpful player functions.
+]]
+
+--[[
+ Table: slapSounds
+
+ These are the sounds used for slaps.
+]]
+local slapSounds = {
+ "physics/body/body_medium_impact_hard1.wav",
+ "physics/body/body_medium_impact_hard2.wav",
+ "physics/body/body_medium_impact_hard3.wav",
+ "physics/body/body_medium_impact_hard5.wav",
+ "physics/body/body_medium_impact_hard6.wav",
+ "physics/body/body_medium_impact_soft5.wav",
+ "physics/body/body_medium_impact_soft6.wav",
+ "physics/body/body_medium_impact_soft7.wav",
+}
+
+
+--[[
+ Function: slap
+
+ Slaps an entity, can be a user or any entity.
+
+ Parameters:
+
+ ent - The target ent.
+ damage - *(Optional, defaults to 0)* The amount of damage to inflict on the entity.
+ power - *(Optional, defaults to 30)* The power of the slap.
+ nosound - *(Optional, defaults to false)* If true, no sound will be played.
+]]
+function ULib.slap( ent, damage, power, nosound )
+ if ent:GetMoveType() == MOVETYPE_OBSERVER then return end -- Nothing we can do.
+
+ damage = damage or 0
+ power = power or 500
+
+ if ent:IsPlayer() then
+ if not ent:Alive() then
+ return -- Nothing we can do.
+ end
+
+ if ent:InVehicle() then
+ ent:ExitVehicle()
+ end
+
+ if ent:GetMoveType() == MOVETYPE_NOCLIP then
+ ent:SetMoveType( MOVETYPE_WALK )
+ end
+ end
+
+ if not nosound then -- Play a slap sound
+ local sound_num = math.random( #slapSounds ) -- Choose at random
+ ent:EmitSound( slapSounds[ sound_num ] )
+ end
+
+ local direction = Vector( math.random( 20 )-10, math.random( 20 )-10, math.random( 20 )-5 ) -- Make it random, slightly biased to go up.
+ ULib.applyAccel( ent, power, direction )
+
+ local angle_punch_pitch = math.Rand( -20, 20 )
+ local angle_punch_yaw = math.sqrt( 20*20 - angle_punch_pitch * angle_punch_pitch )
+ if math.random( 0, 1 ) == 1 then
+ angle_punch_yaw = angle_punch_yaw * -1
+ end
+ ent:ViewPunch( Angle( angle_punch_pitch, angle_punch_yaw, 0 ) )
+
+ local newHp = ent:Health() - damage
+ if newHp <= 0 then
+ if ent:IsPlayer() then
+ ent:Kill()
+ else
+ ent:Fire( "break", 1, 0 )
+ end
+ return
+ end
+ ent:SetHealth( newHp )
+end
+
+--[[
+ Function: kick
+
+ Kicks a user.
+
+ Parameters:
+
+ ply - The player to kick.
+ reason - *(Optional)* The reason to give for kicking.
+]]
+function ULib.kick( ply, reason, calling_ply )
+ if reason and calling_ply ~= nil then
+ local nick = calling_ply:IsValid() and string.format( "%s(%s)", calling_ply:Nick(), calling_ply:SteamID() ) or "Console"
+ ply:Kick( string.format( "Kicked by %s (%s)", nick, reason or "[ULX] Kicked from server" ) )
+ else
+ ply:Kick( reason or "[ULX] Kicked from server" )
+ end
+end
+
+
+--[[
+ Function: ban
+
+ Bans a user.
+
+ Parameters:
+
+ ply - The player to ban.
+ time - *(Optional)* The time in minutes to ban the person for, leave nil or 0 for permaban.
+ reason - *(Optional)* The reason for banning
+ admin - *(Optional)* Admin player enacting ban
+
+ Revisions:
+
+ v2.10 - Added support for custom ban list
+]]
+function ULib.ban( ply, time, reason, admin )
+ if not time or type( time ) ~= "number" then
+ time = 0
+ end
+
+ ULib.addBan( ply:SteamID(), time, reason, ply:Name(), admin )
+
+ -- Load our currently banned users so we don't overwrite them
+ if ULib.fileExists( "cfg/banned_user.cfg" ) then
+ ULib.execFile( "cfg/banned_user.cfg" )
+ end
+end
+
+
+--[[
+ Function: kickban
+
+ Kicks and bans a user.
+
+ Parameters:
+
+ ply - The player to ban.
+ time - *(Optional)* The time in minutes to ban the person for, leave nil or 0 for permaban.
+ reason - *(Optional)* The reason for banning
+ admin - *(Optional)* Admin player enacting ban
+
+ Revisions:
+
+ v2.10 - Added support for custom ban list
+]]
+function ULib.kickban( ply, time, reason, admin )
+ if not time or type( time ) ~= "number" then
+ time = 0
+ end
+
+ ULib.addBan( ply:SteamID(), time, reason, ply:Name(), admin )
+
+ -- Load our currently banned users so we don't overwrite them
+ if ULib.fileExists( "cfg/banned_user.cfg" ) then
+ ULib.execFile( "cfg/banned_user.cfg" )
+ end
+end
+
+--[[
+ Function: addBan
+
+ Helper function to store additional data about bans.
+
+ Parameters:
+
+ steamid - Banned player's steamid
+ time - Length of ban
+ reason - *(Optional)* Reason for banning
+ name - *(Optional)* Name of player banned
+ admin - *(Optional)* Admin player enacting the ban
+
+ Revisions:
+
+ 2.10 - Initial
+ 2.40 - If the steamid is connected, kicks them with the reason given
+]]
+function ULib.addBan( steamid, time, reason, name, admin )
+ local strTime = time ~= 0 and string.format( "for %s minute(s)", time ) or "permanently"
+ local showReason = string.format( "Banned %s: %s", strTime, reason )
+
+ local players = player.GetAll()
+ for i=1, #players do
+ if players[ i ]:SteamID() == steamid then
+ ULib.kick( players[ i ], showReason, admin )
+ end
+ end
+
+ -- Remove all semicolons from the reason to prevent command injection
+ showReason = string.gsub(showReason, ";", "")
+
+ -- This redundant kick code is to ensure they're kicked -- even if they're joining
+ game.ConsoleCommand( string.format( "kickid %s %s\n", steamid, showReason or "" ) )
+ game.ConsoleCommand( string.format( "banid %f %s kick\n", time, steamid ) )
+ game.ConsoleCommand( "writeid\n" )
+
+ local admin_name
+ if admin then
+ admin_name = "(Console)"
+ if admin:IsValid() then
+ admin_name = string.format( "%s(%s)", admin:Name(), admin:SteamID() )
+ end
+ end
+
+ local t = {}
+ if ULib.bans[ steamid ] then
+ t = ULib.bans[ steamid ]
+ t.modified_admin = admin_name
+ t.modified_time = os.time()
+ else
+ t.admin = admin_name
+ end
+ t.time = t.time or os.time()
+ if time > 0 then
+ t.unban = ( ( time * 60 ) + os.time() )
+ else
+ t.unban = 0
+ end
+ if reason then
+ t.reason = reason
+ end
+ if name then
+ t.name = name
+ end
+ ULib.bans[ steamid ] = t
+ ULib.fileWrite( ULib.BANS_FILE, ULib.makeKeyValues( ULib.bans ) )
+end
+
+--[[
+ Function: unban
+
+ Unbans the given steamid.
+
+ Parameters:
+
+ steamid - The steamid to unban.
+ admin - *(Optional)* Admin player unbanning steamid
+
+ Revisions:
+
+ v2.10 - Initial
+]]
+function ULib.unban( steamid, admin )
+
+ --Default banlist
+ if ULib.fileExists( "cfg/banned_user.cfg" ) then
+ ULib.execFile( "cfg/banned_user.cfg" )
+ end
+ ULib.queueFunctionCall( game.ConsoleCommand, "removeid " .. steamid .. ";writeid\n" ) -- Execute after done loading bans
+
+ --ULib banlist
+ ULib.bans[ steamid ] = nil
+ ULib.fileWrite( ULib.BANS_FILE, ULib.makeKeyValues( ULib.bans ) )
+end
+
+local function doInvis()
+ local players = player.GetAll()
+ local remove = true
+ for _, player in ipairs( players ) do
+ local t = player:GetTable()
+ if t.invis then
+ remove = false
+ if player:Alive() and player:GetActiveWeapon():IsValid() then
+ if player:GetActiveWeapon() ~= t.invis.wep then
+
+ if t.invis.wep and IsValid( t.invis.wep ) then -- If changed weapon, set the old weapon to be visible.
+ t.invis.wep:SetRenderMode( RENDERMODE_NORMAL )
+ t.invis.wep:Fire( "alpha", 255, 0 )
+ t.invis.wep:SetMaterial( "" )
+ end
+
+ t.invis.wep = player:GetActiveWeapon()
+ ULib.invisible( player, true, t.invis.vis )
+ end
+ end
+ end
+ end
+
+ if remove then
+ hook.Remove( "Think", "InvisThink" )
+ end
+end
+
+--[[
+ Function: invisible
+
+ Makes a user invisible
+
+ Parameters:
+
+ ply - The player to affect.
+ bool - Whether they're invisible or not
+ visibility - *(Optional, defaults to 0)* A number from 0 to 255 for their visibility.
+
+ Revisions:
+
+ v2.40 - Removes shadow when invisible
+]]
+function ULib.invisible( ply, bool, visibility )
+ if not ply:IsValid() then return end -- This is called on a timer so we need to verify they're still connected
+
+ if bool then
+ visibility = visibility or 0
+ ply:DrawShadow( false )
+ ply:SetMaterial( "models/effects/vol_light001" )
+ ply:SetRenderMode( RENDERMODE_TRANSALPHA )
+ ply:Fire( "alpha", visibility, 0 )
+ ply:GetTable().invis = { vis=visibility, wep=ply:GetActiveWeapon() }
+
+ if IsValid( ply:GetActiveWeapon() ) then
+ ply:GetActiveWeapon():SetRenderMode( RENDERMODE_TRANSALPHA )
+ ply:GetActiveWeapon():Fire( "alpha", visibility, 0 )
+ ply:GetActiveWeapon():SetMaterial( "models/effects/vol_light001" )
+ if ply:GetActiveWeapon():GetClass() == "gmod_tool" then
+ ply:DrawWorldModel( false ) -- tool gun has problems
+ else
+ ply:DrawWorldModel( true )
+ end
+ end
+
+ hook.Add( "Think", "InvisThink", doInvis )
+ else
+ ply:DrawShadow( true )
+ ply:SetMaterial( "" )
+ ply:SetRenderMode( RENDERMODE_NORMAL )
+ ply:Fire( "alpha", 255, 0 )
+ local activeWeapon = ply:GetActiveWeapon()
+ if IsValid( activeWeapon ) then
+ activeWeapon:SetRenderMode( RENDERMODE_NORMAL )
+ activeWeapon:Fire( "alpha", 255, 0 )
+ activeWeapon:SetMaterial( "" )
+ end
+ ply:GetTable().invis = nil
+ end
+end
+
+
+--[[
+ Function: refreshBans
+
+ Refreshes the ULib bans.
+]]
+function ULib.refreshBans()
+ local err
+ if not ULib.fileExists( ULib.BANS_FILE ) then
+ ULib.bans = {}
+ else
+ ULib.bans, err = ULib.parseKeyValues( ULib.fileRead( ULib.BANS_FILE ) )
+ end
+
+ if err then
+ Msg( "Bans file was not formatted correctly. Attempting to fix and backing up original\n" )
+ if err then
+ Msg( "Error while reading bans file was: " .. err .. "\n" )
+ end
+ Msg( "Original file was backed up to " .. ULib.backupFile( ULib.BANS_FILE ) .. "\n" )
+ ULib.bans = {}
+ end
+
+ local default_bans = ""
+ if ULib.fileExists( "cfg/banned_user.cfg" ) then
+ ULib.execFile( "cfg/banned_user.cfg" )
+ ULib.queueFunctionCall( game.ConsoleCommand, "writeid\n" )
+ default_bans = ULib.fileRead( "cfg/banned_user.cfg" )
+ end
+
+ --default_bans = ULib.makePatternSafe( default_bans )
+ default_bans = string.gsub( default_bans, "banid %d+ ", "" )
+ default_bans = string.Explode( "\n", default_bans:gsub( "\r", "" ) )
+ local ban_set = {}
+ for _, v in pairs( default_bans ) do
+ if v ~= "" then
+ ban_set[ v ] = true
+ if not ULib.bans[ v ] then
+ ULib.bans[ v ] = { unban = 0 }
+ end
+ end
+ end
+
+ for k, v in pairs( ULib.bans ) do
+ if type( v ) == "table" and type( k ) == "string" then
+ local time = ( v.unban - os.time() ) / 60
+ if time > 0 then
+ game.ConsoleCommand( string.format( "banid %f %s\n", time, k ) )
+ elseif math.floor( v.unban ) == 0 then -- We floor it because GM10 has floating point errors that might make it be 0.1e-20 or something dumb.
+ if not ban_set[ k ] then
+ ULib.bans[ k ] = nil
+ end
+ else
+ ULib.bans[ k ] = nil
+ end
+ else
+ Msg( "Warning: Bad ban data is being ignored, key = " .. tostring( k ) .. "\n" )
+ ULib.bans[ k ] = nil
+ end
+ end
+
+ -- We're queueing this because it will split the load out for VERY large ban files
+ ULib.queueFunctionCall( function() ULib.fileWrite( ULib.BANS_FILE, ULib.makeKeyValues( ULib.bans ) ) end )
+end
+ULib.pcallError( ULib.refreshBans )
+
+
+--[[
+ Function: getSpawnInfo
+
+ Grabs and returns player information that can be used to respawn player with same health/armor as before the spawn.
+
+ Parameters:
+
+ ply - The player to grab information for.
+
+
+ Returns:
+
+ Updates player object to store health and armor. Has no effect unless ULib.Spawn is used later.
+]]
+function ULib.getSpawnInfo( player )
+ local result = {}
+
+ local t = {}
+ player.ULibSpawnInfo = t
+ t.health = player:Health()
+ t.armor = player:Armor()
+ if player:GetActiveWeapon():IsValid() then
+ t.curweapon = player:GetActiveWeapon():GetClass()
+ end
+
+ local weapons = player:GetWeapons()
+ local data = {}
+ for _, weapon in ipairs( weapons ) do
+ printname = weapon:GetClass()
+ data[ printname ] = {}
+ data[ printname ].clip1 = weapon:Clip1()
+ data[ printname ].clip2 = weapon:Clip2()
+ data[ printname ].ammo1 = player:GetAmmoCount( weapon:GetPrimaryAmmoType() )
+ data[ printname ].ammo2 = player:GetAmmoCount( weapon:GetSecondaryAmmoType() )
+ end
+ t.data = data
+end
+
+-- Helper function for ULib.spawn()
+local function doWeapons( player, t )
+ if not player:IsValid() then return end -- Drat, missed 'em.
+
+ player:StripAmmo()
+ player:StripWeapons()
+
+ for printname, data in pairs( t.data ) do
+ player:Give( printname )
+ local weapon = player:GetWeapon( printname )
+ weapon:SetClip1( data.clip1 )
+ weapon:SetClip2( data.clip2 )
+ player:SetAmmo( data.ammo1, weapon:GetPrimaryAmmoType() )
+ player:SetAmmo( data.ammo2, weapon:GetSecondaryAmmoType() )
+ end
+
+ if t.curweapon then
+ player:SelectWeapon( t.curweapon )
+ end
+end
+
+
+--[[
+ Function: spawn
+
+ Enhanced spawn player. Can spawn player and return health/armor to status before the spawn. (Only IF ULib.getSpawnInfo was used previously.)
+ Clears previously set values that were stored from ULib.getSpawnInfo.
+
+ Parameters:
+
+ ply - The player to grab information for.
+ bool - If true, spawn will set player information to values stored using ULib.SpawnInfo
+
+ Returns:
+
+ Spawns player. Sets health/armor to stored defaults if ULib.getSpawnInfo was used previously. Clears SpawnInfo table afterwards.
+]]
+function ULib.spawn( player, bool )
+ player:Spawn()
+
+ if bool and player.ULibSpawnInfo then
+ local t = player.ULibSpawnInfo
+ player:SetHealth( t.health )
+ player:SetArmor( t.armor )
+ timer.Simple( 0.1, function() doWeapons( player, t ) end )
+ player.ULibSpawnInfo = nil
+ end
+end
diff --git a/lua/ulib/server/player_ext.lua b/lua/ulib/server/player_ext.lua
index 8914bac..2494055 100644
--- a/lua/ulib/server/player_ext.lua
+++ b/lua/ulib/server/player_ext.lua
@@ -1,67 +1,67 @@
-local meta = FindMetaTable( "Player" )
-
-ULib.spawnWhitelist = -- Tool white list for tools that don't spawn things
-{
- "colour",
- "material",
- "paint",
- "ballsocket",
- "ballsocket_adv",
- "weld",
- "keepupright",
- "nocollide",
- "eyeposer",
- "faceposer",
- "statue",
- "weld_ez",
- "axis",
-}
-
--- Return if there's nothing to add on to
-if not meta then return end
-
-function meta:DisallowNoclip( bool )
- self.NoNoclip = bool
-end
-
-function meta:DisallowSpawning( bool )
- self.NoSpawning = bool
-end
-
-function meta:DisallowVehicles( bool )
- self.NoVehicles = bool
-end
-
-local function tool( ply, tr, toolmode )
- if ply.NoSpawning then
- if not table.HasValue( ULib.spawnWhitelist, toolmode ) then
- return false
- end
- end
-end
-hook.Add( "CanTool", "ULibPlayerToolCheck", tool, HOOK_HIGH )
-
-local function noclip( ply )
- if ply.NoNoclip then return false end
-end
-hook.Add( "PlayerNoClip", "ULibNoclipCheck", noclip, HOOK_HIGH )
-
-local function spawnblock( ply )
- if ply.NoSpawning then return false end
-end
-hook.Add( "PlayerSpawnObject", "ULibSpawnBlock", spawnblock )
-hook.Add( "PlayerSpawnEffect", "ULibSpawnBlock", spawnblock )
-hook.Add( "PlayerSpawnProp", "ULibSpawnBlock", spawnblock )
-hook.Add( "PlayerSpawnNPC", "ULibSpawnBlock", spawnblock )
-hook.Add( "PlayerSpawnVehicle", "ULibSpawnBlock", spawnblock )
-hook.Add( "PlayerSpawnRagdoll", "ULibSpawnBlock", spawnblock )
-hook.Add( "PlayerSpawnSENT", "ULibSpawnBlock", spawnblock )
-hook.Add( "PlayerGiveSWEP", "ULibSpawnBlock", spawnblock )
-
-local function vehicleblock( ply, ent )
- if ply.NoVehicles then
- return false
- end
-end
-hook.Add( "CanPlayerEnterVehicle", "ULibVehicleBlock", vehicleblock, HOOK_HIGH )
-hook.Add( "CanDrive", "ULibVehicleDriveBlock", vehicleblock, HOOK_HIGH )
+local meta = FindMetaTable( "Player" )
+
+ULib.spawnWhitelist = -- Tool white list for tools that don't spawn things
+{
+ "colour",
+ "material",
+ "paint",
+ "ballsocket",
+ "ballsocket_adv",
+ "weld",
+ "keepupright",
+ "nocollide",
+ "eyeposer",
+ "faceposer",
+ "statue",
+ "weld_ez",
+ "axis",
+}
+
+-- Return if there's nothing to add on to
+if not meta then return end
+
+function meta:DisallowNoclip( bool )
+ self.NoNoclip = bool
+end
+
+function meta:DisallowSpawning( bool )
+ self.NoSpawning = bool
+end
+
+function meta:DisallowVehicles( bool )
+ self.NoVehicles = bool
+end
+
+local function tool( ply, tr, toolmode )
+ if ply.NoSpawning then
+ if not table.HasValue( ULib.spawnWhitelist, toolmode ) then
+ return false
+ end
+ end
+end
+hook.Add( "CanTool", "ULibPlayerToolCheck", tool, HOOK_HIGH )
+
+local function noclip( ply )
+ if ply.NoNoclip then return false end
+end
+hook.Add( "PlayerNoClip", "ULibNoclipCheck", noclip, HOOK_HIGH )
+
+local function spawnblock( ply )
+ if ply.NoSpawning then return false end
+end
+hook.Add( "PlayerSpawnObject", "ULibSpawnBlock", spawnblock )
+hook.Add( "PlayerSpawnEffect", "ULibSpawnBlock", spawnblock )
+hook.Add( "PlayerSpawnProp", "ULibSpawnBlock", spawnblock )
+hook.Add( "PlayerSpawnNPC", "ULibSpawnBlock", spawnblock )
+hook.Add( "PlayerSpawnVehicle", "ULibSpawnBlock", spawnblock )
+hook.Add( "PlayerSpawnRagdoll", "ULibSpawnBlock", spawnblock )
+hook.Add( "PlayerSpawnSENT", "ULibSpawnBlock", spawnblock )
+hook.Add( "PlayerGiveSWEP", "ULibSpawnBlock", spawnblock )
+
+local function vehicleblock( ply, ent )
+ if ply.NoVehicles then
+ return false
+ end
+end
+hook.Add( "CanPlayerEnterVehicle", "ULibVehicleBlock", vehicleblock, HOOK_HIGH )
+hook.Add( "CanDrive", "ULibVehicleDriveBlock", vehicleblock, HOOK_HIGH )
diff --git a/lua/ulib/server/util.lua b/lua/ulib/server/util.lua
index 9940b14..e14ef06 100644
--- a/lua/ulib/server/util.lua
+++ b/lua/ulib/server/util.lua
@@ -1,274 +1,274 @@
---[[
- Title: Utilities
-
- Has some useful server utilities
-]]
-
-
---[[
- Function: clientRPC
-
- Think of this function as if you're calling a client function directly from the server. You state who should run it, what the name of
- the function is, and then a list of parameters to pass to that function on the client. ULib handles the rest. Parameters can be any
- data type that's allowed on the network and of any size. Send huge tables or strings, it's all the same, and it all works.
-
- Parameters:
-
- filter - The Player object, table of Player objects for who you want to send this to, nil sends to everyone.
- fn - A string of the function to run on the client. Does *not* need to be in the global namespace, "myTable.myFunction" works too.
- ... - *Optional* The parameters to pass to the function.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.clientRPC( plys, fn, ... )
- ULib.checkArg( 1, "ULib.clientRPC", {"nil","Player","table"}, plys )
- ULib.checkArg( 2, "ULib.clientRPC", {"string"}, fn )
-
- net.Start( "URPC" )
- net.WriteString( fn )
- net.WriteTable( {...} )
- if plys then
- net.Send( plys )
- else
- net.Broadcast()
- end
-end
-
-
---[[
- Function: umsgSend
-
- Makes sending umsgs a blast. You don't have to bother knowing what type you're sending, just use ULib.umsgRcv() on the client.
- Note that while you can send tables with this function, you're limited by the max umsg size. If you're sending a large amount of data,
- consider using instead.
-
- Parameters:
-
- v - The value to send.
- queue - *(For use by ONLY)* A boolean of whether the messages should be queued with RPC or not.
-]]
-function ULib.umsgSend( v, queue )
- local tv = type( v )
- local function call( fn, ... )
- if queue then
- queueRPC( fn, ... )
- else
- fn( ... )
- end
- end
-
- if tv == "string" then
- call( umsg.Char, ULib.TYPE_STRING )
- call( umsg.String, v )
- elseif tv == "number" then
- if math.fmod( v, 1 ) ~= 0 then -- It's a float
- call( umsg.Char, ULib.TYPE_FLOAT )
- call( umsg.Float, v )
- else
- if v <= 127 and v >= -127 then
- call( umsg.Char, ULib.TYPE_CHAR )
- call( umsg.Char, v )
- elseif v < 32767 and v > -32768 then
- call( umsg.Char, ULib.TYPE_SHORT )
- call( umsg.Short, v )
- else
- call( umsg.Char, ULib.TYPE_LONG )
- call( umsg.Long, v )
- end
- end
- elseif tv == "boolean" then
- call( umsg.Char, ULib.TYPE_BOOLEAN )
- call( umsg.Bool, v )
- elseif tv == "Entity" or tv == "Player" then
- call( umsg.Char, ULib.TYPE_ENTITY )
- call( umsg.Entity, v )
- elseif tv == "Vector" then
- call( umsg.Char, ULib.TYPE_VECTOR )
- call( umsg.Vector, v )
- elseif tv == "Angle" then
- call( umsg.Char, ULib.TYPE_ANGLE )
- call( umsg.Angle, v )
- elseif tv == "table" then
- call( umsg.Char, ULib.TYPE_TABLE_BEGIN )
- for key, value in pairs( v ) do
- ULib.umsgSend( key, queue )
- ULib.umsgSend( value, queue )
- end
- call( umsg.Char, ULib.TYPE_TABLE_END )
- elseif tv == "nil" then
- call( umsg.Char, ULib.TYPE_NIL )
- else
- ULib.error( "Unknown type passed to umsgSend -- " .. tv )
- end
-end
-
-
---[[
- Function: play3DSound
-
- Plays a 3D sound, the further away from the point the player is, the softer the sound will be.
-
- Parameters:
-
- sound - The sound to play, relative to the sound folder.
- vector - The point to play the sound at.
- volume - *(Optional, defaults to 1)* The volume to make the sound.
- pitch - *(Optional, defaults to 1)* The pitch to make the sound, 1 = normal.
-]]
-function ULib.play3DSound( sound, vector, volume, pitch )
- volume = volume or 100
- pitch = pitch or 100
-
- local ent = ents.Create( "info_null" )
- if not ent:IsValid() then return end
- ent:SetPos( vector )
- ent:Spawn()
- ent:Activate()
- ent:EmitSound( sound, volume, pitch )
-end
-
-
---[[
- Function: getAllReadyPlayers
-
- Similar to player.GetAll(), except it only returns players that have ULib ready to go.
-
- Revisions:
-
- 2.40 - Initial
-]]
-function ULib.getAllReadyPlayers()
- local players = player.GetAll()
- for i=#players, 1, -1 do
- if not players[ i ].ulib_ready then
- table.remove( players, i )
- end
- end
-
- return players
-end
-
-
-ULib.repcvars = ULib.repcvars or {} -- This is used for in order to keep track of valid cvars and access info.
-local repcvars = ULib.repcvars
-local repCvarServerChanged
---[[
- Function: replicatedWritableCvar
-
- This function is mainly intended for use with the menus. This function is very similar to creating a replicated cvar with one caveat:
- This function also creates a cvar on the client that can be modified and will be sent back to the server.
-
- Parameters:
-
- sv_cvar - The string of server side cvar.
- cl_cvar - The string of the client side cvar. *THIS MUST BE DIFFERENT FROM THE sv_cvar VALUE IF YOU'RE PIGGY BACKING AN EXISTING REPLICATED CVAR (like sv_gravity)*.
- default_value - The string of the default value for the cvar.
- save - Boolean of whether or not the value is persistent across map changes.
- This uses garry's way, which has lots of issues. We recommend you watch the cvar for changes and handle saving yourself.
- notify - Boolean of whether or not value changes are announced on the server
- access - The string of the access required for a client to actually change the value.
-
- Returns:
-
- The server-side cvar object.
-
- Revisions:
-
- v2.40 - Initial.
- v2.50 - Changed to not depend on the replicated cvars themselves due to Garry-breakage.
-]]
-function ULib.replicatedWritableCvar( sv_cvar, cl_cvar, default_value, save, notify, access )
- sv_cvar = sv_cvar:lower()
- cl_cvar = cl_cvar:lower()
-
- local flags = 0
- if save then
- flags = flags + FCVAR_ARCHIVE
- end
- if notify then
- flags = flags + FCVAR_NOTIFY
- end
-
- local cvar_obj = GetConVar( sv_cvar ) or CreateConVar( sv_cvar, default_value, flags )
-
- umsg.Start( "ulib_repWriteCvar" ) -- Send to everyone connected
- umsg.String( sv_cvar )
- umsg.String( cl_cvar )
- umsg.String( default_value )
- umsg.String( cvar_obj:GetString() )
- umsg.End()
-
- repcvars[ sv_cvar ] = { access=access, default=default_value, cl_cvar=cl_cvar, cvar_obj=cvar_obj }
- cvars.AddChangeCallback( sv_cvar, repCvarServerChanged )
-
- hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, cl_cvar, nil, nil, cvar_obj:GetString() )
-
- return cvar_obj
-end
-
-local function repCvarOnJoin( ply )
- for sv_cvar, v in pairs( repcvars ) do
- umsg.Start( "ulib_repWriteCvar", ply )
- umsg.String( sv_cvar )
- umsg.String( v.cl_cvar )
- umsg.String( v.default )
- umsg.String( v.cvar_obj:GetString() )
- umsg.End()
- end
-end
-hook.Add( ULib.HOOK_LOCALPLAYERREADY, "ULibSendCvars", repCvarOnJoin )
-
-
-local function clientChangeCvar( ply, command, argv )
- local sv_cvar = argv[ 1 ]
- local newvalue = argv[ 2 ]
-
- if not sv_cvar or not newvalue or not repcvars[ sv_cvar:lower() ] then -- Bad value, ignore
- return
- end
-
- sv_cvar = sv_cvar:lower()
- cvar_obj = repcvars[ sv_cvar ].cvar_obj
- local oldvalue = cvar_obj:GetString()
- if oldvalue == newvalue then return end -- Agreement
-
- local access = repcvars[ sv_cvar ].access
- if not ply:query( access ) then
- ULib.tsayError( ply, "You do not have access to this cvar (" .. sv_cvar .. "), " .. ply:Nick() .. "." )
- umsg.Start( "ulib_repChangeCvar", ply )
- umsg.Entity( ply )
- umsg.String( repcvars[ sv_cvar ].cl_cvar )
- umsg.String( oldvalue )
- umsg.String( oldvalue ) -- No change
- umsg.End()
- return
- end
-
- repcvars[ sv_cvar ].ignore = ply -- Flag other hook not to go off. Flag will be removed at hook.
- RunConsoleCommand( sv_cvar, newvalue )
- hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, repcvars[ sv_cvar ].cl_cvar, ply, oldvalue, newvalue )
-end
-concommand.Add( "ulib_update_cvar", clientChangeCvar, nil, nil, FCVAR_SERVER_CAN_EXECUTE )
--- Adding FCVAR_SERVER_CAN_EXECUTE above prevents an odd bug where if a user hosts a listen server, this command gets registered,
--- but when they join another server they can't change any replicated cvars.
-
-repCvarServerChanged = function( sv_cvar, oldvalue, newvalue )
- if not repcvars[ sv_cvar ] then -- Bad value or we need to ignore it
- return
- end
-
- umsg.Start( "ulib_repChangeCvar" ) -- Tell clients to reset to new value
- umsg.Entity( repcvars[ sv_cvar ].ignore or Entity( 0 ) )
- umsg.String( repcvars[ sv_cvar ].cl_cvar )
- umsg.String( oldvalue )
- umsg.String( newvalue )
- umsg.End()
-
- if repcvars[ sv_cvar ].ignore then
- repcvars[ sv_cvar ].ignore = nil
- else
- hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, repcvars[ sv_cvar ].cl_cvar, Entity( 0 ), oldvalue, newvalue )
- end
-end
+--[[
+ Title: Utilities
+
+ Has some useful server utilities
+]]
+
+
+--[[
+ Function: clientRPC
+
+ Think of this function as if you're calling a client function directly from the server. You state who should run it, what the name of
+ the function is, and then a list of parameters to pass to that function on the client. ULib handles the rest. Parameters can be any
+ data type that's allowed on the network and of any size. Send huge tables or strings, it's all the same, and it all works.
+
+ Parameters:
+
+ filter - The Player object, table of Player objects for who you want to send this to, nil sends to everyone.
+ fn - A string of the function to run on the client. Does *not* need to be in the global namespace, "myTable.myFunction" works too.
+ ... - *Optional* The parameters to pass to the function.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.clientRPC( plys, fn, ... )
+ ULib.checkArg( 1, "ULib.clientRPC", {"nil","Player","table"}, plys )
+ ULib.checkArg( 2, "ULib.clientRPC", {"string"}, fn )
+
+ net.Start( "URPC" )
+ net.WriteString( fn )
+ net.WriteTable( {...} )
+ if plys then
+ net.Send( plys )
+ else
+ net.Broadcast()
+ end
+end
+
+
+--[[
+ Function: umsgSend
+
+ Makes sending umsgs a blast. You don't have to bother knowing what type you're sending, just use ULib.umsgRcv() on the client.
+ Note that while you can send tables with this function, you're limited by the max umsg size. If you're sending a large amount of data,
+ consider using instead.
+
+ Parameters:
+
+ v - The value to send.
+ queue - *(For use by ONLY)* A boolean of whether the messages should be queued with RPC or not.
+]]
+function ULib.umsgSend( v, queue )
+ local tv = type( v )
+ local function call( fn, ... )
+ if queue then
+ queueRPC( fn, ... )
+ else
+ fn( ... )
+ end
+ end
+
+ if tv == "string" then
+ call( umsg.Char, ULib.TYPE_STRING )
+ call( umsg.String, v )
+ elseif tv == "number" then
+ if math.fmod( v, 1 ) ~= 0 then -- It's a float
+ call( umsg.Char, ULib.TYPE_FLOAT )
+ call( umsg.Float, v )
+ else
+ if v <= 127 and v >= -127 then
+ call( umsg.Char, ULib.TYPE_CHAR )
+ call( umsg.Char, v )
+ elseif v < 32767 and v > -32768 then
+ call( umsg.Char, ULib.TYPE_SHORT )
+ call( umsg.Short, v )
+ else
+ call( umsg.Char, ULib.TYPE_LONG )
+ call( umsg.Long, v )
+ end
+ end
+ elseif tv == "boolean" then
+ call( umsg.Char, ULib.TYPE_BOOLEAN )
+ call( umsg.Bool, v )
+ elseif tv == "Entity" or tv == "Player" then
+ call( umsg.Char, ULib.TYPE_ENTITY )
+ call( umsg.Entity, v )
+ elseif tv == "Vector" then
+ call( umsg.Char, ULib.TYPE_VECTOR )
+ call( umsg.Vector, v )
+ elseif tv == "Angle" then
+ call( umsg.Char, ULib.TYPE_ANGLE )
+ call( umsg.Angle, v )
+ elseif tv == "table" then
+ call( umsg.Char, ULib.TYPE_TABLE_BEGIN )
+ for key, value in pairs( v ) do
+ ULib.umsgSend( key, queue )
+ ULib.umsgSend( value, queue )
+ end
+ call( umsg.Char, ULib.TYPE_TABLE_END )
+ elseif tv == "nil" then
+ call( umsg.Char, ULib.TYPE_NIL )
+ else
+ ULib.error( "Unknown type passed to umsgSend -- " .. tv )
+ end
+end
+
+
+--[[
+ Function: play3DSound
+
+ Plays a 3D sound, the further away from the point the player is, the softer the sound will be.
+
+ Parameters:
+
+ sound - The sound to play, relative to the sound folder.
+ vector - The point to play the sound at.
+ volume - *(Optional, defaults to 1)* The volume to make the sound.
+ pitch - *(Optional, defaults to 1)* The pitch to make the sound, 1 = normal.
+]]
+function ULib.play3DSound( sound, vector, volume, pitch )
+ volume = volume or 100
+ pitch = pitch or 100
+
+ local ent = ents.Create( "info_null" )
+ if not ent:IsValid() then return end
+ ent:SetPos( vector )
+ ent:Spawn()
+ ent:Activate()
+ ent:EmitSound( sound, volume, pitch )
+end
+
+
+--[[
+ Function: getAllReadyPlayers
+
+ Similar to player.GetAll(), except it only returns players that have ULib ready to go.
+
+ Revisions:
+
+ 2.40 - Initial
+]]
+function ULib.getAllReadyPlayers()
+ local players = player.GetAll()
+ for i=#players, 1, -1 do
+ if not players[ i ].ulib_ready then
+ table.remove( players, i )
+ end
+ end
+
+ return players
+end
+
+
+ULib.repcvars = ULib.repcvars or {} -- This is used for in order to keep track of valid cvars and access info.
+local repcvars = ULib.repcvars
+local repCvarServerChanged
+--[[
+ Function: replicatedWritableCvar
+
+ This function is mainly intended for use with the menus. This function is very similar to creating a replicated cvar with one caveat:
+ This function also creates a cvar on the client that can be modified and will be sent back to the server.
+
+ Parameters:
+
+ sv_cvar - The string of server side cvar.
+ cl_cvar - The string of the client side cvar. *THIS MUST BE DIFFERENT FROM THE sv_cvar VALUE IF YOU'RE PIGGY BACKING AN EXISTING REPLICATED CVAR (like sv_gravity)*.
+ default_value - The string of the default value for the cvar.
+ save - Boolean of whether or not the value is persistent across map changes.
+ This uses garry's way, which has lots of issues. We recommend you watch the cvar for changes and handle saving yourself.
+ notify - Boolean of whether or not value changes are announced on the server
+ access - The string of the access required for a client to actually change the value.
+
+ Returns:
+
+ The server-side cvar object.
+
+ Revisions:
+
+ v2.40 - Initial.
+ v2.50 - Changed to not depend on the replicated cvars themselves due to Garry-breakage.
+]]
+function ULib.replicatedWritableCvar( sv_cvar, cl_cvar, default_value, save, notify, access )
+ sv_cvar = sv_cvar:lower()
+ cl_cvar = cl_cvar:lower()
+
+ local flags = 0
+ if save then
+ flags = flags + FCVAR_ARCHIVE
+ end
+ if notify then
+ flags = flags + FCVAR_NOTIFY
+ end
+
+ local cvar_obj = GetConVar( sv_cvar ) or CreateConVar( sv_cvar, default_value, flags )
+
+ umsg.Start( "ulib_repWriteCvar" ) -- Send to everyone connected
+ umsg.String( sv_cvar )
+ umsg.String( cl_cvar )
+ umsg.String( default_value )
+ umsg.String( cvar_obj:GetString() )
+ umsg.End()
+
+ repcvars[ sv_cvar ] = { access=access, default=default_value, cl_cvar=cl_cvar, cvar_obj=cvar_obj }
+ cvars.AddChangeCallback( sv_cvar, repCvarServerChanged )
+
+ hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, cl_cvar, nil, nil, cvar_obj:GetString() )
+
+ return cvar_obj
+end
+
+local function repCvarOnJoin( ply )
+ for sv_cvar, v in pairs( repcvars ) do
+ umsg.Start( "ulib_repWriteCvar", ply )
+ umsg.String( sv_cvar )
+ umsg.String( v.cl_cvar )
+ umsg.String( v.default )
+ umsg.String( v.cvar_obj:GetString() )
+ umsg.End()
+ end
+end
+hook.Add( ULib.HOOK_LOCALPLAYERREADY, "ULibSendCvars", repCvarOnJoin )
+
+
+local function clientChangeCvar( ply, command, argv )
+ local sv_cvar = argv[ 1 ]
+ local newvalue = argv[ 2 ]
+
+ if not sv_cvar or not newvalue or not repcvars[ sv_cvar:lower() ] then -- Bad value, ignore
+ return
+ end
+
+ sv_cvar = sv_cvar:lower()
+ cvar_obj = repcvars[ sv_cvar ].cvar_obj
+ local oldvalue = cvar_obj:GetString()
+ if oldvalue == newvalue then return end -- Agreement
+
+ local access = repcvars[ sv_cvar ].access
+ if not ply:query( access ) then
+ ULib.tsayError( ply, "You do not have access to this cvar (" .. sv_cvar .. "), " .. ply:Nick() .. "." )
+ umsg.Start( "ulib_repChangeCvar", ply )
+ umsg.Entity( ply )
+ umsg.String( repcvars[ sv_cvar ].cl_cvar )
+ umsg.String( oldvalue )
+ umsg.String( oldvalue ) -- No change
+ umsg.End()
+ return
+ end
+
+ repcvars[ sv_cvar ].ignore = ply -- Flag other hook not to go off. Flag will be removed at hook.
+ RunConsoleCommand( sv_cvar, newvalue )
+ hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, repcvars[ sv_cvar ].cl_cvar, ply, oldvalue, newvalue )
+end
+concommand.Add( "ulib_update_cvar", clientChangeCvar, nil, nil, FCVAR_SERVER_CAN_EXECUTE )
+-- Adding FCVAR_SERVER_CAN_EXECUTE above prevents an odd bug where if a user hosts a listen server, this command gets registered,
+-- but when they join another server they can't change any replicated cvars.
+
+repCvarServerChanged = function( sv_cvar, oldvalue, newvalue )
+ if not repcvars[ sv_cvar ] then -- Bad value or we need to ignore it
+ return
+ end
+
+ umsg.Start( "ulib_repChangeCvar" ) -- Tell clients to reset to new value
+ umsg.Entity( repcvars[ sv_cvar ].ignore or Entity( 0 ) )
+ umsg.String( repcvars[ sv_cvar ].cl_cvar )
+ umsg.String( oldvalue )
+ umsg.String( newvalue )
+ umsg.End()
+
+ if repcvars[ sv_cvar ].ignore then
+ repcvars[ sv_cvar ].ignore = nil
+ else
+ hook.Call( ULib.HOOK_REPCVARCHANGED, _, sv_cvar, repcvars[ sv_cvar ].cl_cvar, Entity( 0 ), oldvalue, newvalue )
+ end
+end
diff --git a/lua/ulib/shared/hook.lua b/lua/ulib/shared/hook.lua
index 6cc976d..b25df50 100644
--- a/lua/ulib/shared/hook.lua
+++ b/lua/ulib/shared/hook.lua
@@ -1,252 +1,252 @@
-
-local gmod = gmod
-local pairs = pairs
-local isfunction = isfunction
-local isstring = isstring
-local isnumber = isnumber
-local math = math
-local IsValid = IsValid
-
---[[
-local concommand = concommand
-local print = print
-local PrintTable = PrintTable
-local tostring = tostring
-local assert = assert
-local table = table--]]
-
-HOOK_MONITOR_HIGH = -2
-HOOK_HIGH = -1
-HOOK_NORMAL = 0
-HOOK_LOW = 1
-HOOK_MONITOR_LOW = 2
-
-if hook.GetULibTable then return end -- Prevent autorefresh reloading this file
-
--- Grab all previous hooks from the pre-existing hook module.
-local OldHooks = hook.GetTable()
-
-module( "hook" )
-
-local Hooks = {}
-local BackwardsHooks = {} -- A table fully to garry's spec for aVoN
-
---
--- For access to the Hooks table.. for some reason.
---
-function GetTable() return BackwardsHooks end
-function GetULibTable() return Hooks end
-
---
--- Add a hook
---
-function Add( event_name, name, func, priority )
-
- priority = priority or 0
- if ( !isfunction( func ) ) then return end
- if ( !isstring( event_name ) ) then return end
- if ( !isnumber( priority ) ) then return end
-
- priority = math.floor( priority )
- if ( priority < -2 ) then priority = -2 end -- math.Clamp may not have been defined yet
- if ( priority > 2 ) then priority = 2 end
-
- Remove( event_name, name ) -- This keeps the event name unique, even among the priorities
-
- if (Hooks[ event_name ] == nil) then
- Hooks[ event_name ] = {[-2]={}, [-1]={}, [0]={}, [1]={}, [2]={}}
- BackwardsHooks[ event_name ] = {}
- end
-
- Hooks[ event_name ][ priority ][ name ] = { fn=func, isstring=isstring( name ) }
- BackwardsHooks[ event_name ][ name ] = func -- Keep the classic style too so we won't break anything
-
-end
-
-
---
--- Remove a hook
---
-function Remove( event_name, name )
-
- if ( !isstring( event_name ) ) then return end
- if ( !Hooks[ event_name ] ) then return end
-
- for i=-2, 2 do
- Hooks[ event_name ][ i ][ name ] = nil
- end
-
- BackwardsHooks[ event_name ][ name ] = nil
-
-end
-
---
--- Run a hook (this replaces Call)
---
-function Run( name, ... )
- return Call( name, gmod and gmod.GetGamemode() or nil, ... )
-end
-
---
--- Called by the engine
---
-function Call( name, gm, ... )
-
- --
- -- Run hooks
- --
- local HookTable = Hooks[ name ]
- if ( HookTable != nil ) then
-
- for i=-2, 2 do
-
- for k, v in pairs( HookTable[ i ] ) do
-
- if ( v.isstring ) then
-
- --
- -- If it's a string, it's cool
- --
- local a, b, c, d, e, f = v.fn( ... )
- if ( a != nil && i > -2 && i < 2 ) then
- return a, b, c, d, e, f
- end
-
- else
-
- --
- -- If the key isn't a string - we assume it to be an entity
- -- Or panel, or something else that IsValid works on.
- --
- if ( IsValid( k ) ) then
- --
- -- If the object is valid - pass it as the first argument (self)
- --
- local a, b, c, d, e, f = v.fn( k, ... )
- if ( a != nil && i > -2 && i < 2 ) then
- return a, b, c, d, e, f
- end
- else
- --
- -- If the object has become invalid - remove it
- --
- HookTable[ i ][ k ] = nil
- end
- end
- end
- end
- end
-
- --
- -- Call the gamemode function
- --
- if ( !gm ) then return end
-
- local GamemodeFunction = gm[ name ]
- if ( GamemodeFunction == nil ) then return end
-
- return GamemodeFunction( gm, ... )
-
-end
-
--- Bring in all the old hooks
-for event_name, t in pairs( OldHooks ) do
- for name, func in pairs( t ) do
- Add( event_name, name, func )
- end
-end
-
---[[
-local failed = false
-local shouldFail = false
-local i, t
-
--- Since the correctness of this file is so important, we've made a little test suite
-local function appendGenerator( n )
- return function( t )
- table.insert( t, n )
- end
-end
-
-local function returnRange()
- return 1, 2, 3, 4, 5, 6, 7, 8
-end
-
-local function noop()
-end
-
-local function err()
- if shouldFail then
- error( "this error is normal!" )
- else
- error( "this error is bad!" )
- failed = true
- end
-end
-
-local function doTests( ply, cmd, argv )
- print( "Being run on client: " .. tostring( CLIENT ) )
-
- -- First make sure there's no return value leakage...
- Add( "LeakageA", "a", returnRange )
- t = { Call( "LeakageA", _ ) }
- assert( #t == 6 )
- for k, v in pairs( t ) do
- assert( k == v )
- end
-
- Add( "LeakageB", "a", noop )
- t = { Call( "LeakageB", _ ) }
- assert( #t == 0 )
-
- -- Now let's make sure errors are handled correctly...
- shouldFail = true
- Add( "ErrCheck", "a", noop )
- --Add( "ErrCheck", "b", err )
- Add( "ErrCheck", "c", noop )
- Add( "ErrCheck", "d", returnRange )
- t = { Call( "ErrCheck", _ ) }
- assert( #t == 6 )
- --assert( #Hooks.ErrCheck == 3 and Hooks.ErrCheck[4] == nil ) -- Should have been reduced so that the 'b' got removed
-
- shouldFail = false
- t = { Call( "ErrCheck", _ ) }
- assert( #t == 6 )
-
- -- Check for override
- Add( "ErrCheck", "d", noop, 1 ) -- Different priority, same name should still override
- t = { Call( "ErrCheck", _ ) }
- assert( #t == 0 )
-
- -- Check for order and readonly'ness...
- Add( "Order", "n20a", returnRange, -2 )
- Add( "Order", "a", appendGenerator( 3 ) )
- Add( "Order", "n20a", appendGenerator( 1 ), -2 )
- Add( "Order", "n10a", appendGenerator( 2 ), -1 )
- Add( "Order", "10a", appendGenerator( 4 ), 1 )
- Add( "Order", "20a", returnRange, 2 )
- Add( "Order", "20aa", appendGenerator( 5 ), 2 )
-
- t = {}
- Call( "Order", _, t )
- print( t, #t )
- PrintTable( t )
- assert( #t == 5 )
- for k, v in pairs( t ) do
- assert( k == v )
- end
-
- Add( "Test", "AAA", function () print( "AAA" ) Remove( "Test", "AAA" ) end )
- Add( "Test", "BBB", function () print( "BBB" ) end)
- Run( "Test" )
- Run( "Test" )
-
- if failed then
- print( "Tests failed!" )
- else
- print( "All tests passed!" )
- end
-end
-
-concommand.Add( "run_hook_tests", doTests )--]]
-
+
+local gmod = gmod
+local pairs = pairs
+local isfunction = isfunction
+local isstring = isstring
+local isnumber = isnumber
+local math = math
+local IsValid = IsValid
+
+--[[
+local concommand = concommand
+local print = print
+local PrintTable = PrintTable
+local tostring = tostring
+local assert = assert
+local table = table--]]
+
+HOOK_MONITOR_HIGH = -2
+HOOK_HIGH = -1
+HOOK_NORMAL = 0
+HOOK_LOW = 1
+HOOK_MONITOR_LOW = 2
+
+if hook.GetULibTable then return end -- Prevent autorefresh reloading this file
+
+-- Grab all previous hooks from the pre-existing hook module.
+local OldHooks = hook.GetTable()
+
+module( "hook" )
+
+local Hooks = {}
+local BackwardsHooks = {} -- A table fully to garry's spec for aVoN
+
+--
+-- For access to the Hooks table.. for some reason.
+--
+function GetTable() return BackwardsHooks end
+function GetULibTable() return Hooks end
+
+--
+-- Add a hook
+--
+function Add( event_name, name, func, priority )
+
+ priority = priority or 0
+ if ( !isfunction( func ) ) then return end
+ if ( !isstring( event_name ) ) then return end
+ if ( !isnumber( priority ) ) then return end
+
+ priority = math.floor( priority )
+ if ( priority < -2 ) then priority = -2 end -- math.Clamp may not have been defined yet
+ if ( priority > 2 ) then priority = 2 end
+
+ Remove( event_name, name ) -- This keeps the event name unique, even among the priorities
+
+ if (Hooks[ event_name ] == nil) then
+ Hooks[ event_name ] = {[-2]={}, [-1]={}, [0]={}, [1]={}, [2]={}}
+ BackwardsHooks[ event_name ] = {}
+ end
+
+ Hooks[ event_name ][ priority ][ name ] = { fn=func, isstring=isstring( name ) }
+ BackwardsHooks[ event_name ][ name ] = func -- Keep the classic style too so we won't break anything
+
+end
+
+
+--
+-- Remove a hook
+--
+function Remove( event_name, name )
+
+ if ( !isstring( event_name ) ) then return end
+ if ( !Hooks[ event_name ] ) then return end
+
+ for i=-2, 2 do
+ Hooks[ event_name ][ i ][ name ] = nil
+ end
+
+ BackwardsHooks[ event_name ][ name ] = nil
+
+end
+
+--
+-- Run a hook (this replaces Call)
+--
+function Run( name, ... )
+ return Call( name, gmod and gmod.GetGamemode() or nil, ... )
+end
+
+--
+-- Called by the engine
+--
+function Call( name, gm, ... )
+
+ --
+ -- Run hooks
+ --
+ local HookTable = Hooks[ name ]
+ if ( HookTable != nil ) then
+
+ for i=-2, 2 do
+
+ for k, v in pairs( HookTable[ i ] ) do
+
+ if ( v.isstring ) then
+
+ --
+ -- If it's a string, it's cool
+ --
+ local a, b, c, d, e, f = v.fn( ... )
+ if ( a != nil && i > -2 && i < 2 ) then
+ return a, b, c, d, e, f
+ end
+
+ else
+
+ --
+ -- If the key isn't a string - we assume it to be an entity
+ -- Or panel, or something else that IsValid works on.
+ --
+ if ( IsValid( k ) ) then
+ --
+ -- If the object is valid - pass it as the first argument (self)
+ --
+ local a, b, c, d, e, f = v.fn( k, ... )
+ if ( a != nil && i > -2 && i < 2 ) then
+ return a, b, c, d, e, f
+ end
+ else
+ --
+ -- If the object has become invalid - remove it
+ --
+ HookTable[ i ][ k ] = nil
+ end
+ end
+ end
+ end
+ end
+
+ --
+ -- Call the gamemode function
+ --
+ if ( !gm ) then return end
+
+ local GamemodeFunction = gm[ name ]
+ if ( GamemodeFunction == nil ) then return end
+
+ return GamemodeFunction( gm, ... )
+
+end
+
+-- Bring in all the old hooks
+for event_name, t in pairs( OldHooks ) do
+ for name, func in pairs( t ) do
+ Add( event_name, name, func )
+ end
+end
+
+--[[
+local failed = false
+local shouldFail = false
+local i, t
+
+-- Since the correctness of this file is so important, we've made a little test suite
+local function appendGenerator( n )
+ return function( t )
+ table.insert( t, n )
+ end
+end
+
+local function returnRange()
+ return 1, 2, 3, 4, 5, 6, 7, 8
+end
+
+local function noop()
+end
+
+local function err()
+ if shouldFail then
+ error( "this error is normal!" )
+ else
+ error( "this error is bad!" )
+ failed = true
+ end
+end
+
+local function doTests( ply, cmd, argv )
+ print( "Being run on client: " .. tostring( CLIENT ) )
+
+ -- First make sure there's no return value leakage...
+ Add( "LeakageA", "a", returnRange )
+ t = { Call( "LeakageA", _ ) }
+ assert( #t == 6 )
+ for k, v in pairs( t ) do
+ assert( k == v )
+ end
+
+ Add( "LeakageB", "a", noop )
+ t = { Call( "LeakageB", _ ) }
+ assert( #t == 0 )
+
+ -- Now let's make sure errors are handled correctly...
+ shouldFail = true
+ Add( "ErrCheck", "a", noop )
+ --Add( "ErrCheck", "b", err )
+ Add( "ErrCheck", "c", noop )
+ Add( "ErrCheck", "d", returnRange )
+ t = { Call( "ErrCheck", _ ) }
+ assert( #t == 6 )
+ --assert( #Hooks.ErrCheck == 3 and Hooks.ErrCheck[4] == nil ) -- Should have been reduced so that the 'b' got removed
+
+ shouldFail = false
+ t = { Call( "ErrCheck", _ ) }
+ assert( #t == 6 )
+
+ -- Check for override
+ Add( "ErrCheck", "d", noop, 1 ) -- Different priority, same name should still override
+ t = { Call( "ErrCheck", _ ) }
+ assert( #t == 0 )
+
+ -- Check for order and readonly'ness...
+ Add( "Order", "n20a", returnRange, -2 )
+ Add( "Order", "a", appendGenerator( 3 ) )
+ Add( "Order", "n20a", appendGenerator( 1 ), -2 )
+ Add( "Order", "n10a", appendGenerator( 2 ), -1 )
+ Add( "Order", "10a", appendGenerator( 4 ), 1 )
+ Add( "Order", "20a", returnRange, 2 )
+ Add( "Order", "20aa", appendGenerator( 5 ), 2 )
+
+ t = {}
+ Call( "Order", _, t )
+ print( t, #t )
+ PrintTable( t )
+ assert( #t == 5 )
+ for k, v in pairs( t ) do
+ assert( k == v )
+ end
+
+ Add( "Test", "AAA", function () print( "AAA" ) Remove( "Test", "AAA" ) end )
+ Add( "Test", "BBB", function () print( "BBB" ) end)
+ Run( "Test" )
+ Run( "Test" )
+
+ if failed then
+ print( "Tests failed!" )
+ else
+ print( "All tests passed!" )
+ end
+end
+
+concommand.Add( "run_hook_tests", doTests )--]]
+
diff --git a/lua/ulib/shared/messages.lua b/lua/ulib/shared/messages.lua
index b5de7d4..b434b40 100644
--- a/lua/ulib/shared/messages.lua
+++ b/lua/ulib/shared/messages.lua
@@ -1,285 +1,285 @@
---[[
- Title: Messages
-
- Handles messaging like logging, debug, etc.
-]]
-
-
---[[
- Function: tsay
-
- Prints a message in talk say as well as in the user's consoles.
-
- Parameters:
-
- ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
- msg - The message to print.
- wait - *(Optional, defaults to false)* Wait one frame before posting. (Useful to use from things like chat hooks)
- wasValid - *(INTERNAL USE ONLY)* This is flagged on waiting if the player *was* valid.
-
- Revisions:
-
- v2.10 - Initial
-]]
-function ULib.tsay( ply, msg, wait, wasValid )
- ULib.checkArg( 1, "ULib.tsay", {"nil","Player","Entity"}, ply )
- ULib.checkArg( 2, "ULib.tsay", "string", msg )
- ULib.checkArg( 3, "ULib.tsay", {"nil","boolean"}, wait )
-
- if wait then ULib.namedQueueFunctionCall( "ULibChats", ULib.tsay, ply, msg, false, ply and ply:IsValid() ) return end -- Call next frame
-
- if SERVER and ply and not ply:IsValid() then -- Server console
- if wasValid then -- This means we had a valid player that left, so do nothing
- return
- end
- Msg( msg .. "\n" )
- return
- end
-
- if CLIENT then
- LocalPlayer():ChatPrint( msg )
- return
- end
-
- if ply then
- ply:ChatPrint( msg )
- else
- local players = player.GetAll()
- for _, player in ipairs( players ) do
- player:ChatPrint( msg )
- end
- end
-end
-
-local serverConsole = {} -- Used in the function below to identify the server console (internal use)
-
-local function tsayColorCallback( ply, ... )
- if CLIENT then
- chat.AddText( ... )
- return
- end
-
- if ply and ply ~= serverConsole and not ply:IsValid() then return end -- Player must have left the server
-
- local args = { ... }
-
- if ply == serverConsole then
- for i=2, #args, 2 do
- Msg( args[ i ] )
- end
- Msg( "\n" );
- return
- end
-
- local current_chunk = { size = 0 }
- local chunks = { current_chunk }
- local max_chunk_size = 240
- while #args > 0 do
- local arg = table.remove( args, 1 )
- local typ = type( arg )
- local arg_size = typ == "table" and 4 or #arg + 2 -- Include null in strings, bool in both
- if typ == "string" and current_chunk.size + arg_size > max_chunk_size then -- Split a large string up into multiple messages
- local substr = arg:sub( 1, math.max( 1, max_chunk_size - current_chunk.size - 2 ) )
- if #substr > 0 then
- table.insert( current_chunk, substr )
- end
- table.insert( args, 1, arg:sub( #substr + 1) )
-
- current_chunk = { size = 0 }
- table.insert( chunks, current_chunk )
- else
- if current_chunk.size + arg_size > max_chunk_size then
- current_chunk = { size = 0 }
- table.insert( chunks, current_chunk )
- end
- current_chunk.size = current_chunk.size + arg_size
- table.insert( current_chunk, arg )
- end
- end
-
- for chunk_num=1, #chunks do
- local chunk = chunks[ chunk_num ]
- umsg.Start( "tsayc", ply )
- umsg.Bool( chunk_num == #chunks )
- umsg.Char( #chunk )
- for i=1, #chunk do
- local arg = chunk[ i ]
- if type( arg ) == "string" then
- umsg.Bool( true )
- umsg.String( arg )
- else
- umsg.Bool( false )
- umsg.Char( arg.r - 128 )
- umsg.Char( arg.g - 128 )
- umsg.Char( arg.b - 128 )
- end
- end
- umsg.End()
- end
-end
-
-if CLIENT then
-local accumulator = {}
-
-local function tsayColorHook( um )
- local last = um:ReadBool()
- local argn = um:ReadChar()
- for i=1, argn do
- if um:ReadBool() then
- table.insert( accumulator, um:ReadString() )
- else
- table.insert( accumulator, Color( um:ReadChar() + 128, um:ReadChar() + 128, um:ReadChar() + 128) )
- end
- end
-
- if last then
- chat.AddText( unpack( accumulator ) )
- accumulator = {}
- end
-end
-usermessage.Hook( "tsayc", tsayColorHook )
-end
-
-
---[[
- Function: tsayColor
-
- Prints a tsay message in color!
-
- Parameters:
-
- ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
- wait - *(Optional, defaults to false)* Wait one frame before posting. (Useful to use from things like chat hooks)
- ... - color arg and text arg ad infinitum, color needs to come before the text it's coloring.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.tsayColor( ply, wait, ... )
- if SERVER and ply and not ply:IsValid() then ply = serverConsole end -- Mark as server
-
- if wait then ULib.namedQueueFunctionCall( "ULibChats", tsayColorCallback, ply, ... ) return end -- Call next frame
- tsayColorCallback( ply, ... )
-end
-
-
---[[
- Function: tsayError
-
- Just like tsay, but prints the string in red
-
- Parameters:
-
- ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
- msg - The message to print.
- wait - *(Optional, defaults to false)* Wait one frame before posting. (Useful to use from things like chat hooks)
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.tsayError( ply, msg, wait )
- return ULib.tsayColor( ply, wait, Color( 255, 140, 39 ), msg )
-end
-
-
---[[
- Function: csay
-
- Prints a message in center of the screen as well as in the user's consoles.
-
- Parameters:
-
- ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
- msg - The message to print.
- color - *(Optional)* The amount of red to use for the text.
- duration - *(Optional)* The amount of time to show the text.
- fade - *(Optional, defaults to 0.5)* The length of fade time
-
- Revisions:
-
- v2.10 - Added fade parameter. Fixed it sending the message multiple times.
- v2.40 - Changed to use clientRPC.
-]]
-function ULib.csay( ply, msg, color, duration, fade )
- if CLIENT then
- ULib.csayDraw( msg, color, duration )
- Msg( msg .. "\n" )
- return
- end
-
- ULib.clientRPC( ply, "ULib.csayDraw", msg, color, duration, fade )
- ULib.console( ply, msg )
-end
-
-
---[[
- Function: console
-
- Prints a message in the user's consoles.
-
- Parameters:
-
- ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
- msg - The message to print.
-]]
-function ULib.console( ply, msg )
- if CLIENT or (ply and not ply:IsValid()) then
- Msg( msg .. "\n" )
- return
- end
-
- if ply then
- ply:PrintMessage( HUD_PRINTCONSOLE, msg .. "\n" )
- else
- local players = player.GetAll()
- for _, player in ipairs( players ) do
- player:PrintMessage( HUD_PRINTCONSOLE, msg .. "\n" )
- end
- end
-end
-
-
---[[
- Function: error
-
- Gives an error to console.
-
- Parameters:
-
- s - The string to use as the error message
-]]
-function ULib.error( s )
- if CLIENT then
- Msg( "[LC ULIB ERROR] " .. s .. "\n" )
- else
- Msg( "[LS ULIB ERROR] " .. s .. "\n" )
- end
-end
-
-
---[[
- Function: debugFunctionCall
-
- Prints a function call, very useful for debugging.
-
- Parameters:
-
- name - The name of the function called.
- ... - all arguments to the function.
-
- Revisions:
-
- v2.40 - Now uses print instead of Msg, since Msg seems to have a low max length.
- Changed how the variable length params work so you can pass nil followed by more params
-]]
-function ULib.debugFunctionCall( name, ... )
- local args = { ... }
-
- print( "Function '" .. name .. "' called. Parameters:" )
- for i=1, #args do
- local value = ULib.serialize( args[ i ] )
- print( "[PARAMETER " .. i .. "]: Type=" .. type( args[ i ] ) .. "\tValue=(" .. value .. ")" )
- end
+--[[
+ Title: Messages
+
+ Handles messaging like logging, debug, etc.
+]]
+
+
+--[[
+ Function: tsay
+
+ Prints a message in talk say as well as in the user's consoles.
+
+ Parameters:
+
+ ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
+ msg - The message to print.
+ wait - *(Optional, defaults to false)* Wait one frame before posting. (Useful to use from things like chat hooks)
+ wasValid - *(INTERNAL USE ONLY)* This is flagged on waiting if the player *was* valid.
+
+ Revisions:
+
+ v2.10 - Initial
+]]
+function ULib.tsay( ply, msg, wait, wasValid )
+ ULib.checkArg( 1, "ULib.tsay", {"nil","Player","Entity"}, ply )
+ ULib.checkArg( 2, "ULib.tsay", "string", msg )
+ ULib.checkArg( 3, "ULib.tsay", {"nil","boolean"}, wait )
+
+ if wait then ULib.namedQueueFunctionCall( "ULibChats", ULib.tsay, ply, msg, false, ply and ply:IsValid() ) return end -- Call next frame
+
+ if SERVER and ply and not ply:IsValid() then -- Server console
+ if wasValid then -- This means we had a valid player that left, so do nothing
+ return
+ end
+ Msg( msg .. "\n" )
+ return
+ end
+
+ if CLIENT then
+ LocalPlayer():ChatPrint( msg )
+ return
+ end
+
+ if ply then
+ ply:ChatPrint( msg )
+ else
+ local players = player.GetAll()
+ for _, player in ipairs( players ) do
+ player:ChatPrint( msg )
+ end
+ end
+end
+
+local serverConsole = {} -- Used in the function below to identify the server console (internal use)
+
+local function tsayColorCallback( ply, ... )
+ if CLIENT then
+ chat.AddText( ... )
+ return
+ end
+
+ if ply and ply ~= serverConsole and not ply:IsValid() then return end -- Player must have left the server
+
+ local args = { ... }
+
+ if ply == serverConsole then
+ for i=2, #args, 2 do
+ Msg( args[ i ] )
+ end
+ Msg( "\n" );
+ return
+ end
+
+ local current_chunk = { size = 0 }
+ local chunks = { current_chunk }
+ local max_chunk_size = 240
+ while #args > 0 do
+ local arg = table.remove( args, 1 )
+ local typ = type( arg )
+ local arg_size = typ == "table" and 4 or #arg + 2 -- Include null in strings, bool in both
+ if typ == "string" and current_chunk.size + arg_size > max_chunk_size then -- Split a large string up into multiple messages
+ local substr = arg:sub( 1, math.max( 1, max_chunk_size - current_chunk.size - 2 ) )
+ if #substr > 0 then
+ table.insert( current_chunk, substr )
+ end
+ table.insert( args, 1, arg:sub( #substr + 1) )
+
+ current_chunk = { size = 0 }
+ table.insert( chunks, current_chunk )
+ else
+ if current_chunk.size + arg_size > max_chunk_size then
+ current_chunk = { size = 0 }
+ table.insert( chunks, current_chunk )
+ end
+ current_chunk.size = current_chunk.size + arg_size
+ table.insert( current_chunk, arg )
+ end
+ end
+
+ for chunk_num=1, #chunks do
+ local chunk = chunks[ chunk_num ]
+ umsg.Start( "tsayc", ply )
+ umsg.Bool( chunk_num == #chunks )
+ umsg.Char( #chunk )
+ for i=1, #chunk do
+ local arg = chunk[ i ]
+ if type( arg ) == "string" then
+ umsg.Bool( true )
+ umsg.String( arg )
+ else
+ umsg.Bool( false )
+ umsg.Char( arg.r - 128 )
+ umsg.Char( arg.g - 128 )
+ umsg.Char( arg.b - 128 )
+ end
+ end
+ umsg.End()
+ end
+end
+
+if CLIENT then
+local accumulator = {}
+
+local function tsayColorHook( um )
+ local last = um:ReadBool()
+ local argn = um:ReadChar()
+ for i=1, argn do
+ if um:ReadBool() then
+ table.insert( accumulator, um:ReadString() )
+ else
+ table.insert( accumulator, Color( um:ReadChar() + 128, um:ReadChar() + 128, um:ReadChar() + 128) )
+ end
+ end
+
+ if last then
+ chat.AddText( unpack( accumulator ) )
+ accumulator = {}
+ end
+end
+usermessage.Hook( "tsayc", tsayColorHook )
+end
+
+
+--[[
+ Function: tsayColor
+
+ Prints a tsay message in color!
+
+ Parameters:
+
+ ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
+ wait - *(Optional, defaults to false)* Wait one frame before posting. (Useful to use from things like chat hooks)
+ ... - color arg and text arg ad infinitum, color needs to come before the text it's coloring.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.tsayColor( ply, wait, ... )
+ if SERVER and ply and not ply:IsValid() then ply = serverConsole end -- Mark as server
+
+ if wait then ULib.namedQueueFunctionCall( "ULibChats", tsayColorCallback, ply, ... ) return end -- Call next frame
+ tsayColorCallback( ply, ... )
+end
+
+
+--[[
+ Function: tsayError
+
+ Just like tsay, but prints the string in red
+
+ Parameters:
+
+ ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
+ msg - The message to print.
+ wait - *(Optional, defaults to false)* Wait one frame before posting. (Useful to use from things like chat hooks)
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.tsayError( ply, msg, wait )
+ return ULib.tsayColor( ply, wait, Color( 255, 140, 39 ), msg )
+end
+
+
+--[[
+ Function: csay
+
+ Prints a message in center of the screen as well as in the user's consoles.
+
+ Parameters:
+
+ ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
+ msg - The message to print.
+ color - *(Optional)* The amount of red to use for the text.
+ duration - *(Optional)* The amount of time to show the text.
+ fade - *(Optional, defaults to 0.5)* The length of fade time
+
+ Revisions:
+
+ v2.10 - Added fade parameter. Fixed it sending the message multiple times.
+ v2.40 - Changed to use clientRPC.
+]]
+function ULib.csay( ply, msg, color, duration, fade )
+ if CLIENT then
+ ULib.csayDraw( msg, color, duration )
+ Msg( msg .. "\n" )
+ return
+ end
+
+ ULib.clientRPC( ply, "ULib.csayDraw", msg, color, duration, fade )
+ ULib.console( ply, msg )
+end
+
+
+--[[
+ Function: console
+
+ Prints a message in the user's consoles.
+
+ Parameters:
+
+ ply - The player to print to, set to nil to send to everyone. (Ignores this param if called on client)
+ msg - The message to print.
+]]
+function ULib.console( ply, msg )
+ if CLIENT or (ply and not ply:IsValid()) then
+ Msg( msg .. "\n" )
+ return
+ end
+
+ if ply then
+ ply:PrintMessage( HUD_PRINTCONSOLE, msg .. "\n" )
+ else
+ local players = player.GetAll()
+ for _, player in ipairs( players ) do
+ player:PrintMessage( HUD_PRINTCONSOLE, msg .. "\n" )
+ end
+ end
+end
+
+
+--[[
+ Function: error
+
+ Gives an error to console.
+
+ Parameters:
+
+ s - The string to use as the error message
+]]
+function ULib.error( s )
+ if CLIENT then
+ Msg( "[LC ULIB ERROR] " .. s .. "\n" )
+ else
+ Msg( "[LS ULIB ERROR] " .. s .. "\n" )
+ end
+end
+
+
+--[[
+ Function: debugFunctionCall
+
+ Prints a function call, very useful for debugging.
+
+ Parameters:
+
+ name - The name of the function called.
+ ... - all arguments to the function.
+
+ Revisions:
+
+ v2.40 - Now uses print instead of Msg, since Msg seems to have a low max length.
+ Changed how the variable length params work so you can pass nil followed by more params
+]]
+function ULib.debugFunctionCall( name, ... )
+ local args = { ... }
+
+ print( "Function '" .. name .. "' called. Parameters:" )
+ for i=1, #args do
+ local value = ULib.serialize( args[ i ] )
+ print( "[PARAMETER " .. i .. "]: Type=" .. type( args[ i ] ) .. "\tValue=(" .. value .. ")" )
+ end
end
\ No newline at end of file
diff --git a/lua/ulib/shared/misc.lua b/lua/ulib/shared/misc.lua
index a375db2..3ea0c14 100644
--- a/lua/ulib/shared/misc.lua
+++ b/lua/ulib/shared/misc.lua
@@ -1,904 +1,904 @@
---[[
- Title: Miscellaneous
-
- Some utility functions. Unlike the functions in util.lua, this file only holds non-HL2 specific functions.
-]]
-
---[[
- Function: explode
-
- Split a string by a separator.
-
- Parameters:
-
- separator - The separator string.
- str - A string.
- limit - *(Optional)* Max number of elements in the table
-
- Returns:
-
- A table of str split by separator, nil and error message otherwise.
-
- Revisions:
-
- v2.10 - Initial (dragged over from a GM9 archive though)
-]]
-function ULib.explode( separator, str, limit )
- local t = {}
- local curpos = 1
- while true do -- We have a break in the loop
- local newpos, endpos = str:find( separator, curpos ) -- find the next separator in the string
- if newpos ~= nil then -- if found then..
- table.insert( t, str:sub( curpos, newpos - 1 ) ) -- Save it in our table.
- curpos = endpos + 1 -- save just after where we found it for searching next time.
- else
- if limit and #t > limit then
- return t -- Reached limit
- end
- table.insert( t, str:sub( curpos ) ) -- Save what's left in our array.
- break
- end
- end
-
- return t
-end
-
-
---[[
- Function: stripComments
-
- Strips comments from a string
-
- Parameters:
-
- str - The string to stip comments from
- comment - The comment string. If it's found, whatever comes after it on that line is ignored. ( IE: "//" )
- blockcommentbeg - *(Optional)* The block comment begin string. ( IE: "/" )
- blockcommentend - *(Optional, must be specified if above parameter is)* The block comment end string. ( IE: "/" )
-
- Returns:
-
- The string with the comments stripped, nil and error otherwise.
-
- Revisions:
-
- v2.02 - Fixed block comments in more complicated files.
-]]
-function ULib.stripComments( str, comment, blockcommentbeg, blockcommentend )
- if blockcommentbeg and string.sub( blockcommentbeg, 1, string.len( comment ) ) == comment then -- If the first of the block comment is the linecomment ( IE: --[[ and -- ).
- string.gsub( str, ULib.makePatternSafe( comment ) .. "[%S \t]*", function ( match )
- if string.sub( match, 1, string.len( blockcommentbeg ) ) == blockcommentbeg then
- return "" -- No substitution, this is a block comment.
- end
- str = string.gsub( str, ULib.makePatternSafe( match ), "", 1 )
- return ""
- end )
-
- str = string.gsub( str, ULib.makePatternSafe( blockcommentbeg ) .. ".-" .. ULib.makePatternSafe( blockcommentend ), "" )
- else -- Doesn't need special processing.
- str = string.gsub( str, ULib.makePatternSafe( comment ) .. "[%S \t]*", "" )
- if blockcommentbeg and blockcommentend then
- str = string.gsub( str, ULib.makePatternSafe( blockcommentbeg ) .. ".-" .. ULib.makePatternSafe( blockcommentend ), "" )
- end
- end
-
- return str
-end
-
-
---[[
- Function: makePatternSafe
-
- Makes a string safe for pattern usage, like in string.gsub(). Basically replaces all keywords with % and keyword.
-
- Parameters:
-
- str - The string to make pattern safe
-
- Returns:
-
- The pattern safe string
-]]
-function ULib.makePatternSafe( str )
- return str:gsub( "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1" )
-end
-
-
---[[
- Function: stripQuotes
-
- Trims leading and tailing quotes from a string
-
- Parameters:
-
- s - The string to strip
-
- Returns:
-
- The stripped string
-]]
-function ULib.stripQuotes( s )
- return s:gsub( "^%s*[\"]*(.-)[\"]*%s*$", "%1" )
-end
-
-
---[[
- Function: unescapeBackslash
-
- Converts '\\' to '\'
-
- Parameters:
-
- s - The string to convert
-
- Returns:
-
- The converted string
-]]
-function ULib.unescapeBackslash( s )
- return s:gsub( "\\\\", "\\" )
-end
-
-
---[[
- Function: splitPort
-
- Parameters:
-
- ipAndPort - An IP address in the form xxx.xxx.xxx.xxx:xxxx
-
- Returns:
-
- The IP as the first return value, the port as the second return value
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.splitPort( ipAndPort )
- return unpack( ULib.explode( ":", ipAndPort ) )
-end
-
---[[
- Function: splitArgs
-
- This is similar to string.Explode( " ", str ) except that it will also obey quotation marks.
-
- Parameters:
-
- args - The string to split from
- start_token - The string character to start a string with.
- end_token - The string character to end a string with.
-
- Returns:
-
- A table containing the individual arguments and a boolean stating whether or not mismatched quotes were found.
-
- Example:
-
- :ULib.splitArgs( "This is a \"Cool sentence to\" make \"split up\"" )
-
- returns...
-
- :{ "This", "is", "a", "Cool sentence to", "make", "split up" }
-
- Notes:
-
- * Mismatched quotes will result in having the last quote grouping the remaining input into
- one argument.
- * Arguments outside of quotes are trimmed (via string.Trim), while what's inside quotes is not
- trimmed at all.
-
- Revisions:
-
- v2.10 - Can now handle tabs and trims strings before returning.
- v2.30 - Rewrite. Can now properly handle escaped quotes. New param, ignore_mismatch.
- v2.40 - Rewrite. Much more stable and predictable now. Removed ignore_mismatch param. As
- far as I can tell, it now matches the source engine's split arg behavior exactly. Also
- accepts tokens to consider a string.
-]]
-function ULib.splitArgs( args, start_token, end_token )
- args = args:Trim()
- local argv = {}
- local curpos = 1 -- Our current position within the string
- local in_quote = false -- Is the text we're currently processing in a quote?
- start_token = start_token or "\""
- end_token = end_token or "\""
- local args_len = args:len()
-
- while in_quote or curpos <= args_len do
- local quotepos = args:find( in_quote and end_token or start_token, curpos, true )
-
- -- The string up to the quote, the whole string if no quote was found
- local prefix = args:sub( curpos, (quotepos or 0) - 1 )
- if not in_quote then
- local trimmed = prefix:Trim()
- if trimmed ~= "" then -- Something to be had from this...
- local t = ULib.explode( "%s+", trimmed )
- table.Add( argv, t )
- end
- else
- table.insert( argv, prefix )
- end
-
- -- If a quote was found, reduce our position and note our state
- if quotepos ~= nil then
- curpos = quotepos + 1
- in_quote = not in_quote
- else -- Otherwise we've processed the whole string now
- break
- end
- end
-
- return argv, in_quote
-end
-
-
---[[
- Function: parseKeyValues
-
- Parses a keyvalue formatted string into a table.
-
- Parameters:
-
- str - The string to parse.
- convert - *(Optional, defaults to false)* Setting this to true will convert garry's keyvalues to a better form. This has two effects.
- First, it will remove the "Out"{} wrapper. Second, it will convert any keys that equate to a number to a number.
-
- Returns:
-
- The table, nil and error otherwise. *If you find you're missing information from the table, the file format might be incorrect.*
-
- Example format:
-:test
-:{
-: "howdy" "bbq"
-:
-: foo
-: {
-: "bar" "false"
-: }
-:
-:}
-
- Revisions:
-
- v2.10 - Initial (but tastefully stolen from a GM9 version)
- v2.30 - Rewrite. Much more robust and properly unescapes backslashes now.
- v2.40 - Properly handles escaped quotes now.
-]]
-function ULib.parseKeyValues( str, convert )
- local lines = ULib.explode( "\r?\n", str )
- local parent_tables = {} -- Traces our way to root
- local current_table = {}
- local is_insert_last_op = false
-
- for i, line in ipairs( lines ) do
- local tmp_string = string.char( 01, 02, 03 ) -- Replacement
- local tokens = ULib.splitArgs( (line:gsub( "\\\"", tmp_string )) )
- for i, token in ipairs( tokens ) do
- tokens[ i ] = ULib.unescapeBackslash( token ):gsub( tmp_string, "\"" )
- end
-
- local num_tokens = #tokens
-
- if num_tokens == 1 then
- local token = tokens[ 1 ]
- if token == "{" then
- local new_table = {}
- if is_insert_last_op then
- current_table[ table.remove( current_table ) ] = new_table
- else
- table.insert( current_table, new_table )
- end
- is_insert_last_op = false
- table.insert( parent_tables, current_table )
- current_table = new_table
-
- elseif token == "}" then
- is_insert_last_op = false
- current_table = table.remove( parent_tables )
- if current_table == nil then
- return nil, "Mismatched recursive tables on line " .. i
- end
-
- else
- is_insert_last_op = true
- table.insert( current_table, tokens[ 1 ] )
- end
-
- elseif num_tokens == 2 then
- is_insert_last_op = false
- if convert and tonumber( tokens[ 1 ] ) then
- tokens[ 1 ] = tonumber( tokens[ 1 ] )
- end
-
- current_table[ tokens[ 1 ] ] = tokens[ 2 ]
-
- elseif num_tokens > 2 then
- return nil, "Bad input on line " .. i
- end
- end
-
- if #parent_tables ~= 0 then
- return nil, "Mismatched recursive tables"
- end
-
- if convert and table.Count( current_table ) == 1 and
- type( current_table.Out ) == "table" then -- If we caught a stupid garry-wrapper
-
- current_table = current_table.Out
- end
-
- return current_table
-end
-
-
---[[
- Function: makeKeyValues
-
- Makes a key values string from a table.
-
- Parameters:
-
- t - The table to make the keyvalues from. This can only contain tables, numbers, and strings.
- tab - *Only for internal use*, this helps make inline tables look better.
- completed - A list of table values that have already been parsed, this is *only for internal use* to make sure we don't hit an infinite loop.
-
- Returns:
-
- The string, nil and error otherwise.
-
- Notes:
-
- If you use numbers as keys in the table, just the values will be used.
-
- Example table format:
-:{ test = { howdy = "bbq", foo = { bar = "false" } } }
-
- Example return format:
-:test
-:{
-: "howdy" "bbq"
-:
-: foo
-: {
-: "bar" "false"
-: }
-:
-:}
-
- Revisions:
-
- v2.10 - Initial (but tastefully stolen from a GM9 version)
- v2.40 - Increased performance for insanely high table counts.
-]]
-function ULib.makeKeyValues( t, tab, completed )
- ULib.checkArg( 1, "ULib.makeKeyValues", "table", t )
-
- tab = tab or ""
- completed = completed or {}
- if completed[ t ] then return "" end -- We've already done this table.
- completed[ t ] = true
-
- local str = ""
-
- for k, v in pairs( t ) do
- str = str .. tab
- if type( k ) ~= "number" then
- str = string.format( "%s%q\t", str, tostring( k ) )
- end
-
- if type( v ) == "table" then
- str = string.format( "%s\n%s{\n%s%s}\n", str, tab, ULib.makeKeyValues( v, tab .. "\t", completed ), tab )
- elseif type( v ) == "string" then
- str = string.format( "%s%q\n", str, v )
- else
- str = str .. tostring( v ) .. "\n"
- end
- end
-
- return str
-end
-
-
---[[
- Function: toBool
-
- Converts a bool, nil, string, or number to a bool
-
- Parameters:
-
- x - The string or number
-
- Returns:
-
- The bool
-
- Revisions:
-
- v2.10 - Initial.
- v2.40 - Added ability to convert nils and bools.
-]]
-function ULib.toBool( x )
- if type( x ) == "boolean" then return x end
- if x == nil then return false end
-
- if tonumber( x ) ~= nil then
- x = math.Round( tonumber( x ) )
- if x == 0 then
- return false
- else
- return true
- end
- end
-
- x = x:lower()
- if x == "t" or x == "true" or x == "yes" or x == "y" then
- return true
- else
- return false
- end
-end
-
-
---[[
- Function: findVar
-
- Given a string, find a var starting from the global namespace. This will correctly parse tables. IE, "ULib.serialize".
-
- Parameters:
-
- var - The variable you wish to find
-
- Returns:
-
- The variable or nil
-
- Revisions:
-
- v2.40 - Removed dependency on gmod functions.
-]]
-function ULib.findVar( var )
- if not var then error( "Nil param passed to ULib.findVar", 2 ) end
- local loc = ULib.explode( "%.", var )
- local x = _G
- for _, v in ipairs( loc ) do
- x = x[ v ]
- if not x then return end
- end
-
- return x
-end
-
-
---[[
- Function: throwBadArg
-
- Throws an error similar to the lua "bad argument #x to ( expected, got ).
-
- Parameters:
-
- argnum - *(Optional)* The argument number that was bad.
- fnName - *(Optional)* The name of the function being called.
- expected - *(Optional)* The string of the type you expected.
- data - *(Optional)* The actual data you got.
- throwLevel - *(Optional, defaults to 3)* How many levels up to throw the error.
-
- Returns:
-
- Never returns, throws an error
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.throwBadArg( argnum, fnName, expected, data, throwLevel )
- throwLevel = throwLevel or 3
-
- local str = "bad argument"
- if argnum then
- str = str .. " #" .. tostring( argnum )
- end
- if fnName then
- str = str .. " to " .. fnName
- end
- if expected or data then
- str = str .. " ("
- if expected then
- str = str .. expected .. " expected"
- end
- if expected and data then
- str = str .. ", "
- end
- if data then
- str = str .. "got " .. type( data )
- end
- str = str .. ")"
- end
-
- error( str, throwLevel )
-end
-
-
---[[
- Function: checkArg
-
- Checks to see if an arg matches what is expected, if not, calls throwBadArg().
-
- Parameters:
-
- argnum - *(Optional)* The argument number you're.
- fnName - *(Optional)* The name of the function being called.
- expected - The string of the type you expect or a table of types you expect.
- data - The actual data you got.
- throwLevel - *(Optional, defaults to 4)* How many levels up to throw the error.
-
- Returns:
-
- Never returns if the data is bad, throws an error. Otherwise returns nil.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.checkArg( argnum, fnName, expected, data, throwLevel )
- throwLevel = throwLevel or 4
- if type( expected ) == "string" then
- if type( data ) == expected then
- return
- else
- return ULib.throwBadArg( argnum, fnName, expected, data, throwLevel )
- end
- else
- if table.HasValue( expected, type( data ) ) then
- return
- else
- return ULib.throwBadArg( argnum, fnName, table.concat( expected, "," ), data, throwLevel )
- end
- end
-end
-
-
---[[
- Function: isValidSteamID
-
- Checks to see if a given string is a valid steamid.
-
- Parameters:
-
- steamid - The string of the supposed steamid.
-
- Returns:
-
- True if it's valid, false if not.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.isValidSteamID( steamid )
- return steamid:match( "^STEAM_%d:%d:%d+$" ) ~= nil
-end
-
-
---[[
- Function: isValidIP
-
- Checks to see if a given string is a valid IPv4 address.
-
- Parameters:
-
- ip - The string of the supposed ip.
-
- Returns:
-
- True if it's valid, false if not.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.isValidIP( ip )
- if ip:find( "^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?$" ) then
- return true
- else
- return false
- end
-end
-
-
---[[
- Function: removeCommentHeader
-
- Removes a comment header.
-
- Parameters:
-
- data - The string to remove the comment from.
- comment_char - The comment char.
-
- Returns:
-
- Data without the comment header.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.removeCommentHeader( data, comment_char )
- comment_char = comment_char or ";"
- local lines = ULib.explode( "\r?\n", data )
- local end_comment_line = 0
- for _, line in ipairs( lines ) do
- local trimmed = line:Trim()
- if trimmed == "" or trimmed:sub( 1, 1 ) == comment_char then
- end_comment_line = end_comment_line + 1
- else
- break
- end
- end
-
- local not_comment = table.concat( lines, "\n", end_comment_line + 1 )
- return not_comment:Trim()
-end
-
-
---[[
- Function: stringTimeToSeconds
-
- Converts a string containing time information to seconds.
-
- Parameters:
-
- str - The time string. Defaults to minutes, "h" is for hours, "d" is for days, "w" is for weeks.
-
- Returns:
-
- The number of minutes represented by the string or nil if it's unable to parse the string.
-
- Revisions:
-
- v2.41 - Initial
- v2.43 - Added year parameter
-]]
-function ULib.stringTimeToSeconds( str )
- if str == nil or type( str ) == "number" then
- return str
- end
-
- str = str:gsub( " ", "" )
- local minutes = 0
- local keycode_location = str:find( "%a" )
- while keycode_location do
- local keycode = str:sub( keycode_location, keycode_location )
- local num = tonumber( str:sub( 1, keycode_location - 1 ) )
- if not num then
- return nil
- end
-
- local multiplier
- if keycode == "h" then
- multiplier = 60
- elseif keycode == "d" then
- multiplier = 60 * 24
- elseif keycode == "w" then
- multiplier = 60 * 24 * 7
- elseif keycode == "y" then
- multiplier = 60 * 24 * 365
- else
- return nil
- end
-
- str = str:sub( keycode_location + 1 )
- keycode_location = str:find( "%a" )
- minutes = minutes + num * multiplier
- end
-
- local num = 0
- if str ~= "" then
- num = tonumber( str )
- end
-
- if num == nil then
- return nil
- end
-
- return minutes + num
-end
-
-
---[[
- Section: Inheritance
-]]
-
---[[
- Function: inheritsFrom
-
- Creates a psudeo-inheritance for lua. It will search for variables that do
- not exist in derived 'classes' in the parent 'classes', among other things
- explained below.
-
- Parameters:
-
- baseClass - The class to derive from. This value *must* either be nil
- or a class created using .
-
- Returns:
-
- The table of the derived class.
-
- Revisions:
-
- v2.40 - Initial.
-
- Notes:
-
- * Adapted with improvements from a lua-users inheritance tutorial
- .
- * Create using Class:create( ... ) or Class( ... ) (equivalent).
- * Whatever's passed in the '...', above, is passed to
- derived_class:instantiate(). This allows for a 'constructor'.
-
- See Also:
-
- *
- *
- *
- *
- *
- *
-
- Example:
-
-:b = inherits_from( nil )
-:function b:instantiate( ... )
-: print( "base", unpack( arg ) )
-:end
-:
-:d = inherits_from( b )
-:function d:instantiate( ... )
-: print( "derived", unpack( arg ) )
-:end
-:
-:b1 = b( "should be base" )
-:d1 = d( "should be derived" )
-:print( "d1 is d?", d1:isa( d ), "is b?", d1:isa( b ) )
-:print( "b1 is d?", b1:isa( d ), "is b?", b1:isa( b ) )
-
- Output:
-
-:base should be base
-:derived should be derived
-:d1 is d? true is b? true
-:b1 is d? false is b? true
-]]
-function inheritsFrom( base_class )
- local new_class = {}
-
- -- The meta-table for INSTANCES (IE, created with Class:create())
- local instance_mt = { __index = new_class, class=new_class, base_class=base_class }
-
- -- The meta-table for the root_class (this will only ever have one table associated with it)
- local class_mt = table.Copy( instance_mt ) -- Only a few differences so copy
- class_mt.__index = base_class or root_class -- Use base or our special meta-base
- class_mt.__call = root_class.call -- Set up call alias
- class_mt.class = new_class -- Set up alias to ourself
- class_mt.instance_mt = instance_mt -- Need this for root_class:create()
-
- setmetatable( new_class, class_mt )
-
- return new_class
-end
-
-
---[[
- Table: root_class
-
- This is a local table that holds our functions that we want *all* classes
- to have.
-]]
-root_class = {}
-
-
---[[
- Function: root_class:call
-
- This is a utility function used by the metatable __call to resolve Class( ... ) to Class:create( ... ).
-
- Parameters:
-
- parent_table - The table of the caller.
- ... - Extra construction parameters, passed to Class:instantiate.
-
- Returns:
-
- The 'class instance'.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function root_class.call( parent_table, ... )
- return parent_table:class():create( ... )
-end
-
-
---[[
- Function: root_class:create
-
- This is used to create new 'class instances'.
-
- Parameters:
-
- ... - Extra construction parameters, passed to Class:instantiate.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function root_class:create( ... )
- local newinst = {}
- setmetatable( newinst, getmetatable( self ).instance_mt )
- newinst:instantiate( ... ) -- 'Constructor'
- return newinst
-end
-
-
--- Return the class object of the instance
-function root_class:class()
- return getmetatable( self ).class
-end
-
-
--- Return the super class object of the instance
-function root_class:superClass()
- base_class = getmetatable( self ).base_class
- return base_class ~= root_class and base_class or nil -- Nil if root class
-end
-
-
--- We need to make sure this func exists, but can be overridden
-function root_class:instantiate()
-end
-
-
--- Return true if the caller is an instance of theClass
-function root_class:isa( target_class )
- local cur_class = self:class()
-
- while cur_class and not b_isa do
- if cur_class == target_class then
- return true
- else
- cur_class = cur_class:superClass()
- end
- end
-
- return false
-end
-
-function isClass( obj )
- return type( obj ) == "table" and type( obj.isa ) == "function" and obj:isa( root_class )
-end
-
-
--- This wonderful bit of following code will make sure that no rogue coder can screw us up by changing the value of '_'
-_ = nil -- Make sure we're starting out right.
-local meta = getmetatable( _G ) or {}
-if type( meta ) == "boolean" then return end -- Metatable is protected, so we aren't able to run this code without erroring.
-local old__newindex = meta.__newindex
-setmetatable( _G, meta )
-function meta.__newindex( t, k, v )
- if k == "_" then
- -- If you care enough to fix bad scripts uncomment this following line.
- -- error( "attempt to modify global variable '_'", 2 )
- return
- end
-
- if old__newindex then
- old__newindex( t, k, v )
- else
- rawset( t, k, v )
- end
-end
+--[[
+ Title: Miscellaneous
+
+ Some utility functions. Unlike the functions in util.lua, this file only holds non-HL2 specific functions.
+]]
+
+--[[
+ Function: explode
+
+ Split a string by a separator.
+
+ Parameters:
+
+ separator - The separator string.
+ str - A string.
+ limit - *(Optional)* Max number of elements in the table
+
+ Returns:
+
+ A table of str split by separator, nil and error message otherwise.
+
+ Revisions:
+
+ v2.10 - Initial (dragged over from a GM9 archive though)
+]]
+function ULib.explode( separator, str, limit )
+ local t = {}
+ local curpos = 1
+ while true do -- We have a break in the loop
+ local newpos, endpos = str:find( separator, curpos ) -- find the next separator in the string
+ if newpos ~= nil then -- if found then..
+ table.insert( t, str:sub( curpos, newpos - 1 ) ) -- Save it in our table.
+ curpos = endpos + 1 -- save just after where we found it for searching next time.
+ else
+ if limit and #t > limit then
+ return t -- Reached limit
+ end
+ table.insert( t, str:sub( curpos ) ) -- Save what's left in our array.
+ break
+ end
+ end
+
+ return t
+end
+
+
+--[[
+ Function: stripComments
+
+ Strips comments from a string
+
+ Parameters:
+
+ str - The string to stip comments from
+ comment - The comment string. If it's found, whatever comes after it on that line is ignored. ( IE: "//" )
+ blockcommentbeg - *(Optional)* The block comment begin string. ( IE: "/" )
+ blockcommentend - *(Optional, must be specified if above parameter is)* The block comment end string. ( IE: "/" )
+
+ Returns:
+
+ The string with the comments stripped, nil and error otherwise.
+
+ Revisions:
+
+ v2.02 - Fixed block comments in more complicated files.
+]]
+function ULib.stripComments( str, comment, blockcommentbeg, blockcommentend )
+ if blockcommentbeg and string.sub( blockcommentbeg, 1, string.len( comment ) ) == comment then -- If the first of the block comment is the linecomment ( IE: --[[ and -- ).
+ string.gsub( str, ULib.makePatternSafe( comment ) .. "[%S \t]*", function ( match )
+ if string.sub( match, 1, string.len( blockcommentbeg ) ) == blockcommentbeg then
+ return "" -- No substitution, this is a block comment.
+ end
+ str = string.gsub( str, ULib.makePatternSafe( match ), "", 1 )
+ return ""
+ end )
+
+ str = string.gsub( str, ULib.makePatternSafe( blockcommentbeg ) .. ".-" .. ULib.makePatternSafe( blockcommentend ), "" )
+ else -- Doesn't need special processing.
+ str = string.gsub( str, ULib.makePatternSafe( comment ) .. "[%S \t]*", "" )
+ if blockcommentbeg and blockcommentend then
+ str = string.gsub( str, ULib.makePatternSafe( blockcommentbeg ) .. ".-" .. ULib.makePatternSafe( blockcommentend ), "" )
+ end
+ end
+
+ return str
+end
+
+
+--[[
+ Function: makePatternSafe
+
+ Makes a string safe for pattern usage, like in string.gsub(). Basically replaces all keywords with % and keyword.
+
+ Parameters:
+
+ str - The string to make pattern safe
+
+ Returns:
+
+ The pattern safe string
+]]
+function ULib.makePatternSafe( str )
+ return str:gsub( "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1" )
+end
+
+
+--[[
+ Function: stripQuotes
+
+ Trims leading and tailing quotes from a string
+
+ Parameters:
+
+ s - The string to strip
+
+ Returns:
+
+ The stripped string
+]]
+function ULib.stripQuotes( s )
+ return s:gsub( "^%s*[\"]*(.-)[\"]*%s*$", "%1" )
+end
+
+
+--[[
+ Function: unescapeBackslash
+
+ Converts '\\' to '\'
+
+ Parameters:
+
+ s - The string to convert
+
+ Returns:
+
+ The converted string
+]]
+function ULib.unescapeBackslash( s )
+ return s:gsub( "\\\\", "\\" )
+end
+
+
+--[[
+ Function: splitPort
+
+ Parameters:
+
+ ipAndPort - An IP address in the form xxx.xxx.xxx.xxx:xxxx
+
+ Returns:
+
+ The IP as the first return value, the port as the second return value
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.splitPort( ipAndPort )
+ return unpack( ULib.explode( ":", ipAndPort ) )
+end
+
+--[[
+ Function: splitArgs
+
+ This is similar to string.Explode( " ", str ) except that it will also obey quotation marks.
+
+ Parameters:
+
+ args - The string to split from
+ start_token - The string character to start a string with.
+ end_token - The string character to end a string with.
+
+ Returns:
+
+ A table containing the individual arguments and a boolean stating whether or not mismatched quotes were found.
+
+ Example:
+
+ :ULib.splitArgs( "This is a \"Cool sentence to\" make \"split up\"" )
+
+ returns...
+
+ :{ "This", "is", "a", "Cool sentence to", "make", "split up" }
+
+ Notes:
+
+ * Mismatched quotes will result in having the last quote grouping the remaining input into
+ one argument.
+ * Arguments outside of quotes are trimmed (via string.Trim), while what's inside quotes is not
+ trimmed at all.
+
+ Revisions:
+
+ v2.10 - Can now handle tabs and trims strings before returning.
+ v2.30 - Rewrite. Can now properly handle escaped quotes. New param, ignore_mismatch.
+ v2.40 - Rewrite. Much more stable and predictable now. Removed ignore_mismatch param. As
+ far as I can tell, it now matches the source engine's split arg behavior exactly. Also
+ accepts tokens to consider a string.
+]]
+function ULib.splitArgs( args, start_token, end_token )
+ args = args:Trim()
+ local argv = {}
+ local curpos = 1 -- Our current position within the string
+ local in_quote = false -- Is the text we're currently processing in a quote?
+ start_token = start_token or "\""
+ end_token = end_token or "\""
+ local args_len = args:len()
+
+ while in_quote or curpos <= args_len do
+ local quotepos = args:find( in_quote and end_token or start_token, curpos, true )
+
+ -- The string up to the quote, the whole string if no quote was found
+ local prefix = args:sub( curpos, (quotepos or 0) - 1 )
+ if not in_quote then
+ local trimmed = prefix:Trim()
+ if trimmed ~= "" then -- Something to be had from this...
+ local t = ULib.explode( "%s+", trimmed )
+ table.Add( argv, t )
+ end
+ else
+ table.insert( argv, prefix )
+ end
+
+ -- If a quote was found, reduce our position and note our state
+ if quotepos ~= nil then
+ curpos = quotepos + 1
+ in_quote = not in_quote
+ else -- Otherwise we've processed the whole string now
+ break
+ end
+ end
+
+ return argv, in_quote
+end
+
+
+--[[
+ Function: parseKeyValues
+
+ Parses a keyvalue formatted string into a table.
+
+ Parameters:
+
+ str - The string to parse.
+ convert - *(Optional, defaults to false)* Setting this to true will convert garry's keyvalues to a better form. This has two effects.
+ First, it will remove the "Out"{} wrapper. Second, it will convert any keys that equate to a number to a number.
+
+ Returns:
+
+ The table, nil and error otherwise. *If you find you're missing information from the table, the file format might be incorrect.*
+
+ Example format:
+:test
+:{
+: "howdy" "bbq"
+:
+: foo
+: {
+: "bar" "false"
+: }
+:
+:}
+
+ Revisions:
+
+ v2.10 - Initial (but tastefully stolen from a GM9 version)
+ v2.30 - Rewrite. Much more robust and properly unescapes backslashes now.
+ v2.40 - Properly handles escaped quotes now.
+]]
+function ULib.parseKeyValues( str, convert )
+ local lines = ULib.explode( "\r?\n", str )
+ local parent_tables = {} -- Traces our way to root
+ local current_table = {}
+ local is_insert_last_op = false
+
+ for i, line in ipairs( lines ) do
+ local tmp_string = string.char( 01, 02, 03 ) -- Replacement
+ local tokens = ULib.splitArgs( (line:gsub( "\\\"", tmp_string )) )
+ for i, token in ipairs( tokens ) do
+ tokens[ i ] = ULib.unescapeBackslash( token ):gsub( tmp_string, "\"" )
+ end
+
+ local num_tokens = #tokens
+
+ if num_tokens == 1 then
+ local token = tokens[ 1 ]
+ if token == "{" then
+ local new_table = {}
+ if is_insert_last_op then
+ current_table[ table.remove( current_table ) ] = new_table
+ else
+ table.insert( current_table, new_table )
+ end
+ is_insert_last_op = false
+ table.insert( parent_tables, current_table )
+ current_table = new_table
+
+ elseif token == "}" then
+ is_insert_last_op = false
+ current_table = table.remove( parent_tables )
+ if current_table == nil then
+ return nil, "Mismatched recursive tables on line " .. i
+ end
+
+ else
+ is_insert_last_op = true
+ table.insert( current_table, tokens[ 1 ] )
+ end
+
+ elseif num_tokens == 2 then
+ is_insert_last_op = false
+ if convert and tonumber( tokens[ 1 ] ) then
+ tokens[ 1 ] = tonumber( tokens[ 1 ] )
+ end
+
+ current_table[ tokens[ 1 ] ] = tokens[ 2 ]
+
+ elseif num_tokens > 2 then
+ return nil, "Bad input on line " .. i
+ end
+ end
+
+ if #parent_tables ~= 0 then
+ return nil, "Mismatched recursive tables"
+ end
+
+ if convert and table.Count( current_table ) == 1 and
+ type( current_table.Out ) == "table" then -- If we caught a stupid garry-wrapper
+
+ current_table = current_table.Out
+ end
+
+ return current_table
+end
+
+
+--[[
+ Function: makeKeyValues
+
+ Makes a key values string from a table.
+
+ Parameters:
+
+ t - The table to make the keyvalues from. This can only contain tables, numbers, and strings.
+ tab - *Only for internal use*, this helps make inline tables look better.
+ completed - A list of table values that have already been parsed, this is *only for internal use* to make sure we don't hit an infinite loop.
+
+ Returns:
+
+ The string, nil and error otherwise.
+
+ Notes:
+
+ If you use numbers as keys in the table, just the values will be used.
+
+ Example table format:
+:{ test = { howdy = "bbq", foo = { bar = "false" } } }
+
+ Example return format:
+:test
+:{
+: "howdy" "bbq"
+:
+: foo
+: {
+: "bar" "false"
+: }
+:
+:}
+
+ Revisions:
+
+ v2.10 - Initial (but tastefully stolen from a GM9 version)
+ v2.40 - Increased performance for insanely high table counts.
+]]
+function ULib.makeKeyValues( t, tab, completed )
+ ULib.checkArg( 1, "ULib.makeKeyValues", "table", t )
+
+ tab = tab or ""
+ completed = completed or {}
+ if completed[ t ] then return "" end -- We've already done this table.
+ completed[ t ] = true
+
+ local str = ""
+
+ for k, v in pairs( t ) do
+ str = str .. tab
+ if type( k ) ~= "number" then
+ str = string.format( "%s%q\t", str, tostring( k ) )
+ end
+
+ if type( v ) == "table" then
+ str = string.format( "%s\n%s{\n%s%s}\n", str, tab, ULib.makeKeyValues( v, tab .. "\t", completed ), tab )
+ elseif type( v ) == "string" then
+ str = string.format( "%s%q\n", str, v )
+ else
+ str = str .. tostring( v ) .. "\n"
+ end
+ end
+
+ return str
+end
+
+
+--[[
+ Function: toBool
+
+ Converts a bool, nil, string, or number to a bool
+
+ Parameters:
+
+ x - The string or number
+
+ Returns:
+
+ The bool
+
+ Revisions:
+
+ v2.10 - Initial.
+ v2.40 - Added ability to convert nils and bools.
+]]
+function ULib.toBool( x )
+ if type( x ) == "boolean" then return x end
+ if x == nil then return false end
+
+ if tonumber( x ) ~= nil then
+ x = math.Round( tonumber( x ) )
+ if x == 0 then
+ return false
+ else
+ return true
+ end
+ end
+
+ x = x:lower()
+ if x == "t" or x == "true" or x == "yes" or x == "y" then
+ return true
+ else
+ return false
+ end
+end
+
+
+--[[
+ Function: findVar
+
+ Given a string, find a var starting from the global namespace. This will correctly parse tables. IE, "ULib.serialize".
+
+ Parameters:
+
+ var - The variable you wish to find
+
+ Returns:
+
+ The variable or nil
+
+ Revisions:
+
+ v2.40 - Removed dependency on gmod functions.
+]]
+function ULib.findVar( var )
+ if not var then error( "Nil param passed to ULib.findVar", 2 ) end
+ local loc = ULib.explode( "%.", var )
+ local x = _G
+ for _, v in ipairs( loc ) do
+ x = x[ v ]
+ if not x then return end
+ end
+
+ return x
+end
+
+
+--[[
+ Function: throwBadArg
+
+ Throws an error similar to the lua "bad argument #x to ( expected, got ).
+
+ Parameters:
+
+ argnum - *(Optional)* The argument number that was bad.
+ fnName - *(Optional)* The name of the function being called.
+ expected - *(Optional)* The string of the type you expected.
+ data - *(Optional)* The actual data you got.
+ throwLevel - *(Optional, defaults to 3)* How many levels up to throw the error.
+
+ Returns:
+
+ Never returns, throws an error
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.throwBadArg( argnum, fnName, expected, data, throwLevel )
+ throwLevel = throwLevel or 3
+
+ local str = "bad argument"
+ if argnum then
+ str = str .. " #" .. tostring( argnum )
+ end
+ if fnName then
+ str = str .. " to " .. fnName
+ end
+ if expected or data then
+ str = str .. " ("
+ if expected then
+ str = str .. expected .. " expected"
+ end
+ if expected and data then
+ str = str .. ", "
+ end
+ if data then
+ str = str .. "got " .. type( data )
+ end
+ str = str .. ")"
+ end
+
+ error( str, throwLevel )
+end
+
+
+--[[
+ Function: checkArg
+
+ Checks to see if an arg matches what is expected, if not, calls throwBadArg().
+
+ Parameters:
+
+ argnum - *(Optional)* The argument number you're.
+ fnName - *(Optional)* The name of the function being called.
+ expected - The string of the type you expect or a table of types you expect.
+ data - The actual data you got.
+ throwLevel - *(Optional, defaults to 4)* How many levels up to throw the error.
+
+ Returns:
+
+ Never returns if the data is bad, throws an error. Otherwise returns nil.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.checkArg( argnum, fnName, expected, data, throwLevel )
+ throwLevel = throwLevel or 4
+ if type( expected ) == "string" then
+ if type( data ) == expected then
+ return
+ else
+ return ULib.throwBadArg( argnum, fnName, expected, data, throwLevel )
+ end
+ else
+ if table.HasValue( expected, type( data ) ) then
+ return
+ else
+ return ULib.throwBadArg( argnum, fnName, table.concat( expected, "," ), data, throwLevel )
+ end
+ end
+end
+
+
+--[[
+ Function: isValidSteamID
+
+ Checks to see if a given string is a valid steamid.
+
+ Parameters:
+
+ steamid - The string of the supposed steamid.
+
+ Returns:
+
+ True if it's valid, false if not.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.isValidSteamID( steamid )
+ return steamid:match( "^STEAM_%d:%d:%d+$" ) ~= nil
+end
+
+
+--[[
+ Function: isValidIP
+
+ Checks to see if a given string is a valid IPv4 address.
+
+ Parameters:
+
+ ip - The string of the supposed ip.
+
+ Returns:
+
+ True if it's valid, false if not.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.isValidIP( ip )
+ if ip:find( "^%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?$" ) then
+ return true
+ else
+ return false
+ end
+end
+
+
+--[[
+ Function: removeCommentHeader
+
+ Removes a comment header.
+
+ Parameters:
+
+ data - The string to remove the comment from.
+ comment_char - The comment char.
+
+ Returns:
+
+ Data without the comment header.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.removeCommentHeader( data, comment_char )
+ comment_char = comment_char or ";"
+ local lines = ULib.explode( "\r?\n", data )
+ local end_comment_line = 0
+ for _, line in ipairs( lines ) do
+ local trimmed = line:Trim()
+ if trimmed == "" or trimmed:sub( 1, 1 ) == comment_char then
+ end_comment_line = end_comment_line + 1
+ else
+ break
+ end
+ end
+
+ local not_comment = table.concat( lines, "\n", end_comment_line + 1 )
+ return not_comment:Trim()
+end
+
+
+--[[
+ Function: stringTimeToSeconds
+
+ Converts a string containing time information to seconds.
+
+ Parameters:
+
+ str - The time string. Defaults to minutes, "h" is for hours, "d" is for days, "w" is for weeks.
+
+ Returns:
+
+ The number of minutes represented by the string or nil if it's unable to parse the string.
+
+ Revisions:
+
+ v2.41 - Initial
+ v2.43 - Added year parameter
+]]
+function ULib.stringTimeToSeconds( str )
+ if str == nil or type( str ) == "number" then
+ return str
+ end
+
+ str = str:gsub( " ", "" )
+ local minutes = 0
+ local keycode_location = str:find( "%a" )
+ while keycode_location do
+ local keycode = str:sub( keycode_location, keycode_location )
+ local num = tonumber( str:sub( 1, keycode_location - 1 ) )
+ if not num then
+ return nil
+ end
+
+ local multiplier
+ if keycode == "h" then
+ multiplier = 60
+ elseif keycode == "d" then
+ multiplier = 60 * 24
+ elseif keycode == "w" then
+ multiplier = 60 * 24 * 7
+ elseif keycode == "y" then
+ multiplier = 60 * 24 * 365
+ else
+ return nil
+ end
+
+ str = str:sub( keycode_location + 1 )
+ keycode_location = str:find( "%a" )
+ minutes = minutes + num * multiplier
+ end
+
+ local num = 0
+ if str ~= "" then
+ num = tonumber( str )
+ end
+
+ if num == nil then
+ return nil
+ end
+
+ return minutes + num
+end
+
+
+--[[
+ Section: Inheritance
+]]
+
+--[[
+ Function: inheritsFrom
+
+ Creates a psudeo-inheritance for lua. It will search for variables that do
+ not exist in derived 'classes' in the parent 'classes', among other things
+ explained below.
+
+ Parameters:
+
+ baseClass - The class to derive from. This value *must* either be nil
+ or a class created using .
+
+ Returns:
+
+ The table of the derived class.
+
+ Revisions:
+
+ v2.40 - Initial.
+
+ Notes:
+
+ * Adapted with improvements from a lua-users inheritance tutorial
+ .
+ * Create using Class:create( ... ) or Class( ... ) (equivalent).
+ * Whatever's passed in the '...', above, is passed to
+ derived_class:instantiate(). This allows for a 'constructor'.
+
+ See Also:
+
+ *
+ *
+ *
+ *
+ *
+ *
+
+ Example:
+
+:b = inherits_from( nil )
+:function b:instantiate( ... )
+: print( "base", unpack( arg ) )
+:end
+:
+:d = inherits_from( b )
+:function d:instantiate( ... )
+: print( "derived", unpack( arg ) )
+:end
+:
+:b1 = b( "should be base" )
+:d1 = d( "should be derived" )
+:print( "d1 is d?", d1:isa( d ), "is b?", d1:isa( b ) )
+:print( "b1 is d?", b1:isa( d ), "is b?", b1:isa( b ) )
+
+ Output:
+
+:base should be base
+:derived should be derived
+:d1 is d? true is b? true
+:b1 is d? false is b? true
+]]
+function inheritsFrom( base_class )
+ local new_class = {}
+
+ -- The meta-table for INSTANCES (IE, created with Class:create())
+ local instance_mt = { __index = new_class, class=new_class, base_class=base_class }
+
+ -- The meta-table for the root_class (this will only ever have one table associated with it)
+ local class_mt = table.Copy( instance_mt ) -- Only a few differences so copy
+ class_mt.__index = base_class or root_class -- Use base or our special meta-base
+ class_mt.__call = root_class.call -- Set up call alias
+ class_mt.class = new_class -- Set up alias to ourself
+ class_mt.instance_mt = instance_mt -- Need this for root_class:create()
+
+ setmetatable( new_class, class_mt )
+
+ return new_class
+end
+
+
+--[[
+ Table: root_class
+
+ This is a local table that holds our functions that we want *all* classes
+ to have.
+]]
+root_class = {}
+
+
+--[[
+ Function: root_class:call
+
+ This is a utility function used by the metatable __call to resolve Class( ... ) to Class:create( ... ).
+
+ Parameters:
+
+ parent_table - The table of the caller.
+ ... - Extra construction parameters, passed to Class:instantiate.
+
+ Returns:
+
+ The 'class instance'.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function root_class.call( parent_table, ... )
+ return parent_table:class():create( ... )
+end
+
+
+--[[
+ Function: root_class:create
+
+ This is used to create new 'class instances'.
+
+ Parameters:
+
+ ... - Extra construction parameters, passed to Class:instantiate.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function root_class:create( ... )
+ local newinst = {}
+ setmetatable( newinst, getmetatable( self ).instance_mt )
+ newinst:instantiate( ... ) -- 'Constructor'
+ return newinst
+end
+
+
+-- Return the class object of the instance
+function root_class:class()
+ return getmetatable( self ).class
+end
+
+
+-- Return the super class object of the instance
+function root_class:superClass()
+ base_class = getmetatable( self ).base_class
+ return base_class ~= root_class and base_class or nil -- Nil if root class
+end
+
+
+-- We need to make sure this func exists, but can be overridden
+function root_class:instantiate()
+end
+
+
+-- Return true if the caller is an instance of theClass
+function root_class:isa( target_class )
+ local cur_class = self:class()
+
+ while cur_class and not b_isa do
+ if cur_class == target_class then
+ return true
+ else
+ cur_class = cur_class:superClass()
+ end
+ end
+
+ return false
+end
+
+function isClass( obj )
+ return type( obj ) == "table" and type( obj.isa ) == "function" and obj:isa( root_class )
+end
+
+
+-- This wonderful bit of following code will make sure that no rogue coder can screw us up by changing the value of '_'
+_ = nil -- Make sure we're starting out right.
+local meta = getmetatable( _G ) or {}
+if type( meta ) == "boolean" then return end -- Metatable is protected, so we aren't able to run this code without erroring.
+local old__newindex = meta.__newindex
+setmetatable( _G, meta )
+function meta.__newindex( t, k, v )
+ if k == "_" then
+ -- If you care enough to fix bad scripts uncomment this following line.
+ -- error( "attempt to modify global variable '_'", 2 )
+ return
+ end
+
+ if old__newindex then
+ old__newindex( t, k, v )
+ else
+ rawset( t, k, v )
+ end
+end
diff --git a/lua/ulib/shared/tables.lua b/lua/ulib/shared/tables.lua
index 5609bae..6d22863 100644
--- a/lua/ulib/shared/tables.lua
+++ b/lua/ulib/shared/tables.lua
@@ -1,163 +1,163 @@
---[[
- Title: Tables
-
- Some table helpers.
-]]
-
--- Based off "RecursiveReadOnlyTables" by VeLoSo (http://lua-users.org/wiki/RecursiveReadOnlyTables)
-
--- cache the metatables of all existing read-only tables,
--- so our functions can get to them, but user code can't
-local metatable_cache = setmetatable( {}, { __mode = "k" } )
-
-local function make_getter( real_table )
- local function getter( dummy, key )
- local ret = real_table[ key ]
- if type( ret ) == "table" and not metatable_cache[ ret ] then
- ret = ULib.makeReadOnly( ret )
- end
- return ret
- end
- return getter
-end
-
-local function setter()
- ULib.error( "Attempt to modify read-only table!" )
-end
-
-local function make_pairs( real_table )
- local function pairs()
- local key, value, cur_key = nil, nil, nil
- local function nexter() -- both args dummy
- key, value = next( real_table, cur_key )
- cur_key = key
- if type( key ) == "table" and not metatable_cache[ key ] then
- key = ULib.makeReadOnly( key )
- end
- if type( value ) == "table" and not metatable_cache[ value ] then
- value = ULib.makeReadOnly( value )
- end
- return key, value
- end
- return nexter -- values 2 and 3 dummy
- end
- return pairs
-end
-
-
---[[
- Function: makeReadOnly
-
- Makes a table and all recursive tables read-only
-
- Parameters:
-
- t - The table to make read-only
-
- Returns:
-
- The table read-only'fied
-]]
-function ULib.makeReadOnly( t )
- local new={}
- local mt={
- __metatable = "read only table",
- __index = make_getter( t ),
- __newindex = setter,
- __pairs = make_pairs( t ),
- __type = "read-only table" }
- setmetatable( new, mt )
- metatable_cache[ new ] = mt
- return new
-end
-
-
---[[
- Function: ropairs
-
- The equivalent of "pairs" for a readonly table, since "pairs" won't work.
-
- Parameters:
-
- t - The table
-]]
-function ULib.ropairs( t )
- local mt = metatable_cache[ t ]
- if mt==nil then
- ULib.error( "bad argument #1 to 'ropairs' (read-only table expected, got " .. type(t) .. ")" )
- end
- return mt.__pairs()
-end
-
-
---[[
- Function: findInTable
-
- Finds a value in a table. As opposed to table.HasValue(), this function will *only* check numeric keys, and will return a number of where the value is.
-
- Parameters:
-
- t - The table to check
- check - The value to check if it exists in t. Can be any type.
- init - *(Optional, defaults to 1)* The value to start from.
- last - *(Optional, defaults to the length of the table)* The value to end at.
- recursive - *(Optional, default to false)* If true, it will check any subtables it comes across.
-
- Returns:
-
- The number of the key where check resides, false if none is found. If init > last it returns false as well.
-]]
-function ULib.findInTable( t, check, init, last, recursive )
- init = init or 1
- last = last or #t
-
- if init > last then return false end
-
- for i=init, last do
- if t[ i ] == check then return i end
-
- if type( t[ i ] ) == "table" and recursive then return ULib.findInTable( v, check, 1, recursive ) end
- end
-
- return false
-end
-
---[[
- Function: matrixTable
-
- Splits a table into a number of given columns. Does not change original table.
-
- Parameters:
-
- t - The table to split
- columns, The number of columns to create
-
- Returns:
-
- The new table with the column being the first key and the row being the second key.
-
- Revisions:
-
- v2.10 - Initial
-]]
-function ULib.matrixTable( t, columns )
- local baserows = math.floor( #t / columns )
- local remainder = math.fmod( #t, columns )
- local nt = {} -- New table after we process
- local curn = 1 -- What value to grab next from our old table
-
- for i=1, columns do
- local numtograb = baserows
- if i <= remainder then
- numtograb = baserows + 1
- end
-
- nt[ i ] = {}
- for n=0, numtograb - 1 do
- table.insert( nt[ i ], t[ curn + n ] )
- end
- curn = curn + numtograb
- end
-
- return nt
-end
+--[[
+ Title: Tables
+
+ Some table helpers.
+]]
+
+-- Based off "RecursiveReadOnlyTables" by VeLoSo (http://lua-users.org/wiki/RecursiveReadOnlyTables)
+
+-- cache the metatables of all existing read-only tables,
+-- so our functions can get to them, but user code can't
+local metatable_cache = setmetatable( {}, { __mode = "k" } )
+
+local function make_getter( real_table )
+ local function getter( dummy, key )
+ local ret = real_table[ key ]
+ if type( ret ) == "table" and not metatable_cache[ ret ] then
+ ret = ULib.makeReadOnly( ret )
+ end
+ return ret
+ end
+ return getter
+end
+
+local function setter()
+ ULib.error( "Attempt to modify read-only table!" )
+end
+
+local function make_pairs( real_table )
+ local function pairs()
+ local key, value, cur_key = nil, nil, nil
+ local function nexter() -- both args dummy
+ key, value = next( real_table, cur_key )
+ cur_key = key
+ if type( key ) == "table" and not metatable_cache[ key ] then
+ key = ULib.makeReadOnly( key )
+ end
+ if type( value ) == "table" and not metatable_cache[ value ] then
+ value = ULib.makeReadOnly( value )
+ end
+ return key, value
+ end
+ return nexter -- values 2 and 3 dummy
+ end
+ return pairs
+end
+
+
+--[[
+ Function: makeReadOnly
+
+ Makes a table and all recursive tables read-only
+
+ Parameters:
+
+ t - The table to make read-only
+
+ Returns:
+
+ The table read-only'fied
+]]
+function ULib.makeReadOnly( t )
+ local new={}
+ local mt={
+ __metatable = "read only table",
+ __index = make_getter( t ),
+ __newindex = setter,
+ __pairs = make_pairs( t ),
+ __type = "read-only table" }
+ setmetatable( new, mt )
+ metatable_cache[ new ] = mt
+ return new
+end
+
+
+--[[
+ Function: ropairs
+
+ The equivalent of "pairs" for a readonly table, since "pairs" won't work.
+
+ Parameters:
+
+ t - The table
+]]
+function ULib.ropairs( t )
+ local mt = metatable_cache[ t ]
+ if mt==nil then
+ ULib.error( "bad argument #1 to 'ropairs' (read-only table expected, got " .. type(t) .. ")" )
+ end
+ return mt.__pairs()
+end
+
+
+--[[
+ Function: findInTable
+
+ Finds a value in a table. As opposed to table.HasValue(), this function will *only* check numeric keys, and will return a number of where the value is.
+
+ Parameters:
+
+ t - The table to check
+ check - The value to check if it exists in t. Can be any type.
+ init - *(Optional, defaults to 1)* The value to start from.
+ last - *(Optional, defaults to the length of the table)* The value to end at.
+ recursive - *(Optional, default to false)* If true, it will check any subtables it comes across.
+
+ Returns:
+
+ The number of the key where check resides, false if none is found. If init > last it returns false as well.
+]]
+function ULib.findInTable( t, check, init, last, recursive )
+ init = init or 1
+ last = last or #t
+
+ if init > last then return false end
+
+ for i=init, last do
+ if t[ i ] == check then return i end
+
+ if type( t[ i ] ) == "table" and recursive then return ULib.findInTable( v, check, 1, recursive ) end
+ end
+
+ return false
+end
+
+--[[
+ Function: matrixTable
+
+ Splits a table into a number of given columns. Does not change original table.
+
+ Parameters:
+
+ t - The table to split
+ columns, The number of columns to create
+
+ Returns:
+
+ The new table with the column being the first key and the row being the second key.
+
+ Revisions:
+
+ v2.10 - Initial
+]]
+function ULib.matrixTable( t, columns )
+ local baserows = math.floor( #t / columns )
+ local remainder = math.fmod( #t, columns )
+ local nt = {} -- New table after we process
+ local curn = 1 -- What value to grab next from our old table
+
+ for i=1, columns do
+ local numtograb = baserows
+ if i <= remainder then
+ numtograb = baserows + 1
+ end
+
+ nt[ i ] = {}
+ for n=0, numtograb - 1 do
+ table.insert( nt[ i ], t[ curn + n ] )
+ end
+ curn = curn + numtograb
+ end
+
+ return nt
+end
diff --git a/lua/ulib/shared/util.lua b/lua/ulib/shared/util.lua
index c3f8e70..b3f91fb 100644
--- a/lua/ulib/shared/util.lua
+++ b/lua/ulib/shared/util.lua
@@ -1,577 +1,577 @@
---[[
- Title: Utilities
-
- Some utility functions. Unlike the functions in misc.lua, this file only holds HL2 specific functions.
-]]
-
-local dataFolder = "data"
---[[
- Function: fileExists
-
- Checks for the existence of a file by path.
-
- Parameters:
-
- f - The path to check, rooted at the garry's mod root directory.
- noMount - *(Optional)* If true, will not look in mounted directories.
-
- Returns:
-
- True if the file exists, false otherwise.
-
- Revisions:
-
- v2.51 - Initial revision (tired of Garry changing his API all the time).
- v2.70 - Added noMount parameter to *only* look in mod directory.
-]]
-function ULib.fileExists( f, noMount )
- if noMount then return file.Exists( f, "MOD" ) end
-
- local isDataFolder = f:lower():sub( 1, dataFolder:len() ) ~= dataFolder
- fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
-
- return file.Exists( f, "GAME" ) or (isDataFolder and file.Exists( fWoData, "DATA" ))
-end
-
---[[
- Function: fileRead
-
- Reads a file and returns the contents. This function is not very forgiving on providing oddly formatted filepaths.
-
- Parameters:
-
- f - The file to read, rooted at the garrysmod directory.
- noMount - *(Optional)* If true, will not look in mounted directories.
-
- Returns:
-
- The file contents or nil if the file does not exist.
-
- Revisions:
-
- v2.51 - Initial revision (tired of Garry changing his API all the time).
- v2.70 - Added noMount parameter to *only* look in mod directory.
-]]
-function ULib.fileRead( f, noMount )
- local existsWoMount = ULib.fileExists( f, true )
-
- if noMount then
- if not existsWoMount then
- return nil
- end
-
- return file.Read( f, "MOD" )
- end
-
- local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
- fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
-
- if not existsWoMount and not ULib.fileExists( f ) then
- return nil
- end
-
- if not isDataFolder then
- return file.Read( f, "GAME" )
- else
- -- We want to prefer any data files at the root, but allow for mounted directories
- if existsWoMount then
- return file.Read( fWoData, "DATA" )
- else
- return file.Read( f, "GAME" )
- end
- end
-end
-
---[[
- Function: fileWrite
-
- Writes file content.
-
- Parameters:
-
- f - The file path to write to, rooted at the garrysmod directory.
- content - The content to write.
-
- Revisions:
-
- v2.51 - Initial revision (tired of Garry changing his API all the time).
-]]
-function ULib.fileWrite( f, content )
- local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
- fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
-
- if not isDataFolder then return nil end
-
- file.Write( fWoData, content )
-end
-
-
---[[
- Function: fileAppend
-
- Append to file content.
-
- Parameters:
-
- f - The file path to append to, rooted at the garrysmod directory.
- content - The content to append.
-
- Revisions:
-
- v2.51 - Initial revision (tired of Garry changing his API all the time).
-]]
-function ULib.fileAppend( f, content )
- local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
- fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
-
- if not isDataFolder then return nil end
-
- file.Append( fWoData, content )
-end
-
-
---[[
- Function: fileCreateDir
-
- Create a directory.
-
- Parameters:
-
- f - The directory path to create, rooted at the garrysmod directory.
-
- Revisions:
-
- v2.51 - Initial revision (tired of Garry changing his API all the time).
-]]
-function ULib.fileCreateDir( f )
- local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
- fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
-
- if not isDataFolder then return nil end
-
- file.CreateDir( fWoData )
-end
-
-
---[[
- Function: fileDelete
-
- Delete file contents.
-
- Parameters:
-
- f - The file path to delete, rooted at the garrysmod directory.
-
- Revisions:
-
- v2.51 - Initial revision (tired of Garry changing his API all the time).
-]]
-function ULib.fileDelete( f )
- local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
- fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
-
- if not isDataFolder then return nil end
-
- file.Delete( fWoData )
-end
-
-
---[[
- Function: fileIsDir
-
- Is file a directory?
-
- Parameters:
-
- f - The file path to check, rooted at the garrysmod directory.
- noMount - *(Optional)* If true, will not look in mounted directories.
-
- Returns:
-
- True if dir, false otherwise.
-
- Revisions:
-
- v2.51 - Initial revision (tired of Garry changing his API all the time).
- v2.70 - Added noMount parameter to *only* look in mod directory.
-]]
-function ULib.fileIsDir( f, noMount )
- if not noMount then
- return file.IsDir( f, "GAME" )
- else
- return file.IsDir( f, "MOD" )
- end
-end
-
-
---[[
- Function: execFile
-
- Executes a file on the console. Use this instead of the "exec" command when the config lies outside the cfg folder.
-
- Parameters:
-
- f - The file, relative to the garrysmod folder.
- queueName - The queue name to ULib.namedQueueFunctionCall to use.
- noMount - *(Optional)* If true, will not look in mounted directories.
-
- Revisions:
-
- v2.40 - No longer strips comments, removed ability to execute on players.
- v2.50 - Added option to conform to Garry's API changes and queueName to specify queue name to use.
- v2.51 - Removed option parameter.
- v2.70 - Added noMount parameter to *only* look in mod directory.
-]]
-function ULib.execFile( f, queueName, noMount )
- if not ULib.fileExists( f, noMount ) then
- ULib.error( "Called execFile with invalid file! " .. f )
- return
- end
-
- ULib.execString( ULib.fileRead( f, noMount ), queueName )
-end
-
-
---[[
- Function: execString
-
- Just like , except acts on newline-delimited strings.
-
- Parameters:
-
- f - The string.
- queueName - The queue name to ULib.namedQueueFunctionCall to use.
-
- Revisions:
-
- v2.40 - Initial.
- v2.50 - Added queueName to specify queue name to use. Removed ability to execute on players.
-]]
-function ULib.execString( f, queueName )
- local lines = string.Explode( "\n", f )
-
- local buffer = ""
- local buffer_lines = 0
- local exec = "exec "
- for _, line in ipairs( lines ) do
- line = string.Trim( line )
- if line:lower():sub( 1, exec:len() ) == exec then
- local dummy, dummy, cfg = line:lower():find( "^exec%s+([%w%.]+)%s*/?/?.*$")
- if not cfg:find( ".cfg", 1, true ) then cfg = cfg .. ".cfg" end -- Add it if it's not there
- ULib.execFile( "cfg/" .. cfg, queueName )
- elseif line ~= "" then
- buffer = buffer .. line .. "\n"
- buffer_lines = buffer_lines + 1
-
- if buffer_lines >= 10 then
- ULib.namedQueueFunctionCall( queueName, ULib.consoleCommand, buffer )
- buffer_lines = 0
- buffer = ""
- end
- end
- end
-
- if buffer_lines > 0 then
- ULib.namedQueueFunctionCall( queueName, ULib.consoleCommand, buffer )
- end
-end
-
-
---[[
- Function: serialize
-
- Serializes a variable. It basically converts a variable into a runnable code string. It works correctly with inline tables.
-
- Parameters:
-
- v - The variable you wish to serialize
-
- Returns:
-
- The string of the serialized variable
-
- Revisions:
-
- v2.40 - Can now serialize entities and players
-]]
-function ULib.serialize( v )
- local t = type( v )
- local str
- if t == "string" then
- str = string.format( "%q", v )
- elseif t == "boolean" or t == "number" then
- str = tostring( v )
- elseif t == "table" then
- str = table.ToString( v )
- elseif t == "Vector" then
- str = "Vector(" .. v.x .. "," .. v.y .. "," .. v.z .. ")"
- elseif t == "Angle" then
- str = "Angle(" .. v.pitch .. "," .. v.yaw .. "," .. v.roll .. ")"
- elseif t == "Player" then
- str = tostring( v )
- elseif t == "Entity" then
- str = tostring( v )
- elseif t == "nil" then
- str = "nil"
- else
- ULib.error( "Passed an invalid parameter to serialize! (type: " .. t .. ")" )
- return
- end
- return str
-end
-
-
---[[
- Function: isSandbox
-
- Returns true if the current gamemode is sandbox or is derived from sandbox.
-]]
-function ULib.isSandbox()
- return GAMEMODE.IsSandboxDerived
-end
-
-
-local function insertResult( files, result, relDir )
- if not relDir then
- table.insert( files, result )
- else
- table.insert( files, relDir .. "/" .. result )
- end
-end
-
---[[
- Function: filesInDir
-
- Returns files in directory.
-
- Parameters:
-
- dir - The dir to look for files in.
- recurse - *(Optional, defaults to false)* If true, searches directories recursively.
- noMount - *(Optional)* If true, will not look in mounted directories.
- root - *INTERNAL USE ONLY* This helps with recursive functions.
-
- Revisions:
-
- v2.10 - Initial (But dragged over from GM9 archive).
- v2.40 - Fixed (was completely broken).
- v2.50 - Now assumes paths relative to base folder.
- v2.60 - Fix for Garry API-changes
- v2.70 - Added noMount parameter to *only* look in mod directory.
-]]
-function ULib.filesInDir( dir, recurse, noMount, root )
- if not ULib.fileIsDir( dir ) then
- return nil
- end
-
- local files = {}
- local relDir
- if root then
- relDir = dir:gsub( root .. "[\\/]", "" )
- end
- root = root or dir
-
- local resultFiles, resultFolders = file.Find( dir .. "/*", not noMount and "GAME" or "MOD" )
-
- for i=1, #resultFiles do
- insertResult( files, resultFiles[ i ], relDir )
- end
-
- for i=1, #resultFolders do
- if recurse then
- files = table.Add( files, ULib.filesInDir( dir .. "/" .. resultFolders[ i ], recurse, noMount, root ) )
- else
- insertResult( files, resultFolders[ i ], relDir )
- end
- end
-
- return files
-end
-
-
--- Helper function for
-local stacks = {}
-local function onThink()
- local remove = true
- for queueName, stack in pairs( stacks ) do
- local num = #stack
- if num > 0 then
- remove = false
- local b, e = pcall( stack[ 1 ].fn, unpack( stack[ 1 ], 1, stack[ 1 ].n ) )
- if not b then
- ErrorNoHalt( "ULib queue error: " .. tostring( e ) .. "\n" )
- end
- table.remove( stack, 1 ) -- Remove the first inserted item. This is FIFO
- end
- end
-
- if remove then
- hook.Remove( "Think", "ULibQueueThink" )
- end
-end
-
-
---[[
- Function: queueFunctionCall
-
- Adds a function call to the queue to be called. Guaranteed to be called sometime after the current frame. Very handy
- when you need to delay a call for some reason. Uses a think hook, but it's only hooked when there's stuff in the queue.
-
- Parameters:
-
- fn - The function to call
- ... - *(Optional)* The parameters to pass to the function
-
- Revisions:
-
- v2.40 - Initial (But dragged over from UPS).
-]]
-function ULib.queueFunctionCall( fn, ... )
- if type( fn ) ~= "function" then
- error( "queueFunctionCall received a bad function", 2 )
- return
- end
-
- ULib.namedQueueFunctionCall( "defaultQueueName", fn, ... )
-end
-
---[[
- Function: namedQueueFunctionCall
-
- Exactly like , but allows for separately running queues to exist.
-
- Parameters:
-
- queueName - The unique name of the queue (the queue group)
- fn - The function to call
- ... - *(Optional)* The parameters to pass to the function
-
- Revisions:
-
- v2.50 - Initial.
-]]
-function ULib.namedQueueFunctionCall( queueName, fn, ... )
- queueName = queueName or "defaultQueueName"
- if type( fn ) ~= "function" then
- error( "queueFunctionCall received a bad function", 2 )
- return
- end
-
- stacks[ queueName ] = stacks[ queueName ] or {}
- table.insert( stacks[ queueName ], { fn=fn, n=select( "#", ... ), ... } )
- hook.Add( "Think", "ULibQueueThink", onThink, HOOK_MONITOR_HIGH )
-end
-
-
---[[
- Function: backupFile
-
- Copies a file to a backup file. If a backup file already exists, makes incrementing numbered backup files.
-
- Parameters:
-
- f - The file to backup, rooted in the garrysmod directory.
-
- Returns:
-
- The pathname of the file it was backed up to.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.backupFile( f )
- local contents = ULib.fileRead( f )
- local filename = f:GetFileFromFilename():sub( 1, -5 ) -- Remove '.txt'
- local folder = f:GetPathFromFilename()
-
- local num = 1
- local targetPath = folder .. filename .. "_backup.txt"
- while ULib.fileExists( targetPath ) do
- num = num + 1
- targetPath = folder .. filename .. "_backup" .. num .. ".txt"
- end
-
- -- We now have a filename that doesn't yet exist!
- ULib.fileWrite( targetPath, contents )
-
- return targetPath
-end
-
---[[
- Function: nameCheck
-
- Checks all players' names at regular intervals to detect name changes. Calls ULibPlayerNameChanged if the name changed. *DO NOT CALL DIRECTLY*
-
- Revisions:
-
- 2.20 - Initial
-]]
-function ULib.nameCheck()
- local players = player.GetAll()
- for _, ply in ipairs( players ) do
- if not ply.ULibLastKnownName then ply.ULibLastKnownName = ply:Nick() end
-
- if ply.ULibLastKnownName ~= ply:Nick() then
- hook.Call( ULib.HOOK_PLAYER_NAME_CHANGED, nil, ply, ply.ULibLastKnownName, ply:Nick() )
- ply.ULibLastKnownName = ply:Nick()
- end
- end
-end
-timer.Create( "ULibNameCheck", 1, 0, ULib.nameCheck )
-
-
---[[
- Function: getPlyByUID
-
- Parameters:
-
- uid - The uid to lookup.
-
- Returns:
-
- The player that has the specified unique id, nil if none exists.
-
- Revisions:
-
- v2.40 - Initial.
-]]
-function ULib.getPlyByUID( uid )
- local players = player.GetAll()
- for _, ply in ipairs( players ) do
- if ply:UniqueID() == uid then
- return ply
- end
- end
-
- return nil
-end
-
-
---[[
- Function: pcallError
-
- An adaptation of a function that used to exist before GM13, allows you to
- call functions safely and print errors (if it errors).
-
- Parameters:
-
- ... - Arguments to pass to the function
-
- Returns:
-
- The same thing regular pcall returns
-
- Revisions:
-
- v2.50 - Initial.
-]]
-function ULib.pcallError( ... )
- local returns = { pcall( ... ) }
-
- if not returns[ 1 ] then -- The status flag
- ErrorNoHalt( returns[ 2 ] ) -- The error message
- end
-
- return unpack( returns )
-end
+--[[
+ Title: Utilities
+
+ Some utility functions. Unlike the functions in misc.lua, this file only holds HL2 specific functions.
+]]
+
+local dataFolder = "data"
+--[[
+ Function: fileExists
+
+ Checks for the existence of a file by path.
+
+ Parameters:
+
+ f - The path to check, rooted at the garry's mod root directory.
+ noMount - *(Optional)* If true, will not look in mounted directories.
+
+ Returns:
+
+ True if the file exists, false otherwise.
+
+ Revisions:
+
+ v2.51 - Initial revision (tired of Garry changing his API all the time).
+ v2.70 - Added noMount parameter to *only* look in mod directory.
+]]
+function ULib.fileExists( f, noMount )
+ if noMount then return file.Exists( f, "MOD" ) end
+
+ local isDataFolder = f:lower():sub( 1, dataFolder:len() ) ~= dataFolder
+ fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
+
+ return file.Exists( f, "GAME" ) or (isDataFolder and file.Exists( fWoData, "DATA" ))
+end
+
+--[[
+ Function: fileRead
+
+ Reads a file and returns the contents. This function is not very forgiving on providing oddly formatted filepaths.
+
+ Parameters:
+
+ f - The file to read, rooted at the garrysmod directory.
+ noMount - *(Optional)* If true, will not look in mounted directories.
+
+ Returns:
+
+ The file contents or nil if the file does not exist.
+
+ Revisions:
+
+ v2.51 - Initial revision (tired of Garry changing his API all the time).
+ v2.70 - Added noMount parameter to *only* look in mod directory.
+]]
+function ULib.fileRead( f, noMount )
+ local existsWoMount = ULib.fileExists( f, true )
+
+ if noMount then
+ if not existsWoMount then
+ return nil
+ end
+
+ return file.Read( f, "MOD" )
+ end
+
+ local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
+ fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
+
+ if not existsWoMount and not ULib.fileExists( f ) then
+ return nil
+ end
+
+ if not isDataFolder then
+ return file.Read( f, "GAME" )
+ else
+ -- We want to prefer any data files at the root, but allow for mounted directories
+ if existsWoMount then
+ return file.Read( fWoData, "DATA" )
+ else
+ return file.Read( f, "GAME" )
+ end
+ end
+end
+
+--[[
+ Function: fileWrite
+
+ Writes file content.
+
+ Parameters:
+
+ f - The file path to write to, rooted at the garrysmod directory.
+ content - The content to write.
+
+ Revisions:
+
+ v2.51 - Initial revision (tired of Garry changing his API all the time).
+]]
+function ULib.fileWrite( f, content )
+ local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
+ fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
+
+ if not isDataFolder then return nil end
+
+ file.Write( fWoData, content )
+end
+
+
+--[[
+ Function: fileAppend
+
+ Append to file content.
+
+ Parameters:
+
+ f - The file path to append to, rooted at the garrysmod directory.
+ content - The content to append.
+
+ Revisions:
+
+ v2.51 - Initial revision (tired of Garry changing his API all the time).
+]]
+function ULib.fileAppend( f, content )
+ local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
+ fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
+
+ if not isDataFolder then return nil end
+
+ file.Append( fWoData, content )
+end
+
+
+--[[
+ Function: fileCreateDir
+
+ Create a directory.
+
+ Parameters:
+
+ f - The directory path to create, rooted at the garrysmod directory.
+
+ Revisions:
+
+ v2.51 - Initial revision (tired of Garry changing his API all the time).
+]]
+function ULib.fileCreateDir( f )
+ local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
+ fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
+
+ if not isDataFolder then return nil end
+
+ file.CreateDir( fWoData )
+end
+
+
+--[[
+ Function: fileDelete
+
+ Delete file contents.
+
+ Parameters:
+
+ f - The file path to delete, rooted at the garrysmod directory.
+
+ Revisions:
+
+ v2.51 - Initial revision (tired of Garry changing his API all the time).
+]]
+function ULib.fileDelete( f )
+ local isDataFolder = f:lower():sub( 1, dataFolder:len() ) == dataFolder
+ fWoData = f:sub( dataFolder:len() + 2 ) -- +2 removes path seperator
+
+ if not isDataFolder then return nil end
+
+ file.Delete( fWoData )
+end
+
+
+--[[
+ Function: fileIsDir
+
+ Is file a directory?
+
+ Parameters:
+
+ f - The file path to check, rooted at the garrysmod directory.
+ noMount - *(Optional)* If true, will not look in mounted directories.
+
+ Returns:
+
+ True if dir, false otherwise.
+
+ Revisions:
+
+ v2.51 - Initial revision (tired of Garry changing his API all the time).
+ v2.70 - Added noMount parameter to *only* look in mod directory.
+]]
+function ULib.fileIsDir( f, noMount )
+ if not noMount then
+ return file.IsDir( f, "GAME" )
+ else
+ return file.IsDir( f, "MOD" )
+ end
+end
+
+
+--[[
+ Function: execFile
+
+ Executes a file on the console. Use this instead of the "exec" command when the config lies outside the cfg folder.
+
+ Parameters:
+
+ f - The file, relative to the garrysmod folder.
+ queueName - The queue name to ULib.namedQueueFunctionCall to use.
+ noMount - *(Optional)* If true, will not look in mounted directories.
+
+ Revisions:
+
+ v2.40 - No longer strips comments, removed ability to execute on players.
+ v2.50 - Added option to conform to Garry's API changes and queueName to specify queue name to use.
+ v2.51 - Removed option parameter.
+ v2.70 - Added noMount parameter to *only* look in mod directory.
+]]
+function ULib.execFile( f, queueName, noMount )
+ if not ULib.fileExists( f, noMount ) then
+ ULib.error( "Called execFile with invalid file! " .. f )
+ return
+ end
+
+ ULib.execString( ULib.fileRead( f, noMount ), queueName )
+end
+
+
+--[[
+ Function: execString
+
+ Just like , except acts on newline-delimited strings.
+
+ Parameters:
+
+ f - The string.
+ queueName - The queue name to ULib.namedQueueFunctionCall to use.
+
+ Revisions:
+
+ v2.40 - Initial.
+ v2.50 - Added queueName to specify queue name to use. Removed ability to execute on players.
+]]
+function ULib.execString( f, queueName )
+ local lines = string.Explode( "\n", f )
+
+ local buffer = ""
+ local buffer_lines = 0
+ local exec = "exec "
+ for _, line in ipairs( lines ) do
+ line = string.Trim( line )
+ if line:lower():sub( 1, exec:len() ) == exec then
+ local dummy, dummy, cfg = line:lower():find( "^exec%s+([%w%.]+)%s*/?/?.*$")
+ if not cfg:find( ".cfg", 1, true ) then cfg = cfg .. ".cfg" end -- Add it if it's not there
+ ULib.execFile( "cfg/" .. cfg, queueName )
+ elseif line ~= "" then
+ buffer = buffer .. line .. "\n"
+ buffer_lines = buffer_lines + 1
+
+ if buffer_lines >= 10 then
+ ULib.namedQueueFunctionCall( queueName, ULib.consoleCommand, buffer )
+ buffer_lines = 0
+ buffer = ""
+ end
+ end
+ end
+
+ if buffer_lines > 0 then
+ ULib.namedQueueFunctionCall( queueName, ULib.consoleCommand, buffer )
+ end
+end
+
+
+--[[
+ Function: serialize
+
+ Serializes a variable. It basically converts a variable into a runnable code string. It works correctly with inline tables.
+
+ Parameters:
+
+ v - The variable you wish to serialize
+
+ Returns:
+
+ The string of the serialized variable
+
+ Revisions:
+
+ v2.40 - Can now serialize entities and players
+]]
+function ULib.serialize( v )
+ local t = type( v )
+ local str
+ if t == "string" then
+ str = string.format( "%q", v )
+ elseif t == "boolean" or t == "number" then
+ str = tostring( v )
+ elseif t == "table" then
+ str = table.ToString( v )
+ elseif t == "Vector" then
+ str = "Vector(" .. v.x .. "," .. v.y .. "," .. v.z .. ")"
+ elseif t == "Angle" then
+ str = "Angle(" .. v.pitch .. "," .. v.yaw .. "," .. v.roll .. ")"
+ elseif t == "Player" then
+ str = tostring( v )
+ elseif t == "Entity" then
+ str = tostring( v )
+ elseif t == "nil" then
+ str = "nil"
+ else
+ ULib.error( "Passed an invalid parameter to serialize! (type: " .. t .. ")" )
+ return
+ end
+ return str
+end
+
+
+--[[
+ Function: isSandbox
+
+ Returns true if the current gamemode is sandbox or is derived from sandbox.
+]]
+function ULib.isSandbox()
+ return GAMEMODE.IsSandboxDerived
+end
+
+
+local function insertResult( files, result, relDir )
+ if not relDir then
+ table.insert( files, result )
+ else
+ table.insert( files, relDir .. "/" .. result )
+ end
+end
+
+--[[
+ Function: filesInDir
+
+ Returns files in directory.
+
+ Parameters:
+
+ dir - The dir to look for files in.
+ recurse - *(Optional, defaults to false)* If true, searches directories recursively.
+ noMount - *(Optional)* If true, will not look in mounted directories.
+ root - *INTERNAL USE ONLY* This helps with recursive functions.
+
+ Revisions:
+
+ v2.10 - Initial (But dragged over from GM9 archive).
+ v2.40 - Fixed (was completely broken).
+ v2.50 - Now assumes paths relative to base folder.
+ v2.60 - Fix for Garry API-changes
+ v2.70 - Added noMount parameter to *only* look in mod directory.
+]]
+function ULib.filesInDir( dir, recurse, noMount, root )
+ if not ULib.fileIsDir( dir ) then
+ return nil
+ end
+
+ local files = {}
+ local relDir
+ if root then
+ relDir = dir:gsub( root .. "[\\/]", "" )
+ end
+ root = root or dir
+
+ local resultFiles, resultFolders = file.Find( dir .. "/*", not noMount and "GAME" or "MOD" )
+
+ for i=1, #resultFiles do
+ insertResult( files, resultFiles[ i ], relDir )
+ end
+
+ for i=1, #resultFolders do
+ if recurse then
+ files = table.Add( files, ULib.filesInDir( dir .. "/" .. resultFolders[ i ], recurse, noMount, root ) )
+ else
+ insertResult( files, resultFolders[ i ], relDir )
+ end
+ end
+
+ return files
+end
+
+
+-- Helper function for
+local stacks = {}
+local function onThink()
+ local remove = true
+ for queueName, stack in pairs( stacks ) do
+ local num = #stack
+ if num > 0 then
+ remove = false
+ local b, e = pcall( stack[ 1 ].fn, unpack( stack[ 1 ], 1, stack[ 1 ].n ) )
+ if not b then
+ ErrorNoHalt( "ULib queue error: " .. tostring( e ) .. "\n" )
+ end
+ table.remove( stack, 1 ) -- Remove the first inserted item. This is FIFO
+ end
+ end
+
+ if remove then
+ hook.Remove( "Think", "ULibQueueThink" )
+ end
+end
+
+
+--[[
+ Function: queueFunctionCall
+
+ Adds a function call to the queue to be called. Guaranteed to be called sometime after the current frame. Very handy
+ when you need to delay a call for some reason. Uses a think hook, but it's only hooked when there's stuff in the queue.
+
+ Parameters:
+
+ fn - The function to call
+ ... - *(Optional)* The parameters to pass to the function
+
+ Revisions:
+
+ v2.40 - Initial (But dragged over from UPS).
+]]
+function ULib.queueFunctionCall( fn, ... )
+ if type( fn ) ~= "function" then
+ error( "queueFunctionCall received a bad function", 2 )
+ return
+ end
+
+ ULib.namedQueueFunctionCall( "defaultQueueName", fn, ... )
+end
+
+--[[
+ Function: namedQueueFunctionCall
+
+ Exactly like , but allows for separately running queues to exist.
+
+ Parameters:
+
+ queueName - The unique name of the queue (the queue group)
+ fn - The function to call
+ ... - *(Optional)* The parameters to pass to the function
+
+ Revisions:
+
+ v2.50 - Initial.
+]]
+function ULib.namedQueueFunctionCall( queueName, fn, ... )
+ queueName = queueName or "defaultQueueName"
+ if type( fn ) ~= "function" then
+ error( "queueFunctionCall received a bad function", 2 )
+ return
+ end
+
+ stacks[ queueName ] = stacks[ queueName ] or {}
+ table.insert( stacks[ queueName ], { fn=fn, n=select( "#", ... ), ... } )
+ hook.Add( "Think", "ULibQueueThink", onThink, HOOK_MONITOR_HIGH )
+end
+
+
+--[[
+ Function: backupFile
+
+ Copies a file to a backup file. If a backup file already exists, makes incrementing numbered backup files.
+
+ Parameters:
+
+ f - The file to backup, rooted in the garrysmod directory.
+
+ Returns:
+
+ The pathname of the file it was backed up to.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.backupFile( f )
+ local contents = ULib.fileRead( f )
+ local filename = f:GetFileFromFilename():sub( 1, -5 ) -- Remove '.txt'
+ local folder = f:GetPathFromFilename()
+
+ local num = 1
+ local targetPath = folder .. filename .. "_backup.txt"
+ while ULib.fileExists( targetPath ) do
+ num = num + 1
+ targetPath = folder .. filename .. "_backup" .. num .. ".txt"
+ end
+
+ -- We now have a filename that doesn't yet exist!
+ ULib.fileWrite( targetPath, contents )
+
+ return targetPath
+end
+
+--[[
+ Function: nameCheck
+
+ Checks all players' names at regular intervals to detect name changes. Calls ULibPlayerNameChanged if the name changed. *DO NOT CALL DIRECTLY*
+
+ Revisions:
+
+ 2.20 - Initial
+]]
+function ULib.nameCheck()
+ local players = player.GetAll()
+ for _, ply in ipairs( players ) do
+ if not ply.ULibLastKnownName then ply.ULibLastKnownName = ply:Nick() end
+
+ if ply.ULibLastKnownName ~= ply:Nick() then
+ hook.Call( ULib.HOOK_PLAYER_NAME_CHANGED, nil, ply, ply.ULibLastKnownName, ply:Nick() )
+ ply.ULibLastKnownName = ply:Nick()
+ end
+ end
+end
+timer.Create( "ULibNameCheck", 1, 0, ULib.nameCheck )
+
+
+--[[
+ Function: getPlyByUID
+
+ Parameters:
+
+ uid - The uid to lookup.
+
+ Returns:
+
+ The player that has the specified unique id, nil if none exists.
+
+ Revisions:
+
+ v2.40 - Initial.
+]]
+function ULib.getPlyByUID( uid )
+ local players = player.GetAll()
+ for _, ply in ipairs( players ) do
+ if ply:UniqueID() == uid then
+ return ply
+ end
+ end
+
+ return nil
+end
+
+
+--[[
+ Function: pcallError
+
+ An adaptation of a function that used to exist before GM13, allows you to
+ call functions safely and print errors (if it errors).
+
+ Parameters:
+
+ ... - Arguments to pass to the function
+
+ Returns:
+
+ The same thing regular pcall returns
+
+ Revisions:
+
+ v2.50 - Initial.
+]]
+function ULib.pcallError( ... )
+ local returns = { pcall( ... ) }
+
+ if not returns[ 1 ] then -- The status flag
+ ErrorNoHalt( returns[ 2 ] ) -- The error message
+ end
+
+ return unpack( returns )
+end