Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6069f218dc | ||
|
|
7b44937899 | ||
|
|
0cfdd2023c | ||
|
|
9c918c46e5 |
12
README.md
@@ -1,11 +1,11 @@
|
||||
# Willard Networks: Development Server
|
||||
This branch contains files related to the **Willard Networks: Development server**. They were retrieved on 2024/08/05 and are presented as is with minimal edits. This means that potential developers' bugs have been kept in their original form and it is up to you to resolve them. We do not and will not provide direct support (i.e. regular updates or patches). However, you can always create an issue, pointing out the problem.
|
||||
# Willard Networks: Poland (Veles)
|
||||
This branch contains files related to the **Willard Networks: Veles**. They were retrieved on 2024/08/04 and are presented as is with minimal edits. This means that potential developers' bugs have been kept in their original form and it is up to you to resolve them. We do not and will not provide direct support (i.e. regular updates or patches). However, you can always create an issue, pointing out the problem.
|
||||
|
||||
**The files provided contain:**
|
||||
* Half-Life 2 RPG framework by Willard Networks, used on development server as of 2024/08/05 in English.
|
||||
* Settings (data/\*, settings/\*) used on the development server as of 2024/08/05.
|
||||
* Credentials from the development server database (including the master and SAM database).
|
||||
* Helix logs (data/helix/logs) from the development server as of 2024/08/05.
|
||||
* Half-Life 2 RPG framework by Willard Networks, used on the Polish server as of 2024/08/04.
|
||||
* Settings (data/\*, settings/\*) used on the Polish server as of 2024/08/04.
|
||||
* Credentials from the Polish server database (including the master and SAM database).
|
||||
* Helix logs (data/helix/logs) from the Polish server as of 2024/08/04.
|
||||
|
||||
**The files provided do not contain:**
|
||||
* Exported dump of databases (including the master and SAM database), since these files are overweight and cannot be uploaded to GitHub. Despite this, it is included in builds distributed via [The Twilight Zone](https://wnsrc.plymouth.thetwilightzone.ru/).
|
||||
|
||||
211
addons/LICENSE.txt
Normal file
@@ -0,0 +1,211 @@
|
||||
## Preamble
|
||||
|
||||
1. This Agreement, created on:
|
||||
|
||||
13th November 2023
|
||||
(hereinafter: Effective Date)
|
||||
|
||||
governs the relationship between
|
||||
|
||||
[Atle](https://www.gmodstore.com/users/76561198002319944)
|
||||
|
||||
(hereinafter: Licensee)
|
||||
|
||||
and
|
||||
|
||||
[Stellio AS](https://www.gmodstore.com/teams/06f5de34-3186-4d26-baf8-926899b4b3ae)
|
||||
|
||||
(hereinafter: Licensor).
|
||||
|
||||
This Agreement sets the terms, rights, restrictions and obligations on using
|
||||
|
||||
[eProtect - Keep exploiters/cheaters at bay!](https://www.gmodstore.com/market/view/7794c410-bcf4-49d3-aba2-f253cfb15344)
|
||||
|
||||
(hereinafter: The Software)
|
||||
|
||||
created and owned by Licensor, as detailed herein.
|
||||
|
||||
## License Grant
|
||||
|
||||
2. Licensor hereby grants Licensee a Personal, Non-assignable & non-transferable,
|
||||
Commercial, Royalty free, Including the rights to create but not distribute
|
||||
derivative works, Non-exclusive license, all with accordance with the terms
|
||||
set forth and other legal restrictions set forth in 3rd party software used
|
||||
while running The Software.
|
||||
|
||||
3. **Limited**: licensee may use The Software for the purpose of:
|
||||
- a. running The Software on Licensee’s Website[s] and Server[s]; and
|
||||
- b. publishing The Software’s output to Licensee and 3rd Parties; and
|
||||
- c. modify The Software to suit Licensee’s needs and specifications;
|
||||
|
||||
### Non Assignable & Non-Transferable
|
||||
|
||||
4. Licensee may not assign or transfer his rights and duties under this
|
||||
Agreement.
|
||||
|
||||
### Commercial, Royalty Free
|
||||
|
||||
5. Licensee may use The Software for any purpose, including paid-services,
|
||||
without any royalties.
|
||||
|
||||
### The Right to Create Derivative Works
|
||||
|
||||
6. Licensee may create derivative works based on The Software, including
|
||||
amending The Software’s source code, modifying it, integrating it into a
|
||||
larger work or removing portions of Software, as long as no distribution of the derivative works is made.
|
||||
**This no longer applies if**:
|
||||
- a. it is stated otherwise in this Agreement's amendments; or
|
||||
- b. the Software uses a Digital Rights Management system ("DRM"), see clause 22 "Digital Rights Management ("DRM") system";
|
||||
|
||||
Any derivative works or modifications of The Software's source code must be in
|
||||
accordance with the terms set forth in clauses 21. and 22.
|
||||
|
||||
## Term & Termination
|
||||
|
||||
7. The Term of this license shall be until terminated. Licensor may terminate
|
||||
this Agreement, including Licensee’s license in the case where Licensee:
|
||||
- a. was in breach of this license's terms and conditions and such
|
||||
breach was not cured, immediately upon notification; or
|
||||
- b. was in breach of the terms of clause 2 to this Agreement; or
|
||||
- c. otherwise entered into any arrangement which caused Licensor to be unable
|
||||
to enforce his rights under this Agreement; or
|
||||
- d. was granted a full refund by their own request; or
|
||||
- e. issued a claim or chargeback on the payment made for The Software; or
|
||||
- f. received access to The Software for free, and only for a limited time or through a conditional agreement where the agreement has ended;
|
||||
|
||||
## Upgrades, Updates and Fixes
|
||||
|
||||
8. Licensor may provide Licensee, from time to time, with Upgrades, Updates or
|
||||
Fixes, as detailed herein and according to his sole discretion. Licensee
|
||||
hereby warrants to keep The Software up-to-date and install all relevant
|
||||
updates and fixes, and may, at his sole discretion, purchase upgrades,
|
||||
according to the rates set by Licensor. Licensor may provide any update or
|
||||
Fix free of charge; however, nothing in this Agreement shall require Licensor
|
||||
to provide Updates or Fixes.
|
||||
|
||||
9. **Upgrades**: for the purpose of this Agreement, an Upgrade shall be a material amendment
|
||||
in The Software, which contains new features and or major performance
|
||||
improvements.
|
||||
|
||||
10. **Updates**: for the purpose of this Agreement, an update shall be a minor amendment in
|
||||
The Software, which may contain new features or minor improvements.
|
||||
|
||||
11. **Fixes**: for the purpose of this Agreement, a fix shall be a minor amendment in The
|
||||
Software, intended to remove bugs or alter minor features which impair
|
||||
The Software's functionality.
|
||||
|
||||
## Support
|
||||
|
||||
12. The Software is provided under an AS-IS basis and without any support, updates
|
||||
or maintenance. Nothing in this Agreement shall require Licensor to provide
|
||||
Licensee with support or fixes to any bug, failure, mis-performance or other
|
||||
defect in The Software, except in the case where Licensor explicitly states
|
||||
that he shall provide support or fixes to any bug, failure, mis-performance or
|
||||
other defect in The Software.
|
||||
|
||||
### Bug Notification
|
||||
|
||||
13. Licensee may provide Licensor of details regarding any bug, defect or
|
||||
failure in The Software promptly and with no delay from such event; Licensee
|
||||
shall comply with Licensor's request for information regarding bugs, defects
|
||||
or failures and furnish him with information, screenshots and try to
|
||||
reproduce such bugs, defects or failures.
|
||||
|
||||
### Feature Request
|
||||
|
||||
14. Licensee may request additional features in The Software, provided, however,
|
||||
that:
|
||||
|
||||
- a. Licensee shall waive any claim or right in such feature should
|
||||
feature be developed by Licensor;
|
||||
- b. Licensee shall be prohibited from developing the feature, or disclose such feature request, or feature, to
|
||||
any 3rd party directly competing with Licensor or any 3rd party which may
|
||||
be, following the development of such feature, in direct competition with
|
||||
Licensor;
|
||||
- c. Licensee warrants that feature does not infringe any 3rd
|
||||
party patent, trademark, trade-secret or any other intellectual property
|
||||
right; and
|
||||
- d. Licensee developed, envisioned or created the feature
|
||||
solely by himself.
|
||||
|
||||
## Liability
|
||||
|
||||
15. To the extent permitted under Law, The Software is provided under an AS-IS
|
||||
basis. Licensor shall never, and without any limit, be liable for any damage,
|
||||
cost, expense or any other payment incurred by Licensee as a result of
|
||||
The Software’s actions, failure, bugs and/or any other interaction between The
|
||||
Software and Licensee's end-equipment, computers, other software or any 3rd
|
||||
party, end-equipment, computer or services. Moreover, Licensor shall never
|
||||
be liable for any defect in source code written by Licensee when relying on
|
||||
The Software or using The Software’s source code.
|
||||
|
||||
## Warranty
|
||||
|
||||
16. **Intellectual Property**:
|
||||
licensor hereby warrants that The Software does not violate or infringe any
|
||||
3rd party claims in regard to intellectual property, patents and/or
|
||||
trademarks and that to the best of its knowledge no legal action has been
|
||||
taken against it for any infringement or violation of any 3rd party
|
||||
intellectual property rights.
|
||||
|
||||
17. The Software is provided without any warranty; Licensor hereby disclaims
|
||||
any warranty that The Software shall be error free, without defects or
|
||||
code which may cause damage to Licensee’s computers or to Licensee, and
|
||||
that The Software shall be functional. Licensee shall be solely liable to
|
||||
any damage, defect or loss incurred as a result of operating software and
|
||||
undertake the risks contained in running The Software on Licensee’s
|
||||
Server[s] and Website[s].
|
||||
|
||||
### Prior Inspection
|
||||
|
||||
18. Licensee hereby states that he inspected The Software thoroughly and found
|
||||
it satisfactory and adequate to his needs, that it does not interfere with
|
||||
his regular operation and that it does meet the standards and scope of his
|
||||
computer systems and architecture. Licensee found that The Software
|
||||
interacts with his development, website and server environment and that it
|
||||
does not infringe any of End User License Agreement of any software Licensee
|
||||
may use in performing his services. Licensee hereby waives any claims
|
||||
regarding The Software's incompatibility, performance, results and features,
|
||||
and warrants that he inspected The Software.
|
||||
|
||||
## Indemnification
|
||||
|
||||
19. Licensee hereby warrants to hold Licensor harmless and indemnify Licensor for
|
||||
any lawsuit brought against it in regard to Licensee’s use of The Software
|
||||
in means that violate, breach or otherwise circumvent this license,
|
||||
Licensor's intellectual property rights or Licensor's title in The Software.
|
||||
Licensor shall promptly notify Licensee in case of such legal action and
|
||||
request Licensee’s consent prior to any settlement in relation to such
|
||||
lawsuit or claim.
|
||||
|
||||
## Class Action Waiver
|
||||
|
||||
20. Licensee hereby agrees not to initiate class-action lawsuits against Licensor
|
||||
in relation to this license and to compensate Licensor for any legal fees,
|
||||
cost or attorney fees should any claim brought by Licensee against Licensor
|
||||
be denied, in part or in full.
|
||||
|
||||
## Branding, Copyright, & Public Notice
|
||||
|
||||
21. Licensee hereby agrees not to remove the branding, copyright or other brand indicators in The Software, including but not limited
|
||||
to the title of The Software. All copies of The Software must contain the same proprietary notices that appear on and in The Software,
|
||||
including all copyright notices and branding embedded in The Software, which must remain unaltered
|
||||
from the original and visible at all times, unless by specific prior arrangement by Licensor.
|
||||
|
||||
## Digital Rights Management ("DRM") system
|
||||
|
||||
22. If applicable to The Software, licensee hereby agrees not to attempt to tamper with, change, or otherwise modify
|
||||
the DRM system bundled with The Software beyond what is advertised on The Software's market page, this Agreement's amendments
|
||||
or other official sources made available by Licensor.
|
||||
|
||||
## Changes or amendments made to this Agreement
|
||||
|
||||
23. **Changes made by GmodStore**: we may make changes to this Agreement at any time, and, if we make changes, we will
|
||||
take reasonable steps to let Licensee know about these changes.
|
||||
|
||||
24. **Amendments made by Licensor**: Licensor may make amendments to this Agreement at any time, and,
|
||||
if they make amendments, they will take reasonable steps to let Licensee know about these amendments.
|
||||
|
||||
---
|
||||
|
||||
Agreement version: 2022-09-21
|
||||
1
addons/archive-2024-07-27T000221+0200.tar.gz
Normal file
@@ -0,0 +1 @@
|
||||
<1F>
|
||||
@@ -1,9 +0,0 @@
|
||||
"AddonInfo"
|
||||
{
|
||||
"name" "Chess"
|
||||
"version" "1.3.10"
|
||||
"author_name" "my_hat_stinks"
|
||||
"author_url" ""
|
||||
"info" "Let's play Chess"
|
||||
"override" "0"
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
if SERVER then
|
||||
AddCSLuaFile()
|
||||
AddCSLuaFile( "chess/sh_player_ext.lua" )
|
||||
AddCSLuaFile( "chess/cl_top.lua" )
|
||||
AddCSLuaFile( "chess/cl_dermaboard.lua" )
|
||||
|
||||
include( "chess/sh_player_ext.lua" )
|
||||
include( "chess/sv_sql.lua" )
|
||||
else
|
||||
include( "chess/sh_player_ext.lua" )
|
||||
include( "chess/cl_top.lua" )
|
||||
include( "chess/cl_dermaboard.lua" )
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
function ChessBoard_DoOverrides()
|
||||
if GAMEMODE.Name=="Cinema" then --Cinema overrides
|
||||
hook.Add("CanPlayerEnterVehicle", "EnterSeat", function(ply, vehicle) --Overrides default func
|
||||
if vehicle:GetClass() != "prop_vehicle_prisoner_pod" then return end
|
||||
|
||||
if vehicle.Removing then return false end
|
||||
return (vehicle:GetOwner() == ply) or vehicle:GetNWBool( "IsChessSeat", false )
|
||||
end)
|
||||
end
|
||||
end
|
||||
hook.Add( "Initialize", "ChessBoardOverrides", ChessBoard_DoOverrides )
|
||||
|
||||
CreateConVar( "chess_wagers", 1, FCVAR_ARCHIVE, "Set whether players can wager on their chess games." )
|
||||
CreateConVar( "chess_darkrp_wager", 1, FCVAR_ARCHIVE, "[DarkRP only] Wagers should use DarkRP wallet." )
|
||||
|
||||
CreateConVar( "chess_debug", 0, FCVAR_ARCHIVE, "Debug mode." )
|
||||
CreateConVar( "chess_limitmoves", 1, FCVAR_ARCHIVE, "Enable 50 move rule." )
|
||||
else -- CLIENT
|
||||
CreateConVar( "chess_gridletters", 1, FCVAR_ARCHIVE, "Show grid letters." )
|
||||
end
|
||||
|
||||
-- DarkRP --
|
||||
------------
|
||||
hook.Add("canArrest", "Chess PreventArrest", function( cop, target )
|
||||
if not (IsValid(target) and target:GetNWBool("IsInChess", false)) then return end
|
||||
|
||||
local board = target:GetNWEntity( "ActiveChessBoard", nil )
|
||||
if not (IsValid(board) and board:GetPlaying()) then return end
|
||||
if target~=board:GetWhitePlayer() and target~=board:GetBlackPlayer() then return end
|
||||
|
||||
return false,"Cannot arrest players during a game in progress" -- Prevent arrest during Chess
|
||||
end)
|
||||
|
||||
-- Admin Commands --
|
||||
--------------------
|
||||
local function SetupCommands()
|
||||
if serverguard then
|
||||
/////////////////
|
||||
// Serverguard //
|
||||
/////////////////
|
||||
serverguard.permission:Add("Set Chess Elo")
|
||||
|
||||
if SERVER then
|
||||
// Update function
|
||||
local function SGUpdate(player, target, newElo, isDraughts)
|
||||
if type(target)=="Player" and IsValid(target) then
|
||||
if isDraughts then
|
||||
target:SetDraughtsElo( newElo )
|
||||
else
|
||||
target:SetChessElo( newElo )
|
||||
end
|
||||
Chess_UpdateElo( target )
|
||||
|
||||
serverguard.Notify(nil,
|
||||
SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player),
|
||||
SERVERGUARD.NOTIFY.WHITE, isDraughts and " has set the Checkers Elo rating of " or " has set the Chess Elo rating of ",
|
||||
SERVERGUARD.NOTIFY.RED, serverguard.player:GetName(target),
|
||||
SERVERGUARD.NOTIFY.WHITE, " to ",
|
||||
SERVERGUARD.NOTIFY.RED, tostring(newElo),
|
||||
SERVERGUARD.NOTIFY.WHITE, "."
|
||||
)
|
||||
elseif (string.SteamID(target)) then
|
||||
local success,reason = Chess_SetElo( target, newElo, isDraughts )
|
||||
|
||||
local queryObj = serverguard.mysql:Select("serverguard_users");
|
||||
queryObj:Where("steam_id", target)
|
||||
queryObj:Limit(1)
|
||||
queryObj:Callback(function(result, status, lastID)
|
||||
local name = target
|
||||
if (type(result) == "table" and #result > 0) then
|
||||
name = result[1].name or name
|
||||
end
|
||||
|
||||
if success then
|
||||
serverguard.Notify(nil,
|
||||
SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player),
|
||||
SERVERGUARD.NOTIFY.WHITE, isDraughts and " has set the Checkers Elo rating of " or " has set the Chess Elo rating of ",
|
||||
SERVERGUARD.NOTIFY.RED, name,
|
||||
SERVERGUARD.NOTIFY.WHITE, " to ",
|
||||
SERVERGUARD.NOTIFY.RED, tostring(newElo),
|
||||
SERVERGUARD.NOTIFY.WHITE, "."
|
||||
)
|
||||
else
|
||||
serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, ("Could not set elo. (%s)"):format(tostring(reason)) )
|
||||
end
|
||||
end)
|
||||
queryObj:Execute()
|
||||
else
|
||||
serverguard.Notify(player, SGPF("cant_find_player_with_identifier"))
|
||||
end
|
||||
end
|
||||
|
||||
// Chess command
|
||||
local command = {}
|
||||
|
||||
command.help = "Set a player's Chess Elo rating."
|
||||
command.command = "setelo"
|
||||
command.arguments = {"player", "elo"}
|
||||
command.permissions = {"Set Chess Elo"}
|
||||
command.aliases = {"setelochess", "chesselo", "setchess", "setchesselo"}
|
||||
|
||||
function command:Execute(player, silent, arguments)
|
||||
local target = util.FindPlayer(arguments[1], player, true)
|
||||
local newElo = tonumber(arguments[2]) or 1400
|
||||
|
||||
SGUpdate( player, IsValid(target) and target or arguments[1], newElo, false )
|
||||
end
|
||||
serverguard.command:Add(command)
|
||||
|
||||
// Draughts command
|
||||
local command = {}
|
||||
|
||||
command.help = "Set a player's Checkers Elo rating."
|
||||
command.command = "setelocheckers"
|
||||
command.arguments = {"player", "elo"}
|
||||
command.permissions = {"Set Chess Elo"}
|
||||
command.aliases = {"setcheckers", "setcheckerselo", "checkerselo", "setelodraughts", "setdraughts", "setdraughtselo", "draughtselo"}
|
||||
|
||||
function command:Execute(player, silent, arguments)
|
||||
local target = util.FindPlayer(arguments[1], player, true)
|
||||
local newElo = tonumber(arguments[2]) or 1400
|
||||
|
||||
SGUpdate( player, IsValid(target) and target or arguments[1], newElo, true )
|
||||
end
|
||||
|
||||
serverguard.command:Add(command)
|
||||
end
|
||||
end
|
||||
if ulx then
|
||||
/////////
|
||||
// ULX //
|
||||
/////////
|
||||
|
||||
// Update
|
||||
local function ULXUpdate(calling_ply, target, newElo, isDraughts)
|
||||
if CLIENT then return end
|
||||
|
||||
if type(target)=="Player" and IsValid(target) then
|
||||
if isDraughts then
|
||||
target:SetDraughtsElo( newElo )
|
||||
else
|
||||
target:SetChessElo( newElo )
|
||||
end
|
||||
Chess_UpdateElo( target )
|
||||
|
||||
ulx.fancyLogAdmin( calling_ply, "#A set the #s Elo rating of #T to #i.", isDraughts and "Checkers" or "Chess", target, newElo )
|
||||
elseif (string.SteamID(target or "")) then
|
||||
local success,reason = Chess_SetElo( target, newElo, isDraughts )
|
||||
|
||||
if success then
|
||||
ulx.fancyLogAdmin( calling_ply, "#A set the #s Elo rating of #s to #i.", isDraughts and "Checkers" or "Chess", target, newElo )
|
||||
else
|
||||
ULib.tsayError( calling_ply, ("Could not set elo. (%s)"):format(tostring(reason)) )
|
||||
end
|
||||
else
|
||||
ULib.tsayError( calling_ply, "Invalid SteamID or Player." )
|
||||
end
|
||||
end
|
||||
|
||||
// Autocomplete
|
||||
local function AutoComplete(...) return ULib.cmds.PlayerArg.complete(ULib.cmds.PlayerArg, ...) end
|
||||
|
||||
// Chess command
|
||||
local function SetChessElo( calling_ply, steamid, newElo )
|
||||
local target = ULib.getUser( steamid or "", true, calling_ply )
|
||||
|
||||
ULXUpdate( calling_ply, IsValid(target) and target or steamid, newElo, false )
|
||||
end
|
||||
local setchess = ulx.command( "Chess", "ulx chesselo", SetChessElo, {"!setelo", "!setelochess", "!chesselo", "!setchess", "!setchesselo"}, false, false, true )
|
||||
setchess:addParam{ type=ULib.cmds.StringArg, hint="Player or SteamID", autocomplete_fn=AutoComplete }
|
||||
setchess:addParam{ type=ULib.cmds.NumArg, hint="New Elo", min=0, default=1400 }
|
||||
setchess:defaultAccess( ULib.ACCESS_SUPERADMIN )
|
||||
setchess:help( "Set Chess Elo rating for user." )
|
||||
|
||||
// Draughts command
|
||||
local function SetDraughtsElo( calling_ply, steamid, newElo )
|
||||
local target = ULib.getUser( steamid, true, calling_ply )
|
||||
|
||||
ULXUpdate( calling_ply, IsValid(target) and target or steamID, newElo, true )
|
||||
end
|
||||
local setdraughts = ulx.command( "Chess", "ulx checkerselo", SetDraughtsElo, {"!setelocheckers", "!setcheckers", "!setcheckerselo", "!checkerselo", "!setelodraughts", "!setdraughts", "!setdraughtselo", "!draughtselo"}, false, false, true )
|
||||
setdraughts:addParam{ type=ULib.cmds.StringArg, hint="Player or SteamID", autocomplete_fn=AutoComplete }
|
||||
setdraughts:addParam{ type=ULib.cmds.NumArg, hint="New Elo", min=0, default=1400 }
|
||||
setdraughts:defaultAccess( ULib.ACCESS_SUPERADMIN )
|
||||
setdraughts:help( "Set Checkers Elo rating for user." )
|
||||
end
|
||||
end
|
||||
hook.Add( "Initialize", "ChessBoardPermissions", SetupCommands )
|
||||
@@ -1,174 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PanelCol = {
|
||||
Main = Color(0,0,0,200), Hover = Color(0,255,0,50), Selected = Color(150,50,50,170),
|
||||
Move = Color(10,25,150,150), HasMoved = Color(50,50,50,100), Text = Color(0,0,0,255),
|
||||
|
||||
GridWhite = Color(255, 248, 220), GridBlack = Color(210, 180, 140),
|
||||
|
||||
White = Color(180,180,180), Black = Color(0,0,0),
|
||||
}
|
||||
-- local ColText = Color(50,50,50,200)
|
||||
-- local ColBlack,ColWhite = Color(0,0,0,120),Color(255,255,255,10)
|
||||
|
||||
local NumToLetter = {"a", "b", "c", "d", "e", "f", "g", "h", ["a"]=1, ["b"]=2, ["c"]=3, ["d"]=4, ["e"]=5, ["f"]=6, ["g"]=7, ["h"]=8} --Used extensively for conversions
|
||||
surface.CreateFont( "ChessPieces", { font = "Arial", size = 64, weight = 300})
|
||||
|
||||
function Chess_Open2DBoard( board )
|
||||
if not IsValid(board) then return end
|
||||
if IsValid( Chess_2DDermaPanel ) then Chess_2DDermaPanel:Remove() end
|
||||
|
||||
Chess_2DDermaPanel = vgui.Create( "DFrame" )
|
||||
local frame = Chess_2DDermaPanel
|
||||
frame:SetTitle( "2D Game Board" )
|
||||
frame:SetSize( 740, 760 )
|
||||
frame:SetPos( (ScrW()/2)-370, (ScrH()/2)-380 )
|
||||
frame.Paint = function( s,w,h )
|
||||
if not IsValid(board) then
|
||||
s:Remove()
|
||||
end
|
||||
|
||||
draw.RoundedBox( 8, 0, 0, w, h, PanelCol.Main )
|
||||
end
|
||||
frame:MakePopup()
|
||||
|
||||
local pnlBoard = vgui.Create( "DPanel", frame )
|
||||
pnlBoard:SetSize( 720, 720 )
|
||||
pnlBoard:SetPos(10,30)
|
||||
|
||||
pnlBoard.Squares = {}
|
||||
pnlBoard.Paint = function() end
|
||||
|
||||
local function DoTiles(swapped)
|
||||
if pnlBoard.Squares then
|
||||
for _,v in pairs(pnlBoard.Squares) do
|
||||
for _,pnl in pairs(v) do
|
||||
pnl:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i=0,7 do
|
||||
pnlBoard.Squares[i] = {}
|
||||
for n=0,7 do
|
||||
local pnl = vgui.Create( "DModelPanel", pnlBoard )
|
||||
pnlBoard.Squares[i][n] = pnl
|
||||
|
||||
pnl:SetPos( (swapped and 7-i or i)*90, (swapped and 7-n or n)*90 )
|
||||
pnl:SetSize(90,90)
|
||||
pnl.GridCol = (((i+n)%2)==1) and PanelCol.GridBlack or PanelCol.GridWhite
|
||||
pnl.GridPos = {i,n}
|
||||
pnl.oPaint = pnl.Paint
|
||||
pnl.Paint = function(s,w,h)
|
||||
surface.SetDrawColor( s.GridCol )
|
||||
surface.DrawRect( 0,0, w,h )
|
||||
|
||||
if not IsValid(board) then return end
|
||||
|
||||
if s:IsHovered() then
|
||||
surface.SetDrawColor( PanelCol.Hover )
|
||||
surface.DrawRect( 0,0, w,h )
|
||||
end
|
||||
if board.Selected and board.Selected[1]==s.GridPos[1] and board.Selected[2]==s.GridPos[2] then
|
||||
surface.SetDrawColor( PanelCol.Selected )
|
||||
surface.DrawRect( 0,0, w,h )
|
||||
end
|
||||
if board:GetTableGrid( board.Moves, s.GridPos[1], s.GridPos[2]) then
|
||||
surface.SetDrawColor( PanelCol.Move )
|
||||
surface.DrawRect( 0,0, w,h )
|
||||
end
|
||||
|
||||
local col,square = board.Pieces[ NumToLetter[s.GridPos[1]+1] ]
|
||||
if col then
|
||||
square = col[8-s.GridPos[2]]
|
||||
if square then
|
||||
if not (square.Team and square.Class) then return end
|
||||
|
||||
if square.Moving then
|
||||
surface.SetDrawColor( PanelCol.HasMoved )
|
||||
surface.DrawRect( 0,0, w,h )
|
||||
end
|
||||
|
||||
if board.Characters then
|
||||
draw.SimpleText( board.Characters[square.Team .. square.Class], "ChessPieces", w/2, h/2, PanelCol.Text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
else
|
||||
s:SetModel( board.Models[square.Team .. square.Class] )
|
||||
|
||||
return s.oPaint( s,w,h )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if cvars.Bool("chess_gridletters") then
|
||||
local str = (NumToLetter[i+1]..tostring(8-n)):upper()
|
||||
draw.SimpleText( str, "ChessTextSmall", 5, h-20, PanelCol.Black )
|
||||
end
|
||||
end
|
||||
|
||||
pnl.DoClick = function(s)
|
||||
if not IsValid(board) then return end
|
||||
|
||||
if board.Selected and board:GetTableGrid( board.Moves, pnl.GridPos[1], pnl.GridPos[2] ) then
|
||||
board:RequestMove( board.Selected[1], board.Selected[2], pnl.GridPos[1], pnl.GridPos[2] )
|
||||
board:ResetHighlights()
|
||||
elseif board.Selected and board.Selected[1]==s.GridPos[1] and board.Selected[2]==s.GridPos[2] then
|
||||
board:ResetHighlights()
|
||||
else
|
||||
board:ResetHighlights()
|
||||
board.Selected = {s.GridPos[1],s.GridPos[2]}
|
||||
board.Moves = board:GetMove( NumToLetter[s.GridPos[1]+1], 8-s.GridPos[2] )
|
||||
end
|
||||
end
|
||||
pnl.LayoutEntity = function( s, ent )
|
||||
ent:SetPos( Vector(20,20,20) )
|
||||
ent:SetAngles( Angle(0,-50,0) )
|
||||
end
|
||||
|
||||
pnl:SetModel( board.Models["WhitePawn"] )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local swap = vgui.Create( "DButton", frame )
|
||||
swap:SetImage( "icon16/arrow_rotate_clockwise.png" )
|
||||
swap:SetSize( 18,18 )
|
||||
swap:SetPos( 620,6 )
|
||||
swap:SetText( "" )
|
||||
swap.DoClick = function(s)
|
||||
s.Swapped = not s.Swapped
|
||||
DoTiles( s.Swapped )
|
||||
end
|
||||
swap.Paint = function() end
|
||||
|
||||
local toggleGridLetters = vgui.Create( "DCheckBox", frame )
|
||||
toggleGridLetters:SetImage( "icon16/font.png" )
|
||||
toggleGridLetters:SetSize( 18,18 )
|
||||
toggleGridLetters:SetPos( 600,6 )
|
||||
toggleGridLetters:SetText( "" )
|
||||
toggleGridLetters:SetConVar( "chess_gridletters" )
|
||||
toggleGridLetters.OnChange = function(s,newvalue)
|
||||
s:SetAlpha( s:GetChecked() and 255 or 100 )
|
||||
end
|
||||
toggleGridLetters.Paint = function() end
|
||||
toggleGridLetters.PerformLayout = function(s) -- DCheckBox overwrites DButton's PerformLayout, re-apply it
|
||||
if IsValid(s.m_Image) then
|
||||
s.m_Image:SetPos( 4, ( s:GetTall() - s.m_Image:GetTall() ) * 0.5 )
|
||||
s:SetTextInset( s.m_Image:GetWide() + 16, 0 )
|
||||
end
|
||||
|
||||
DLabel.PerformLayout( s )
|
||||
end
|
||||
|
||||
swap.Swapped = false
|
||||
DoTiles( swap.Swapped )
|
||||
end
|
||||
@@ -1,149 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local Top10
|
||||
local function ChessTop10( typ )
|
||||
if IsValid(Top10) then Top10:Remove() end
|
||||
|
||||
typ = typ or "Chess"
|
||||
|
||||
Top10 = vgui.Create( "DFrame" )
|
||||
Top10:SetSize( 450, 245 )
|
||||
Top10:SetPos( (ScrW()/2)-150, (ScrH()/2)-100 )
|
||||
Top10:SetTitle( "Top 10 Chess Elo ratings" )
|
||||
Top10:MakePopup()
|
||||
Top10.Column = "Elo"
|
||||
|
||||
local pnl = vgui.Create("DPanel", Top10)
|
||||
pnl.Paint = function() end
|
||||
pnl:SetTall(20)
|
||||
pnl:Dock(BOTTOM)
|
||||
|
||||
Top10.Rank = vgui.Create( "DLabel", pnl )
|
||||
Top10.Rank:Dock( LEFT )
|
||||
Top10.Rank:SetTall( 20 )
|
||||
Top10.Rank:SetWide( 150 )
|
||||
Top10.Rank:SetText("")
|
||||
|
||||
Top10.Updated = vgui.Create( "DLabel", pnl )
|
||||
Top10.Updated:Dock( RIGHT )
|
||||
Top10.Updated:SetTall( 150 )
|
||||
Top10.Updated:SetText("")
|
||||
|
||||
Top10.List = vgui.Create( "DListView", Top10 )
|
||||
local List = Top10.List
|
||||
List:Dock( FILL )
|
||||
List:AddColumn( "Rank" )
|
||||
List:AddColumn( "Name" )
|
||||
List:AddColumn( "SteamID" )
|
||||
List:AddColumn( "Elo" )
|
||||
|
||||
List:AddLine( "", "Loading..." )
|
||||
-- PrintTable( List.Columns )
|
||||
List:OnRequestResize( List.Columns[1], 10 )
|
||||
List:OnRequestResize( List.Columns[2], 150 )
|
||||
List:OnRequestResize( List.Columns[3], 100 )
|
||||
List:OnRequestResize( List.Columns[4], 10 )
|
||||
|
||||
net.Start( "Chess Top10" ) net.WriteString( typ ) net.SendToServer()
|
||||
end
|
||||
local function DraughtsTop10()
|
||||
ChessTop10( "Draughts" )
|
||||
end
|
||||
concommand.Add( "chess_top", function( p,c,a ) ChessTop10() end)
|
||||
concommand.Add( "checkers_top", function( p,c,a ) DraughtsTop10() end)
|
||||
|
||||
local ChatCommands = {
|
||||
["!chess"]=ChessTop10, ["!chesstop"]=ChessTop10, ["!topchess"]=ChessTop10,
|
||||
["/chess"]=ChessTop10, ["/chesstop"]=ChessTop10, ["/topchess"]=ChessTop10,
|
||||
|
||||
["!draughts"]=DraughtsTop10, ["!draughtstop"]=DraughtsTop10, ["!topdraughts"]=DraughtsTop10,
|
||||
["/draughts"]=DraughtsTop10, ["/draughtstop"]=DraughtsTop10, ["/topdraughts"]=DraughtsTop10,
|
||||
|
||||
["!checkers"]=DraughtsTop10, ["!checkerstop"]=DraughtsTop10, ["!topcheckers"]=DraughtsTop10,
|
||||
["/checkers"]=DraughtsTop10, ["/checkerstop"]=DraughtsTop10, ["/topcheckers"]=DraughtsTop10,
|
||||
}
|
||||
hook.Add( "OnPlayerChat", "Chess Top10 PlayerChat", function( ply, str, tm, dead )
|
||||
if ChatCommands[str:lower()] then
|
||||
if ply==LocalPlayer() then
|
||||
ChatCommands[str:lower()]()
|
||||
end
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
local function SecondsToTime(num)
|
||||
if updateTime>=86400 then
|
||||
return ("%i day%s"):format( math.floor(updateTime/86400), updateTime>=172800 and "s" or "")
|
||||
elseif updateTime>=3600 then
|
||||
return ("%i hour%s"):format( math.floor(updateTime/3600), updateTime>=7200 and "s" or "")
|
||||
elseif updateTime>=60 then
|
||||
return ("%i minute%s"):format( math.floor(updateTime/60), updateTime>=120 and "s" or "")
|
||||
else
|
||||
return ("%i second%s"):format(updateTime, updateTime>=2 and "s" or "")
|
||||
end
|
||||
end
|
||||
|
||||
local lastTable = {}
|
||||
local lastRank = {}
|
||||
local lastUpdate = {}
|
||||
net.Receive( "Chess Top10", function()
|
||||
if not (IsValid(Top10) and IsValid(Top10.List)) then return end
|
||||
|
||||
Top10.List:Clear()
|
||||
|
||||
local typ = net.ReadString() or "[Error]"
|
||||
|
||||
Top10:SetTitle( "Top 10 "..typ.." Elo ratings" )
|
||||
|
||||
local tbl = net.ReadTable()
|
||||
if (not tbl) or (#tbl==0) then -- Rate limit exceeded
|
||||
if lastTable[typ] then
|
||||
for i=1,#lastTable[typ] do
|
||||
Top10.List:AddLine( tonumber(lastTable[typ][i].Rank), lastTable[typ][i].Username, lastTable[typ][i].SteamID, tonumber(lastTable[typ][i]["Elo" ]) )
|
||||
end
|
||||
Top10.Rank:SetText( "You are rank "..lastRank[typ] )
|
||||
|
||||
if lastUpdate[typ] then
|
||||
updateTime = math.floor(CurTime() - lastUpdate[typ])
|
||||
if updateTime>0 then
|
||||
Top10.Updated:SetText( ("Updated %s ago."):format(SecondsToTime(updateTime)) )
|
||||
end
|
||||
Top10.Updated:SizeToContents()
|
||||
end
|
||||
else
|
||||
Top10.List:AddLine( "", "ERROR: Rate limit exceeded." )
|
||||
Top10.List:AddLine( "", "Please try again in a few minutes." )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
for i=1,#tbl do
|
||||
Top10.List:AddLine( tonumber(tbl[i].Rank), tbl[i].Username, tbl[i].SteamID, tonumber(tbl[i]["Elo" ]) )
|
||||
end
|
||||
|
||||
local rank = (net.ReadString() or "N/A")
|
||||
Top10.Rank:SetText( "You are rank "..rank )
|
||||
|
||||
lastUpdate[typ] = tonumber(net.ReadString())
|
||||
if lastUpdate[typ] then
|
||||
updateTime = math.floor(CurTime() - lastUpdate[typ])
|
||||
|
||||
if updateTime>0 then
|
||||
Top10.Updated:SetText( ("Updated %s ago."):format(SecondsToTime(updateTime)) )
|
||||
end
|
||||
Top10.Updated:SizeToContents()
|
||||
end
|
||||
|
||||
lastTable[typ] = tbl
|
||||
lastRank[typ] = rank
|
||||
end)
|
||||
@@ -1,123 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PLAYER = FindMetaTable( "Player" )
|
||||
if not PLAYER then return end
|
||||
|
||||
function PLAYER:GetChessElo()
|
||||
return self:GetNWInt( "ChessElo", 1400 ) or 1400
|
||||
end
|
||||
function PLAYER:GetDraughtsElo()
|
||||
return self:GetNWInt( "DraughtsElo", 1400 ) or 1400
|
||||
end
|
||||
if CLIENT then return end
|
||||
function PLAYER:SetChessElo( num )
|
||||
self:SetNWInt( "ChessElo", num or Chess_GetElo(ply:SteamID()) or 1400 )
|
||||
Chess_SetElo( self:SteamID(), num, false )
|
||||
end
|
||||
function PLAYER:SetDraughtsElo( num )
|
||||
self:SetNWInt( "DraughtsElo", num or Chess_GetElo(ply:SteamID(), true) or 1400 )
|
||||
Chess_SetElo( self:SteamID(), num, true )
|
||||
end
|
||||
|
||||
function PLAYER:RefreshChessElo()
|
||||
-- Convert old PData stuff
|
||||
if self:GetPData("ChessElo") then
|
||||
Chess_SetElo( self:SteamID(), self:GetPData("ChessElo"), false )
|
||||
self:RemovePData("ChessElo")
|
||||
end
|
||||
if self:GetPData("DraughtsElo") then
|
||||
Chess_SetElo( self:SteamID(), self:GetPData("DraughtsElo"), true )
|
||||
self:RemovePData("DraughtsElo")
|
||||
end
|
||||
|
||||
self:SetChessElo( Chess_GetElo(self:SteamID()) or 1400)
|
||||
self:SetDraughtsElo( Chess_GetElo(self:SteamID(), true) or 1400)
|
||||
end
|
||||
hook.Add( "PlayerInitialSpawn", "Chess InitialSpawn InitElo", function(ply)
|
||||
ply:RefreshChessElo()
|
||||
end)
|
||||
|
||||
function PLAYER:ExpectedChessWin( against )
|
||||
return (1/ (1+( 10^( (against:GetChessElo() - self:GetChessElo())/400 ) )) )
|
||||
end
|
||||
function PLAYER:ExpectedDraughtsWin( against )
|
||||
return (1/ (1+( 10^( (against:GetDraughtsElo() - self:GetDraughtsElo())/400 ) )) )
|
||||
end
|
||||
|
||||
function PLAYER:GetChessKFactor() --Imitating FIDE's K-factor ranges
|
||||
local games = tonumber(self:GetPData( "ChessGamesPlayed", 0 )) or 0
|
||||
if games<30 then
|
||||
self:SetPData( "ChessEloKFactor", 15 )
|
||||
return 30
|
||||
end
|
||||
local k = self:GetChessElo()>=2400 and 10 or self:GetPData( "ChessEloKFactor", 15 ) or 15
|
||||
self:SetPData( "ChessEloKFactor", k )
|
||||
return k
|
||||
end
|
||||
function PLAYER:GetDraughtsKFactor() --Imitating FIDE's K-factor ranges
|
||||
local games = self:GetPData( "DraughtsGamesPlayed", 0 )
|
||||
if games<30 then
|
||||
self:SetPData( "DraughtsEloKFactor", 15 )
|
||||
return 30
|
||||
end
|
||||
local k = self:GetDraughtsElo()>=2400 and 10 or self:GetPData( "DraughtsEloKFactor", 15 ) or 15
|
||||
self:SetPData( "DraughtsEloKFactor", k )
|
||||
return k
|
||||
end
|
||||
|
||||
function PLAYER:DoChessElo( score, against )
|
||||
local mod = math.ceil(self:GetChessKFactor() * (score - self:ExpectedChessWin(against)))
|
||||
local NewElo = math.floor( self:GetChessElo() + mod )
|
||||
|
||||
self:SetChessElo( NewElo )
|
||||
|
||||
if IsValid(against) then
|
||||
mod = mod*(-1)
|
||||
local NewElo = math.floor( against:GetChessElo() + mod )
|
||||
|
||||
against:SetChessElo( NewElo )
|
||||
local rank,count = Chess_GetRank(against)
|
||||
against:ChatPrint( "Your chess Elo rating changed by "..tostring(mod).." to "..tostring(NewElo).."!" ..(rank and " You are #"..tostring(rank).." on this server." or "") )
|
||||
end
|
||||
local rank,count = Chess_GetRank(self)
|
||||
self:ChatPrint( "Your chess Elo rating changed by "..tostring(mod).." to "..tostring(NewElo).."!" ..(rank and " You are #"..tostring(rank).." on this server." or "") )
|
||||
|
||||
Chess_UpdateElo( self )
|
||||
end
|
||||
function PLAYER:ChessWin( against )
|
||||
if not IsValid(against) then return end
|
||||
|
||||
self:DoChessElo(1, against)
|
||||
end
|
||||
function PLAYER:ChessDraw( against ) self:DoChessElo(0.5, against) end
|
||||
|
||||
function PLAYER:DoDraughtsElo( score, against )
|
||||
local mod = math.ceil(self:GetDraughtsKFactor() * (score - self:ExpectedDraughtsWin(against)))
|
||||
local NewElo = math.floor( self:GetDraughtsElo() + mod )
|
||||
|
||||
self:SetDraughtsElo( NewElo )
|
||||
|
||||
if IsValid(against) then
|
||||
mod = mod*(-1)
|
||||
local NewElo = math.floor( against:GetDraughtsElo() + mod )
|
||||
|
||||
against:SetDraughtsElo( NewElo )
|
||||
local rank = Chess_GetRank(self, "Draughts")
|
||||
against:ChatPrint( "Your draughts Elo rating changed by "..tostring(mod).." to "..tostring(NewElo).."!" ..(rank and " You are #"..tostring(rank).." on this server." or "") )
|
||||
end
|
||||
local rank = Chess_GetRank(self, "Draughts")
|
||||
self:ChatPrint( "Your draughts Elo rating changed by "..tostring(mod).." to "..tostring(NewElo).."!" ..(rank and " You are #"..tostring(rank).." on this server." or "") )
|
||||
|
||||
Chess_UpdateElo( self )
|
||||
end
|
||||
function PLAYER:DraughtsWin( against ) self:DoDraughtsElo(1, against) end
|
||||
function PLAYER:DraughtsDraw( against ) self:DoDraughtsElo(0.5, against) end
|
||||
@@ -1,212 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local nextTopUpdate = {}
|
||||
local playerResults = {}
|
||||
|
||||
local function InitChessLeaderboard()
|
||||
sql.Begin()
|
||||
local query = sql.Query( [[CREATE TABLE ChessLeaderboard (SteamID varchar(255), Username varchar(255), Elo int, DraughtsElo int);]] )
|
||||
sql.Commit()
|
||||
|
||||
if query==false then else print("Chess: Created Elo table") end
|
||||
end
|
||||
InitChessLeaderboard()
|
||||
|
||||
function Chess_UpdateElo( ply )
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
local SelStr = [[SELECT * FROM ChessLeaderboard WHERE SteamID==]].. sql.SQLStr(ply:SteamID()) ..[[;]]
|
||||
sql.Begin()
|
||||
local Select = sql.Query( SelStr )
|
||||
sql.Commit()
|
||||
|
||||
local UpdateStr
|
||||
if Select==false then
|
||||
Error( "Chess: Save failed for player "..ply:Nick().." - Query error. "..sql.LastError().."\n" )
|
||||
elseif Select==nil then
|
||||
UpdateStr = [[INSERT INTO ChessLeaderboard (SteamID, Username, Elo, DraughtsElo) VALUES (]]..sql.SQLStr(ply:SteamID())..[[,]]..sql.SQLStr(ply:Nick())..[[,]]..sql.SQLStr(ply:GetChessElo())..[[,]]..sql.SQLStr(ply:GetDraughtsElo())..[[);]]
|
||||
else
|
||||
UpdateStr = [[UPDATE ChessLeaderboard SET Username=]]..sql.SQLStr(ply:GetName())..[[,Elo=]]..sql.SQLStr(ply:GetChessElo())..[[,DraughtsElo=]]..sql.SQLStr(ply:GetDraughtsElo())..[[ WHERE SteamID==]]..sql.SQLStr(ply:SteamID())..[[;]]
|
||||
end
|
||||
|
||||
sql.Begin()
|
||||
local Update = sql.Query( UpdateStr )
|
||||
sql.Commit()
|
||||
|
||||
if Update==false then
|
||||
Error( "Chess: Update failed for player "..ply:Nick().." - Query error. "..sql.LastError().."\n" )
|
||||
end
|
||||
|
||||
nextTopUpdate = {}
|
||||
playerResults = {}
|
||||
end
|
||||
function Chess_SetElo( steamID, newElo, isDraughts )
|
||||
if not tonumber(newElo) then return false,"Elo must be a number." end
|
||||
if type(steamID)=="Player" then
|
||||
if not IsValid(steamID) then return end
|
||||
steamID = steamID:SteamID()
|
||||
end
|
||||
|
||||
local ply = player.GetBySteamID(steamID)
|
||||
|
||||
local SelStr = [[SELECT * FROM ChessLeaderboard WHERE SteamID==]].. sql.SQLStr(steamID) ..[[;]]
|
||||
sql.Begin()
|
||||
local Select = sql.Query( SelStr )
|
||||
sql.Commit()
|
||||
|
||||
local UpdateStr
|
||||
if Select==nil then
|
||||
UpdateStr = [[INSERT INTO ChessLeaderboard (SteamID, Username, Elo, DraughtsElo) VALUES (]]..sql.SQLStr(steamID)..[[,]]..sql.SQLStr(IsValid(ply) and ply:Nick() or "N/A")..[[,]]..sql.SQLStr(IsValid(ply) and ply:GetChessElo() or 1400)..[[,]]..sql.SQLStr(IsValid(ply) and ply:GetDraughtsElo() or 1400)..[[);]]
|
||||
end
|
||||
|
||||
if isDraughts then
|
||||
UpdateStr = [[UPDATE ChessLeaderboard SET DraughtsElo=]]..sql.SQLStr(tonumber(newElo))..[[ WHERE SteamID==]]..sql.SQLStr(steamID)..[[;]]
|
||||
else
|
||||
UpdateStr = [[UPDATE ChessLeaderboard SET Elo=]]..sql.SQLStr(tonumber(newElo))..[[ WHERE SteamID==]]..sql.SQLStr(steamID)..[[;]]
|
||||
end
|
||||
|
||||
sql.Begin()
|
||||
local Update = sql.Query( UpdateStr )
|
||||
sql.Commit()
|
||||
|
||||
if Update==false then
|
||||
Error( "Chess: Update failed for player "..steamID.." - Query error. "..sql.LastError().."\n" )
|
||||
return false
|
||||
end
|
||||
|
||||
nextTopUpdate = {}
|
||||
playerResults = {}
|
||||
end
|
||||
function Chess_GetElo( steamID, isDraughts )
|
||||
if type(steamID)=="Player" then
|
||||
if not IsValid(steamID) then return end
|
||||
steamID = steamID:SteamID()
|
||||
end
|
||||
|
||||
local SelStr = [[SELECT * FROM ChessLeaderboard WHERE SteamID==]].. sql.SQLStr(steamID) ..[[;]]
|
||||
sql.Begin()
|
||||
local Select = sql.Query( SelStr )
|
||||
sql.Commit()
|
||||
|
||||
if Select==nil then
|
||||
return false, "Player does not have an Elo rating"
|
||||
end
|
||||
|
||||
if isDraughts then
|
||||
return Select[1].DraughtsElo or 1400
|
||||
else
|
||||
return Select[1].Elo or 1400 -- Chess
|
||||
end
|
||||
end
|
||||
|
||||
function Chess_GetRank( ply, typ )
|
||||
local query = string.gsub([[SELECT (
|
||||
SELECT COUNT(*)+1
|
||||
FROM ChessLeaderboard ordered
|
||||
WHERE (ordered.{ELO}) > (uo.{ELO})
|
||||
) AS Rank, (SELECT COUNT(*) FROM ChessLeaderboard) AS Count
|
||||
FROM ChessLeaderboard uo WHERE SteamID==]]..sql.SQLStr(ply:SteamID())..[[;]], "{ELO}", (typ=="Draughts" and [[DraughtsElo]] or [[Elo]]) )
|
||||
sql.Begin()
|
||||
local rank = sql.Query( query )
|
||||
sql.Commit()
|
||||
|
||||
if not (rank and rank[1] and rank[1].Rank and rank[1].Count) then return false end
|
||||
|
||||
return rank and rank[1] and rank[1].Rank, rank and rank[1] and rank[1].Count
|
||||
end
|
||||
|
||||
util.AddNetworkString( "Chess Top10" )
|
||||
local ValidType = {["Chess"] = true, ["Draughts"] = true}
|
||||
|
||||
local TopTable = {}
|
||||
net.Receive( "Chess Top10", function( len, ply )
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
local typ = net.ReadString()
|
||||
if not (typ and ValidType[typ]) then typ = "Chess" end
|
||||
if not playerResults[typ] then playerResults[typ] = {} end
|
||||
|
||||
if playerResults[typ][ply] and (CurTime()<(playerResults[typ][ply].nextUpdate or 0)) then
|
||||
net.Start( "Chess Top10" )
|
||||
net.WriteString( typ )
|
||||
net.Send( ply )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if CurTime()>(nextTopUpdate[typ] or 0) then
|
||||
local query = string.gsub([[SELECT Username, SteamID, {ELO} as Elo, (
|
||||
SELECT COUNT(*)+1
|
||||
FROM ChessLeaderboard ordered
|
||||
WHERE (ordered.{ELO}) > (uo.{ELO})
|
||||
) AS Rank
|
||||
FROM ChessLeaderboard uo ORDER BY {ELO} DESC LIMIT 10;]], "{ELO}", (typ=="Draughts" and [[DraughtsElo]] or [[Elo]]) )
|
||||
sql.Begin()
|
||||
local Top10 = sql.Query( query )
|
||||
sql.Commit()
|
||||
|
||||
if Top10==false then
|
||||
Error( "Chess: Top10 retrieval failed - Query error. "..sql.LastError().."\n" )
|
||||
return
|
||||
elseif Top10==nil then
|
||||
Error( "Chess: Top10 retrieval failed - No data." )
|
||||
return
|
||||
end
|
||||
|
||||
TopTable = Top10
|
||||
|
||||
table.Empty(playerResults[typ])
|
||||
nextTopUpdate[typ] = CurTime() + 120
|
||||
end
|
||||
|
||||
local str = ""
|
||||
if (not playerResults[typ][ply]) or CurTime()<(playerResults[typ][ply].nextUpdate) then
|
||||
local rank,total = Chess_GetRank(ply, typ)
|
||||
playerResults[typ][ply] = {rank = rank, total = total}
|
||||
end
|
||||
local res = playerResults[typ][ply]
|
||||
str = (res.rank and res.total and res.rank.." of "..res.total) or res.rank or "N/A"
|
||||
|
||||
net.Start( "Chess Top10" )
|
||||
net.WriteString( typ )
|
||||
net.WriteTable( TopTable )
|
||||
|
||||
net.WriteString( str )
|
||||
net.WriteString(math.floor(nextTopUpdate[typ] - 120))
|
||||
net.Send( ply )
|
||||
|
||||
playerResults[typ][ply].nextUpdate = CurTime() + 30
|
||||
end)
|
||||
|
||||
-- function TestElo()
|
||||
-- local typ = ""
|
||||
|
||||
-- local query = string.gsub([[SELECT *,(
|
||||
-- SELECT COUNT(*)+1
|
||||
-- FROM ChessLeaderboard ordered
|
||||
-- WHERE (ordered.{ELO}) > (uo.{ELO})
|
||||
-- ) AS Rank, (SELECT COUNT(*) FROM ChessLeaderboard) AS Count
|
||||
-- FROM ChessLeaderboard uo ORDER BY {ELO} DESC;]], "{ELO}", (typ=="Draughts" and [[DraughtsElo]] or [[Elo]]) )
|
||||
-- sql.Begin()
|
||||
-- local Top10 = sql.Query( query )
|
||||
-- sql.Commit()
|
||||
|
||||
-- if Top10 then
|
||||
-- print( Top10 )
|
||||
-- PrintTable( Top10 )
|
||||
|
||||
-- print( Top10[1].Rank, type(Top10[1].Rank) )
|
||||
-- else
|
||||
-- print( sql.LastError() )
|
||||
-- end
|
||||
-- end
|
||||
|
||||
@@ -1,684 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
if SERVER then
|
||||
AddCSLuaFile()
|
||||
end
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.Model = Model("models/weapons/w_slam.mdl")
|
||||
ENT.Base = "ent_chess_board"
|
||||
ENT.Models = {
|
||||
["board"] = Model("models/props_phx/games/chess/board.mdl"),
|
||||
["table"] = Model("models/props/de_tides/restaurant_table.mdl"),
|
||||
["hl2table"] = Model( "models/props_c17/furnituretable001a.mdl" ),
|
||||
|
||||
["dama"] = Model("models/props_phx/games/chess/white_pawn.mdl"),
|
||||
|
||||
["WhiteMan"] = Model("models/props_phx/games/chess/white_dama.mdl"), ["BlackMan"] = Model("models/props_phx/games/chess/black_dama.mdl"),
|
||||
["WhiteKing"] = Model("models/props_phx/games/chess/white_dama.mdl"), ["BlackKing"] = Model("models/props_phx/games/chess/black_dama.mdl"),
|
||||
}
|
||||
ENT.Characters = { -- Notepad's being weird and not showing these characters. If you can't see them, they'll still show up in-game.
|
||||
["WhiteMan"] = Model("⛀"), ["BlackMan"] = Model("⛂"),
|
||||
["WhiteKing"] = Model("⛁"), ["BlackKing"] = Model("⛃"),
|
||||
}
|
||||
ENT.DrawDouble = {
|
||||
["King"] = true,
|
||||
}
|
||||
|
||||
ENT.PrintName = "Draughts/Checkers"
|
||||
ENT.Author = "my_hat_stinks"
|
||||
ENT.Information = "A draughts (checkers) board"
|
||||
ENT.Category = "Game boards"
|
||||
|
||||
ENT.Game = "Draughts"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
ENT.AdminSpawnable = true
|
||||
|
||||
--Status
|
||||
local CHESS_INACTIVE = 0
|
||||
local CHESS_WHITEMOVE = 1
|
||||
local CHESS_BLACKMOVE = 2
|
||||
local CHESS_WHITEPROMO = 3 local CHESS_WHITEJUMP = 3
|
||||
local CHESS_BLACKPROMO = 4 local CHESS_BLACKJUMP = 4
|
||||
local CHESS_WAGER = 5
|
||||
|
||||
--Captured piece squares
|
||||
local CHESS_WCAP1 = 10
|
||||
local CHESS_WCAP2 = 11
|
||||
local CHESS_BCAP1 = 12
|
||||
local CHESS_BCAP2 = 13
|
||||
|
||||
-- Draw Offer
|
||||
local PLAYER_NONE = 0 -- Nobody offering to draw
|
||||
local PLAYER_WHITE = 1 -- White offering to draw
|
||||
local PLAYER_BLACK = 2 -- Black offering to draw
|
||||
|
||||
local NumToLetter = {"a", "b", "c", "d", "e", "f", "g", "h", ["a"]=1, ["b"]=2, ["c"]=3, ["d"]=4, ["e"]=5, ["f"]=6, ["g"]=7, ["h"]=8} --Used extensively for conversions
|
||||
|
||||
ENT.StartState = CHESS_BLACKMOVE
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
-- self:NetworkVar( "Int", 0, "BlackPassant" )
|
||||
-- self:NetworkVar( "Int", 1, "WhitePassant" )
|
||||
|
||||
self:NetworkVar( "Int", 2, "ChessState" )
|
||||
self:NetworkVar( "Bool", 0, "Playing" )
|
||||
self:NetworkVar( "Int", 3, "DrawOffer" )
|
||||
|
||||
self:NetworkVar( "Float", 0, "WhiteWager" )
|
||||
self:NetworkVar( "Float", 1, "BlackWager" )
|
||||
|
||||
self:NetworkVar( "Entity", 0, "WhitePlayer" )
|
||||
self:NetworkVar( "Entity", 1, "BlackPlayer" )
|
||||
self:NetworkVar( "Entity", 2, "TableEnt" )
|
||||
|
||||
-- self:NetworkVar( "Int", 3, "MoveCount" )
|
||||
-- self:NetworkVar( "Bool", 1, "Repetition" )
|
||||
|
||||
self:NetworkVar( "Bool", 2, "PSWager" )
|
||||
|
||||
self:NetworkVar( "Float", 2, "WhiteTime" )
|
||||
self:NetworkVar( "Float", 3, "BlackTime" )
|
||||
|
||||
--Draughts vars
|
||||
self:NetworkVar( "Bool", 1, "JumpMove" )
|
||||
self:NetworkVar( "Int", 0, "JumpLet" )
|
||||
self:NetworkVar( "Int", 1, "JumpNum" )
|
||||
end
|
||||
|
||||
function ENT:Initialize()
|
||||
self.ChessDerived = true
|
||||
self.IsDraughts = true
|
||||
|
||||
return self.BaseClass.Initialize( self )
|
||||
end
|
||||
|
||||
function ENT:GetManMoves( tbl, GridLet, GridNum, IsWhite )
|
||||
local CapMove = false
|
||||
|
||||
--Forward Right
|
||||
local TargetRow = GridNum+ (IsWhite and 1 or (-1))
|
||||
local TargetColumn = NumToLetter[GridLet]+1
|
||||
if TargetRow<=8 and TargetRow>=1 and TargetColumn<=8 and TargetColumn>=1 then
|
||||
local target = self:GetSquare( NumToLetter[TargetColumn], TargetRow )
|
||||
if target then
|
||||
if ((self:SquareTeam(target)=="White")~=IsWhite) then --Enemy piece
|
||||
local CapRow = TargetRow+ (IsWhite and 1 or (-1))
|
||||
local CapCol = TargetColumn+1
|
||||
|
||||
if CapRow<=8 and CapRow>=1 and CapCol<=8 and CapCol>=1 then --In range
|
||||
local target = self:GetSquare( NumToLetter[CapCol], CapRow )
|
||||
if not target then --Empty space
|
||||
tbl[NumToLetter[CapCol]][CapRow] = {"CAPTURE", NumToLetter[TargetColumn], TargetRow} --Capture move
|
||||
CapMove = true --Flag as capture move
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
tbl[NumToLetter[TargetColumn]][TargetRow] = true --Standard valid move
|
||||
end
|
||||
end
|
||||
--Forward Left
|
||||
local TargetRow = GridNum+ (IsWhite and 1 or (-1))
|
||||
local TargetColumn = NumToLetter[GridLet]-1
|
||||
if TargetRow<=8 and TargetRow>=1 and TargetColumn<=8 and TargetColumn>=1 then
|
||||
local target = self:GetSquare( NumToLetter[TargetColumn], TargetRow )
|
||||
if target then
|
||||
if ((self:SquareTeam(target)=="White")~=IsWhite) then
|
||||
local CapRow = TargetRow+ (IsWhite and 1 or (-1))
|
||||
local CapCol = TargetColumn-1
|
||||
|
||||
if CapRow<=8 and CapRow>=1 and CapCol<=8 and CapCol>=1 then
|
||||
local target = self:GetSquare( NumToLetter[CapCol], CapRow )
|
||||
if not target then
|
||||
tbl[NumToLetter[CapCol]][CapRow] = {"CAPTURE", NumToLetter[TargetColumn], TargetRow}
|
||||
CapMove = true
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
tbl[NumToLetter[TargetColumn]][TargetRow] = true --Standard valid move
|
||||
end
|
||||
end
|
||||
|
||||
return CapMove
|
||||
end
|
||||
function ENT:GetKingMoves( tbl, GridLet, GridNum, IsWhite )
|
||||
local CapMove = self:GetManMoves( tbl, GridLet, GridNum, IsWhite ) --Forward moves
|
||||
|
||||
--Back Right
|
||||
local TargetRow = GridNum+ (IsWhite and (-1) or (1))
|
||||
local TargetColumn = NumToLetter[GridLet]+1
|
||||
if TargetRow<=8 and TargetRow>=1 and TargetColumn<=8 and TargetColumn>=1 then
|
||||
local target = self:GetSquare( NumToLetter[TargetColumn], TargetRow )
|
||||
if target then
|
||||
if ((self:SquareTeam(target)=="White")~=IsWhite) then
|
||||
local CapRow = TargetRow+ (IsWhite and (-1) or (1))
|
||||
local CapCol = TargetColumn+1
|
||||
|
||||
if CapRow<=8 and CapRow>=1 and CapCol<=8 and CapCol>=1 then
|
||||
local target = self:GetSquare( NumToLetter[CapCol], CapRow )
|
||||
if not target then
|
||||
tbl[NumToLetter[CapCol]][CapRow] = {"CAPTURE", NumToLetter[TargetColumn], TargetRow}
|
||||
CapMove = true
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
tbl[NumToLetter[TargetColumn]][TargetRow] = true --Standard valid move
|
||||
end
|
||||
end
|
||||
--Back Left
|
||||
local TargetRow = GridNum+ (IsWhite and (-1) or (1))
|
||||
local TargetColumn = NumToLetter[GridLet]-1
|
||||
if TargetRow<=8 and TargetRow>=1 and TargetColumn<=8 and TargetColumn>=1 then
|
||||
local target = self:GetSquare( NumToLetter[TargetColumn], TargetRow )
|
||||
if target then
|
||||
if ((self:SquareTeam(target)=="White")~=IsWhite) then
|
||||
local CapRow = TargetRow+ (IsWhite and (-1) or (1))
|
||||
local CapCol = TargetColumn-1
|
||||
|
||||
if CapRow<=8 and CapRow>=1 and CapCol<=8 and CapCol>=1 then
|
||||
local target = self:GetSquare( NumToLetter[CapCol], CapRow )
|
||||
if not target then
|
||||
tbl[NumToLetter[CapCol]][CapRow] = {"CAPTURE", NumToLetter[TargetColumn], TargetRow}
|
||||
CapMove = true
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
tbl[NumToLetter[TargetColumn]][TargetRow] = true --Standard valid move
|
||||
end
|
||||
end
|
||||
|
||||
return CapMove
|
||||
end
|
||||
function ENT:GetMove( GridLet, GridNum, IgnoreCap )
|
||||
if not (GridLet and GridNum) then return {} end
|
||||
if not NumToLetter[GridLet] then return {} end
|
||||
if NumToLetter[GridLet]<1 or NumToLetter[GridLet]>8 then return {} end
|
||||
if GridNum<1 or GridNum>8 then return {} end
|
||||
|
||||
local square = self:GetSquare( GridLet, GridNum )
|
||||
if not square then return {} end
|
||||
|
||||
local class = square.Class or (IsValid(square.Ent) and square.Ent:GetRole())
|
||||
if not class then return {} end
|
||||
|
||||
if self:GetJumpMove() and self:GetJumpLet()~=0 and self:GetJumpNum()~=0 and (NumToLetter[GridLet]~=self:GetJumpLet() or GridNum~=self:GetJumpNum()) then return {} end
|
||||
|
||||
local IsWhite = self:SquareTeam(square)=="White"
|
||||
local Moved = self:SquareMoved(square)
|
||||
|
||||
local CanJump = IgnoreCap or self:CanCapture( IsWhite )
|
||||
local tbl = { ["a"] = {}, ["b"] = {}, ["c"] = {}, ["d"] = {}, ["e"] = {}, ["f"] = {}, ["g"] = {}, ["h"] = {} }
|
||||
if class=="King" then
|
||||
self:GetKingMoves( tbl, GridLet, GridNum, IsWhite )
|
||||
else
|
||||
self:GetManMoves( tbl, GridLet, GridNum, IsWhite )
|
||||
end
|
||||
|
||||
if CanJump then
|
||||
for CheckLet,File in pairs(tbl) do
|
||||
for CheckNum,v in pairs(File) do
|
||||
if v==true then
|
||||
tbl[CheckLet][CheckNum] = nil --We can capture, but this isn't a capture move
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
function ENT:ResetBoard()
|
||||
if SERVER then
|
||||
self:SetDrawOffer( PLAYER_NONE )
|
||||
|
||||
self:SetWhiteWager( -1 )
|
||||
self:SetBlackWager( -1 )
|
||||
|
||||
self:SetWhiteTime( 600 )
|
||||
self:SetBlackTime( 600 )
|
||||
|
||||
self:SetJumpMove( false )
|
||||
self:SetJumpLet( 0 )
|
||||
self:SetJumpNum( 0 )
|
||||
end
|
||||
self:RefreshSquares()
|
||||
|
||||
if self.Pieces then
|
||||
for _,File in pairs( self.Pieces ) do
|
||||
for _,Square in pairs(File) do
|
||||
if IsValid(Square.Ent) then Square.Ent:SetGridNum(-1) Square.Ent:Remove() end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.Pieces = {
|
||||
["a"] = {
|
||||
[1] = {Team="White",Class="Man",Moved=false}, [3] = {Team="White",Class="Man",Moved=false}, [7] = {Team="Black",Class="Man",Moved=false},
|
||||
},
|
||||
["b"] = {
|
||||
[2] = {Team="White",Class="Man",Moved=false}, [6] = {Team="Black",Class="Man",Moved=false}, [8] = {Team="Black",Class="Man",Moved=false},
|
||||
},
|
||||
["c"] = {
|
||||
[1] = {Team="White",Class="Man",Moved=false}, [3] = {Team="White",Class="Man",Moved=false}, [7] = {Team="Black",Class="Man",Moved=false},
|
||||
},
|
||||
["d"] = {
|
||||
[2] = {Team="White",Class="Man",Moved=false}, [6] = {Team="Black",Class="Man",Moved=false}, [8] = {Team="Black",Class="Man",Moved=false},
|
||||
},
|
||||
["e"] = {
|
||||
[1] = {Team="White",Class="Man",Moved=false}, [3] = {Team="White",Class="Man",Moved=false}, [7] = {Team="Black",Class="Man",Moved=false},
|
||||
},
|
||||
["f"] = {
|
||||
[2] = {Team="White",Class="Man",Moved=false}, [6] = {Team="Black",Class="Man",Moved=false}, [8] = {Team="Black",Class="Man",Moved=false},
|
||||
},
|
||||
["g"] = {
|
||||
[1] = {Team="White",Class="Man",Moved=false}, [3] = {Team="White",Class="Man",Moved=false}, [7] = {Team="Black",Class="Man",Moved=false},
|
||||
},
|
||||
["h"] = {
|
||||
[2] = {Team="White",Class="Man",Moved=false}, [6] = {Team="Black",Class="Man",Moved=false}, [8] = {Team="Black",Class="Man",Moved=false},
|
||||
},
|
||||
[CHESS_WCAP1] = {}, [CHESS_WCAP2] = {}, [CHESS_BCAP1] = {}, [CHESS_BCAP2] = {},
|
||||
}
|
||||
self:Update()
|
||||
end
|
||||
|
||||
function ENT:CanCapture( White )
|
||||
for Let,column in pairs( self.Pieces ) do
|
||||
for Num,square in pairs( column ) do
|
||||
if square.Team==(White and "White" or "Black") then
|
||||
local moves = self:GetMove( Let, Num, true )
|
||||
for _,column in pairs( moves ) do
|
||||
for _,move in pairs( column ) do
|
||||
if type(move)=="table" and move[1]=="CAPTURE" then return true end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
function ENT:CanMove( White )
|
||||
for Let,column in pairs( self.Pieces ) do
|
||||
for Num,square in pairs( column ) do
|
||||
if square.Team==(White and "White" or "Black") then
|
||||
local moves = self:GetMove( Let, Num )
|
||||
for _,column in pairs( moves ) do
|
||||
for _,move in pairs( column ) do
|
||||
if move then return true end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:NoMaterialCheck()
|
||||
local BlackMat = {}
|
||||
local WhiteMat = {}
|
||||
|
||||
for GridLet,File in pairs(self.Pieces) do
|
||||
if GridLet==CHESS_WCAP1 or GridLet==CHESS_WCAP2 or GridLet==CHESS_BCAP1 or GridLet==CHESS_BCAP2 then continue end
|
||||
for GridNum,square in pairs(File) do
|
||||
if square then
|
||||
local IsWhite = self:SquareTeam(square)=="White"
|
||||
|
||||
if IsWhite then
|
||||
table.insert( WhiteMat, {Square=square, Class=Class, GridLet=GridLet, GridNum=GridNum} )
|
||||
else
|
||||
table.insert( BlackMat, {square=square, Class=Class, GridLet=GridLet, GridNum=GridNum} )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (#BlackMat+#WhiteMat)==0 then self:EndGame() return false end
|
||||
if #WhiteMat==0 then self:EndGame("Black") return false end
|
||||
if #BlackMat==0 then self:EndGame("White") return false end
|
||||
|
||||
return true
|
||||
end
|
||||
function ENT:EndGame( winner, NoMsg )
|
||||
self:SetChessState( CHESS_INACTIVE )
|
||||
self:SetPlaying( false )
|
||||
|
||||
local White = self:GetPlayer( "White" )
|
||||
local Black = self:GetPlayer( "Black" )
|
||||
local WhiteName = IsValid(White) and White:Nick() or "[Anonymous White]"
|
||||
local BlackName = IsValid(Black) and Black:Nick() or "[Anonymous Black]"
|
||||
if not NoMsg then
|
||||
net.Start( "Chess GameOver" )
|
||||
if winner=="White" then
|
||||
net.WriteTable( {" ", Color(255,255,255), WhiteName, Color(150,255,150), " has won against ", Color(100,100,100), BlackName, Color(150,255,150), "!"} )
|
||||
else
|
||||
net.WriteTable( {" ", Color(100,100,100), BlackName, Color(150,255,150), " has won against ", Color(255,255,255), WhiteName, Color(150,255,150), "!"} )
|
||||
end
|
||||
net.WriteString( "icon16/medal_gold_2.png" )
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
timer.Simple( 0.5, function()
|
||||
if not IsValid(self) then return end
|
||||
if IsValid(Black) and Black:GetVehicle()==self.BlackSeat then Black:ExitVehicle() end
|
||||
if IsValid(White) and White:GetVehicle()==self.WhiteSeat then White:ExitVehicle() end
|
||||
end)
|
||||
|
||||
local winnings = (self.WagerValue or 0)*2
|
||||
if IsValid( White ) then
|
||||
if winner=="White" then
|
||||
if IsValid(Black) then White:DraughtsWin( Black ) end
|
||||
if self.WagerValue then
|
||||
if self:GetPSWager() then
|
||||
White:PS_GivePoints( winnings )
|
||||
else
|
||||
if White.addMoney then White:addMoney( winnings ) else White:SetDarkRPVar( "money", (White:getDarkRPVar( "money" ) or 0) + winnings ) end
|
||||
end
|
||||
end
|
||||
elseif winner~="Black" then
|
||||
if IsValid(Black) and winner~="Error" then White:DraughtsDraw( Black ) end
|
||||
if self.WagerValue then
|
||||
if self:GetPSWager() then
|
||||
White:PS_GivePoints( self.WagerValue )
|
||||
else
|
||||
if White.addMoney then White:addMoney( self.WagerValue ) else White:SetDarkRPVar( "money", (White:getDarkRPVar( "money" ) or 0) + self.WagerValue ) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if IsValid( Black ) then
|
||||
if winner=="Black" then
|
||||
if IsValid(White) then Black:DraughtsWin( White ) end
|
||||
if self.WagerValue then
|
||||
if self:GetPSWager() then
|
||||
Black:PS_GivePoints( winnings )
|
||||
else
|
||||
if Black.addMoney then Black:addMoney( winnings ) else Black:SetDarkRPVar( "money", (Black:getDarkRPVar( "money" ) or 0) + winnings ) end
|
||||
end
|
||||
end
|
||||
elseif winner~="White" then
|
||||
if self.WagerValue then
|
||||
if self:GetPSWager() then
|
||||
White:PS_GivePoints( self.WagerValue )
|
||||
else
|
||||
if White.addMoney then White:addMoney( self.WagerValue ) else White:SetDarkRPVar( "money", (White:getDarkRPVar( "money" ) or 0) + self.WagerValue ) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function ENT:DoCapture( square, EndLet, EndNum )
|
||||
if not square then return end
|
||||
|
||||
local class = square.Class
|
||||
|
||||
local made = false
|
||||
local CapLet,CapNum
|
||||
if square.Team=="White" then --Black captured
|
||||
for i=CHESS_BCAP1,CHESS_BCAP2 do
|
||||
for n=1,8 do
|
||||
local CapSq = self:GetSquare( i, n )
|
||||
if not CapSq then
|
||||
self.Pieces[i][n] = {Team="White", Class=class, Moved=false}
|
||||
CapSq = self.Pieces[i][n]
|
||||
|
||||
made = true
|
||||
CapLet,CapNum = i,n
|
||||
break
|
||||
end
|
||||
end
|
||||
if made then break end
|
||||
end
|
||||
else
|
||||
for i=CHESS_WCAP1,CHESS_WCAP2 do
|
||||
for n=1,8 do
|
||||
local CapSq = self:GetSquare( i, n )
|
||||
if not CapSq then
|
||||
self.Pieces[i][n] = {Team="Black", Class=class, Moved=false}
|
||||
CapSq = self.Pieces[i][n]
|
||||
|
||||
made = true
|
||||
CapLet,CapNum = i,n
|
||||
break
|
||||
end
|
||||
end
|
||||
if made then break end
|
||||
end
|
||||
end
|
||||
|
||||
return {From={EndLet,EndNum}, To={CapLet,CapNum}}
|
||||
end
|
||||
function ENT:DoMove( StartLet, StartNum, EndLet, EndNum )
|
||||
if CLIENT then return end
|
||||
if not (StartLet and EndLet and StartNum and EndNum) then return end
|
||||
if (StartLet==EndLet) and (StartNum==EndNum) then return end
|
||||
|
||||
local Start = self:GetSquare( StartLet, StartNum )
|
||||
if not Start then return end
|
||||
|
||||
local Moves = self:GetMove( StartLet, StartNum )
|
||||
if not Moves[EndLet][EndNum] then return end
|
||||
local Move = Moves[EndLet][EndNum]
|
||||
|
||||
local CapMove
|
||||
if type(Move)=="table" then
|
||||
if Move[1]=="CAPTURE" then
|
||||
local CapLet, CapNum = Move[2], Move[3]
|
||||
local square = self:GetSquare( CapLet, CapNum )
|
||||
if CapLet and CapNum then
|
||||
CapMove = self:DoCapture( square, CapLet, CapNum )
|
||||
self.Pieces[CapLet][CapNum] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local End = self:GetSquare( EndLet, EndNum )
|
||||
if not End then
|
||||
self.Pieces[EndLet] = self.Pieces[EndLet] or {}
|
||||
self.Pieces[EndLet][EndNum] = self.Pieces[EndLet][EndNum] or {}
|
||||
End = self.Pieces[EndLet][EndNum]
|
||||
end
|
||||
|
||||
End.Team=Start.Team
|
||||
End.Class=Start.Class
|
||||
End.Moved=true
|
||||
|
||||
self.Pieces[StartLet][StartNum] = nil
|
||||
|
||||
local ply = self:GetPlayer( End.Team )
|
||||
if (EndNum==1 or EndNum==8) and End.Class=="Man" then --End of the board, promote
|
||||
End.Class = "King"
|
||||
// self:SetChessState( End.Team=="White" and CHESS_BLACKMOVE or CHESS_WHITEMOVE )
|
||||
//
|
||||
// self:SetJumpMove( false )
|
||||
// self:SetJumpLet( 0 )
|
||||
// self:SetJumpNum( 0 )
|
||||
end
|
||||
if type(Move)=="table" and Move[1]=="CAPTURE" then
|
||||
self:SetJumpMove(false)
|
||||
if self:CanCapture( End.Team=="White" ) then
|
||||
local GetMoves = self:GetMove(EndLet, EndNum)
|
||||
local Cap = false
|
||||
for _,column in pairs( GetMoves ) do
|
||||
for _,move in pairs(column) do
|
||||
if move and move~=true then
|
||||
Cap=true
|
||||
end
|
||||
end
|
||||
end
|
||||
if Cap then
|
||||
self:SetJumpMove( true )
|
||||
self:SetJumpLet( NumToLetter[EndLet] )
|
||||
self:SetJumpNum( EndNum )
|
||||
else
|
||||
self:SetChessState( End.Team=="White" and CHESS_BLACKMOVE or CHESS_WHITEMOVE )
|
||||
|
||||
self:SetJumpMove( false )
|
||||
self:SetJumpLet( 0 )
|
||||
self:SetJumpNum( 0 )
|
||||
end
|
||||
else
|
||||
self:SetChessState( End.Team=="White" and CHESS_BLACKMOVE or CHESS_WHITEMOVE )
|
||||
|
||||
self:SetJumpMove( false )
|
||||
self:SetJumpLet( 0 )
|
||||
self:SetJumpNum( 0 )
|
||||
end
|
||||
else --Standard move, other player's turn
|
||||
self:SetChessState( End.Team=="White" and CHESS_BLACKMOVE or CHESS_WHITEMOVE )
|
||||
|
||||
self:SetJumpMove( false )
|
||||
self:SetJumpLet( 0 )
|
||||
self:SetJumpNum( 0 )
|
||||
end
|
||||
|
||||
local move = {From={StartLet,StartNum},To={EndLet,EndNum}}
|
||||
self:Update( move, CapMove )
|
||||
|
||||
self:NoMaterialCheck()
|
||||
|
||||
if self:GetChessState()==CHESS_BLACKMOVE and not self:CanMove( false ) then self:EndGame( "White" ) end
|
||||
if self:GetChessState()==CHESS_WHITEMOVE and not self:CanMove( true ) then self:EndGame( "Black" ) end
|
||||
|
||||
return move
|
||||
end
|
||||
|
||||
function ENT:GetElo( ply )
|
||||
return IsValid(ply) and " ("..ply:GetDraughtsElo()..")" or ""
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
local PanelCol = {
|
||||
Main = Color(0,0,0,200), ToMove = Color(200,200,200,20), Text = Color(180,180,180),
|
||||
White = Color(255,255,255), Black = Color(20,20,20,255),
|
||||
}
|
||||
local StateToString = {[CHESS_INACTIVE] = "Waiting", [CHESS_WHITEMOVE] = "White", [CHESS_BLACKMOVE] = "Black", [CHESS_WHITEPROMO] = "White (jumping)", [CHESS_BLACKPROMO] = "Black (jumping)", [CHESS_WAGER] = "Wagers"}
|
||||
function ENT:CreateChessPanel()
|
||||
self:EndSpectating()
|
||||
|
||||
local frame = vgui.Create( "DFrame" )
|
||||
frame:SetSize(400,115)
|
||||
frame:SetPos( (ScrW()/2)-100, ScrH()-150 )
|
||||
--frame:SetDraggable( false )
|
||||
frame:SetTitle( "" )
|
||||
frame:ShowCloseButton( false )
|
||||
frame:SetDeleteOnClose( true )
|
||||
frame.Paint = function( s,w,h )
|
||||
if not IsValid(self) then
|
||||
s:Remove()
|
||||
gui.EnableScreenClicker( false )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
draw.RoundedBox( 8, 0, 0, w, h, PanelCol.Main )
|
||||
end
|
||||
frame:DockMargin( 0,0,0,0 )
|
||||
frame:DockPadding( 5,6,5,5 )
|
||||
|
||||
local TimePnl = vgui.Create( "DPanel", frame )
|
||||
TimePnl:Dock( RIGHT )
|
||||
TimePnl:SetWide( 100 )
|
||||
TimePnl:DockMargin( 2,2,2,2 )
|
||||
TimePnl.Paint = function(s,w,h)
|
||||
if not IsValid(self) then return end
|
||||
|
||||
draw.RoundedBox( 16, 0, 0, w, (h/2)-1, PanelCol.ToMove )
|
||||
draw.RoundedBox( 16, 0, (h/2)+1, w, (h/2)-1, PanelCol.ToMove )
|
||||
|
||||
draw.SimpleText( string.FormattedTime( math.Round(self:GetWhiteTime() or 300,1), "%02i:%02i" ), "ChessText", w/2, h/4, PanelCol.White, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
draw.SimpleText( string.FormattedTime( math.Round(self:GetBlackTime() or 300,1), "%02i:%02i" ), "ChessText", w/2, (h/4)+(h/2), PanelCol.Black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
|
||||
local ButtonPanel = vgui.Create( "DPanel", frame )
|
||||
ButtonPanel:SetSize( 100, 20 )
|
||||
ButtonPanel:Dock( LEFT )
|
||||
ButtonPanel.Paint = function() end
|
||||
|
||||
local ToMove = vgui.Create( "DPanel", frame )
|
||||
ToMove:SetSize(200,80)
|
||||
ToMove:Dock( FILL )
|
||||
ToMove.Paint = function( s,w,h )
|
||||
draw.RoundedBox( 4, 0, 0, w, h, PanelCol.ToMove )
|
||||
draw.SimpleText( "To move", "ChessTextSmall", 5, 0, PanelCol.Text )
|
||||
local state = IsValid(self) and self:GetChessState()
|
||||
if not (IsValid( self ) and state) then
|
||||
draw.SimpleText( "[N/A]", "ChessTextSmall", w/2, h/2, PanelCol.Text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
else
|
||||
local str = (StateToString[state] or "N/A")..( (self:GetPlaying() and self:GetJumpMove() and " (jump)") or "" )
|
||||
local col = ((state==CHESS_WHITEMOVE or state==CHESS_WHITEPROMO) and PanelCol.White) or ((state==CHESS_BLACKMOVE or state==CHESS_BLACKPROMO) and PanelCol.Black) or PanelCol.Text
|
||||
draw.SimpleText( str, "ChessTextLarge", w/2, h/2, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
|
||||
frame.OfferDraw = vgui.Create( "DButton", ButtonPanel)
|
||||
frame.OfferDraw:SetSize(94,35)
|
||||
frame.OfferDraw:Dock( TOP )
|
||||
frame.OfferDraw:SetText( "Offer Draw" )
|
||||
frame.OfferDraw.DoClick = function( s )
|
||||
if (IsValid(self)) and not (self:GetPlaying()) then
|
||||
chat.AddText( Color(150,255,150), "You can't offer a draw before the game starts!" )
|
||||
return
|
||||
end
|
||||
net.Start( "Chess DrawOffer" ) net.SendToServer()
|
||||
-- s:SetText( "Draw offered" )
|
||||
end
|
||||
frame.OfferDraw.Think = function(s)
|
||||
if IsValid(self) and self:GetDrawOffer()~=PLAYER_NONE then
|
||||
if s.TextChanged then return end
|
||||
s.TextChanged = true
|
||||
|
||||
if LocalPlayer()==self:GetWhitePlayer() then
|
||||
if self:GetDrawOffer()==PLAYER_WHITE then
|
||||
s:SetText( "Draw Offered" )
|
||||
elseif self:GetDrawOffer()==PLAYER_BLACK then
|
||||
s:SetText( "Accept Draw Offer" )
|
||||
end
|
||||
elseif LocalPlayer()==self:GetBlackPlayer() then
|
||||
if self:GetDrawOffer()==PLAYER_WHITE then
|
||||
s:SetText( "Accept Draw Offer" )
|
||||
elseif self:GetDrawOffer()==PLAYER_BLACK then
|
||||
s:SetText( "Draw Offered" )
|
||||
end
|
||||
end
|
||||
elseif s.TextChanged then
|
||||
s.TextChanged = false
|
||||
s:SetText( "Offer Draw" )
|
||||
end
|
||||
end
|
||||
|
||||
local Resign = vgui.Create( "DButton", ButtonPanel)
|
||||
Resign:SetSize(94,35)
|
||||
Resign:Dock( TOP )
|
||||
Resign:SetText( "Resign" )
|
||||
Resign.DoClick = function( s )
|
||||
net.Start( "Chess ClientResign" ) net.SendToServer() --No client-side exit func :/
|
||||
end
|
||||
|
||||
local DermaMode = vgui.Create( "DButton", ButtonPanel)
|
||||
DermaMode:SetSize(94,35)
|
||||
DermaMode:Dock( TOP )
|
||||
DermaMode:SetText( "Toggle 2D Mode" )
|
||||
DermaMode.DoClick = function( s )
|
||||
if IsValid(Chess_2DDermaPanel) then
|
||||
Chess_2DDermaPanel:Remove()
|
||||
else
|
||||
Chess_Open2DBoard( self )
|
||||
end
|
||||
end
|
||||
|
||||
return frame
|
||||
end
|
||||
end
|
||||
@@ -1,241 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--Easy placement and saving for Chess boards
|
||||
if SERVER then AddCSLuaFile() end
|
||||
|
||||
SWEP.Base = "weapon_base"
|
||||
|
||||
SWEP.Category = "Game boards"
|
||||
SWEP.Author = "my_hat_stinks"
|
||||
SWEP.Instructions = "Left click to place a board, right click to remove a board, reload for menu"
|
||||
|
||||
SWEP.Spawnable = true
|
||||
SWEP.AdminOnly = true
|
||||
SWEP.AdminSpawnable = true
|
||||
|
||||
SWEP.Slot = 4
|
||||
SWEP.PrintName = "Chess Admin Tool"
|
||||
|
||||
SWEP.ViewModelFOV = 80
|
||||
SWEP.Weight = 5
|
||||
SWEP.AutoSwitchTo = false
|
||||
SWEP.AutoSwitchFrom = false
|
||||
|
||||
SWEP.WorldModel = "models/weapons/w_toolgun.mdl"
|
||||
SWEP.ViewModel = "models/weapons/c_toolgun.mdl"
|
||||
SWEP.UseHands = true
|
||||
|
||||
SWEP.Primary.Recoil = 1
|
||||
SWEP.Primary.Damage = 5
|
||||
SWEP.Primary.NumShots = 1
|
||||
SWEP.Primary.Cone = 0
|
||||
SWEP.Primary.Delay = 1
|
||||
|
||||
SWEP.Primary.ClipSize = -1
|
||||
SWEP.Primary.DefaultClip = -1
|
||||
SWEP.Primary.Automatic = false
|
||||
SWEP.Primary.Ammo = "none"
|
||||
SWEP.Primary.ClipMax = -1
|
||||
|
||||
SWEP.Secondary.ClipSize = -1
|
||||
SWEP.Secondary.DefaultClip = -1
|
||||
SWEP.Secondary.Automatic = false
|
||||
SWEP.Secondary.Ammo = "none"
|
||||
SWEP.Secondary.ClipMax = -1
|
||||
|
||||
SWEP.DeploySpeed = 1.5
|
||||
|
||||
SWEP.PrimaryAnim = ACT_VM_PRIMARYATTACK
|
||||
SWEP.ReloadAnim = ACT_VM_RELOAD
|
||||
SWEP.HoldType = "pistol"
|
||||
|
||||
SWEP.GameEntities = {
|
||||
{"Chess", "ent_chess_board", {["board"] = Model("models/props_phx/games/chess/board.mdl"), ["table"] = Model("models/props/de_tides/restaurant_table.mdl")}},
|
||||
{"Draughts/Checkers", "ent_draughts_board", {["board"] = Model("models/props_phx/games/chess/board.mdl"), ["table"] = Model("models/props/de_tides/restaurant_table.mdl")}},
|
||||
}
|
||||
function SWEP:SetupDataTables()
|
||||
self:NetworkVar( "Int", 0, "EntID" )
|
||||
end
|
||||
|
||||
function SWEP:Initialize()
|
||||
self:SetEntID( 1 ) --Chess by default
|
||||
end
|
||||
function SWEP:PrimaryAttack()
|
||||
if SERVER and IsValid( self.Owner ) then
|
||||
if not self.Owner:IsAdmin() then
|
||||
self.Owner:ChatPrint( "You are not allowed to use this tool!" )
|
||||
self:Remove()
|
||||
return
|
||||
end
|
||||
local tr = self.Owner:GetEyeTrace()
|
||||
if tr.Hit and tr.HitPos then
|
||||
local ent = ents.Create( self.GameEntities[self:GetEntID()][2] )
|
||||
ent:SetPos( tr.HitPos )
|
||||
ent:Spawn()
|
||||
end
|
||||
end
|
||||
end
|
||||
function SWEP:SecondaryAttack()
|
||||
if SERVER and IsValid( self.Owner ) then
|
||||
if not self.Owner:IsAdmin() then
|
||||
self.Owner:ChatPrint( "You are not allowed to use this tool!" )
|
||||
self:Remove()
|
||||
return
|
||||
end
|
||||
|
||||
local tr = self.Owner:GetEyeTrace()
|
||||
if IsValid(tr.Entity) and tr.Entity.IsChessEntity then tr.Entity:Remove() end
|
||||
end
|
||||
end
|
||||
function SWEP:Reload()
|
||||
if CLIENT then self:OpenMenu() end
|
||||
end
|
||||
|
||||
function SWEP:OpenMenu()
|
||||
if SERVER then return end
|
||||
if IsValid(self.Menu) then self.Menu:Remove() end
|
||||
|
||||
self.Menu = vgui.Create( "DFrame" )
|
||||
self.Menu:SetTitle( "Chess Admin Tool" )
|
||||
self.Menu:SetSize( 300, 80 )
|
||||
self.Menu:SetPos( ScrW()/2-150, ScrH()/2-50 )
|
||||
self.Menu:MakePopup()
|
||||
|
||||
local drop = vgui.Create( "DComboBox", self.Menu )
|
||||
drop:Dock( TOP )
|
||||
drop:SetValue( "Select Board" )
|
||||
for i=1,#self.GameEntities do
|
||||
drop:AddChoice( self.GameEntities[i][1], i )
|
||||
end
|
||||
drop.OnSelect = function( s, ind, val, data )
|
||||
RunConsoleCommand( "chess_admin_toolent", tostring(data) )
|
||||
end
|
||||
|
||||
local btnpnl = vgui.Create( "DPanel", self.Menu )
|
||||
btnpnl:Dock( BOTTOM )
|
||||
btnpnl:SetTall( 20 )
|
||||
btnpnl.Paint = function() end
|
||||
|
||||
local close = vgui.Create( "DButton", btnpnl )
|
||||
close:SetWidth( 98 )
|
||||
close:Dock( RIGHT )
|
||||
close:SetText( "Close" )
|
||||
close.DoClick = function(s) if IsValid(self) and IsValid(self.Menu) then self.Menu:Remove() end end
|
||||
|
||||
local rem = vgui.Create( "DButton", btnpnl )
|
||||
rem:SetWidth( 98 )
|
||||
rem:Dock( LEFT )
|
||||
rem:SetText( "Remove tool" )
|
||||
rem.DoClick = function(s)
|
||||
RunConsoleCommand( "chess_admin_toolremove" )
|
||||
if IsValid(self) and IsValid(self.Menu) then self.Menu:Remove() end
|
||||
end
|
||||
|
||||
local sv = vgui.Create( "DButton", btnpnl )
|
||||
sv:SetWidth( 98 )
|
||||
sv:Dock( FILL )
|
||||
sv:SetText( "Save boards" )
|
||||
sv.DoClick = function(s)
|
||||
RunConsoleCommand( "chess_save" )
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:OnRemove()
|
||||
if CLIENT and self.Ghosts then
|
||||
for _,v in pairs(self.Ghosts) do if IsValid(v) then v:Remove() end end
|
||||
end
|
||||
end
|
||||
function SWEP:Holster()
|
||||
if CLIENT then
|
||||
if self.Ghosts then
|
||||
for _,v in pairs(self.Ghosts) do if IsValid(v) then v:Remove() end end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
local function SetToolEnt( tool, index )
|
||||
if tool.GameEntities[index] then tool:SetEntID( index ) end
|
||||
end
|
||||
concommand.Add( "chess_admin_toolent", function(p,c,a)
|
||||
if IsValid(p:GetActiveWeapon()) and p:GetActiveWeapon():GetClass()=="chess_admin_tool" then
|
||||
SetToolEnt( p:GetActiveWeapon(), tonumber(a[1]) )
|
||||
end
|
||||
end)
|
||||
concommand.Add( "chess_admin_toolremove", function(p,c,a)
|
||||
if IsValid(p:GetActiveWeapon()) and p:GetActiveWeapon():GetClass()=="chess_admin_tool" then p:GetActiveWeapon():Remove() end
|
||||
end)
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
surface.CreateFont( "ChessAdmin", {
|
||||
font="Arial", size=40,
|
||||
})
|
||||
local ColBox = Color(0,0,0,150)
|
||||
local ColText = Color(255,255,255,255)
|
||||
local ColGhost = Color(0,255,0,150)
|
||||
function SWEP:DrawHUD()
|
||||
local w,h = ScrW(), ScrH()
|
||||
local txt = "Board: ".. tostring( self.GameEntities[self:GetEntID()][1] )
|
||||
|
||||
surface.SetFont( "ChessAdmin" )
|
||||
local tw, th = surface.GetTextSize( txt )
|
||||
|
||||
surface.SetDrawColor( ColBox )
|
||||
surface.DrawRect( (w/2) - ((tw/2)+3), h - (th+6), tw+6, th+6 )
|
||||
|
||||
draw.DrawText( txt, "ChessAdmin", w/2, h-(th)-3, ColText, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
|
||||
function SWEP:DoGhosts()
|
||||
local tr = self.Owner:GetEyeTrace()
|
||||
if (not tr.Hit) then return end
|
||||
|
||||
self.Ghosts = self.Ghosts or {}
|
||||
local mdltbl = self.GameEntities[self:GetEntID()][3]
|
||||
if not mdltbl then return end
|
||||
|
||||
self.Ghosts[1] = IsValid(self.Ghosts[1]) and self.Ghosts[1] or ClientsideModel( mdltbl.table, RENDERGROUP_BOTH )
|
||||
self.Ghosts[1]:SetPos( tr.HitPos )
|
||||
self.Ghosts[1]:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
self.Ghosts[1]:SetColor( ColGhost )
|
||||
|
||||
local h = 30 --(self.Ghosts[1]:OBBMaxs()[3] - self.Ghosts[1]:OBBMins()[3])
|
||||
self.Ghosts[2] = IsValid(self.Ghosts[2]) and self.Ghosts[2] or ClientsideModel( mdltbl.board, RENDERGROUP_BOTH )
|
||||
self.Ghosts[2]:SetPos( tr.HitPos+Vector(0,0,h or 50) )
|
||||
self.Ghosts[2]:SetAngles( Angle(-90,0,0) )
|
||||
self.Ghosts[2]:SetModelScale( 0.35, 0 )
|
||||
self.Ghosts[2]:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
self.Ghosts[2]:SetColor( ColGhost )
|
||||
|
||||
self.Ghosts[3] = IsValid(self.Ghosts[3]) and self.Ghosts[3] or ClientsideModel( "models/nova/chair_plastic01.mdl", RENDERGROUP_BOTH )
|
||||
self.Ghosts[3]:SetPos( tr.HitPos+ (self.Ghosts[2]:GetRight()*40) )
|
||||
self.Ghosts[3]:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
self.Ghosts[3]:SetColor( ColGhost )
|
||||
|
||||
self.Ghosts[4] = IsValid(self.Ghosts[4]) and self.Ghosts[4] or ClientsideModel( "models/nova/chair_plastic01.mdl", RENDERGROUP_BOTH )
|
||||
self.Ghosts[4]:SetPos( tr.HitPos+ (self.Ghosts[2]:GetRight()*-40) )
|
||||
self.Ghosts[4]:SetAngles( Angle(0,180,0) )
|
||||
self.Ghosts[4]:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
self.Ghosts[4]:SetColor( ColGhost )
|
||||
end
|
||||
function SWEP:PostDrawViewModel()
|
||||
if LocalPlayer()~=self.Owner then return self.BaseClass.PostDrawViewModel( self ) end
|
||||
self:DoGhosts()
|
||||
end
|
||||
function SWEP:DrawWorldModel()
|
||||
if LocalPlayer()~=self.Owner then return self.BaseClass.DrawWorldModel( self ) end
|
||||
self:DrawModel()
|
||||
self:DoGhosts()
|
||||
end
|
||||
end
|
||||
1
addons/csgocontent/models/weapons/w_rif_m4a1_mag.mdl
Normal file
@@ -0,0 +1 @@
|
||||
IDST1
|
||||
1
addons/csgocontent/models/weapons/w_rif_sg556_mag.mdl
Normal file
@@ -0,0 +1 @@
|
||||
IDST1
|
||||
1
addons/csgocontent/models/weapons/w_smg_mp7_mag.mdl
Normal file
@@ -0,0 +1 @@
|
||||
IDST1
|
||||
1
addons/csgocontent/models/weapons/w_snip_awp_mag.mdl
Normal file
@@ -0,0 +1 @@
|
||||
IDST1
|
||||
@@ -8,5 +8,5 @@
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
if slib and slib.loadFolder then slib.loadFolder("e_protect/", true) end
|
||||
hook.Add("slib:loadedUtils", "eP:Initialize", function() slib.loadFolder("e_protect/", true) end)
|
||||
if slib and slib.loadFolder then slib.loadFolder("e_protect/", true, {{"e_protect/", "sh_config.lua"}}) end
|
||||
hook.Add("slib:loadedUtils", "eP:Initialize", function() slib.loadFolder("e_protect/", true, {{"e_protect/", "sh_config.lua"}}) end)
|
||||
@@ -11,6 +11,10 @@
|
||||
eProtect = eProtect or {}
|
||||
eProtect.data = eProtect.data or {}
|
||||
|
||||
local margin = slib.getTheme("margin")
|
||||
local maincolor_7, maincolor_10, hovercolor, linecol, textcolor_min50 = slib.getTheme("maincolor", 7), slib.getTheme("maincolor", 10), slib.getTheme("hovercolor"), Color(0,0,0,160), slib.getTheme("textcolor", -50)
|
||||
local arrow_ico = Material("slib/down-arrow.png", "smooth noclamp")
|
||||
|
||||
local function networkData(data, ...)
|
||||
local args = {...}
|
||||
|
||||
@@ -82,13 +86,16 @@ end
|
||||
|
||||
local function openScreenshot(ply, id)
|
||||
if !IsValid(ply) then return end
|
||||
http.Fetch("https://stromic.dev/eprotect/img.php?id="..id, function(result)
|
||||
local nick = ply:Nick()
|
||||
|
||||
http.Fetch(eProtect.config["scURL"].."?id="..id, function(result)
|
||||
local sc_frame = vgui.Create("SFrame")
|
||||
sc_frame:SetSize(slib.getScaledSize(960, "x"), slib.getScaledSize(540, "y") + slib.getScaledSize(25, "y"))
|
||||
:setTitle(slib.getLang("eprotect", eProtect.config["language"], "sc-preview")..ply:Nick())
|
||||
sc_frame:SetSize(slib.getScaledSize(960, "x"), slib.getScaledSize(540 + 25, "y"))
|
||||
:setTitle(slib.getLang("eprotect", eProtect.config["language"], "sc-preview")..nick)
|
||||
:MakePopup()
|
||||
:addCloseButton()
|
||||
:Center()
|
||||
:SetBG(true, true, nil, true)
|
||||
:setBlur(true)
|
||||
|
||||
local display = vgui.Create("HTML", sc_frame.frame)
|
||||
@@ -101,6 +108,64 @@ local function sid64format(sid64)
|
||||
return slib.findName(sid64).." ("..sid64..")"
|
||||
end
|
||||
|
||||
local function showAlts(ply, json)
|
||||
local font, sid_font = slib.createFont("Roboto", 16), slib.createFont("Roboto", 12)
|
||||
local alts = util.JSONToTable(json)
|
||||
|
||||
local show_alts = vgui.Create("SFrame")
|
||||
show_alts:SetSize(slib.getScaledSize(470, "x"), slib.getScaledSize(360, "y"))
|
||||
:setTitle(slib.getLang("eprotect", eProtect.config["language"], "show-alts", ply))
|
||||
:MakePopup()
|
||||
:addCloseButton()
|
||||
:Center()
|
||||
:SetBG(true, true, nil, true)
|
||||
:setBlur(true)
|
||||
|
||||
local alts_scroll = vgui.Create("SScrollPanel", show_alts.frame)
|
||||
alts_scroll:Dock(FILL)
|
||||
alts_scroll:DockMargin(0,margin,0,0)
|
||||
|
||||
for k,v in ipairs(alts) do
|
||||
local alt = vgui.Create("EditablePanel", alts_scroll)
|
||||
alt:Dock(TOP)
|
||||
alt:DockMargin(margin,0,margin,margin)
|
||||
alt:SetTall(slib.getScaledSize(26, "y"))
|
||||
alt.Paint = function(s,w,h)
|
||||
surface.SetDrawColor(maincolor_7)
|
||||
surface.DrawRect(0,0,w,h)
|
||||
|
||||
surface.SetFont(font)
|
||||
|
||||
local name = slib.findName(v)
|
||||
local txt_w = surface.GetTextSize(name or "")
|
||||
|
||||
draw.SimpleText(name, font, h + margin * 2, h * .5, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("[ "..v.." ]", sid_font, (h + margin * 3) + txt_w, h * .5, textcolor_min50, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
local avatar = vgui.Create("EditablePanel", alt)
|
||||
avatar:SetSize(alt:GetTall(), alt:GetTall())
|
||||
avatar.Paint = function(s,w,h)
|
||||
surface.SetDrawColor(color_white)
|
||||
surface.SetMaterial(slib.findAvatar(v, true))
|
||||
surface.DrawTexturedRect(0,0,w,h)
|
||||
end
|
||||
|
||||
local open_profile_h = slib.getScaledSize(22, "y")
|
||||
local gap_top_bottom = (alt:GetTall() - open_profile_h) / 2
|
||||
|
||||
local open_profile = vgui.Create("SButton", alt)
|
||||
open_profile:setTitle(slib.getLang("eprotect", eProtect.config["language"], "open-profile"))
|
||||
open_profile:SetTall(open_profile_h)
|
||||
open_profile:Dock(RIGHT)
|
||||
open_profile:DockMargin(0,gap_top_bottom,margin,gap_top_bottom)
|
||||
|
||||
open_profile.DoClick = function()
|
||||
gui.OpenURL("http://steamcommunity.com/profiles/"..v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function fillCleanData(index, tbl)
|
||||
local files, directories = file.Find(index, "DATA")
|
||||
|
||||
@@ -122,10 +187,6 @@ local function fillCleanData(index, tbl)
|
||||
end
|
||||
end
|
||||
|
||||
local margin = slib.getTheme("margin")
|
||||
local maincolor_7, maincolor_10, hovercolor, linecol = slib.getTheme("maincolor", 7), slib.getTheme("maincolor", 10), slib.getTheme("hovercolor"), Color(0,0,0,160)
|
||||
local arrow_ico = Material("slib/down-arrow.png", "smooth noclamp")
|
||||
|
||||
local createPaginator = function(parent)
|
||||
local font = slib.createFont("Roboto", 16)
|
||||
local paginator_tall = slib.getScaledSize(25, "y")
|
||||
@@ -557,6 +618,41 @@ local function openMenu()
|
||||
for k,v in ipairs(tbl_detections) do
|
||||
local _, line = punishment_log:addLine(v.name, function() return slib.getLang("eprotect", eProtect.config["language"], v.reason) end, v.info, function() return slib.getLang("eprotect", eProtect.config["language"], typeToLang[tonumber(v.type)]) end)
|
||||
line.isLine = true
|
||||
|
||||
line.DoClick = function()
|
||||
local dropdown = vgui.Create("SDropDown")
|
||||
dropdown.buttonh = slib.getScaledSize(20, "y")
|
||||
dropdown.buttonfont = slib.createFont("Roboto", 15)
|
||||
dropdown.buttonbg = maincolor_10
|
||||
dropdown.buttoncol = maincolor_min35
|
||||
dropdown:addOption( slib.getLang("eprotect", eProtect.config["language"], "open-profile"))
|
||||
:addOption( slib.getLang("eprotect", eProtect.config["language"], "copy_name"))
|
||||
:addOption( slib.getLang("eprotect", eProtect.config["language"], "copy_steamid"))
|
||||
:addOption( slib.getLang("eprotect", eProtect.config["language"], "copy_steamid64"))
|
||||
|
||||
if v.reason == "alt-detection" and v.additional_info and v.additional_info != "NULL" and v.additional_info != "" then
|
||||
dropdown:addOption( slib.getLang("eprotect", eProtect.config["language"], "show_alts"))
|
||||
end
|
||||
|
||||
dropdown.onValueChange = function(val)
|
||||
if val == slib.getLang("eprotect", eProtect.config["language"], "open-profile") then
|
||||
gui.OpenURL("https://steamcommunity.com/profiles/"..v.sid64)
|
||||
elseif val == slib.getLang("eprotect", eProtect.config["language"], "copy_name") then
|
||||
SetClipboardText(v.name)
|
||||
slib.notify(eProtect.config["prefix"]..slib.getLang("eprotect", eProtect.config["language"], "copied_clipboard"))
|
||||
elseif val == slib.getLang("eprotect", eProtect.config["language"], "copy_steamid") then
|
||||
SetClipboardText(util.SteamIDFrom64(v.sid64))
|
||||
slib.notify(eProtect.config["prefix"]..slib.getLang("eprotect", eProtect.config["language"], "copied_clipboard"))
|
||||
elseif val == slib.getLang("eprotect", eProtect.config["language"], "copy_steamid64") then
|
||||
SetClipboardText(v.sid64)
|
||||
slib.notify(eProtect.config["prefix"]..slib.getLang("eprotect", eProtect.config["language"], "copied_clipboard"))
|
||||
elseif val == slib.getLang("eprotect", eProtect.config["language"], "show_alts") then
|
||||
showAlts(v.name, v.additional_info)
|
||||
end
|
||||
end
|
||||
|
||||
dropdown:popupAlone()
|
||||
end
|
||||
end
|
||||
|
||||
punishment_log:GetCanvas():SetTall(punishment_log:GetTall())
|
||||
11
addons/eprotect_1.4.20/lua/e_protect/client/cl_utils.lua
Normal file
@@ -11,6 +11,7 @@
|
||||
-- This is the default language! 76561198002319953
|
||||
if CLIENT then
|
||||
slib.setLang("eprotect", "en", "sc-preview", "Screenshot Preview - ")
|
||||
slib.setLang("eprotect", "en", "show-alts", "Alts Detected - %s")
|
||||
slib.setLang("eprotect", "en", "net-info", "Net Info - ")
|
||||
slib.setLang("eprotect", "en", "ip-info", "IP Info - ")
|
||||
slib.setLang("eprotect", "en", "id-info", "ID Info - ")
|
||||
@@ -115,6 +116,12 @@ if CLIENT then
|
||||
slib.setLang("eprotect", "en", "notified", "Notified")
|
||||
|
||||
slib.setLang("eprotect", "en", "copied_clipboard", "Copied to clipboard")
|
||||
slib.setLang("eprotect", "en", "open-profile", "Open Profile")
|
||||
|
||||
slib.setLang("eprotect", "en", "copy_name", "Copy Name")
|
||||
slib.setLang("eprotect", "en", "copy_steamid", "Copy SteamID")
|
||||
slib.setLang("eprotect", "en", "copy_steamid64", "Copy SteamID64")
|
||||
slib.setLang("eprotect", "en", "show_alts", "Show Alts")
|
||||
|
||||
slib.setLang("eprotect", "en", "page_of_page", "Page %s/%s")
|
||||
slib.setLang("eprotect", "en", "previous", "Previous")
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
local punished = {}
|
||||
|
||||
eProtect.logDetectionHandeler = function(ply, reason, info, type)
|
||||
eProtect.logDetectionHandeler = function(ply, reason, info, type, additional_info)
|
||||
if eProtect.data.general["bypassgroup"][ply:GetUserGroup()] or eProtect.config["disabledModules"]["detection_log"] then return end
|
||||
local sid, sid64 = ply:SteamID(), ply:SteamID64()
|
||||
|
||||
@@ -21,7 +21,7 @@ eProtect.logDetectionHandeler = function(ply, reason, info, type)
|
||||
|
||||
local name, sid64 = ply:Nick(), ply:SteamID64()
|
||||
|
||||
eProtect.logDetection(name, sid64, reason, info, type)
|
||||
eProtect.logDetection(name, sid64, reason, info, type, additional_info)
|
||||
end
|
||||
|
||||
if eProtect.queueNetworking then
|
||||
@@ -24,13 +24,8 @@ end
|
||||
|
||||
hook.Add("PlayerInitialSpawn", "eP:IPLogging", function(ply)
|
||||
if ply:IsBot() or eProtect.config["disabledModules"]["identifier"] then return end
|
||||
local ip = ""
|
||||
|
||||
for k,v in ipairs(string.ToTable(ply:IPAddress())) do
|
||||
if v == ":" then break end
|
||||
|
||||
ip = ip..v
|
||||
end
|
||||
local plyIP = ply:IPAddress()
|
||||
local ip = string.sub(plyIP, 1, string.find(plyIP, ":") - 1)
|
||||
|
||||
handleIPLoggin(ply, ip)
|
||||
end)
|
||||
@@ -276,7 +276,6 @@ hook.Add("PlayerInitialSpawn", "eP:AutomaticChecks", function(ply)
|
||||
if correlatedIPs and istable(correlatedIPs) and !table.IsEmpty(correlatedIPs) then
|
||||
detect_type = "correlated-ip"
|
||||
|
||||
local foundAlts = {}
|
||||
for k,v in ipairs(correlatedIPs) do
|
||||
table.insert(altsDetected, v.sid64)
|
||||
end
|
||||
@@ -297,7 +296,7 @@ hook.Add("PlayerInitialSpawn", "eP:AutomaticChecks", function(ply)
|
||||
if automatic_identifier == 1 then
|
||||
for k, v in ipairs(player.GetAll()) do
|
||||
if eProtect.data.general["notification-groups"][v:GetUserGroup()] then
|
||||
slib.notify(eProtect.config["prefix"]..slib.getLang("eprotect", eProtect.config["language"], "auto-detected-alt", ply:Nick(), detections), ply)
|
||||
slib.notify(eProtect.config["prefix"]..slib.getLang("eprotect", eProtect.config["language"], "auto-detected-alt", ply:Nick(), detections), v)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -313,7 +312,7 @@ hook.Add("PlayerInitialSpawn", "eP:AutomaticChecks", function(ply)
|
||||
end
|
||||
|
||||
if doneAction then
|
||||
eProtect.logDetectionHandeler(ply, "alt-detection", slib.getLang("eprotect", eProtect.config["language"], detect_type), settingConverter[automatic_identifier])
|
||||
eProtect.logDetectionHandeler(ply, "alt-detection", slib.getLang("eprotect", eProtect.config["language"], detect_type), settingConverter[automatic_identifier], util.TableToJSON(altsDetected))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -26,14 +26,17 @@ local create_queries = {
|
||||
reason CHAR(32),
|
||||
info CHAR(32),
|
||||
type INTEGER DEFAULT 0,
|
||||
logged_time INTEGER DEFAULT 0
|
||||
logged_time INTEGER DEFAULT 0,
|
||||
additional_info TEXT NULL
|
||||
)]],
|
||||
[3] = [[CREATE TABLE IF NOT EXISTS eprotect_http(
|
||||
id INTEGER PRIMARY KEY %s,
|
||||
link CHAR(64),
|
||||
type CHAR(6),
|
||||
called INTEGER DEFAULT 0
|
||||
)]]
|
||||
)]],
|
||||
[4] = [[ALTER TABLE eprotect_detections
|
||||
ADD additional_info TEXT NULL]]
|
||||
}
|
||||
|
||||
local function makeTables()
|
||||
@@ -105,8 +108,8 @@ end
|
||||
eProtect.correlateIP = function(target, callback)
|
||||
if !IsValid(target) then return end
|
||||
|
||||
local sid64 = target:SteamID64()
|
||||
local tbl = {}
|
||||
local sid64, tbl = target:SteamID64(), {}
|
||||
|
||||
query("SELECT * FROM eprotect_ips WHERE sid64 = '"..sid64.."'", function(result)
|
||||
if result and result[1] then
|
||||
local parent_tbl = {}
|
||||
@@ -157,8 +160,10 @@ eProtect.registerIP = function(sid64, ip, country, time)
|
||||
end)
|
||||
end
|
||||
|
||||
eProtect.logDetection = function(name, sid64, reason, info, type)
|
||||
query(string.format("INSERT INTO eprotect_detections(name, sid64, reason, info, type, logged_time) VALUES('%s', '%s', '%s', '%s', '%s', %s)", escape_str(name), escape_str(sid64), escape_str(reason), escape_str(info), escape_str(type), os.time()))
|
||||
eProtect.logDetection = function(name, sid64, reason, info, type, additional_info)
|
||||
additional_info = additional_info or ""
|
||||
|
||||
query(string.format("INSERT INTO eprotect_detections(name, sid64, reason, info, type, logged_time, additional_info) VALUES('%s', '%s', '%s', '%s', '%s', %s, '%s')", escape_str(name), escape_str(sid64), escape_str(reason), escape_str(info), escape_str(type), os.time(), escape_str(additional_info)))
|
||||
end
|
||||
|
||||
eProtect.logHTTP = function(link, type, called)
|
||||
@@ -16,12 +16,14 @@ eProtect.config["language"] = "en"
|
||||
|
||||
eProtect.config["prefix"] = "[eProtect] "
|
||||
|
||||
eProtect.config["storage_type"] = "mysql"-- (sql_local or mysql)
|
||||
eProtect.config["storage_type"] = "sql_local"-- (sql_local or mysql)
|
||||
|
||||
eProtect.config["disablehttplogging"] = false -- If a DRM is ran after eProtect it could break if they check for HTTP modifications! If so make this true.
|
||||
|
||||
eProtect.config["ignoreDRM"] = false
|
||||
|
||||
eProtect.config["scURL"] = "https://stromic.dev/eprotect/img.php" -- This is the URL used to handle screenshots, can be self hosted here: https://github.com/Stromic/eprotect-web
|
||||
|
||||
eProtect.config["punishMaliciousIntent"] = true
|
||||
|
||||
eProtect.config["disabledModules"] = {
|
||||
@@ -37,7 +39,5 @@ eProtect.config["disabledModules"] = {
|
||||
|
||||
eProtect.config["permission"] = {
|
||||
["owner"] = true,
|
||||
["superadmin"] = true,
|
||||
["community_manager"] = true,
|
||||
["headofstaff"] = true
|
||||
["superadmin"] = true
|
||||
}
|
||||
@@ -13,11 +13,11 @@ eProtect = eProtect or {}
|
||||
eProtect.config = eProtect.config or {}
|
||||
|
||||
eProtect.config["mysql_info"] = {
|
||||
host = "144.76.235.183",
|
||||
host = "",
|
||||
port = 3306,
|
||||
database = "s6_eprotect",
|
||||
username = "u6_PouapUJD6P",
|
||||
password = "V!3P!7^NQSXGvyBZPE76O^GY"
|
||||
database = "",
|
||||
username = "",
|
||||
password = ""
|
||||
}
|
||||
|
||||
eProtect.config["command"] = "!eprotect"
|
||||
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
|
Before Width: | Height: | Size: 8 B After Width: | Height: | Size: 8 B |
@@ -1,502 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
@@ -1,61 +0,0 @@
|
||||
# Garry's mod profiler
|
||||
|
||||
A profiler based on `debug.sethook` and `SysTime`. Perfect for the following things:
|
||||
|
||||
- Finding performance bottlenecks (i.e. which code is spent the most time on)
|
||||
- Finding out which functions are called most
|
||||
- Identifying the source of lag spikes
|
||||
- Profiling specific functions
|
||||
|
||||
This profiler distinguishes itself from [DBugR](https://github.com/oubliette32/DBugR) in its approach.
|
||||
DBugR profiles at hooks, timers and net messages specifically. FProfiler looks purely at functions outside of their context.
|
||||
|
||||
I would recommend DBugR to measure networking and/or specific hooks and/or timers. I would recommend FProfiler for the things mentioned above.
|
||||
|
||||
## Using FProfiler
|
||||
The `FProfiler` console command opens the profiler. Everything can be done from there.
|
||||
|
||||
Here's an explanation of the FProfiler menu:
|
||||
|
||||
UI thing | Description
|
||||
------------ | -------------
|
||||
Realm | Whether you're profiling the client or the server. Note: You need to be a SuperAdmin (or have the `FProfiler` permission in your favourite admin mod) to be allowed to do any serverside profiling!
|
||||
(Re)start profiling | Starts a profiling session. If there is any previous profiling session, it starts anew, disregarding any old data.
|
||||
Stop profiling | Stop an ongoing profiling session.
|
||||
Continue profiling | Continue a profiling session that has previously been stopped. It will simply continue gathering data.
|
||||
Profiling Focus | Focus the profiling on a specific function. Note: you **cannot** put arbitrary Lua in there, just function names! E.g. `player.GetAll` will work, but `hook.GetTable().Think.Cavity` will _not_.
|
||||
Bottlenecks | Shows the functions that the game has spent its most time on. The top ones are the ones that hurt your FPS most.
|
||||
Top n most expensive | Perfect for finding the cause of lag spikes. Lists the functions that took a long time on specific times they were called. Differs from the Bottlenecks tab in that Bottlenecks is about *all* the times the functions were called, this tab is about the single times they ran at their slowest.
|
||||
Focus button | Sets the profiling focus to the selected function
|
||||
|
||||
## Using FProfiler in code
|
||||
|
||||
FProfiler has an API, a simple one too. All functions listed below are shared:
|
||||
```lua
|
||||
-- Starts profiling.
|
||||
-- When focus is given, the profiler will only profile the focussed upon function, and the functions it calls
|
||||
FProfiler.start([focus])
|
||||
|
||||
-- Stops profiling
|
||||
FProfiler.stop()
|
||||
|
||||
-- Continue profiling
|
||||
FProfiler.continueProfiling()
|
||||
```
|
||||
|
||||
All the data of the profiling sessions can be seen in the `FProfiler` menu. Because of that, there need to be **no** data retrieving functions in the API.
|
||||
If you don't want to use the UI, you *probably* want the profiling in a custom format. There are some internal functions available for that. Check out `lua/fprofiler/gather.lua` and `lua/fprofiler/report`.
|
||||
|
||||
|
||||
## About bottlenecks
|
||||
|
||||
When faced with performance problems, people tend to dive in the code to perform micro-optimisations. Think of localising libraries to the scope of a file, storing `LocalPlayer()` in a variable for re-use and that kind of stuff. This is a naive approach and is unlikely to get you very far.
|
||||
The reason for that is very simple: micro-optimisations have **very** little effect on the eventual performance of the game. They're called micro-optimisations for a reason.
|
||||
|
||||
What you *should* be after is macro-optimisations, i.e. the big guys. Attacking those will give you the biggest benefit. Doubling your FPS is not uncommon when you attack the big guys.
|
||||
|
||||
What do I mean by macro-optimisation/the big guys you ask? Think of reducing an O(n^2) algorithm to an O(n lg n) one. Think of things like using more efficient data structures, more efficient algorithms, caching the results of complicated calculations, alternative ways to calculate things that don't give the exact right result, but give a "good enough" result and are *way faster* than the original algorithm. **THAT** kind of shit.
|
||||
|
||||
That's where the profiler comes in. Always mistrust what you **think** is a big performance hog is, **measure** it. Even the assumptions of people who have optimising code as their profession are often proven wrong by the profiler. Don't be smug and think you can do any better.
|
||||
|
||||
When working on performance, the profiler is to be your guide. The profiler is to tell you what to optimise. Do not bother with anything other than the most expensive functions for you will be wasting your time.
|
||||
@@ -1,48 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
FProfiler = FProfiler or {}
|
||||
FProfiler.Internal = {}
|
||||
FProfiler.UI = {}
|
||||
|
||||
AddCSLuaFile()
|
||||
AddCSLuaFile("fprofiler/cami.lua")
|
||||
AddCSLuaFile("fprofiler/gather.lua")
|
||||
AddCSLuaFile("fprofiler/report.lua")
|
||||
AddCSLuaFile("fprofiler/util.lua")
|
||||
AddCSLuaFile("fprofiler/prettyprint.lua")
|
||||
|
||||
AddCSLuaFile("fprofiler/ui/model.lua")
|
||||
AddCSLuaFile("fprofiler/ui/frame.lua")
|
||||
AddCSLuaFile("fprofiler/ui/clientcontrol.lua")
|
||||
AddCSLuaFile("fprofiler/ui/servercontrol.lua")
|
||||
|
||||
include("fprofiler/cami.lua")
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "FProfiler",
|
||||
MinAccess = "superadmin"
|
||||
}
|
||||
|
||||
|
||||
include("fprofiler/prettyprint.lua")
|
||||
include("fprofiler/util.lua")
|
||||
include("fprofiler/gather.lua")
|
||||
include("fprofiler/report.lua")
|
||||
|
||||
|
||||
if CLIENT then
|
||||
include("fprofiler/ui/model.lua")
|
||||
include("fprofiler/ui/frame.lua")
|
||||
include("fprofiler/ui/clientcontrol.lua")
|
||||
include("fprofiler/ui/servercontrol.lua")
|
||||
else
|
||||
include("fprofiler/ui/server.lua")
|
||||
end
|
||||
@@ -1,534 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[
|
||||
CAMI - Common Admin Mod Interface.
|
||||
Makes admin mods intercompatible and provides an abstract privilege interface
|
||||
for third party addons.
|
||||
|
||||
IMPORTANT: This is a draft script. It is very much WIP.
|
||||
|
||||
Follows the specification on this page:
|
||||
https://docs.google.com/document/d/1QIRVcAgZfAYf1aBl_dNV_ewR6P25wze2KmUVzlbFgMI
|
||||
|
||||
|
||||
Structures:
|
||||
CAMI_USERGROUP, defines the charactaristics of a usergroup:
|
||||
{
|
||||
Name
|
||||
string
|
||||
The name of the usergroup
|
||||
Inherits
|
||||
string
|
||||
The name of the usergroup this usergroup inherits from
|
||||
}
|
||||
|
||||
CAMI_PRIVILEGE, defines the charactaristics of a privilege:
|
||||
{
|
||||
Name
|
||||
string
|
||||
The name of the privilege
|
||||
MinAccess
|
||||
string
|
||||
One of the following three: user/admin/superadmin
|
||||
HasAccess
|
||||
function(
|
||||
privilege :: CAMI_PRIVILEGE,
|
||||
actor :: Player,
|
||||
target :: Player
|
||||
) :: bool
|
||||
optional
|
||||
Function that decides whether a player can execute this privilege,
|
||||
optionally on another player (target).
|
||||
}
|
||||
]]
|
||||
|
||||
-- Version number in YearMonthDay format.
|
||||
local version = 20150902.1
|
||||
|
||||
if CAMI and CAMI.Version >= version then return end
|
||||
|
||||
CAMI = CAMI or {}
|
||||
CAMI.Version = version
|
||||
|
||||
--[[
|
||||
usergroups
|
||||
Contains the registered CAMI_USERGROUP usergroup structures.
|
||||
Indexed by usergroup name.
|
||||
]]
|
||||
local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or {
|
||||
user = {
|
||||
Name = "user",
|
||||
Inherits = "user"
|
||||
},
|
||||
admin = {
|
||||
Name = "admin",
|
||||
Inherits = "user"
|
||||
},
|
||||
superadmin = {
|
||||
Name = "superadmin",
|
||||
Inherits = "admin"
|
||||
}
|
||||
}
|
||||
|
||||
--[[
|
||||
privileges
|
||||
Contains the registered CAMI_PRIVILEGE privilege structures.
|
||||
Indexed by privilege name.
|
||||
]]
|
||||
local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {}
|
||||
|
||||
--[[
|
||||
CAMI.RegisterUsergroup
|
||||
Registers a usergroup with CAMI.
|
||||
|
||||
Parameters:
|
||||
usergroup
|
||||
CAMI_USERGROUP
|
||||
(see CAMI_USERGROUP structure)
|
||||
source
|
||||
any
|
||||
Identifier for your own admin mod. Can be anything.
|
||||
Use this to make sure CAMI.RegisterUsergroup function and the
|
||||
CAMI.OnUsergroupRegistered hook don't cause an infinite loop
|
||||
|
||||
|
||||
|
||||
Return value:
|
||||
CAMI_USERGROUP
|
||||
The usergroup given as argument.
|
||||
]]
|
||||
function CAMI.RegisterUsergroup(usergroup, source)
|
||||
usergroups[usergroup.Name] = usergroup
|
||||
|
||||
hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source)
|
||||
return usergroup
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.UnregisterUsergroup
|
||||
Unregisters a usergroup from CAMI. This will call a hook that will notify
|
||||
all other admin mods of the removal.
|
||||
|
||||
Call only when the usergroup is to be permanently removed.
|
||||
|
||||
Parameters:
|
||||
usergroupName
|
||||
string
|
||||
The name of the usergroup.
|
||||
source
|
||||
any
|
||||
Identifier for your own admin mod. Can be anything.
|
||||
Use this to make sure CAMI.UnregisterUsergroup function and the
|
||||
CAMI.OnUsergroupUnregistered hook don't cause an infinite loop
|
||||
|
||||
Return value:
|
||||
bool
|
||||
Whether the unregistering succeeded.
|
||||
]]
|
||||
function CAMI.UnregisterUsergroup(usergroupName, source)
|
||||
if not usergroups[usergroupName] then return false end
|
||||
|
||||
local usergroup = usergroups[usergroupName]
|
||||
usergroups[usergroupName] = nil
|
||||
|
||||
hook.Call("CAMI.OnUsergroupUnregistered", nil, usergroup, source)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetUsergroups
|
||||
Retrieves all registered usergroups.
|
||||
|
||||
Return value:
|
||||
Table of CAMI_USERGROUP, indexed by their names.
|
||||
]]
|
||||
function CAMI.GetUsergroups()
|
||||
return usergroups
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetUsergroup
|
||||
Receives information about a usergroup.
|
||||
|
||||
Return value:
|
||||
CAMI_USERGROUP
|
||||
Returns nil when the usergroup does not exist.
|
||||
]]
|
||||
function CAMI.GetUsergroup(usergroupName)
|
||||
return usergroups[usergroupName]
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.UsergroupInherits
|
||||
Returns true when usergroupName1 inherits usergroupName2.
|
||||
Note that usergroupName1 does not need to be a direct child.
|
||||
Every usergroup trivially inherits itself.
|
||||
|
||||
Parameters:
|
||||
usergroupName1
|
||||
string
|
||||
The name of the usergroup that is queried.
|
||||
usergroupName2
|
||||
string
|
||||
The name of the usergroup of which is queried whether usergroupName1
|
||||
inherits from.
|
||||
|
||||
Return value:
|
||||
bool
|
||||
Whether usergroupName1 inherits usergroupName2.
|
||||
]]
|
||||
function CAMI.UsergroupInherits(usergroupName1, usergroupName2)
|
||||
repeat
|
||||
if usergroupName1 == usergroupName2 then return true end
|
||||
|
||||
usergroupName1 = usergroups[usergroupName1] and
|
||||
usergroups[usergroupName1].Inherits or
|
||||
usergroupName1
|
||||
until not usergroups[usergroupName1] or
|
||||
usergroups[usergroupName1].Inherits == usergroupName1
|
||||
|
||||
-- One can only be sure the usergroup inherits from user if the
|
||||
-- usergroup isn't registered.
|
||||
return usergroupName1 == usergroupName2 or usergroupName2 == "user"
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.InheritanceRoot
|
||||
All usergroups must eventually inherit either user, admin or superadmin.
|
||||
Regardless of what inheritance mechism an admin may or may not have, this
|
||||
always applies.
|
||||
|
||||
This method always returns either user, admin or superadmin, based on what
|
||||
usergroups eventually inherit.
|
||||
|
||||
Parameters:
|
||||
usergroupName
|
||||
string
|
||||
The name of the usergroup of which the root of inheritance is
|
||||
requested
|
||||
|
||||
Return value:
|
||||
string
|
||||
The name of the root usergroup (either user, admin or superadmin)
|
||||
]]
|
||||
function CAMI.InheritanceRoot(usergroupName)
|
||||
if not usergroups[usergroupName] then return end
|
||||
|
||||
local inherits = usergroups[usergroupName].Inherits
|
||||
while inherits ~= usergroups[usergroupName].Inherits do
|
||||
usergroupName = usergroups[usergroupName].Inherits
|
||||
end
|
||||
|
||||
return usergroupName
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.RegisterPrivilege
|
||||
Registers a privilege with CAMI.
|
||||
Note: do NOT register all your admin mod's privileges with this function!
|
||||
This function is for third party addons to register privileges
|
||||
with admin mods, not for admin mods sharing the privileges amongst one
|
||||
another.
|
||||
|
||||
Parameters:
|
||||
privilege
|
||||
CAMI_PRIVILEGE
|
||||
See CAMI_PRIVILEGE structure.
|
||||
|
||||
Return value:
|
||||
CAMI_PRIVILEGE
|
||||
The privilege given as argument.
|
||||
]]
|
||||
function CAMI.RegisterPrivilege(privilege)
|
||||
privileges[privilege.Name] = privilege
|
||||
|
||||
hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege)
|
||||
|
||||
return privilege
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.UnregisterPrivilege
|
||||
Unregisters a privilege from CAMI. This will call a hook that will notify
|
||||
all other admin mods of the removal.
|
||||
|
||||
Call only when the privilege is to be permanently removed.
|
||||
|
||||
Parameters:
|
||||
privilegeName
|
||||
string
|
||||
The name of the privilege.
|
||||
|
||||
Return value:
|
||||
bool
|
||||
Whether the unregistering succeeded.
|
||||
]]
|
||||
function CAMI.UnregisterPrivilege(privilegeName)
|
||||
if not privileges[privilegeName] then return false end
|
||||
|
||||
local privilege = privileges[privilegeName]
|
||||
privileges[privilegeName] = nil
|
||||
|
||||
hook.Call("CAMI.OnPrivilegeUnregistered", nil, privilege)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetPrivileges
|
||||
Retrieves all registered privileges.
|
||||
|
||||
Return value:
|
||||
Table of CAMI_PRIVILEGE, indexed by their names.
|
||||
]]
|
||||
function CAMI.GetPrivileges()
|
||||
return privileges
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetPrivilege
|
||||
Receives information about a privilege.
|
||||
|
||||
Return value:
|
||||
CAMI_PRIVILEGE when the privilege exists.
|
||||
nil when the privilege does not exist.
|
||||
]]
|
||||
function CAMI.GetPrivilege(privilegeName)
|
||||
return privileges[privilegeName]
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.PlayerHasAccess
|
||||
Queries whether a certain player has the right to perform a certain action.
|
||||
Note: this function does NOT return an immediate result!
|
||||
The result is in the callback!
|
||||
|
||||
Parameters:
|
||||
actorPly
|
||||
Player
|
||||
The player of which is requested whether they have the privilege.
|
||||
privilegeName
|
||||
string
|
||||
The name of the privilege.
|
||||
callback
|
||||
function(bool, string)
|
||||
This function will be called with the answer. The bool signifies the
|
||||
yes or no answer as to whether the player is allowed. The string
|
||||
will optionally give a reason.
|
||||
targetPly
|
||||
Optional.
|
||||
The player on which the privilege is executed.
|
||||
extraInfoTbl
|
||||
Optional.
|
||||
Table containing extra information.
|
||||
Officially supported members:
|
||||
Fallback
|
||||
string
|
||||
Either of user/admin/superadmin. When no admin mod replies,
|
||||
the decision is based on the admin status of the user.
|
||||
Defaults to admin if not given.
|
||||
IgnoreImmunity
|
||||
bool
|
||||
Ignore any immunity mechanisms an admin mod might have.
|
||||
CommandArguments
|
||||
table
|
||||
Extra arguments that were given to the privilege command.
|
||||
|
||||
Return value:
|
||||
None, the answer is given in the callback function in order to allow
|
||||
for the admin mod to perform e.g. a database lookup.
|
||||
]]
|
||||
-- Default access handler
|
||||
local defaultAccessHandler = {["CAMI.PlayerHasAccess"] =
|
||||
function(_, actorPly, privilegeName, callback, _, extraInfoTbl)
|
||||
-- The server always has access in the fallback
|
||||
if not IsValid(actorPly) then return callback(true, "Fallback.") end
|
||||
|
||||
local priv = privileges[privilegeName]
|
||||
|
||||
local fallback = extraInfoTbl and (
|
||||
not extraInfoTbl.Fallback and actorPly:IsAdmin() or
|
||||
extraInfoTbl.Fallback == "user" and true or
|
||||
extraInfoTbl.Fallback == "admin" and actorPly:IsAdmin() or
|
||||
extraInfoTbl.Fallback == "superadmin" and actorPly:IsSuperAdmin())
|
||||
|
||||
|
||||
if not priv then return callback(fallback, "Fallback.") end
|
||||
|
||||
callback(
|
||||
priv.MinAccess == "user" or
|
||||
priv.MinAccess == "admin" and actorPly:IsAdmin() or
|
||||
priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin()
|
||||
, "Fallback.")
|
||||
end,
|
||||
["CAMI.SteamIDHasAccess"] =
|
||||
function(_, _, _, callback)
|
||||
callback(false, "No information available.")
|
||||
end
|
||||
}
|
||||
function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly,
|
||||
extraInfoTbl)
|
||||
hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly,
|
||||
privilegeName, callback, targetPly, extraInfoTbl)
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetPlayersWithAccess
|
||||
Finds the list of currently joined players who have the right to perform a
|
||||
certain action.
|
||||
NOTE: this function will NOT return an immediate result!
|
||||
The result is in the callback!
|
||||
|
||||
Parameters:
|
||||
privilegeName
|
||||
string
|
||||
The name of the privilege.
|
||||
callback
|
||||
function(players)
|
||||
This function will be called with the list of players with access.
|
||||
targetPly
|
||||
Optional.
|
||||
The player on which the privilege is executed.
|
||||
extraInfoTbl
|
||||
Optional.
|
||||
Table containing extra information.
|
||||
Officially supported members:
|
||||
Fallback
|
||||
string
|
||||
Either of user/admin/superadmin. When no admin mod replies,
|
||||
the decision is based on the admin status of the user.
|
||||
Defaults to admin if not given.
|
||||
IgnoreImmunity
|
||||
bool
|
||||
Ignore any immunity mechanisms an admin mod might have.
|
||||
CommandArguments
|
||||
table
|
||||
Extra arguments that were given to the privilege command.
|
||||
]]
|
||||
function CAMI.GetPlayersWithAccess(privilegeName, callback, targetPly,
|
||||
extraInfoTbl)
|
||||
local allowedPlys = {}
|
||||
local allPlys = player.GetAll()
|
||||
local countdown = #allPlys
|
||||
|
||||
local function onResult(ply, hasAccess, _)
|
||||
countdown = countdown - 1
|
||||
|
||||
if hasAccess then table.insert(allowedPlys, ply) end
|
||||
if countdown == 0 then callback(allowedPlys) end
|
||||
end
|
||||
|
||||
for _, ply in pairs(allPlys) do
|
||||
CAMI.PlayerHasAccess(ply, privilegeName,
|
||||
function(...) onResult(ply, ...) end,
|
||||
targetPly, extraInfoTbl)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.SteamIDHasAccess
|
||||
Queries whether a player with a steam ID has the right to perform a certain
|
||||
action.
|
||||
Note: the player does not need to be in the server for this to
|
||||
work.
|
||||
|
||||
Note: this function does NOT return an immediate result!
|
||||
The result is in the callback!
|
||||
|
||||
Parameters:
|
||||
actorSteam
|
||||
Player
|
||||
The SteamID of the player of which is requested whether they have
|
||||
the privilege.
|
||||
privilegeName
|
||||
string
|
||||
The name of the privilege.
|
||||
callback
|
||||
function(bool, string)
|
||||
This function will be called with the answer. The bool signifies the
|
||||
yes or no answer as to whether the player is allowed. The string
|
||||
will optionally give a reason.
|
||||
targetSteam
|
||||
Optional.
|
||||
The SteamID of the player on which the privilege is executed.
|
||||
extraInfoTbl
|
||||
Optional.
|
||||
Table containing extra information.
|
||||
Officially supported members:
|
||||
IgnoreImmunity
|
||||
bool
|
||||
Ignore any immunity mechanisms an admin mod might have.
|
||||
CommandArguments
|
||||
table
|
||||
Extra arguments that were given to the privilege command.
|
||||
|
||||
Return value:
|
||||
None, the answer is given in the callback function in order to allow
|
||||
for the admin mod to perform e.g. a database lookup.
|
||||
]]
|
||||
function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback,
|
||||
targetSteam, extraInfoTbl)
|
||||
hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam,
|
||||
privilegeName, callback, targetSteam, extraInfoTbl)
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.SignalUserGroupChanged
|
||||
Signify that your admin mod has changed the usergroup of a player. This
|
||||
function communicates to other admin mods what it thinks the usergroup
|
||||
of a player should be.
|
||||
|
||||
Listen to the hook to receive the usergroup changes of other admin mods.
|
||||
|
||||
Parameters:
|
||||
ply
|
||||
Player
|
||||
The player for which the usergroup is changed
|
||||
old
|
||||
string
|
||||
The previous usergroup of the player.
|
||||
new
|
||||
string
|
||||
The new usergroup of the player.
|
||||
source
|
||||
any
|
||||
Identifier for your own admin mod. Can be anything.
|
||||
]]
|
||||
function CAMI.SignalUserGroupChanged(ply, old, new, source)
|
||||
hook.Call("CAMI.PlayerUsergroupChanged", nil, ply, old, new, source)
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.SignalSteamIDUserGroupChanged
|
||||
Signify that your admin mod has changed the usergroup of a disconnected
|
||||
player. This communicates to other admin mods what it thinks the usergroup
|
||||
of a player should be.
|
||||
|
||||
Listen to the hook to receive the usergroup changes of other admin mods.
|
||||
|
||||
Parameters:
|
||||
ply
|
||||
string
|
||||
The steam ID of the player for which the usergroup is changed
|
||||
old
|
||||
string
|
||||
The previous usergroup of the player.
|
||||
new
|
||||
string
|
||||
The new usergroup of the player.
|
||||
source
|
||||
any
|
||||
Identifier for your own admin mod. Can be anything.
|
||||
]]
|
||||
function CAMI.SignalSteamIDUserGroupChanged(steamId, old, new, source)
|
||||
hook.Call("CAMI.SteamIDUsergroupChanged", nil, steamId, old, new, source)
|
||||
end
|
||||
@@ -1,267 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local timeMeasurementFunc = SysTime
|
||||
|
||||
-- Helper function
|
||||
-- Get all local variables
|
||||
local NIL = {}
|
||||
setmetatable(NIL, {__tostring = function() return "nil" end})
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Call counts:
|
||||
|
||||
registers how often function have been called
|
||||
---------------------------------------------------------------------------]]
|
||||
local callcounts = {}
|
||||
|
||||
|
||||
-- Gets the call counts
|
||||
FProfiler.Internal.getCallCounts = function() return callcounts end
|
||||
|
||||
|
||||
-- Resets the call counts
|
||||
function FProfiler.Internal.resetCallCounts()
|
||||
callcounts = {}
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Inclusive function times
|
||||
|
||||
Keeps track of how long functions take in total
|
||||
i.e. average time between the start and return of a function * times called
|
||||
|
||||
This includes the time that any function called by this function takes
|
||||
(that's what the "inclusive" refers to).
|
||||
Note: recursive calls are not counted double
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
local inclusiveTimes = {}
|
||||
|
||||
-- Gets the inclusive times
|
||||
FProfiler.Internal.getInclusiveTimes = function() return inclusiveTimes end
|
||||
|
||||
-- Resets the inclusive times
|
||||
function FProfiler.Internal.resetInclusiveTimes()
|
||||
inclusiveTimes = {}
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Top n most expensive single function calls
|
||||
Keeps track of the functions that took the longest time to run
|
||||
Note: functions can appear in this list at most once
|
||||
---------------------------------------------------------------------------]]
|
||||
local mostExpensiveSingleCalls = {}
|
||||
|
||||
-- Gets most expensive single calls
|
||||
FProfiler.Internal.getMostExpensiveSingleCalls = function() return mostExpensiveSingleCalls end
|
||||
|
||||
-- Dictionary to make sure the same function doesn't appear multiple times
|
||||
-- in the top n
|
||||
local mostExpensiveSingleDict = {}
|
||||
|
||||
function FProfiler.Internal.resetMostExpensiveSingleCalls()
|
||||
for i = 1, 50 do mostExpensiveSingleCalls[i] = {runtime = 0} end
|
||||
mostExpensiveSingleDict = {}
|
||||
end
|
||||
|
||||
-- Initial empty list
|
||||
FProfiler.Internal.resetMostExpensiveSingleCalls()
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Function information
|
||||
Using debug.getinfo on a function object won't give you any function names
|
||||
that's because functions can have multiple names.
|
||||
Luckily, when the functions are called, debug.getinfo(level) gives the
|
||||
function name and scope
|
||||
---------------------------------------------------------------------------]]
|
||||
local functionNames = {}
|
||||
|
||||
FProfiler.Internal.getFunctionNames = function() return functionNames end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Recursion depth
|
||||
|
||||
Used internally to make sure recursive functions' times aren't counted
|
||||
multiple times
|
||||
---------------------------------------------------------------------------]]
|
||||
local recursiveCount = {}
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Function start times
|
||||
|
||||
Used internally to keep track of when functions were called
|
||||
---------------------------------------------------------------------------]]
|
||||
local startTimes = {}
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Lua code event handlers
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
-- The recursion depth of the function that is in focus.
|
||||
-- Only applies when profiling a specific function (i.e. laying focus upon)
|
||||
local focusDepth = 0
|
||||
|
||||
-- Called when a function in the code is called
|
||||
local function registerFunctionCall(funcInfo)
|
||||
local func = funcInfo.func
|
||||
|
||||
-- Update call counts
|
||||
callcounts[func] = (callcounts[func] or 0) + 1
|
||||
|
||||
-- Increase recursion depth for this function
|
||||
recursiveCount[func] = (recursiveCount[func] or 0) + 1
|
||||
|
||||
-- Store function info
|
||||
local funcname = funcInfo.name or ""
|
||||
functionNames[func] = functionNames[func] or {}
|
||||
functionNames[func][funcname] = functionNames[func][funcname] or
|
||||
{ namewhat = funcInfo.namewhat,
|
||||
nparams = funcInfo.nparams
|
||||
}
|
||||
|
||||
local time = timeMeasurementFunc()
|
||||
|
||||
-- Update inclusive function times,
|
||||
-- only when we're on the first recursive call
|
||||
if recursiveCount[func] == 1 then
|
||||
startTimes[func] = time
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Called when a function returns
|
||||
local function registerReturn(funcInfo)
|
||||
local time = timeMeasurementFunc()
|
||||
local func = funcInfo.func
|
||||
local runtime = time - startTimes[func]
|
||||
|
||||
-- Update inclusive function time
|
||||
-- Only update on the topmost call, to prevent recursive
|
||||
-- calls for being counted multiple times.
|
||||
if recursiveCount[func] == 1 then
|
||||
inclusiveTimes[func] = (inclusiveTimes[func] or 0) + runtime
|
||||
end
|
||||
|
||||
-- Maintain recursion depth
|
||||
recursiveCount[func] = recursiveCount[func] - 1
|
||||
|
||||
-- Update top n list
|
||||
-- This path will be taken most often: the function isn't special
|
||||
-- Also only counts the top recursion
|
||||
if runtime <= mostExpensiveSingleCalls[50].runtime or recursiveCount[func] > 1 then return end
|
||||
|
||||
-- If the function already appears in the top 10, replace it or discard the result
|
||||
if mostExpensiveSingleDict[func] then
|
||||
local i = mostExpensiveSingleDict[func]
|
||||
|
||||
-- Discard this info
|
||||
if runtime < mostExpensiveSingleCalls[i].runtime then return end
|
||||
|
||||
-- Update the entry
|
||||
mostExpensiveSingleCalls[i].runtime = runtime
|
||||
mostExpensiveSingleCalls[i].info = funcInfo
|
||||
mostExpensiveSingleCalls[i].func = func
|
||||
|
||||
-- Move the updated entry up the top 10 list if applicable
|
||||
while i > 1 and runtime > mostExpensiveSingleCalls[i - 1].runtime do
|
||||
mostExpensiveSingleDict[mostExpensiveSingleCalls[i - 1].func] = i
|
||||
mostExpensiveSingleCalls[i - 1], mostExpensiveSingleCalls[i] = mostExpensiveSingleCalls[i], mostExpensiveSingleCalls[i - 1]
|
||||
i = i - 1
|
||||
end
|
||||
|
||||
mostExpensiveSingleDict[func] = i
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Knowing that the function belongs in the top n, find its position
|
||||
local i = 50
|
||||
while i >= 1 and runtime > mostExpensiveSingleCalls[i].runtime do
|
||||
-- Update the dictionary
|
||||
-- All functions faster than the current one move down the list
|
||||
if not mostExpensiveSingleCalls[i].func then i = i - 1 continue end
|
||||
mostExpensiveSingleDict[mostExpensiveSingleCalls[i].func] = i + 1
|
||||
|
||||
i = i - 1
|
||||
end
|
||||
|
||||
-- Insert the expensive call in the top n
|
||||
mostExpensiveSingleDict[func] = i + 1
|
||||
table.insert(mostExpensiveSingleCalls, i + 1,
|
||||
{
|
||||
func = func,
|
||||
runtime = runtime,
|
||||
info = funcInfo,
|
||||
})
|
||||
|
||||
|
||||
-- What was previously the 50th most expensive function
|
||||
-- is now kicked out of the top 10
|
||||
if mostExpensiveSingleCalls[51].func then
|
||||
mostExpensiveSingleDict[mostExpensiveSingleCalls[51].func] = nil
|
||||
end
|
||||
mostExpensiveSingleCalls[51] = nil
|
||||
end
|
||||
|
||||
|
||||
-- Called on any Lua event
|
||||
local function onLuaEvent(event, focus)
|
||||
local info = debug.getinfo(3)
|
||||
local func = info.func
|
||||
|
||||
if event == "call" or event == "tail call" then
|
||||
-- Only track the focussed function and the functions
|
||||
-- called by the focussed function
|
||||
if focus == func then focusDepth = focusDepth + 1 end
|
||||
if focus and focusDepth == 0 then return end
|
||||
|
||||
registerFunctionCall(info)
|
||||
else
|
||||
-- Functions that return right after the call to FProfiler.Internal.start()
|
||||
-- are not to be counted
|
||||
if not recursiveCount[func] or recursiveCount[func] == 0 then return end
|
||||
|
||||
if focus == func then focusDepth = focusDepth - 1 end
|
||||
if focus and focusDepth == 0 then return end
|
||||
|
||||
registerReturn(info)
|
||||
end
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Profiling control
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
-- Start profiling
|
||||
-- focus: only measure data of everything that happens within a certain function
|
||||
function FProfiler.Internal.start(focus)
|
||||
-- Empty start times, so unfinished functions aren't
|
||||
-- registered as returns on a second profiling session
|
||||
-- local time = SysTime()
|
||||
-- for k,v in pairs(startTimes) do startTimes[k] = time end
|
||||
table.Empty(startTimes)
|
||||
table.Empty(recursiveCount)
|
||||
|
||||
debug.sethook(function(event) onLuaEvent(event, focus) end, "cr")
|
||||
end
|
||||
|
||||
|
||||
-- Stop profiling
|
||||
function FProfiler.Internal.stop()
|
||||
debug.sethook()
|
||||
end
|
||||
|
||||
-- Reset all profiling data
|
||||
function FProfiler.Internal.reset()
|
||||
FProfiler.Internal.resetCallCounts()
|
||||
FProfiler.Internal.resetInclusiveTimes()
|
||||
FProfiler.Internal.resetMostExpensiveSingleCalls()
|
||||
end
|
||||
@@ -1,594 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
-- Based on MDave's thing
|
||||
-- https://gist.github.com/mentlerd/d56ad9e6361f4b86af84
|
||||
if SERVER then AddCSLuaFile() end
|
||||
|
||||
local type_weight = {
|
||||
[TYPE_FUNCTION] = 1,
|
||||
[TYPE_TABLE] = 2,
|
||||
}
|
||||
|
||||
local type_colors = {
|
||||
[TYPE_BOOL] = Color(175, 130, 255),
|
||||
[TYPE_NUMBER] = Color(175, 130, 255),
|
||||
[TYPE_STRING] = Color(230, 220, 115),
|
||||
[TYPE_FUNCTION] = Color(100, 220, 240)
|
||||
}
|
||||
|
||||
local color_neutral = Color(220, 220, 220)
|
||||
local color_name = Color(260, 150, 30)
|
||||
|
||||
local color_reference = Color(150, 230, 50)
|
||||
local color_comment = Color( 30, 210, 30)
|
||||
|
||||
-- 'nil' value
|
||||
local NIL = {}
|
||||
|
||||
-- Localise for faster access
|
||||
local pcall = pcall
|
||||
|
||||
local string_len = string.len
|
||||
local string_sub = string.sub
|
||||
local string_find = string.find
|
||||
|
||||
local table_concat = table.concat
|
||||
local table_insert = table.insert
|
||||
local table_sort = table.sort
|
||||
|
||||
|
||||
-- Stream interface
|
||||
local gMsgF -- Print fragment
|
||||
local gMsgN -- Print newline
|
||||
local gMsgC -- Set print color
|
||||
|
||||
local PrintLocals, gBegin, gFinish, PrintTableGrep
|
||||
|
||||
do
|
||||
local grep_color = Color(235, 70, 70)
|
||||
|
||||
-- Grep parameters (static between gBegin/gEnd)
|
||||
local grep
|
||||
local grep_raw
|
||||
|
||||
local grep_proximity
|
||||
|
||||
|
||||
-- Current line parameters
|
||||
local buffer
|
||||
local colors
|
||||
local markers
|
||||
|
||||
local baseColor
|
||||
local currColor
|
||||
|
||||
local length
|
||||
|
||||
-- History
|
||||
local history
|
||||
local remain
|
||||
|
||||
|
||||
-- Actual printing
|
||||
local function gCheckMatch( buffer )
|
||||
local raw = table_concat(buffer)
|
||||
|
||||
return raw, string_find(raw, grep, 0, grep_raw)
|
||||
end
|
||||
|
||||
local function gFlushEx( raw, markers, colors, baseColor )
|
||||
|
||||
-- Print entire buffer
|
||||
local len = string_len(raw)
|
||||
|
||||
-- Keep track of the current line properties
|
||||
local index = 1
|
||||
local marker = 1
|
||||
|
||||
local currColor = baseColor
|
||||
|
||||
-- Method to print to a preset area
|
||||
local function printToIndex( limit, color )
|
||||
local mark = markers and markers[marker]
|
||||
|
||||
-- Print all marker areas until we would overshoot
|
||||
while mark and mark < limit do
|
||||
|
||||
-- Catch up to the marker
|
||||
MsgC(color or currColor or color_neutral, string_sub(raw, index, mark))
|
||||
index = mark +1
|
||||
|
||||
-- Set new color
|
||||
currColor = colors[marker]
|
||||
|
||||
-- Select next marker
|
||||
marker = marker +1
|
||||
mark = markers[marker]
|
||||
|
||||
end
|
||||
|
||||
-- Print the remaining between the last marker and the limit
|
||||
MsgC(color or currColor or color_neutral, string_sub(raw, index, limit))
|
||||
index = limit +1
|
||||
end
|
||||
|
||||
-- Grep!
|
||||
local match, last = 1
|
||||
local from, to = string_find(raw, grep, 0, grep_raw)
|
||||
|
||||
while from do
|
||||
printToIndex(from -1)
|
||||
printToIndex(to, grep_color)
|
||||
|
||||
last = to +1
|
||||
from, to = string_find(raw, grep, last, grep_raw)
|
||||
end
|
||||
|
||||
printToIndex(len)
|
||||
MsgN()
|
||||
end
|
||||
|
||||
|
||||
local function gCommit()
|
||||
if grep_proximity then
|
||||
-- Check if the line has at least one match
|
||||
local raw, match = gCheckMatch(buffer)
|
||||
|
||||
if match then
|
||||
|
||||
-- Divide matches
|
||||
if history[grep_proximity] then
|
||||
MsgN("...")
|
||||
end
|
||||
|
||||
-- Flush history
|
||||
if grep_proximity ~= 0 then
|
||||
local len = #history
|
||||
|
||||
for index = len -1, 1, -1 do
|
||||
local entry = history[index]
|
||||
history[index] = nil
|
||||
|
||||
gFlushEx( entry[1], entry[2], entry[3], entry[4] )
|
||||
end
|
||||
|
||||
history[len] = nil
|
||||
end
|
||||
|
||||
-- Flush line, allow next X lines to get printed
|
||||
gFlushEx( raw, markers, colors, baseColor )
|
||||
remain = grep_proximity -1
|
||||
|
||||
history[grep_proximity +1] = nil
|
||||
elseif remain > 0 then
|
||||
-- Flush immediately
|
||||
gFlushEx( raw, markers, colors, baseColor )
|
||||
remain = remain -1
|
||||
else
|
||||
-- Store in history
|
||||
table_insert(history, 1, {raw, markers, colors, baseColor})
|
||||
history[grep_proximity +1] = nil
|
||||
end
|
||||
else
|
||||
-- Flush anyway
|
||||
gFlushEx( table_concat(buffer), markers, colors, baseColor )
|
||||
end
|
||||
|
||||
-- Reset state
|
||||
length = 0
|
||||
buffer = {}
|
||||
|
||||
markers = nil
|
||||
colors = nil
|
||||
|
||||
baseColor = nil
|
||||
currColor = nil
|
||||
end
|
||||
|
||||
-- State machine
|
||||
function gBegin( new, prox )
|
||||
grep = isstring(new) and new
|
||||
|
||||
if grep then
|
||||
grep_raw = not pcall(string_find, ' ', grep)
|
||||
grep_proximity = isnumber(prox) and prox
|
||||
|
||||
-- Reset everything
|
||||
buffer = {}
|
||||
history = {}
|
||||
end
|
||||
|
||||
length = 0
|
||||
remain = 0
|
||||
|
||||
baseColor = nil
|
||||
currColor = nil
|
||||
end
|
||||
|
||||
function gFinish()
|
||||
if grep_proximity and history and history[1] then
|
||||
MsgN("...")
|
||||
end
|
||||
|
||||
-- Free memory
|
||||
buffer = nil
|
||||
markers = nil
|
||||
colors = nil
|
||||
|
||||
history = nil
|
||||
end
|
||||
|
||||
|
||||
function gMsgC( color )
|
||||
if grep then
|
||||
|
||||
-- Try to save some memory by not immediately allocating colors
|
||||
if length == 0 then
|
||||
baseColor = color
|
||||
return
|
||||
end
|
||||
|
||||
-- Record color change
|
||||
if color ~= currColor then
|
||||
if not markers then
|
||||
markers = {}
|
||||
colors = {}
|
||||
end
|
||||
|
||||
-- Record color change
|
||||
table_insert(markers, length)
|
||||
table_insert(colors, color)
|
||||
end
|
||||
end
|
||||
|
||||
currColor = color
|
||||
end
|
||||
|
||||
function gMsgF( str )
|
||||
if grep then
|
||||
|
||||
-- Split multiline fragments to separate ones
|
||||
local fragColor = currColor or baseColor
|
||||
|
||||
local last = 1
|
||||
local from, to = string_find(str, '\n')
|
||||
|
||||
while from do
|
||||
local frag = string_sub(str, last, from -1)
|
||||
local len = from - last
|
||||
|
||||
-- Merge fragment to the line
|
||||
length = length + len
|
||||
table_insert(buffer, frag)
|
||||
|
||||
-- Print finished line
|
||||
gCommit()
|
||||
|
||||
-- Assign base color as previous fragColor
|
||||
baseColor = fragColor
|
||||
|
||||
-- Look for more
|
||||
last = to +1
|
||||
from, to = string_find(str, '\n', last)
|
||||
end
|
||||
|
||||
-- Push last fragment
|
||||
local frag = string_sub(str, last)
|
||||
local len = string_len(str) - last +1
|
||||
|
||||
length = length + len
|
||||
table_insert(buffer, frag)
|
||||
else
|
||||
-- Push immediately
|
||||
MsgC(currColor or baseColor or color_neutral, str)
|
||||
end
|
||||
end
|
||||
|
||||
function gMsgN()
|
||||
-- Print everything in the buffer
|
||||
if grep then
|
||||
gCommit()
|
||||
else
|
||||
MsgN()
|
||||
end
|
||||
|
||||
baseColor = nil
|
||||
currColor = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function InternalPrintValue( value )
|
||||
|
||||
-- 'nil' values can also be printed
|
||||
if value == NIL then
|
||||
gMsgC(color_comment)
|
||||
gMsgF("nil")
|
||||
return
|
||||
end
|
||||
|
||||
local color = type_colors[ TypeID(value) ]
|
||||
|
||||
-- For strings, place quotes
|
||||
if isstring(value) then
|
||||
if string_len(value) <= 1 then
|
||||
value = string.format([['%s']], value)
|
||||
else
|
||||
value = string.format([["%s"]], value)
|
||||
end
|
||||
|
||||
gMsgC(color)
|
||||
gMsgF(value)
|
||||
return
|
||||
end
|
||||
|
||||
-- Workaround for userdata not using MetaName
|
||||
if string_sub(tostring(value), 0, 8) == "userdata" then
|
||||
local meta = getmetatable(value)
|
||||
|
||||
if meta and meta.MetaName then
|
||||
value = string.format("%s: %p", meta.MetaName, value)
|
||||
end
|
||||
end
|
||||
|
||||
-- General print
|
||||
gMsgC(color)
|
||||
gMsgF(tostring(value))
|
||||
|
||||
-- For functions append source info
|
||||
if isfunction(value) then
|
||||
local info = debug.getinfo(value, 'S')
|
||||
local aux
|
||||
|
||||
if info.what == 'C' then
|
||||
aux = "\t-- [C]: -1"
|
||||
else
|
||||
if info.linedefined ~= info.lastlinedefined then
|
||||
aux = string.format("\t-- [%s]: %i-%i", info.short_src, info.linedefined, info.lastlinedefined)
|
||||
else
|
||||
aux = string.format("\t-- [%s]: %i", info.short_src, info.linedefined)
|
||||
end
|
||||
end
|
||||
|
||||
gMsgC(color_comment)
|
||||
gMsgF(aux)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Associated to object keys
|
||||
local objID
|
||||
|
||||
local function isprimitive( value )
|
||||
local id = TypeID(value)
|
||||
|
||||
return id <= TYPE_FUNCTION and id ~= TYPE_TABLE
|
||||
end
|
||||
|
||||
local function InternalPrintTable( table, path, prefix, names, todo )
|
||||
|
||||
-- Collect keys and some info about them
|
||||
local keyList = {}
|
||||
local keyStr = {}
|
||||
|
||||
local keyCount = 0
|
||||
|
||||
for key, value in pairs( table ) do
|
||||
-- Add to key list for later sorting
|
||||
table_insert(keyList, key)
|
||||
|
||||
-- Describe key as string
|
||||
if isprimitive(key) then
|
||||
keyStr[key] = tostring(key)
|
||||
else
|
||||
-- Lookup already known name
|
||||
local name = names[key]
|
||||
|
||||
-- Assign a new unique identifier
|
||||
if not name then
|
||||
objID = objID +1
|
||||
name = string.format("%s: obj #%i", tostring(key), objID)
|
||||
|
||||
names[key] = name
|
||||
todo[key] = true
|
||||
end
|
||||
|
||||
-- Substitute object with name
|
||||
keyStr[key] = name
|
||||
end
|
||||
|
||||
keyCount = keyCount +1
|
||||
end
|
||||
|
||||
|
||||
-- Exit early for empty tables
|
||||
if keyCount == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- Determine max key length
|
||||
local keyLen = 4
|
||||
|
||||
for key, str in pairs(keyStr) do
|
||||
keyLen = math.max(keyLen, string.len(str))
|
||||
end
|
||||
|
||||
-- Sort table keys
|
||||
if keyCount > 1 then
|
||||
table_sort( keyList, function( A, B )
|
||||
|
||||
-- Sort numbers numerically correct
|
||||
if isnumber(A) and isnumber(B) then
|
||||
return A < B
|
||||
end
|
||||
|
||||
-- Weight types
|
||||
local wA = type_weight[ TypeID( table[A] ) ] or 0
|
||||
local wB = type_weight[ TypeID( table[B] ) ] or 0
|
||||
|
||||
if wA ~= wB then
|
||||
return wA < wB
|
||||
end
|
||||
|
||||
-- Order by string representation
|
||||
return keyStr[A] < keyStr[B]
|
||||
|
||||
end )
|
||||
end
|
||||
|
||||
-- Determine the next level ident
|
||||
local new_prefix = string.format( "%s║%s", prefix, string.rep(' ', keyLen) )
|
||||
|
||||
-- Mark object as done
|
||||
todo[table] = nil
|
||||
|
||||
-- Start describing table
|
||||
for index, key in ipairs(keyList) do
|
||||
local value = table[key]
|
||||
|
||||
-- Assign names to already described keys/values
|
||||
local kName = names[key]
|
||||
local vName = names[value]
|
||||
|
||||
-- Decide to either fully describe, or print the value
|
||||
local describe = not isprimitive(value) and ( not vName or todo[value] )
|
||||
|
||||
-- Ident
|
||||
gMsgF(prefix)
|
||||
|
||||
-- Fancy table guides
|
||||
local moreLines = (index ~= keyCount) or describe
|
||||
|
||||
if index == 1 then
|
||||
gMsgF(moreLines and '╦ ' or '═ ')
|
||||
else
|
||||
gMsgF(moreLines and '╠ ' or '╚ ')
|
||||
end
|
||||
|
||||
-- Print key
|
||||
local sKey = kName or keyStr[key]
|
||||
|
||||
gMsgC(kName and color_reference or color_name)
|
||||
gMsgF(sKey)
|
||||
|
||||
-- Describe non primitives
|
||||
describe = istable(value) and ( not names[value] or todo[value] ) and value ~= NIL
|
||||
|
||||
-- Print key postfix
|
||||
local padding = keyLen - string.len(sKey)
|
||||
local postfix = string.format(describe and ":%s" or "%s = ", string.rep(' ', padding))
|
||||
|
||||
gMsgC(color_neutral)
|
||||
gMsgF(postfix)
|
||||
|
||||
-- Print the value
|
||||
if describe then
|
||||
gMsgN()
|
||||
|
||||
-- Expand access path
|
||||
local new_path = sKey
|
||||
|
||||
if isnumber(key) or kName then
|
||||
new_path = string.format("%s[%s]", path or '', key)
|
||||
elseif path then
|
||||
new_path = string.format("%s.%s", path, new_path)
|
||||
end
|
||||
|
||||
-- Name the object to mark it done
|
||||
names[value] = names[value] or new_path
|
||||
|
||||
-- Describe
|
||||
InternalPrintTable(value, new_path, new_prefix, names, todo)
|
||||
else
|
||||
-- Print the value (or the reference name)
|
||||
if vName and not todo[value] then
|
||||
gMsgC(color_reference)
|
||||
gMsgF(string.format("ref: %s",vName))
|
||||
else
|
||||
InternalPrintValue(value)
|
||||
end
|
||||
|
||||
gMsgN()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function PrintTableGrep( table, grep, proximity )
|
||||
local base = {
|
||||
[_G] = "_G",
|
||||
[table] = "root"
|
||||
}
|
||||
|
||||
gBegin(grep, proximity)
|
||||
objID = 0
|
||||
InternalPrintTable(table, nil, "", base, {})
|
||||
gFinish()
|
||||
end
|
||||
|
||||
function PrintLocals( level )
|
||||
local level = level or 2
|
||||
local hash = {}
|
||||
|
||||
for index = 1, 255 do
|
||||
local name, value = debug.getlocal(2, index)
|
||||
|
||||
if not name then
|
||||
break
|
||||
end
|
||||
|
||||
if value == nil then
|
||||
value = NIL
|
||||
end
|
||||
|
||||
hash[name] = value
|
||||
end
|
||||
|
||||
PrintTableGrep( hash )
|
||||
end
|
||||
|
||||
function show(...)
|
||||
local n = select('#', ...)
|
||||
local tbl = {...}
|
||||
|
||||
for i = 1, n do
|
||||
if istable(tbl[i]) then MsgN(tostring(tbl[i])) PrintTableGrep(tbl[i])
|
||||
else InternalPrintValue(tbl[i]) MsgN() end
|
||||
end
|
||||
end
|
||||
|
||||
-- Hacky way of creating a pretty string from the above code
|
||||
-- because I don't feel like refactoring the entire thing
|
||||
local strResult
|
||||
local toStringMsgF = function(txt)
|
||||
table.insert(strResult, txt)
|
||||
end
|
||||
|
||||
local toStringMsgN = function()
|
||||
table.insert(strResult, "\n")
|
||||
end
|
||||
|
||||
local toStringMsgC = function(_, txt)
|
||||
table.insert(strResult, txt)
|
||||
end
|
||||
|
||||
function showStr(...)
|
||||
local oldF, oldN, oldMsgC, oldMsgN = gMsgF, gMsgN, MsgC, MsgN
|
||||
gMsgF, gMsgN, MsgC, MsgN = toStringMsgF, toStringMsgN, toStringMsgC, toStringMsgN
|
||||
|
||||
strResult = {}
|
||||
show(...)
|
||||
|
||||
gMsgF, gMsgN, MsgC, MsgN = oldF, oldN, oldMsgC, oldMsgN
|
||||
|
||||
return table.concat(strResult, "")
|
||||
end
|
||||
@@ -1,114 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local function getData()
|
||||
local callCounts = FProfiler.Internal.getCallCounts()
|
||||
local inclusiveTimes = FProfiler.Internal.getInclusiveTimes()
|
||||
local funcNames = FProfiler.Internal.getFunctionNames()
|
||||
|
||||
local data = {}
|
||||
for func, called in pairs(callCounts) do
|
||||
local row = {}
|
||||
row.func = func
|
||||
row.info = debug.getinfo(func, "nfS")
|
||||
row.total_called = called
|
||||
row.total_time = inclusiveTimes[func] or 0
|
||||
row.average_time = row.total_time / row.total_called
|
||||
|
||||
row.name, row.namewhat = nil, nil
|
||||
|
||||
row.names = {}
|
||||
for name, namedata in pairs(funcNames[func]) do
|
||||
table.insert(row.names, {name = name, namewhat = namedata.namewhat, nparams = namedata.nparams})
|
||||
end
|
||||
|
||||
table.insert(data, row)
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
local function cull(data, count)
|
||||
if not count then return data end
|
||||
|
||||
for i = count + 1, #data do
|
||||
data[i] = nil
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The functions that are called most often
|
||||
Their implementations are O(n lg n),
|
||||
which is probably suboptimal but not worth my time optimising.
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.Internal.mostOftenCalled(count)
|
||||
local sorted = getData()
|
||||
|
||||
table.SortByMember(sorted, "total_called")
|
||||
|
||||
return cull(sorted, count)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The functions that take the longest time in total
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.Internal.mostTimeInclusive(count)
|
||||
local sorted = getData()
|
||||
|
||||
table.SortByMember(sorted, "total_time")
|
||||
|
||||
return cull(sorted, count)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The functions that take the longest average time
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.Internal.mostTimeInclusiveAverage(count)
|
||||
local sorted = getData()
|
||||
|
||||
table.SortByMember(sorted, "average_time")
|
||||
|
||||
return cull(sorted, count)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Get the top <count> of most often called, time inclusive and average
|
||||
NOTE: This will almost definitely return more than <count> results.
|
||||
Up to three times <count> is possible.
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.Internal.getAggregatedResults(count)
|
||||
count = count or 100
|
||||
|
||||
local dict = {}
|
||||
local mostTime = FProfiler.Internal.mostTimeInclusive(count)
|
||||
for i = 1, #mostTime do dict[mostTime[i].func] = true end
|
||||
|
||||
local mostAvg = FProfiler.Internal.mostTimeInclusiveAverage(count)
|
||||
|
||||
for i = 1, #mostAvg do
|
||||
if dict[mostAvg[i].func] then continue end
|
||||
dict[mostAvg[i].func] = true
|
||||
table.insert(mostTime, mostAvg[i])
|
||||
end
|
||||
|
||||
local mostCalled = FProfiler.Internal.mostOftenCalled(count)
|
||||
|
||||
for i = 1, #mostCalled do
|
||||
if dict[mostCalled[i].func] then continue end
|
||||
dict[mostCalled[i].func] = true
|
||||
table.insert(mostTime, mostCalled[i])
|
||||
end
|
||||
|
||||
table.SortByMember(mostTime, "total_time")
|
||||
|
||||
return mostTime
|
||||
end
|
||||
@@ -1,110 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local get, update, onUpdate = FProfiler.UI.getModelValue, FProfiler.UI.updateModel, FProfiler.UI.onModelUpdate
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
(Re)start clientside profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
local function restartProfiling()
|
||||
if get({"client", "shouldReset"}) then
|
||||
FProfiler.Internal.reset()
|
||||
update({"client", "recordTime"}, 0)
|
||||
end
|
||||
|
||||
local focus = get({"client", "focusObj"})
|
||||
|
||||
update({"client", "sessionStart"}, CurTime())
|
||||
update({"client", "sessionStartSysTime"}, SysTime())
|
||||
FProfiler.Internal.start(focus)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Stop profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
local function stopProfiling()
|
||||
FProfiler.Internal.stop()
|
||||
|
||||
local newTime = get({"client", "recordTime"}) + SysTime() - (get({"client", "sessionStartSysTime"}) or 0)
|
||||
|
||||
-- Get the aggregated data
|
||||
local mostTime = FProfiler.Internal.getAggregatedResults(100)
|
||||
|
||||
update({"client", "bottlenecks"}, mostTime)
|
||||
update({"client", "topLagSpikes"}, FProfiler.Internal.getMostExpensiveSingleCalls())
|
||||
|
||||
update({"client", "recordTime"}, newTime)
|
||||
update({"client", "sessionStart"}, nil)
|
||||
update({"client", "sessionStartSysTime"}, nil)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Start/stop recording when the recording status is changed
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate({"client", "status"}, function(new, old)
|
||||
if new == old then return end
|
||||
(new == "Started" and restartProfiling or stopProfiling)()
|
||||
end)
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Update the current selected focus object when data is entered
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate({"client", "focusStr"}, function(new)
|
||||
update({"client", "focusObj"}, FProfiler.funcNameToObj(new))
|
||||
end)
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Update info when a different line is selected
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate({"client", "currentSelected"}, function(new)
|
||||
if not new or not new.info or not new.info.linedefined or not new.info.lastlinedefined or not new.info.short_src then return end
|
||||
|
||||
update({"client", "sourceText"}, FProfiler.readSource(new.info.short_src, new.info.linedefined, new.info.lastlinedefined))
|
||||
end)
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
When a function is to be printed to console
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate({"client", "toConsole"}, function(data)
|
||||
if not data then return end
|
||||
|
||||
update({"client", "toConsole"}, nil)
|
||||
show(data)
|
||||
|
||||
file.CreateDir("fprofiler")
|
||||
file.Write("fprofiler/profiledata.txt", showStr(data))
|
||||
MsgC(Color(200, 200, 200), "-----", Color(120, 120, 255), "NOTE", Color(200, 200, 200), "---------------\n")
|
||||
MsgC(Color(200, 200, 200), "If the above function does not fit in console, you can find it in data/fprofiler/profiledata.txt\n\n")
|
||||
end)
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
API function: start profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.start(focus)
|
||||
update({"client", "focusStr"}, tostring(focus))
|
||||
update({"client", "focusObj"}, focus)
|
||||
update({"client", "shouldReset"}, true)
|
||||
update({"client", "status"}, "Started")
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
API function: stop profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.stop()
|
||||
update({"client", "status"}, "Stopped")
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
API function: continue profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.continueProfiling()
|
||||
update({"client", "shouldReset"}, false)
|
||||
update({"client", "status"}, "Started")
|
||||
end
|
||||
@@ -1,475 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The panel that contains the realm switcher
|
||||
---------------------------------------------------------------------------]]
|
||||
local REALMPANEL = {}
|
||||
|
||||
function REALMPANEL:Init()
|
||||
self:DockPadding(0, 0, 0, 0)
|
||||
self:DockMargin(0, 0, 5, 0)
|
||||
|
||||
self.realmLabel = vgui.Create("DLabel", self)
|
||||
self.realmLabel:SetDark(true)
|
||||
self.realmLabel:SetText("Realm:")
|
||||
|
||||
self.realmLabel:SizeToContents()
|
||||
self.realmLabel:Dock(TOP)
|
||||
|
||||
self.realmbox = vgui.Create("DComboBox", self)
|
||||
self.realmbox:AddChoice("Client")
|
||||
self.realmbox:AddChoice("Server")
|
||||
self.realmbox:Dock(TOP)
|
||||
|
||||
FProfiler.UI.onModelUpdate("realm", function(new)
|
||||
self.realmbox.selected = new == "client" and 1 or 2
|
||||
self.realmbox:SetText(new == "client" and "Client" or "Server")
|
||||
end)
|
||||
|
||||
FProfiler.UI.onModelUpdate("serverAccess", function(hasAccess)
|
||||
self.realmbox:SetDisabled(not hasAccess)
|
||||
|
||||
if not hasAccess and self.realmbox.selected == 2 then
|
||||
FProfiler.UI.updateModel("realm", "client")
|
||||
end
|
||||
end)
|
||||
|
||||
self.realmbox.OnSelect = function(_, _, value) FProfiler.UI.updateModel("realm", string.lower(value)) end
|
||||
end
|
||||
|
||||
function REALMPANEL:PerformLayout()
|
||||
self.realmLabel:SizeToContents()
|
||||
local top = ( self:GetTall() - self.realmLabel:GetTall() - self.realmbox:GetTall()) * 0.5
|
||||
self:DockPadding(0, top, 0, 0)
|
||||
end
|
||||
|
||||
derma.DefineControl("FProfileRealmPanel", "", REALMPANEL, "Panel")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The little red or green indicator that indicates whether the focussing
|
||||
function is correct
|
||||
---------------------------------------------------------------------------]]
|
||||
local FUNCINDICATOR = {}
|
||||
|
||||
function FUNCINDICATOR:Init()
|
||||
self:SetTall(5)
|
||||
self.color = Color(0, 0, 0, 0)
|
||||
end
|
||||
|
||||
function FUNCINDICATOR:Paint()
|
||||
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.color)
|
||||
end
|
||||
|
||||
derma.DefineControl("FProfileFuncIndicator", "", FUNCINDICATOR, "DPanel")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The panel that contains the focus text entry and the focus indicator
|
||||
---------------------------------------------------------------------------]]
|
||||
local FOCUSPANEL = {}
|
||||
|
||||
function FOCUSPANEL:Init()
|
||||
self:DockPadding(0, 0, 0, 0)
|
||||
self:DockMargin(0, 0, 5, 0)
|
||||
|
||||
self.focusLabel = vgui.Create("DLabel", self)
|
||||
self.focusLabel:SetDark(true)
|
||||
self.focusLabel:SetText("Profiling Focus:")
|
||||
|
||||
self.focusLabel:SizeToContents()
|
||||
self.focusLabel:Dock(TOP)
|
||||
|
||||
self.funcIndicator = vgui.Create("FProfileFuncIndicator", self)
|
||||
self.funcIndicator:Dock(BOTTOM)
|
||||
|
||||
self.focusBox = vgui.Create("DTextEntry", self)
|
||||
self.focusBox:SetText("")
|
||||
self.focusBox:SetWidth(150)
|
||||
self.focusBox:Dock(BOTTOM)
|
||||
self.focusBox:SetTooltip("Focus the profiling on a single function.\nEnter a global function name here (like player.GetAll)\nYou're not allowed to call functions in here (e.g. hook.GetTable() is not allowed)")
|
||||
|
||||
function self.focusBox:OnChange()
|
||||
FProfiler.UI.updateCurrentRealm("focusStr", self:GetText())
|
||||
end
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("focusObj", function(new)
|
||||
self.funcIndicator.color = FProfiler.UI.getCurrentRealmValue("focusStr") == "" and Color(0, 0, 0, 0) or new and Color(80, 255, 80, 255) or Color(255, 80, 80, 255)
|
||||
end)
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("focusStr", function(new, old)
|
||||
if self.focusBox:GetText() == new then return end
|
||||
|
||||
self.focusBox:SetText(tostring(new))
|
||||
end)
|
||||
end
|
||||
|
||||
function FOCUSPANEL:PerformLayout()
|
||||
self.focusBox:SetWide(200)
|
||||
self.focusLabel:SizeToContents()
|
||||
end
|
||||
|
||||
derma.DefineControl("FProfileFocusPanel", "", FOCUSPANEL, "Panel")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The timer that keeps track of for how long the profiling has been going on
|
||||
---------------------------------------------------------------------------]]
|
||||
local TIMERPANEL = {}
|
||||
|
||||
function TIMERPANEL:Init()
|
||||
self:DockPadding(0, 5, 0, 5)
|
||||
self:DockMargin(5, 0, 5, 0)
|
||||
|
||||
self.timeLabel = vgui.Create("DLabel", self)
|
||||
self.timeLabel:SetDark(true)
|
||||
self.timeLabel:SetText("Total profiling time:")
|
||||
|
||||
self.timeLabel:SizeToContents()
|
||||
self.timeLabel:Dock(TOP)
|
||||
|
||||
self.counter = vgui.Create("DLabel", self)
|
||||
self.counter:SetDark(true)
|
||||
self.counter:SetText("00:00:00")
|
||||
self.counter:SizeToContents()
|
||||
self.counter:Dock(RIGHT)
|
||||
|
||||
function self.counter:Think()
|
||||
local recordTime, sessionStart = FProfiler.UI.getCurrentRealmValue("recordTime"), FProfiler.UI.getCurrentRealmValue("sessionStart")
|
||||
|
||||
local totalTime = recordTime + (sessionStart and (CurTime() - sessionStart) or 0)
|
||||
|
||||
self:SetText(string.FormattedTime(totalTime, "%02i:%02i:%02i"))
|
||||
end
|
||||
end
|
||||
|
||||
function TIMERPANEL:PerformLayout()
|
||||
self.timeLabel:SizeToContents()
|
||||
self.counter:SizeToContents()
|
||||
end
|
||||
|
||||
derma.DefineControl("FProfileTimerPanel", "", TIMERPANEL, "Panel")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The top bar
|
||||
---------------------------------------------------------------------------]]
|
||||
local MAGICBAR = {}
|
||||
|
||||
function MAGICBAR:Init()
|
||||
self:DockPadding(5, 5, 5, 5)
|
||||
self.realmpanel = vgui.Create("FProfileRealmPanel", self)
|
||||
|
||||
-- (Re)Start profiling
|
||||
self.restartProfiling = vgui.Create("DButton", self)
|
||||
self.restartProfiling:SetText(" (Re)Start\n Profiling")
|
||||
self.restartProfiling:DockMargin(0, 0, 5, 0)
|
||||
self.restartProfiling:Dock(LEFT)
|
||||
|
||||
self.restartProfiling.DoClick = function()
|
||||
FProfiler.UI.updateCurrentRealm("shouldReset", true)
|
||||
FProfiler.UI.updateCurrentRealm("status", "Started")
|
||||
end
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
|
||||
self.restartProfiling:SetDisabled(new == "Started")
|
||||
end)
|
||||
|
||||
-- Stop profiling
|
||||
self.stopProfiling = vgui.Create("DButton", self)
|
||||
self.stopProfiling:SetText(" Stop\n Profiling")
|
||||
self.stopProfiling:DockMargin(0, 0, 5, 0)
|
||||
self.stopProfiling:Dock(LEFT)
|
||||
|
||||
self.stopProfiling.DoClick = function()
|
||||
FProfiler.UI.updateCurrentRealm("status", "Stopped")
|
||||
end
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
|
||||
self.stopProfiling:SetDisabled(new == "Stopped")
|
||||
end)
|
||||
|
||||
-- Continue profiling
|
||||
self.continueProfiling = vgui.Create("DButton", self)
|
||||
self.continueProfiling:SetText(" Continue\n Profiling")
|
||||
self.continueProfiling:DockMargin(0, 0, 5, 0)
|
||||
self.continueProfiling:Dock(LEFT)
|
||||
|
||||
self.continueProfiling.DoClick = function()
|
||||
FProfiler.UI.updateCurrentRealm("shouldReset", false)
|
||||
FProfiler.UI.updateCurrentRealm("status", "Started")
|
||||
end
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
|
||||
self.continueProfiling:SetDisabled(new == "Started")
|
||||
end)
|
||||
|
||||
self.realmpanel:Dock(LEFT)
|
||||
|
||||
self.focuspanel = vgui.Create("FProfileFocusPanel", self)
|
||||
self.focuspanel:Dock(LEFT)
|
||||
|
||||
-- Timer
|
||||
self.timerpanel = vgui.Create("FProfileTimerPanel", self)
|
||||
self.timerpanel:Dock(RIGHT)
|
||||
end
|
||||
|
||||
function MAGICBAR:PerformLayout()
|
||||
self.realmpanel:SizeToChildren(true, false)
|
||||
self.focuspanel:SizeToChildren(true, false)
|
||||
self.timerpanel:SizeToChildren(true, false)
|
||||
end
|
||||
|
||||
|
||||
derma.DefineControl("FProfileMagicBar", "", MAGICBAR, "DPanel")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
A custom sort by column function to deal with sorting by numeric value
|
||||
--------------------------------------------------------------------------]]
|
||||
local function SortByColumn(self, ColumnID, Desc)
|
||||
table.Copy(self.Sorted, self.Lines)
|
||||
|
||||
table.sort(self.Sorted, function(a, b)
|
||||
if Desc then
|
||||
a, b = b, a
|
||||
end
|
||||
|
||||
local aval = a:GetSortValue(ColumnID) or a:GetColumnText(ColumnID)
|
||||
local bval = b:GetSortValue(ColumnID) or b:GetColumnText(ColumnID)
|
||||
|
||||
local anum = tonumber(aval)
|
||||
local bnum = tonumber(bval)
|
||||
|
||||
if anum and bnum then
|
||||
return anum < bnum
|
||||
end
|
||||
|
||||
return tostring(aval) < tostring(bval)
|
||||
end)
|
||||
|
||||
self:SetDirty(true)
|
||||
self:InvalidateLayout()
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The Bottlenecks tab's contents
|
||||
---------------------------------------------------------------------------]]
|
||||
local BOTTLENECKTAB = {}
|
||||
|
||||
BOTTLENECKTAB.SortByColumn = SortByColumn
|
||||
|
||||
function BOTTLENECKTAB:Init()
|
||||
self:SetMultiSelect(false)
|
||||
self:AddColumn("Name")
|
||||
self:AddColumn("Path")
|
||||
self:AddColumn("Lines")
|
||||
self:AddColumn("Amount of times called")
|
||||
self:AddColumn("Total time in ms (inclusive)")
|
||||
self:AddColumn("Average time in ms (inclusive)")
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("bottlenecks", function(new)
|
||||
self:Clear()
|
||||
|
||||
for _, row in ipairs(new) do
|
||||
local names = {}
|
||||
local path = row.info.short_src
|
||||
local lines = path ~= "[C]" and row.info.linedefined .. " - " .. row.info.lastlinedefined or "N/A"
|
||||
local amountCalled = row.total_called
|
||||
local totalTime = row.total_time * 100
|
||||
local avgTime = row.average_time * 100
|
||||
|
||||
for _, fname in ipairs(row.names or {}) do
|
||||
if fname.namewhat == "" and fname.name == "" then continue end
|
||||
table.insert(names, fname.namewhat .. " " .. fname.name)
|
||||
end
|
||||
|
||||
if #names == 0 then names[1] = "Unknown" end
|
||||
|
||||
local line = self:AddLine(table.concat(names, "/"), path, lines, amountCalled, totalTime, avgTime)
|
||||
line.data = row
|
||||
end
|
||||
end)
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("currentSelected", function(new, old)
|
||||
if new == old then return end
|
||||
|
||||
for _, line in pairs(self.Lines) do
|
||||
line:SetSelected(line.data.func == new.func)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function BOTTLENECKTAB:OnRowSelected(id, line)
|
||||
FProfiler.UI.updateCurrentRealm("currentSelected", line.data)
|
||||
end
|
||||
|
||||
|
||||
derma.DefineControl("FProfileBottleNecks", "", BOTTLENECKTAB, "DListView")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The Top n lag spikes tab's contents
|
||||
---------------------------------------------------------------------------]]
|
||||
local TOPTENTAB = {}
|
||||
|
||||
TOPTENTAB.SortByColumn = SortByColumn
|
||||
|
||||
function TOPTENTAB:Init()
|
||||
self:SetMultiSelect(false)
|
||||
self:AddColumn("Name")
|
||||
self:AddColumn("Path")
|
||||
self:AddColumn("Lines")
|
||||
self:AddColumn("Runtime in ms")
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("topLagSpikes", function(new)
|
||||
self:Clear()
|
||||
|
||||
for _, row in ipairs(new) do
|
||||
if not row.func then break end
|
||||
|
||||
local name = row.info.name and row.info.name ~= "" and (row.info.namewhat .. " " .. row.info.name) or "Unknown"
|
||||
local path = row.info.short_src
|
||||
local lines = path ~= "[C]" and row.info.linedefined .. " - " .. row.info.lastlinedefined or "N/A"
|
||||
local runtime = row.runtime * 100
|
||||
|
||||
local line = self:AddLine(name, path, lines, runtime)
|
||||
line.data = row
|
||||
end
|
||||
end)
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("currentSelected", function(new, old)
|
||||
if new == old then return end
|
||||
|
||||
for _, line in pairs(self.Lines) do
|
||||
line:SetSelected(line.data.func == new.func)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function TOPTENTAB:OnRowSelected(id, line)
|
||||
FProfiler.UI.updateCurrentRealm("currentSelected", line.data)
|
||||
end
|
||||
|
||||
derma.DefineControl("FProfileTopTen", "", TOPTENTAB, "DListView")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The Tab panel of the bottlenecks and top n lag spikes
|
||||
---------------------------------------------------------------------------]]
|
||||
local RESULTSHEET = {}
|
||||
|
||||
function RESULTSHEET:Init()
|
||||
self:DockMargin(0, 10, 0, 0)
|
||||
self:SetPadding(0)
|
||||
|
||||
self.bottlenecksTab = vgui.Create("FProfileBottleNecks")
|
||||
self:AddSheet("Bottlenecks", self.bottlenecksTab)
|
||||
|
||||
self.toptenTab = vgui.Create("FProfileTopTen")
|
||||
self:AddSheet("Top 50 most expensive function calls", self.toptenTab)
|
||||
|
||||
end
|
||||
|
||||
|
||||
derma.DefineControl("FProfileResultSheet", "", RESULTSHEET, "DPropertySheet")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The function details panel
|
||||
---------------------------------------------------------------------------]]
|
||||
local FUNCDETAILS = {}
|
||||
|
||||
function FUNCDETAILS:Init()
|
||||
self.titleLabel = vgui.Create("DLabel", self)
|
||||
self.titleLabel:SetDark(true)
|
||||
self.titleLabel:SetFont("DermaLarge")
|
||||
self.titleLabel:SetText("Function Details")
|
||||
self.titleLabel:SizeToContents()
|
||||
-- self.titleLabel:Dock(TOP)
|
||||
|
||||
self.focus = vgui.Create("DButton", self)
|
||||
self.focus:SetText("Focus")
|
||||
self.focus:SetTall(50)
|
||||
self.focus:SetFont("DermaDefaultBold")
|
||||
self.focus:Dock(BOTTOM)
|
||||
|
||||
function self.focus:DoClick()
|
||||
local sel = FProfiler.UI.getCurrentRealmValue("currentSelected")
|
||||
if not sel then return end
|
||||
|
||||
FProfiler.UI.updateCurrentRealm("focusStr", sel.func)
|
||||
end
|
||||
|
||||
self.source = vgui.Create("DTextEntry", self)
|
||||
self.source:SetKeyboardInputEnabled(false)
|
||||
self.source:DockMargin(0, 40, 0, 0)
|
||||
self.source:SetMultiline(true)
|
||||
self.source:Dock(FILL)
|
||||
|
||||
FProfiler.UI.onCurrentRealmUpdate("sourceText", function(new)
|
||||
self.source:SetText(string.Replace(new, "\t", " "))
|
||||
end)
|
||||
|
||||
self.toConsole = vgui.Create("DButton", self)
|
||||
self.toConsole:SetText("Print Details to Console")
|
||||
self.toConsole:SetTall(50)
|
||||
self.toConsole:SetFont("DermaDefaultBold")
|
||||
self.toConsole:Dock(BOTTOM)
|
||||
|
||||
function self.toConsole:DoClick()
|
||||
FProfiler.UI.updateCurrentRealm("toConsole", FProfiler.UI.getCurrentRealmValue("currentSelected"))
|
||||
end
|
||||
end
|
||||
|
||||
function FUNCDETAILS:PerformLayout()
|
||||
self.titleLabel:CenterHorizontal()
|
||||
end
|
||||
derma.DefineControl("FProfileFuncDetails", "", FUNCDETAILS, "DPanel")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The actual frame
|
||||
---------------------------------------------------------------------------]]
|
||||
local FRAME = {}
|
||||
|
||||
local frameInstance
|
||||
function FRAME:Init()
|
||||
self:SetTitle("FProfiler profiling tool")
|
||||
self:SetSize(ScrW() * 0.8, ScrH() * 0.8)
|
||||
self:Center()
|
||||
self:SetVisible(true)
|
||||
self:MakePopup()
|
||||
self:SetDeleteOnClose(false)
|
||||
|
||||
self.magicbar = vgui.Create("FProfileMagicBar", self)
|
||||
self.magicbar:SetTall(math.max(self:GetTall() * 0.07, 48))
|
||||
self.magicbar:Dock(TOP)
|
||||
|
||||
self.resultsheet = vgui.Create("FProfileResultSheet", self)
|
||||
self.resultsheet:SetWide(self:GetWide() * 0.8)
|
||||
self.resultsheet:Dock(LEFT)
|
||||
|
||||
self.details = vgui.Create("FProfileFuncDetails", self)
|
||||
self.details:SetWide(self:GetWide() * 0.2 - 12)
|
||||
self.details:DockMargin(5, 31, 0, 0)
|
||||
self.details:Dock(RIGHT)
|
||||
end
|
||||
|
||||
function FRAME:OnClose()
|
||||
FProfiler.UI.updateModel("frameVisible", false)
|
||||
end
|
||||
|
||||
derma.DefineControl("FProfileFrame", "", FRAME, "DFrame")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The command to start the profiler
|
||||
---------------------------------------------------------------------------]]
|
||||
concommand.Add("FProfiler",
|
||||
function()
|
||||
frameInstance = frameInstance or vgui.Create("FProfileFrame")
|
||||
frameInstance:SetVisible(true)
|
||||
|
||||
FProfiler.UI.updateModel("frameVisible", true)
|
||||
end,
|
||||
nil, "Starts FProfiler")
|
||||
@@ -1,188 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The model describes the data that the drives the UI
|
||||
Loosely based on the Elm architecture
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
local model =
|
||||
{
|
||||
realm = "client", -- "client" or "server"
|
||||
serverAccess = false, -- Whether the player has access to profile the server
|
||||
frameVisible = false, -- Whether the frame is visible
|
||||
|
||||
client = {
|
||||
status = "Stopped", -- Started or Stopped
|
||||
shouldReset = true, -- Whether profiling should start anew
|
||||
recordTime = 0, -- Total time spent on the last full profiling session
|
||||
sessionStart = nil, -- When the last profiling session was started
|
||||
sessionStartSysTime = nil, -- When the last profiling session was started, measured in SysTime
|
||||
bottlenecks = {}, -- The list of bottleneck functions
|
||||
topLagSpikes = {}, -- Top of lagging functions
|
||||
currentSelected = nil, -- Currently selected function
|
||||
|
||||
focusObj = nil, -- The current function being focussed upon in profiling
|
||||
focusStr = "", -- The current function name being entered
|
||||
|
||||
toConsole = nil, -- Any functions that should be printed to console
|
||||
|
||||
sourceText = "", -- The text of the source function (if available)
|
||||
},
|
||||
|
||||
server = {
|
||||
status = "Stopped", -- Started or Stopped
|
||||
shouldReset = true, -- Whether profiling should start anew
|
||||
bottlenecks = {}, -- The list of bottleneck functions
|
||||
recordTime = 0, -- Total time spent on the last full profiling session
|
||||
sessionStart = nil, -- When the last profiling session was started
|
||||
topLagSpikes = {}, -- Top of lagging functions
|
||||
currentSelected = nil, -- Currently selected function
|
||||
|
||||
focusObj = nil, -- The current function being focussed upon in profiling
|
||||
focusStr = "", -- The current function name
|
||||
|
||||
toConsole = nil, -- Any functions that should be printed to console
|
||||
|
||||
sourceText = "", -- The text of the source function (if available)
|
||||
fromServer = false, -- Whether a change of the model came from the server.
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
local updaters = {}
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Update the model.
|
||||
Automatically calls the registered update hook functions
|
||||
|
||||
e.g. updating the realm would be:
|
||||
FProfiler.UI.updateModel("realm", "server")
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.UI.updateModel(path, value)
|
||||
path = istable(path) and path or {path}
|
||||
|
||||
local updTbl = updaters
|
||||
local mdlTbl = model
|
||||
local key = path[#path]
|
||||
|
||||
for i = 1, #path - 1 do
|
||||
mdlTbl = mdlTbl[path[i]]
|
||||
updTbl = updTbl and updTbl[path[i]]
|
||||
end
|
||||
|
||||
local oldValue = mdlTbl[key]
|
||||
mdlTbl[key] = value
|
||||
|
||||
for _, updFunc in ipairs(updTbl and updTbl[key] or {}) do
|
||||
updFunc(value, oldValue)
|
||||
end
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Update the model of the current realm
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.UI.updateCurrentRealm(path, value)
|
||||
path = istable(path) and path or {path}
|
||||
|
||||
table.insert(path, 1, model.realm)
|
||||
|
||||
FProfiler.UI.updateModel(path, value)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Retrieve a value of the model
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.UI.getModelValue(path)
|
||||
path = istable(path) and path or {path}
|
||||
|
||||
local mdlTbl = model
|
||||
local key = path[#path]
|
||||
|
||||
for i = 1, #path - 1 do
|
||||
mdlTbl = mdlTbl[path[i]]
|
||||
end
|
||||
|
||||
return mdlTbl[key]
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Retrieve a value of the model regardless of realm
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.UI.getCurrentRealmValue(path)
|
||||
path = istable(path) and path or {path}
|
||||
|
||||
table.insert(path, 1, model.realm)
|
||||
|
||||
return FProfiler.UI.getModelValue(path)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Registers a hook that gets triggered when a certain part of the model is updated
|
||||
e.g. FProfiler.UI.onModelUpdate("realm", print) prints when the realm is changed
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.UI.onModelUpdate(path, func)
|
||||
path = istable(path) and path or {path}
|
||||
|
||||
local updTbl = updaters
|
||||
local mdlTbl = model
|
||||
local key = path[#path]
|
||||
|
||||
for i = 1, #path - 1 do
|
||||
mdlTbl = mdlTbl[path[i]]
|
||||
updTbl[path[i]] = updTbl[path[i]] or {}
|
||||
updTbl = updTbl[path[i]]
|
||||
end
|
||||
|
||||
updTbl[key] = updTbl[key] or {}
|
||||
|
||||
table.insert(updTbl[key], func)
|
||||
|
||||
-- Call update with the initial value
|
||||
if mdlTbl[key] ~= nil then
|
||||
func(mdlTbl[key], mdlTbl[key])
|
||||
end
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Registers a hook to both realms
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.UI.onCurrentRealmUpdate(path, func)
|
||||
path = istable(path) and path or {path}
|
||||
|
||||
table.insert(path, 1, "client")
|
||||
FProfiler.UI.onModelUpdate(path, function(...)
|
||||
if FProfiler.UI.getModelValue("realm") == "server" then return end
|
||||
|
||||
func(...)
|
||||
end)
|
||||
|
||||
path[1] = "server"
|
||||
FProfiler.UI.onModelUpdate(path, function(...)
|
||||
if FProfiler.UI.getModelValue("realm") == "client" then return end
|
||||
|
||||
func(...)
|
||||
end)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
When the realm is changed, all update functions of the new realm are to be called
|
||||
---------------------------------------------------------------------------]]
|
||||
FProfiler.UI.onModelUpdate("realm", function(new, old)
|
||||
if not updaters[new] then return end
|
||||
|
||||
for k, funcTbl in pairs(updaters[new]) do
|
||||
for _, func in ipairs(funcTbl) do
|
||||
func(model[new][k], model[new][k])
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
The server is involved in the ui in the sense that it interacts with its model
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
-- Net messages
|
||||
util.AddNetworkString("FProfile_startProfiling")
|
||||
util.AddNetworkString("FProfile_stopProfiling")
|
||||
util.AddNetworkString("FProfile_focusObj")
|
||||
util.AddNetworkString("FProfile_getSource")
|
||||
util.AddNetworkString("FProfile_printFunction")
|
||||
util.AddNetworkString("FProfile_fullModelUpdate")
|
||||
util.AddNetworkString("FProfile_focusUpdate")
|
||||
util.AddNetworkString("FProfile_unsubscribe")
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Simplified version of the model
|
||||
Contains only what the server needs to know
|
||||
---------------------------------------------------------------------------]]
|
||||
local model =
|
||||
{
|
||||
focusObj = nil, -- the function currently in focus
|
||||
sessionStart = nil, -- When the last profiling session was started. Used for the live timer.
|
||||
sessionStartSysTime = nil, -- Same as sessionStart, but measured in SysTime. Used to update recordTime
|
||||
recordTime = 0, -- Total time spent on the last full profiling session
|
||||
bottlenecks = {}, -- The list of bottleneck functions
|
||||
topLagSpikes = {}, -- Top of lagging functions
|
||||
subscribers = RecipientFilter(), -- The players that get updates of the profiler model
|
||||
}
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Helper function: receive a net message
|
||||
---------------------------------------------------------------------------]]
|
||||
local function receive(msg, f)
|
||||
net.Receive(msg, function(len, ply)
|
||||
-- Check access.
|
||||
CAMI.PlayerHasAccess(ply, "FProfiler", function(b, _)
|
||||
if not b then return end
|
||||
|
||||
f(len, ply)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Helper function:
|
||||
Write generic row data to a net message
|
||||
---------------------------------------------------------------------------]]
|
||||
local function writeRowData(row)
|
||||
net.WriteString(tostring(row.func))
|
||||
net.WriteString(row.info.short_src)
|
||||
net.WriteUInt(row.info.linedefined, 16)
|
||||
net.WriteUInt(row.info.lastlinedefined, 16)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Helper function:
|
||||
Send the bottlenecks to the client
|
||||
Only sends the things displayed
|
||||
---------------------------------------------------------------------------]]
|
||||
local function writeBottleNecks()
|
||||
net.WriteUInt(#model.bottlenecks, 16)
|
||||
|
||||
for i, row in ipairs(model.bottlenecks) do
|
||||
writeRowData(row)
|
||||
|
||||
net.WriteUInt(#row.names, 8)
|
||||
|
||||
for j, name in ipairs(row.names) do
|
||||
net.WriteString(name.name)
|
||||
net.WriteString(name.namewhat)
|
||||
end
|
||||
|
||||
net.WriteUInt(row.total_called, 32)
|
||||
net.WriteDouble(row.total_time)
|
||||
net.WriteDouble(row.average_time)
|
||||
end
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Helper function:
|
||||
Sends the top n functions
|
||||
---------------------------------------------------------------------------]]
|
||||
local function writeTopN()
|
||||
local count = #model.topLagSpikes
|
||||
|
||||
-- All top N f
|
||||
for i = count, 0, -1 do
|
||||
if model.topLagSpikes and model.topLagSpikes[i] and model.topLagSpikes[i].info then break end -- Entry exists
|
||||
count = i
|
||||
end
|
||||
|
||||
net.WriteUInt(count, 8)
|
||||
|
||||
for i = 1, count do
|
||||
local row = model.topLagSpikes[i]
|
||||
|
||||
if not row.info then break end
|
||||
|
||||
writeRowData(row)
|
||||
|
||||
net.WriteString(row.info.name or "")
|
||||
net.WriteString(row.info.namewhat or "")
|
||||
net.WriteDouble(row.runtime)
|
||||
end
|
||||
end
|
||||
|
||||
-- Start profiling
|
||||
local function startProfiling()
|
||||
model.sessionStart = CurTime()
|
||||
model.sessionStartSysTime = SysTime()
|
||||
FProfiler.Internal.start(model.focusObj)
|
||||
|
||||
net.Start("FProfile_startProfiling")
|
||||
net.WriteDouble(model.recordTime)
|
||||
net.WriteDouble(model.sessionStart)
|
||||
net.Send(model.subscribers:GetPlayers())
|
||||
end
|
||||
|
||||
-- Stop profiling
|
||||
local function stopProfiling()
|
||||
FProfiler.Internal.stop()
|
||||
|
||||
model.recordTime = model.recordTime + SysTime() - (model.sessionStartSysTime or 0)
|
||||
model.sessionStart = nil
|
||||
model.sessionStartSysTime = nil
|
||||
|
||||
model.bottlenecks = FProfiler.Internal.getAggregatedResults(100)
|
||||
model.topLagSpikes = FProfiler.Internal.getMostExpensiveSingleCalls()
|
||||
|
||||
net.Start("FProfile_stopProfiling")
|
||||
net.WriteDouble(model.recordTime)
|
||||
|
||||
writeBottleNecks()
|
||||
writeTopN()
|
||||
net.Send(model.subscribers:GetPlayers())
|
||||
end
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Receive an update of the function to focus on
|
||||
---------------------------------------------------------------------------]]
|
||||
receive("FProfile_focusObj", function(_, ply)
|
||||
local funcStr = net.ReadString()
|
||||
|
||||
model.focusObj = FProfiler.funcNameToObj(funcStr)
|
||||
|
||||
net.Start("FProfile_focusObj")
|
||||
net.WriteBool(model.focusObj and true or false)
|
||||
net.Send(ply)
|
||||
|
||||
-- Send a focus update to all other players
|
||||
net.Start("FProfile_focusUpdate")
|
||||
net.WriteString(funcStr)
|
||||
net.WriteBool(model.focusObj and true or false)
|
||||
model.subscribers:RemovePlayer(ply)
|
||||
net.Send(model.subscribers:GetPlayers())
|
||||
model.subscribers:AddPlayer(ply)
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Receive a "start profiling" signal
|
||||
---------------------------------------------------------------------------]]
|
||||
receive("FProfile_startProfiling", function(_, ply)
|
||||
local shouldReset = net.ReadBool()
|
||||
if shouldReset then
|
||||
FProfiler.Internal.reset()
|
||||
model.recordTime = 0
|
||||
end
|
||||
|
||||
startProfiling()
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Receive a stop profiling signal
|
||||
---------------------------------------------------------------------------]]
|
||||
receive("FProfile_stopProfiling", function(_, ply)
|
||||
stopProfiling()
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Send the source of a function to a client
|
||||
---------------------------------------------------------------------------]]
|
||||
receive("FProfile_getSource", function(_, ply)
|
||||
local func = FProfiler.funcNameToObj(net.ReadString())
|
||||
|
||||
if not func then return end
|
||||
|
||||
local info = debug.getinfo(func)
|
||||
|
||||
if not info then return end
|
||||
|
||||
net.Start("FProfile_getSource")
|
||||
net.WriteString(FProfiler.readSource(info.short_src, info.linedefined, info.lastlinedefined) or "")
|
||||
net.Send(ply)
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Print the details of a function
|
||||
---------------------------------------------------------------------------]]
|
||||
receive("FProfile_printFunction", function(_, ply)
|
||||
local source = net.ReadBool() -- true is from bottlenecks, false is from Top-N
|
||||
local dataSource = source and model.bottlenecks or model.topLagSpikes
|
||||
local func = net.ReadString()
|
||||
|
||||
local data
|
||||
|
||||
for _, row in ipairs(dataSource or {}) do
|
||||
if tostring(row.func) == func then data = row break end
|
||||
end
|
||||
|
||||
if not data then return end
|
||||
|
||||
-- Show the data
|
||||
show(data)
|
||||
local plaintext = showStr(data)
|
||||
|
||||
-- Write to file if necessary
|
||||
file.CreateDir("fprofiler")
|
||||
file.Write("fprofiler/profiledata.txt", plaintext)
|
||||
MsgC(Color(200, 200, 200), "-----", Color(120, 120, 255), "NOTE", Color(200, 200, 200), "---------------\n")
|
||||
MsgC(Color(200, 200, 200), "If the above function does not fit in console, you can find it in data/fprofiler/profiledata.txt\n\n")
|
||||
|
||||
-- Listen server hosts already see the server console
|
||||
if ply:IsListenServerHost() then return end
|
||||
|
||||
-- Send a plaintext version to the client
|
||||
local binary = util.Compress(plaintext)
|
||||
|
||||
net.Start("FProfile_printFunction")
|
||||
net.WriteData(binary, #binary)
|
||||
net.Send(ply)
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Request of a full model update
|
||||
Particularly useful when someone else has done (or is performing) a profiling session
|
||||
and the current player wants to see the results
|
||||
---------------------------------------------------------------------------]]
|
||||
receive("FProfile_fullModelUpdate", function(_, ply)
|
||||
-- This player is now subscribed to the updates
|
||||
model.subscribers:AddPlayer(ply)
|
||||
|
||||
net.Start("FProfile_fullModelUpdate")
|
||||
net.WriteBool(model.focusObj ~= nil)
|
||||
if model.focusObj ~= nil then net.WriteString(tostring(model.focusObj)) end
|
||||
|
||||
-- Bool also indicates whether it's currently profiling
|
||||
net.WriteBool(model.sessionStart ~= nil)
|
||||
if model.sessionStart ~= nil then net.WriteDouble(model.sessionStart) end
|
||||
|
||||
net.WriteDouble(model.recordTime)
|
||||
|
||||
writeBottleNecks()
|
||||
writeTopN()
|
||||
|
||||
net.Send(ply)
|
||||
end)
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Unsubscribe from the updates of the profiler
|
||||
---------------------------------------------------------------------------]]
|
||||
receive("FProfile_unsubscribe", function(_, ply)
|
||||
model.subscribers:RemovePlayer(ply)
|
||||
end)
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
API function: start profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.start(focus)
|
||||
FProfiler.Internal.reset()
|
||||
|
||||
model.recordTime = 0
|
||||
model.focusObj = focus
|
||||
|
||||
startProfiling()
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
API function: stop profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.stop()
|
||||
stopProfiling()
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
API function: continue profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
function FProfiler.continueProfiling()
|
||||
startProfiling()
|
||||
end
|
||||
@@ -1,238 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local get, update, onUpdate = FProfiler.UI.getModelValue, FProfiler.UI.updateModel, FProfiler.UI.onModelUpdate
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Update the current selected focus object when data is entered
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate({"server", "focusStr"}, function(new)
|
||||
if not new or get({"server", "fromServer"}) then return end
|
||||
|
||||
net.Start("FProfile_focusObj")
|
||||
net.WriteString(new)
|
||||
net.SendToServer()
|
||||
end)
|
||||
|
||||
net.Receive("FProfile_focusObj", function()
|
||||
update({"server", "focusObj"}, net.ReadBool() and get({"server", "focusStr"}) or nil)
|
||||
end)
|
||||
|
||||
-- A focus update occurs when someone else changes the focus
|
||||
net.Receive("FProfile_focusUpdate", function()
|
||||
update({"server", "fromServer"}, true)
|
||||
|
||||
local focusStr = net.ReadString()
|
||||
update({"server", "focusStr"}, focusStr)
|
||||
update({"server", "focusObj"}, net.ReadBool() and focusStr or nil)
|
||||
|
||||
update({"server", "fromServer"}, false)
|
||||
end)
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
(Re)start profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
local function restartProfiling()
|
||||
local shouldReset = get({"server", "shouldReset"})
|
||||
|
||||
net.Start("FProfile_startProfiling")
|
||||
net.WriteBool(shouldReset)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
net.Receive("FProfile_startProfiling", function()
|
||||
update({"server", "fromServer"}, true)
|
||||
update({"server", "status"}, "Started")
|
||||
update({"server", "recordTime"}, net.ReadDouble())
|
||||
update({"server", "sessionStart"}, net.ReadDouble())
|
||||
update({"server", "fromServer"}, false)
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Stop profiling
|
||||
---------------------------------------------------------------------------]]
|
||||
local function stopProfiling()
|
||||
net.Start("FProfile_stopProfiling")
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
-- Read a row from a net message
|
||||
local function readDataRow(countSize, readSpecific)
|
||||
local res = {}
|
||||
|
||||
local count = net.ReadUInt(countSize)
|
||||
|
||||
for i = 1, count do
|
||||
local row = {}
|
||||
row.info = {}
|
||||
|
||||
row.func = net.ReadString()
|
||||
row.info.short_src = net.ReadString()
|
||||
row.info.linedefined = net.ReadUInt(16)
|
||||
row.info.lastlinedefined = net.ReadUInt(16)
|
||||
|
||||
readSpecific(row)
|
||||
|
||||
table.insert(res, row)
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
-- Read a bottleneck row
|
||||
local function readBottleneckRow(row)
|
||||
local nameCount = net.ReadUInt(8)
|
||||
|
||||
row.names = {}
|
||||
for i = 1, nameCount do
|
||||
table.insert(row.names, {
|
||||
name = net.ReadString(),
|
||||
namewhat = net.ReadString()
|
||||
})
|
||||
end
|
||||
|
||||
row.total_called = net.ReadUInt(32)
|
||||
row.total_time = net.ReadDouble()
|
||||
row.average_time = net.ReadDouble()
|
||||
end
|
||||
|
||||
-- Read the top n row
|
||||
local function readTopNRow(row)
|
||||
row.info.name = net.ReadString()
|
||||
row.info.namewhat = net.ReadString()
|
||||
row.runtime = net.ReadDouble()
|
||||
end
|
||||
|
||||
net.Receive("FProfile_stopProfiling", function()
|
||||
update({"server", "fromServer"}, true)
|
||||
update({"server", "status"}, "Stopped")
|
||||
update({"server", "sessionStart"}, nil)
|
||||
update({"server", "recordTime"}, net.ReadDouble())
|
||||
|
||||
update({"server", "bottlenecks"}, readDataRow(16, readBottleneckRow))
|
||||
update({"server", "topLagSpikes"}, readDataRow(8, readTopNRow))
|
||||
update({"server", "fromServer"}, false)
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Start/stop recording when the recording status is changed
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate({"server", "status"}, function(new, old)
|
||||
if new == old or get({"server", "fromServer"}) then return end
|
||||
(new == "Started" and restartProfiling or stopProfiling)()
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Update info when a different line is selected
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate({"server", "currentSelected"}, function(new)
|
||||
if not new or not new.info or not new.info.linedefined or not new.info.lastlinedefined or not new.info.short_src then return end
|
||||
|
||||
net.Start("FProfile_getSource")
|
||||
net.WriteString(tostring(new.func))
|
||||
net.SendToServer()
|
||||
end)
|
||||
|
||||
net.Receive("FProfile_getSource", function()
|
||||
update({"server", "sourceText"}, net.ReadString())
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
When a function is to be printed to console
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate({"server", "toConsole"}, function(data)
|
||||
if not data then return end
|
||||
|
||||
update({"server", "toConsole"}, nil)
|
||||
|
||||
net.Start("FProfile_printFunction")
|
||||
net.WriteBool(data.total_called and true or false) -- true for bottleneck function, false for top-n function
|
||||
net.WriteString(tostring(data.func))
|
||||
net.SendToServer()
|
||||
end)
|
||||
|
||||
net.Receive("FProfile_printFunction", function(len)
|
||||
local data = net.ReadData(len)
|
||||
local decompressed = util.Decompress(data)
|
||||
|
||||
-- Print the text line by line, otherwise big parts of big data will not be printed
|
||||
local split = string.Explode("\n", decompressed, false)
|
||||
for _, line in ipairs(split) do
|
||||
MsgN(line)
|
||||
end
|
||||
|
||||
-- Write the thing to a file
|
||||
file.CreateDir("fprofiler")
|
||||
file.Write("fprofiler/profiledata.txt", showStr(data))
|
||||
MsgC(Color(200, 200, 200), "-----", Color(120, 120, 255), "NOTE", Color(200, 200, 200), "---------------\n")
|
||||
MsgC(Color(200, 200, 200), "In the server's console you can find a colour coded version of the above output.\nIf the above function does not fit in console, you can find it in data/fprofiler/profiledata.txt\n\n")
|
||||
end)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Check access when the frame opens
|
||||
Also request a full serverside model update
|
||||
---------------------------------------------------------------------------]]
|
||||
onUpdate("frameVisible", function(isOpen)
|
||||
-- Don't network if the server doesn't have FProfiler installed
|
||||
if util.NetworkStringToID("FProfile_fullModelUpdate") == 0 then
|
||||
update("serverAccess", false)
|
||||
return
|
||||
end
|
||||
|
||||
-- Update access
|
||||
CAMI.PlayerHasAccess(LocalPlayer(), "FProfiler", function(b, _)
|
||||
update("serverAccess", b)
|
||||
end)
|
||||
|
||||
if not isOpen then
|
||||
net.Start("FProfile_unsubscribe")
|
||||
net.SendToServer()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
net.Start("FProfile_fullModelUpdate")
|
||||
net.SendToServer()
|
||||
end)
|
||||
|
||||
|
||||
net.Receive("FProfile_fullModelUpdate", function()
|
||||
update({"server", "fromServer"}, true)
|
||||
|
||||
local focusExists = net.ReadBool()
|
||||
if focusExists then
|
||||
local focus = net.ReadString()
|
||||
update({"server", "focusObj"}, focus)
|
||||
update({"server", "focusStr"}, focus)
|
||||
end
|
||||
|
||||
local startingTimeExists = net.ReadBool()
|
||||
|
||||
if startingTimeExists then
|
||||
update({"server", "status"}, "Started")
|
||||
update({"server", "sessionStart"}, net.ReadDouble())
|
||||
else
|
||||
update({"server", "status"}, "Stopped")
|
||||
end
|
||||
|
||||
update({"server", "recordTime"}, net.ReadDouble())
|
||||
|
||||
update({"server", "bottlenecks"}, readDataRow(16, readBottleneckRow))
|
||||
update({"server", "topLagSpikes"}, readDataRow(8, readTopNRow))
|
||||
|
||||
update({"server", "fromServer"}, false)
|
||||
end)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- Try to find the function represented by a string
|
||||
function FProfiler.funcNameToObj(str)
|
||||
if isfunction(str) then return str end
|
||||
|
||||
local times = FProfiler.Internal.getCallCounts()
|
||||
for func, _ in pairs(times) do
|
||||
if tostring(func) == str then return func end
|
||||
end
|
||||
|
||||
local tbl = _G
|
||||
local exploded = string.Explode(".", str, false)
|
||||
if not exploded or not exploded[1] then return end
|
||||
|
||||
for i = 1, #exploded - 1 do
|
||||
tbl = (tbl or {})[exploded[i]]
|
||||
if not istable(tbl) then return end
|
||||
end
|
||||
|
||||
local func = (tbl or {})[exploded[#exploded]]
|
||||
|
||||
if not isfunction(func) then return end
|
||||
|
||||
return func
|
||||
end
|
||||
|
||||
-- Read a file
|
||||
function FProfiler.readSource(fname, startLine, endLine)
|
||||
if not file.Exists(fname, "GAME") then return "" end
|
||||
if startLine < 0 or endLine < 0 or endLine < startLine then return "" end
|
||||
|
||||
local f = file.Open(fname, "r", "GAME")
|
||||
|
||||
for i = 1, startLine - 1 do f:ReadLine() end
|
||||
|
||||
local res = {}
|
||||
for i = startLine, endLine do
|
||||
table.insert(res, f:ReadLine() or "")
|
||||
end
|
||||
|
||||
return table.concat(res, "\n")
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
Plugin
|
||||
{
|
||||
file "lua/bin/gmsv_gluapack_plugin.so"
|
||||
}
|
||||