Initial commit
This commit is contained in:
57
gamemodes/darkrp/gamemode/cl_init.lua
Normal file
57
gamemodes/darkrp/gamemode/cl_init.lua
Normal file
@@ -0,0 +1,57 @@
|
||||
hook.Run("DarkRPStartedLoading")
|
||||
|
||||
GM.Version = "2.7.0"
|
||||
GM.Name = "DarkRP"
|
||||
GM.Author = "By FPtje Falco et al."
|
||||
|
||||
DeriveGamemode("sandbox")
|
||||
DEFINE_BASECLASS("gamemode_sandbox")
|
||||
GM.Sandbox = BaseClass
|
||||
|
||||
|
||||
local function LoadModules()
|
||||
local root = GM.FolderName .. "/gamemode/modules/"
|
||||
local _, folders = file.Find(root .. "*", "LUA")
|
||||
|
||||
for _, folder in SortedPairs(folders, true) do
|
||||
if DarkRP.disabledDefaults["modules"][folder] then continue end
|
||||
|
||||
for _, File in SortedPairs(file.Find(root .. folder .. "/sh_*.lua", "LUA"), true) do
|
||||
if File == "sh_interface.lua" then continue end
|
||||
include(root .. folder .. "/" .. File)
|
||||
end
|
||||
|
||||
for _, File in SortedPairs(file.Find(root .. folder .. "/cl_*.lua", "LUA"), true) do
|
||||
if File == "cl_interface.lua" then continue end
|
||||
include(root .. folder .. "/" .. File)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
GM.Config = {} -- config table
|
||||
GM.NoLicense = GM.NoLicense or {}
|
||||
|
||||
include("config/config.lua")
|
||||
include("libraries/sh_cami.lua")
|
||||
include("libraries/simplerr.lua")
|
||||
include("libraries/fn.lua")
|
||||
include("libraries/tablecheck.lua")
|
||||
include("libraries/interfaceloader.lua")
|
||||
include("libraries/disjointset.lua")
|
||||
include("config/licenseweapons.lua")
|
||||
|
||||
include("libraries/modificationloader.lua")
|
||||
|
||||
hook.Call("DarkRPPreLoadModules", GM)
|
||||
|
||||
LoadModules()
|
||||
|
||||
DarkRP.DARKRP_LOADING = true
|
||||
include("config/jobrelated.lua")
|
||||
include("config/addentities.lua")
|
||||
include("config/ammotypes.lua")
|
||||
DarkRP.DARKRP_LOADING = nil
|
||||
|
||||
DarkRP.finish()
|
||||
|
||||
hook.Call("DarkRPFinishedLoading", GM)
|
||||
@@ -0,0 +1,4 @@
|
||||
You should not edit DarkRP files. Not even the configuration files.
|
||||
|
||||
Use the DarkRPMod addon instead
|
||||
https://github.com/FPtje/DarkRPModification
|
||||
9
gamemodes/darkrp/gamemode/config/_MySQL.lua
Normal file
9
gamemodes/darkrp/gamemode/config/_MySQL.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
RP_MySQLConfig = {}
|
||||
RP_MySQLConfig.EnableMySQL = false
|
||||
RP_MySQLConfig.Host = "127.0.0.1"
|
||||
RP_MySQLConfig.Username = "user"
|
||||
RP_MySQLConfig.Password = "password"
|
||||
RP_MySQLConfig.Database_name = "DarkRP"
|
||||
RP_MySQLConfig.Database_port = 3306
|
||||
RP_MySQLConfig.Preferred_module = "mysqloo"
|
||||
RP_MySQLConfig.MultiStatements = false
|
||||
166
gamemodes/darkrp/gamemode/config/addentities.lua
Normal file
166
gamemodes/darkrp/gamemode/config/addentities.lua
Normal file
@@ -0,0 +1,166 @@
|
||||
DarkRP.createShipment("Глоки отмычки", {
|
||||
model = "models/weapons/w_pist_glock18.mdl",
|
||||
entity = "qwb_glock18",
|
||||
price = 10000,
|
||||
amount = 10,
|
||||
separate = false,
|
||||
pricesep = 160,
|
||||
noship = false,
|
||||
allowed = {TEAM_GUN},
|
||||
category = "Пистолеты",
|
||||
})
|
||||
|
||||
DarkRP.createShipment("Юсп доброграды", {
|
||||
model = "models/weapons/w_pist_usp.mdl",
|
||||
entity = "qwb_usp",
|
||||
price = 8000,
|
||||
amount = 10,
|
||||
separate = false,
|
||||
pricesep = 160,
|
||||
noship = false,
|
||||
allowed = {TEAM_GUN},
|
||||
category = "Пистолеты",
|
||||
})
|
||||
|
||||
DarkRP.createShipment("Юмп сенвуя", {
|
||||
model = "models/weapons/w_smg_ump45.mdl",
|
||||
entity = "qwb_ump45",
|
||||
price = 15000,
|
||||
amount = 10,
|
||||
separate = false,
|
||||
pricesep = nil,
|
||||
noship = false,
|
||||
allowed = {TEAM_GUN},
|
||||
category = "ПП",
|
||||
})
|
||||
|
||||
DarkRP.createShipment("Дробовик жоский", {
|
||||
model = "models/weapons/w_shot_m3super90.mdl",
|
||||
entity = "qwb_m3super",
|
||||
price = 19000,
|
||||
amount = 10,
|
||||
separate = false,
|
||||
pricesep = nil,
|
||||
noship = false,
|
||||
allowed = {TEAM_GUN},
|
||||
category = "Дробовики",
|
||||
})
|
||||
|
||||
DarkRP.createShipment("Авп симпла", {
|
||||
model = "models/weapons/w_snip_g3sg1.mdl",
|
||||
entity = "qwb_awp",
|
||||
price = 27000,
|
||||
amount = 10,
|
||||
separate = false,
|
||||
pricesep = nil,
|
||||
noship = false,
|
||||
allowed = {TEAM_GUN},
|
||||
category = "Снайперки",
|
||||
})
|
||||
|
||||
DarkRP.createEntity("Принтер с урбапетличка рпг", {
|
||||
ent = "nyxteam_printer",
|
||||
model = "models/props_lab/reciever01a.mdl",
|
||||
price = 3000,
|
||||
max = 2,
|
||||
cmd = "buymoneyprinter"
|
||||
})
|
||||
|
||||
DarkRP.createEntity("Медкит", {
|
||||
ent = "item_healthkit",
|
||||
model = "models/Items/HealthKit.mdl",
|
||||
price = 300,
|
||||
max = 4,
|
||||
cmd = "buymedkit"
|
||||
})
|
||||
|
||||
DarkRP.createEntity("Арморкит", {
|
||||
ent = "item_battery",
|
||||
model = "models/Items/battery.mdl",
|
||||
price = 300,
|
||||
max = 4,
|
||||
cmd = "buyarmorkit"
|
||||
})
|
||||
|
||||
if not DarkRP.disabledDefaults["modules"]["hungermod"] then
|
||||
DarkRP.createEntity("Microwave", {
|
||||
ent = "microwave",
|
||||
model = "models/props/cs_office/microwave.mdl",
|
||||
price = 400,
|
||||
max = 1,
|
||||
cmd = "buymicrowave",
|
||||
allowed = TEAM_COOK
|
||||
})
|
||||
end
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Другое",
|
||||
categorises = "entities",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 255,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Другое",
|
||||
categorises = "shipments",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 255,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "ПП",
|
||||
categorises = "shipments",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 100,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Дробовики",
|
||||
categorises = "shipments",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 101,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Снайперки",
|
||||
categorises = "shipments",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 102,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Пистолеты",
|
||||
categorises = "shipments",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 100,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Другое",
|
||||
categorises = "weapons",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 255,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Другое",
|
||||
categorises = "vehicles",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 255,
|
||||
}
|
||||
43
gamemodes/darkrp/gamemode/config/ammotypes.lua
Normal file
43
gamemodes/darkrp/gamemode/config/ammotypes.lua
Normal file
@@ -0,0 +1,43 @@
|
||||
DarkRP.createAmmoType("pistol", {
|
||||
name = "Пистолетные патроны",
|
||||
model = "models/Items/BoxSRounds.mdl",
|
||||
price = 30,
|
||||
amountGiven = 24
|
||||
})
|
||||
|
||||
DarkRP.createAmmoType("buckshot", {
|
||||
name = "Дробовичные патроны",
|
||||
model = "models/Items/BoxBuckshot.mdl",
|
||||
price = 50,
|
||||
amountGiven = 8
|
||||
})
|
||||
|
||||
DarkRP.createAmmoType("smg1", {
|
||||
name = "ПП патроны",
|
||||
model = "models/Items/BoxMRounds.mdl",
|
||||
price = 60,
|
||||
amountGiven = 30
|
||||
})
|
||||
|
||||
DarkRP.createAmmoType("AR2", {
|
||||
name = "Винтовочные патроны",
|
||||
model = "models/Items/BoxMRounds.mdl",
|
||||
price = 80,
|
||||
amountGiven = 30
|
||||
})
|
||||
|
||||
DarkRP.createAmmoType("357", {
|
||||
name = "Револьверные патроны",
|
||||
model = "models/Items/BoxMRounds.mdl",
|
||||
price = 50,
|
||||
amountGiven = 15
|
||||
})
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Другое",
|
||||
categorises = "ammo",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 255,
|
||||
}
|
||||
542
gamemodes/darkrp/gamemode/config/config.lua
Normal file
542
gamemodes/darkrp/gamemode/config/config.lua
Normal file
@@ -0,0 +1,542 @@
|
||||
--[[-------------------------------------------------------------------------
|
||||
DarkRP config settings.
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
This is the settings file of DarkRP. Every DarkRP setting is listed here.
|
||||
|
||||
Warning:
|
||||
If this file is missing settings (because of e.g. an update), DarkRP will assume default values for these settings.
|
||||
Don't worry about updating this file. If a new setting is added you can manually add them to this file.
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
|
||||
--[[
|
||||
Toggle settings.
|
||||
Set to true or false.
|
||||
]]
|
||||
|
||||
-- voice3D - Enable/disable 3DVoice is enabled.
|
||||
GM.Config.voice3D = true
|
||||
-- AdminsCopWeapons - Enable/disable admins spawning with cop weapons.
|
||||
GM.Config.AdminsCopWeapons = false
|
||||
-- adminBypassJobCustomCheck - Enable/disable whether an admin can force set a job with whenever customCheck returns false.
|
||||
GM.Config.adminBypassJobRestrictions = true
|
||||
-- Acts/Taunts - Enable/disable Taunts (e.g. act salute)
|
||||
GM.Config.allowActs = false
|
||||
-- allow people getting their own custom jobs.
|
||||
GM.Config.allowjobswitch = true
|
||||
-- allowrpnames - Allow Players to Set their RP names using the /rpname command.
|
||||
GM.Config.allowrpnames = true
|
||||
-- allowsprays - Enable/disable the use of sprays on the server.
|
||||
GM.Config.allowsprays = true
|
||||
-- allowvehicleowning - Enable/disable whether people can own vehicles.
|
||||
GM.Config.allowvehicleowning = true
|
||||
-- allowvnocollide - Enable/disable the ability to no-collide a vehicle (for security).
|
||||
GM.Config.allowvnocollide = false
|
||||
-- alltalk - Enable for global chat, disable for local chat.
|
||||
GM.Config.alltalk = false
|
||||
-- antimultirun - Disallow people joining your server(s) twice on the same account.
|
||||
GM.Config.antimultirun = true
|
||||
-- autovehiclelock - Enable/Disable automatic locking of a vehicle when a player exits it.
|
||||
GM.Config.autovehiclelock = false
|
||||
-- babygod - people spawn godded (prevent spawn killing).
|
||||
GM.Config.babygod = true
|
||||
-- canforcedooropen - whether players can force an unownable door open with lockpick or battering ram or w/e.
|
||||
GM.Config.canforcedooropen = true
|
||||
-- chatsounds - sounds are played when some things are said in chat.
|
||||
GM.Config.chatsounds = true
|
||||
-- chiefjailpos - Allow the Chief to set the jail positions.
|
||||
GM.Config.chiefjailpos = false
|
||||
-- cit_propertytax - Enable/disable property tax that is exclusive only for citizens.
|
||||
GM.Config.cit_propertytax = false
|
||||
-- copscanunfreeze - Enable/disable the ability of cops to unfreeze other people's props.
|
||||
GM.Config.copscanunfreeze = true
|
||||
-- copscanunweld - Enable/disable the ability of cops to unweld other people's props.
|
||||
GM.Config.copscanunweld = false
|
||||
-- cpcanarrestcp - Allow/Disallow CPs to arrest other CPs.
|
||||
GM.Config.cpcanarrestcp = true
|
||||
-- currencyLeft - The position of the currency symbol. true for left, false for right.
|
||||
GM.Config.currencyLeft = true
|
||||
-- customjobs - Enable/disable the /job command (personalized job names).
|
||||
GM.Config.customjobs = true
|
||||
-- customspawns - Enable/disable whether custom spawns should be used.
|
||||
GM.Config.customspawns = true
|
||||
-- deathblack - Whether or not a player sees black on death.
|
||||
GM.Config.deathblack = false
|
||||
-- showdeaths - Display kill information in the upper right corner of everyone's screen.
|
||||
GM.Config.showdeaths = true
|
||||
-- deadtalk - Enable/disable whether people talk and use commands while dead.
|
||||
GM.Config.deadtalk = true
|
||||
-- deadvoice - Enable/disable whether people talk through the microphone while dead.
|
||||
GM.Config.deadvoice = true
|
||||
-- deathpov - Enable/disable whether people see their death in first person view.
|
||||
GM.Config.deathpov = false
|
||||
-- decalcleaner - Enable/Disable clearing every player's decals.
|
||||
GM.Config.decalcleaner = false
|
||||
-- disallowClientsideScripts - Clientside scripts can be very useful for customizing the HUD or to aid in building. This option bans those scripts.
|
||||
GM.Config.disallowClientsideScripts = false
|
||||
-- doorwarrants - Enable/disable Warrant requirement to enter property.
|
||||
GM.Config.doorwarrants = true
|
||||
-- dropmoneyondeath - Enable/disable whether people drop money on death.
|
||||
GM.Config.dropmoneyondeath = false
|
||||
-- droppocketarrest - Enable/disable whether people drop the stuff in their pockets when they get arrested.
|
||||
GM.Config.droppocketarrest = false
|
||||
-- droppocketdeath - Enable/disable whether people drop the stuff in their pockets when they die.
|
||||
GM.Config.droppocketdeath = true
|
||||
-- dropweapondeath - Enable/disable whether people drop their current weapon when they die.
|
||||
GM.Config.dropweapondeath = false
|
||||
-- Whether players can drop the weapons they spawn with.
|
||||
GM.Config.dropspawnedweapons = false
|
||||
-- dynamicvoice - Enable/disable whether only people in the same room as you can hear your mic.
|
||||
GM.Config.dynamicvoice = true
|
||||
-- earthquakes - Enable/disable earthquakes.
|
||||
GM.Config.earthquakes = false
|
||||
-- enablebuypistol - Turn /buy on of off.
|
||||
GM.Config.enablebuypistol = true
|
||||
-- enforceplayermodel - Whether or not to force players to use their role-defined character models.
|
||||
GM.Config.enforceplayermodel = true
|
||||
-- globalshow - Whether or not to display player info above players' heads in-game.
|
||||
GM.Config.globalshow = false
|
||||
-- ironshoot - Enable/disable whether people need iron sights to shoot.
|
||||
GM.Config.ironshoot = true
|
||||
-- showjob - Whether or not to display a player's job above their head in-game.
|
||||
GM.Config.showjob = true
|
||||
-- letters - Enable/disable letter writing / typing.
|
||||
GM.Config.letters = true
|
||||
-- license - Enable/disable People need a license to be able to pick up guns.
|
||||
GM.Config.license = false
|
||||
-- lockdown - Enable/Disable initiating lockdowns for mayors.
|
||||
GM.Config.lockdown = true
|
||||
-- lockpickfading - Enable/disable the lockpicking of fading doors.
|
||||
GM.Config.lockpickfading = true
|
||||
-- logging - Enable/disable logging everything that happens.
|
||||
GM.Config.logging = true
|
||||
-- lottery - Enable/disable creating lotteries for mayors.
|
||||
GM.Config.lottery = true
|
||||
-- showname - Whether or not to display a player's name above their head in-game.
|
||||
GM.Config.showname = true
|
||||
-- showhealth - Whether or not to display a player's health above their head in-game.
|
||||
GM.Config.showhealth = true
|
||||
-- needwantedforarrest - Enable/disable Cops can only arrest wanted people.
|
||||
GM.Config.needwantedforarrest = false
|
||||
-- noguns - Enabling this feature bans Guns and Gun Dealers.
|
||||
GM.Config.noguns = false
|
||||
-- norespawn - Enable/Disable that people don't have to respawn when they change job.
|
||||
GM.Config.norespawn = true
|
||||
-- keepPickedUp - Enable/Disable keeping picked up weapons when switching jobs.
|
||||
GM.Config.keepPickedUp = false
|
||||
-- instantjob - Enable/Disable instantly respawning when norespawn is false
|
||||
GM.Config.instantjob = false
|
||||
-- npcarrest - Enable/disable arresting npc's.
|
||||
GM.Config.npcarrest = false
|
||||
-- ooc - Whether or not OOC tags are enabled.
|
||||
GM.Config.ooc = true
|
||||
-- propertytax - Enable/disable property tax.
|
||||
GM.Config.propertytax = false
|
||||
-- proppaying - Whether or not players should pay for spawning props.
|
||||
GM.Config.proppaying = false
|
||||
-- propspawning - Enable/disable props spawning. Applies to admins too.
|
||||
GM.Config.propspawning = true
|
||||
-- removeclassitems - Enable/disable shipments/microwaves/etc. removal when someone changes team.
|
||||
GM.Config.removeclassitems = true
|
||||
-- removeondisconnect - Enable/disable shipments/microwaves/etc. removal when someone disconnects.
|
||||
GM.Config.removeondisconnect = true
|
||||
-- respawninjail - Enable/disable whether people can respawn in jail when they die.
|
||||
GM.Config.respawninjail = true
|
||||
-- restrictallteams - Enable/disable Players can only be citizen until an admin allows them.
|
||||
GM.Config.restrictallteams = false
|
||||
-- restrictbuypistol - Enabling this feature makes /buy available only to Gun Dealers.
|
||||
GM.Config.restrictbuypistol = false
|
||||
-- restrictdrop - Enable/disable restricting the weapons players can drop. Setting this to true disallows weapons from shipments from being dropped.
|
||||
GM.Config.restrictdrop = false
|
||||
-- revokeLicenseOnJobChange - Whether licenses are revoked when a player changes jobs.
|
||||
GM.Config.revokeLicenseOnJobChange = true
|
||||
-- shouldResetLaws - Enable/Disable resetting the laws back to the default law set when the mayor changes.
|
||||
GM.Config.shouldResetLaws = false
|
||||
-- strictsuicide - Whether or not players should spawn where they suicided.
|
||||
GM.Config.strictsuicide = false
|
||||
-- telefromjail - Enable/disable teleporting from jail.
|
||||
GM.Config.telefromjail = true
|
||||
-- teletojail - Enable/disable teleporting to jail.
|
||||
GM.Config.teletojail = true
|
||||
-- unlockdoorsonstart - Enable/Disable unlocking all doors on map start.
|
||||
GM.Config.unlockdoorsonstart = false
|
||||
-- voiceradius - Enable/disable local voice chat.
|
||||
GM.Config.voiceradius = true
|
||||
-- tax - Whether players pay taxes on their wallets.
|
||||
GM.Config.wallettax = false
|
||||
-- wantedrespawn - Whether players remain wanted on respawn.
|
||||
GM.Config.wantedrespawn = false
|
||||
-- wantedsuicide - Enable/Disable suiciding while you are wanted by the police.
|
||||
GM.Config.wantedsuicide = false
|
||||
-- realisticfalldamage - Enable/Disable dynamic fall damage. Setting mp_falldamage to 1 will over-ride this.
|
||||
GM.Config.realisticfalldamage = true
|
||||
-- printeroverheat - Whether the default money printer can overheat on its own.
|
||||
GM.Config.printeroverheat = true
|
||||
-- weaponCheckerHideDefault - Hide default weapons when checking weapons.
|
||||
GM.Config.weaponCheckerHideDefault = true
|
||||
-- weaponCheckerHideNoLicense - Hide weapons that do not require a license.
|
||||
GM.Config.weaponCheckerHideNoLicense = false
|
||||
|
||||
--[[
|
||||
Value settings
|
||||
]]
|
||||
-- adminnpcs - Whether or not NPCs should be admin only. 0 = everyone, 1 = admin or higher, 2 = superadmin or higher, 3 = rcon only
|
||||
GM.Config.adminnpcs = 2
|
||||
-- adminsents - Whether or not SENTs should be admin only. 0 = everyone, 1 = admin or higher, 2 = superadmin or higher, 3 = rcon only
|
||||
GM.Config.adminsents = 2
|
||||
-- adminvehicles - Whether or not vehicles should be admin only. 0 = everyone, 1 = admin or higher, 2 = superadmin or higher, 3 = rcon only
|
||||
GM.Config.adminvehicles = 2
|
||||
-- adminweapons - Who can spawn weapons: 0: admins only, 1: supadmins only, 2: no one, 3: everyone
|
||||
GM.Config.adminweapons = 1
|
||||
-- arrestspeed - Sets the max arrest speed.
|
||||
GM.Config.arrestspeed = 120
|
||||
-- babygodtime - How long the babygod lasts.
|
||||
GM.Config.babygodtime = 5
|
||||
-- chatsoundsdelay - How long to wait before letting a player emit a sound from their chat again.
|
||||
-- Leave this on at least a few seconds to prevent people from spamming sounds. Set to 0 to disable.
|
||||
GM.Config.chatsoundsdelay = 5
|
||||
-- deathfee - the amount of money someone drops when dead.
|
||||
GM.Config.deathfee = 30
|
||||
-- decaltimer - Sets the time to clear clientside decals (in seconds).
|
||||
GM.Config.decaltimer = 120
|
||||
-- demotetime - Number of seconds before a player can rejoin a team after demotion from that team.
|
||||
GM.Config.demotetime = 120
|
||||
-- doorcost - Sets the cost of a door.
|
||||
GM.Config.doorcost = 30
|
||||
-- EntitySpamTime - Antispam time between spawning entities.
|
||||
GM.Config.EntitySpamTime = 2
|
||||
-- entremovedelay - how long to wait before removing a bought entity after disconnect.
|
||||
GM.Config.entremovedelay = 0
|
||||
-- gunlabweapon - The weapon that the gunlab spawns.
|
||||
GM.Config.gunlabweapon = "weapon_p2282"
|
||||
-- jailtimer - Sets the jailtimer (in seconds).
|
||||
GM.Config.jailtimer = 120
|
||||
-- lockdowndelay - The amount of time a mayor must wait before starting the next lockdown.
|
||||
GM.Config.lockdowndelay = 120
|
||||
-- maxadvertbillboards - The maximum number of /advert billboards a player can place.
|
||||
GM.Config.maxadvertbillboards = 3
|
||||
-- maxCheques - The maximum number of cheques someone can write
|
||||
GM.Config.maxCheques = 0
|
||||
-- maxdoors - Sets the max amount of doors one can own.
|
||||
GM.Config.maxdoors = 20
|
||||
-- maxdrugs - Sets max drugs.
|
||||
GM.Config.maxdrugs = 2
|
||||
-- maxfoods - Sets the max food cartons per Microwave owner.
|
||||
GM.Config.maxfoods = 2
|
||||
-- maxfooditems - Sets the max amount of food items a player can buy from the F4 menu.
|
||||
GM.Config.maxfooditems = 20
|
||||
-- maxlawboards - The maximum number of law boards the mayor can place.
|
||||
GM.Config.maxlawboards = 2
|
||||
-- maxletters - Sets max letters.
|
||||
GM.Config.maxletters = 10
|
||||
-- maxlotterycost - Maximum payment the mayor can set to join a lottery.
|
||||
GM.Config.maxlotterycost = 250
|
||||
-- maxvehicles - Sets how many vehicles one can buy.
|
||||
GM.Config.maxvehicles = 5
|
||||
-- microwavefoodcost - Sets the sale price of Microwave Food.
|
||||
GM.Config.microwavefoodcost = 30
|
||||
-- gunlabguncost - Sets the initial price of a gun from a gunlab. Note that the
|
||||
-- gunlab owner can change this price.
|
||||
GM.Config.gunlabguncost = 200
|
||||
-- druglabdrugcost - Sets the initial price of drugs from a drugs lab. Note that
|
||||
-- the drugs lab owner can change this price.
|
||||
GM.Config.druglabdrugcost = 100
|
||||
-- minlotterycost - Minimum payment the mayor can set to join a lottery.
|
||||
GM.Config.minlotterycost = 30
|
||||
-- Money packets will get removed if they don't get picked up after a while. Set to 0 to disable.
|
||||
GM.Config.moneyRemoveTime = 600
|
||||
-- mprintamount - Value of the money printed by the money printer.
|
||||
GM.Config.mprintamount = 250
|
||||
-- normalsalary - Sets the starting salary for newly joined players.
|
||||
GM.Config.normalsalary = 45
|
||||
-- npckillpay - Sets the money given for each NPC kill.
|
||||
GM.Config.npckillpay = 10
|
||||
-- paydelay - Sets how long it takes before people get salary.
|
||||
GM.Config.paydelay = 160
|
||||
-- pocketitems - Sets the amount of objects the pocket can carry.
|
||||
GM.Config.pocketitems = 10
|
||||
-- pricecap - The maximum price of items (using /price).
|
||||
GM.Config.pricecap = 500
|
||||
-- pricemin - The minimum price of items (using /price).
|
||||
GM.Config.pricemin = 50
|
||||
-- propcost - How much prop spawning should cost (prop paying must be enabled for this to have an effect).
|
||||
GM.Config.propcost = 10
|
||||
-- quakechance - Chance of an earthquake happening.
|
||||
GM.Config.quakechance = 4000
|
||||
-- respawntime - Minimum amount of seconds a player has to wait before respawning.
|
||||
GM.Config.respawntime = 1
|
||||
-- changejobtime - Minimum amount of seconds a player has to wait before changing job.
|
||||
GM.Config.changejobtime = 10
|
||||
-- runspeed - Sets the max running speed.
|
||||
GM.Config.runspeed = 240
|
||||
-- runspeed - Sets the max running speed for CP teams.
|
||||
GM.Config.runspeedcp = 255
|
||||
-- searchtime - Number of seconds for which a search warrant is valid.
|
||||
GM.Config.searchtime = 30
|
||||
-- ShipmentSpamTime - Antispam time between spawning shipments.
|
||||
GM.Config.ShipmentSpamTime = 3
|
||||
-- shipmenttime - The number of seconds it takes for a shipment to spawn.
|
||||
GM.Config.shipmentspawntime = 10
|
||||
-- startinghealth - the health when you spawn.
|
||||
GM.Config.startinghealth = 100
|
||||
-- startingmoney - your wallet when you join for the first time.
|
||||
GM.Config.startingmoney = 20000
|
||||
-- stunstickdamage - amount of damage the stunstick will do to entities.
|
||||
-- When between 0 and 1, the damage is relative, where 1 takes the entire health of the entity.
|
||||
-- When above 1, the damage is absolute
|
||||
GM.Config.stunstickdamage = 1000
|
||||
-- vehiclecost - Sets the cost of a vehicle (To own it).
|
||||
GM.Config.vehiclecost = 40
|
||||
-- wallettaxmax - Maximum percentage of tax to be paid.
|
||||
GM.Config.wallettaxmax = 5
|
||||
-- wallettaxmin - Minimum percentage of tax to be paid.
|
||||
GM.Config.wallettaxmin = 1
|
||||
-- wallettaxtime - Time in seconds between taxing players. Requires server restart.
|
||||
GM.Config.wallettaxtime = 600
|
||||
-- wantedtime - Number of seconds for which a player is wanted for.
|
||||
GM.Config.wantedtime = 120
|
||||
-- walkspeed - Sets the max walking speed.
|
||||
GM.Config.walkspeed = 160
|
||||
-- falldamagedamper - The damper on realistic fall damage. Default is 15. Decrease this for more damage.
|
||||
GM.Config.falldamagedamper = 15
|
||||
-- falldamageamount - The base damage taken from falling for static fall damage. Default is 10.
|
||||
GM.Config.falldamageamount = 10
|
||||
-- printeroverheatchance - The likelyhood of a printer overheating. The higher this number, the less likely (minimum 3, default 22).
|
||||
GM.Config.printeroverheatchance = 22
|
||||
-- printerreward - Reward for destroying a money printer.
|
||||
GM.Config.printerreward = 950
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Chat distance settings
|
||||
Distance is in source units (similar to inches)
|
||||
---------------------------------------------------------------------------]]
|
||||
GM.Config.talkDistance = 250
|
||||
GM.Config.whisperDistance = 90
|
||||
GM.Config.yellDistance = 550
|
||||
GM.Config.meDistance = 250
|
||||
GM.Config.voiceDistance = 550
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Other settings
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
-- The classname of money packets. Use this to create your own money entity!
|
||||
-- Note: the money packet must support the "Setamount" method (or the amount DTVar).
|
||||
GM.Config.MoneyClass = "spawned_money"
|
||||
-- In case you do wish to keep the default money, but change the model, this option is the way to go:
|
||||
GM.Config.moneyModel = "models/props/cs_assault/money.mdl"
|
||||
-- You can set your own, custom sound to be played for all players whenever a lockdown is initiated.
|
||||
-- Note: Remember to include the folder where the sound file is located.
|
||||
GM.Config.lockdownsound = "npc/overwatch/cityvoice/f_confirmcivilstatus_1_spkr.wav"
|
||||
|
||||
-- The skin DarkRP uses. Set to "default" to use the GMod default derma theme.
|
||||
GM.Config.DarkRPSkin = "DarkRP"
|
||||
GM.Config.currency = "$"
|
||||
GM.Config.currencyThousandSeparator = ","
|
||||
GM.Config.chatCommandPrefix = "/"
|
||||
GM.Config.F1MenuHelpPage = "https://darkrp.miraheze.org/wiki/Main_Page"
|
||||
GM.Config.F1MenuHelpPageTitle = "DarkRP Wiki"
|
||||
|
||||
-- The sound that plays when you get a DarkRP notification
|
||||
GM.Config.notificationSound = "buttons/lightswitch2.wav"
|
||||
|
||||
-- Put Steam ID's and ranks in this list, and the players will have that rank when they join.
|
||||
GM.Config.DefaultPlayerGroups = {
|
||||
["STEAM_0:0:00000000"] = "superadmin",
|
||||
["STEAM_0:0:11111111"] = "admin",
|
||||
}
|
||||
|
||||
-- Custom modules in this addon that are disabled.
|
||||
GM.Config.DisabledCustomModules = {
|
||||
["hudreplacement"] = false,
|
||||
["extraf4tab"] = false,
|
||||
}
|
||||
|
||||
-- The list of weapons that players are not allowed to drop. Items set to true are not allowed to be dropped.
|
||||
GM.Config.DisallowDrop = {
|
||||
["arrest_stick"] = true,
|
||||
["door_ram"] = true,
|
||||
["gmod_camera"] = true,
|
||||
["gmod_tool"] = true,
|
||||
["keys"] = true,
|
||||
["lockpick"] = true,
|
||||
["med_kit"] = true,
|
||||
["pocket"] = true,
|
||||
["stunstick"] = true,
|
||||
["unarrest_stick"] = true,
|
||||
["weapon_keypadchecker"] = true,
|
||||
["weapon_physcannon"] = true,
|
||||
["weapon_physgun"] = true,
|
||||
["weaponchecker"] = true,
|
||||
}
|
||||
|
||||
-- The list of weapons people spawn with.
|
||||
GM.Config.DefaultWeapons = {
|
||||
"keys",
|
||||
"weapon_physcannon",
|
||||
"gmod_camera",
|
||||
"gmod_tool",
|
||||
"itemstore_pickup",
|
||||
"weapon_physgun",
|
||||
}
|
||||
|
||||
-- Override categories.
|
||||
-- NOTE: categories are to be set in the "category" field of the custom jobs/shipments/entities/ammo/pistols/vehicles.
|
||||
-- Use this only to override the categories of _default_ things.
|
||||
-- This will NOT work for your own custom stuff.
|
||||
-- Make sure the category is created in the darkrp_customthings/categories.lua, otherwise it won't work!
|
||||
GM.Config.CategoryOverride = {
|
||||
-- jobs = {
|
||||
-- ["Citizen"] = "Citizens",
|
||||
-- ["Hobo"] = "Citizens",
|
||||
-- ["Gun Dealer"] = "Citizens",
|
||||
-- ["Medic"] = "Citizens",
|
||||
-- ["Civil Protection"] = "Civil Protection",
|
||||
-- ["Gangster"] = "Gangsters",
|
||||
-- ["Mob boss"] = "Gangsters",
|
||||
-- ["Civil Protection Chief"] = "Civil Protection",
|
||||
-- ["Mayor"] = "Civil Protection",
|
||||
-- },
|
||||
-- entities = {
|
||||
-- ["Drug lab"] = "Other",
|
||||
-- ["Money printer"] = "Other",
|
||||
-- ["Gun lab"] = "Other",
|
||||
|
||||
-- },
|
||||
-- shipments = {
|
||||
-- ["AK47"] = "Rifles",
|
||||
-- ["MP5"] = "Rifles",
|
||||
-- ["M4"] = "Rifles",
|
||||
-- ["Mac 10"] = "Other",
|
||||
-- ["Pump shotgun"] = "Shotguns",
|
||||
-- ["Sniper rifle"] = "Snipers",
|
||||
|
||||
-- },
|
||||
-- weapons = {
|
||||
-- ["Desert eagle"] = "Pistols",
|
||||
-- ["Fiveseven"] = "Pistols",
|
||||
-- ["Glock"] = "Pistols",
|
||||
-- ["P228"] = "Pistols",
|
||||
-- },
|
||||
-- vehicles = {}, -- There are no default vehicles.
|
||||
-- ammo = {
|
||||
-- ["Pistol ammo"] = "Other",
|
||||
-- ["Shotgun ammo"] = "Other",
|
||||
-- ["Rifle ammo"] = "Other",
|
||||
-- },
|
||||
}
|
||||
|
||||
-- The list of weapons admins spawn with, in addition to the default weapons, a job's weapons and GM.Config.AdminsCopWeapons.
|
||||
GM.Config.AdminWeapons = {
|
||||
"weapon_keypadchecker",
|
||||
}
|
||||
|
||||
-- These are the default laws, they're unchangeable in-game.
|
||||
GM.Config.DefaultLaws = {
|
||||
"Нелья пиздиться без причины.",
|
||||
"Нельзя грабить воровать.",
|
||||
"Маники и накрота запрещены.",
|
||||
}
|
||||
|
||||
GM.Config.PocketBlacklist = {
|
||||
["fadmin_jail"] = true,
|
||||
["meteor"] = true,
|
||||
["door"] = true,
|
||||
["func_"] = true,
|
||||
["player"] = true,
|
||||
["beam"] = true,
|
||||
["worldspawn"] = true,
|
||||
["env_"] = true,
|
||||
["path_"] = true,
|
||||
["prop_physics"] = true,
|
||||
["money_printer"] = true,
|
||||
["gunlab"] = true,
|
||||
["prop_dynamic"] = true,
|
||||
["prop_vehicle_prisoner_pod"] = true,
|
||||
["keypad_wire"] = true,
|
||||
["gmod_button"] = true,
|
||||
["gmod_rtcameraprop"] = true,
|
||||
["gmod_cameraprop"] = true,
|
||||
["gmod_dynamite"] = true,
|
||||
["gmod_thruster"] = true,
|
||||
["gmod_light"] = true,
|
||||
["gmod_lamp"] = true,
|
||||
["gmod_emitter"] = true,
|
||||
}
|
||||
|
||||
-- These weapons are classed as 'legal' in the weapon checker and are not stripped when confiscating weapons.
|
||||
-- This setting is used IN ADDITION to GM.Config.weaponCheckerHideDefault and GM.Config.weaponCheckerHideNoLicense.
|
||||
-- You should use the former if you want to class the default weapons (GM.Config.DefaultWeapons and, if admin, GM.Config.AdminWeapons) and a player's job weapons as legal.
|
||||
-- The latter takes GM.NoLicense weapons as legal (see licenseweapons.lua).
|
||||
-- The format of this config is similar to GM.Config.DisallowDrop.
|
||||
GM.Config.noStripWeapons = {
|
||||
|
||||
}
|
||||
|
||||
-- The entities listed here will not be removed when a player changes their job.
|
||||
-- This only applies when removeclassitems is set to true.
|
||||
-- Note: entities will only be removed when the player changes to a job that is not allowed to have the entity.
|
||||
GM.Config.preventClassItemRemoval = {
|
||||
["gunlab"] = false,
|
||||
["microwave"] = false,
|
||||
["spawned_shipment"] = false,
|
||||
}
|
||||
|
||||
-- Properties set to true are allowed to be used. Values set to false or are missing from this list are blocked.
|
||||
GM.Config.allowedProperties = {
|
||||
remover = true,
|
||||
ignite = false,
|
||||
extinguish = true,
|
||||
keepupright = true,
|
||||
gravity = true,
|
||||
collision = true,
|
||||
skin = true,
|
||||
bodygroups = true,
|
||||
}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
F4 menu
|
||||
---------------------------------------------------------------------------]]
|
||||
-- hide the items that you can't buy and the jobs you can't get (instead of graying them out).
|
||||
-- this option hides items when you don't have enough money, when the maximum is reached for a job or any other reason.
|
||||
GM.Config.hideNonBuyable = false
|
||||
|
||||
-- Hide only the items that you have the wrong job for (or for which the customCheck says no).
|
||||
-- When you set this option to true and hideNonBuyable to false, you WILL see e.g. items that are too expensive for you to buy.
|
||||
-- but you won't see gundealer shipments when you have the citizen job.
|
||||
GM.Config.hideTeamUnbuyable = true
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
AFK module
|
||||
---------------------------------------------------------------------------]]
|
||||
-- The time of inactivity before being demoted.
|
||||
GM.Config.afkdemotetime = 600
|
||||
-- Prevent people from spamming AFK.
|
||||
GM.Config.AFKDelay = 300
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Hitmenu module
|
||||
---------------------------------------------------------------------------]]
|
||||
-- The minimum price for a hit.
|
||||
GM.Config.minHitPrice = 200
|
||||
-- The maximum price for a hit.
|
||||
GM.Config.maxHitPrice = 50000
|
||||
-- The minimum distance between a hitman and his customer when they make the deal.
|
||||
GM.Config.minHitDistance = 150
|
||||
-- The text that tells the player he can press use on the hitman to request a hit.
|
||||
GM.Config.hudText = "I am a hitman.\nPress E on me to request a hit!"
|
||||
-- The text above a hitman when he's got a hit.
|
||||
GM.Config.hitmanText = "Hit\naccepted!"
|
||||
-- The cooldown time for a hit target (so they aren't spam killed).
|
||||
GM.Config.hitTargetCooldown = 120
|
||||
-- How long a customer has to wait to be able to buy another hit (from the moment the hit is accepted).
|
||||
GM.Config.hitCustomerCooldown = 240
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Hungermod module
|
||||
---------------------------------------------------------------------------]]
|
||||
-- hungerspeed <Amount> - Set the rate at which players will become hungry (2 is the default).
|
||||
GM.Config.hungerspeed = 2
|
||||
-- starverate <Amount> - How much health that is taken away every second the player is starving (3 is the default).
|
||||
GM.Config.starverate = 3
|
||||
270
gamemodes/darkrp/gamemode/config/jobrelated.lua
Normal file
270
gamemodes/darkrp/gamemode/config/jobrelated.lua
Normal file
@@ -0,0 +1,270 @@
|
||||
-- People often copy jobs. When they do, the GM table does not exist anymore.
|
||||
-- This line makes the job code work both inside and outside of gamemode files.
|
||||
-- You should not copy this line into your code.
|
||||
local GAMEMODE = GAMEMODE or GM
|
||||
--[[--------------------------------------------------------
|
||||
Default teams. Please do not edit this file. Please use the darkrpmod addon instead.
|
||||
--------------------------------------------------------]]
|
||||
TEAM_CITIZEN = DarkRP.createJob("Гражданин", {
|
||||
color = Color(20, 150, 20, 255),
|
||||
model = {
|
||||
"models/Humans/Group02/Player/Tale_01.mdl",
|
||||
"models/Humans/Group02/Player/Tale_03.mdl",
|
||||
"models/Humans/Group02/Player/Tale_04.mdl",
|
||||
"models/Humans/Group02/Player/Tale_05.mdl",
|
||||
"models/Humans/Group02/Player/Tale_06.mdl",
|
||||
"models/Humans/Group02/Player/Tale_07.mdl",
|
||||
"models/Humans/Group02/Player/Tale_08.mdl",
|
||||
"models/Humans/Group02/Player/Tale_09.mdl",
|
||||
|
||||
"models/Humans/Group02/Player/Temale_01.mdl",
|
||||
"models/Humans/Group02/Player/Temale_02.mdl",
|
||||
"models/Humans/Group02/Player/Temale_07.mdl"
|
||||
},
|
||||
description = [[Обычный пясун йобнуца.]],
|
||||
weapons = {},
|
||||
command = "citizen",
|
||||
max = 0,
|
||||
salary = GAMEMODE.Config.normalsalary,
|
||||
admin = 0,
|
||||
vote = false,
|
||||
hasLicense = false,
|
||||
candemote = false,
|
||||
category = "Гражданские",
|
||||
})
|
||||
|
||||
TEAM_POLICE = DarkRP.createJob("Мент", {
|
||||
color = Color(25, 25, 170, 255),
|
||||
model = {"models/player/police.mdl", "models/player/police_fem.mdl"},
|
||||
description = [[Мусорок йобнуца.]],
|
||||
weapons = {"arrest_stick", "unarrest_stick", "qwb_glock18", "stunstick", "door_ram", "weaponchecker"},
|
||||
command = "cp",
|
||||
max = 4,
|
||||
salary = GAMEMODE.Config.normalsalary * 1.45,
|
||||
admin = 0,
|
||||
vote = true,
|
||||
hasLicense = true,
|
||||
ammo = {
|
||||
["pistol"] = 60,
|
||||
},
|
||||
category = "Менты",
|
||||
})
|
||||
|
||||
TEAM_GANG = DarkRP.createJob("Гангстер", {
|
||||
color = Color(75, 75, 75, 255),
|
||||
model = {
|
||||
"models/player/Group03/Female_01.mdl",
|
||||
"models/player/Group03/Female_02.mdl",
|
||||
"models/player/Group03/Female_03.mdl",
|
||||
"models/player/Group03/Female_04.mdl",
|
||||
"models/player/Group03/Female_06.mdl",
|
||||
"models/player/group03/male_01.mdl",
|
||||
"models/player/Group03/Male_02.mdl",
|
||||
"models/player/Group03/male_03.mdl",
|
||||
"models/player/Group03/Male_04.mdl",
|
||||
"models/player/Group03/Male_05.mdl",
|
||||
"models/player/Group03/Male_06.mdl",
|
||||
"models/player/Group03/Male_07.mdl",
|
||||
"models/player/Group03/Male_08.mdl",
|
||||
"models/player/Group03/Male_09.mdl"},
|
||||
description = [[Бандит чтобы с ментами пиздиться.]],
|
||||
weapons = {},
|
||||
command = "gangster",
|
||||
max = 3,
|
||||
salary = GAMEMODE.Config.normalsalary,
|
||||
admin = 0,
|
||||
vote = false,
|
||||
hasLicense = false,
|
||||
category = "Бандиты",
|
||||
})
|
||||
|
||||
TEAM_MOB = DarkRP.createJob("Пахан", {
|
||||
color = Color(25, 25, 25, 255),
|
||||
model = "models/player/gman_high.mdl",
|
||||
description = [[Заправляет гангстерами.]],
|
||||
weapons = {"lockpick", "unarrest_stick"},
|
||||
command = "mobboss",
|
||||
max = 1,
|
||||
salary = GAMEMODE.Config.normalsalary * 1.34,
|
||||
admin = 0,
|
||||
vote = false,
|
||||
hasLicense = false,
|
||||
category = "Бандиты",
|
||||
})
|
||||
|
||||
TEAM_GUN = DarkRP.createJob("Продавец оружия", {
|
||||
color = Color(255, 140, 0, 255),
|
||||
model = "models/player/monk.mdl",
|
||||
description = [[Продает пушки доброградовские.]],
|
||||
weapons = {},
|
||||
command = "gundealer",
|
||||
max = 2,
|
||||
salary = GAMEMODE.Config.normalsalary,
|
||||
admin = 0,
|
||||
vote = false,
|
||||
hasLicense = false,
|
||||
category = "Гражданские",
|
||||
})
|
||||
|
||||
TEAM_MEDIC = DarkRP.createJob("Медик", {
|
||||
color = Color(47, 79, 79, 255),
|
||||
model = "models/player/kleiner.mdl",
|
||||
description = [[Юзлесс профа тут но похуй пусть будет.]],
|
||||
weapons = {"med_kit"},
|
||||
command = "medic",
|
||||
max = 3,
|
||||
salary = GAMEMODE.Config.normalsalary,
|
||||
admin = 0,
|
||||
vote = false,
|
||||
hasLicense = false,
|
||||
medic = true,
|
||||
category = "Гражданские",
|
||||
})
|
||||
|
||||
TEAM_CHIEF = DarkRP.createJob("Глава ментов", {
|
||||
color = Color(20, 20, 255, 255),
|
||||
model = "models/player/combine_soldier_prisonguard.mdl",
|
||||
description = [[Управляешь мусорками.]],
|
||||
weapons = {"arrest_stick", "unarrest_stick", "qwb_ump45", "stunstick", "door_ram", "weaponchecker"},
|
||||
command = "chief",
|
||||
max = 1,
|
||||
salary = GAMEMODE.Config.normalsalary * 1.67,
|
||||
admin = 0,
|
||||
vote = false,
|
||||
hasLicense = true,
|
||||
chief = true,
|
||||
NeedToChangeFrom = TEAM_POLICE,
|
||||
ammo = {
|
||||
["pistol"] = 60,
|
||||
},
|
||||
category = "Менты",
|
||||
})
|
||||
|
||||
TEAM_MAYOR = DarkRP.createJob("Мэр", {
|
||||
color = Color(150, 20, 20, 255),
|
||||
model = "models/player/breen.mdl",
|
||||
description = [[Создаешь законы и прочую хуйню делаешь]],
|
||||
weapons = {},
|
||||
command = "mayor",
|
||||
max = 1,
|
||||
salary = GAMEMODE.Config.normalsalary * 1.89,
|
||||
admin = 0,
|
||||
vote = true,
|
||||
hasLicense = false,
|
||||
mayor = true,
|
||||
category = "Менты",
|
||||
})
|
||||
|
||||
TEAM_HOBO = DarkRP.createJob("Бомж", {
|
||||
color = Color(80, 45, 0, 255),
|
||||
model = "models/player/corpse1.mdl",
|
||||
description = [[Вам сасет сенвай хотябы ладно]],
|
||||
weapons = {"weapon_bugbait"},
|
||||
command = "hobo",
|
||||
max = 5,
|
||||
salary = 0,
|
||||
admin = 0,
|
||||
vote = false,
|
||||
hasLicense = false,
|
||||
candemote = false,
|
||||
hobo = true,
|
||||
category = "Гражданские",
|
||||
})
|
||||
|
||||
if not DarkRP.disabledDefaults["modules"]["hungermod"] then
|
||||
TEAM_COOK = DarkRP.createJob("Cook", {
|
||||
color = Color(238, 99, 99, 255),
|
||||
model = "models/player/mossman.mdl",
|
||||
description = [[As a cook, it is your responsibility to feed the other members of your city.
|
||||
You can spawn a microwave and sell the food you make:
|
||||
/buymicrowave]],
|
||||
weapons = {},
|
||||
command = "cook",
|
||||
max = 2,
|
||||
salary = 45,
|
||||
admin = 0,
|
||||
vote = false,
|
||||
hasLicense = false,
|
||||
cook = true
|
||||
})
|
||||
end
|
||||
|
||||
-- Compatibility for when default teams are disabled
|
||||
TEAM_CITIZEN = TEAM_CITIZEN or -1
|
||||
TEAM_POLICE = TEAM_POLICE or -1
|
||||
TEAM_GANG = TEAM_GANG or -1
|
||||
TEAM_MOB = TEAM_MOB or -1
|
||||
TEAM_GUN = TEAM_GUN or -1
|
||||
TEAM_MEDIC = TEAM_MEDIC or -1
|
||||
TEAM_CHIEF = TEAM_CHIEF or -1
|
||||
TEAM_MAYOR = TEAM_MAYOR or -1
|
||||
TEAM_HOBO = TEAM_HOBO or -1
|
||||
TEAM_COOK = TEAM_COOK or -1
|
||||
|
||||
-- Door groups
|
||||
AddDoorGroup("Менты", TEAM_CHIEF, TEAM_POLICE, TEAM_MAYOR)
|
||||
AddDoorGroup("Оружейник", TEAM_GUN)
|
||||
|
||||
|
||||
-- Agendas
|
||||
DarkRP.createAgenda("Гангстерская повестка", TEAM_MOB, {TEAM_GANG})
|
||||
DarkRP.createAgenda("Ментовская повестка", {TEAM_MAYOR, TEAM_CHIEF}, {TEAM_POLICE})
|
||||
|
||||
-- Group chats
|
||||
DarkRP.createGroupChat(function(ply) return ply:isCP() end)
|
||||
DarkRP.createGroupChat(TEAM_MOB, TEAM_GANG)
|
||||
DarkRP.createGroupChat(function(listener, ply) return not ply or ply:Team() == listener:Team() end)
|
||||
|
||||
-- Initial team when first spawning
|
||||
GAMEMODE.DefaultTeam = TEAM_CITIZEN
|
||||
|
||||
-- Teams that belong to Менты
|
||||
GAMEMODE.CivilProtection = {
|
||||
[TEAM_POLICE] = true,
|
||||
[TEAM_CHIEF] = true,
|
||||
[TEAM_MAYOR] = true,
|
||||
}
|
||||
|
||||
-- Hitman team
|
||||
-- DarkRP.addHitmanTeam(TEAM_MOB)
|
||||
|
||||
-- Demote groups
|
||||
DarkRP.createDemoteGroup("Менты", {TEAM_POLICE, TEAM_CHIEF})
|
||||
DarkRP.createDemoteGroup("Гангстеры", {TEAM_GANG, TEAM_MOB})
|
||||
|
||||
-- Default categories
|
||||
DarkRP.createCategory{
|
||||
name = "Гражданские",
|
||||
categorises = "jobs",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 100,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Менты",
|
||||
categorises = "jobs",
|
||||
startExpanded = true,
|
||||
color = Color(25, 25, 170, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 101,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Бандиты",
|
||||
categorises = "jobs",
|
||||
startExpanded = true,
|
||||
color = Color(75, 75, 75, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 101,
|
||||
}
|
||||
|
||||
DarkRP.createCategory{
|
||||
name = "Другие",
|
||||
categorises = "jobs",
|
||||
startExpanded = true,
|
||||
color = Color(0, 107, 0, 255),
|
||||
canSee = fp{fn.Id, true},
|
||||
sortOrder = 255,
|
||||
}
|
||||
5
gamemodes/darkrp/gamemode/config/licenseweapons.lua
Normal file
5
gamemodes/darkrp/gamemode/config/licenseweapons.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
GM.NoLicense["weapon_physcannon"] = true
|
||||
GM.NoLicense["weapon_physgun"] = true
|
||||
GM.NoLicense["weapon_bugbait"] = true
|
||||
GM.NoLicense["gmod_tool"] = true
|
||||
GM.NoLicense["gmod_camera"] = true
|
||||
114
gamemodes/darkrp/gamemode/init.lua
Normal file
114
gamemodes/darkrp/gamemode/init.lua
Normal file
@@ -0,0 +1,114 @@
|
||||
hook.Run("DarkRPStartedLoading")
|
||||
|
||||
GM.Version = "2.7.0"
|
||||
GM.Name = "DarkRP"
|
||||
GM.Author = "By FPtje Falco et al."
|
||||
|
||||
DeriveGamemode("sandbox")
|
||||
DEFINE_BASECLASS("gamemode_sandbox")
|
||||
|
||||
GM.Sandbox = BaseClass
|
||||
|
||||
|
||||
AddCSLuaFile("libraries/sh_cami.lua")
|
||||
AddCSLuaFile("libraries/simplerr.lua")
|
||||
AddCSLuaFile("libraries/interfaceloader.lua")
|
||||
AddCSLuaFile("libraries/modificationloader.lua")
|
||||
AddCSLuaFile("libraries/disjointset.lua")
|
||||
AddCSLuaFile("libraries/fn.lua")
|
||||
AddCSLuaFile("libraries/tablecheck.lua")
|
||||
|
||||
AddCSLuaFile("config/config.lua")
|
||||
AddCSLuaFile("config/addentities.lua")
|
||||
AddCSLuaFile("config/jobrelated.lua")
|
||||
AddCSLuaFile("config/ammotypes.lua")
|
||||
AddCSLuaFile("config/licenseweapons.lua")
|
||||
|
||||
AddCSLuaFile("cl_init.lua")
|
||||
|
||||
GM.Config = GM.Config or {}
|
||||
GM.NoLicense = GM.NoLicense or {}
|
||||
|
||||
include("libraries/interfaceloader.lua")
|
||||
|
||||
include("config/_MySQL.lua")
|
||||
include("config/config.lua")
|
||||
include("config/licenseweapons.lua")
|
||||
|
||||
include("libraries/fn.lua")
|
||||
include("libraries/tablecheck.lua")
|
||||
include("libraries/sh_cami.lua")
|
||||
include("libraries/simplerr.lua")
|
||||
include("libraries/modificationloader.lua")
|
||||
include("libraries/mysqlite/mysqlite.lua")
|
||||
include("libraries/disjointset.lua")
|
||||
|
||||
resource.AddFile("materials/vgui/entities/arrest_stick.vmt")
|
||||
resource.AddFile("materials/vgui/entities/door_ram.vmt")
|
||||
resource.AddFile("materials/vgui/entities/keys.vmt")
|
||||
resource.AddFile("materials/vgui/entities/lockpick.vmt")
|
||||
resource.AddFile("materials/vgui/entities/ls_sniper.vmt")
|
||||
resource.AddFile("materials/vgui/entities/med_kit.vmt")
|
||||
resource.AddFile("materials/vgui/entities/pocket.vmt")
|
||||
resource.AddFile("materials/vgui/entities/stunstick.vmt")
|
||||
resource.AddFile("materials/vgui/entities/unarrest_stick.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_ak472.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_deagle2.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_fiveseven2.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_glock2.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_keypadchecker.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_m42.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_mac102.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_mp52.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_p2282.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weapon_pumpshotgun2.vmt")
|
||||
resource.AddFile("materials/vgui/entities/weaponchecker.vmt")
|
||||
|
||||
|
||||
hook.Call("DarkRPPreLoadModules", GM)
|
||||
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Loading modules
|
||||
---------------------------------------------------------------------------]]
|
||||
local fol = GM.FolderName .. "/gamemode/modules/"
|
||||
local files, folders = file.Find(fol .. "*", "LUA")
|
||||
local SortedPairs = SortedPairs
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
if DarkRP.disabledDefaults["modules"][v:Left(-5)] then continue end
|
||||
if string.GetExtensionFromFilename(v) ~= "lua" then continue end
|
||||
include(fol .. v)
|
||||
end
|
||||
|
||||
for _, folder in SortedPairs(folders, true) do
|
||||
if folder == "." or folder == ".." or DarkRP.disabledDefaults["modules"][folder] then continue end
|
||||
|
||||
for _, File in SortedPairs(file.Find(fol .. folder .. "/sh_*.lua", "LUA"), true) do
|
||||
if File == "sh_interface.lua" then continue end
|
||||
AddCSLuaFile(fol .. folder .. "/" .. File)
|
||||
include(fol .. folder .. "/" .. File)
|
||||
end
|
||||
|
||||
for _, File in SortedPairs(file.Find(fol .. folder .. "/sv_*.lua", "LUA"), true) do
|
||||
if File == "sv_interface.lua" then continue end
|
||||
include(fol .. folder .. "/" .. File)
|
||||
end
|
||||
|
||||
for _, File in SortedPairs(file.Find(fol .. folder .. "/cl_*.lua", "LUA"), true) do
|
||||
if File == "cl_interface.lua" then continue end
|
||||
AddCSLuaFile(fol .. folder .. "/" .. File)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
DarkRP.DARKRP_LOADING = true
|
||||
include("config/jobrelated.lua")
|
||||
include("config/addentities.lua")
|
||||
include("config/ammotypes.lua")
|
||||
DarkRP.DARKRP_LOADING = nil
|
||||
|
||||
DarkRP.finish()
|
||||
|
||||
hook.Call("DarkRPFinishedLoading", GM)
|
||||
MySQLite.initialize()
|
||||
90
gamemodes/darkrp/gamemode/libraries/disjointset.lua
Normal file
90
gamemodes/darkrp/gamemode/libraries/disjointset.lua
Normal file
@@ -0,0 +1,90 @@
|
||||
/*---------------------------------------------------------------------------
|
||||
Disjoint-set forest implementation
|
||||
Inspired by the book Introduction To Algorithms (third edition)
|
||||
|
||||
by FPtje Atheos
|
||||
|
||||
Running time per operation (Union/FindSet): O(a(n)) where a is the inverse of the Ackermann function.
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
local ipairs = ipairs
|
||||
local setmetatable = setmetatable
|
||||
local string = string
|
||||
local table = table
|
||||
local tostring = tostring
|
||||
|
||||
module("disjoint")
|
||||
|
||||
local metatable
|
||||
|
||||
-- Make a singleton set. Parent parameter is optional, must be a disjoint-set as well.
|
||||
function MakeSet(x, parent)
|
||||
local set = {}
|
||||
set.value = x
|
||||
set.rank = 0
|
||||
set.parent = parent or set
|
||||
|
||||
setmetatable(set, metatable)
|
||||
|
||||
return set
|
||||
end
|
||||
|
||||
local function Link(x, y)
|
||||
if x == y then return x end
|
||||
|
||||
-- Union by rank
|
||||
if x.rank > y.rank then
|
||||
y.parent = x
|
||||
return x
|
||||
end
|
||||
|
||||
x.parent = y
|
||||
|
||||
if x.rank == y.rank then
|
||||
y.rank = y.rank + 1
|
||||
end
|
||||
|
||||
return y
|
||||
end
|
||||
|
||||
-- Apply the union operation between two sets.
|
||||
function Union(x, y)
|
||||
return Link(FindSet(x), FindSet(y))
|
||||
end
|
||||
|
||||
function FindSet(x)
|
||||
local parent = x
|
||||
local listParents
|
||||
|
||||
-- Go up the tree to find the parent
|
||||
while parent ~= parent.parent do
|
||||
parent = parent.parent
|
||||
|
||||
listParents = listParents or {}
|
||||
table.insert(listParents, parent)
|
||||
end
|
||||
|
||||
-- Path compression, update all parents to refer to the top parent
|
||||
if listParents then
|
||||
for _, v in ipairs(listParents) do
|
||||
v.parent = parent
|
||||
end
|
||||
end
|
||||
|
||||
return parent
|
||||
end
|
||||
|
||||
function Disconnect(x)
|
||||
x.parent = x
|
||||
|
||||
return x
|
||||
end
|
||||
|
||||
|
||||
metatable = {
|
||||
__tostring = function(self)
|
||||
return string.format("Disjoint-Set [value: %s][Rank: %s][Parent: %s]", tostring(self.value), self.rank, tostring(self.parent.value))
|
||||
end,
|
||||
__metatable = true, -- restrict access to metatable
|
||||
__add = Union
|
||||
}
|
||||
337
gamemodes/darkrp/gamemode/libraries/fn.lua
Normal file
337
gamemodes/darkrp/gamemode/libraries/fn.lua
Normal file
@@ -0,0 +1,337 @@
|
||||
/*---------------------------------------------------------------------------
|
||||
Functional library
|
||||
|
||||
by FPtje Atheos
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Function currying
|
||||
Take a function with n parameters.
|
||||
Currying is the procedure of storing k < n parameters "in the function"
|
||||
in such a way that the remaining function can be called with n - k parameters
|
||||
|
||||
Example:
|
||||
DebugPrint = fp{print, "[DEBUG]"}
|
||||
DebugPrint("TEST")
|
||||
> [DEBUG] TEST
|
||||
---------------------------------------------------------------------------*/
|
||||
function fp(tbl)
|
||||
local func = tbl[1]
|
||||
|
||||
return function(...)
|
||||
local fnArgs = {}
|
||||
local arg = {...}
|
||||
local tblN = table.maxn(tbl)
|
||||
|
||||
for i = 2, tblN do fnArgs[i - 1] = tbl[i] end
|
||||
for i = 1, table.maxn(arg) do fnArgs[tblN + i - 1] = arg[i] end
|
||||
|
||||
return func(unpack(fnArgs, 1, table.maxn(fnArgs)))
|
||||
end
|
||||
end
|
||||
|
||||
local unpack = unpack
|
||||
local table = table
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local error = error
|
||||
local math = math
|
||||
local select = select
|
||||
local type = type
|
||||
local _G = _G
|
||||
local fp = fp
|
||||
|
||||
|
||||
module("fn")
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Parameter manipulation
|
||||
---------------------------------------------------------------------------*/
|
||||
Id = function(...) return ... end
|
||||
|
||||
Flip = function(f)
|
||||
if not f then error("not a function") end
|
||||
return function(b, a, ...)
|
||||
return f(a, b, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- Definition from http://lua-users.org/wiki/CurriedLua
|
||||
ReverseArgs = function(...)
|
||||
|
||||
--reverse args by building a function to do it, similar to the unpack() example
|
||||
local function reverse_h(acc, v, ...)
|
||||
if select('#', ...) == 0 then
|
||||
return v, acc()
|
||||
else
|
||||
return reverse_h(function () return v, acc() end, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- initial acc is the end of the list
|
||||
return reverse_h(function () return end, ...)
|
||||
end
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Misc functions
|
||||
---------------------------------------------------------------------------*/
|
||||
-- function composition
|
||||
do
|
||||
local function comp_h(a, b, ...)
|
||||
if b == nil then return a end
|
||||
b = comp_h(b, ...)
|
||||
return function(...)
|
||||
return a(b(...))
|
||||
end
|
||||
end
|
||||
Compose = function(funcs, ...)
|
||||
if type(funcs) == "table" then
|
||||
return comp_h(unpack(funcs))
|
||||
else
|
||||
return comp_h(funcs, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_G.fc = Compose
|
||||
|
||||
-- Definition from http://lua-users.org/wiki/CurriedLua
|
||||
Curry = function(func, num_args)
|
||||
if not num_args then error("Missing argument #2: num_args") end
|
||||
if not func then error("Function does not exist!", 2) end
|
||||
-- helper
|
||||
local function curry_h(argtrace, n)
|
||||
if n == 0 then
|
||||
-- reverse argument list and call function
|
||||
return func(ReverseArgs(argtrace()))
|
||||
else
|
||||
-- "push" argument (by building a wrapper function) and decrement n
|
||||
return function(x)
|
||||
return curry_h(function() return x, argtrace() end, n - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- no sense currying for 1 arg or less
|
||||
if num_args > 1 then
|
||||
return curry_h(function() return end, num_args)
|
||||
else
|
||||
return func
|
||||
end
|
||||
end
|
||||
|
||||
-- Thanks Lexic!
|
||||
Partial = function(func, ...)
|
||||
local args = {...}
|
||||
return function(...)
|
||||
return func(unpack(table.Add( args, {...})))
|
||||
end
|
||||
end
|
||||
|
||||
Apply = function(f, ...) return f(...) end
|
||||
|
||||
Const = function(a, b) return a end
|
||||
Until = function(cmp, fn, val)
|
||||
if cmp(val) then
|
||||
return val
|
||||
end
|
||||
return Until(cmp, fn, fn(val))
|
||||
end
|
||||
|
||||
Seq = function(f, x) f(x) return x end
|
||||
|
||||
GetGlobalVar = function(key) return _G[key] end
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Mathematical operators and functions
|
||||
---------------------------------------------------------------------------*/
|
||||
Add = function(a, b) return a + b end
|
||||
Sub = function(a, b) return a - b end
|
||||
Mul = function(a, b) return a * b end
|
||||
Div = function(a, b) return a / b end
|
||||
Mod = function(a, b) return a % b end
|
||||
Neg = function(a) return -a end
|
||||
|
||||
Eq = function(a, b) return a == b end
|
||||
Neq = function(a, b) return a ~= b end
|
||||
Gt = function(a, b) return a > b end
|
||||
Lt = function(a, b) return a < b end
|
||||
Gte = function(a, b) return a >= b end
|
||||
Lte = function(a, b) return a <= b end
|
||||
|
||||
Succ = Compose{Add, 1}
|
||||
Pred = Compose{Flip(Sub), 1}
|
||||
Even = Compose{fp{Eq, 0}, fp{Flip(Mod), 2}}
|
||||
Odd = Compose{Not, Even}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Functional logical operators and conditions
|
||||
---------------------------------------------------------------------------*/
|
||||
FAnd = function(fns)
|
||||
return function(...)
|
||||
local val
|
||||
for _, f in pairs(fns) do
|
||||
val = {f(...)}
|
||||
if not val[1] then return unpack(val) end
|
||||
end
|
||||
if val then return unpack(val) end
|
||||
end
|
||||
end
|
||||
|
||||
FOr = function(fns)
|
||||
return function(...)
|
||||
local val
|
||||
for _, f in pairs(fns) do
|
||||
val = {f(...)}
|
||||
if val[1] then return unpack(val) end
|
||||
end
|
||||
return false, unpack(val, 2)
|
||||
end
|
||||
end
|
||||
|
||||
Not = function(x) return not x end
|
||||
|
||||
If = function(f, Then, Else)
|
||||
return function(x)
|
||||
if f(x) then
|
||||
return Then
|
||||
else
|
||||
return Else
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
List operations
|
||||
---------------------------------------------------------------------------*/
|
||||
Map = function(f, xs)
|
||||
for k, v in pairs(xs) do
|
||||
xs[k] = f(v)
|
||||
end
|
||||
return xs
|
||||
end
|
||||
|
||||
Append = function(xs, ys)
|
||||
return table.Add(xs, ys)
|
||||
end
|
||||
|
||||
Filter = function(f, xs)
|
||||
local res = {}
|
||||
for k,v in pairs(xs) do
|
||||
if f(v) then res[k] = v end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
ForEach = function(f, xs)
|
||||
for k,v in pairs(xs) do
|
||||
local val = f(k, v)
|
||||
if val ~= nil then return val end
|
||||
end
|
||||
end
|
||||
|
||||
Head = function(xs)
|
||||
return table.GetFirstValue(xs)
|
||||
end
|
||||
|
||||
Last = function(xs)
|
||||
return xs[#xs] or table.GetLastValue(xs)
|
||||
end
|
||||
|
||||
Tail = function(xs)
|
||||
table.remove(xs, 1)
|
||||
return xs
|
||||
end
|
||||
|
||||
Init = function(xs)
|
||||
xs[#xs] = nil
|
||||
return xs
|
||||
end
|
||||
|
||||
GetValue = function(i, xs)
|
||||
return xs[i]
|
||||
end
|
||||
|
||||
Null = function(xs)
|
||||
for k, v in pairs(xs) do
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Length = function(xs)
|
||||
return #xs
|
||||
end
|
||||
|
||||
Index = function(xs, i)
|
||||
return xs[i]
|
||||
end
|
||||
|
||||
Reverse = function(xs)
|
||||
local res = {}
|
||||
for i = #xs, 1, -1 do
|
||||
res[#xs - i + 1] = xs[i]
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Folds
|
||||
---------------------------------------------------------------------------*/
|
||||
Foldr = function(func, val, xs)
|
||||
for i = #xs, 1, -1 do
|
||||
val = func(xs[i], val)
|
||||
end
|
||||
|
||||
return val
|
||||
end
|
||||
|
||||
Foldl = function(func, val, xs)
|
||||
for k, v in ipairs(xs) do
|
||||
val = func(val, v)
|
||||
end
|
||||
|
||||
return val
|
||||
end
|
||||
|
||||
And = function(xs)
|
||||
for k, v in pairs(xs) do
|
||||
if v ~= true then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Or = function(xs)
|
||||
for k, v in pairs(xs) do
|
||||
if v == true then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
Any = function(func, xs)
|
||||
for k, v in pairs(xs) do
|
||||
if func(v) == true then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
All = function(func, xs)
|
||||
for k, v in pairs(xs) do
|
||||
if func(v) ~= true then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Sum = _G.fp{Foldr, Add, 0}
|
||||
|
||||
Product = _G.fp{Foldr, Mul, 1}
|
||||
|
||||
Concat = _G.fp{Foldr, Append, {}}
|
||||
|
||||
Maximum = _G.fp{Foldl, math.Max, -math.huge}
|
||||
|
||||
Minimum = _G.fp{Foldl, math.Min, math.huge}
|
||||
|
||||
Snd = _G.fp{select, 2}
|
||||
|
||||
Thrd = _G.fp{select, 3}
|
||||
210
gamemodes/darkrp/gamemode/libraries/interfaceloader.lua
Normal file
210
gamemodes/darkrp/gamemode/libraries/interfaceloader.lua
Normal file
@@ -0,0 +1,210 @@
|
||||
module("DarkRP", package.seeall)
|
||||
|
||||
MetaName = "DarkRP"
|
||||
|
||||
-- Variables that maintain the existing stubs and hooks
|
||||
local stubs = {}
|
||||
local hookStubs = {}
|
||||
|
||||
-- Contains the functions that the hooks call by default
|
||||
hooks = {}
|
||||
|
||||
-- Delay the calling of methods until the functions are implemented
|
||||
local delayedCalls = {}
|
||||
|
||||
local returnsLayout, isreturns
|
||||
local parameterLayout, isparameters
|
||||
local isdeprecated
|
||||
local checkStub
|
||||
|
||||
local hookLayout
|
||||
|
||||
local realm -- State variable to manage the realm of the stubs
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Methods that check whether certain fields are valid
|
||||
---------------------------------------------------------------------------]]
|
||||
isreturns = function(tbl)
|
||||
if not istable(tbl) then return false end
|
||||
for _, v in pairs(tbl) do
|
||||
if not checkStub(v, returnsLayout) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
isparameters = function(tbl)
|
||||
if not istable(tbl) then return false end
|
||||
for _, v in pairs(tbl) do
|
||||
if not checkStub(v, parameterLayout) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
isdeprecated = function(val)
|
||||
return val == nil or isstring(val)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
The layouts of stubs
|
||||
---------------------------------------------------------------------------]]
|
||||
local stubLayout = {
|
||||
name = isstring,
|
||||
description = isstring,
|
||||
deprecated = isdeprecated,
|
||||
parameters = isparameters, -- the parameters of a method
|
||||
returns = isreturns, -- the return values of a method
|
||||
metatable = istable -- DarkRP, Player, Entity, Vector, ...
|
||||
}
|
||||
|
||||
hookLayout = {
|
||||
name = isstring,
|
||||
description = isstring,
|
||||
deprecated = isdeprecated,
|
||||
parameters = isreturns, -- doesn't have the 'optional' field
|
||||
returns = isreturns,
|
||||
}
|
||||
|
||||
returnsLayout = {
|
||||
name = isstring,
|
||||
description = isstring,
|
||||
type = isstring
|
||||
}
|
||||
|
||||
parameterLayout = {
|
||||
name = isstring,
|
||||
description = isstring,
|
||||
type = isstring,
|
||||
optional = isbool
|
||||
}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Check the validity of a stub
|
||||
---------------------------------------------------------------------------]]
|
||||
checkStub = function(tbl, stub)
|
||||
if not istable(tbl) then return false, "table" end
|
||||
|
||||
for name, check in pairs(stub) do
|
||||
if not check(tbl[name]) then
|
||||
return false, name
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
When a stub is called, the calling of the method is delayed
|
||||
---------------------------------------------------------------------------]]
|
||||
local function notImplemented(name, args, thisFunc)
|
||||
if stubs[name] and stubs[name].metatable[name] ~= thisFunc then -- when calling the not implemented function after the function was implemented
|
||||
return stubs[name].metatable[name](unpack(args))
|
||||
end
|
||||
table.insert(delayedCalls, {name = name, args = args})
|
||||
|
||||
return nil -- no return value because the method is not implemented
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Generate a stub
|
||||
---------------------------------------------------------------------------]]
|
||||
function stub(tbl)
|
||||
local isStub, field = checkStub(tbl, stubLayout)
|
||||
if not isStub then
|
||||
error("Invalid DarkRP method stub! Field \"" .. field .. "\" is invalid!", 2)
|
||||
end
|
||||
|
||||
tbl.realm = tbl.realm or realm
|
||||
stubs[tbl.name] = tbl
|
||||
|
||||
local function retNotImpl(...)
|
||||
return notImplemented(tbl.name, {...}, retNotImpl)
|
||||
end
|
||||
|
||||
return retNotImpl
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Generate a hook stub
|
||||
---------------------------------------------------------------------------]]
|
||||
function hookStub(tbl)
|
||||
local isStub, field = checkStub(tbl, hookLayout)
|
||||
if not isStub then
|
||||
error("Invalid DarkRP hook! Field \"" .. field .. "\" is invalid!", 2)
|
||||
end
|
||||
|
||||
tbl.realm = tbl.realm or realm
|
||||
hookStubs[tbl.name] = tbl
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve the stubs
|
||||
---------------------------------------------------------------------------]]
|
||||
function getStubs()
|
||||
return table.Copy(stubs)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve the hooks
|
||||
---------------------------------------------------------------------------]]
|
||||
function getHooks()
|
||||
return table.Copy(hookStubs)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Call the cached methods
|
||||
---------------------------------------------------------------------------]]
|
||||
function finish()
|
||||
local calls = table.Copy(delayedCalls) -- Loop through a copy, so the notImplemented function doesn't get called again
|
||||
for _, tbl in ipairs(calls) do
|
||||
local name = tbl.name
|
||||
|
||||
if not stubs[name] then ErrorNoHalt("Calling non-existing stub \"" .. name .. "\"") continue end
|
||||
|
||||
stubs[name].metatable[name](unpack(tbl.args))
|
||||
end
|
||||
|
||||
delayedCalls = {}
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Load the interface files
|
||||
---------------------------------------------------------------------------]]
|
||||
local function loadInterfaces()
|
||||
local root = GM.FolderName .. "/gamemode/modules"
|
||||
|
||||
local _, folders = file.Find(root .. "/*", "LUA")
|
||||
|
||||
ENTITY = FindMetaTable("Entity")
|
||||
PLAYER = FindMetaTable("Player")
|
||||
VECTOR = FindMetaTable("Vector")
|
||||
|
||||
for _, folder in SortedPairs(folders, true) do
|
||||
local interfacefile = string.format("%s/%s/%s_interface.lua", root, folder, "%s")
|
||||
local client = string.format(interfacefile, "cl")
|
||||
local shared = string.format(interfacefile, "sh")
|
||||
local server = string.format(interfacefile, "sv")
|
||||
|
||||
if file.Exists(shared, "LUA") then
|
||||
if SERVER then AddCSLuaFile(shared) end
|
||||
realm = "Shared"
|
||||
include(shared)
|
||||
end
|
||||
|
||||
if SERVER and file.Exists(client, "LUA") then
|
||||
AddCSLuaFile(client)
|
||||
end
|
||||
|
||||
if SERVER and file.Exists(server, "LUA") then
|
||||
realm = "Server"
|
||||
include(server)
|
||||
end
|
||||
|
||||
if CLIENT and file.Exists(client, "LUA") then
|
||||
realm = "Client"
|
||||
include(client)
|
||||
end
|
||||
end
|
||||
|
||||
ENTITY, PLAYER, VECTOR = nil, nil, nil
|
||||
end
|
||||
loadInterfaces()
|
||||
152
gamemodes/darkrp/gamemode/libraries/modificationloader.lua
Normal file
152
gamemodes/darkrp/gamemode/libraries/modificationloader.lua
Normal file
@@ -0,0 +1,152 @@
|
||||
-- Modification loader.
|
||||
-- Dependencies:
|
||||
-- - fn
|
||||
-- - simplerr
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Disabled defaults
|
||||
---------------------------------------------------------------------------]]
|
||||
DarkRP.disabledDefaults = {}
|
||||
DarkRP.disabledDefaults["modules"] = {
|
||||
["afk"] = true,
|
||||
["chatsounds"] = false,
|
||||
["events"] = false,
|
||||
["fpp"] = false,
|
||||
["hitmenu"] = false,
|
||||
["hud"] = false,
|
||||
["hungermod"] = true,
|
||||
["playerscale"] = false,
|
||||
["sleep"] = false,
|
||||
}
|
||||
|
||||
DarkRP.disabledDefaults["agendas"] = {}
|
||||
DarkRP.disabledDefaults["ammo"] = {}
|
||||
DarkRP.disabledDefaults["demotegroups"] = {}
|
||||
DarkRP.disabledDefaults["doorgroups"] = {}
|
||||
DarkRP.disabledDefaults["entities"] = {}
|
||||
DarkRP.disabledDefaults["food"] = {}
|
||||
DarkRP.disabledDefaults["groupchat"] = {}
|
||||
DarkRP.disabledDefaults["hitmen"] = {}
|
||||
DarkRP.disabledDefaults["jobs"] = {}
|
||||
DarkRP.disabledDefaults["shipments"] = {}
|
||||
DarkRP.disabledDefaults["vehicles"] = {}
|
||||
DarkRP.disabledDefaults["workarounds"] = {}
|
||||
|
||||
-- The client cannot use simplerr.runLuaFile because of restrictions in GMod.
|
||||
local doInclude = CLIENT and include or fc{simplerr.wrapError, simplerr.wrapLog, simplerr.runFile}
|
||||
|
||||
if file.Exists("darkrp_config/disabled_defaults.lua", "LUA") then
|
||||
if SERVER then AddCSLuaFile("darkrp_config/disabled_defaults.lua") end
|
||||
doInclude("darkrp_config/disabled_defaults.lua")
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Config
|
||||
---------------------------------------------------------------------------]]
|
||||
local configFiles = {
|
||||
"darkrp_config/settings.lua",
|
||||
"darkrp_config/licenseweapons.lua",
|
||||
}
|
||||
|
||||
for _, File in ipairs(configFiles) do
|
||||
if not file.Exists(File, "LUA") then continue end
|
||||
|
||||
if SERVER then AddCSLuaFile(File) end
|
||||
doInclude(File)
|
||||
end
|
||||
if SERVER and file.Exists("darkrp_config/mysql.lua", "LUA") then doInclude("darkrp_config/mysql.lua") end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Modules
|
||||
---------------------------------------------------------------------------]]
|
||||
local function loadModules()
|
||||
local fol = "darkrp_modules/"
|
||||
|
||||
local _, folders = file.Find(fol .. "*", "LUA")
|
||||
|
||||
for _, folder in SortedPairs(folders, true) do
|
||||
if folder == "." or folder == ".." or GAMEMODE.Config.DisabledCustomModules[folder] then continue end
|
||||
-- Sound but incomplete way of detecting the error of putting addons in the darkrpmod folder
|
||||
if file.Exists(fol .. folder .. "/addon.txt", "LUA") or file.Exists(fol .. folder .. "/addon.json", "LUA") then
|
||||
DarkRP.errorNoHalt("Addon detected in the darkrp_modules folder.", 2, {
|
||||
"This addon is not supposed to be in the darkrp_modules folder.",
|
||||
"It is supposed to be in garrysmod/addons/ instead.",
|
||||
"Whether a mod is to be installed in darkrp_modules or addons is the author's decision.",
|
||||
"Please read the readme of the addons you're installing next time."
|
||||
},
|
||||
"<darkrpmod addon>/lua/darkrp_modules/" .. folder, -1)
|
||||
continue
|
||||
end
|
||||
|
||||
for _, File in SortedPairs(file.Find(fol .. folder .. "/sh_*.lua", "LUA"), true) do
|
||||
if SERVER then
|
||||
AddCSLuaFile(fol .. folder .. "/" .. File)
|
||||
end
|
||||
|
||||
if File == "sh_interface.lua" then continue end
|
||||
doInclude(fol .. folder .. "/" .. File)
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
for _, File in SortedPairs(file.Find(fol .. folder .. "/sv_*.lua", "LUA"), true) do
|
||||
if File == "sv_interface.lua" then continue end
|
||||
doInclude(fol .. folder .. "/" .. File)
|
||||
end
|
||||
end
|
||||
|
||||
for _, File in SortedPairs(file.Find(fol .. folder .. "/cl_*.lua", "LUA"), true) do
|
||||
if File == "cl_interface.lua" then continue end
|
||||
|
||||
if SERVER then
|
||||
AddCSLuaFile(fol .. folder .. "/" .. File)
|
||||
else
|
||||
doInclude(fol .. folder .. "/" .. File)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function loadLanguages()
|
||||
local fol = "darkrp_language/"
|
||||
|
||||
local files, _ = file.Find(fol .. "*", "LUA")
|
||||
for _, File in ipairs(files) do
|
||||
if SERVER then AddCSLuaFile(fol .. File) end
|
||||
doInclude(fol .. File)
|
||||
end
|
||||
end
|
||||
|
||||
local customFiles = {
|
||||
"darkrp_customthings/jobs.lua",
|
||||
"darkrp_customthings/shipments.lua",
|
||||
"darkrp_customthings/entities.lua",
|
||||
"darkrp_customthings/vehicles.lua",
|
||||
"darkrp_customthings/food.lua",
|
||||
"darkrp_customthings/ammo.lua",
|
||||
"darkrp_customthings/groupchats.lua",
|
||||
"darkrp_customthings/categories.lua",
|
||||
"darkrp_customthings/agendas.lua", -- has to be run after jobs.lua
|
||||
"darkrp_customthings/doorgroups.lua", -- has to be run after jobs.lua
|
||||
"darkrp_customthings/demotegroups.lua", -- has to be run after jobs.lua
|
||||
}
|
||||
local function loadCustomDarkRPItems()
|
||||
for _, File in ipairs(customFiles) do
|
||||
if not file.Exists(File, "LUA") then continue end
|
||||
if File == "darkrp_customthings/food.lua" and DarkRP.disabledDefaults["modules"]["hungermod"] then continue end
|
||||
|
||||
if SERVER then AddCSLuaFile(File) end
|
||||
doInclude(File)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function GM:DarkRPFinishedLoading()
|
||||
-- GAMEMODE gets set after the last statement in the gamemode files is run. That is not the case in this hook
|
||||
GAMEMODE = GAMEMODE or GM
|
||||
|
||||
loadLanguages()
|
||||
loadModules()
|
||||
loadCustomDarkRPItems()
|
||||
hook.Call("loadCustomDarkRPItems", self)
|
||||
hook.Call("postLoadCustomDarkRPItems", self)
|
||||
end
|
||||
519
gamemodes/darkrp/gamemode/libraries/mysqlite/mysqlite.lua
Normal file
519
gamemodes/darkrp/gamemode/libraries/mysqlite/mysqlite.lua
Normal file
@@ -0,0 +1,519 @@
|
||||
--[[
|
||||
MySQLite - Abstraction mechanism for SQLite and MySQL
|
||||
|
||||
Why use this?
|
||||
- Easy to use interface for MySQL
|
||||
- No need to modify code when switching between SQLite and MySQL
|
||||
- Queued queries: execute a bunch of queries in order an run the callback when all queries are done
|
||||
|
||||
License: LGPL V2.1 (read here: https://www.gnu.org/licenses/lgpl-2.1.html)
|
||||
|
||||
Supported MySQL modules:
|
||||
- MySQLOO
|
||||
- tmysql4
|
||||
|
||||
Note: When both MySQLOO and tmysql4 modules are installed, MySQLOO is used by default.
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Documentation
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
MySQLite.initialize([config :: table]) :: No value
|
||||
Initialize MySQLite. Loads the config from either the config parameter OR the MySQLite_config global.
|
||||
This loads the module (if necessary) and connects to the MySQL database (if set up).
|
||||
The config must have this layout:
|
||||
{
|
||||
EnableMySQL :: Bool - set to true to use MySQL, false for SQLite
|
||||
Host :: String - database hostname
|
||||
Username :: String - database username
|
||||
Password :: String - database password (keep away from clients!)
|
||||
Database_name :: String - name of the database
|
||||
Database_port :: Number - connection port (3306 by default)
|
||||
Preferred_module :: String - Preferred module, case sensitive, must be either "mysqloo" or "tmysql4"
|
||||
MultiStatements :: Bool - Only available in tmysql4: allow multiple SQL statements per query
|
||||
}
|
||||
|
||||
----------------------------- Utility functions -----------------------------
|
||||
MySQLite.isMySQL() :: Bool
|
||||
Returns whether MySQLite is set up to use MySQL. True for MySQL, false for SQLite.
|
||||
Use this when the query syntax between SQLite and MySQL differs (example: AUTOINCREMENT vs AUTO_INCREMENT)
|
||||
|
||||
MySQLite.SQLStr(str :: String) :: String
|
||||
Escapes the string and puts it in quotes.
|
||||
It uses the escaping method of the module that is currently being used.
|
||||
|
||||
MySQLite.tableExists(tbl :: String, callback :: function, errorCallback :: function)
|
||||
Checks whether table tbl exists.
|
||||
|
||||
callback format: function(res :: Bool)
|
||||
res is a boolean indicating whether the table exists.
|
||||
|
||||
The errorCallback format is the same as in MySQLite.query.
|
||||
|
||||
----------------------------- Running queries -----------------------------
|
||||
MySQLite.query(sqlText :: String, callback :: function, errorCallback :: function) :: No value
|
||||
Runs a query. Calls the callback parameter when finished, calls errorCallback when an error occurs.
|
||||
|
||||
callback format:
|
||||
function(result :: table, lastInsert :: number)
|
||||
Result is the table with results (nil when there are no results or when the result list is empty)
|
||||
lastInsert is the row number of the last inserted value (use with AUTOINCREMENT)
|
||||
|
||||
Note: lastInsert is NOT supported when using SQLite.
|
||||
|
||||
errorCallback format:
|
||||
function(error :: String, query :: String) :: Bool
|
||||
error is the error given by the database module.
|
||||
query is the query that triggered the error.
|
||||
|
||||
Return true to suppress the error!
|
||||
|
||||
MySQLite.queryValue(sqlText :: String, callback :: function, errorCallback :: function) :: No value
|
||||
Runs a query and returns the first value it comes across.
|
||||
|
||||
callback format:
|
||||
function(result :: any)
|
||||
where the result is either a string or a number, depending on the requested database field.
|
||||
|
||||
The errorCallback format is the same as in MySQLite.query.
|
||||
|
||||
MySQLite.prepare(sqlText :: String, sqlParams :: Table, callback :: function, errorCallback :: function)
|
||||
Calls a prepared statement, faster for queries that are ran multiple times. Params do not need to be escaped.
|
||||
This does not work with SQLite, and will instead call a regular query
|
||||
|
||||
callback format:
|
||||
function(result :: table, lastInsert :: number, rowsChanged :: number)
|
||||
Result is the table with results (nil when there are no results or when the result list is empty)
|
||||
lastInsert is the row number of the last inserted value (use with AUTOINCREMENT)
|
||||
rowsChanged is the amount of rows that were changed with the query
|
||||
|
||||
Note: lastInsert is NOT supported when using SQLite.
|
||||
|
||||
The errorCallback format is the same as in MySQLite.query.
|
||||
|
||||
Example:
|
||||
MySQLite.prepare("UPDATE `darkrp_player` SET `rpname` = ? WHERE `rpname` = ?", { "players_old_name", "players_new_name"}, success_callback, error_callback)
|
||||
|
||||
----------------------------- Transactions -----------------------------
|
||||
MySQLite.begin() :: No value
|
||||
Starts a transaction. Use in combination with MySQLite.queueQuery and MySQLite.commit.
|
||||
|
||||
MySQLite.queueQuery(sqlText :: String, callback :: function, errorCallback :: function) :: No value
|
||||
Queues a query in the transaction. Note: a transaction must be started with MySQLite.begin() for this to work.
|
||||
The callback will be called when this specific query has been executed successfully.
|
||||
The errorCallback function will be called when an error occurs in this specific query.
|
||||
|
||||
See MySQLite.query for the callback and errorCallback format.
|
||||
|
||||
MySQLite.commit(onFinished)
|
||||
Commits a transaction and calls onFinished when EVERY queued query has finished.
|
||||
onFinished is NOT called when an error occurs in one of the queued queries.
|
||||
|
||||
onFinished is called without arguments.
|
||||
|
||||
----------------------------- Hooks -----------------------------
|
||||
DatabaseInitialized
|
||||
Called when a successful connection to the database has been made.
|
||||
]]
|
||||
|
||||
local debug = debug
|
||||
local error = error
|
||||
local ErrorNoHalt = ErrorNoHalt
|
||||
local hook = hook
|
||||
local pairs = pairs
|
||||
local require = require
|
||||
local sql = sql
|
||||
local string = string
|
||||
local table = table
|
||||
local timer = timer
|
||||
local tostring = tostring
|
||||
local mysqlOO
|
||||
local TMySQL
|
||||
local _G = _G
|
||||
local ipairs = ipairs
|
||||
local type = type
|
||||
local unpack = unpack
|
||||
|
||||
local multistatements
|
||||
|
||||
local MySQLite_config = MySQLite_config or RP_MySQLConfig or FPP_MySQLConfig
|
||||
local moduleLoaded
|
||||
|
||||
local function loadMySQLModule()
|
||||
if moduleLoaded or not MySQLite_config or not MySQLite_config.EnableMySQL then return end
|
||||
|
||||
local moo, tmsql = util.IsBinaryModuleInstalled("mysqloo"), util.IsBinaryModuleInstalled("tmysql4")
|
||||
|
||||
if not moo and not tmsql then
|
||||
error("Could not find a suitable MySQL module. Please either:\n - Install tmysql. It can be obtained from https://github.com/SuperiorServers/gm_tmysql4\n - Install MySQLOO. It can be obtained from https://github.com/FredyH/MySQLOO\nDue to this error, MySQL is disabled. This means that SQLite is used instead to store data.")
|
||||
end
|
||||
moduleLoaded = true
|
||||
|
||||
require(
|
||||
moo and tmsql and MySQLite_config.Preferred_module or
|
||||
moo and "mysqloo" or
|
||||
tmsql and "tmysql4"
|
||||
)
|
||||
|
||||
multistatements = CLIENT_MULTI_STATEMENTS
|
||||
|
||||
mysqlOO = mysqloo
|
||||
TMySQL = tmysql
|
||||
|
||||
if MySQLite_config.Preferred_module == "tmysql4" then
|
||||
if not tmsql then
|
||||
ErrorNoHalt("The preferred module for MySQL is selected to be tmysql4. However, tmysql4 does not appear to be installed. Please either:\n - Install tmysql. It can be obtained from https://github.com/SuperiorServers/gm_tmysql4\n - Select MySQLOO as the preferred module for MySQL. MySQLOO appears to be installed.")
|
||||
return
|
||||
end
|
||||
|
||||
if not tmysql.Version or tmysql.Version < 4.1 then
|
||||
MsgC(Color(255, 0, 0), "Using older tmysql version, please consider updating!\n")
|
||||
MsgC(Color(255, 0, 0), "Newer Version: https://github.com/SuperiorServers/gm_tmysql4\n")
|
||||
end
|
||||
|
||||
-- Turns tmysql.Connect into tmysql.Initialize if they're using an older version.
|
||||
TMySQL.Connect = tmysql.Version and tmysql.Version >= 4.1 and TMySQL.Connect or TMySQL.initialize
|
||||
TMySQL.SetOption = tmysql.Version and tmysql.Version >= 4.1 and TMySQL.SetOption or TMySQL.Option
|
||||
else
|
||||
if not moo then
|
||||
ErrorNoHalt("The preferred module for MySQL is selected to be MySQLOO. However, MySQLOO does not appear to be installed. Please either:\n - Install MySQLOO. It can be obtained from https://github.com/FredyH/MySQLOO\n - Select tmysql4 as the preferred module for MySQL. tmysql4 appears to be installed.")
|
||||
end
|
||||
end
|
||||
end
|
||||
loadMySQLModule()
|
||||
|
||||
module("MySQLite")
|
||||
|
||||
-- Helper function to return the first value found when iterating over a table.
|
||||
-- Replaces the now deprecated table.GetFirstValue
|
||||
local function arbitraryTableValue(tbl)
|
||||
for _, v in pairs(tbl) do return v end
|
||||
end
|
||||
|
||||
function initialize(config)
|
||||
MySQLite_config = config or MySQLite_config
|
||||
|
||||
if not MySQLite_config then
|
||||
ErrorNoHalt("Warning: No MySQL config!")
|
||||
end
|
||||
|
||||
loadMySQLModule()
|
||||
|
||||
if MySQLite_config.EnableMySQL then
|
||||
connectToMySQL(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port)
|
||||
else
|
||||
timer.Simple(0, function()
|
||||
_G.GAMEMODE.DatabaseInitialized = _G.GAMEMODE.DatabaseInitialized or function() end
|
||||
hook.Call("DatabaseInitialized", _G.GAMEMODE)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local CONNECTED_TO_MYSQL = false
|
||||
local msOOConnect
|
||||
databaseObject = nil
|
||||
|
||||
local queuedQueries
|
||||
local cachedQueries
|
||||
|
||||
function isMySQL()
|
||||
return CONNECTED_TO_MYSQL
|
||||
end
|
||||
|
||||
function begin()
|
||||
if not CONNECTED_TO_MYSQL then
|
||||
sql.Begin()
|
||||
else
|
||||
if queuedQueries then
|
||||
debug.Trace()
|
||||
error("Transaction ongoing!")
|
||||
end
|
||||
queuedQueries = {}
|
||||
end
|
||||
end
|
||||
|
||||
function commit(onFinished)
|
||||
if not CONNECTED_TO_MYSQL then
|
||||
sql.Commit()
|
||||
if onFinished then onFinished() end
|
||||
return
|
||||
end
|
||||
|
||||
if not queuedQueries then
|
||||
error("No queued queries! Call begin() first!")
|
||||
end
|
||||
|
||||
if #queuedQueries == 0 then
|
||||
queuedQueries = nil
|
||||
if onFinished then onFinished() end
|
||||
return
|
||||
end
|
||||
|
||||
-- Copy the table so other scripts can create their own queue
|
||||
local queue = table.Copy(queuedQueries)
|
||||
queuedQueries = nil
|
||||
|
||||
-- Handle queued queries in order
|
||||
local queuePos = 0
|
||||
local call
|
||||
|
||||
-- Recursion invariant: queuePos > 0 and queue[queuePos] <= #queue
|
||||
call = function(...)
|
||||
queuePos = queuePos + 1
|
||||
|
||||
if queue[queuePos].callback then
|
||||
queue[queuePos].callback(...)
|
||||
end
|
||||
|
||||
-- Base case, end of the queue
|
||||
if queuePos + 1 > #queue then
|
||||
if onFinished then onFinished() end -- All queries have finished
|
||||
return
|
||||
end
|
||||
|
||||
-- Recursion
|
||||
local nextQuery = queue[queuePos + 1]
|
||||
query(nextQuery.query, call, nextQuery.onError)
|
||||
end
|
||||
|
||||
query(queue[1].query, call, queue[1].onError)
|
||||
end
|
||||
|
||||
function queueQuery(sqlText, callback, errorCallback)
|
||||
if CONNECTED_TO_MYSQL then
|
||||
table.insert(queuedQueries, {query = sqlText, callback = callback, onError = errorCallback})
|
||||
return
|
||||
end
|
||||
-- SQLite is instantaneous, simply running the query is equal to queueing it
|
||||
query(sqlText, callback, errorCallback)
|
||||
end
|
||||
|
||||
local function msOOQuery(sqlText, callback, errorCallback, queryValue)
|
||||
local queryObject = databaseObject:query(sqlText)
|
||||
local data
|
||||
queryObject.onData = function(Q, D)
|
||||
data = data or {}
|
||||
data[#data + 1] = D
|
||||
end
|
||||
|
||||
queryObject.onError = function(Q, E)
|
||||
if databaseObject:status() == mysqlOO.DATABASE_NOT_CONNECTED then
|
||||
table.insert(cachedQueries, {sqlText, callback, queryValue})
|
||||
|
||||
-- Immediately try reconnecting
|
||||
msOOConnect(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port)
|
||||
return
|
||||
end
|
||||
|
||||
local supp = errorCallback and errorCallback(E, sqlText)
|
||||
if not supp then error(E .. " (" .. sqlText .. ")") end
|
||||
end
|
||||
|
||||
queryObject.onSuccess = function()
|
||||
local res = queryValue and data and data[1] and arbitraryTableValue(data[1]) or not queryValue and data or nil
|
||||
if callback then callback(res, queryObject:lastInsert()) end
|
||||
end
|
||||
queryObject:start()
|
||||
end
|
||||
|
||||
local preparedStatements = {}
|
||||
local paramTypes = {}
|
||||
paramTypes["number"] = function(queryObj, paramIndex, paramValue) return queryObj:setNumber(paramIndex, paramValue) end
|
||||
paramTypes["string"] = function(queryObj, paramIndex, paramValue) return queryObj:setString(paramIndex, paramValue) end
|
||||
paramTypes["boolean"] = function(queryObj, paramIndex, paramValue) return queryObj:setBoolean(paramIndex, paramValue) end
|
||||
|
||||
local function msOOPrepare(sqlText, sqlParams, callback, errorCallback)
|
||||
local queryObject
|
||||
|
||||
if preparedStatements[sqlText] then
|
||||
queryObject = preparedStatements[sqlText]
|
||||
else
|
||||
queryObject = databaseObject:prepare(sqlText)
|
||||
preparedStatements[sqlText] = queryObject
|
||||
end
|
||||
|
||||
for i, param in ipairs(sqlParams) do
|
||||
local paramType = type(param)
|
||||
|
||||
if paramTypes[paramType] then
|
||||
paramTypes[paramType](queryObject, i, param)
|
||||
else
|
||||
queryObject:setString(i, param)
|
||||
end
|
||||
end
|
||||
|
||||
queryObject.onError = function(_, E)
|
||||
local supp = errorCallback and errorCallback(E, sqlText)
|
||||
if not supp then error(E .. " (" .. sqlText .. ")") end
|
||||
end
|
||||
|
||||
queryObject.onSuccess = function(_, data)
|
||||
if callback then callback(data, queryObject:lastInsert(), queryObject:affectedRows()) end
|
||||
end
|
||||
|
||||
queryObject:start()
|
||||
end
|
||||
|
||||
local function tmsqlPrepare(sqlText, sqlParams, callback, errorCallback)
|
||||
local queryObject
|
||||
|
||||
if preparedStatements[sqlText] then
|
||||
queryObject = preparedStatements[sqlText]
|
||||
else
|
||||
queryObject = databaseObject:Prepare(sqlText)
|
||||
preparedStatements[sqlText] = queryObject
|
||||
end
|
||||
|
||||
for i, param in ipairs(sqlParams) do
|
||||
param = SQLStr(param, true)
|
||||
end
|
||||
|
||||
local varcount = queryObject:GetArgCount()
|
||||
|
||||
sqlParams[varcount + 1] = function(results)
|
||||
if results[1].error ~= nil then
|
||||
local supp = errorCallback and errorCallback(E, results[1].error)
|
||||
if not supp then error(E .. " (" .. results[1].error .. ")") end
|
||||
end
|
||||
|
||||
if callback then callback(data, results[1].lastid, results[1].affected) end
|
||||
end
|
||||
|
||||
queryObject:Run(unpack(sqlParams, 1, varcount + 2))
|
||||
end
|
||||
|
||||
local function tmsqlQuery(sqlText, callback, errorCallback, queryValue)
|
||||
local call = function(res)
|
||||
res = res[1] -- For now only support one result set
|
||||
if not res.status then
|
||||
local supp = errorCallback and errorCallback(res.error, sqlText)
|
||||
if not supp then error(res.error .. " (" .. sqlText .. ")") end
|
||||
return
|
||||
end
|
||||
|
||||
if not res.data or #res.data == 0 then res.data = nil end -- compatibility with other backends
|
||||
if queryValue and callback then return callback(res.data and res.data[1] and arbitraryTableValue(res.data[1]) or nil) end
|
||||
if callback then callback(res.data, res.lastid) end
|
||||
end
|
||||
|
||||
databaseObject:Query(sqlText, call)
|
||||
end
|
||||
|
||||
local function SQLiteQuery(sqlText, callback, errorCallback, queryValue)
|
||||
sql.m_strError = "" -- reset last error
|
||||
|
||||
local lastError = sql.LastError()
|
||||
local Result = queryValue and sql.QueryValue(sqlText) or sql.Query(sqlText)
|
||||
|
||||
if sql.LastError() and sql.LastError() ~= lastError then
|
||||
local err = sql.LastError()
|
||||
local supp = errorCallback and errorCallback(err, sqlText)
|
||||
if supp == false then error(err .. " (" .. sqlText .. ")", 2) end
|
||||
return
|
||||
end
|
||||
|
||||
if callback then callback(Result) end
|
||||
return Result
|
||||
end
|
||||
|
||||
-- SQLite doesn't support preparedStatements, so convert it to a regular query.
|
||||
local function SQLitePrepare(sqlText, sqlParams, callback, errorCallback)
|
||||
for _, param in ipairs(sqlParams) do
|
||||
sqlText = sqlText:gsub("%?", sql.SQLStr(param), 1)
|
||||
end
|
||||
|
||||
return SQLiteQuery(sqlText, callback, errorCallback, false)
|
||||
end
|
||||
|
||||
function query(sqlText, callback, errorCallback)
|
||||
local qFunc = (CONNECTED_TO_MYSQL and ((mysqlOO and msOOQuery) or (TMySQL and tmsqlQuery))) or SQLiteQuery
|
||||
return qFunc(sqlText, callback, errorCallback, false)
|
||||
end
|
||||
|
||||
function queryValue(sqlText, callback, errorCallback)
|
||||
local qFunc = (CONNECTED_TO_MYSQL and ((mysqlOO and msOOQuery) or (TMySQL and tmsqlQuery))) or SQLiteQuery
|
||||
return qFunc(sqlText, callback, errorCallback, true)
|
||||
end
|
||||
|
||||
function prepare(sqlText, sqlParams, callback, errorCallback)
|
||||
local qFunc = (CONNECTED_TO_MYSQL and ((mysqlOO and msOOPrepare) or (TMySQL and tmsqlPrepare))) or SQLitePrepare
|
||||
return qFunc(sqlText, sqlParams, callback, errorCallback, false)
|
||||
end
|
||||
|
||||
local function onConnected()
|
||||
CONNECTED_TO_MYSQL = true
|
||||
|
||||
-- Run the queries that were called before the connection was made
|
||||
for k, v in pairs(cachedQueries or {}) do
|
||||
cachedQueries[k] = nil
|
||||
if v[3] then
|
||||
queryValue(v[1], v[2])
|
||||
else
|
||||
query(v[1], v[2])
|
||||
end
|
||||
end
|
||||
cachedQueries = {}
|
||||
local GM = _G.GAMEMODE or _G.GM
|
||||
|
||||
hook.Call("DatabaseInitialized", GM.DatabaseInitialized and GM or nil)
|
||||
|
||||
end
|
||||
|
||||
msOOConnect = function(host, username, password, database_name, database_port)
|
||||
databaseObject = mysqlOO.connect(host, username, password, database_name, database_port)
|
||||
|
||||
if timer.Exists("darkrp_check_mysql_status") then timer.Remove("darkrp_check_mysql_status") end
|
||||
|
||||
databaseObject.onConnectionFailed = function(_, msg)
|
||||
timer.Simple(5, function()
|
||||
msOOConnect(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port)
|
||||
end)
|
||||
error("Connection failed! " .. tostring(msg) .. "\nTrying again in 5 seconds.")
|
||||
end
|
||||
|
||||
databaseObject.onConnected = onConnected
|
||||
|
||||
databaseObject:connect()
|
||||
end
|
||||
|
||||
local function tmsqlConnect(host, username, password, database_name, database_port)
|
||||
local db, err = TMySQL.Connect(host, username, password, database_name, database_port, nil, MySQLite_config.MultiStatements and multistatements or nil)
|
||||
if err then error("Connection failed! " .. err .. "\n") end
|
||||
|
||||
databaseObject = db
|
||||
onConnected()
|
||||
|
||||
if (TMySQL.Version and TMySQL.Version >= 4.1) then
|
||||
hook.Add("Think", "MySQLite:tmysqlPoll", function()
|
||||
db:Poll()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function connectToMySQL(host, username, password, database_name, database_port)
|
||||
database_port = database_port or 3306
|
||||
local func = mysqlOO and msOOConnect or TMySQL and tmsqlConnect or function() end
|
||||
func(host, username, password, database_name, database_port)
|
||||
end
|
||||
|
||||
function SQLStr(sqlStr)
|
||||
local escape =
|
||||
not CONNECTED_TO_MYSQL and sql.SQLStr or
|
||||
mysqlOO and function(str) return "\"" .. databaseObject:escape(tostring(str)) .. "\"" end or
|
||||
TMySQL and function(str) return "\"" .. databaseObject:Escape(tostring(str)) .. "\"" end
|
||||
|
||||
return escape(sqlStr)
|
||||
end
|
||||
|
||||
function tableExists(tbl, callback, errorCallback)
|
||||
if not CONNECTED_TO_MYSQL then
|
||||
local exists = sql.TableExists(tbl)
|
||||
callback(exists)
|
||||
|
||||
return exists
|
||||
end
|
||||
|
||||
queryValue(string.format("SHOW TABLES LIKE %s", SQLStr(tbl)), function(v)
|
||||
callback(v ~= nil)
|
||||
end, errorCallback)
|
||||
end
|
||||
362
gamemodes/darkrp/gamemode/libraries/sh_cami.lua
Normal file
362
gamemodes/darkrp/gamemode/libraries/sh_cami.lua
Normal file
@@ -0,0 +1,362 @@
|
||||
--[[
|
||||
CAMI - Common Admin Mod Interface.
|
||||
Copyright 2020 CAMI Contributors
|
||||
|
||||
Makes admin mods intercompatible and provides an abstract privilege interface
|
||||
for third party addons.
|
||||
|
||||
Follows the specification on this page:
|
||||
https://github.com/glua/CAMI/blob/master/README.md
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
|
||||
-- Version number in YearMonthDay format.
|
||||
local version = 20211019
|
||||
|
||||
if CAMI and CAMI.Version >= version then return end
|
||||
|
||||
CAMI = CAMI or {}
|
||||
CAMI.Version = version
|
||||
|
||||
|
||||
--- @class CAMI_USERGROUP
|
||||
--- defines the charactaristics of a usergroup
|
||||
--- @field Name string @The name of the usergroup
|
||||
--- @field Inherits string @The name of the usergroup this usergroup inherits from
|
||||
--- @field CAMI_Source string @The source specified by the admin mod which registered this usergroup (if any, converted to a string)
|
||||
|
||||
--- @class CAMI_PRIVILEGE
|
||||
--- defines the charactaristics of a privilege
|
||||
--- @field Name string @The name of the privilege
|
||||
--- @field MinAccess "'user'" | "'admin'" | "'superadmin'" @Default group that should have this privilege
|
||||
--- @field Description string | nil @Optional text describing the purpose of the privilege
|
||||
local CAMI_PRIVILEGE = {}
|
||||
--- Optional function to check if a player has access to this privilege
|
||||
--- (and optionally execute it on another player)
|
||||
---
|
||||
--- ⚠ **Warning**: This function may not be called by all admin mods
|
||||
--- @param actor GPlayer @The player
|
||||
--- @param target GPlayer | nil @Optional - the target
|
||||
--- @return boolean @If they can or not
|
||||
--- @return string | nil @Optional reason
|
||||
function CAMI_PRIVILEGE:HasAccess(actor, target)
|
||||
end
|
||||
|
||||
--- Contains the registered CAMI_USERGROUP usergroup structures.
|
||||
--- Indexed by usergroup name.
|
||||
--- @type CAMI_USERGROUP[]
|
||||
local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or {
|
||||
user = {
|
||||
Name = "user",
|
||||
Inherits = "user",
|
||||
CAMI_Source = "Garry's Mod",
|
||||
},
|
||||
admin = {
|
||||
Name = "admin",
|
||||
Inherits = "user",
|
||||
CAMI_Source = "Garry's Mod",
|
||||
},
|
||||
superadmin = {
|
||||
Name = "superadmin",
|
||||
Inherits = "admin",
|
||||
CAMI_Source = "Garry's Mod",
|
||||
}
|
||||
}
|
||||
|
||||
--- Contains the registered CAMI_PRIVILEGE privilege structures.
|
||||
--- Indexed by privilege name.
|
||||
--- @type CAMI_PRIVILEGE[]
|
||||
local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {}
|
||||
|
||||
--- Registers a usergroup with CAMI.
|
||||
---
|
||||
--- Use the source parameter to make sure CAMI.RegisterUsergroup function and
|
||||
--- the CAMI.OnUsergroupRegistered hook don't cause an infinite loop
|
||||
--- @param usergroup CAMI_USERGROUP @The structure for the usergroup you want to register
|
||||
--- @param source any @Identifier for your own admin mod. Can be anything.
|
||||
--- @return CAMI_USERGROUP @The usergroup given as an argument
|
||||
function CAMI.RegisterUsergroup(usergroup, source)
|
||||
if source then
|
||||
usergroup.CAMI_Source = tostring(source)
|
||||
end
|
||||
usergroups[usergroup.Name] = usergroup
|
||||
|
||||
hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source)
|
||||
return usergroup
|
||||
end
|
||||
|
||||
--- Unregisters a usergroup from CAMI. This will call a hook that will notify
|
||||
--- all other admin mods of the removal.
|
||||
---
|
||||
--- ⚠ **Warning**: Call only when the usergroup is to be permanently removed.
|
||||
---
|
||||
--- Use the source parameter to make sure CAMI.UnregisterUsergroup function and
|
||||
--- the CAMI.OnUsergroupUnregistered hook don't cause an infinite loop
|
||||
--- @param usergroupName string @The name of the usergroup.
|
||||
--- @param source any @Identifier for your own admin mod. Can be anything.
|
||||
--- @return boolean @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
|
||||
|
||||
--- Retrieves all registered usergroups.
|
||||
--- @return CAMI_USERGROUP[] @Usergroups indexed by their names.
|
||||
function CAMI.GetUsergroups()
|
||||
return usergroups
|
||||
end
|
||||
|
||||
--- Receives information about a usergroup.
|
||||
--- @param usergroupName string
|
||||
--- @return CAMI_USERGROUP | nil @Returns nil when the usergroup does not exist.
|
||||
function CAMI.GetUsergroup(usergroupName)
|
||||
return usergroups[usergroupName]
|
||||
end
|
||||
|
||||
--- Checks to see if potentialAncestor is an ancestor of usergroupName.
|
||||
--- All usergroups are ancestors of themselves.
|
||||
---
|
||||
--- Examples:
|
||||
--- * `user` is an ancestor of `admin` and also `superadmin`
|
||||
--- * `admin` is an ancestor of `superadmin`, but not `user`
|
||||
--- @param usergroupName string @The usergroup to query
|
||||
--- @param potentialAncestor string @The ancestor to query
|
||||
--- @return boolean @Whether usergroupName inherits potentialAncestor.
|
||||
function CAMI.UsergroupInherits(usergroupName, potentialAncestor)
|
||||
repeat
|
||||
if usergroupName == potentialAncestor then return true end
|
||||
|
||||
usergroupName = usergroups[usergroupName] and
|
||||
usergroups[usergroupName].Inherits or
|
||||
usergroupName
|
||||
until not usergroups[usergroupName] or
|
||||
usergroups[usergroupName].Inherits == usergroupName
|
||||
|
||||
-- One can only be sure the usergroup inherits from user if the
|
||||
-- usergroup isn't registered.
|
||||
return usergroupName == potentialAncestor or potentialAncestor == "user"
|
||||
end
|
||||
|
||||
--- Find the base group a usergroup inherits from.
|
||||
---
|
||||
--- This function traverses down the inheritence chain, so for example if you have
|
||||
--- `user` -> `group1` -> `group2`
|
||||
--- this function will return `user` if you pass it `group2`.
|
||||
---
|
||||
--- ℹ **NOTE**: All usergroups must eventually inherit either user, admin or superadmin.
|
||||
--- @param usergroupName string @The name of the usergroup
|
||||
--- @return "'user'" | "'admin'" | "'superadmin'" @The name of the root usergroup
|
||||
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
|
||||
|
||||
--- Registers an addon privilege with CAMI.
|
||||
---
|
||||
--- ⚠ **Warning**: This should only be used by addons. Admin mods must *NOT*
|
||||
--- register their privileges using this function.
|
||||
--- @param privilege CAMI_PRIVILEGE
|
||||
--- @return CAMI_PRIVILEGE @The privilege given as argument.
|
||||
function CAMI.RegisterPrivilege(privilege)
|
||||
privileges[privilege.Name] = privilege
|
||||
|
||||
hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege)
|
||||
|
||||
return privilege
|
||||
end
|
||||
|
||||
--- Unregisters a privilege from CAMI.
|
||||
--- This will call a hook that will notify any admin mods of the removal.
|
||||
---
|
||||
--- ⚠ **Warning**: Call only when the privilege is to be permanently removed.
|
||||
--- @param privilegeName string @The name of the privilege.
|
||||
--- @return boolean @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
|
||||
|
||||
--- Retrieves all registered privileges.
|
||||
--- @return CAMI_PRIVILEGE[] @All privileges indexed by their names.
|
||||
function CAMI.GetPrivileges()
|
||||
return privileges
|
||||
end
|
||||
|
||||
--- Receives information about a privilege.
|
||||
--- @param privilegeName string
|
||||
--- @return CAMI_PRIVILEGE | nil
|
||||
function CAMI.GetPrivilege(privilegeName)
|
||||
return privileges[privilegeName]
|
||||
end
|
||||
|
||||
-- Default access handler
|
||||
local defaultAccessHandler = {["CAMI.PlayerHasAccess"] =
|
||||
function(_, actorPly, privilegeName, callback, targetPly, 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
|
||||
|
||||
local hasAccess =
|
||||
priv.MinAccess == "user" or
|
||||
priv.MinAccess == "admin" and actorPly:IsAdmin() or
|
||||
priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin()
|
||||
|
||||
if hasAccess and priv.HasAccess then
|
||||
hasAccess = priv:HasAccess(actorPly, targetPly)
|
||||
end
|
||||
|
||||
callback(hasAccess, "Fallback.")
|
||||
end,
|
||||
["CAMI.SteamIDHasAccess"] =
|
||||
function(_, _, _, callback)
|
||||
callback(false, "No information available.")
|
||||
end
|
||||
}
|
||||
|
||||
--- @class CAMI_ACCESS_EXTRA_INFO
|
||||
--- @field Fallback "'user'" | "'admin'" | "'superadmin'" @Fallback status for if the privilege doesn't exist. Defaults to `admin`.
|
||||
--- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have.
|
||||
--- @field CommandArguments table @Extra arguments that were given to the privilege command.
|
||||
|
||||
--- Checks if a player has access to a privilege
|
||||
--- (and optionally can execute it on targetPly)
|
||||
---
|
||||
--- This function is designed to be asynchronous but will be invoked
|
||||
--- synchronously if no callback is passed.
|
||||
---
|
||||
--- ⚠ **Warning**: If the currently installed admin mod does not support
|
||||
--- synchronous queries, this function will throw an error!
|
||||
--- @param actorPly GPlayer @The player to query
|
||||
--- @param privilegeName string @The privilege to query
|
||||
--- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer, or nil for synchronous
|
||||
--- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban)
|
||||
--- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
|
||||
--- @return boolean | nil @Synchronous only - if the player has the privilege
|
||||
--- @return string | nil @Synchronous only - optional reason from admin mod
|
||||
function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly,
|
||||
extraInfoTbl)
|
||||
local hasAccess, reason = nil, nil
|
||||
local callback_ = callback or function(hA, r) hasAccess, reason = hA, r end
|
||||
|
||||
hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly,
|
||||
privilegeName, callback_, targetPly, extraInfoTbl)
|
||||
|
||||
if callback ~= nil then return end
|
||||
|
||||
if hasAccess == nil then
|
||||
local err = [[The function CAMI.PlayerHasAccess was used to find out
|
||||
whether Player %s has privilege "%s", but an admin mod did not give an
|
||||
immediate answer!]]
|
||||
error(string.format(err,
|
||||
actorPly:IsPlayer() and actorPly:Nick() or tostring(actorPly),
|
||||
privilegeName))
|
||||
end
|
||||
|
||||
return hasAccess, reason
|
||||
end
|
||||
|
||||
--- Get all the players on the server with a certain privilege
|
||||
--- (and optionally who can execute it on targetPly)
|
||||
---
|
||||
--- ℹ **NOTE**: This is an asynchronous function!
|
||||
--- @param privilegeName string @The privilege to query
|
||||
--- @param callback fun(players: GPlayer[]) @Callback to receive the answer
|
||||
--- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban)
|
||||
--- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
|
||||
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 ipairs(allPlys) do
|
||||
CAMI.PlayerHasAccess(ply, privilegeName,
|
||||
function(...) onResult(ply, ...) end,
|
||||
targetPly, extraInfoTbl)
|
||||
end
|
||||
end
|
||||
|
||||
--- @class CAMI_STEAM_ACCESS_EXTRA_INFO
|
||||
--- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have.
|
||||
--- @field CommandArguments table @Extra arguments that were given to the privilege command.
|
||||
|
||||
--- Checks if a (potentially offline) SteamID has access to a privilege
|
||||
--- (and optionally if they can execute it on a target SteamID)
|
||||
---
|
||||
--- ℹ **NOTE**: This is an asynchronous function!
|
||||
--- @param actorSteam string | nil @The SteamID to query
|
||||
--- @param privilegeName string @The privilege to query
|
||||
--- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer
|
||||
--- @param targetSteam string | nil @Optional - target SteamID for if the privilege effects another player (eg kick/ban)
|
||||
--- @param extraInfoTbl CAMI_STEAM_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod
|
||||
function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback,
|
||||
targetSteam, extraInfoTbl)
|
||||
hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam,
|
||||
privilegeName, callback, targetSteam, extraInfoTbl)
|
||||
end
|
||||
|
||||
--- 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.
|
||||
--- @param ply GPlayer @The player for which the usergroup is changed
|
||||
--- @param old string @The previous usergroup of the player.
|
||||
--- @param new string @The new usergroup of the player.
|
||||
--- @param 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
|
||||
|
||||
--- 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.
|
||||
--- @param steamId string @The steam ID of the player for which the usergroup is changed
|
||||
--- @param old string @The previous usergroup of the player.
|
||||
--- @param new string @The new usergroup of the player.
|
||||
--- @param 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
|
||||
557
gamemodes/darkrp/gamemode/libraries/simplerr.lua
Normal file
557
gamemodes/darkrp/gamemode/libraries/simplerr.lua
Normal file
@@ -0,0 +1,557 @@
|
||||
local CompileFile = CompileFile
|
||||
local CompileString = CompileString
|
||||
local debug = debug
|
||||
local error = error
|
||||
local file = file
|
||||
local hook = hook
|
||||
local include = include
|
||||
local isfunction = isfunction
|
||||
local isstring = isstring
|
||||
local math = math
|
||||
local os = os
|
||||
local string = string
|
||||
local table = table
|
||||
local tonumber = tonumber
|
||||
local unpack = unpack
|
||||
local xpcall = xpcall
|
||||
|
||||
-- Template for syntax errors
|
||||
-- The [ERROR] start of it cannot be removed, because that would make the
|
||||
-- error mechanism remove all square brackets. Only Garry can make that bullshit up.
|
||||
local synErrTranslation = [=[[ERROR] Lua is unable to understand file "%s" because its author made a mistake around line number %i.
|
||||
The best help I can give you is this:
|
||||
|
||||
%s
|
||||
|
||||
Hints:
|
||||
%s
|
||||
|
||||
------- End of Simplerr error -------
|
||||
]=] -- The end is a special string by which simplerr errors are internally recognised
|
||||
|
||||
-- Template for runtime errors
|
||||
local runErrTranslation = [=[[ERROR] A runtime error has occurred in "%s" on line %i.
|
||||
The best help I can give you is this:
|
||||
|
||||
%s
|
||||
|
||||
Hints:
|
||||
%s
|
||||
|
||||
The responsibility for the error above lies with (the authors of) one (or more) of these files:
|
||||
%s
|
||||
------- End of Simplerr error -------
|
||||
]=]
|
||||
|
||||
-- Structure that contains syntax errors and their translations. Catches only the most common errors.
|
||||
-- Order is important: the structure with the first match is taken.
|
||||
local synErrs = {
|
||||
{
|
||||
match = "'=' expected near '(.*)'",
|
||||
text = "Right before the '%s', Lua expected to read an '='-sign, but it didn't.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"Did you simply forget the '='-sign?",
|
||||
"Did you forget a comma?",
|
||||
"Is this supposed to be a local variable?"
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "'.' expected [(]to close '([{[(])' at line ([0-9-]+)[)] near '(.*)'",
|
||||
text = "There is an opening '%s' bracket at line %i, but this bracket is never closed or not closed in time. It was expected to be closed before the '%s' at line %i.",
|
||||
format = function(m, l) return m[1], m[2], m[3], l end,
|
||||
hints = {
|
||||
"Did you forget a comma?",
|
||||
"All open brackets ({, (, [) must have a matching closing bracket. Are you sure it's there?",
|
||||
"Brackets must be opened and closed in the right order. This will work: ({}), but this won't: ({)}."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "'end' expected [(]to close '(.*)' at line ([0-9-]+)[)] near '(.*)'",
|
||||
text = "An '%s' was started on line %i, but it was never ended or not ended in time. It was expected to be ended before the '%s' at line %i",
|
||||
format = function(m, l) return m[1], m[2], m[3], l end,
|
||||
hints = {
|
||||
"For every if/for/do/while/function there must be an 'end' that closes it."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "unfinished string near '(.*)'",
|
||||
text = "The string '%s' at line %i is opened, but not closed.",
|
||||
format = function(m, l) return m[1], l end,
|
||||
hints = {
|
||||
"A string is a different word for literal text.",
|
||||
"Strings must be in single or double quotation marks (e.g. 'example', \"example\")",
|
||||
"A third option for strings is for them to be in double square brackets.",
|
||||
"Whatever you use (quotations or square brackets), you must not forget that strings are enclosed within a pair of quotation marks/square brackets."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "unfinished long string near '(.*)'",
|
||||
text = "Lua expected to see the end of a multiline string somewhere before the '%s' at line %i.",
|
||||
format = function(m, l) return m[1], l end,
|
||||
hints = {
|
||||
"A string is a different word for literal text.",
|
||||
"Multiline strings are strings that span over multiple lines.",
|
||||
"Multiline strings must be enclosed by double square brackets.",
|
||||
"Whatever you use (quotations or square brackets), you must not forget that strings are enclosed within a pair of quotation marks/square brackets.",
|
||||
"If you used brackets, the source of the mistake may be somewhere above the reported line."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "unfinished long comment near '(.*)'",
|
||||
text = "Lua expected to see the end of a multiline comment somewhere before the '%s' at line %i.",
|
||||
format = function(m, l) return m[1], l end,
|
||||
hints = {
|
||||
"A comment is text ignored by Lua.",
|
||||
"Multiline comments are ones that span multiple lines.",
|
||||
"Multiline comments must be enclosed by either /* and */ or double square brackets.",
|
||||
"Whatever you use (/**/ or square brackets), you must not forget that once you start a comment, you must end it.",
|
||||
"The source of the mistake may be somewhere above the reported line."
|
||||
}
|
||||
},
|
||||
-- Generic error messages
|
||||
{
|
||||
match = "function arguments expected near '(.*)'",
|
||||
text = "A function is being called right before '%s', but its arguments are not given.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"Did you write 'something:otherthing'? Try changing it to 'something:otherthing()'"
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "unexpected symbol near '(.*)'",
|
||||
text = "Right before the '%s', Lua encountered something it could not make sense of.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {"Did you forget something here? (Perhaps a closing bracket)", "Is it a typo?"}
|
||||
},
|
||||
{
|
||||
match = "'(.*)' expected near '(.*)'",
|
||||
text = "Right before the '%s', Lua expected to read a '%s', but it didn't.",
|
||||
format = function(m) return m[2], m[1] end,
|
||||
hints = {"Did you forget a keyword?", "Did you forget a comma?"}
|
||||
},
|
||||
{
|
||||
match = "malformed number near '(.*)'",
|
||||
text = "Lua attempted to read '%s' as a number, but failed to do so.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"Numbers starting with '0x' are hexidecimal.",
|
||||
"Lua can get confused when doing '<number>..\"some text\"'. Try inserting a space between the number and the '..'."
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
-- Similar structure for runtime errors. Catches only the most common errors.
|
||||
-- Order is important: the structure with the first match is taken
|
||||
local runErrs = {
|
||||
{
|
||||
match = "table index is nil",
|
||||
text = "A table is being indexed by something that does not exist (table index is nil).", -- Requires improvement
|
||||
format = function() end,
|
||||
hints = {
|
||||
"The thing between square brackets does not exist (is nil)."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "table index is NaN",
|
||||
text = "A table is being indexed by something that is not really a number (table index is NaN).",
|
||||
format = function() end,
|
||||
hints = {
|
||||
"Did you divide zero by zero thinking it would be funny?"
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to index global '(.*)' [(]a nil value[)]",
|
||||
text = "'%s' is being indexed like it is a table, but in reality it does not exist (is nil).",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to index global '(.*)' [(]a (.*) value[)]",
|
||||
text = "'%s' is being indexed like it is a table, but in reality it is a %s value.",
|
||||
format = function(m) return m[1], m[2] end,
|
||||
hints = {
|
||||
"You either have 'something.somethingElse' or 'something:somethingElse(more)'. The 'something' here is not a table."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to index a nil value",
|
||||
text = "Something is being indexed like it is a table, but in reality does not exist (is nil).",
|
||||
format = function() end,
|
||||
hints = {
|
||||
"You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to index a (.*) value",
|
||||
text = "Something is being indexed like it is a table, but in reality it is a %s value.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here is not a table."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to call global '(.*)' [(]a nil value[)]",
|
||||
text = "'%s' is being called like it is a function, but in reality does not exist (is nil).",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"You are doing something(<otherstuff>). The 'something' here does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to call a nil value",
|
||||
text = "Something is being called like it is a function, but in reality it does not exist (is nil).",
|
||||
format = function() end,
|
||||
hints = {
|
||||
"You are doing something(<otherstuff>). The 'something' here does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to call global '(.*)' [(]a (.*) value[)]",
|
||||
text = "'%s' is being called like it is a function, but in reality it is a %s.",
|
||||
format = function(m) return m[1], m[2] end,
|
||||
hints = {
|
||||
"You are doing something(<otherstuff>). The 'something' here is not a function."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to call a (.*) value",
|
||||
text = "Something is being called like it is a function, but in reality it is a %s.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"You are doing something(<otherstuff>). The 'something' here is not a function."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to call field '(.*)' [(]a nil value[)]",
|
||||
text = "'%s' is being called like it is a function, but in reality it does not exist (is nil).",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"You are doing either stuff.something(<otherstuff>) or stuff:something(<otherstuff>). The 'something' here does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to call field '(.*)' [(]a (.*) value[)]",
|
||||
text = "'%s' is being called like it is a function, but in reality it is a %s.",
|
||||
format = function(m) return m[1], m[2] end,
|
||||
hints = {
|
||||
"You are doing either stuff.something(<otherstuff>) or stuff:something(<otherstuff>). The 'something' here is not a function."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to concatenate global '(.*)' [(]a nil value[)]",
|
||||
text = "'%s' is being concatenated to something else, but '%s' does not exist (is nil).",
|
||||
format = function(m) return m[1], m[1] end,
|
||||
hints = {
|
||||
"Concatenation looks like this: something .. otherThing. Either something or otherThing does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to concatenate global '(.*)' [(]a (.*) value[)]",
|
||||
text = "'%s' is being concatenated to something else, but %s values cannot be concatenated.",
|
||||
format = function(m) return m[1], m[2] end,
|
||||
hints = {
|
||||
"Concatenation looks like this: something .. otherThing. Either something or otherThing is neither string nor number."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to concatenate a nil value",
|
||||
text = "Two (or more) things are being concatenated and one of them does not exist (is nil).",
|
||||
format = function() end,
|
||||
hints = {
|
||||
"Concatenation looks like this: something .. otherThing. Either something or otherThing does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to concatenate a (.*) value",
|
||||
text = "Two (or more) things are being concatenated and one of them is neither string nor number, but a %s.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"Concatenation looks like this: something .. otherThing. Either something or otherThing is neither string nor number."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "stack overflow",
|
||||
text = "The stack of function calls has overflowed",
|
||||
format = function() end,
|
||||
hints = {
|
||||
"Most likely infinite recursion.",
|
||||
"Do you have a function calling itself?"
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to compare two (.*) values",
|
||||
text = "A comparison is being made between two %s values. They cannot be compared.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"This error usually occurs when two incompatible things are being compared.",
|
||||
"'comparison' in this context means one of <, >, <=, >= (smaller than, greater than, etc.)"
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to compare (.*) with (.*)",
|
||||
text = "A comparison is being made between a %s and a %s. This is not possible.",
|
||||
format = function(m) return m[1], m[2] end,
|
||||
hints = {
|
||||
"This error usually occurs when two incompatible things are being compared.",
|
||||
"'Comparison' in this context means one of <, >, <=, >= (smaller than, greater than, etc.)"
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to perform arithmetic on a (.*) value",
|
||||
text = "Arithmetic operations are being performed on a %s. This is not possible.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"'Arithmetic' in this context means adding, multiplying, dividing, etc."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to get length of global '(.*)' [(]a nil value[)]",
|
||||
text = "The length of '%s' is requested as if it is a table, but in reality it does not exist (is nil).",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"You are doing #something. The 'something' here is does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to get length of global '(.*)' [(]a (.*) value[)]",
|
||||
text = "The length of '%s' is requested as if it is a table, but in reality it is a %s.",
|
||||
format = function(m) return m[1], m[2] end,
|
||||
hints = {
|
||||
"You are doing #something. The 'something' here is not a table."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to get length of a nil value",
|
||||
text = "The length of something is requested as if it is a table, but in reality it does not exist (is nil).",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"You are doing #something. The 'something' here is does not exist."
|
||||
}
|
||||
},
|
||||
{
|
||||
match = "attempt to get length of a (.*) value",
|
||||
text = "The length of something is requested as if it is a table, but in reality it is a %s.",
|
||||
format = function(m) return m[1] end,
|
||||
hints = {
|
||||
"You are doing #something. The 'something' here is not a table."
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
module("simplerr")
|
||||
|
||||
-- Get a nicely formatted stack trace. Start is where to start numbering
|
||||
-- stackMod allows the caller to modify the stack before it is numbered
|
||||
local function getStack(i, start, stackMod)
|
||||
i = i or 1
|
||||
start = start or 1
|
||||
local stack = {}
|
||||
|
||||
-- Invariant: stack level (i + count) >= 2 and <= last stack item
|
||||
for count = 1, math.huge do -- user visible count
|
||||
local info = debug.getinfo(i + count, "Sln")
|
||||
if not info then break end
|
||||
|
||||
local line = info.currentline or "unknown"
|
||||
if line == -1 and info.name then
|
||||
table.insert(stack, string.format("function '%s'", info.name))
|
||||
else
|
||||
table.insert(stack, string.format("%s on line %s", info.short_src, line))
|
||||
end
|
||||
end
|
||||
|
||||
-- Allow modification of the stack
|
||||
if stackMod then stack = stackMod(stack) end
|
||||
|
||||
-- add the numbering
|
||||
for count = 1, #stack do
|
||||
local stackLevel = start + count - 1
|
||||
stack[count] = string.format("\t%i. %s", stackLevel, stack[count])
|
||||
end
|
||||
|
||||
return table.concat(stack, "\n")
|
||||
end
|
||||
|
||||
-- Translate a runtime error to simplerr format.
|
||||
-- Decorate with e.g. wrapError to have it actually throw the error.
|
||||
function runError(msg, stackNr, hints, path, line, stack)
|
||||
stackNr = stackNr or 1
|
||||
hints = hints or {"No hints, sorry."}
|
||||
hints = "\t- " .. table.concat(hints, "\n\t- ")
|
||||
|
||||
if not path and not line then
|
||||
local info = debug.getinfo(stackNr + 1, "Sln") or debug.getinfo(stackNr, "Sln") or debug.getinfo(stackNr - 1, "Sln")
|
||||
path = info.short_src
|
||||
line = info.currentline
|
||||
end
|
||||
|
||||
return false, string.format(runErrTranslation, path, line, msg, hints, stack or getStack(stackNr + 1))
|
||||
end
|
||||
|
||||
-- Translate the message of an error
|
||||
local function translateMsg(msg, path, line, errs)
|
||||
local res
|
||||
local hints = {"No hints, sorry."}
|
||||
|
||||
for i = 1, #errs do
|
||||
local trans = errs[i]
|
||||
if not string.find(msg, trans.match) then continue end
|
||||
|
||||
-- translate <eof>
|
||||
msg = string.Replace(msg, "<eof>", "end of the file")
|
||||
|
||||
res = string.format(trans.text, trans.format({string.match(msg, trans.match)}, line, path))
|
||||
hints = trans.hints
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
return res or msg, "\t- " .. table.concat(hints, "\n\t- ")
|
||||
end
|
||||
|
||||
-- Translate an error into a language understandable by non-programmers
|
||||
local function translateError(path, line, err, translation, errs, stack)
|
||||
-- Using .* instead of path because path may be wrong when error is called
|
||||
local msg, hints = translateMsg(string.match(err, ".*:[0-9-]+: (.*)"), path, line, errs)
|
||||
local res = string.format(translation, path, line, msg, hints, stack)
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
-- Trims the [C] functions at the beginning of the stack
|
||||
local function trimStart(stack)
|
||||
while true do
|
||||
if string.StartWith(stack[1], "function ") then
|
||||
table.remove(stack, 1)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return stack
|
||||
end
|
||||
|
||||
-- safeCall uses xpcall, which has the downside that both xpcall and
|
||||
-- the safeCall function itself end up in the stack trace.
|
||||
-- This function removes them from the stack trace
|
||||
local function removeXpcall(stack)
|
||||
for i = #stack - 1, 1, -1 do
|
||||
if stack[i] == "function 'xpcall'" and string.find(stack[i + 1], "simplerr") then
|
||||
table.remove(stack, i)
|
||||
table.remove(stack, i) -- also remove the simplerr safeCall call
|
||||
|
||||
return stack
|
||||
end
|
||||
end
|
||||
|
||||
return stack
|
||||
end
|
||||
|
||||
-- Combines the two above functions
|
||||
local function stackModAggregate(stack)
|
||||
stack = trimStart(stack)
|
||||
return removeXpcall(stack)
|
||||
end
|
||||
|
||||
-- Used as the error handler in safeCall
|
||||
local function errorHandler(err, func)
|
||||
-- Investigate the stack. Not using err matching because calls to error can give a different path and line
|
||||
local stack = getStack(func and 1 or 2, 1, stackModAggregate) -- add called func to stack
|
||||
|
||||
-- Fetch the path and line number from the top of the stack
|
||||
local firstLine = string.sub(stack, 1, string.match(stack, "()\n") - 1)
|
||||
local path, line = string.match(firstLine, "\t[0-9-]+%. (.*) on line ([0-9-]+)")
|
||||
line = tonumber(line)
|
||||
|
||||
return {err, path, line, stack}
|
||||
end
|
||||
|
||||
-- Call a function and catch immediate runtime errors
|
||||
function safeCall(f, ...)
|
||||
-- Use xpcall so fetching of debug info is in the stack of the error rather than after it is unwound
|
||||
local res = {xpcall(f, errorHandler, ...)}
|
||||
|
||||
local succ, errInfo = res[1], res[2]
|
||||
|
||||
if succ then return unpack(res) end
|
||||
|
||||
-- This will only happen if the error is "not enough memory" or "error in error handling".
|
||||
-- The former tends to crash the game and the latter will mean it'll probably error in the next line.
|
||||
-- But we will try anyway.
|
||||
-- Note: stack trace will be less accurate.
|
||||
if isstring(errInfo) then errInfo = errorHandler(errInfo, f) end
|
||||
|
||||
-- Skip translation if the error is already a simplerr error
|
||||
-- This prevents nested simplerr errors when runError is called by a file loaded by runFile
|
||||
local mustTranslate = not string.find(errInfo[1], "------- End of Simplerr error -------")
|
||||
return false, mustTranslate and translateError(errInfo[2], errInfo[3], errInfo[1], runErrTranslation, runErrs, errInfo[4]) or errInfo[1]
|
||||
end
|
||||
|
||||
-- Run a file or explain its syntax errors in layman's terms
|
||||
-- Returns bool succeed, [string error]
|
||||
-- Do NOT use this on clientside files.
|
||||
-- Clientside files sent by the server cannot be read using file.Read unless you're the host of a listen server
|
||||
function runFile(path)
|
||||
if not file.Exists(path, "LUA") then error(string.format("Could not run file '%s' (file not found)", path)) end
|
||||
local contents = file.Read(path, "LUA")
|
||||
|
||||
-- Files can make a comment containing #NoSimplerr# to disable simplerr (and thus enable autorefresh)
|
||||
if string.find(contents, "#NoSimplerr#") then include(path) return true end
|
||||
|
||||
-- Catch syntax errors with CompileString
|
||||
local err = CompileString(contents, path, false)
|
||||
|
||||
-- CompileString returns the following string whenever a file is empty: Invalid script - or too short.
|
||||
-- It also prints: Not running script <path> - it's too short.
|
||||
-- If so, do nothing.
|
||||
if err == "Invalid script - or too short." then return true end
|
||||
|
||||
-- No syntax errors, check for immediate runtime errors using CompileFile
|
||||
-- Using the function CompileString returned leads to relative path trouble
|
||||
if isfunction(err) then return safeCall(CompileFile(path), path) end
|
||||
|
||||
-- Fetch the line number from the error
|
||||
local line = string.match(err, ".*:([0-9-]+): .*")
|
||||
line = tonumber(line)
|
||||
|
||||
return false, translateError(path, line, err, synErrTranslation, synErrs)
|
||||
end
|
||||
|
||||
-- Error wrapper: decorator for runFile and safeCall that throws an error on failure.
|
||||
-- Breaks execution. Must be the last decorator.
|
||||
function wrapError(succ, err, ...)
|
||||
if succ then return succ, err, ... end
|
||||
|
||||
error(err)
|
||||
end
|
||||
|
||||
-- Hook wrapper: Calls a hook on error
|
||||
function wrapHook(succ, err, ...)
|
||||
if not succ then hook.Call("onSimplerrError", nil, err) end
|
||||
|
||||
return succ, err, ...
|
||||
end
|
||||
|
||||
-- Logging wrapper: decorator for runFile and safeCall that logs failures.
|
||||
local log = {}
|
||||
function wrapLog(succ, err, ...)
|
||||
if succ then return succ, err, ... end
|
||||
|
||||
local data = {
|
||||
err = err,
|
||||
time = os.time()
|
||||
}
|
||||
|
||||
table.insert(log, data)
|
||||
|
||||
return succ, err, ...
|
||||
end
|
||||
|
||||
-- Retrieve the log
|
||||
function getLog() return log end
|
||||
|
||||
-- Clear the log
|
||||
function clearLog() log = {} end
|
||||
364
gamemodes/darkrp/gamemode/libraries/tablecheck.lua
Normal file
364
gamemodes/darkrp/gamemode/libraries/tablecheck.lua
Normal file
@@ -0,0 +1,364 @@
|
||||
--[[
|
||||
tablecheck
|
||||
|
||||
WIP
|
||||
|
||||
Author: FPtje Falco
|
||||
|
||||
Purpose:
|
||||
Allow validating tables by creating schemas of tables. Inspired by Joi (https://github.com/hapijs/joi)
|
||||
|
||||
Requires fn library (https://github.com/FPtje/GModFunctional),
|
||||
|
||||
Example:
|
||||
```lua
|
||||
local schema = tc.checkTable{
|
||||
name = tc.addHint(isstring, "The name must be a string!"),
|
||||
id = tc.addHint(isnumber, "The id must be a number!"),
|
||||
gender = tc.addHint(tc.oneOf{"male", "female", "carp"}, "Gender missing or not recognised!", {"Perhaps you are a carp?"}),
|
||||
}
|
||||
|
||||
local correct, err, hints = schema({name = "Dick", id = 3, gender = "carp"})
|
||||
print(correct) -- true
|
||||
|
||||
|
||||
local correct, err, hints = schema({name = "Dick", id = 3, gender = "crap"})
|
||||
print(correct) -- false
|
||||
print(err) -- Gender missing or not recognised!
|
||||
PrintTable(hints) -- {"Perhaps you are a carp?"}
|
||||
```
|
||||
|
||||
For further examples, including nesting and combining of schemas, please see the `unitTests` function for now.
|
||||
--]]
|
||||
|
||||
module("tc", package.seeall)
|
||||
|
||||
-- Helpers for quick access to metatables
|
||||
angle = FindMetaTable("Angle")
|
||||
convar = FindMetaTable("ConVar")
|
||||
effectdata = FindMetaTable("CEffectData")
|
||||
entity = FindMetaTable("Entity")
|
||||
file = FindMetaTable("File")
|
||||
imaterial = FindMetaTable("IMaterial")
|
||||
irestore = FindMetaTable("IRestore")
|
||||
isave = FindMetaTable("ISave")
|
||||
itexture = FindMetaTable("ITexture")
|
||||
lualocomotion = FindMetaTable("CLuaLocomotion")
|
||||
movedata = FindMetaTable("CMoveData")
|
||||
navarea = FindMetaTable("CNavArea")
|
||||
navladder = FindMetaTable("CNavLadder")
|
||||
nextbot = FindMetaTable("NextBot")
|
||||
npc = FindMetaTable("NPC")
|
||||
pathfollower = FindMetaTable("PathFollower")
|
||||
physobj = FindMetaTable("PhysObj")
|
||||
player = FindMetaTable("Player")
|
||||
recipientfilter = FindMetaTable("CRecipientFilter")
|
||||
soundpatch = FindMetaTable("CSoundPatch")
|
||||
takedamageinfo = FindMetaTable("CTakeDamageInfo")
|
||||
usercmd = FindMetaTable("CUserCmd")
|
||||
vector = FindMetaTable("Vector")
|
||||
vehicle = FindMetaTable("Vehicle")
|
||||
vmatrix = FindMetaTable("VMatrix")
|
||||
weapon = FindMetaTable("Weapon")
|
||||
|
||||
-- Assert function, asserts a property and returns the error if false.
|
||||
-- Allows f to override err and hints by simply returning them
|
||||
addHint = function(f, err, hints) return function(...)
|
||||
local res = {f(...)}
|
||||
res[2] = err
|
||||
res[3] = hints
|
||||
|
||||
return unpack(res)
|
||||
end end
|
||||
|
||||
--[[ Validates a table against a schema
|
||||
Capable of nesting
|
||||
--]]
|
||||
function checkTable(schema)
|
||||
return function(tbl)
|
||||
if not istable(tbl) then
|
||||
return false, "Not a table!"
|
||||
end
|
||||
|
||||
for k, v in pairs(schema or {}) do
|
||||
local correct, err, hints = tbl[v] ~= nil
|
||||
if isfunction(v) then correct, err, hints, replace, replaceWith = v(tbl[k], tbl) end
|
||||
|
||||
|
||||
if not correct then
|
||||
err = err or string.format("Element '%s' is corrupt!", k)
|
||||
return correct, err, hints
|
||||
end
|
||||
|
||||
-- Update the value
|
||||
if correct and replace == true and replaceWith then
|
||||
tbl[k] = replaceWith
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns whether a value is nil
|
||||
isnil = fn.Curry(fn.Eq, 2)(nil)
|
||||
|
||||
-- Returns whether a value is a color
|
||||
iscolor = IsColor
|
||||
|
||||
-- Returns true on the client
|
||||
client = function() return CLIENT end
|
||||
|
||||
-- returns true on the server
|
||||
server = function() return SERVER end
|
||||
|
||||
-- Optional value, when filled in it must meet the conditions
|
||||
optional = function(...) return fn.FOr{isnil, ...} end
|
||||
|
||||
-- Default value, implies optional. Only works in combination with tc.checkTable
|
||||
-- Note that the tc.addHint is to be the second parameter of default.
|
||||
-- tc.addHint(tc.default(x)) does NOT work, default(x, tc.addHint(...)) does.
|
||||
-- example: tc.checkTable{test = tc.default(3, tc.addHint(isnumber, "must be a number"))}
|
||||
-- example: tc.checkTable{test = tc.default(3)}
|
||||
default = function(def, f)
|
||||
return function(val, ...)
|
||||
if val == nil then
|
||||
-- second return value is the default value. Expects parent function to actually change the value
|
||||
return true, nil, nil, true, def
|
||||
end
|
||||
-- Return in if statement rather than "return f and f(val) or true" to allow multiple return values
|
||||
if f then return f(val, ...) else return true end
|
||||
end
|
||||
end
|
||||
|
||||
-- A table of which each element must meet condition f
|
||||
-- i.e. "this must be a table of xxx"
|
||||
-- example: tc.tableOf(isnumber) demands that the table contains only numbers
|
||||
tableOf = function(f) return function(tbl, parentTbl)
|
||||
if not istable(tbl) then return false end
|
||||
for _, v in pairs(tbl) do
|
||||
local res = {f(v, parentTbl)}
|
||||
if not res[1] then
|
||||
return unpack(res)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end end
|
||||
|
||||
-- Checks whether a value is amongst a given set of values
|
||||
-- exapmle: tc.oneOf{"jobs", "entities", "shipments", "weapons", "vehicles", "ammo"}
|
||||
oneOf = function(f) return fp{table.HasValue, f} end
|
||||
|
||||
-- A table that is non-empty, also useful for wrapping around tableOf
|
||||
-- example: tc.nonEmpty(tc.tableOf(isnumber))
|
||||
-- example: tc.nonEmpty() -- just checks that the table is non-empty
|
||||
nonEmpty = function(f) return function(tbl, parentTbl)
|
||||
if not istable(tbl) or table.IsEmpty(tbl) then return false end
|
||||
if not f then return true end
|
||||
return f(tbl, parentTbl)
|
||||
end end
|
||||
|
||||
-- Number check: minimum
|
||||
min = function(n) return fn.FAnd{isnumber, fp{fn.Lte, n}} end
|
||||
|
||||
-- Number check: maximum
|
||||
max = function(n) return fn.FAnd{isnumber, fp{fn.Gte, n}} end
|
||||
|
||||
-- Number check: positive
|
||||
positive = min(0)
|
||||
|
||||
-- Number check: negative
|
||||
negative = max(0)
|
||||
|
||||
|
||||
-- Whether the input matches regex
|
||||
-- Note: uses string.match, so it doesn't support full regex.
|
||||
-- May also allow numbers, since string.match also accepts numbers.
|
||||
-- Note, also matches on substrings. Use ^pattern$ for a full match.
|
||||
regex = function(pattern, startpos) return function(val)
|
||||
return (isstring(val) or isnumber(val)) and tobool(string.match(val, pattern, startpos))
|
||||
end end
|
||||
|
||||
-- Requires that the value only contains alphanumeric characters
|
||||
alphanum = regex("^[a-zA-Z0-9]+$")
|
||||
|
||||
|
||||
-- Test cases. Also serve as nice examples
|
||||
function unitTests()
|
||||
local id = 0
|
||||
|
||||
-- unit test helper functions
|
||||
local function checkCorrect(correct, err, hints)
|
||||
id = id + 1
|
||||
|
||||
if correct ~= true then
|
||||
print(id, "Incorrect value that should be correct!", correct, err, hints)
|
||||
if hints then PrintTable(hints) end
|
||||
return
|
||||
end
|
||||
|
||||
print(id, "Correct")
|
||||
end
|
||||
|
||||
local function checkIncorrect(correct, err, hints)
|
||||
id = id + 1
|
||||
|
||||
if correct then
|
||||
print(id, "Correct value that should be incorrect!", correct, err, hints)
|
||||
if hints then PrintTable(hints) end
|
||||
return
|
||||
end
|
||||
|
||||
print(id, "Correct")
|
||||
end
|
||||
|
||||
--[[
|
||||
Simple value schema. Checks whether the input is a number.
|
||||
]]
|
||||
local simpleSchema = tc.addHint(isnumber, "Must be a number!")
|
||||
|
||||
-- This is how a schema is to be used. Just call it with the value you want to check.
|
||||
-- In further unit tests, the schema function is immediately called inside the checkCorrect/checIncorrect call for brevity
|
||||
local correct, err, hints = simpleSchema(3)
|
||||
|
||||
checkCorrect(correct, err, hints)
|
||||
|
||||
|
||||
--[[
|
||||
Simple table schema
|
||||
]]
|
||||
local simpleTableSchema = tc.checkTable{
|
||||
name = tc.addHint(isstring, "The name must be a string!"),
|
||||
id = tc.addHint(isnumber, "The id must be a number!"),
|
||||
gender = tc.addHint(tc.oneOf{"male", "female", "carp"}, "Gender missing or not recognised!", {"Perhaps you are a carp?"}),
|
||||
nilthing = tc.addHint(tc.isnil, "nilthing must be nil"),
|
||||
nonEmpty = tc.addHint(tc.nonEmpty(tc.tableOf(isnumber)), "nonEmpty not table of numbers"),
|
||||
optnum = tc.addHint(tc.optional(isnumber), "optnum given, but not a number"),
|
||||
strnum = tc.addHint(fn.FOr{isstring, isnumber}, "strnum must either be a string or a number"),
|
||||
minmax = tc.addHint(fn.FAnd{tc.min(5), tc.max(10)}),
|
||||
pos = tc.addHint(tc.optional(tc.positive)),
|
||||
regx = tc.addHint(tc.optional(tc.regex("[a-z]+"))),
|
||||
letters = tc.addHint(tc.optional(tc.alphanum)),
|
||||
}
|
||||
|
||||
checkCorrect(simpleTableSchema({name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 5, regx = "asdf", letters = "asdfj", pos = 3}))
|
||||
|
||||
-- Counterexamples, should throw errors
|
||||
local badTables = {
|
||||
{},
|
||||
{name = 1, id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7},
|
||||
{name = "Dick", id = "3", gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7},
|
||||
{name = "Dick", id = 3, gender = "other", nonEmpty = {1,2,3}, strnum = "str", minmax = 7},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {}, strnum = "str", minmax = 7},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = {}, minmax = 7},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", optnum = "nope", minmax = 7},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 4},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 11},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str"},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7, regx = "666"},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7, regx = "asdf", letters = ">:D"},
|
||||
{name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7, regx = "asdf", letters = ">:D", pos = -1},
|
||||
}
|
||||
|
||||
for _, tbl in pairs(badTables) do
|
||||
checkIncorrect(simpleTableSchema(tbl))
|
||||
end
|
||||
|
||||
--[[
|
||||
Table Schema with no explicit keys
|
||||
]]
|
||||
local nokeysSchema = tc.checkTable{
|
||||
tc.addHint(isstring, "The first value must be a string."),
|
||||
tc.addHint(isnumber, "The second value must be a number!"),
|
||||
}
|
||||
checkCorrect(nokeysSchema({"string", 3}))
|
||||
|
||||
--[[
|
||||
Nested table schema
|
||||
]]
|
||||
local nestedSchema = tc.checkTable{
|
||||
nested = tc.checkTable{
|
||||
val = tc.addHint(isnumber, "'val' must be a number!")
|
||||
}
|
||||
}
|
||||
|
||||
checkCorrect(nestedSchema({nested = {val = 3}}))
|
||||
checkIncorrect(nestedSchema({}))
|
||||
|
||||
--[[
|
||||
Combining schemas using the fn library
|
||||
]]
|
||||
local andSchema = fn.FAnd{
|
||||
tc.checkTable{
|
||||
num = tc.addHint(isnumber, "num is not a number")
|
||||
},
|
||||
tc.checkTable{
|
||||
str = tc.addHint(isstring, "str is not a string")
|
||||
}
|
||||
}
|
||||
|
||||
checkCorrect(andSchema({num = 1, str = "string!"}))
|
||||
checkIncorrect(andSchema({num = 1}))
|
||||
checkIncorrect(andSchema({str = "string!"}))
|
||||
|
||||
local orSchema = fn.FOr{
|
||||
tc.checkTable{
|
||||
num = tc.addHint(isnumber, "num is not a number")
|
||||
},
|
||||
tc.checkTable{
|
||||
str = tc.addHint(isstring, "str is not a string")
|
||||
}
|
||||
}
|
||||
checkCorrect(orSchema({num = 1}))
|
||||
checkCorrect(orSchema({str = "string!"}))
|
||||
|
||||
--[[
|
||||
Default value with a check
|
||||
]]
|
||||
local withDefaultSchema = tc.checkTable{
|
||||
value = tc.default(10, tc.addHint(isnumber, "must be a number!"))
|
||||
}
|
||||
checkCorrect(withDefaultSchema({value = 30}))
|
||||
checkIncorrect(withDefaultSchema({value = "string"}))
|
||||
|
||||
local empty = {}
|
||||
checkCorrect(withDefaultSchema(empty))
|
||||
if empty.value ~= 10 then
|
||||
print("Default did NOT set the value to 10!")
|
||||
else
|
||||
print("Default test OK!")
|
||||
end
|
||||
|
||||
--[[
|
||||
Default value with no checks
|
||||
]]
|
||||
local withDefaultNoCheck = tc.checkTable{
|
||||
value = tc.default(10)
|
||||
}
|
||||
checkCorrect(withDefaultNoCheck({}))
|
||||
checkCorrect(withDefaultNoCheck({value = "string"}))
|
||||
|
||||
--[[
|
||||
Creating your own checker function that returns an error message
|
||||
When both the function and the tc.addHint define error messages, there's a conflict
|
||||
]]
|
||||
local function customCheck(val)
|
||||
return false, "function error message", {"function hint"}
|
||||
end
|
||||
|
||||
local customCheckSchema = tc.checkTable{
|
||||
value = tc.addHint(customCheck, "added error message", {"added hint"})
|
||||
}
|
||||
checkIncorrect(customCheckSchema{value = 1})
|
||||
checkIncorrect(customCheckSchema{})
|
||||
|
||||
_, err, hints = customCheckSchema{value = 2}
|
||||
if err ~= "added error message" or hints[1] ~= "added hint" then
|
||||
print("Wrong conflict solution", err, hints[1])
|
||||
else
|
||||
print("Conflict solution OK!")
|
||||
end
|
||||
|
||||
print("finished")
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
Do not add, edit or remove anything from this folder.
|
||||
|
||||
Use the DarkRPMod addon instead
|
||||
https://github.com/FPtje/DarkRPModification
|
||||
17
gamemodes/darkrp/gamemode/modules/afk/cl_afk.lua
Normal file
17
gamemodes/darkrp/gamemode/modules/afk/cl_afk.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
local TextColor = Color(GetConVar("Healthforeground1"):GetFloat(), GetConVar("Healthforeground2"):GetFloat(), GetConVar("Healthforeground3"):GetFloat(), GetConVar("Healthforeground4"):GetFloat())
|
||||
|
||||
local function AFKHUDPaint()
|
||||
if not LocalPlayer():getDarkRPVar("AFK") then return end
|
||||
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("afk_mode"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 100, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("salary_frozen"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 60, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
if not LocalPlayer():getDarkRPVar("AFKDemoted") then
|
||||
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("no_auto_demote"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
else
|
||||
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("youre_afk_demoted"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("afk_cmd_to_exit"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) + 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
hook.Add("HUDPaint", "AFK_HUD", AFKHUDPaint)
|
||||
8
gamemodes/darkrp/gamemode/modules/afk/sh_commands.lua
Normal file
8
gamemodes/darkrp/gamemode/modules/afk/sh_commands.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
DarkRP.registerDarkRPVar("AFK", net.WriteBit, fn.Compose{tobool, net.ReadBit})
|
||||
DarkRP.registerDarkRPVar("AFKDemoted", net.WriteBit, fn.Compose{tobool, net.ReadBit})
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "afk",
|
||||
description = "Go AFK",
|
||||
delay = 1.5
|
||||
}
|
||||
129
gamemodes/darkrp/gamemode/modules/afk/sv_afk.lua
Normal file
129
gamemodes/darkrp/gamemode/modules/afk/sv_afk.lua
Normal file
@@ -0,0 +1,129 @@
|
||||
-- How to use:
|
||||
-- If a player uses /afk, they go into AFK mode, they will not be autodemoted and their salary is set to $0 (you can still be killed/vote demoted though!).
|
||||
-- If a player does not use /afk, and they don't do anything for the demote time specified, they will be automatically demoted to hobo.
|
||||
|
||||
local function AFKDemote(ply)
|
||||
local shouldDemote, demoteTeam, suppressMsg, msg = hook.Call("playerAFKDemoted", nil, ply)
|
||||
demoteTeam = demoteTeam or GAMEMODE.DefaultTeam
|
||||
|
||||
if ply:Team() ~= demoteTeam and shouldDemote ~= false then
|
||||
local rpname = ply:getDarkRPVar("rpname")
|
||||
ply:changeTeam(demoteTeam, true)
|
||||
if not suppressMsg then DarkRP.notifyAll(0, 5, msg or DarkRP.getPhrase("hes_afk_demoted", rpname)) end
|
||||
end
|
||||
ply:setSelfDarkRPVar("AFKDemoted", true)
|
||||
ply:setDarkRPVar("job", "AFK")
|
||||
end
|
||||
|
||||
local function SetAFK(ply)
|
||||
local rpname = ply:getDarkRPVar("rpname")
|
||||
ply:setSelfDarkRPVar("AFK", not ply:getDarkRPVar("AFK"))
|
||||
|
||||
ply.blackScreen = ply:getDarkRPVar("AFK")
|
||||
SendUserMessage("blackScreen", ply, ply:getDarkRPVar("AFK"))
|
||||
|
||||
if ply:getDarkRPVar("AFK") then
|
||||
DarkRP.retrieveSalary(ply, function(amount) ply.OldSalary = amount end)
|
||||
ply.OldJob = ply:getDarkRPVar("job")
|
||||
ply.lastHealth = ply:Health()
|
||||
DarkRP.notifyAll(0, 5, DarkRP.getPhrase("player_now_afk", rpname))
|
||||
|
||||
ply.AFKDemote = math.huge
|
||||
|
||||
ply:KillSilent()
|
||||
ply:Lock()
|
||||
else
|
||||
ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime
|
||||
DarkRP.notifyAll(1, 5, DarkRP.getPhrase("player_no_longer_afk", rpname))
|
||||
DarkRP.notify(ply, 0, 5, DarkRP.getPhrase("salary_restored"))
|
||||
ply:Spawn()
|
||||
ply:UnLock()
|
||||
|
||||
ply:SetHealth(ply.lastHealth and ply.lastHealth > 0 and ply.lastHealth or 100)
|
||||
ply.lastHealth = nil
|
||||
end
|
||||
|
||||
if not ply.demotedWhileDead then
|
||||
ply:setDarkRPVar("job", ply:getDarkRPVar("AFK") and "AFK" or ply:getDarkRPVar("AFKDemoted") and team.GetName(ply:Team()) or ply.OldJob)
|
||||
ply:setSelfDarkRPVar("salary", ply:getDarkRPVar("AFK") and 0 or ply.OldSalary or 0)
|
||||
end
|
||||
|
||||
hook.Run("playerSetAFK", ply, ply:getDarkRPVar("AFK"))
|
||||
end
|
||||
|
||||
DarkRP.defineChatCommand("afk", function(ply)
|
||||
if ply.DarkRPLastAFK and not ply:getDarkRPVar("AFK") and ply.DarkRPLastAFK > CurTime() - GAMEMODE.Config.AFKDelay then
|
||||
DarkRP.notify(ply, 0, 5, DarkRP.getPhrase("unable_afk_spam_prevention"))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canAFK = hook.Run("canGoAFK", ply, not ply:getDarkRPVar("AFK"))
|
||||
|
||||
if canAFK == false then return "" end
|
||||
|
||||
ply.DarkRPLastAFK = CurTime()
|
||||
SetAFK(ply)
|
||||
|
||||
return ""
|
||||
end)
|
||||
|
||||
local function StartAFKOnPlayer(ply)
|
||||
ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime
|
||||
end
|
||||
hook.Add("PlayerInitialSpawn", "StartAFKOnPlayer", StartAFKOnPlayer)
|
||||
|
||||
local function AFKTimer(ply, key)
|
||||
ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime
|
||||
if ply:getDarkRPVar("AFKDemoted") then
|
||||
ply:setDarkRPVar("job", team.GetName(ply:Team()))
|
||||
timer.Simple(3, function() if IsValid(ply) then ply:setSelfDarkRPVar("AFKDemoted", nil) end end)
|
||||
end
|
||||
end
|
||||
hook.Add("KeyPress", "DarkRPKeyReleasedCheck", AFKTimer)
|
||||
|
||||
local function KillAFKTimer()
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if ply.AFKDemote and CurTime() > ply.AFKDemote and not ply:getDarkRPVar("AFK") and not ply:IsBot() then
|
||||
SetAFK(ply)
|
||||
AFKDemote(ply)
|
||||
ply.AFKDemote = math.huge
|
||||
end
|
||||
end
|
||||
end
|
||||
timer.Create("DarkRPKeyPressedCheck", 1, 0, function()
|
||||
KillAFKTimer()
|
||||
end)
|
||||
|
||||
local function BlockAFKTeamChange(ply, t, force)
|
||||
if ply:getDarkRPVar("AFK") and (not force or t ~= GAMEMODE.DefaultTeam) then
|
||||
local TEAM = RPExtraTeams[t]
|
||||
if TEAM then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", GAMEMODE.Config.chatCommandPrefix .. TEAM.command, DarkRP.getPhrase("afk_mode"))) end
|
||||
return false
|
||||
end
|
||||
end
|
||||
hook.Add("playerCanChangeTeam", "AFKCanChangeTeam", BlockAFKTeamChange)
|
||||
|
||||
-- Freeze AFK player's salary
|
||||
hook.Add("playerGetSalary", "AFKGetSalary", function(ply, amount)
|
||||
if ply:getDarkRPVar("AFK") then
|
||||
return true, "", 0
|
||||
end
|
||||
end)
|
||||
|
||||
-- For when a player's team is changed by force
|
||||
hook.Add("OnPlayerChangedTeam", "AFKCanChangeTeam", function(ply)
|
||||
if not ply:getDarkRPVar("AFK") then return end
|
||||
|
||||
ply.OldSalary = ply:getDarkRPVar("salary")
|
||||
ply.OldJob = nil
|
||||
ply:setSelfDarkRPVar("salary", 0)
|
||||
end)
|
||||
|
||||
local function unAFKPlayer(ply)
|
||||
if ply:getDarkRPVar("AFK") then
|
||||
SetAFK(ply)
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add("playerArrested", "DarkRP_AFK", unAFKPlayer)
|
||||
hook.Add("playerUnArrested", "DarkRP_AFK", unAFKPlayer)
|
||||
76
gamemodes/darkrp/gamemode/modules/afk/sv_interface.lua
Normal file
76
gamemodes/darkrp/gamemode/modules/afk/sv_interface.lua
Normal file
@@ -0,0 +1,76 @@
|
||||
DarkRP.hookStub{
|
||||
name = "playerAFKDemoted",
|
||||
description = "When a player is demoted for being AFK.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player being demoted.",
|
||||
type = "Player"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "shouldDemote",
|
||||
description = "Prevent the player from being actually demoted.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "team",
|
||||
description = "The team the player is to be demoted to (shouldDemote must be true.)",
|
||||
type = "number"
|
||||
},
|
||||
{
|
||||
name = "suppressMessage",
|
||||
description = "Suppress the demote message.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "demoteMessage",
|
||||
description = "Replacement of the demote message text.",
|
||||
type = "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "playerSetAFK",
|
||||
description = "When a player is set to AFK or returns from AFK.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "afk",
|
||||
description = "True when the player starts being AFK, false when the player stops being AFK.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "canGoAFK",
|
||||
description = "When a player can MANUALLY start being AFK by entering the chat command. Note: this hook does NOT get called when a player is set to AFK automatically! That hook will not be added, because I don't want asshole server owners to make AFK rules not apply to admins.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "afk",
|
||||
description = "True when the player starts being AFK, false when the player stops being AFK.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "canGoAFK",
|
||||
description = "Whether the player is allowed to go AFK",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
152
gamemodes/darkrp/gamemode/modules/animations/sh_animations.lua
Normal file
152
gamemodes/darkrp/gamemode/modules/animations/sh_animations.lua
Normal file
@@ -0,0 +1,152 @@
|
||||
local Anims = {}
|
||||
|
||||
-- Load animations after the languages for translation purposes
|
||||
hook.Add("loadCustomDarkRPItems", "loadAnimations", function()
|
||||
Anims[ACT_GMOD_GESTURE_BOW] = DarkRP.getPhrase("bow")
|
||||
Anims[ACT_GMOD_TAUNT_MUSCLE] = DarkRP.getPhrase("sexy_dance")
|
||||
Anims[ACT_GMOD_GESTURE_BECON] = DarkRP.getPhrase("follow_me")
|
||||
Anims[ACT_GMOD_TAUNT_LAUGH] = DarkRP.getPhrase("laugh")
|
||||
Anims[ACT_GMOD_TAUNT_PERSISTENCE] = DarkRP.getPhrase("lion_pose")
|
||||
Anims[ACT_GMOD_GESTURE_DISAGREE] = DarkRP.getPhrase("nonverbal_no")
|
||||
Anims[ACT_GMOD_GESTURE_AGREE] = DarkRP.getPhrase("thumbs_up")
|
||||
Anims[ACT_GMOD_GESTURE_WAVE] = DarkRP.getPhrase("wave")
|
||||
Anims[ACT_GMOD_TAUNT_DANCE] = DarkRP.getPhrase("dance")
|
||||
end)
|
||||
|
||||
function DarkRP.addPlayerGesture(anim, text)
|
||||
if not anim then DarkRP.error("Argument #1 of DarkRP.addPlayerGesture (animation/gesture) does not exist.", 2) end
|
||||
if not text then DarkRP.error("Argument #2 of DarkRP.addPlayerGesture (text) does not exist.", 2) end
|
||||
|
||||
Anims[anim] = text
|
||||
end
|
||||
|
||||
function DarkRP.removePlayerGesture(anim)
|
||||
if not anim then DarkRP.error("Argument #1 of DarkRP.removePlayerGesture (animation/gesture) does not exist.", 2) end
|
||||
|
||||
Anims[anim] = nil
|
||||
end
|
||||
|
||||
local function physGunCheck(ply)
|
||||
local hookName = "darkrp_anim_physgun_" .. ply:EntIndex()
|
||||
hook.Add("Think", hookName, function()
|
||||
if IsValid(ply) and
|
||||
ply:Alive() and
|
||||
ply:GetActiveWeapon():IsValid() and
|
||||
ply:GetActiveWeapon():GetClass() == "weapon_physgun" and
|
||||
ply:KeyDown(IN_ATTACK) and
|
||||
(ply:GetAllowWeaponsInVehicle() or not ply:InVehicle()) then
|
||||
local ent = ply:GetEyeTrace().Entity
|
||||
if IsValid(ent) and ent:IsPlayer() and not ply.SaidHi then
|
||||
ply.SaidHi = true
|
||||
ply:DoAnimationEvent(ACT_SIGNAL_GROUP)
|
||||
end
|
||||
else
|
||||
if IsValid(ply) then
|
||||
ply.SaidHi = nil
|
||||
end
|
||||
hook.Remove("Think", hookName)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
hook.Add("KeyPress", "darkrp_animations", function(ply, key)
|
||||
if key == IN_ATTACK then
|
||||
local weapon = ply:GetActiveWeapon()
|
||||
|
||||
if weapon:IsValid() then
|
||||
local class = weapon:GetClass()
|
||||
|
||||
-- Saying hi/hello to a player
|
||||
if class == "weapon_physgun" then
|
||||
physGunCheck(ply)
|
||||
|
||||
-- Hobo throwing poop!
|
||||
elseif class == "weapon_bugbait" then
|
||||
local Team = ply:Team()
|
||||
if RPExtraTeams[Team] and RPExtraTeams[Team].hobo then
|
||||
ply:DoAnimationEvent(ACT_GMOD_GESTURE_ITEM_THROW)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
if SERVER then
|
||||
local function CustomAnim(ply, cmd, args)
|
||||
if ply:EntIndex() == 0 then return end
|
||||
local Gesture = tonumber(args[1] or 0)
|
||||
if not Anims[Gesture] then return end
|
||||
|
||||
local RP = RecipientFilter()
|
||||
RP:AddAllPlayers()
|
||||
|
||||
umsg.Start("_DarkRP_CustomAnim", RP)
|
||||
umsg.Entity(ply)
|
||||
umsg.Short(Gesture)
|
||||
umsg.End()
|
||||
end
|
||||
concommand.Add("_DarkRP_DoAnimation", CustomAnim)
|
||||
return
|
||||
end
|
||||
|
||||
local function KeysAnims(um)
|
||||
local ply = um:ReadEntity()
|
||||
local act = um:ReadString()
|
||||
|
||||
if not IsValid(ply) then return end
|
||||
ply:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act == "usekeys" and ACT_GMOD_GESTURE_ITEM_PLACE or ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST, true)
|
||||
end
|
||||
usermessage.Hook("anim_keys", KeysAnims)
|
||||
|
||||
local function CustomAnimation(um)
|
||||
local ply = um:ReadEntity()
|
||||
local act = um:ReadShort()
|
||||
|
||||
if not IsValid(ply) then return end
|
||||
ply:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act, true)
|
||||
end
|
||||
usermessage.Hook("_DarkRP_CustomAnim", CustomAnimation)
|
||||
|
||||
local AnimFrame
|
||||
local function AnimationMenu()
|
||||
if AnimFrame then return end
|
||||
|
||||
local Panel = vgui.Create("Panel")
|
||||
Panel:SetPos(0,0)
|
||||
Panel:SetSize(ScrW(), ScrH())
|
||||
function Panel:OnMousePressed()
|
||||
AnimFrame:Close()
|
||||
end
|
||||
|
||||
AnimFrame = AnimFrame or vgui.Create("DFrame", Panel)
|
||||
local Height = table.Count(Anims) * 55 + 32
|
||||
AnimFrame:SetSize(130, Height)
|
||||
AnimFrame:SetPos(ScrW() / 2 + ScrW() * 0.1, ScrH() / 2 - (Height / 2))
|
||||
AnimFrame:SetTitle(DarkRP.getPhrase("custom_animation"))
|
||||
AnimFrame.btnMaxim:SetVisible(false)
|
||||
AnimFrame.btnMinim:SetVisible(false)
|
||||
AnimFrame:SetVisible(true)
|
||||
AnimFrame:MakePopup()
|
||||
AnimFrame:ParentToHUD()
|
||||
|
||||
function AnimFrame:Close()
|
||||
Panel:Remove()
|
||||
AnimFrame:Remove()
|
||||
AnimFrame = nil
|
||||
end
|
||||
|
||||
local i = 0
|
||||
for k, v in SortedPairs(Anims) do
|
||||
i = i + 1
|
||||
local button = vgui.Create("DButton", AnimFrame)
|
||||
button:SetPos(10, (i - 1) * 55 + 30)
|
||||
button:SetSize(110, 50)
|
||||
button:SetText(v)
|
||||
|
||||
button.DoClick = function()
|
||||
RunConsoleCommand("_DarkRP_DoAnimation", k)
|
||||
end
|
||||
end
|
||||
AnimFrame:SetSkin(GAMEMODE.Config.DarkRPSkin)
|
||||
end
|
||||
concommand.Add("_DarkRP_AnimationMenu", AnimationMenu)
|
||||
@@ -0,0 +1,37 @@
|
||||
DarkRP.addPlayerGesture = DarkRP.stub{
|
||||
name = "addPlayerGesture",
|
||||
description = "Add a player gesture to the DarkRP animations menu (the one that opens with the keys weapon.). Note: This function must be called BOTH serverside AND clientside!",
|
||||
parameters = {
|
||||
{
|
||||
name = "anim",
|
||||
description = "The gesture enumeration.",
|
||||
type = "number",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "text",
|
||||
description = "The textual description of the animation. This is what players see on the button in the menu.",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.removePlayerGesture = DarkRP.stub{
|
||||
name = "removePlayerGesture",
|
||||
description = "Removes a player gesture from the DarkRP animations menu (the one that opens with the keys weapon.). Note: This function must be called BOTH serverside AND clientside!",
|
||||
parameters = {
|
||||
{
|
||||
name = "anim",
|
||||
description = "The gesture enumeration.",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
30
gamemodes/darkrp/gamemode/modules/base/cl_drawfunctions.lua
Normal file
30
gamemodes/darkrp/gamemode/modules/base/cl_drawfunctions.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
-- concatenate a space to avoid the text being parsed as valve string
|
||||
local function safeText(text)
|
||||
return string.match(text, "^#([a-zA-Z_]+)$") and text .. " " or text
|
||||
end
|
||||
|
||||
DarkRP.deLocalise = safeText
|
||||
|
||||
function draw.DrawNonParsedText(text, font, x, y, color, xAlign)
|
||||
return draw.DrawText(safeText(text), font, x, y, color, xAlign)
|
||||
end
|
||||
|
||||
function draw.DrawNonParsedSimpleText(text, font, x, y, color, xAlign, yAlign)
|
||||
return draw.SimpleText(safeText(text), font, x, y, color, xAlign, yAlign)
|
||||
end
|
||||
|
||||
function draw.DrawNonParsedSimpleTextOutlined(text, font, x, y, color, xAlign, yAlign, outlineWidth, outlineColor)
|
||||
return draw.SimpleTextOutlined(safeText(text), font, x, y, color, xAlign, yAlign, outlineWidth, outlineColor)
|
||||
end
|
||||
|
||||
function surface.DrawNonParsedText(text)
|
||||
return surface.DrawText(safeText(text))
|
||||
end
|
||||
|
||||
function chat.AddNonParsedText(...)
|
||||
local tbl = {...}
|
||||
for i = 2, #tbl, 2 do
|
||||
tbl[i] = safeText(tbl[i])
|
||||
end
|
||||
return chat.AddText(unpack(tbl))
|
||||
end
|
||||
139
gamemodes/darkrp/gamemode/modules/base/cl_entityvars.lua
Normal file
139
gamemodes/darkrp/gamemode/modules/base/cl_entityvars.lua
Normal file
@@ -0,0 +1,139 @@
|
||||
DarkRP.ClientsideDarkRPVars = DarkRP.ClientsideDarkRPVars or {}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Interface
|
||||
---------------------------------------------------------------------------]]
|
||||
local pmeta = FindMetaTable("Player")
|
||||
-- This function is made local to optimise getDarkRPVar, which is called often
|
||||
-- enough to warrant optimizing. See https://github.com/FPtje/DarkRP/pull/3212
|
||||
local get_user_id = pmeta.UserID
|
||||
function pmeta:getDarkRPVar(var, fallback)
|
||||
local user_id = get_user_id(self)
|
||||
|
||||
-- Special case: when in the EntityRemoved hook, UserID returns -1. In this
|
||||
-- case, hope that we still have a stored userID lying around somewhere.
|
||||
-- See https://github.com/FPtje/DarkRP/pull/3270
|
||||
if user_id == -1 then
|
||||
user_id = self._darkrp_stored_user_id_for_entity_removed_hook
|
||||
end
|
||||
|
||||
local vars = DarkRP.ClientsideDarkRPVars[user_id]
|
||||
if vars == nil then return fallback end
|
||||
|
||||
local results = vars[var]
|
||||
if results == nil then return fallback end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve the information of a player var
|
||||
---------------------------------------------------------------------------]]
|
||||
local function RetrievePlayerVar(userID, var, value)
|
||||
local ply = Player(userID)
|
||||
DarkRP.ClientsideDarkRPVars[userID] = DarkRP.ClientsideDarkRPVars[userID] or {}
|
||||
|
||||
hook.Call("DarkRPVarChanged", nil, ply, var, DarkRP.ClientsideDarkRPVars[userID][var], value)
|
||||
DarkRP.ClientsideDarkRPVars[userID][var] = value
|
||||
|
||||
-- Backwards compatibility
|
||||
if IsValid(ply) then
|
||||
ply.DarkRPVars = DarkRP.ClientsideDarkRPVars[userID]
|
||||
end
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve a player var.
|
||||
Read the usermessage and attempt to set the DarkRP var
|
||||
---------------------------------------------------------------------------]]
|
||||
local function doRetrieve()
|
||||
local userID = net.ReadUInt(16)
|
||||
local var, value = DarkRP.readNetDarkRPVar()
|
||||
|
||||
RetrievePlayerVar(userID, var, value)
|
||||
end
|
||||
net.Receive("DarkRP_PlayerVar", doRetrieve)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve the message to remove a DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
local function doRetrieveRemoval()
|
||||
local userID = net.ReadUInt(16)
|
||||
local vars = DarkRP.ClientsideDarkRPVars[userID] or {}
|
||||
local var = DarkRP.readNetDarkRPVarRemoval()
|
||||
local ply = Player(userID)
|
||||
|
||||
hook.Call("DarkRPVarChanged", nil, ply, var, vars[var], nil)
|
||||
|
||||
vars[var] = nil
|
||||
end
|
||||
net.Receive("DarkRP_PlayerVarRemoval", doRetrieveRemoval)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Initialize the DarkRPVars at the start of the game
|
||||
---------------------------------------------------------------------------]]
|
||||
local function InitializeDarkRPVars(len)
|
||||
local plyCount = net.ReadUInt(8)
|
||||
|
||||
for i = 1, plyCount, 1 do
|
||||
local userID = net.ReadUInt(16)
|
||||
local varCount = net.ReadUInt(DarkRP.DARKRP_ID_BITS + 2)
|
||||
|
||||
for j = 1, varCount, 1 do
|
||||
local var, value = DarkRP.readNetDarkRPVar()
|
||||
RetrievePlayerVar(userID, var, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
net.Receive("DarkRP_InitializeVars", InitializeDarkRPVars)
|
||||
timer.Simple(0, fp{RunConsoleCommand, "_sendDarkRPvars"})
|
||||
|
||||
net.Receive("DarkRP_DarkRPVarDisconnect", function(len)
|
||||
local userID = net.ReadUInt(16)
|
||||
local ply = Player(userID)
|
||||
|
||||
-- If the player is already gone, then immediately clear the data and move on.
|
||||
if not IsValid(ply) then
|
||||
DarkRP.ClientsideDarkRPVars[userID] = nil
|
||||
return
|
||||
end
|
||||
-- Otherwise, we need to wait until the player is actually removed
|
||||
-- clientside. The net message may come in _much_ earlier than the message
|
||||
-- that the player disconnected and should therefore be removed.
|
||||
local hook_name = "darkrp_remove_darkrp_var_" .. userID
|
||||
|
||||
-- Workaround: the player's user ID is -1 in the EntityRemoved hook. This
|
||||
-- stores the user ID in a separate variable so that it is still accessible.
|
||||
-- See https://github.com/Facepunch/garrysmod-issues/issues/6117
|
||||
--
|
||||
-- This will allow getDarkRPVar to keep working
|
||||
if IsValid(ply) then
|
||||
ply._darkrp_stored_user_id_for_entity_removed_hook = userID
|
||||
end
|
||||
|
||||
hook.Add("EntityRemoved", hook_name, function(ent)
|
||||
if ent ~= ply then return end
|
||||
hook.Remove("EntityRemoved", hook_name)
|
||||
|
||||
-- Placing this in a timer allows for the rest of the hook runners to
|
||||
-- still use the DarkRPVars until the entity is _really_ gone.
|
||||
-- See https://github.com/FPtje/DarkRP/pull/3270
|
||||
timer.Simple(0, function()
|
||||
DarkRP.ClientsideDarkRPVars[userID] = nil
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Request the DarkRPVars when they haven't arrived
|
||||
---------------------------------------------------------------------------]]
|
||||
timer.Create("DarkRPCheckifitcamethrough", 15, 0, function()
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if v:getDarkRPVar("rpname") then continue end
|
||||
|
||||
RunConsoleCommand("_sendDarkRPvars")
|
||||
return
|
||||
end
|
||||
|
||||
timer.Remove("DarkRPCheckifitcamethrough")
|
||||
end)
|
||||
158
gamemodes/darkrp/gamemode/modules/base/cl_fonts.lua
Normal file
158
gamemodes/darkrp/gamemode/modules/base/cl_fonts.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
The fonts that DarkRP uses
|
||||
---------------------------------------------------------------------------]]
|
||||
local function loadFonts()
|
||||
surface.CreateFont("DarkRPHUD1", {
|
||||
size = 20,
|
||||
weight = 600,
|
||||
antialias = true,
|
||||
shadow = true,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("DarkRPHUD2", {
|
||||
size = 23,
|
||||
weight = 400,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Roboto20", {
|
||||
size = 20,
|
||||
weight = 600,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Trebuchet18", {
|
||||
size = 18,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Trebuchet MS",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Trebuchet20", {
|
||||
size = 20,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Trebuchet MS",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Trebuchet24", {
|
||||
size = 24,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Trebuchet MS",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Trebuchet48", {
|
||||
size = 48,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Trebuchet MS",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("TabLarge", {
|
||||
size = 18,
|
||||
weight = 700,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("UiBold", {
|
||||
size = 16,
|
||||
weight = 800,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Verdana",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("HUDNumber5", {
|
||||
size = 30,
|
||||
weight = 800,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Verdana",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardHeader", {
|
||||
size = 32,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardSubtitle", {
|
||||
size = 22,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardPlayerName", {
|
||||
size = 19,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardPlayerName2", {
|
||||
size = 15,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardPlayerNameBig", {
|
||||
size = 22,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("AckBarWriting", {
|
||||
size = 20,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Akbar",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("DarkRP_tipjar", {
|
||||
size = 100,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = true,
|
||||
font = "Verdana",
|
||||
extended = true,
|
||||
})
|
||||
end
|
||||
loadFonts()
|
||||
@@ -0,0 +1,84 @@
|
||||
local GUIToggled = false
|
||||
local mouseX, mouseY = ScrW() / 2, ScrH() / 2
|
||||
function GM:ShowSpare1()
|
||||
local jobTable = LocalPlayer():getJobTable()
|
||||
|
||||
-- We need to check for the existance of jobTable here, because in very rare edge cases, the player's team isn't set, when the getJobTable-function is called here.
|
||||
if jobTable and jobTable.ShowSpare1 then
|
||||
return jobTable.ShowSpare1(LocalPlayer())
|
||||
end
|
||||
|
||||
GUIToggled = not GUIToggled
|
||||
|
||||
if GUIToggled then
|
||||
gui.SetMousePos(mouseX, mouseY)
|
||||
else
|
||||
mouseX, mouseY = gui.MousePos()
|
||||
end
|
||||
gui.EnableScreenClicker(GUIToggled)
|
||||
end
|
||||
|
||||
function GM:ShowSpare2()
|
||||
local jobTable = LocalPlayer():getJobTable()
|
||||
|
||||
-- We need to check for the existance of jobTable here, because in very rare edge cases, the player's team isn't set, when the getJobTable-function is called here.
|
||||
if jobTable and jobTable.ShowSpare2 then
|
||||
return jobTable.ShowSpare2(LocalPlayer())
|
||||
end
|
||||
|
||||
-- DarkRP.toggleF4Menu()
|
||||
end
|
||||
|
||||
function GM:PlayerStartVoice(ply)
|
||||
if ply == LocalPlayer() then
|
||||
ply.DRPIsTalking = true
|
||||
return -- Not the original rectangle for yourself! ugh!
|
||||
end
|
||||
self.Sandbox.PlayerStartVoice(self, ply)
|
||||
end
|
||||
|
||||
function GM:PlayerEndVoice(ply)
|
||||
if ply == LocalPlayer() then
|
||||
ply.DRPIsTalking = false
|
||||
return
|
||||
end
|
||||
|
||||
self.Sandbox.PlayerEndVoice(self, ply)
|
||||
end
|
||||
|
||||
function GM:OnPlayerChat()
|
||||
end
|
||||
|
||||
local FKeyBinds = {
|
||||
["gm_showhelp"] = "ShowHelp",
|
||||
["gm_showteam"] = "ShowTeam",
|
||||
["gm_showspare1"] = "ShowSpare1",
|
||||
["gm_showspare2"] = "ShowSpare2"
|
||||
}
|
||||
|
||||
function GM:PlayerBindPress(ply, bind, pressed)
|
||||
self.Sandbox.PlayerBindPress(self, ply, bind, pressed)
|
||||
|
||||
local bnd = string.match(string.lower(bind), "gm_[a-z]+[12]?")
|
||||
if bnd and FKeyBinds[bnd] then
|
||||
hook.Call(FKeyBinds[bnd], GAMEMODE)
|
||||
end
|
||||
|
||||
if not self.Config.deadvoice and not ply:Alive() and string.find(string.lower(bind), "voicerecord") then return true end
|
||||
end
|
||||
|
||||
function GM:InitPostEntity()
|
||||
hook.Call("teamChanged", GAMEMODE, GAMEMODE.DefaultTeam, GAMEMODE.DefaultTeam)
|
||||
end
|
||||
|
||||
function GM:teamChanged(before, after)
|
||||
end
|
||||
|
||||
local function OnChangedTeam(um)
|
||||
local oldTeam, newTeam = um:ReadShort(), um:ReadShort()
|
||||
hook.Call("teamChanged", GAMEMODE, oldTeam, newTeam) -- backwards compatibility
|
||||
hook.Call("OnPlayerChangedTeam", GAMEMODE, LocalPlayer(), oldTeam, newTeam)
|
||||
end
|
||||
usermessage.Hook("OnChangedTeam", OnChangedTeam)
|
||||
|
||||
timer.Simple(0, function() GAMEMODE.ShowTeam = DarkRP.openKeysMenu end)
|
||||
131
gamemodes/darkrp/gamemode/modules/base/cl_interface.lua
Normal file
131
gamemodes/darkrp/gamemode/modules/base/cl_interface.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
DarkRP.PLAYER.isInRoom = DarkRP.stub{
|
||||
name = "isInRoom",
|
||||
description = "Whether the player is in the same room as the LocalPlayer.",
|
||||
parameters = {},
|
||||
returns = {
|
||||
{
|
||||
name = "inRoom",
|
||||
description = "Whether the player is in the same room.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.PLAYER
|
||||
}
|
||||
|
||||
DarkRP.deLocalise = DarkRP.stub{
|
||||
name = "deLocalise",
|
||||
description = "Makes sure the string will not be localised when drawn or printed.",
|
||||
parameters = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The text to delocalise.",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The delocalised text.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.textWrap = DarkRP.stub{
|
||||
name = "textWrap",
|
||||
description = "Wrap a text around when reaching a certain width.",
|
||||
parameters = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The text to wrap.",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "font",
|
||||
description = "The font of the text.",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "width",
|
||||
description = "The maximum width in pixels.",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The wrapped string.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.setPreferredJobModel = DarkRP.stub{
|
||||
name = "setPreferredJobModel",
|
||||
description = "Set the model preferred by the player (if the job allows multiple models).",
|
||||
parameters = {
|
||||
{
|
||||
name = "teamNr",
|
||||
description = "The team number of the job.",
|
||||
type = "number",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "model",
|
||||
description = "The preferred model for the job.",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.getPreferredJobModel = DarkRP.stub{
|
||||
name = "getPreferredJobModel",
|
||||
description = "Get the model preferred by the player (if the job allows multiple models).",
|
||||
parameters = {
|
||||
{
|
||||
name = "teamNr",
|
||||
description = "The team number of the job.",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "model",
|
||||
description = "The preferred model for the job.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "teamChanged",
|
||||
description = "When your team is changed.",
|
||||
deprecated = "Use the OnPlayerChangedTeam hook instead.",
|
||||
parameters = {
|
||||
{
|
||||
name = "before",
|
||||
description = "The team before the change.",
|
||||
type = "number"
|
||||
},
|
||||
{
|
||||
name = "after",
|
||||
description = "The team after the change.",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
100
gamemodes/darkrp/gamemode/modules/base/cl_jobmodels.lua
Normal file
100
gamemodes/darkrp/gamemode/modules/base/cl_jobmodels.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
-- Create a table for the preferred playermodels
|
||||
--
|
||||
-- Note: in DarkRP before 2024-09, there was a different table called
|
||||
-- `darkp_playermodels` (note the misspelling of "darkp"). This table was
|
||||
-- missing the server column, meaning that preferred job models would persist
|
||||
-- across multiple servers. To make preferred job models store per server, this
|
||||
-- new table (without the spelling mistake) was created.
|
||||
--
|
||||
-- See the original issue to create the player model preference feature:
|
||||
-- https://github.com/FPtje/DarkRP/issues/979 and the subsequent refactor at
|
||||
-- https://github.com/FPtje/DarkRP/pull/3266
|
||||
sql.Query([[CREATE TABLE IF NOT EXISTS darkrp_playermodels(
|
||||
server TEXT NOT NULL,
|
||||
jobcmd TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
PRIMARY KEY (server, jobcmd)
|
||||
);]])
|
||||
|
||||
|
||||
local preferredModels = {}
|
||||
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Interface functions
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.setPreferredJobModel(teamNr, model)
|
||||
local job = RPExtraTeams[teamNr]
|
||||
if not job then return end
|
||||
preferredModels[job.command] = model
|
||||
sql.Query(string.format([[REPLACE INTO darkrp_playermodels(server, jobcmd, model) VALUES(%s, %s, %s);]], sql.SQLStr(game.GetIPAddress()), sql.SQLStr(job.command), sql.SQLStr(model)))
|
||||
|
||||
net.Start("DarkRP_preferredjobmodel")
|
||||
net.WriteUInt(teamNr, 8)
|
||||
net.WriteString(model)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
function DarkRP.getPreferredJobModel(teamNr)
|
||||
local job = RPExtraTeams[teamNr]
|
||||
if not job then return end
|
||||
return preferredModels[job.command]
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Load the preferred models
|
||||
---------------------------------------------------------------------------]]
|
||||
local function sendModels()
|
||||
net.Start("DarkRP_preferredjobmodels")
|
||||
for _, job in pairs(RPExtraTeams) do
|
||||
if not preferredModels[job.command] then net.WriteBit(false) continue end
|
||||
|
||||
net.WriteBit(true)
|
||||
net.WriteString(preferredModels[job.command])
|
||||
end
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
local function jobHasModel(job, model)
|
||||
return istable(job.model) and table.HasValue(job.model, model) or job.model == model
|
||||
end
|
||||
|
||||
local function setPreferredModels(models)
|
||||
for _, v in ipairs(models) do
|
||||
local job = DarkRP.getJobByCommand(v.jobcmd)
|
||||
if job == nil or not jobHasModel(job, v.model) then continue end
|
||||
|
||||
preferredModels[v.jobcmd] = v.model
|
||||
end
|
||||
end
|
||||
|
||||
-- The old table, darkp_playermodels, acts as a global mapping of preferred
|
||||
-- models for jobs.
|
||||
local function setModelsFromOldTable()
|
||||
local oldTableExists = tobool(sql.QueryValue([[SELECT 1 FROM sqlite_master WHERE type='table' AND name='darkp_playermodels']]))
|
||||
if not oldTableExists then return end
|
||||
|
||||
local models = sql.Query([[SELECT jobcmd, model FROM darkp_playermodels;]])
|
||||
|
||||
if not models then return end
|
||||
setPreferredModels(models)
|
||||
end
|
||||
|
||||
-- The newer table is server specific.
|
||||
local function setModelsFromNewTable()
|
||||
local models = sql.Query(string.format([[SELECT jobcmd, model FROM darkrp_playermodels WHERE server = %s;]], sql.SQLStr(game.GetIPAddress())))
|
||||
|
||||
if not models then return end
|
||||
setPreferredModels(models)
|
||||
end
|
||||
|
||||
timer.Simple(0, function()
|
||||
-- Run after the jobs have loaded, to make sure the jobs can be looked up.
|
||||
|
||||
-- Set models from the old table, before overriding them with data from the
|
||||
-- new table. That way, server specific preferences always have precedence.
|
||||
setModelsFromOldTable()
|
||||
setModelsFromNewTable()
|
||||
|
||||
sendModels()
|
||||
end)
|
||||
107
gamemodes/darkrp/gamemode/modules/base/cl_util.lua
Normal file
107
gamemodes/darkrp/gamemode/modules/base/cl_util.lua
Normal file
@@ -0,0 +1,107 @@
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Show a black screen
|
||||
---------------------------------------------------------------------------]]
|
||||
local function blackScreen(um)
|
||||
local toggle = um:ReadBool()
|
||||
if toggle then
|
||||
local black = color_black
|
||||
local w, h = ScrW(), ScrH()
|
||||
hook.Add("HUDPaintBackground", "BlackScreen", function()
|
||||
surface.SetDrawColor(black)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end)
|
||||
else
|
||||
hook.Remove("HUDPaintBackground", "BlackScreen")
|
||||
end
|
||||
end
|
||||
usermessage.Hook("blackScreen", blackScreen)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Wrap strings to not become wider than the given amount of pixels
|
||||
---------------------------------------------------------------------------]]
|
||||
local function charWrap(text, remainingWidth, maxWidth)
|
||||
local totalWidth = 0
|
||||
|
||||
text = text:gsub(".", function(char)
|
||||
totalWidth = totalWidth + surface.GetTextSize(char)
|
||||
|
||||
-- Wrap around when the max width is reached
|
||||
if totalWidth >= remainingWidth then
|
||||
-- totalWidth needs to include the character width because it's inserted in a new line
|
||||
totalWidth = surface.GetTextSize(char)
|
||||
remainingWidth = maxWidth
|
||||
return "\n" .. char
|
||||
end
|
||||
|
||||
return char
|
||||
end)
|
||||
|
||||
return text, totalWidth
|
||||
end
|
||||
|
||||
function DarkRP.textWrap(text, font, maxWidth)
|
||||
local totalWidth = 0
|
||||
|
||||
surface.SetFont(font)
|
||||
|
||||
local spaceWidth = surface.GetTextSize(' ')
|
||||
text = text:gsub("(%s?[%S]+)", function(word)
|
||||
local char = string.sub(word, 1, 1)
|
||||
if char == "\n" or char == "\t" then
|
||||
totalWidth = 0
|
||||
end
|
||||
|
||||
local wordlen = surface.GetTextSize(word)
|
||||
totalWidth = totalWidth + wordlen
|
||||
|
||||
-- Wrap around when the max width is reached
|
||||
if wordlen >= maxWidth then -- Split the word if the word is too big
|
||||
local splitWord, splitPoint = charWrap(word, maxWidth - (totalWidth - wordlen), maxWidth)
|
||||
totalWidth = splitPoint
|
||||
return splitWord
|
||||
elseif totalWidth < maxWidth then
|
||||
return word
|
||||
end
|
||||
|
||||
-- Split before the word
|
||||
if char == ' ' then
|
||||
totalWidth = wordlen - spaceWidth
|
||||
return '\n' .. string.sub(word, 2)
|
||||
end
|
||||
|
||||
totalWidth = wordlen
|
||||
return '\n' .. word
|
||||
end)
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Decides whether a given player is in the same room as the local player
|
||||
note: uses a heuristic
|
||||
---------------------------------------------------------------------------]]
|
||||
function plyMeta:isInRoom()
|
||||
local tracedata = {}
|
||||
tracedata.start = LocalPlayer():GetShootPos()
|
||||
tracedata.endpos = self:GetShootPos()
|
||||
local trace = util.TraceLine(tracedata)
|
||||
|
||||
return not trace.HitWorld
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Key name to key int mapping
|
||||
---------------------------------------------------------------------------]]
|
||||
local keyNames
|
||||
function input.KeyNameToNumber(str)
|
||||
if not keyNames then
|
||||
keyNames = {}
|
||||
for i = 1, 107, 1 do
|
||||
keyNames[input.GetKeyName(i)] = i
|
||||
end
|
||||
end
|
||||
|
||||
return keyNames[str]
|
||||
end
|
||||
628
gamemodes/darkrp/gamemode/modules/base/sh_checkitems.lua
Normal file
628
gamemodes/darkrp/gamemode/modules/base/sh_checkitems.lua
Normal file
@@ -0,0 +1,628 @@
|
||||
--[[
|
||||
The base elements are shared by every custom item
|
||||
]]
|
||||
local baseSchema = tc.checkTable{
|
||||
buttonColor =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isnumber)),
|
||||
"The buttonColor must be a Color value."
|
||||
),
|
||||
|
||||
category =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The category must be the name of an existing category!"
|
||||
),
|
||||
|
||||
customCheck =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The customCheck must be a function."
|
||||
),
|
||||
|
||||
CustomCheckFailMsg =
|
||||
tc.addHint(
|
||||
tc.optional(isstring, isfunction),
|
||||
"The CustomCheckFailMsg must be either a string or a function."
|
||||
),
|
||||
|
||||
sortOrder =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The sortOrder must be a number."
|
||||
),
|
||||
|
||||
label =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The label must be a valid string."
|
||||
),
|
||||
}
|
||||
|
||||
--[[
|
||||
Properties shared by anything buyable
|
||||
]]
|
||||
local buyableSchema = fn.FAnd{baseSchema, tc.checkTable{
|
||||
allowed =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isnumber), isnumber),
|
||||
"The allowed field must be either an existing team or a table of existing teams.",
|
||||
{"Is there a job here that doesn't exist (anymore)?"}
|
||||
),
|
||||
|
||||
getPrice =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The getPrice must be a function."
|
||||
),
|
||||
|
||||
model =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The model must be valid."
|
||||
),
|
||||
|
||||
price =
|
||||
tc.addHint(
|
||||
function(v, tbl) return isnumber(v) or isfunction(tbl.getPrice) end,
|
||||
"The price must be an existing number or (for advanced users) the getPrice field must be a function."
|
||||
),
|
||||
|
||||
spawn =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The spawn must be a function."
|
||||
),
|
||||
allowPurchaseWhileDead =
|
||||
tc.addHint(
|
||||
tc.default(false),
|
||||
"The allowPurchaseWhileDead must be either true or false"
|
||||
)
|
||||
}}
|
||||
|
||||
-- The command of an entity must be unique
|
||||
local uniqueEntity = function(cmd, tbl)
|
||||
for _, v in pairs(DarkRPEntities) do
|
||||
if v.cmd ~= cmd then continue end
|
||||
|
||||
return
|
||||
false,
|
||||
"This entity does not have a unique command.",
|
||||
{
|
||||
"There must be some other entity that has the same thing for 'cmd'.",
|
||||
"Fix this by changing the 'cmd' field of your entity to something else."
|
||||
}
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- The command of a job must be unique
|
||||
local uniqueJob = function(v, tbl)
|
||||
local job = DarkRP.getJobByCommand(v)
|
||||
|
||||
if not job then return true end
|
||||
|
||||
return
|
||||
false,
|
||||
"This job does not have a unique command.",
|
||||
{
|
||||
"There must be some other job that has the same command.",
|
||||
"Fix this by changing the 'command' of your job to something else."
|
||||
}
|
||||
end
|
||||
|
||||
--[[
|
||||
Validate jobs
|
||||
]]
|
||||
DarkRP.validateJob = fn.FAnd{baseSchema, tc.checkTable{
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name must be a valid string."
|
||||
),
|
||||
|
||||
color =
|
||||
tc.addHint(
|
||||
tc.tableOf(isnumber),
|
||||
"The color must be a Color value.",
|
||||
{"Color values look like this: Color(r, g, b, a), where r, g, b and a are numbers between 0 and 255."}
|
||||
),
|
||||
|
||||
model =
|
||||
tc.addHint(
|
||||
fn.FOr{isstring, tc.nonEmpty(tc.tableOf(isstring))},
|
||||
"The model must either be a table of correct model strings or a single correct model string.",
|
||||
{
|
||||
"This error could happens when the model does not exist on the server.",
|
||||
"Are you sure the model path is right?",
|
||||
"Is the model from an addon that is not properly installed?"
|
||||
}
|
||||
),
|
||||
|
||||
description =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The description must be a string."
|
||||
),
|
||||
|
||||
weapons =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isstring)),
|
||||
"The weapons must be a valid table of strings.",
|
||||
{"Example: weapons = {\"med_kit\", \"weapon_bugbait\"},"}
|
||||
),
|
||||
|
||||
command =
|
||||
fn.FAnd
|
||||
{
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The command must be a string."
|
||||
),
|
||||
uniqueJob
|
||||
},
|
||||
|
||||
max =
|
||||
tc.addHint(
|
||||
fn.FAnd{isnumber, fp{fn.Lte, 0}},
|
||||
"The max must be a number greater than or equal to zero.",
|
||||
{
|
||||
"Zero means infinite.",
|
||||
"A decimal between 0 and 1 is seen as a percentage."
|
||||
}
|
||||
),
|
||||
|
||||
salary =
|
||||
tc.addHint(
|
||||
fn.FAnd{isnumber, fp{fn.Lte, 0}},
|
||||
"The salary must be a number and it must be greater than zero."
|
||||
),
|
||||
|
||||
admin =
|
||||
tc.default(0,
|
||||
tc.addHint(
|
||||
fn.FAnd{isnumber, fp{fn.Lte, 0}, fp{fn.Gte, 2}},
|
||||
"The admin value must be a number and it must be greater than or equal to zero and smaller than three."
|
||||
)
|
||||
),
|
||||
|
||||
vote =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The vote must be either true or false."
|
||||
),
|
||||
|
||||
ammo =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isnumber)),
|
||||
"The ammo must be a table containing numbers.",
|
||||
{"See example on https://darkrp.miraheze.org/wiki/DarkRP:CustomJobFields"}
|
||||
),
|
||||
|
||||
hasLicense =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The hasLicense must be either true or false."
|
||||
),
|
||||
|
||||
NeedToChangeFrom =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isnumber), isnumber),
|
||||
"The NeedToChangeFrom must be either an existing team or a table of existing teams",
|
||||
{"Is there a job here that doesn't exist (anymore)?"}
|
||||
),
|
||||
|
||||
modelScale =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The modelScale must be a number."
|
||||
),
|
||||
|
||||
maxpocket =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The maxPocket must be a number."
|
||||
),
|
||||
|
||||
maps =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isstring)),
|
||||
"The maps value must be a table of valid map names."
|
||||
),
|
||||
|
||||
candemote =
|
||||
tc.default(true,
|
||||
tc.addHint(
|
||||
isbool,
|
||||
"The candemote value must be either true or false."
|
||||
)
|
||||
),
|
||||
|
||||
mayor =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The mayor value must be either true or false."
|
||||
),
|
||||
|
||||
chief =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The chief value must be either true or false."
|
||||
),
|
||||
|
||||
medic =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The medic value must be either true or false."
|
||||
),
|
||||
|
||||
cook =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The cook value must be either true or false."
|
||||
),
|
||||
|
||||
hobo =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The hobo value must be either true or false."
|
||||
),
|
||||
|
||||
playerClass =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The playerClass must be a valid string."
|
||||
),
|
||||
|
||||
CanPlayerSuicide =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The CanPlayerSuicide must be a function."
|
||||
),
|
||||
|
||||
PlayerCanPickupWeapon =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerCanPickupWeapon must be a function."
|
||||
),
|
||||
|
||||
PlayerDeath =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerDeath must be a function."
|
||||
),
|
||||
|
||||
PlayerLoadout =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerLoadout must be a function."
|
||||
),
|
||||
|
||||
PlayerSelectSpawn =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerSelectSpawn must be a function."
|
||||
),
|
||||
|
||||
PlayerSetModel =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerSetModel must be a function."
|
||||
),
|
||||
|
||||
PlayerSpawn =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerSpawn must be a function."
|
||||
),
|
||||
|
||||
PlayerSpawnProp =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerSpawnProp must be a function."
|
||||
),
|
||||
|
||||
RequiresVote =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The RequiresVote must be a function."
|
||||
),
|
||||
|
||||
ShowSpare1 =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The ShowSpare1 must be a function."
|
||||
),
|
||||
|
||||
ShowSpare2 =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The ShowSpare2 must be a function."
|
||||
),
|
||||
|
||||
canStartVote =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The canStartVote must be a function."
|
||||
),
|
||||
|
||||
canStartVoteReason =
|
||||
tc.addHint(
|
||||
tc.optional(isstring, isfunction),
|
||||
"The canStartVoteReason must be either a string or a function."
|
||||
),
|
||||
}}
|
||||
|
||||
--[[
|
||||
Validate shipments
|
||||
]]
|
||||
DarkRP.validateShipment = fn.FAnd{buyableSchema, tc.checkTable{
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name must be a valid string."
|
||||
),
|
||||
|
||||
entity =
|
||||
tc.addHint(
|
||||
isstring, "The entity of the shipment must be a string."
|
||||
),
|
||||
|
||||
amount =
|
||||
tc.addHint(
|
||||
fn.FAnd{isnumber, fp{fn.Lte, 0}}, "The amount must be a number and it must be greater than zero."
|
||||
),
|
||||
|
||||
separate =
|
||||
tc.addHint(
|
||||
tc.optional(isbool), "the separate field must be either true or false."
|
||||
),
|
||||
|
||||
pricesep =
|
||||
tc.addHint(
|
||||
function(v, tbl) return not tbl.separate or isnumber(v) and v >= 0 end,
|
||||
"The pricesep must be a number and it must be greater than or equal to zero."
|
||||
),
|
||||
|
||||
noship =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The noship must be either true or false."
|
||||
),
|
||||
|
||||
shipmodel =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The shipmodel must be a valid model."
|
||||
),
|
||||
|
||||
weight =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The weight must be a number."
|
||||
),
|
||||
|
||||
spareammo =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The spareammo must be a number."
|
||||
),
|
||||
|
||||
clip1 =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The clip1 must be a number."
|
||||
),
|
||||
|
||||
clip2 =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The clip2 must be a number."
|
||||
),
|
||||
|
||||
shipmentClass =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The shipmentClass must be a string."
|
||||
),
|
||||
|
||||
onBought =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The onBought must be a function."
|
||||
),
|
||||
|
||||
}}
|
||||
|
||||
--[[
|
||||
Validate vehicles
|
||||
]]
|
||||
DarkRP.validateVehicle = fn.FAnd{buyableSchema, tc.checkTable{
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name of the vehicle must be a string."
|
||||
),
|
||||
|
||||
distance =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The distance must be a number."
|
||||
),
|
||||
|
||||
angle =
|
||||
tc.addHint(
|
||||
tc.optional(isangle),
|
||||
"The distance must be a valid Angle."
|
||||
),
|
||||
}}
|
||||
|
||||
--[[
|
||||
Validate Entities
|
||||
]]
|
||||
DarkRP.validateEntity = fn.FAnd{buyableSchema, tc.checkTable{
|
||||
ent =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The ent field must be a string."
|
||||
),
|
||||
|
||||
max =
|
||||
tc.addHint(
|
||||
function(v, tbl) return isnumber(v) or isfunction(tbl.getMax) end,
|
||||
"The max must be an existing number or (for advanced users) the getMax field must be a function."
|
||||
),
|
||||
|
||||
cmd =
|
||||
fn.FAnd
|
||||
{
|
||||
tc.addHint(isstring, "The cmd must be a valid string."),
|
||||
uniqueEntity
|
||||
},
|
||||
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name must be a valid string."
|
||||
),
|
||||
|
||||
allowTools =
|
||||
tc.default(false,
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The allowTools must be either true or false."
|
||||
)
|
||||
),
|
||||
|
||||
delay =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The delay must be a number."
|
||||
),
|
||||
}}
|
||||
|
||||
|
||||
-- Checks whether a team already has an agenda assigned.
|
||||
-- Jobs cannot have multiple agendas.
|
||||
|
||||
local overlappingAgendaCheck = function(t, tbl)
|
||||
local agenda = DarkRP.getAgendas()[t]
|
||||
|
||||
-- Team being -1 means the job is disabled
|
||||
if agenda == nil or t == -1 then return true end
|
||||
|
||||
local teamName = team.GetName(t)
|
||||
local err = "At least one job has multiple agendas assigned to them"
|
||||
local hints = {
|
||||
string.format([[The problem lies with the job called "%s"]], teamName),
|
||||
string.format([[It is assigned to agendas "%s" and "%s"]], agenda.Title or "unknown", tbl.Title or "unknown"),
|
||||
[[A job can only have ONE agenda. Otherwise things would become confusing, since only ONE agenda is always drawn on the screen.]]
|
||||
}
|
||||
|
||||
if agenda.Title == tbl.Title then
|
||||
table.insert(hints, "The titles of the two agendas are the same. It looks like perhaps you've made the same agenda more than once.")
|
||||
table.insert(hints, "Removing one of them should get rid of this error.")
|
||||
end
|
||||
|
||||
return false, err, hints
|
||||
end
|
||||
|
||||
--[[
|
||||
Validate Agendas
|
||||
]]
|
||||
local managerNumberCheck = tc.addHint(
|
||||
isnumber,
|
||||
"The Manager must either be a single team or a non-empty table of existing teams.",
|
||||
{"Is there a job here that doesn't exist (anymore)?"}
|
||||
)
|
||||
|
||||
DarkRP.validateAgenda = tc.checkTable{
|
||||
Title =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The title must be a string."
|
||||
),
|
||||
|
||||
-- Custom function to ensure the right error message is thrown
|
||||
Manager = function(manager, tbl)
|
||||
-- Check whether the manager is an existing team
|
||||
-- that does not already have an agenda assigned
|
||||
if isnumber(manager) then
|
||||
return fn.FAnd{overlappingAgendaCheck}(manager, tbl)
|
||||
|
||||
-- Check whether the manager is a table of existing teams
|
||||
-- and that none of the teams already have agendas assigned
|
||||
elseif istable(manager) then
|
||||
return tc.nonEmpty(
|
||||
tc.tableOf(
|
||||
fn.FAnd{managerNumberCheck, overlappingAgendaCheck}
|
||||
)
|
||||
)(manager, tbl)
|
||||
end
|
||||
|
||||
return managerNumberCheck(manager, tbl)
|
||||
end,
|
||||
Listeners =
|
||||
tc.default({}, -- Default to empty table
|
||||
-- Checks for a table of valid teams that do not already have an
|
||||
-- agenda assigned
|
||||
fn.FAnd{
|
||||
tc.addHint(
|
||||
tc.tableOf(isnumber),
|
||||
"The Listeners must be a table of existing teams.",
|
||||
{
|
||||
"Is there a job here that doesn't exist (anymore)?",
|
||||
"Are you trying to have multiple manager jobs in this agenda? In that case you must put the list of manager jobs in curly braces.",
|
||||
[[Like so: DarkRP.createAgenda("Some agenda", {TEAM_MANAGER1, TEAM_MANAGER2}, {TEAM_LISTENER1, TEAM_LISTENER2})]]
|
||||
}
|
||||
),
|
||||
tc.tableOf(overlappingAgendaCheck)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
--[[
|
||||
Validate Categories
|
||||
]]
|
||||
DarkRP.validateCategory = tc.checkTable{
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name must be a string."
|
||||
),
|
||||
|
||||
categorises =
|
||||
tc.addHint(
|
||||
tc.oneOf{"jobs", "entities", "shipments", "weapons", "vehicles", "ammo"},
|
||||
[[The categorises must be one of "jobs", "entities", "shipments", "weapons", "vehicles", "ammo"]],
|
||||
{
|
||||
"Mind that this is case sensitive.",
|
||||
"Also mind the quotation marks."
|
||||
}
|
||||
),
|
||||
|
||||
startExpanded =
|
||||
tc.addHint(
|
||||
isbool,
|
||||
"The startExpanded must be either true or false."
|
||||
),
|
||||
|
||||
color =
|
||||
tc.addHint(
|
||||
tc.tableOf(isnumber),
|
||||
"The color must be a Color value."
|
||||
),
|
||||
|
||||
canSee =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The canSee must be a function."
|
||||
),
|
||||
|
||||
sortOrder =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The sortOrder must be a number."
|
||||
),
|
||||
}
|
||||
71
gamemodes/darkrp/gamemode/modules/base/sh_commands.lua
Normal file
71
gamemodes/darkrp/gamemode/modules/base/sh_commands.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
DarkRP.declareChatCommand{
|
||||
command = "rpname",
|
||||
description = "Set your RP name",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "name",
|
||||
description = "Set your RP name",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "nick",
|
||||
description = "Set your RP name",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "buy",
|
||||
description = "Buy a pistol",
|
||||
delay = 1.5,
|
||||
condition = fn.FAnd {
|
||||
fn.Compose{fn.Curry(fn.GetValue, 2)("enablebuypistol"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode},
|
||||
fn.Compose{fn.Not, fn.Curry(fn.GetValue, 2)("noguns"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "buyshipment",
|
||||
description = "Buy a shipment",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "buyvehicle",
|
||||
description = "Buy a vehicle",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "buyammo",
|
||||
description = "Purchase ammo",
|
||||
delay = 1.5,
|
||||
condition = fn.Compose{fn.Not, fn.Curry(fn.GetValue, 2)("noguns"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode}
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "price",
|
||||
description = "Set the price of the microwave or gunlab you're looking at",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "setprice",
|
||||
description = "Set the price of the microwave or gunlab you're looking at",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "forcerpname",
|
||||
description = "Forcefully change a player's RP name",
|
||||
delay = 0.5,
|
||||
tableArgs = true
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "freerpname",
|
||||
description = "Remove a RP name from the database so a player can use it",
|
||||
delay = 1.5
|
||||
}
|
||||
922
gamemodes/darkrp/gamemode/modules/base/sh_createitems.lua
Normal file
922
gamemodes/darkrp/gamemode/modules/base/sh_createitems.lua
Normal file
@@ -0,0 +1,922 @@
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- Job commands --
|
||||
-----------------------------------------------------------
|
||||
local function declareTeamCommands(CTeam)
|
||||
local k = 0
|
||||
for num, v in pairs(RPExtraTeams) do
|
||||
if v.command == CTeam.command then
|
||||
k = num
|
||||
end
|
||||
end
|
||||
|
||||
local chatcommandCondition = function(ply)
|
||||
local plyTeam = ply:Team()
|
||||
|
||||
if plyTeam == k then return false end
|
||||
if CTeam.admin == 1 and not ply:IsAdmin() or CTeam.admin == 2 and not ply:IsSuperAdmin() then return false end
|
||||
if isnumber(CTeam.NeedToChangeFrom) and plyTeam ~= CTeam.NeedToChangeFrom then return false end
|
||||
if istable(CTeam.NeedToChangeFrom) and not table.HasValue(CTeam.NeedToChangeFrom, plyTeam) then return false end
|
||||
if CTeam.customCheck and CTeam.customCheck(ply) == false then return false end
|
||||
if ply:isArrested() then return false end
|
||||
local numPlayers = team.NumPlayers(k)
|
||||
if CTeam.max ~= 0 and ((CTeam.max % 1 == 0 and numPlayers >= CTeam.max) or (CTeam.max % 1 ~= 0 and (numPlayers + 1) / player.GetCount() > CTeam.max)) then return false end
|
||||
if ply.LastJob and 10 - (CurTime() - ply.LastJob) >= 0 then return false end
|
||||
if ply.LastVoteCop and CurTime() - ply.LastVoteCop < 80 then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if CTeam.vote or CTeam.RequiresVote then
|
||||
DarkRP.declareChatCommand{
|
||||
command = "vote" .. CTeam.command,
|
||||
description = "Vote to become " .. CTeam.name .. ".",
|
||||
delay = 1.5,
|
||||
condition =
|
||||
function(ply)
|
||||
if CTeam.RequiresVote and not CTeam.RequiresVote(ply, k) then return false end
|
||||
if CTeam.canStartVote and not CTeam.canStartVote(ply) then return false end
|
||||
|
||||
return chatcommandCondition(ply)
|
||||
end
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = CTeam.command,
|
||||
description = "Become " .. CTeam.name .. " and skip the vote.",
|
||||
delay = 1.5,
|
||||
condition =
|
||||
function(ply)
|
||||
local requiresVote = CTeam.RequiresVote and CTeam.RequiresVote(ply, k)
|
||||
|
||||
if requiresVote then return false end
|
||||
if requiresVote ~= false and CTeam.admin == 0 and not ply:IsAdmin() or CTeam.admin == 1 and not ply:IsSuperAdmin() then return false end
|
||||
if CTeam.canStartVote and not CTeam.canStartVote(ply) then return false end
|
||||
|
||||
return chatcommandCondition(ply)
|
||||
end
|
||||
}
|
||||
else
|
||||
DarkRP.declareChatCommand{
|
||||
command = CTeam.command,
|
||||
description = "Become " .. CTeam.name .. ".",
|
||||
delay = 1.5,
|
||||
condition = chatcommandCondition
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function addTeamCommands(CTeam, max)
|
||||
if CLIENT then return end
|
||||
|
||||
local k = 0
|
||||
for num, v in pairs(RPExtraTeams) do
|
||||
if v.command == CTeam.command then
|
||||
k = num
|
||||
end
|
||||
end
|
||||
|
||||
if CTeam.vote or CTeam.RequiresVote then
|
||||
DarkRP.defineChatCommand("vote" .. CTeam.command, function(ply)
|
||||
if CTeam.RequiresVote and not CTeam.RequiresVote(ply, k) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("job_doesnt_require_vote_currently"))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if CTeam.canStartVote and not CTeam.canStartVote(ply) then
|
||||
local reason = isfunction(CTeam.canStartVoteReason) and CTeam.canStartVoteReason(ply, CTeam) or CTeam.canStartVoteReason or ""
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/vote" .. CTeam.command, reason))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if CTeam.admin == 1 and not ply:IsAdmin() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", "/" .. "vote" .. CTeam.command))
|
||||
|
||||
return ""
|
||||
elseif CTeam.admin > 1 and not ply:IsSuperAdmin() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_sadmin", "/" .. "vote" .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if isnumber(CTeam.NeedToChangeFrom) and ply:Team() ~= CTeam.NeedToChangeFrom then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_be_before", team.GetName(CTeam.NeedToChangeFrom), CTeam.name))
|
||||
|
||||
return ""
|
||||
elseif istable(CTeam.NeedToChangeFrom) and not table.HasValue(CTeam.NeedToChangeFrom, ply:Team()) then
|
||||
local teamnames = ""
|
||||
|
||||
for _, b in pairs(CTeam.NeedToChangeFrom) do
|
||||
teamnames = teamnames .. " or " .. team.GetName(b)
|
||||
end
|
||||
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_be_before", string.sub(teamnames, 5), CTeam.name))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if CTeam.customCheck and not CTeam.customCheck(ply) then
|
||||
local message = isfunction(CTeam.CustomCheckFailMsg) and CTeam.CustomCheckFailMsg(ply, CTeam) or CTeam.CustomCheckFailMsg or DarkRP.getPhrase("unable", team.GetName(t), "")
|
||||
DarkRP.notify(ply, 1, 4, message)
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
local allowed, time = ply:changeAllowed(k)
|
||||
if not allowed then
|
||||
local notif = time and DarkRP.getPhrase("have_to_wait", math.ceil(time), "/job, " .. DarkRP.getPhrase("banned_or_demoted")) or DarkRP.getPhrase("unable", team.GetName(k), DarkRP.getPhrase("banned_or_demoted"))
|
||||
DarkRP.notify(ply, 1, 4, notif)
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if ply:Team() == k then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", CTeam.command, ""))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
local numPlayers = team.NumPlayers(k)
|
||||
if max ~= 0 and ((max % 1 == 0 and numPlayers >= max) or (max % 1 ~= 0 and (numPlayers + 1) / player.GetCount() > max)) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("team_limit_reached", CTeam.name))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if ply.LastJob and 10 - (CurTime() - ply.LastJob) >= 0 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(10 - (CurTime() - ply.LastJob)), GAMEMODE.Config.chatCommandPrefix .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
ply.LastVoteCop = ply.LastVoteCop or -80
|
||||
|
||||
if CurTime() - ply.LastVoteCop < 80 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(80 - (CurTime() - ply:GetTable().LastVoteCop)), GAMEMODE.Config.chatCommandPrefix .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
DarkRP.createVote(DarkRP.getPhrase("wants_to_be", ply:Nick(), CTeam.name), "job", ply, 20, function(vote, choice)
|
||||
local target = vote.target
|
||||
if not IsValid(target) then return end
|
||||
|
||||
if choice >= 0 then
|
||||
target:changeTeam(k)
|
||||
else
|
||||
DarkRP.notifyAll(1, 4, DarkRP.getPhrase("has_not_been_made_team", target:Nick(), CTeam.name))
|
||||
end
|
||||
end, nil, nil, {
|
||||
targetTeam = k
|
||||
})
|
||||
|
||||
ply.LastVoteCop = CurTime()
|
||||
|
||||
return ""
|
||||
end)
|
||||
|
||||
local function onJobCommand(ply, hasPriv)
|
||||
if hasPriv then
|
||||
ply:changeTeam(k)
|
||||
return
|
||||
end
|
||||
|
||||
local a = CTeam.admin
|
||||
if a > 0 and not ply:IsAdmin()
|
||||
or a > 1 and not ply:IsSuperAdmin()
|
||||
then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", CTeam.name))
|
||||
return
|
||||
end
|
||||
|
||||
if not CTeam.RequiresVote and
|
||||
(a == 0 and not ply:IsAdmin()
|
||||
or a == 1 and not ply:IsSuperAdmin()
|
||||
or a == 2)
|
||||
or CTeam.RequiresVote and CTeam.RequiresVote(ply, k)
|
||||
then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_make_vote", CTeam.name))
|
||||
return
|
||||
end
|
||||
|
||||
ply:changeTeam(k)
|
||||
end
|
||||
DarkRP.defineChatCommand(CTeam.command, function(ply)
|
||||
CAMI.PlayerHasAccess(ply, "DarkRP_GetJob_" .. CTeam.command, fp{onJobCommand, ply})
|
||||
|
||||
return ""
|
||||
end)
|
||||
else
|
||||
DarkRP.defineChatCommand(CTeam.command, function(ply)
|
||||
if CTeam.admin == 1 and not ply:IsAdmin() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", "/" .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if CTeam.admin > 1 and not ply:IsSuperAdmin() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_sadmin", "/" .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
ply:changeTeam(k)
|
||||
|
||||
return ""
|
||||
end)
|
||||
end
|
||||
|
||||
concommand.Add("rp_" .. CTeam.command, function(ply, cmd, args)
|
||||
if ply:EntIndex() ~= 0 and not ply:IsAdmin() then
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_admin", cmd))
|
||||
return
|
||||
end
|
||||
|
||||
if CTeam.admin > 1 and not ply:IsSuperAdmin() and ply:EntIndex() ~= 0 then
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_sadmin", cmd))
|
||||
return
|
||||
end
|
||||
|
||||
if CTeam.vote then
|
||||
if CTeam.admin >= 1 and ply:EntIndex() ~= 0 and not ply:IsSuperAdmin() then
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_sadmin", cmd))
|
||||
return
|
||||
elseif CTeam.admin > 1 and ply:IsSuperAdmin() and ply:EntIndex() ~= 0 then
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_to_make_vote", CTeam.name))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not args or not args[1] then
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return
|
||||
end
|
||||
|
||||
local target = DarkRP.findPlayer(args[1])
|
||||
|
||||
if not target then
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("could_not_find", tostring(args[1])))
|
||||
return
|
||||
end
|
||||
|
||||
target:changeTeam(k, true)
|
||||
local nick
|
||||
if (ply:EntIndex() ~= 0) then
|
||||
nick = ply:Nick()
|
||||
else
|
||||
nick = "Console"
|
||||
end
|
||||
DarkRP.notify(target, 0, 4, DarkRP.getPhrase("x_made_you_a_y", nick, CTeam.name))
|
||||
end)
|
||||
end
|
||||
|
||||
local function addEntityCommands(tblEnt)
|
||||
DarkRP.declareChatCommand{
|
||||
command = tblEnt.cmd,
|
||||
description = "Purchase a " .. tblEnt.name,
|
||||
delay = tblEnt.delay or GAMEMODE.Config.EntitySpamTime,
|
||||
condition =
|
||||
function(ply)
|
||||
if not tblEnt.allowPurchaseWhileDead and not ply:Alive() then return false end
|
||||
if ply:isArrested() then return false end
|
||||
if istable(tblEnt.allowed) and not table.HasValue(tblEnt.allowed, ply:Team()) then return false end
|
||||
if not ply:canAfford(tblEnt.price) then return false end
|
||||
if tblEnt.customCheck and tblEnt.customCheck(ply) == false then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
}
|
||||
|
||||
if CLIENT then return end
|
||||
|
||||
-- Default spawning function of an entity
|
||||
-- used if tblEnt.spawn is not defined
|
||||
local function defaultSpawn(ply, tr, tblE)
|
||||
local ent = ents.Create(tblE.ent)
|
||||
|
||||
if not ent:IsValid() then error("Entity '" .. tblE.ent .. "' does not exist or is not valid.") end
|
||||
if ent.Setowning_ent then ent:Setowning_ent(ply) end
|
||||
|
||||
ent:SetPos(tr.HitPos)
|
||||
-- These must be set before :Spawn()
|
||||
ent.SID = ply.SID
|
||||
ent.allowed = tblE.allowed
|
||||
ent.DarkRPItem = tblE
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
DarkRP.placeEntity(ent, tr, ply)
|
||||
|
||||
local phys = ent:GetPhysicsObject()
|
||||
if phys:IsValid() then phys:Wake() end
|
||||
|
||||
return ent
|
||||
end
|
||||
|
||||
local function buythis(ply, args)
|
||||
if ply:isArrested() then return "" end
|
||||
if not tblEnt.allowPurchaseWhileDead and not ply:Alive() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", tblEnt.name)))
|
||||
return ""
|
||||
end
|
||||
if istable(tblEnt.allowed) and not table.HasValue(tblEnt.allowed, ply:Team()) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("incorrect_job", tblEnt.name))
|
||||
return ""
|
||||
end
|
||||
|
||||
if tblEnt.customCheck and not tblEnt.customCheck(ply) then
|
||||
local message = isfunction(tblEnt.CustomCheckFailMsg) and tblEnt.CustomCheckFailMsg(ply, tblEnt) or
|
||||
tblEnt.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
DarkRP.notify(ply, 1, 4, message)
|
||||
return ""
|
||||
end
|
||||
|
||||
if ply:customEntityLimitReached(tblEnt) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", tblEnt.name))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyCustomEntity", nil, ply, tblEnt)
|
||||
|
||||
local cost = price or tblEnt.getPrice and tblEnt.getPrice(ply, tblEnt.price) or tblEnt.price
|
||||
|
||||
if not ply:canAfford(cost) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cant_afford", tblEnt.name))
|
||||
return ""
|
||||
end
|
||||
|
||||
if canbuy == false then
|
||||
if not suppress and message then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
ply:addMoney(-cost)
|
||||
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
trace.filter = ply
|
||||
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local ent = (tblEnt.spawn or defaultSpawn)(ply, tr, tblEnt)
|
||||
ent.onlyremover = not tblEnt.allowTools
|
||||
-- Repeat these properties to alleviate work in tblEnt.spawn:
|
||||
ent.SID = ply.SID
|
||||
ent.allowed = tblEnt.allowed
|
||||
ent.DarkRPItem = tblEnt
|
||||
|
||||
hook.Call("playerBoughtCustomEntity", nil, ply, tblEnt, ent, cost)
|
||||
|
||||
if cost == 0 then
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_got_yourself", tblEnt.name))
|
||||
else
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", tblEnt.name, DarkRP.formatMoney(cost), ""))
|
||||
end
|
||||
|
||||
ply:addCustomEntity(tblEnt)
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand(tblEnt.cmd, buythis)
|
||||
end
|
||||
|
||||
RPExtraTeams = {}
|
||||
local jobByCmd = {}
|
||||
DarkRP.getJobByCommand = function(cmd)
|
||||
if not jobByCmd[cmd] then return nil, nil end
|
||||
return RPExtraTeams[jobByCmd[cmd]], jobByCmd[cmd]
|
||||
end
|
||||
plyMeta.getJobTable = function(ply)
|
||||
local tbl = RPExtraTeams[ply:Team()]
|
||||
-- don't error when the player has not fully joined yet
|
||||
if not tbl and (ply.DarkRPInitialised or ply.DarkRPDataRetrievalFailed) then
|
||||
DarkRP.error(
|
||||
string.format("There is a player with an invalid team!\n\nThe player's name is %s, their team number is \"%s\", which has the name \"%s\"",
|
||||
ply:EntIndex() == 0 and "Console" or IsValid(ply) and ply:Nick() or "unknown",
|
||||
ply:Team(),
|
||||
team.GetName(ply:Team())),
|
||||
1,
|
||||
{
|
||||
"It is the server owner's responsibility to figure out why that player has no valid team.",
|
||||
"This error is very likely to be caused by an earlier error. If you don't see any errors in your own console, look at the server console."
|
||||
}
|
||||
)
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
function DarkRP.createJob(Name, colorOrTable, model, Description, Weapons, command, maximum_amount_of_this_class, Salary, admin, Vote, Haslicense, NeedToChangeFrom, CustomCheck)
|
||||
local tableSyntaxUsed = not IsColor(colorOrTable)
|
||||
|
||||
local CustomTeam = tableSyntaxUsed and colorOrTable or
|
||||
{color = colorOrTable, model = model, description = Description, weapons = Weapons, command = command,
|
||||
max = maximum_amount_of_this_class, salary = Salary, admin = admin or 0, vote = tobool(Vote), hasLicense = Haslicense,
|
||||
NeedToChangeFrom = NeedToChangeFrom, customCheck = CustomCheck
|
||||
}
|
||||
CustomTeam.name = Name
|
||||
CustomTeam.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
-- Disabled job
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["jobs"][CustomTeam.command] then return end
|
||||
|
||||
local valid, err, hints = DarkRP.validateJob(CustomTeam)
|
||||
if not valid then DarkRP.error(string.format("Corrupt team: %s!\n%s", CustomTeam.name or "", err), 2, hints) end
|
||||
|
||||
if not (GM or GAMEMODE):CustomObjFitsMap(CustomTeam) then return end
|
||||
|
||||
local jobCount = #RPExtraTeams + 1
|
||||
|
||||
CustomTeam.team = jobCount
|
||||
|
||||
CustomTeam.salary = math.floor(CustomTeam.salary)
|
||||
|
||||
CustomTeam.customCheck = CustomTeam.customCheck and fp{DarkRP.simplerrRun, CustomTeam.customCheck}
|
||||
CustomTeam.CustomCheckFailMsg = isfunction(CustomTeam.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, CustomTeam.CustomCheckFailMsg} or CustomTeam.CustomCheckFailMsg
|
||||
CustomTeam.CanPlayerSuicide = CustomTeam.CanPlayerSuicide and fp{DarkRP.simplerrRun, CustomTeam.CanPlayerSuicide}
|
||||
CustomTeam.PlayerCanPickupWeapon = CustomTeam.PlayerCanPickupWeapon and fp{DarkRP.simplerrRun, CustomTeam.PlayerCanPickupWeapon}
|
||||
CustomTeam.PlayerDeath = CustomTeam.PlayerDeath and fp{DarkRP.simplerrRun, CustomTeam.PlayerDeath}
|
||||
CustomTeam.PlayerLoadout = CustomTeam.PlayerLoadout and fp{DarkRP.simplerrRun, CustomTeam.PlayerLoadout}
|
||||
CustomTeam.PlayerSelectSpawn = CustomTeam.PlayerSelectSpawn and fp{DarkRP.simplerrRun, CustomTeam.PlayerSelectSpawn}
|
||||
CustomTeam.PlayerSetModel = CustomTeam.PlayerSetModel and fp{DarkRP.simplerrRun, CustomTeam.PlayerSetModel}
|
||||
CustomTeam.PlayerSpawn = CustomTeam.PlayerSpawn and fp{DarkRP.simplerrRun, CustomTeam.PlayerSpawn}
|
||||
CustomTeam.PlayerSpawnProp = CustomTeam.PlayerSpawnProp and fp{DarkRP.simplerrRun, CustomTeam.PlayerSpawnProp}
|
||||
CustomTeam.RequiresVote = CustomTeam.RequiresVote and fp{DarkRP.simplerrRun, CustomTeam.RequiresVote}
|
||||
CustomTeam.ShowSpare1 = CustomTeam.ShowSpare1 and fp{DarkRP.simplerrRun, CustomTeam.ShowSpare1}
|
||||
CustomTeam.ShowSpare2 = CustomTeam.ShowSpare2 and fp{DarkRP.simplerrRun, CustomTeam.ShowSpare2}
|
||||
CustomTeam.canStartVote = CustomTeam.canStartVote and fp{DarkRP.simplerrRun, CustomTeam.canStartVote}
|
||||
|
||||
jobByCmd[CustomTeam.command] = table.insert(RPExtraTeams, CustomTeam)
|
||||
DarkRP.addToCategory(CustomTeam, "jobs", CustomTeam.category)
|
||||
|
||||
team.SetUp(jobCount, Name, CustomTeam.color)
|
||||
|
||||
timer.Simple(0, function()
|
||||
declareTeamCommands(CustomTeam)
|
||||
addTeamCommands(CustomTeam, CustomTeam.max)
|
||||
end)
|
||||
|
||||
-- Precache model here. Not right before the job change is done
|
||||
if istable(CustomTeam.model) then
|
||||
for _, v in pairs(CustomTeam.model) do util.PrecacheModel(v) end
|
||||
else
|
||||
util.PrecacheModel(CustomTeam.model)
|
||||
end
|
||||
return jobCount
|
||||
end
|
||||
AddExtraTeam = DarkRP.createJob
|
||||
|
||||
local function removeCustomItem(tbl, category, hookName, reloadF4, i)
|
||||
local item = tbl[i]
|
||||
tbl[i] = nil
|
||||
if category then DarkRP.removeFromCategory(item, category) end
|
||||
if istable(item) and (item.command or item.cmd) then DarkRP.removeChatCommand(item.command or item.cmd) end
|
||||
hook.Run(hookName, i, item)
|
||||
if CLIENT and reloadF4 and IsValid(DarkRP.getF4MenuPanel()) then DarkRP.getF4MenuPanel():Remove() end -- Rebuild entire F4 menu frame
|
||||
end
|
||||
|
||||
function DarkRP.removeJob(i)
|
||||
local job = RPExtraTeams[i]
|
||||
jobByCmd[job.command] = nil
|
||||
|
||||
DarkRP.removeChatCommand("vote" .. job.command)
|
||||
removeCustomItem(RPExtraTeams, "jobs", "onJobRemoved", true, i)
|
||||
end
|
||||
|
||||
RPExtraTeamDoors = {}
|
||||
RPExtraTeamDoorIDs = {}
|
||||
local maxTeamDoorID = 0
|
||||
function DarkRP.createEntityGroup(name, ...)
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["doorgroups"][name] then return end
|
||||
RPExtraTeamDoors[name] = {...}
|
||||
RPExtraTeamDoors[name].name = name
|
||||
|
||||
maxTeamDoorID = maxTeamDoorID + 1
|
||||
RPExtraTeamDoorIDs[name] = maxTeamDoorID
|
||||
end
|
||||
AddDoorGroup = DarkRP.createEntityGroup
|
||||
|
||||
DarkRP.removeEntityGroup = fp{removeCustomItem, RPExtraTeamDoors, nil, "onEntityGroupRemoved", false}
|
||||
|
||||
CustomVehicles = {}
|
||||
CustomShipments = {}
|
||||
local shipByName = {}
|
||||
DarkRP.getShipmentByName = function(name)
|
||||
name = string.lower(name or "")
|
||||
|
||||
if not shipByName[name] then return nil, nil end
|
||||
return CustomShipments[shipByName[name]], shipByName[name]
|
||||
end
|
||||
|
||||
function DarkRP.createShipment(name, model, entity, price, Amount_of_guns_in_one_shipment, Sold_separately, price_separately, noshipment, classes, shipmodel, CustomCheck)
|
||||
local tableSyntaxUsed = istable(model)
|
||||
|
||||
price = tonumber(price)
|
||||
local shipmentmodel = shipmodel or "models/Items/item_item_crate.mdl"
|
||||
|
||||
local customShipment = tableSyntaxUsed and model or
|
||||
{model = model, entity = entity, price = price, amount = Amount_of_guns_in_one_shipment,
|
||||
seperate = Sold_separately, pricesep = price_separately, noship = noshipment, allowed = classes,
|
||||
shipmodel = shipmentmodel, customCheck = CustomCheck, weight = 5}
|
||||
|
||||
-- The pains of backwards compatibility when dealing with ancient spelling errors...
|
||||
if customShipment.separate ~= nil then
|
||||
customShipment.seperate = customShipment.separate
|
||||
end
|
||||
customShipment.separate = customShipment.seperate
|
||||
|
||||
if customShipment.allowed == nil then
|
||||
customShipment.allowed = {}
|
||||
for k in pairs(team.GetAllTeams()) do
|
||||
table.insert(customShipment.allowed, k)
|
||||
end
|
||||
end
|
||||
|
||||
customShipment.name = name
|
||||
customShipment.default = DarkRP.DARKRP_LOADING
|
||||
customShipment.shipmodel = customShipment.shipmodel or shipmentmodel
|
||||
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["shipments"][customShipment.name] then return end
|
||||
|
||||
local valid, err, hints = DarkRP.validateShipment(customShipment)
|
||||
if not valid then DarkRP.error(string.format("Corrupt shipment: %s!\n%s", name or "", err), 2, hints) end
|
||||
|
||||
customShipment.spawn = customShipment.spawn and fp{DarkRP.simplerrRun, customShipment.spawn}
|
||||
customShipment.allowed = isnumber(customShipment.allowed) and {customShipment.allowed} or customShipment.allowed
|
||||
customShipment.customCheck = customShipment.customCheck and fp{DarkRP.simplerrRun, customShipment.customCheck}
|
||||
customShipment.CustomCheckFailMsg = isfunction(customShipment.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, customShipment.CustomCheckFailMsg} or customShipment.CustomCheckFailMsg
|
||||
|
||||
if not customShipment.noship then DarkRP.addToCategory(customShipment, "shipments", customShipment.category) end
|
||||
if customShipment.separate then DarkRP.addToCategory(customShipment, "weapons", customShipment.category) end
|
||||
|
||||
shipByName[string.lower(name or "")] = table.insert(CustomShipments, customShipment)
|
||||
util.PrecacheModel(customShipment.model)
|
||||
end
|
||||
AddCustomShipment = DarkRP.createShipment
|
||||
|
||||
function DarkRP.removeShipment(i)
|
||||
local ship = CustomShipments[i]
|
||||
shipByName[ship.name] = nil
|
||||
removeCustomItem(CustomShipments, "shipments", "onShipmentRemoved", true, i)
|
||||
end
|
||||
|
||||
function DarkRP.createVehicle(Name_of_vehicle, model, price, Jobs_that_can_buy_it, customcheck)
|
||||
local vehicle = istable(Name_of_vehicle) and Name_of_vehicle or
|
||||
{name = Name_of_vehicle, model = model, price = price, allowed = Jobs_that_can_buy_it, customCheck = customcheck}
|
||||
|
||||
vehicle.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["vehicles"][vehicle.name] then return end
|
||||
|
||||
local found = false
|
||||
for k in pairs(DarkRP.getAvailableVehicles()) do
|
||||
if string.lower(k) == string.lower(vehicle.name) then found = true break end
|
||||
end
|
||||
|
||||
local valid, err, hints = DarkRP.validateVehicle(vehicle)
|
||||
if not valid then DarkRP.error(string.format("Corrupt vehicle: %s!\n%s", vehicle.name or "", err), 2, hints) end
|
||||
|
||||
if not found then DarkRP.error("Vehicle invalid: " .. vehicle.name .. ". Unknown vehicle name.", 2) end
|
||||
|
||||
vehicle.customCheck = vehicle.customCheck and fp{DarkRP.simplerrRun, vehicle.customCheck}
|
||||
vehicle.CustomCheckFailMsg = isfunction(vehicle.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, vehicle.CustomCheckFailMsg} or vehicle.CustomCheckFailMsg
|
||||
|
||||
table.insert(CustomVehicles, vehicle)
|
||||
DarkRP.addToCategory(vehicle, "vehicles", vehicle.category)
|
||||
end
|
||||
AddCustomVehicle = DarkRP.createVehicle
|
||||
|
||||
DarkRP.removeVehicle = fp{removeCustomItem, CustomVehicles, "vehicles", "onVehicleRemoved", true}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Decides whether a custom job or shipmet or whatever can be used in a certain map
|
||||
---------------------------------------------------------------------------]]
|
||||
function GM:CustomObjFitsMap(obj)
|
||||
if not obj or not obj.maps then return true end
|
||||
|
||||
local map = string.lower(game.GetMap())
|
||||
for _, v in pairs(obj.maps) do
|
||||
if string.lower(v) == map then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
DarkRPEntities = {}
|
||||
function DarkRP.createEntity(name, entity, model, price, max, command, classes, CustomCheck)
|
||||
local tableSyntaxUsed = istable(entity)
|
||||
|
||||
local tblEnt = tableSyntaxUsed and entity or
|
||||
{ent = entity, model = model, price = price, max = max,
|
||||
cmd = command, allowed = classes, customCheck = CustomCheck}
|
||||
tblEnt.name = name
|
||||
tblEnt.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["entities"][tblEnt.name] then return end
|
||||
|
||||
if isnumber(tblEnt.allowed) then
|
||||
tblEnt.allowed = {tblEnt.allowed}
|
||||
end
|
||||
|
||||
local valid, err, hints = DarkRP.validateEntity(tblEnt)
|
||||
if not valid then DarkRP.error(string.format("Corrupt entity: %s!\n%s", name or "", err), 2, hints) end
|
||||
|
||||
tblEnt.customCheck = tblEnt.customCheck and fp{DarkRP.simplerrRun, tblEnt.customCheck}
|
||||
tblEnt.CustomCheckFailMsg = isfunction(tblEnt.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, tblEnt.CustomCheckFailMsg} or tblEnt.CustomCheckFailMsg
|
||||
tblEnt.getPrice = tblEnt.getPrice and fp{DarkRP.simplerrRun, tblEnt.getPrice}
|
||||
tblEnt.getMax = tblEnt.getMax and fp{DarkRP.simplerrRun, tblEnt.getMax}
|
||||
tblEnt.spawn = tblEnt.spawn and fp{DarkRP.simplerrRun, tblEnt.spawn}
|
||||
|
||||
-- if SERVER and FPP then
|
||||
-- FPP.AddDefaultBlocked(blockTypes, tblEnt.ent)
|
||||
-- end
|
||||
|
||||
table.insert(DarkRPEntities, tblEnt)
|
||||
DarkRP.addToCategory(tblEnt, "entities", tblEnt.category)
|
||||
timer.Simple(0, function() addEntityCommands(tblEnt) end)
|
||||
end
|
||||
AddEntity = DarkRP.createEntity
|
||||
|
||||
DarkRP.removeEntity = fp{removeCustomItem, DarkRPEntities, "entities", "onEntityRemoved", true}
|
||||
|
||||
-- here for backwards compatibility
|
||||
DarkRPAgendas = {}
|
||||
|
||||
local agendas = {}
|
||||
-- Returns the agenda managed by the player
|
||||
plyMeta.getAgenda = fn.Compose{fn.Curry(fn.Flip(fn.GetValue), 2)(DarkRPAgendas), plyMeta.Team}
|
||||
|
||||
-- Returns the agenda this player is member of
|
||||
function plyMeta:getAgendaTable()
|
||||
return agendas[self:Team()]
|
||||
end
|
||||
|
||||
DarkRP.getAgendas = fp{fn.Id, agendas}
|
||||
|
||||
function DarkRP.createAgenda(Title, Manager, Listeners)
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["agendas"][Title] then return end
|
||||
|
||||
local agenda = {Manager = Manager, Title = Title, Listeners = Listeners, ManagersByKey = {}}
|
||||
agenda.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
local valid, err, hints = DarkRP.validateAgenda(agenda)
|
||||
if not valid then DarkRP.error(string.format("Corrupt agenda: %s!\n%s", agenda.Title or "", err), 2, hints) end
|
||||
|
||||
for _, v in pairs(agenda.Listeners) do
|
||||
agendas[v] = agenda
|
||||
end
|
||||
|
||||
for _, v in pairs(istable(agenda.Manager) and agenda.Manager or {agenda.Manager}) do
|
||||
agendas[v] = agenda
|
||||
DarkRPAgendas[v] = agenda -- backwards compat
|
||||
agenda.ManagersByKey[v] = true
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
timer.Simple(0, function()
|
||||
-- Run after scripts have loaded
|
||||
agenda.text = hook.Run("agendaUpdated", nil, agenda, "")
|
||||
end)
|
||||
end
|
||||
end
|
||||
AddAgenda = DarkRP.createAgenda
|
||||
|
||||
function DarkRP.removeAgenda(title)
|
||||
local agenda
|
||||
for k, v in pairs(agendas) do
|
||||
if v.Title == title then
|
||||
agenda = v
|
||||
agendas[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(DarkRPAgendas) do
|
||||
if v.Title == title then DarkRPAgendas[k] = nil end
|
||||
end
|
||||
hook.Run("onAgendaRemoved", title, agenda)
|
||||
end
|
||||
|
||||
GM.DarkRPGroupChats = {}
|
||||
local groupChatNumber = 0
|
||||
function DarkRP.createGroupChat(funcOrTeam, ...)
|
||||
local gm = GM or GAMEMODE
|
||||
gm.DarkRPGroupChats = gm.DarkRPGroupChats or {}
|
||||
if DarkRP.DARKRP_LOADING then
|
||||
groupChatNumber = groupChatNumber + 1
|
||||
if DarkRP.disabledDefaults["groupchat"][groupChatNumber] then return end
|
||||
end
|
||||
-- People can enter either functions or a list of teams as parameter(s)
|
||||
if isfunction(funcOrTeam) then
|
||||
table.insert(gm.DarkRPGroupChats, fp{DarkRP.simplerrRun, funcOrTeam})
|
||||
else
|
||||
local teams = {funcOrTeam, ...}
|
||||
table.insert(gm.DarkRPGroupChats, function(ply) return table.HasValue(teams, ply:Team()) end)
|
||||
end
|
||||
end
|
||||
GM.AddGroupChat = function(_, ...) DarkRP.createGroupChat(...) end
|
||||
|
||||
DarkRP.removeGroupChat = fp{removeCustomItem, GM.DarkRPGroupChats, nil, "onGroupChatRemoved", false}
|
||||
|
||||
DarkRP.getGroupChats = fp{fn.Id, GM.DarkRPGroupChats}
|
||||
|
||||
GM.AmmoTypes = {}
|
||||
|
||||
function DarkRP.createAmmoType(ammoType, name, model, price, amountGiven, customCheck)
|
||||
local gm = GM or GAMEMODE
|
||||
gm.AmmoTypes = gm.AmmoTypes or {}
|
||||
local ammo = istable(name) and name or {
|
||||
name = name,
|
||||
model = model,
|
||||
price = price,
|
||||
amountGiven = amountGiven,
|
||||
customCheck = customCheck
|
||||
}
|
||||
ammo.ammoType = ammoType
|
||||
ammo.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["ammo"][ammo.name] then return end
|
||||
|
||||
ammo.customCheck = ammo.customCheck and fp{DarkRP.simplerrRun, ammo.customCheck}
|
||||
ammo.CustomCheckFailMsg = isfunction(ammo.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, ammo.CustomCheckFailMsg} or ammo.CustomCheckFailMsg
|
||||
ammo.id = table.insert(gm.AmmoTypes, ammo)
|
||||
|
||||
DarkRP.addToCategory(ammo, "ammo", ammo.category)
|
||||
end
|
||||
GM.AddAmmoType = function(_, ...) DarkRP.createAmmoType(...) end
|
||||
|
||||
DarkRP.removeAmmoType = fp{removeCustomItem, GM.AmmoTypes, "ammo", "onAmmoTypeRemoved", true}
|
||||
|
||||
local demoteGroups = {}
|
||||
function DarkRP.createDemoteGroup(name, tbl)
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["demotegroups"][name] then return end
|
||||
if not tbl or not tbl[1] then error("No members in the demote group!") end
|
||||
|
||||
local set = demoteGroups[tbl[1]] or disjoint.MakeSet(tbl[1])
|
||||
set.name = name
|
||||
for i = 2, #tbl do
|
||||
set = (demoteGroups[tbl[i]] or disjoint.MakeSet(tbl[i])) + set
|
||||
set.name = name
|
||||
end
|
||||
|
||||
for _, teamNr in ipairs(tbl) do
|
||||
if demoteGroups[teamNr] then
|
||||
-- Unify the sets if there was already one there
|
||||
demoteGroups[teamNr] = demoteGroups[teamNr] + set
|
||||
else
|
||||
demoteGroups[teamNr] = set
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.removeDemoteGroup(name)
|
||||
local foundSet
|
||||
for k, v in pairs(demoteGroups) do
|
||||
local set = disjoint.FindSet(v)
|
||||
if set.name == name then
|
||||
foundSet = set
|
||||
demoteGroups[k] = nil
|
||||
end
|
||||
end
|
||||
hook.Run("onDemoteGroupRemoved", name, foundSet)
|
||||
end
|
||||
|
||||
function DarkRP.getDemoteGroup(teamNr)
|
||||
demoteGroups[teamNr] = demoteGroups[teamNr] or disjoint.MakeSet(teamNr)
|
||||
return disjoint.FindSet(demoteGroups[teamNr])
|
||||
end
|
||||
|
||||
DarkRP.getDemoteGroups = fp{fn.Id, demoteGroups}
|
||||
|
||||
local categories = {
|
||||
jobs = {},
|
||||
entities = {},
|
||||
shipments = {},
|
||||
weapons = {},
|
||||
vehicles = {},
|
||||
ammo = {},
|
||||
}
|
||||
local categoriesMerged = false -- whether categories and custom items are merged.
|
||||
|
||||
DarkRP.getCategories = fp{fn.Id, categories}
|
||||
|
||||
local categoryOrder = function(a, b)
|
||||
local aso = a.sortOrder or 100
|
||||
local bso = b.sortOrder or 100
|
||||
return aso < bso or aso == bso and a.name < b.name
|
||||
end
|
||||
|
||||
local function insertCategory(destination, tbl)
|
||||
-- Override existing category of applicable
|
||||
for k, cat in pairs(destination) do
|
||||
if cat.name ~= tbl.name then continue end
|
||||
|
||||
destination[k] = tbl
|
||||
tbl.members = cat.members
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(destination, tbl)
|
||||
local i = #destination
|
||||
|
||||
while i > 1 do
|
||||
if categoryOrder(destination[i - 1], tbl) then break end
|
||||
destination[i - 1], destination[i] = destination[i], destination[i - 1]
|
||||
i = i - 1
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.createCategory(tbl)
|
||||
local valid, err, hints = DarkRP.validateCategory(tbl)
|
||||
if not valid then DarkRP.error(string.format("Corrupt category: %s!\n%s", tbl.name or "", err), 2, hints) end
|
||||
tbl.members = {}
|
||||
|
||||
local destination = categories[tbl.categorises]
|
||||
insertCategory(destination, tbl)
|
||||
|
||||
-- Too many people made the mistake of not creating a category for weapons as well as shipments
|
||||
-- when having shipments that can also be sold separately.
|
||||
if tbl.categorises == "shipments" then
|
||||
insertCategory(categories.weapons, table.Copy(tbl))
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.addToCategory(item, kind, cat)
|
||||
cat = cat or "Другое"
|
||||
item.category = cat
|
||||
|
||||
-- The merge process will take care of the category:
|
||||
if not categoriesMerged then return end
|
||||
|
||||
-- Post-merge: manual insertion into category
|
||||
local cats = categories[kind]
|
||||
for _, c in ipairs(cats) do
|
||||
if c.name ~= cat then continue end
|
||||
|
||||
insertCategory(c.members, item)
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.errorNoHalt(string.format([[The category of "%s" ("%s") does not exist!]], item.name, cat), 2, {
|
||||
"Make sure the category is created with DarkRP.createCategory.",
|
||||
"The category name is case sensitive!",
|
||||
"Categories must be created before DarkRP finished loading.",
|
||||
})
|
||||
end
|
||||
|
||||
function DarkRP.removeFromCategory(item, kind)
|
||||
local cats = categories[kind]
|
||||
if not cats then DarkRP.error(string.format("Invalid category kind '%s'.", kind), 2) end
|
||||
local cat = item.category
|
||||
if not cat then return end
|
||||
for _, v in pairs(cats) do
|
||||
if v.name ~= item.category then continue end
|
||||
for k, mem in pairs(v.members) do
|
||||
if mem ~= item then continue end
|
||||
table.remove(v.members, k)
|
||||
break
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Assign custom stuff to their categories
|
||||
local function mergeCategories(customs, catKind, path)
|
||||
local cats = categories[catKind]
|
||||
local catByName = {}
|
||||
for _, v in pairs(cats) do catByName[v.name] = v end
|
||||
for _, v in pairs(customs) do
|
||||
-- Override default thing categories:
|
||||
local catName = v.default and (GAMEMODE.Config.CategoryOverride[catKind] or {})[v.name] or v.category or "Другое"
|
||||
local cat = catByName[catName]
|
||||
if not cat then
|
||||
DarkRP.errorNoHalt(string.format([[The category of "%s" ("%s") does not exist!]], v.name, catName), 3, {
|
||||
"Make sure the category is created with DarkRP.createCategory.",
|
||||
"The category name is case sensitive!",
|
||||
"Categories must be created before DarkRP finished loading."
|
||||
}, path, -1, path)
|
||||
cat = catByName.Другое
|
||||
end
|
||||
|
||||
cat.members = cat.members or {}
|
||||
table.insert(cat.members, v)
|
||||
end
|
||||
|
||||
-- Sort category members
|
||||
for _, v in pairs(cats) do table.sort(v.members, categoryOrder) end
|
||||
end
|
||||
|
||||
hook.Add("loadCustomDarkRPItems", "mergeCategories", function()
|
||||
local shipments = fn.Filter(fc{fn.Not, fp{fn.GetValue, "noship"}}, CustomShipments)
|
||||
local guns = fn.Filter(fp{fn.GetValue, "separate"}, CustomShipments)
|
||||
|
||||
mergeCategories(RPExtraTeams, "jobs", "your jobs")
|
||||
mergeCategories(DarkRPEntities, "entities", "your custom entities")
|
||||
mergeCategories(shipments, "shipments", "your custom shipments")
|
||||
mergeCategories(guns, "weapons", "your custom weapons")
|
||||
mergeCategories(CustomVehicles, "vehicles", "your custom vehicles")
|
||||
mergeCategories(GAMEMODE.AmmoTypes, "ammo", "your custom ammo")
|
||||
|
||||
categoriesMerged = true
|
||||
end)
|
||||
100
gamemodes/darkrp/gamemode/modules/base/sh_entityvars.lua
Normal file
100
gamemodes/darkrp/gamemode/modules/base/sh_entityvars.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
DarkRP.RegisteredDarkRPVarsMaxId = DarkRP.RegisteredDarkRPVarsMaxId or 0
|
||||
DarkRP.RegisteredDarkRPVars = DarkRP.RegisteredDarkRPVars or {}
|
||||
DarkRP.RegisteredDarkRPVarsById = DarkRP.RegisteredDarkRPVarsById or {}
|
||||
|
||||
-- the amount of bits assigned to the value that determines which DarkRPVar we're sending/receiving
|
||||
local DARKRP_ID_BITS = 8
|
||||
local UNKNOWN_DARKRPVAR = 255 -- Should be equal to 2^DARKRP_ID_BITS - 1
|
||||
DarkRP.DARKRP_ID_BITS = DARKRP_ID_BITS
|
||||
|
||||
function DarkRP.registerDarkRPVar(name, writeFn, readFn)
|
||||
-- After a reload, only update the write and read function
|
||||
if DarkRP.RegisteredDarkRPVars[name] then
|
||||
DarkRP.RegisteredDarkRPVars[name].writeFn = writeFn
|
||||
DarkRP.RegisteredDarkRPVars[name].readFn = readFn
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.RegisteredDarkRPVarsMaxId = DarkRP.RegisteredDarkRPVarsMaxId + 1
|
||||
|
||||
-- UNKNOWN_DARKRPVAR is reserved for unknown values
|
||||
if DarkRP.RegisteredDarkRPVarsMaxId >= UNKNOWN_DARKRPVAR then DarkRP.error(string.format("Too many DarkRPVar registrations! DarkRPVar '%s' triggered this error", name), 2) end
|
||||
|
||||
DarkRP.RegisteredDarkRPVars[name] = {id = DarkRP.RegisteredDarkRPVarsMaxId, name = name, writeFn = writeFn, readFn = readFn}
|
||||
DarkRP.RegisteredDarkRPVarsById[DarkRP.RegisteredDarkRPVarsMaxId] = DarkRP.RegisteredDarkRPVars[name]
|
||||
end
|
||||
|
||||
-- Unknown values have unknown types and unknown identifiers, so this is sent inefficiently
|
||||
local function writeUnknown(name, value)
|
||||
net.WriteUInt(UNKNOWN_DARKRPVAR, DARKRP_ID_BITS)
|
||||
net.WriteString(name)
|
||||
net.WriteType(value)
|
||||
end
|
||||
|
||||
-- Read the value of a DarkRPVar that was not registered
|
||||
local function readUnknown()
|
||||
return net.ReadString(), net.ReadType(net.ReadUInt(8))
|
||||
end
|
||||
|
||||
function DarkRP.writeNetDarkRPVar(name, value)
|
||||
local DarkRPVar = DarkRP.RegisteredDarkRPVars[name]
|
||||
if not DarkRPVar then return writeUnknown(name, value) end
|
||||
|
||||
net.WriteUInt(DarkRPVar.id, DARKRP_ID_BITS)
|
||||
return DarkRPVar.writeFn(value)
|
||||
end
|
||||
|
||||
function DarkRP.writeNetDarkRPVarRemoval(name)
|
||||
local DarkRPVar = DarkRP.RegisteredDarkRPVars[name]
|
||||
if not DarkRPVar then
|
||||
net.WriteUInt(UNKNOWN_DARKRPVAR, DARKRP_ID_BITS)
|
||||
net.WriteString(name)
|
||||
return
|
||||
end
|
||||
|
||||
net.WriteUInt(DarkRPVar.id, DARKRP_ID_BITS)
|
||||
end
|
||||
|
||||
function DarkRP.readNetDarkRPVar()
|
||||
local DarkRPVarId = net.ReadUInt(DARKRP_ID_BITS)
|
||||
local DarkRPVar = DarkRP.RegisteredDarkRPVarsById[DarkRPVarId]
|
||||
|
||||
if DarkRPVarId == UNKNOWN_DARKRPVAR then
|
||||
local name, value = readUnknown()
|
||||
|
||||
return name, value
|
||||
end
|
||||
|
||||
local val = DarkRPVar.readFn(value)
|
||||
|
||||
return DarkRPVar.name, val
|
||||
end
|
||||
|
||||
function DarkRP.readNetDarkRPVarRemoval()
|
||||
local id = net.ReadUInt(DARKRP_ID_BITS)
|
||||
return id == UNKNOWN_DARKRPVAR and net.ReadString() or DarkRP.RegisteredDarkRPVarsById[id].name
|
||||
end
|
||||
|
||||
-- The money is a double because it accepts higher values than Int and UInt, which are undefined for >32 bits
|
||||
DarkRP.registerDarkRPVar("money", net.WriteDouble, net.ReadDouble)
|
||||
DarkRP.registerDarkRPVar("salary", fp{fn.Flip(net.WriteInt), 32}, fp{net.ReadInt, 32})
|
||||
DarkRP.registerDarkRPVar("rpname", net.WriteString, net.ReadString)
|
||||
DarkRP.registerDarkRPVar("job", net.WriteString, net.ReadString)
|
||||
DarkRP.registerDarkRPVar("HasGunlicense", net.WriteBit, fc{tobool, net.ReadBit})
|
||||
DarkRP.registerDarkRPVar("Arrested", net.WriteBit, fc{tobool, net.ReadBit})
|
||||
DarkRP.registerDarkRPVar("wanted", net.WriteBit, fc{tobool, net.ReadBit})
|
||||
DarkRP.registerDarkRPVar("wantedReason", net.WriteString, net.ReadString)
|
||||
DarkRP.registerDarkRPVar("agenda", net.WriteString, net.ReadString)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
RP name override
|
||||
---------------------------------------------------------------------------]]
|
||||
local pmeta = FindMetaTable("Player")
|
||||
pmeta.SteamName = pmeta.SteamName or pmeta.Name
|
||||
function pmeta:Name()
|
||||
if not self:IsValid() then DarkRP.error("Attempt to call Name/Nick/GetName on a non-existing player!", SERVER and 1 or 2) end
|
||||
return GAMEMODE.Config.allowrpnames and self:getDarkRPVar("rpname")
|
||||
or self:SteamName()
|
||||
end
|
||||
pmeta.GetName = pmeta.Name
|
||||
pmeta.Nick = pmeta.Name
|
||||
@@ -0,0 +1,86 @@
|
||||
function GM:SetupMove(ply, mv, cmd)
|
||||
if ply:isArrested() then
|
||||
mv:SetMaxClientSpeed(self.Config.arrestspeed)
|
||||
end
|
||||
return self.Sandbox.SetupMove(self, ply, mv, cmd)
|
||||
end
|
||||
|
||||
function GM:StartCommand(ply, usrcmd)
|
||||
-- Used in arrest_stick and unarrest_stick but addons can use it too!
|
||||
local wep = ply:GetActiveWeapon()
|
||||
if wep:IsValid() and isfunction(wep.startDarkRPCommand) then
|
||||
wep:startDarkRPCommand(usrcmd)
|
||||
end
|
||||
end
|
||||
|
||||
function GM:OnPlayerChangedTeam(ply, oldTeam, newTeam)
|
||||
if RPExtraTeams[oldTeam] and RPExtraTeams[oldTeam].OnPlayerLeftTeam then
|
||||
RPExtraTeams[oldTeam].OnPlayerLeftTeam(ply, newTeam)
|
||||
end
|
||||
|
||||
if RPExtraTeams[newTeam] and RPExtraTeams[newTeam].OnPlayerChangedTeam then
|
||||
RPExtraTeams[newTeam].OnPlayerChangedTeam(ply, oldTeam, newTeam)
|
||||
end
|
||||
|
||||
if CLIENT then return end
|
||||
|
||||
local agenda = ply:getAgendaTable()
|
||||
|
||||
-- Remove agenda text when last manager left
|
||||
if agenda and agenda.ManagersByKey[oldTeam] then
|
||||
local found = false
|
||||
for man, _ in pairs(agenda.ManagersByKey) do
|
||||
if team.NumPlayers(man) > 0 then found = true break end
|
||||
end
|
||||
if not found then agenda.text = nil end
|
||||
end
|
||||
|
||||
ply:setSelfDarkRPVar("agenda", agenda and agenda.text or nil)
|
||||
end
|
||||
|
||||
hook.Add("loadCustomDarkRPItems", "CAMI privs", function()
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_SeeEvents",
|
||||
MinAccess = "admin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_GetAdminWeapons",
|
||||
MinAccess = "admin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_SetDoorOwner",
|
||||
MinAccess = "admin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_ChangeDoorSettings",
|
||||
MinAccess = "superadmin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_AdminCommands",
|
||||
MinAccess = "admin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_SetMoney",
|
||||
MinAccess = "superadmin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_SetLicense",
|
||||
MinAccess = "superadmin"
|
||||
}
|
||||
|
||||
for _, v in pairs(RPExtraTeams) do
|
||||
if not v.vote or v.admin and v.admin > 1 then continue end
|
||||
|
||||
local toAdmin = {[0] = "admin", [1] = "superadmin"}
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_GetJob_" .. v.command,
|
||||
MinAccess = toAdmin[v.admin or 0]-- Add privileges for the teams that are voted for
|
||||
}
|
||||
end
|
||||
end)
|
||||
1527
gamemodes/darkrp/gamemode/modules/base/sh_interface.lua
Normal file
1527
gamemodes/darkrp/gamemode/modules/base/sh_interface.lua
Normal file
File diff suppressed because it is too large
Load Diff
44
gamemodes/darkrp/gamemode/modules/base/sh_playerclass.lua
Normal file
44
gamemodes/darkrp/gamemode/modules/base/sh_playerclass.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
local PLAYER_CLASS = {}
|
||||
|
||||
-- Value of -1 = set to config value, if a corresponding setting exists
|
||||
PLAYER_CLASS.DisplayName = "DarkRP Base Player Class"
|
||||
PLAYER_CLASS.WalkSpeed = -1
|
||||
PLAYER_CLASS.RunSpeed = -1
|
||||
PLAYER_CLASS.DuckSpeed = 0.3
|
||||
PLAYER_CLASS.UnDuckSpeed = 0.3
|
||||
PLAYER_CLASS.TeammateNoCollide = false
|
||||
PLAYER_CLASS.StartHealth = -1
|
||||
|
||||
function PLAYER_CLASS:Loadout()
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:SetModel()
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:ShouldDrawLocal()
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:CreateMove(cmd)
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:CalcView(view)
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:GetHandsModel()
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:StartMove(mv, cmd)
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:FinishMove(mv)
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
player_manager.RegisterClass("player_darkrp", PLAYER_CLASS, "player_sandbox")
|
||||
86
gamemodes/darkrp/gamemode/modules/base/sh_simplerr.lua
Normal file
86
gamemodes/darkrp/gamemode/modules/base/sh_simplerr.lua
Normal file
@@ -0,0 +1,86 @@
|
||||
-- simplerrRun: Run a function with the given parameters and send any runtime errors to admins
|
||||
DarkRP.simplerrRun = fc{
|
||||
fn.Snd, -- On success ignore the first return value
|
||||
simplerr.wrapError,
|
||||
simplerr.wrapHook,
|
||||
simplerr.wrapLog,
|
||||
simplerr.safeCall
|
||||
}
|
||||
|
||||
-- error: throw a runtime error without exiting the stack
|
||||
-- parameters: msg, [stackNr], [hints], [path], [line]
|
||||
DarkRP.errorNoHalt = fc{
|
||||
simplerr.wrapHook,
|
||||
simplerr.wrapLog,
|
||||
simplerr.runError,
|
||||
function(msg, err, ...) return msg, err and err + 3 or 4, ... end -- Raise error level one higher
|
||||
}
|
||||
|
||||
-- error: throw a runtime error
|
||||
-- parameters: msg, [stackNr], [hints], [path], [line]
|
||||
DarkRP.error = fc{
|
||||
simplerr.wrapError,
|
||||
DarkRP.errorNoHalt
|
||||
}
|
||||
|
||||
-- Print errors from the server in the console and show a message in chat
|
||||
if CLIENT then
|
||||
local function showError(count, errs)
|
||||
local one = count == 1
|
||||
chat.AddText(Color(255, 0, 0), string.format("There %s %i Lua problem%s!", one and "is" or "are", count, one and "" or 's'))
|
||||
chat.AddText(color_white, "\tPlease check your console for more information!")
|
||||
chat.AddText(color_white, "\tNote: This error likely breaks your server. Make sure to solve the error!")
|
||||
|
||||
for i = 1, count do
|
||||
MsgC(Color(137, 222, 255), errs[i] .. "\n")
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive("DarkRP_simplerrError", function()
|
||||
local count = net.ReadUInt(16)
|
||||
local errs = {}
|
||||
|
||||
for i = 1, count do
|
||||
table.insert(errs, net.ReadString())
|
||||
end
|
||||
|
||||
showError(count, errs)
|
||||
end)
|
||||
hook.Add("onSimplerrError", "DarkRP_Simplerr", function(err) showError(1, {err}) end)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Serverside part
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
util.AddNetworkString("DarkRP_simplerrError")
|
||||
|
||||
-- Send all errors to the client
|
||||
local function sendErrors(plys, errs)
|
||||
local count = #errs
|
||||
local one = count == 1
|
||||
|
||||
DarkRP.notify(plys, 1, 120, string.format("There %s %i Lua problem%s!\nPlease check your console for more information!", one and "is" or "are", count, one and "" or 's'))
|
||||
net.Start("DarkRP_simplerrError")
|
||||
net.WriteUInt(#errs, 16)
|
||||
fn.ForEach(fn.Flip(net.WriteString), errs)
|
||||
net.Send(plys)
|
||||
end
|
||||
|
||||
-- Annoy all admins when an error occurs
|
||||
local function annoyAdmins(err)
|
||||
local admins = fn.Filter(plyMeta.IsAdmin, player.GetAll())
|
||||
sendErrors(admins, {err})
|
||||
end
|
||||
hook.Add("onSimplerrError", "DarkRP_Simplerr", annoyAdmins)
|
||||
|
||||
-- Annoy joining admin with errors
|
||||
local function annoyAdmin(ply)
|
||||
if not IsValid(ply) or not ply:IsAdmin() then return end
|
||||
local errs = table.Copy(simplerr.getLog())
|
||||
if table.IsEmpty(errs) then return end
|
||||
|
||||
fn.Map(fp{fn.GetValue, "err"}, errs)
|
||||
sendErrors(ply, errs)
|
||||
end
|
||||
hook.Add("PlayerInitialSpawn", "DarkRP_Simplerr", function(ply) timer.Simple(1, fp{annoyAdmin, ply}) end)
|
||||
432
gamemodes/darkrp/gamemode/modules/base/sh_util.lua
Normal file
432
gamemodes/darkrp/gamemode/modules/base/sh_util.lua
Normal file
@@ -0,0 +1,432 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
Utility functions
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
local vector = FindMetaTable("Vector")
|
||||
local meta = FindMetaTable("Player")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Decides whether the vector could be seen by the player if they were to look at it
|
||||
---------------------------------------------------------------------------]]
|
||||
function vector:isInSight(filter, ply)
|
||||
ply = ply or LocalPlayer()
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = self
|
||||
trace.filter = filter
|
||||
trace.mask = -1
|
||||
local TheTrace = util.TraceLine(trace)
|
||||
|
||||
return not TheTrace.Hit, TheTrace.HitPos
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Turn a money amount into a pretty string
|
||||
---------------------------------------------------------------------------]]
|
||||
local function attachCurrency(str)
|
||||
local config = GAMEMODE.Config
|
||||
return config.currencyLeft and config.currency .. str or str .. config.currency
|
||||
end
|
||||
|
||||
function DarkRP.formatMoney(n)
|
||||
if not n then return attachCurrency("0") end
|
||||
|
||||
if n >= 1e14 then return attachCurrency(tostring(n)) end
|
||||
if n <= -1e14 then return "-" .. attachCurrency(tostring(math.abs(n))) end
|
||||
|
||||
local config = GAMEMODE.Config
|
||||
|
||||
local negative = n < 0
|
||||
|
||||
n = tostring(math.abs(n))
|
||||
|
||||
local dp = string.find(n, ".", 1, true) or #n + 1
|
||||
|
||||
for i = dp - 4, 1, -3 do
|
||||
n = n:sub(1, i) .. config.currencyThousandSeparator .. n:sub(i + 1)
|
||||
end
|
||||
|
||||
-- Make sure the amount is padded with zeroes
|
||||
if n[#n - 1] == "." then
|
||||
n = n .. "0"
|
||||
end
|
||||
|
||||
return (negative and "-" or "") .. attachCurrency(n)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Find a player based on given information
|
||||
|
||||
Note that there is a searching priority:
|
||||
* UserID
|
||||
* SteamID64
|
||||
* SteamID
|
||||
* Nick
|
||||
* SteamName
|
||||
|
||||
Note also that there are _separate_ loops. This is to make sure the function
|
||||
gives the same result, regardless of the order in which players are iterated
|
||||
over.
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.findPlayer(info)
|
||||
if not info or info == "" then return nil end
|
||||
local pls = player.GetAll()
|
||||
|
||||
local count = #pls
|
||||
local numberInfo = tonumber(info)
|
||||
|
||||
-- First check if the input matches a player by UserID or SteamID64. This is
|
||||
-- only necessary if the input can be parsed as a number.
|
||||
if numberInfo then
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if numberInfo == v:UserID() then
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if info == v:SteamID64() then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local lowerInfo = string.lower(tostring(info))
|
||||
if string.StartsWith(lowerInfo, "steam_") then
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if info == v:SteamID() then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if string.find(string.lower(v:Nick()), lowerInfo, 1, true) ~= nil then
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if string.find(string.lower(v:SteamName()), lowerInfo, 1, true) ~= nil then
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Find multiple players based on a string criterium
|
||||
Taken from FAdmin]]
|
||||
---------------------------------------------------------------------------*/
|
||||
function DarkRP.findPlayers(info)
|
||||
if not info then return nil end
|
||||
local pls = player.GetAll()
|
||||
local found = {}
|
||||
local players
|
||||
|
||||
if string.lower(info) == "*" or string.lower(info) == "<all>" then return pls end
|
||||
|
||||
local InfoPlayers = {}
|
||||
for A in string.gmatch(info .. ";", "([a-zA-Z0-9:_.]*)[;(,%s)%c]") do
|
||||
if A ~= "" then
|
||||
table.insert(InfoPlayers, A)
|
||||
end
|
||||
end
|
||||
|
||||
for _, PlayerInfo in ipairs(InfoPlayers) do
|
||||
-- Playerinfo is always to be treated as UserID when it's a number
|
||||
-- otherwise people with numbers in their names could get confused with UserID's of other players
|
||||
if tonumber(PlayerInfo) then
|
||||
local foundPlayer = Player(PlayerInfo)
|
||||
if IsValid(foundPlayer) and not found[foundPlayer] then
|
||||
found[foundPlayer] = true
|
||||
players = players or {}
|
||||
table.insert(players, foundPlayer)
|
||||
end
|
||||
continue
|
||||
end
|
||||
|
||||
local stringPlayerInfo = string.lower(PlayerInfo)
|
||||
for _, v in ipairs(pls) do
|
||||
-- Prevent duplicates
|
||||
if found[v] then continue end
|
||||
local steamId = v:SteamID()
|
||||
|
||||
-- Find by Steam ID
|
||||
if (PlayerInfo == steamId or steamId == "UNKNOWN") or
|
||||
-- Find by Partial Nick
|
||||
string.find(string.lower(v:Nick()), stringPlayerInfo, 1, true) ~= nil or
|
||||
-- Find by steam name
|
||||
(v.SteamName and string.find(string.lower(v:SteamName()), stringPlayerInfo, 1, true) ~= nil) then
|
||||
found[v] = true
|
||||
players = players or {}
|
||||
table.insert(players, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
function meta:getEyeSightHitEntity(searchDistance, hitDistance, filter)
|
||||
searchDistance = searchDistance or 100
|
||||
hitDistance = (hitDistance or 15) * (hitDistance or 15)
|
||||
|
||||
filter = filter or function(p) return p:IsPlayer() and p ~= self end
|
||||
|
||||
self:LagCompensation(true)
|
||||
|
||||
local shootPos = self:GetShootPos()
|
||||
local entities = ents.FindInSphere(shootPos, searchDistance)
|
||||
local aimvec = self:GetAimVector()
|
||||
|
||||
local smallestDistance = math.huge
|
||||
local foundEnt
|
||||
|
||||
for _, ent in ipairs(entities) do
|
||||
if not IsValid(ent) or filter(ent) == false then continue end
|
||||
|
||||
local center = ent:GetPos()
|
||||
|
||||
-- project the center vector on the aim vector
|
||||
local projected = shootPos + (center - shootPos):Dot(aimvec) * aimvec
|
||||
|
||||
if aimvec:Dot((projected - shootPos):GetNormalized()) < 0 then continue end
|
||||
|
||||
-- the point on the model that has the smallest distance to your line of sight
|
||||
local nearestPoint = ent:NearestPoint(projected)
|
||||
local distance = nearestPoint:DistToSqr(projected)
|
||||
|
||||
if distance < smallestDistance then
|
||||
local trace = {
|
||||
start = self:GetShootPos(),
|
||||
endpos = nearestPoint,
|
||||
filter = {self, ent}
|
||||
}
|
||||
local traceLine = util.TraceLine(trace)
|
||||
if traceLine.Hit then continue end
|
||||
|
||||
smallestDistance = distance
|
||||
foundEnt = ent
|
||||
end
|
||||
end
|
||||
|
||||
self:LagCompensation(false)
|
||||
|
||||
if smallestDistance < hitDistance then
|
||||
return foundEnt, math.sqrt(smallestDistance)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Print the currently available vehicles
|
||||
---------------------------------------------------------------------------]]
|
||||
local function GetAvailableVehicles(ply)
|
||||
if SERVER and IsValid(ply) and not ply:IsAdmin() then return end
|
||||
local print = SERVER and ServerLog or Msg
|
||||
|
||||
print(DarkRP.getPhrase("rp_getvehicles") .. "\n")
|
||||
for k in pairs(DarkRP.getAvailableVehicles()) do
|
||||
print("\"" .. k .. "\"" .. "\n")
|
||||
end
|
||||
end
|
||||
if SERVER then
|
||||
concommand.Add("rp_getvehicles_sv", GetAvailableVehicles)
|
||||
else
|
||||
concommand.Add("rp_getvehicles", GetAvailableVehicles)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Whether a player has a DarkRP privilege
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:hasDarkRPPrivilege(priv)
|
||||
if FAdmin then
|
||||
return FAdmin.Access.PlayerHasPrivilege(self, priv)
|
||||
end
|
||||
return self:IsAdmin()
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Convenience function to return the players sorted by name
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.nickSortedPlayers()
|
||||
local plys = player.GetAll()
|
||||
table.sort(plys, function(a,b) return a:Nick() < b:Nick() end)
|
||||
return plys
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Convert a string to a table of arguments
|
||||
---------------------------------------------------------------------------]]
|
||||
local bitlshift, stringgmatch, stringsub, tableinsert = bit.lshift, string.gmatch, string.sub, table.insert
|
||||
function DarkRP.explodeArg(arg)
|
||||
local args = {}
|
||||
|
||||
local from, to, diff = 1, 0, 0
|
||||
local inQuotes, wasQuotes = false, false
|
||||
|
||||
for c in stringgmatch(arg, '.') do
|
||||
to = to + 1
|
||||
|
||||
if c == '"' then
|
||||
inQuotes = not inQuotes
|
||||
wasQuotes = true
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
if c == ' ' and not inQuotes then
|
||||
diff = wasQuotes and 1 or 0
|
||||
wasQuotes = false
|
||||
tableinsert(args, stringsub(arg, from + diff, to - 1 - diff))
|
||||
from = to + 1
|
||||
end
|
||||
end
|
||||
diff = wasQuotes and 1 or 0
|
||||
|
||||
if from ~= to + 1 then tableinsert(args, stringsub(arg, from + diff, to + 1 - bitlshift(diff, 1))) end
|
||||
|
||||
return args
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Initialize Physics, throw an error on failure
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.ValidatedPhysicsInit(ent, solidType, hint)
|
||||
solidType = solidType or SOLID_VPHYSICS
|
||||
|
||||
if ent:PhysicsInit(solidType) then return true end
|
||||
|
||||
local class = ent:GetClass()
|
||||
|
||||
if solidType == SOLID_BSP then
|
||||
DarkRP.errorNoHalt(string.format("%s has no physics and will be motionless", class), 2, {
|
||||
"Is this a brush model? SOLID_BSP physics cannot initialize on entities that don't have brush models",
|
||||
"The physics limit may have been hit",
|
||||
hint
|
||||
})
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
if solidType == SOLID_VPHYSICS then
|
||||
local mdl = ent:GetModel()
|
||||
|
||||
if not mdl or mdl == "" then
|
||||
DarkRP.errorNoHalt(string.format("Cannot init physics on entity \"%s\" because it has no model", class), 2, {hint})
|
||||
return false
|
||||
end
|
||||
|
||||
mdl = string.lower(mdl)
|
||||
|
||||
if util.IsValidProp(mdl) then
|
||||
-- Has physics, we must have hit the limit
|
||||
DarkRP.errorNoHalt(string.format("physics limit hit - %s will be motionless", class), 2, {hint})
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
if not file.Exists(mdl, "GAME") then
|
||||
DarkRP.errorNoHalt(string.format("%s has missing model \"%s\" and will be invisible and motionless", class, mdl), 2, {
|
||||
"Is the model path correct?",
|
||||
"Is the model from an addon that is not installed?",
|
||||
"Is the model from a game that isn't (properly) mounted? E.g. Counter Strike: Source",
|
||||
hint
|
||||
})
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
DarkRP.errorNoHalt(string.format("%s has model \"%s\" with no physics and will be motionless", class, mdl), 2, {
|
||||
"Does this model have an associated physics model (modelname.phy)?",
|
||||
"Is this model supposed to have physics? Many models, like effects and view models aren't made to have physics",
|
||||
hint
|
||||
})
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
DarkRP.errorNoHalt(string.format("Unable to initilize physics on entity \"%s\"", class, {hint}), 2)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Like tonumber, but makes sure it's an integer
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.toInt(value)
|
||||
value = tonumber(value)
|
||||
return value and math.floor(value)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Check the database for integrity errors. Use in cases when stuff doesn't load
|
||||
on restart, or you get corruption errors.
|
||||
---------------------------------------------------------------------------]]
|
||||
if SERVER then util.AddNetworkString("DarkRP_databaseCheckMessage") end
|
||||
if CLIENT then net.Receive("DarkRP_databaseCheckMessage", fc{print, net.ReadString}) end
|
||||
|
||||
local function checkDatabase(ply)
|
||||
local dbFile = SERVER and "sv.db" or "cl.db"
|
||||
local display = (CLIENT or not IsValid(ply)) and print or function(msg)
|
||||
net.Start("DarkRP_databaseCheckMessage")
|
||||
net.WriteString(msg)
|
||||
net.Send(ply)
|
||||
end
|
||||
|
||||
if SERVER and IsValid(ply) and not ply:IsSuperAdmin() then
|
||||
display("You must be superadmin")
|
||||
return
|
||||
end
|
||||
|
||||
if MySQLite and MySQLite.isMySQL() then
|
||||
display(string.format([[WARNING: DarkRP is using MySQL. This only
|
||||
checks the local SQLite database stored in the %s file in the
|
||||
garrysmod/ folder. The check will continue.]], dbFile))
|
||||
end
|
||||
|
||||
local check = sql.QueryValue("PRAGMA INTEGRITY_CHECK")
|
||||
if check == false then
|
||||
display([[The query to check the database failed. Shit's surely
|
||||
fucked, but the cause is unknown.]])
|
||||
return
|
||||
end
|
||||
|
||||
if check == "ok" then
|
||||
display(string.format("Your %s database file is good.", dbFile))
|
||||
return
|
||||
end
|
||||
|
||||
display(string.format([[There are errors in your %s database file. It's corrupt!
|
||||
|
||||
This can cause the following problems:
|
||||
- Data not loading, think of blocked models, doors, players' money and RP names
|
||||
- Settings resetting to their default values
|
||||
- Lua errors on startup
|
||||
|
||||
The cause of the problem is that the %s file in your garrysmod/ folder on
|
||||
%s is corrupt. How this came to be is unknown, but here's what you can do to solve it:
|
||||
|
||||
- Delete %s, and run a file integrity check. Warning: You will lose ALL data stored in it!
|
||||
- Take the file and try to repair it. This is sadly something that requires some technical knowledge,
|
||||
and may not always succeed.
|
||||
|
||||
The specific error, by the way, is as follows:
|
||||
%s
|
||||
]], dbFile, dbFile, SERVER and "the server" or "your own computer", dbFile, check))
|
||||
|
||||
end
|
||||
concommand.Add("darkrp_check_db_" .. (SERVER and "sv" or "cl"), checkDatabase)
|
||||
627
gamemodes/darkrp/gamemode/modules/base/sv_data.lua
Normal file
627
gamemodes/darkrp/gamemode/modules/base/sv_data.lua
Normal file
@@ -0,0 +1,627 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
Functions and variables
|
||||
---------------------------------------------------------------------------]]
|
||||
local setUpNonOwnableDoors,
|
||||
setUpTeamOwnableDoors,
|
||||
setUpGroupDoors,
|
||||
migrateDB
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Database initialize
|
||||
---------------------------------------------------------]]
|
||||
function DarkRP.initDatabase()
|
||||
MySQLite.begin()
|
||||
-- Gotta love the difference between SQLite and MySQL
|
||||
local is_mysql = MySQLite.isMySQL()
|
||||
local AUTOINCREMENT = is_mysql and "AUTO_INCREMENT" or "AUTOINCREMENT"
|
||||
-- in MySQL, the engine is set to InnoDB. InnoDB has been the default
|
||||
-- for a while, but people might be running old versions of MySQL.
|
||||
-- SQLite has no database engine, so it is not explicitly set.
|
||||
local ENGINE_INNODB = is_mysql and "ENGINE=InnoDB" or ""
|
||||
|
||||
-- Table that holds all position data (jail, spawns etc.)
|
||||
-- Queue these queries because other queries depend on the existence of the darkrp_position table
|
||||
-- Race conditions could occur if the queries are executed simultaneously
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_position(
|
||||
id INTEGER NOT NULL PRIMARY KEY ]] .. AUTOINCREMENT .. [[,
|
||||
map VARCHAR(45) NOT NULL,
|
||||
type CHAR(1) NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
z INTEGER NOT NULL
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- team spawns require extra data
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_jobspawn(
|
||||
id INTEGER NOT NULL PRIMARY KEY REFERENCES darkrp_position(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
|
||||
teamcmd VARCHAR(255) NOT NULL
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- This table is kept for compatibility with older addons and websites
|
||||
-- See https://github.com/FPtje/DarkRP/issues/819
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS playerinformation(
|
||||
uid BIGINT NOT NULL,
|
||||
steamID VARCHAR(50) NOT NULL PRIMARY KEY
|
||||
) ]] .. ENGINE_INNODB .. [[
|
||||
]])
|
||||
|
||||
-- Player information
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_player(
|
||||
uid BIGINT NOT NULL PRIMARY KEY,
|
||||
rpname VARCHAR(45),
|
||||
salary INTEGER NOT NULL DEFAULT 45,
|
||||
wallet BIGINT NOT NULL
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- Door data
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_door(
|
||||
idx INTEGER NOT NULL,
|
||||
map VARCHAR(45) NOT NULL,
|
||||
title VARCHAR(25),
|
||||
isLocked BOOLEAN,
|
||||
isDisabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY(idx, map)
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- Some doors are owned by certain teams
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_doorjobs(
|
||||
idx INTEGER NOT NULL,
|
||||
map VARCHAR(45) NOT NULL,
|
||||
job VARCHAR(255) NOT NULL,
|
||||
|
||||
PRIMARY KEY(idx, map, job)
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- Door groups
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_doorgroups(
|
||||
idx INTEGER NOT NULL,
|
||||
map VARCHAR(45) NOT NULL,
|
||||
doorgroup VARCHAR(100) NOT NULL,
|
||||
|
||||
PRIMARY KEY(idx, map)
|
||||
) ]] .. ENGINE_INNODB .. [[
|
||||
]])
|
||||
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_dbversion(version INTEGER NOT NULL PRIMARY KEY) ]] .. ENGINE_INNODB .. [[
|
||||
]])
|
||||
|
||||
-- Load the last DBVersion into DarkRP.DBVersion, to allow checks to see whether migration is needed.
|
||||
MySQLite.queueQuery([[
|
||||
SELECT MAX(version) AS version FROM darkrp_dbversion
|
||||
]], function(data)
|
||||
-- The database is created with the schema of the latest version. On
|
||||
-- initialization the version is not set yet. Set it to the latest
|
||||
-- version.
|
||||
if not data or not data[1] or not tonumber(data[1].version) then
|
||||
DarkRP.DBVersion = 20211228
|
||||
MySQLite.query([[
|
||||
REPLACE INTO darkrp_dbversion VALUES(20211228)
|
||||
]])
|
||||
else
|
||||
DarkRP.DBVersion = tonumber(data[1].version)
|
||||
end
|
||||
end)
|
||||
|
||||
MySQLite.commit(fp{migrateDB, -- Migrate the database
|
||||
function() -- Initialize the data after all the tables have been created
|
||||
setUpNonOwnableDoors()
|
||||
setUpTeamOwnableDoors()
|
||||
setUpGroupDoors()
|
||||
|
||||
if MySQLite.isMySQL() then -- In a listen server, the connection with the external database is often made AFTER the listen server host has joined,
|
||||
--so he walks around with the settings from the SQLite database
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
DarkRP.offlinePlayerData(v:SteamID(), function(data)
|
||||
local Data = data and data[1]
|
||||
if not IsValid(v) or not Data then return end
|
||||
|
||||
v:setDarkRPVar("rpname", Data.rpname)
|
||||
v:setSelfDarkRPVar("salary", Data.salary)
|
||||
v:setDarkRPVar("money", Data.wallet)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
hook.Call("DarkRPDBInitialized")
|
||||
end})
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Database migration
|
||||
backwards compatibility with older versions of DarkRP
|
||||
---------------------------------------------------------------------------]]
|
||||
function migrateDB(callback)
|
||||
-- Simple function that checks the database version, migrates if
|
||||
-- necessary, and recurses to perform the next migration, until the last
|
||||
-- migration has been performed.
|
||||
-- Calls callback when the migration is finished or not necessary.
|
||||
local function migrate(version)
|
||||
if version < 20160610 then
|
||||
MySQLite.begin()
|
||||
if MySQLite.isMySQL() then
|
||||
-- if only SQLite were this easy
|
||||
MySQLite.queueQuery([[DROP INDEX rpname ON darkrp_player]])
|
||||
else
|
||||
-- darkrp_player used to have a UNIQUE rpname field.
|
||||
-- This sucks, get rid of it
|
||||
MySQLite.queueQuery([[PRAGMA foreign_keys=OFF]])
|
||||
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS new_darkrp_player(
|
||||
uid BIGINT NOT NULL PRIMARY KEY,
|
||||
rpname VARCHAR(45),
|
||||
salary INTEGER NOT NULL DEFAULT 45,
|
||||
wallet INTEGER NOT NULL
|
||||
);
|
||||
]])
|
||||
|
||||
MySQLite.queueQuery([[INSERT INTO new_darkrp_player SELECT * FROM darkrp_player]])
|
||||
|
||||
MySQLite.queueQuery([[DROP TABLE darkrp_player]])
|
||||
|
||||
MySQLite.queueQuery([[ALTER TABLE new_darkrp_player RENAME TO darkrp_player]])
|
||||
|
||||
MySQLite.queueQuery([[PRAGMA foreign_keys=ON]])
|
||||
end
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20160610)]])
|
||||
MySQLite.commit(fp{migrate, 20160610})
|
||||
return
|
||||
end
|
||||
|
||||
if version < 20181013 then
|
||||
-- migrate from darkrp_jobown to darkrp_doorjobs
|
||||
MySQLite.tableExists("darkrp_jobown", function(exists)
|
||||
if not exists then migrate(20181013) return end
|
||||
|
||||
MySQLite.begin()
|
||||
-- Create a temporary table that links job IDs to job commands
|
||||
MySQLite.queueQuery("CREATE TABLE IF NOT EXISTS TempJobCommands(id INT NOT NULL PRIMARY KEY, cmd VARCHAR(255) NOT NULL);")
|
||||
if MySQLite.isMySQL() then
|
||||
local jobCommands = {}
|
||||
for k, v in pairs(RPExtraTeams) do
|
||||
table.insert(jobCommands, "(" .. k .. "," .. MySQLite.SQLStr(v.command) .. ")")
|
||||
end
|
||||
|
||||
-- This WOULD work with SQLite if the implementation in GMod wasn't out of date.
|
||||
MySQLite.queueQuery("INSERT IGNORE INTO TempJobCommands VALUES " .. table.concat(jobCommands, ",") .. ";")
|
||||
else
|
||||
for k, v in pairs(RPExtraTeams) do
|
||||
MySQLite.queueQuery("INSERT INTO TempJobCommands VALUES(" .. k .. ", " .. MySQLite.SQLStr(v.command) .. ");")
|
||||
end
|
||||
end
|
||||
|
||||
MySQLite.queueQuery("REPLACE INTO darkrp_doorjobs SELECT darkrp_jobown.idx AS idx, darkrp_jobown.map AS map, TempJobCommands.cmd AS job FROM darkrp_jobown JOIN TempJobCommands ON darkrp_jobown.job = TempJobCommands.id;")
|
||||
|
||||
-- Clean up the transition table and the old table
|
||||
MySQLite.queueQuery("DROP TABLE TempJobCommands;")
|
||||
MySQLite.queueQuery("DROP TABLE darkrp_jobown;")
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20181013)]])
|
||||
MySQLite.commit(fp{migrate, 20181013})
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if version < 20181014 then
|
||||
MySQLite.query([[SELECT * FROM darkrp_jobspawn]], function(oldData)
|
||||
oldData = oldData or {}
|
||||
MySQLite.begin()
|
||||
|
||||
MySQLite.queueQuery([[DROP TABLE darkrp_jobspawn]])
|
||||
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE darkrp_jobspawn(
|
||||
id INTEGER NOT NULL PRIMARY KEY REFERENCES darkrp_position(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
|
||||
teamcmd VARCHAR(255) NOT NULL
|
||||
);
|
||||
]])
|
||||
|
||||
for i, row in pairs(oldData) do
|
||||
local teamcmd = (RPExtraTeams[tonumber(row.team)] or {}).command
|
||||
if not teamcmd then continue end
|
||||
|
||||
MySQLite.queueQuery(string.format([[INSERT INTO darkrp_jobspawn(id, teamcmd) VALUES(%s, %s)]], row.id, MySQLite.SQLStr(teamcmd)))
|
||||
end
|
||||
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20181014)]])
|
||||
|
||||
MySQLite.commit(fp{migrate, 20181014})
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if version < 20190914 then
|
||||
MySQLite.begin()
|
||||
-- Migration not necessary for SQLite, since BIGINT and
|
||||
-- INTEGER are considered the same in SQLite
|
||||
-- https://www.sqlite.org/datatype3.html
|
||||
if MySQLite.isMySQL() then
|
||||
MySQLite.queueQuery([[DROP TRIGGER IF EXISTS JobPositionFKDelete]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_player MODIFY wallet BIGINT;]])
|
||||
end
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20190914)]])
|
||||
MySQLite.commit(fp{migrate, 20190914})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if version < 20211228 then
|
||||
MySQLite.begin()
|
||||
-- Migrate all tables to InnoDB if they weren't already.
|
||||
-- See https://github.com/FPtje/DarkRP/issues/3157
|
||||
if MySQLite.isMySQL() then
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_dbversion ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_door ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_doorgroups ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_doorjobs ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_jobspawn ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_player ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_position ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE playerinformation ENGINE = InnoDB;]])
|
||||
end
|
||||
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20211228)]])
|
||||
MySQLite.commit(fp{migrate, 20211228})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- All migrations finished
|
||||
callback()
|
||||
end
|
||||
migrate(DarkRP.DBVersion)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Players
|
||||
---------------------------------------------------------]]
|
||||
function DarkRP.storeRPName(ply, name)
|
||||
if not name or string.len(name) < 2 then return end
|
||||
hook.Call("onPlayerChangedName", nil, ply, ply:getDarkRPVar("rpname"), name)
|
||||
ply:setDarkRPVar("rpname", name)
|
||||
|
||||
MySQLite.query([[UPDATE darkrp_player SET rpname = ]] .. MySQLite.SQLStr(name) .. [[ WHERE UID = ]] .. ply:SteamID64() .. ";")
|
||||
MySQLite.query([[UPDATE darkrp_player SET rpname = ]] .. MySQLite.SQLStr(name) .. [[ WHERE UID = ]] .. ply:UniqueID() .. ";")
|
||||
end
|
||||
|
||||
function DarkRP.retrieveRPNames(name, callback)
|
||||
MySQLite.query("SELECT COUNT(*) AS count FROM darkrp_player WHERE rpname = " .. MySQLite.SQLStr(name) .. ";", function(r)
|
||||
callback(tonumber(r[1].count) > 0)
|
||||
end)
|
||||
end
|
||||
|
||||
function DarkRP.offlinePlayerData(steamid, callback, failed)
|
||||
local sid64 = util.SteamIDTo64(steamid)
|
||||
local uniqueid = util.CRC("gm_" .. string.upper(steamid) .. "_gm")
|
||||
|
||||
MySQLite.query(string.format([[REPLACE INTO playerinformation VALUES(%s, %s);]], MySQLite.SQLStr(sid64), MySQLite.SQLStr(steamid)), nil, failed)
|
||||
|
||||
local query = [[
|
||||
SELECT rpname, wallet, salary, "SID64" AS kind
|
||||
FROM darkrp_player
|
||||
where uid = %s
|
||||
|
||||
UNION
|
||||
|
||||
SELECT rpname, wallet, salary, "UniqueID" AS kind
|
||||
FROM darkrp_player
|
||||
where uid = %s
|
||||
;
|
||||
]]
|
||||
|
||||
MySQLite.query(
|
||||
query:format(sid64, uniqueid),
|
||||
function(data, ...)
|
||||
-- The database has no record of the player data in SteamID64 form
|
||||
-- Otherwise the first row would have kind SID64
|
||||
if data and data[1] and data[1].kind == "UniqueID" then
|
||||
-- The rpname must be unique
|
||||
-- adding a new row with uid = SteamID64, but the same rpname will remove the uid=UniqueID row
|
||||
|
||||
local replquery = [[
|
||||
REPLACE INTO darkrp_player(uid, rpname, wallet, salary)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
]]
|
||||
|
||||
MySQLite.begin()
|
||||
MySQLite.queueQuery(
|
||||
replquery:format(
|
||||
sid64,
|
||||
data[1].rpname == "NULL" and "NULL" or MySQLite.SQLStr(data[1].rpname),
|
||||
data[1].wallet,
|
||||
data[1].salary
|
||||
),
|
||||
nil,
|
||||
failed
|
||||
)
|
||||
MySQLite.commit()
|
||||
end
|
||||
|
||||
return callback and callback(data, ...)
|
||||
end
|
||||
, failed
|
||||
)
|
||||
end
|
||||
|
||||
function DarkRP.retrievePlayerData(ply, callback, failed, attempts, err)
|
||||
attempts = attempts or 0
|
||||
|
||||
if attempts > 3 then return failed(err) end
|
||||
|
||||
DarkRP.offlinePlayerData(ply:SteamID(), callback, function(sqlErr)
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
DarkRP.retrievePlayerData(ply, callback, failed, attempts + 1, sqlErr)
|
||||
end)
|
||||
end
|
||||
|
||||
function DarkRP.createPlayerData(ply, name, wallet, salary, onError)
|
||||
MySQLite.query([[REPLACE INTO darkrp_player VALUES(]] ..
|
||||
ply:SteamID64() .. [[, ]] ..
|
||||
MySQLite.SQLStr(utf8.force(name)) .. [[, ]] ..
|
||||
salary .. [[, ]] ..
|
||||
wallet .. ");", nil, onError)
|
||||
|
||||
-- Backwards compatibility
|
||||
MySQLite.query([[REPLACE INTO darkrp_player VALUES(]] ..
|
||||
ply:UniqueID() .. [[, ]] ..
|
||||
MySQLite.SQLStr(utf8.force(name)) .. [[, ]] ..
|
||||
salary .. [[, ]] ..
|
||||
wallet .. ");", nil, onError)
|
||||
end
|
||||
|
||||
function DarkRP.storeMoney(ply, amount)
|
||||
if not isnumber(amount) or amount < 0 or amount >= 1 / 0 then
|
||||
DarkRP.errorNoHalt("Some addon attempted to store a invalid money amount " .. tostring(amount) .. " for Player " .. ply:Nick() .. " (" .. ply:SteamID() .. ")", 1, {
|
||||
"This money amount will not be stored in the database, but it may be set in the game.",
|
||||
"The database simply stores the last valid, non-negative amount of money.",
|
||||
"Please try to find the very first time this error happened for this player. Then look at the files mentioned in this error.",
|
||||
"That will tell you which addon is causing this.",
|
||||
"IMPORTANT: This is NOT a DarkRP bug!",
|
||||
"Note: The player can simply rejoin to fix their negative money, until whatever causes this happens again."
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
-- Also keep deprecated UniqueID data at least somewhat up to date
|
||||
MySQLite.query([[UPDATE darkrp_player SET wallet = ]] .. amount .. [[ WHERE uid = ]] .. ply:UniqueID() .. [[ OR uid = ]] .. ply:SteamID64())
|
||||
end
|
||||
|
||||
function DarkRP.storeOfflineMoney(sid64, amount)
|
||||
if isnumber(sid64) or isstring(sid64) and string.len(sid64) < 17 then -- smaller than 76561197960265728 is not a SteamID64
|
||||
DarkRP.errorNoHalt([[Some addon is giving DarkRP.storeOfflineMoney a UniqueID as its first argument, but this function now expects a SteamID64]], 1, {
|
||||
"The function used to take UniqueIDs, but it does not anymore.",
|
||||
"If you are a server owner, please look closely to the files mentioned in this error",
|
||||
"After all, these files will tell you WHICH addon is doing it",
|
||||
"This is NOT a DarkRP bug!",
|
||||
"Your server will continue working normally",
|
||||
"But whichever addon just tried to store an offline player's money",
|
||||
"Will NOT take effect!"
|
||||
})
|
||||
end
|
||||
|
||||
if not isnumber(amount) or amount < 0 or amount >= 1 / 0 then
|
||||
DarkRP.errorNoHalt("Some addon attempted to store a invalid money amount " .. tostring(amount) .. " for an offline player with steamID64 " .. sid64, 1, {
|
||||
"This money amount will not be stored in the database.",
|
||||
"Please try to find the very first time this error happened for this player. Then look at the files mentioned in this error.",
|
||||
"That will tell you which addon is causing this.",
|
||||
"IMPORTANT: This is NOT a DarkRP bug!"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
-- Also store on deprecated UniqueID
|
||||
local uniqueid = util.CRC("gm_" .. string.upper(util.SteamIDFrom64(sid64)) .. "_gm")
|
||||
MySQLite.query([[UPDATE darkrp_player SET wallet = ]] .. amount .. [[ WHERE uid = ]] .. uniqueid .. [[ OR uid = ]] .. sid64)
|
||||
end
|
||||
|
||||
local function resetAllMoney(ply, cmd, args)
|
||||
if ply:EntIndex() ~= 0 and not ply:IsSuperAdmin() then return end
|
||||
MySQLite.query("UPDATE darkrp_player SET wallet = " .. GAMEMODE.Config.startingmoney .. " ;")
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
v:setDarkRPVar("money", GAMEMODE.Config.startingmoney)
|
||||
end
|
||||
if ply:IsPlayer() then
|
||||
DarkRP.notifyAll(0, 4, DarkRP.getPhrase("reset_money", ply:Nick()))
|
||||
else
|
||||
DarkRP.notifyAll(0, 4, DarkRP.getPhrase("reset_money", "Console"))
|
||||
end
|
||||
end
|
||||
concommand.Add("rp_resetallmoney", resetAllMoney)
|
||||
|
||||
function DarkRP.storeSalary(ply, amount)
|
||||
ply:setSelfDarkRPVar("salary", math.floor(amount))
|
||||
|
||||
return amount
|
||||
end
|
||||
|
||||
function DarkRP.retrieveSalary(ply, callback)
|
||||
local val =
|
||||
ply:getJobTable() and ply:getJobTable().salary or
|
||||
RPExtraTeams[GAMEMODE.DefaultTeam].salary or
|
||||
(GM or GAMEMODE).Config.normalsalary
|
||||
|
||||
if callback then callback(val) end
|
||||
|
||||
return val
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Players
|
||||
---------------------------------------------------------------------------]]
|
||||
local meta = FindMetaTable("Player")
|
||||
function meta:restorePlayerData()
|
||||
self.DarkRPUnInitialized = true
|
||||
|
||||
local function onError(err)
|
||||
if not IsValid(self) then return end
|
||||
self.DarkRPUnInitialized = true -- no information should be saved from here, or the playerdata might be reset
|
||||
|
||||
self:setDarkRPVar("money", GAMEMODE.Config.startingmoney)
|
||||
self:setSelfDarkRPVar("salary", DarkRP.retrieveSalary(self))
|
||||
local name = string.sub(string.gsub(self:SteamName(), "\\\"", "\""), 1, 30)
|
||||
self:setDarkRPVar("rpname", name)
|
||||
|
||||
self.DarkRPDataRetrievalFailed = true -- marker on the player that says shit is fucked
|
||||
DarkRP.error("Failed to retrieve player information from the database. ", nil, {"This means your database or the connection to your database is fucked.", "This is the error given by the database:\n\t\t" .. tostring(err)})
|
||||
end
|
||||
|
||||
DarkRP.retrievePlayerData(self, function(data)
|
||||
if not IsValid(self) then return end
|
||||
|
||||
self.DarkRPUnInitialized = nil
|
||||
|
||||
local info = data and data[1] or {}
|
||||
if not info.rpname or info.rpname == "NULL" then info.rpname = string.sub(string.gsub(self:SteamName(), "\\\"", "\""), 1, 30) end
|
||||
|
||||
info.wallet = info.wallet or GAMEMODE.Config.startingmoney
|
||||
info.salary = DarkRP.retrieveSalary(self)
|
||||
|
||||
self:setDarkRPVar("money", tonumber(info.wallet))
|
||||
self:setSelfDarkRPVar("salary", tonumber(info.salary))
|
||||
|
||||
self:setDarkRPVar("rpname", info.rpname)
|
||||
|
||||
if not data then
|
||||
info = hook.Call("onPlayerFirstJoined", nil, self, info) or info
|
||||
DarkRP.createPlayerData(self, info.rpname, info.wallet, info.salary, onError)
|
||||
end
|
||||
end, onError)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Doors
|
||||
---------------------------------------------------------]]
|
||||
function DarkRP.storeDoorData(ent)
|
||||
if not ent:CreatedByMap() then return end
|
||||
local map = string.lower(game.GetMap())
|
||||
local nonOwnable = ent:getKeysNonOwnable()
|
||||
local title = ent:getKeysTitle()
|
||||
|
||||
MySQLite.query([[REPLACE INTO darkrp_door VALUES(]] .. ent:doorIndex() .. [[, ]] .. MySQLite.SQLStr(map) .. [[, ]] .. (title and MySQLite.SQLStr(title) or "NULL") .. [[, ]] .. "NULL" .. [[, ]] .. (nonOwnable and 1 or 0) .. [[);]])
|
||||
end
|
||||
|
||||
function setUpNonOwnableDoors()
|
||||
MySQLite.query("SELECT idx, title, isLocked, isDisabled FROM darkrp_door WHERE map = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";", function(r)
|
||||
if not r then return end
|
||||
|
||||
for _, row in pairs(r) do
|
||||
local e = DarkRP.doorIndexToEnt(tonumber(row.idx))
|
||||
|
||||
if not IsValid(e) then continue end
|
||||
if e:isKeysOwnable() then
|
||||
if tobool(row.isDisabled) then
|
||||
e:setKeysNonOwnable(tobool(row.isDisabled))
|
||||
end
|
||||
if row.isLocked and row.isLocked ~= "NULL" then
|
||||
e:Fire((tobool(row.isLocked) and "" or "un") .. "lock", "", 0)
|
||||
end
|
||||
e:setKeysTitle(row.title ~= "NULL" and row.title or nil)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local keyValueActions = {
|
||||
["DarkRPNonOwnable"] = function(ent, val) ent:setKeysNonOwnable(tobool(val)) end,
|
||||
["DarkRPTitle"] = function(ent, val) ent:setKeysTitle(val) end,
|
||||
["DarkRPDoorGroup"] = function(ent, val) if RPExtraTeamDoors[val] then ent:setDoorGroup(val) end end,
|
||||
["DarkRPCanLockpick"] = function(ent, val) ent.DarkRPCanLockpick = tobool(val) end
|
||||
}
|
||||
|
||||
local function onKeyValue(ent, key, value)
|
||||
if not ent:isDoor() then return end
|
||||
|
||||
if keyValueActions[key] then
|
||||
keyValueActions[key](ent, value)
|
||||
end
|
||||
end
|
||||
hook.Add("EntityKeyValue", "darkrp_doors", onKeyValue)
|
||||
|
||||
function DarkRP.storeTeamDoorOwnability(ent)
|
||||
if not ent:CreatedByMap() then return end
|
||||
local map = string.lower(game.GetMap())
|
||||
|
||||
MySQLite.query("DELETE FROM darkrp_doorjobs WHERE idx = " .. ent:doorIndex() .. " AND map = " .. MySQLite.SQLStr(map) .. ";")
|
||||
for k in pairs(ent:getKeysDoorTeams() or {}) do
|
||||
MySQLite.query("INSERT INTO darkrp_doorjobs VALUES(" .. ent:doorIndex() .. ", " .. MySQLite.SQLStr(map) .. ", " .. MySQLite.SQLStr(RPExtraTeams[k].command) .. ");")
|
||||
end
|
||||
end
|
||||
|
||||
function setUpTeamOwnableDoors()
|
||||
MySQLite.query("SELECT idx, job FROM darkrp_doorjobs WHERE map = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";", function(r)
|
||||
if not r then return end
|
||||
local map = string.lower(game.GetMap())
|
||||
|
||||
for _, row in pairs(r) do
|
||||
row.idx = tonumber(row.idx)
|
||||
|
||||
local e = DarkRP.doorIndexToEnt(row.idx)
|
||||
if not IsValid(e) then continue end
|
||||
|
||||
local _, job = DarkRP.getJobByCommand(row.job)
|
||||
|
||||
if job then
|
||||
e:addKeysDoorTeam(job)
|
||||
else
|
||||
print(("can't find job %s for door %d, removing from database"):format(row.job, row.idx))
|
||||
MySQLite.query(("DELETE FROM darkrp_doorjobs WHERE idx = %d AND map = %s AND job = %s;"):format(row.idx, MySQLite.SQLStr(map), MySQLite.SQLStr(row.job)))
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function DarkRP.storeDoorGroup(ent, group)
|
||||
if not ent:CreatedByMap() then return end
|
||||
local map = MySQLite.SQLStr(string.lower(game.GetMap()))
|
||||
local index = ent:doorIndex()
|
||||
|
||||
if group == "" or not group then
|
||||
MySQLite.query("DELETE FROM darkrp_doorgroups WHERE map = " .. map .. " AND idx = " .. index .. ";")
|
||||
return
|
||||
end
|
||||
|
||||
MySQLite.query("REPLACE INTO darkrp_doorgroups VALUES(" .. index .. ", " .. map .. ", " .. MySQLite.SQLStr(group) .. ");");
|
||||
end
|
||||
|
||||
function setUpGroupDoors()
|
||||
local map = MySQLite.SQLStr(string.lower(game.GetMap()))
|
||||
MySQLite.query("SELECT idx, doorgroup FROM darkrp_doorgroups WHERE map = " .. map, function(data)
|
||||
if not data then return end
|
||||
|
||||
for _, row in pairs(data) do
|
||||
local ent = DarkRP.doorIndexToEnt(tonumber(row.idx))
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() then
|
||||
continue
|
||||
end
|
||||
|
||||
if not RPExtraTeamDoorIDs[row.doorgroup] then continue end
|
||||
ent:setDoorGroup(row.doorgroup)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
hook.Add("PostCleanupMap", "DarkRP.hooks", function()
|
||||
setUpNonOwnableDoors()
|
||||
setUpTeamOwnableDoors()
|
||||
setUpGroupDoors()
|
||||
end)
|
||||
304
gamemodes/darkrp/gamemode/modules/base/sv_entityvars.lua
Normal file
304
gamemodes/darkrp/gamemode/modules/base/sv_entityvars.lua
Normal file
@@ -0,0 +1,304 @@
|
||||
local meta = FindMetaTable("Player")
|
||||
|
||||
DarkRP.ServerDarkRPVars = DarkRP.ServerDarkRPVars or {}
|
||||
DarkRP.ServerPrivateDarkRPVars = DarkRP.ServerPrivateDarkRPVars or {}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Pooled networking strings
|
||||
---------------------------------------------------------------------------]]
|
||||
util.AddNetworkString("DarkRP_InitializeVars")
|
||||
util.AddNetworkString("DarkRP_PlayerVar")
|
||||
util.AddNetworkString("DarkRP_PlayerVarRemoval")
|
||||
util.AddNetworkString("DarkRP_DarkRPVarDisconnect")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Player vars
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
local warningsShown = {}
|
||||
local function checkDarkRPVarRegistration(name)
|
||||
local DarkRPVar = DarkRP.RegisteredDarkRPVars[name]
|
||||
if DarkRPVar then return end
|
||||
|
||||
if warningsShown[name] then return end
|
||||
warningsShown[name] = true
|
||||
|
||||
DarkRP.errorNoHalt(string.format([[Warning! DarkRPVar '%s' wasn't registered!
|
||||
Please contact the author of the DarkRP Addon to fix this.
|
||||
Until this is fixed you don't need to worry about anything. Everything will keep working.
|
||||
It's just that registering DarkRPVars would make DarkRP faster.]], name), 4)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Remove a player's DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:removeDarkRPVar(var, target)
|
||||
local vars = DarkRP.ServerDarkRPVars[self]
|
||||
hook.Call("DarkRPVarChanged", nil, self, var, vars and vars[var], nil)
|
||||
target = target or player.GetAll()
|
||||
|
||||
DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {}
|
||||
DarkRP.ServerDarkRPVars[self][var] = nil
|
||||
|
||||
checkDarkRPVarRegistration(var)
|
||||
|
||||
net.Start("DarkRP_PlayerVarRemoval")
|
||||
net.WriteUInt(self:UserID(), 16)
|
||||
DarkRP.writeNetDarkRPVarRemoval(var)
|
||||
net.Send(target)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Set a player's DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:setDarkRPVar(var, value, target)
|
||||
target = target or player.GetAll()
|
||||
|
||||
if value == nil then return self:removeDarkRPVar(var, target) end
|
||||
|
||||
local vars = DarkRP.ServerDarkRPVars[self]
|
||||
hook.Call("DarkRPVarChanged", nil, self, var, vars and vars[var], value)
|
||||
|
||||
DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {}
|
||||
DarkRP.ServerDarkRPVars[self][var] = value
|
||||
|
||||
checkDarkRPVarRegistration(var)
|
||||
|
||||
net.Start("DarkRP_PlayerVar")
|
||||
net.WriteUInt(self:UserID(), 16)
|
||||
DarkRP.writeNetDarkRPVar(var, value)
|
||||
net.Send(target)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Set a private DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:setSelfDarkRPVar(var, value)
|
||||
DarkRP.ServerPrivateDarkRPVars[self] = DarkRP.ServerPrivateDarkRPVars[self] or {}
|
||||
DarkRP.ServerPrivateDarkRPVars[self][var] = true
|
||||
|
||||
self:setDarkRPVar(var, value, self)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Get a DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:getDarkRPVar(var, fallback)
|
||||
local vars = DarkRP.ServerDarkRPVars[self]
|
||||
if vars == nil then return fallback end
|
||||
|
||||
local results = vars[var]
|
||||
if results == nil then return fallback end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Backwards compatibility: Set ply.DarkRPVars attribute
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:setDarkRPVarsAttribute()
|
||||
DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {}
|
||||
-- With a reference to the table, ply.DarkRPVars should always remain
|
||||
-- up-to-date. One needs only be careful that DarkRP.ServerDarkRPVars[ply]
|
||||
-- is never replaced by a different table.
|
||||
self.DarkRPVars = DarkRP.ServerDarkRPVars[self]
|
||||
end
|
||||
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Send the DarkRPVars to a client
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:sendDarkRPVars()
|
||||
if self:EntIndex() == 0 then return end
|
||||
|
||||
local plys = player.GetAll()
|
||||
|
||||
net.Start("DarkRP_InitializeVars")
|
||||
net.WriteUInt(#plys, 8)
|
||||
for _, target in ipairs(plys) do
|
||||
net.WriteUInt(target:UserID(), 16)
|
||||
|
||||
local vars = {}
|
||||
for var, value in pairs(DarkRP.ServerDarkRPVars[target] or {}) do
|
||||
if self ~= target and (DarkRP.ServerPrivateDarkRPVars[target] or {})[var] then continue end
|
||||
table.insert(vars, var)
|
||||
end
|
||||
|
||||
local vars_cnt = #vars
|
||||
net.WriteUInt(vars_cnt, DarkRP.DARKRP_ID_BITS + 2) -- Allow for three times as many unknown DarkRPVars than the limit
|
||||
for i = 1, vars_cnt, 1 do
|
||||
DarkRP.writeNetDarkRPVar(vars[i], DarkRP.ServerDarkRPVars[target][vars[i]])
|
||||
end
|
||||
end
|
||||
net.Send(self)
|
||||
end
|
||||
concommand.Add("_sendDarkRPvars", function(ply)
|
||||
if ply.DarkRPVarsSent and ply.DarkRPVarsSent > (CurTime() - 3) then return end -- prevent spammers
|
||||
ply.DarkRPVarsSent = CurTime()
|
||||
ply:sendDarkRPVars()
|
||||
end)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Admin DarkRPVar commands
|
||||
---------------------------------------------------------------------------]]
|
||||
local function setRPName(ply, args)
|
||||
if not args[2] or string.len(args[2]) < 2 or string.len(args[2]) > 30 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "<2/>30"))
|
||||
return
|
||||
end
|
||||
|
||||
local name = table.concat(args, " ", 2)
|
||||
|
||||
local target = DarkRP.findPlayer(args[1])
|
||||
|
||||
if not target then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args[1]))
|
||||
return
|
||||
end
|
||||
|
||||
local oldname = target:Nick()
|
||||
|
||||
DarkRP.retrieveRPNames(name, function(taken)
|
||||
if not IsValid(target) then return end
|
||||
|
||||
if taken then
|
||||
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("unable", "RPname", DarkRP.getPhrase("already_taken")))
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.storeRPName(target, name)
|
||||
target:setDarkRPVar("rpname", name)
|
||||
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_set_x_name", oldname, name))
|
||||
|
||||
local nick = ""
|
||||
if ply:EntIndex() == 0 then
|
||||
nick = "Console"
|
||||
else
|
||||
nick = ply:Nick()
|
||||
end
|
||||
DarkRP.notify(target, 0, 4, DarkRP.getPhrase("x_set_your_name", nick, name))
|
||||
if ply:EntIndex() == 0 then
|
||||
DarkRP.log("Console set " .. target:SteamName() .. "'s name to " .. name, Color(30, 30, 30))
|
||||
else
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") set " .. target:SteamName() .. "'s name to " .. name, Color(30, 30, 30))
|
||||
end
|
||||
end)
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("forcerpname", "DarkRP_AdminCommands", setRPName)
|
||||
|
||||
local function freerpname(ply, args)
|
||||
local name = args ~= "" and args or IsValid(ply) and ply:Nick() or ""
|
||||
|
||||
MySQLite.query(("UPDATE darkrp_player SET rpname = NULL WHERE rpname = %s"):format(MySQLite.SQLStr(name)))
|
||||
|
||||
local nick = IsValid(ply) and ply:Nick() or "Console"
|
||||
DarkRP.log(("%s has freed the rp name '%s'"):format(nick, name), Color(30, 30, 30))
|
||||
DarkRP.notify(ply, 0, 4, ("'%s' has been freed"):format(name))
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("freerpname", "DarkRP_AdminCommands", freerpname)
|
||||
|
||||
local function RPName(ply, args)
|
||||
if ply.LastNameChange and ply.LastNameChange > (CurTime() - 5) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(5 - (CurTime() - ply.LastNameChange)), "/rpname"))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not GAMEMODE.Config.allowrpnames then
|
||||
DarkRP.notify(ply, 1, 6, DarkRP.getPhrase("disabled", "/rpname", ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
args = args:find"^%s*$" and '' or args:match"^%s*(.*%S)"
|
||||
|
||||
local canChangeName, reason = hook.Call("CanChangeRPName", GAMEMODE, ply, args)
|
||||
if canChangeName == false then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/rpname", reason or ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
ply:setRPName(args)
|
||||
ply.LastNameChange = CurTime()
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("rpname", RPName)
|
||||
DarkRP.defineChatCommand("name", RPName)
|
||||
DarkRP.defineChatCommand("nick", RPName)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Setting the RP name
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:setRPName(name, firstRun)
|
||||
-- Make sure nobody on this server already has this RP name
|
||||
local lowername = string.lower(tostring(name))
|
||||
DarkRP.retrieveRPNames(name, function(taken)
|
||||
if not IsValid(self) or string.len(lowername) < 2 and not firstrun then return end
|
||||
-- If we found that this name exists for another player
|
||||
if taken then
|
||||
if firstRun then
|
||||
-- If we just connected and another player happens to be using our steam name as their RP name
|
||||
-- Put a 1 after our steam name
|
||||
DarkRP.storeRPName(self, name .. " 1")
|
||||
DarkRP.notify(self, 0, 12, DarkRP.getPhrase("someone_stole_steam_name"))
|
||||
else
|
||||
DarkRP.notify(self, 1, 5, DarkRP.getPhrase("unable", "/rpname", DarkRP.getPhrase("already_taken")))
|
||||
return ""
|
||||
end
|
||||
else
|
||||
if not firstRun then -- Don't save the steam name in the database
|
||||
DarkRP.notifyAll(2, 6, DarkRP.getPhrase("rpname_changed", self:SteamName(), name))
|
||||
DarkRP.storeRPName(self, name)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Maximum entity values
|
||||
---------------------------------------------------------------------------]]
|
||||
local maxEntities = {}
|
||||
function meta:addCustomEntity(entTable)
|
||||
maxEntities[self] = maxEntities[self] or {}
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] + 1
|
||||
end
|
||||
|
||||
function meta:removeCustomEntity(entTable)
|
||||
maxEntities[self] = maxEntities[self] or {}
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] - 1
|
||||
end
|
||||
|
||||
function meta:customEntityLimitReached(entTable)
|
||||
maxEntities[self] = maxEntities[self] or {}
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0
|
||||
local max = entTable.getMax and entTable.getMax(self) or entTable.max
|
||||
|
||||
return max ~= 0 and maxEntities[self][entTable.cmd] >= max
|
||||
end
|
||||
|
||||
function meta:customEntityCount(entTable)
|
||||
local entities = maxEntities[self]
|
||||
if entities == nil then return 0 end
|
||||
|
||||
entities = entities[entTable.cmd]
|
||||
if entities == nil then return 0 end
|
||||
|
||||
return entities
|
||||
end
|
||||
|
||||
-- We use EntityRemoved to clear players of tables, because it is always called
|
||||
-- after the PlayerDisconnected hook. This is called _after_ the GAMEMODE
|
||||
-- function, to make sure that all regular hooks can still use DarkRPVars until
|
||||
-- the very end. See https://github.com/FPtje/DarkRP/pull/3270
|
||||
(GAMEMODE or GM).DarkRPPostEntityRemoved = function(_gm, ent)
|
||||
if not ent:IsPlayer() then return end
|
||||
|
||||
maxEntities[ent] = nil
|
||||
DarkRP.ServerDarkRPVars[ent] = nil
|
||||
DarkRP.ServerPrivateDarkRPVars[ent] = nil
|
||||
|
||||
net.Start("DarkRP_DarkRPVarDisconnect")
|
||||
net.WriteUInt(ent:UserID(), 16)
|
||||
net.Broadcast()
|
||||
end
|
||||
1146
gamemodes/darkrp/gamemode/modules/base/sv_gamemode_functions.lua
Normal file
1146
gamemodes/darkrp/gamemode/modules/base/sv_gamemode_functions.lua
Normal file
File diff suppressed because it is too large
Load Diff
1325
gamemodes/darkrp/gamemode/modules/base/sv_interface.lua
Normal file
1325
gamemodes/darkrp/gamemode/modules/base/sv_interface.lua
Normal file
File diff suppressed because it is too large
Load Diff
38
gamemodes/darkrp/gamemode/modules/base/sv_jobmodels.lua
Normal file
38
gamemodes/darkrp/gamemode/modules/base/sv_jobmodels.lua
Normal file
@@ -0,0 +1,38 @@
|
||||
util.AddNetworkString("DarkRP_preferredjobmodels")
|
||||
util.AddNetworkString("DarkRP_preferredjobmodel")
|
||||
|
||||
local preferredJobModels = {}
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
local received = {}
|
||||
net.Receive("DarkRP_preferredjobmodels", function(len, ply)
|
||||
preferredJobModels[ply] = {}
|
||||
|
||||
for i in pairs(RPExtraTeams) do
|
||||
if net.ReadBit() == 0 then continue end
|
||||
|
||||
preferredJobModels[ply][i] = net.ReadString()
|
||||
end
|
||||
|
||||
if not received[ply] and preferredJobModels[ply][ply:Team()] then
|
||||
gamemode.Call("PlayerSetModel", ply)
|
||||
end
|
||||
|
||||
received[ply] = true
|
||||
end)
|
||||
|
||||
net.Receive("DarkRP_preferredjobmodel", function(len, ply)
|
||||
local teamNr = net.ReadUInt(8)
|
||||
local model = net.ReadString()
|
||||
|
||||
if not RPExtraTeams[teamNr] then return end
|
||||
|
||||
preferredJobModels[ply] = preferredJobModels[ply] or {}
|
||||
preferredJobModels[ply][teamNr] = model
|
||||
end)
|
||||
|
||||
function plyMeta:getPreferredModel(TeamNr)
|
||||
preferredJobModels[self] = preferredJobModels[self] or {}
|
||||
|
||||
return preferredJobModels[self][TeamNr]
|
||||
end
|
||||
466
gamemodes/darkrp/gamemode/modules/base/sv_purchasing.lua
Normal file
466
gamemodes/darkrp/gamemode/modules/base/sv_purchasing.lua
Normal file
@@ -0,0 +1,466 @@
|
||||
function DarkRP.hooks:canBuyPistol(ply, shipment)
|
||||
local price = shipment.getPrice and shipment.getPrice(ply, shipment.pricesep) or shipment.pricesep or 0
|
||||
|
||||
if not GAMEMODE:CustomObjFitsMap(shipment) then
|
||||
return false, false, "Custom object does not fit map"
|
||||
end
|
||||
|
||||
if ply:isArrested() then
|
||||
return false, false, DarkRP.getPhrase("unable", "/buy", "")
|
||||
end
|
||||
|
||||
if shipment.customCheck and not shipment.customCheck(ply) then
|
||||
local message = isfunction(shipment.CustomCheckFailMsg) and shipment.CustomCheckFailMsg(ply, shipment) or
|
||||
shipment.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
return false, false, message
|
||||
end
|
||||
|
||||
if not ply:canAfford(price) then
|
||||
return false, false, DarkRP.getPhrase("cant_afford", "/buy")
|
||||
end
|
||||
|
||||
if not GAMEMODE.Config.restrictbuypistol or
|
||||
(GAMEMODE.Config.restrictbuypistol and (not shipment.allowed[1] or table.HasValue(shipment.allowed, ply:Team()))) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function BuyPistol(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not GAMEMODE.Config.enablebuypistol then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", "/buy", ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
if GAMEMODE.Config.noguns then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", "/buy", ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local shipment = DarkRP.getShipmentByName(args)
|
||||
if not shipment or not shipment.separate then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("weapon_")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyPistol", DarkRP.hooks, ply, shipment)
|
||||
|
||||
if not canbuy then
|
||||
message = message or DarkRP.getPhrase("incorrect_job", "/buy")
|
||||
if not suppress then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
local cost = price or shipment.getPrice and shipment.getPrice(ply, shipment.pricesep) or shipment.pricesep or 0
|
||||
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
trace.filter = ply
|
||||
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local defaultClip, clipSize
|
||||
local wep_tbl = weapons.Get(shipment.entity)
|
||||
if wep_tbl and wep_tbl.Primary then
|
||||
defaultClip = wep_tbl.Primary.DefaultClip
|
||||
clipSize = wep_tbl.Primary.ClipSize
|
||||
end
|
||||
|
||||
local weapon = ents.Create("spawned_weapon")
|
||||
weapon:SetModel(shipment.model)
|
||||
weapon:SetWeaponClass(shipment.entity)
|
||||
weapon:SetPos(tr.HitPos)
|
||||
weapon.ammoadd = shipment.spareammo or defaultClip
|
||||
weapon.clip1 = shipment.clip1 or clipSize
|
||||
weapon.clip2 = shipment.clip2
|
||||
weapon.nodupe = true
|
||||
weapon:Spawn()
|
||||
|
||||
DarkRP.placeEntity(weapon, tr, ply)
|
||||
|
||||
if shipment.onBought then
|
||||
shipment.onBought(ply, shipment, weapon)
|
||||
end
|
||||
hook.Call("playerBoughtPistol", nil, ply, shipment, weapon, cost)
|
||||
|
||||
if IsValid(weapon) then
|
||||
ply:addMoney(-cost)
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", args, DarkRP.formatMoney(cost)))
|
||||
else
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/buy", args))
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("buy", BuyPistol, 0.2)
|
||||
|
||||
function DarkRP.hooks:canBuyShipment(ply, shipment)
|
||||
if not GAMEMODE:CustomObjFitsMap(shipment) then
|
||||
return false, false, "Custom object does not fit map"
|
||||
end
|
||||
|
||||
if ply.LastShipmentSpawn and ply.LastShipmentSpawn > (CurTime() - GAMEMODE.Config.ShipmentSpamTime) then
|
||||
return false, false, DarkRP.getPhrase("shipment_antispam_wait")
|
||||
end
|
||||
|
||||
if ply:isArrested() then
|
||||
return false, false, DarkRP.getPhrase("unable", "/buyshipment", "")
|
||||
end
|
||||
|
||||
if shipment.customCheck and not shipment.customCheck(ply) then
|
||||
local message = isfunction(shipment.CustomCheckFailMsg) and shipment.CustomCheckFailMsg(ply, shipment) or
|
||||
shipment.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
return false, false, message
|
||||
end
|
||||
|
||||
local canbecome = false
|
||||
for _, b in pairs(shipment.allowed) do
|
||||
if ply:Team() == b then
|
||||
canbecome = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not canbecome then
|
||||
return false, false, DarkRP.getPhrase("incorrect_job", "/buyshipment")
|
||||
end
|
||||
|
||||
local cost = shipment.getPrice and shipment.getPrice(ply, shipment.price) or shipment.price
|
||||
|
||||
if not ply:canAfford(cost) then
|
||||
return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("shipment"))
|
||||
end
|
||||
|
||||
if not shipment.allowPurchaseWhileDead and not ply:Alive() then
|
||||
return false, false, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", DarkRP.getPhrase("shipments")))
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function BuyShipment(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local found, foundKey = DarkRP.getShipmentByName(args)
|
||||
if not found or found.noship or not GAMEMODE:CustomObjFitsMap(found) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("shipment")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyShipment", DarkRP.hooks, ply, found)
|
||||
|
||||
if not canbuy then
|
||||
message = message or DarkRP.getPhrase("incorrect_job", "/buy")
|
||||
if not suppress then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price
|
||||
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
trace.filter = ply
|
||||
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local crate = ents.Create(found.shipmentClass or "spawned_shipment")
|
||||
crate.SID = ply.SID
|
||||
crate:Setowning_ent(ply)
|
||||
crate:SetContents(foundKey, found.amount)
|
||||
|
||||
crate:SetPos(Vector(tr.HitPos.x, tr.HitPos.y, tr.HitPos.z))
|
||||
crate.nodupe = true
|
||||
crate.ammoadd = found.spareammo
|
||||
crate.clip1 = found.clip1
|
||||
crate.clip2 = found.clip2
|
||||
crate:Spawn()
|
||||
crate:SetPlayer(ply)
|
||||
|
||||
DarkRP.placeEntity(crate, tr, ply)
|
||||
|
||||
local phys = crate:GetPhysicsObject()
|
||||
phys:Wake()
|
||||
if found.weight then
|
||||
phys:SetMass(found.weight)
|
||||
end
|
||||
|
||||
if CustomShipments[foundKey].onBought then
|
||||
CustomShipments[foundKey].onBought(ply, CustomShipments[foundKey], crate)
|
||||
end
|
||||
hook.Call("playerBoughtShipment", nil, ply, CustomShipments[foundKey], crate, cost)
|
||||
|
||||
if IsValid(crate) then
|
||||
ply:addMoney(-cost)
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", args, DarkRP.formatMoney(cost)))
|
||||
else
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/buyshipment", arg))
|
||||
end
|
||||
|
||||
ply.LastShipmentSpawn = CurTime()
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("buyshipment", BuyShipment)
|
||||
|
||||
function DarkRP.hooks:canBuyVehicle(ply, vehicle)
|
||||
if not vehicle.allowPurchaseWhileDead and not ply:Alive() then
|
||||
return false, false, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", vehicle.name))
|
||||
end
|
||||
if not GAMEMODE:CustomObjFitsMap(vehicle) then
|
||||
return false, false, "Custom object does not fit map"
|
||||
end
|
||||
|
||||
if ply:isArrested() then
|
||||
return false, false, DarkRP.getPhrase("unable", "/buyvehicle", "")
|
||||
end
|
||||
|
||||
if vehicle.allowed and not table.HasValue(vehicle.allowed, ply:Team()) then
|
||||
return false, false, DarkRP.getPhrase("incorrect_job", "/buyvehicle")
|
||||
end
|
||||
|
||||
if vehicle.customCheck and not vehicle.customCheck(ply) then
|
||||
local message = isfunction(vehicle.CustomCheckFailMsg) and vehicle.CustomCheckFailMsg(ply, vehicle) or
|
||||
vehicle.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
return false, false, message
|
||||
end
|
||||
|
||||
ply.Vehicles = ply.Vehicles or 0
|
||||
if GAMEMODE.Config.maxvehicles and ply.Vehicles >= GAMEMODE.Config.maxvehicles then
|
||||
return false, false, DarkRP.getPhrase("limit", DarkRP.getPhrase("vehicle"))
|
||||
end
|
||||
|
||||
local cost = vehicle.getPrice and vehicle.getPrice(ply, vehicle.price) or vehicle.price
|
||||
if not ply:canAfford(cost) then
|
||||
return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("vehicle"))
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function BuyVehicle(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local found = false
|
||||
-- Allow people to have multiple vehicles with the same name
|
||||
-- vehicles are bought through the command
|
||||
for k, v in pairs(CustomVehicles) do
|
||||
if v.command and string.lower(v.command) == string.lower(args) then
|
||||
found = CustomVehicles[k]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
for k,v in pairs(CustomVehicles) do
|
||||
if string.lower(v.name) == string.lower(args) then found = CustomVehicles[k] break end
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("vehicle")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local Vehicle = DarkRP.getAvailableVehicles()[found.name]
|
||||
if not Vehicle then DarkRP.notify(ply, 1, 4, "Incorrect vehicle, fix your vehicles.") return "" end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyVehicle", DarkRP.hooks, ply, found)
|
||||
|
||||
if not canbuy then
|
||||
message = message or DarkRP.getPhrase("incorrect_job", "/buy")
|
||||
if not suppress then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price
|
||||
|
||||
ply:addMoney(-cost)
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", found.label or found.name, DarkRP.formatMoney(cost)))
|
||||
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * (found.distance or 85)
|
||||
trace.filter = ply
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local ent = ents.Create(Vehicle.Class)
|
||||
if not ent:IsValid() then error("Vehicle '" .. Vehicle.Class .. "' does not exist or is not valid.") end
|
||||
|
||||
ent:SetModel(Vehicle.Model)
|
||||
if Vehicle.KeyValues then
|
||||
for k, v in pairs(Vehicle.KeyValues) do
|
||||
ent:SetKeyValue(k, v)
|
||||
end
|
||||
end
|
||||
|
||||
ent:SetPos(tr.HitPos)
|
||||
ent.VehicleName = found.name
|
||||
ent.VehicleTable = Vehicle
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
ent.SID = ply.SID
|
||||
ent.ClassOverride = Vehicle.Class
|
||||
if Vehicle.Members then
|
||||
table.Merge(ent, Vehicle.Members)
|
||||
end
|
||||
ent:CPPISetOwner(ply)
|
||||
ent:keysOwn(ply)
|
||||
|
||||
DarkRP.placeEntity(ent, tr, ply)
|
||||
|
||||
local angOff = found.angle or Angle(0, 0, 0)
|
||||
ent:SetAngles(ent:GetAngles() + angOff)
|
||||
|
||||
hook.Call("PlayerSpawnedVehicle", GAMEMODE, ply, ent) -- VUMod compatability
|
||||
hook.Call("playerBoughtCustomVehicle", nil, ply, found, ent, cost)
|
||||
|
||||
if found.onBought then
|
||||
found.onBought(ply, found, ent)
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("buyvehicle", BuyVehicle)
|
||||
|
||||
function DarkRP.hooks:canBuyAmmo(ply, ammo)
|
||||
if not GAMEMODE:CustomObjFitsMap(ammo) then
|
||||
return false, false, "Custom object does not fit map"
|
||||
end
|
||||
|
||||
if ply:isArrested() then
|
||||
return false, false, DarkRP.getPhrase("unable", "/buyammo", "")
|
||||
end
|
||||
|
||||
if ammo.allowed and not table.HasValue(ammo.allowed, ply:Team()) then
|
||||
return false, false, DarkRP.getPhrase("incorrect_job", "/buyammo")
|
||||
end
|
||||
|
||||
if ammo.customCheck and not ammo.customCheck(ply) then
|
||||
local message = isfunction(ammo.CustomCheckFailMsg) and ammo.CustomCheckFailMsg(ply, ammo) or
|
||||
ammo.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
return false, false, message
|
||||
end
|
||||
|
||||
local cost = ammo.getPrice and ammo.getPrice(ply, ammo.price) or ammo.price
|
||||
if not ply:canAfford(cost) then
|
||||
return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("ammo"))
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function BuyAmmo(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
if GAMEMODE.Config.noguns then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", DarkRP.getPhrase("ammo"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local found
|
||||
local num = tonumber(args)
|
||||
if num and GAMEMODE.AmmoTypes[num] then
|
||||
found = GAMEMODE.AmmoTypes[num]
|
||||
else
|
||||
for _, v in pairs(GAMEMODE.AmmoTypes) do
|
||||
if v.ammoType ~= args then continue end
|
||||
|
||||
found = v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("ammo")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyAmmo", DarkRP.hooks, ply, found)
|
||||
|
||||
if not canbuy then
|
||||
message = message or DarkRP.getPhrase("incorrect_job", "/buy")
|
||||
if not suppress then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price
|
||||
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", found.name, DarkRP.formatMoney(cost)))
|
||||
ply:addMoney(-cost)
|
||||
|
||||
-- local trace = {}
|
||||
-- trace.start = ply:EyePos()
|
||||
-- trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
-- trace.filter = ply
|
||||
|
||||
-- local tr = util.TraceLine(trace)
|
||||
|
||||
-- local ammo = ents.Create("spawned_ammo")
|
||||
-- ammo:SetModel(found.model)
|
||||
-- ammo:SetPos(tr.HitPos)
|
||||
-- ammo.nodupe = true
|
||||
-- ammo.amountGiven, ammo.ammoType = found.amountGiven, found.ammoType
|
||||
-- ammo:Spawn()
|
||||
|
||||
-- DarkRP.placeEntity(ammo, tr, ply)
|
||||
|
||||
-- hook.Call("playerBoughtAmmo", nil, ply, found, ammo, cost)
|
||||
--
|
||||
ply:GiveAmmo(found.amountGiven, found.ammoType)
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("buyammo", BuyAmmo, 1)
|
||||
|
||||
local function SetPrice(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local price = DarkRP.toInt(args)
|
||||
if not price then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
price = math.Clamp(price, GAMEMODE.Config.pricemin, (GAMEMODE.Config.pricecap ~= 0 and GAMEMODE.Config.pricecap) or 500)
|
||||
local trace = {}
|
||||
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
trace.filter = ply
|
||||
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local ent = tr.Entity
|
||||
|
||||
if IsValid(ent) and ent.CanSetPrice and ent.SID == ply.SID then
|
||||
ent:Setprice(price)
|
||||
else
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("any_lab")))
|
||||
end
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("price", SetPrice)
|
||||
DarkRP.defineChatCommand("setprice", SetPrice)
|
||||
235
gamemodes/darkrp/gamemode/modules/base/sv_util.lua
Normal file
235
gamemodes/darkrp/gamemode/modules/base/sv_util.lua
Normal file
@@ -0,0 +1,235 @@
|
||||
function DarkRP.notify(ply, msgtype, len, msg)
|
||||
if not istable(ply) then
|
||||
if not IsValid(ply) then
|
||||
-- Dedicated server console
|
||||
print(msg)
|
||||
return
|
||||
end
|
||||
|
||||
ply = {ply}
|
||||
end
|
||||
|
||||
local rcp = RecipientFilter()
|
||||
for _, v in pairs(ply) do
|
||||
rcp:AddPlayer(v)
|
||||
end
|
||||
|
||||
if hook.Run("onNotify", rcp:GetPlayers(), msgtype, len, msg) == true then return end
|
||||
|
||||
umsg.Start("_Notify", rcp)
|
||||
umsg.String(msg)
|
||||
umsg.Short(msgtype)
|
||||
umsg.Long(len)
|
||||
umsg.End()
|
||||
end
|
||||
|
||||
function DarkRP.notifyAll(msgtype, len, msg)
|
||||
if hook.Run("onNotify", player.GetAll(), msgtype, len, msg) == true then return end
|
||||
|
||||
umsg.Start("_Notify")
|
||||
umsg.String(msg)
|
||||
umsg.Short(msgtype)
|
||||
umsg.Long(len)
|
||||
umsg.End()
|
||||
end
|
||||
|
||||
function DarkRP.printMessageAll(msgtype, msg)
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
v:PrintMessage(msgtype, msg)
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.printConsoleMessage(ply, msg)
|
||||
if ply:EntIndex() == 0 then
|
||||
print(msg)
|
||||
else
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, msg)
|
||||
end
|
||||
end
|
||||
|
||||
util.AddNetworkString("DarkRP_Chat")
|
||||
|
||||
function DarkRP.talkToRange(ply, PlayerName, Message, size)
|
||||
local ents = player.GetHumans()
|
||||
local col = team.GetColor(ply:Team())
|
||||
local filter = {}
|
||||
|
||||
local plyPos = ply:EyePos()
|
||||
local sizeSqr = size * size
|
||||
|
||||
for _, v in ipairs(ents) do
|
||||
if (v:EyePos():DistToSqr(plyPos) <= sizeSqr) and (v == ply or hook.Run("PlayerCanSeePlayersChat", PlayerName .. ": " .. Message, false, v, ply) ~= false) then
|
||||
table.insert(filter, v)
|
||||
end
|
||||
end
|
||||
|
||||
if PlayerName == ply:Nick() then PlayerName = "" end -- If it's just normal chat, why not cut down on networking and get the name on the client
|
||||
|
||||
net.Start("DarkRP_Chat")
|
||||
net.WriteUInt(col.r, 8)
|
||||
net.WriteUInt(col.g, 8)
|
||||
net.WriteUInt(col.b, 8)
|
||||
net.WriteString(PlayerName)
|
||||
net.WriteEntity(ply)
|
||||
net.WriteUInt(255, 8)
|
||||
net.WriteUInt(255, 8)
|
||||
net.WriteUInt(255, 8)
|
||||
net.WriteString(Message)
|
||||
net.Send(filter)
|
||||
end
|
||||
|
||||
function DarkRP.talkToPerson(receiver, col1, text1, col2, text2, sender)
|
||||
if not IsValid(receiver) then return end
|
||||
if receiver:IsBot() then return end
|
||||
local concatenatedText = (text1 or "") .. ": " .. (text2 or "")
|
||||
|
||||
if sender == receiver or hook.Run("PlayerCanSeePlayersChat", concatenatedText, false, receiver, sender) ~= false then
|
||||
net.Start("DarkRP_Chat")
|
||||
net.WriteUInt(col1.r, 8)
|
||||
net.WriteUInt(col1.g, 8)
|
||||
net.WriteUInt(col1.b, 8)
|
||||
net.WriteString(text1)
|
||||
|
||||
sender = sender or Entity(0)
|
||||
net.WriteEntity(sender)
|
||||
|
||||
col2 = col2 or color_black
|
||||
net.WriteUInt(col2.r, 8)
|
||||
net.WriteUInt(col2.g, 8)
|
||||
net.WriteUInt(col2.b, 8)
|
||||
net.WriteString(text2 or "")
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.isEmpty(vector, ignore)
|
||||
ignore = ignore or {}
|
||||
|
||||
local point = util.PointContents(vector)
|
||||
local a = point ~= CONTENTS_SOLID
|
||||
and point ~= CONTENTS_MOVEABLE
|
||||
and point ~= CONTENTS_LADDER
|
||||
and point ~= CONTENTS_PLAYERCLIP
|
||||
and point ~= CONTENTS_MONSTERCLIP
|
||||
if not a then return false end
|
||||
|
||||
local b = true
|
||||
|
||||
for _, v in ipairs(ents.FindInSphere(vector, 35)) do
|
||||
if (v:IsNPC() or v:IsPlayer() or v:GetClass() == "prop_physics" or v.NotEmptyPos) and not table.HasValue(ignore, v) then
|
||||
b = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return a and b
|
||||
end
|
||||
|
||||
function DarkRP.placeEntity(ent, tr, ply)
|
||||
if IsValid(ply) then
|
||||
local ang = ply:EyeAngles()
|
||||
ang.pitch = 0
|
||||
ang.yaw = ang.yaw + 180
|
||||
ang.roll = 0
|
||||
ent:SetAngles(ang)
|
||||
end
|
||||
|
||||
local vFlushPoint = tr.HitPos - (tr.HitNormal * 512)
|
||||
vFlushPoint = ent:NearestPoint(vFlushPoint)
|
||||
vFlushPoint = ent:GetPos() - vFlushPoint
|
||||
vFlushPoint = tr.HitPos + vFlushPoint
|
||||
ent:SetPos(vFlushPoint)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Find an empty position near the position given in the first parameter
|
||||
pos - The position to use as a center for looking around
|
||||
ignore - what entities to ignore when looking for the position (the position can be within the entity)
|
||||
distance - how far to look
|
||||
step - how big the steps are
|
||||
area - the position relative to pos that should also be free
|
||||
|
||||
Performance: O(N^2) (The Lua part, that is, I don't know about the C++ counterpart)
|
||||
Don't call this function too often or with big inputs.
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.findEmptyPos(pos, ignore, distance, step, area)
|
||||
if DarkRP.isEmpty(pos, ignore) and DarkRP.isEmpty(pos + area, ignore) then
|
||||
return pos
|
||||
end
|
||||
|
||||
for j = step, distance, step do
|
||||
for i = -1, 1, 2 do -- alternate in direction
|
||||
local k = j * i
|
||||
|
||||
-- Look North/South
|
||||
if DarkRP.isEmpty(pos + Vector(k, 0, 0), ignore) and DarkRP.isEmpty(pos + Vector(k, 0, 0) + area, ignore) then
|
||||
return pos + Vector(k, 0, 0)
|
||||
end
|
||||
|
||||
-- Look East/West
|
||||
if DarkRP.isEmpty(pos + Vector(0, k, 0), ignore) and DarkRP.isEmpty(pos + Vector(0, k, 0) + area, ignore) then
|
||||
return pos + Vector(0, k, 0)
|
||||
end
|
||||
|
||||
-- Look Up/Down
|
||||
if DarkRP.isEmpty(pos + Vector(0, 0, k), ignore) and DarkRP.isEmpty(pos + Vector(0, 0, k) + area, ignore) then
|
||||
return pos + Vector(0, 0, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return pos
|
||||
end
|
||||
|
||||
local meta = FindMetaTable("Player")
|
||||
function meta:applyPlayerClassVars(applyHealth)
|
||||
local playerClass = baseclass.Get(player_manager.GetPlayerClass(self))
|
||||
|
||||
self:SetWalkSpeed(playerClass.WalkSpeed >= 0 and playerClass.WalkSpeed or GAMEMODE.Config.walkspeed)
|
||||
self:SetRunSpeed(playerClass.RunSpeed >= 0 and playerClass.RunSpeed or (self:isCP() and GAMEMODE.Config.runspeedcp or GAMEMODE.Config.runspeed))
|
||||
|
||||
hook.Call("UpdatePlayerSpeed", GAMEMODE, self) -- Backwards compatitibly, do not use
|
||||
|
||||
self:SetCrouchedWalkSpeed(playerClass.CrouchedWalkSpeed)
|
||||
self:SetDuckSpeed(playerClass.DuckSpeed)
|
||||
self:SetUnDuckSpeed(playerClass.UnDuckSpeed)
|
||||
self:SetJumpPower(playerClass.JumpPower)
|
||||
self:AllowFlashlight(playerClass.CanUseFlashlight)
|
||||
|
||||
self:SetMaxHealth(playerClass.MaxHealth >= 0 and playerClass.MaxHealth or (tonumber(GAMEMODE.Config.startinghealth) or 100))
|
||||
if applyHealth then
|
||||
self:SetHealth(playerClass.StartHealth >= 0 and playerClass.StartHealth or (tonumber(GAMEMODE.Config.startinghealth) or 100))
|
||||
end
|
||||
self:SetArmor(playerClass.StartArmor)
|
||||
|
||||
self.dropWeaponOnDeath = playerClass.DropWeaponOnDie
|
||||
self:SetNoCollideWithTeammates(playerClass.TeammateNoCollide)
|
||||
self:SetAvoidPlayers(playerClass.AvoidPlayers)
|
||||
|
||||
hook.Call("playerClassVarsApplied", nil, self)
|
||||
end
|
||||
|
||||
local function LookPersonUp(ply, cmd, args)
|
||||
if not args[1] then
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return
|
||||
end
|
||||
local P = DarkRP.findPlayer(args[1])
|
||||
if not IsValid(P) then
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("could_not_find", tostring(args[1])))
|
||||
return
|
||||
end
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("name", P:Nick()))
|
||||
DarkRP.printConsoleMessage(ply, "Steam " .. DarkRP.getPhrase("name", P:SteamName()))
|
||||
DarkRP.printConsoleMessage(ply, "Steam ID: " .. P:SteamID())
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("job", team.GetName(P:Team())))
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("kills", P:Frags()))
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("deaths", P:Deaths()))
|
||||
|
||||
CAMI.PlayerHasAccess(ply, "DarkRP_AdminCommands", function(access)
|
||||
if not access then return end
|
||||
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("wallet", DarkRP.formatMoney(P:getDarkRPVar("money")), ""))
|
||||
end)
|
||||
end
|
||||
concommand.Add("rp_lookup", LookPersonUp)
|
||||
90
gamemodes/darkrp/gamemode/modules/chat/cl_chat.lua
Normal file
90
gamemodes/darkrp/gamemode/modules/chat/cl_chat.lua
Normal file
@@ -0,0 +1,90 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
Gamemode function
|
||||
---------------------------------------------------------------------------]]
|
||||
function GM:OnPlayerChat()
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Add a message to chat
|
||||
---------------------------------------------------------------------------]]
|
||||
local function AddToChat(bits)
|
||||
local col1 = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8))
|
||||
|
||||
local prefixText = net.ReadString()
|
||||
local ply = net.ReadEntity()
|
||||
ply = IsValid(ply) and ply or LocalPlayer()
|
||||
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
if prefixText == "" or not prefixText then
|
||||
prefixText = ply:Nick()
|
||||
prefixText = prefixText ~= "" and prefixText or ply:SteamName()
|
||||
end
|
||||
|
||||
local col2 = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8))
|
||||
|
||||
local text = net.ReadString()
|
||||
local shouldShow
|
||||
if text and text ~= "" then
|
||||
if IsValid(ply) then
|
||||
shouldShow = hook.Call("OnPlayerChat", GAMEMODE, ply, text, false, not ply:Alive(), prefixText, col1, col2)
|
||||
end
|
||||
|
||||
if shouldShow ~= true then
|
||||
chat.AddNonParsedText(col1, prefixText, col2, ": " .. text)
|
||||
end
|
||||
else
|
||||
shouldShow = hook.Call("ChatText", GAMEMODE, "0", prefixText, prefixText, "darkrp")
|
||||
|
||||
if shouldShow ~= true then
|
||||
chat.AddNonParsedText(col1, prefixText)
|
||||
end
|
||||
end
|
||||
chat.PlaySound()
|
||||
end
|
||||
net.Receive("DarkRP_Chat", AddToChat)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Credits
|
||||
|
||||
Please only ADD to the credits.
|
||||
---------------------------------------------------------------------------]]
|
||||
local creds =
|
||||
[[
|
||||
|
||||
LightRP was created by Rick darkalonio. LightRP was sandbox with some added RP elements.
|
||||
LightRP was released at the end of January 2007
|
||||
|
||||
DarkRP was created as a spoof of LightRP by Rickster, somewhere during the summer of 2007.
|
||||
Note: There was a DarkRP in 2006, but that was an entirely different gamemode.
|
||||
|
||||
Rickster went to serve his country and went to Afghanistan. During that time, the following people updated DarkRP:
|
||||
Picwizdan
|
||||
Sibre
|
||||
[GNC] Matt
|
||||
PhilXYZ
|
||||
Chromebolt A.K.A. Unib5 (STEAM_0:1:19045957)
|
||||
|
||||
In 2008, Unib5 was administrator on a DarkRP server called EuroRP, owned by Jiggu. FPtje frequently joined this server to prop kill en masse. While Jiggu loved watching the chaos unfold, Unib5 hated it and banned FPtje on sight. Since Jiggu kept unbanning FPtje, Unib5 felt powerless. In an attempt to stop FPtje, Unib5 put FPtje's favourite prop killing props (the locker and the sawblade) in the default blacklist of DarkRP in an update. This in turn enraged FPtje, as he swore to make an update in secret that would suddenly pop up and overthrow the established version. As a result, DarkRP 2.3.1 was released in December 2008. After a bit of a fight, FPtje became the official updater of DarkRP.
|
||||
|
||||
Current developer:
|
||||
Falco A.K.A. FPtje Atheos (STEAM_0:0:8944068)
|
||||
|
||||
People who have contributed (ordered by commits, with at least two commits)
|
||||
Bo98
|
||||
Drakehawke (STEAM_0:0:22342869) (64 commits on old SVN)
|
||||
FiG-Scorn
|
||||
Noiwex
|
||||
KoZ
|
||||
Eusion (STEAM_0:0:20450406) (3 commits on old SVN)
|
||||
Gangleider
|
||||
MattWalton12
|
||||
TypicalRookie
|
||||
]]
|
||||
|
||||
local function credits(um)
|
||||
chat.AddNonParsedText(Color(255, 0, 0, 255), "[", Color(50,50,50,255), GAMEMODE.Name, Color(255, 0, 0, 255), "] ", color_white, DarkRP.getPhrase("credits_see_console"))
|
||||
|
||||
MsgC(Color(255, 0, 0, 255), DarkRP.getPhrase("credits_for", GAMEMODE.Name), color_white, creds)
|
||||
end
|
||||
usermessage.Hook("DarkRP_Credits", credits)
|
||||
207
gamemodes/darkrp/gamemode/modules/chat/cl_chatlisteners.lua
Normal file
207
gamemodes/darkrp/gamemode/modules/chat/cl_chatlisteners.lua
Normal file
@@ -0,0 +1,207 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
This module finds out for you who can see you talk or speak through the microphone
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Variables
|
||||
---------------------------------------------------------------------------]]
|
||||
local receivers
|
||||
local currentChatText = {}
|
||||
local receiverConfigs = {}
|
||||
local currentConfig = {text = "", hearFunc = fn.Id} -- Default config is not loaded yet
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
addChatReceiver
|
||||
Add a chat command with specific receivers
|
||||
|
||||
prefix: the chat command itself ("/pm", "/ooc", "/me" are some examples)
|
||||
text: the text that shows up when it says "Some people can hear you X"
|
||||
hearFunc: a function(ply, splitText) that decides whether this player can or cannot hear you.
|
||||
return true if the player can hear you
|
||||
false if the player cannot
|
||||
nil if you want to prevent the text from showing up temporarily
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.addChatReceiver(prefix, text, hearFunc)
|
||||
receiverConfigs[prefix] = {
|
||||
text = text,
|
||||
hearFunc = hearFunc
|
||||
}
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
removeChatReceiver
|
||||
Remove a chat command.
|
||||
|
||||
prefix: the command, like in addChatReceiver
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.removeChatReceiver(prefix)
|
||||
receiverConfigs[prefix] = nil
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Draw the results to the screen
|
||||
---------------------------------------------------------------------------]]
|
||||
local function drawChatReceivers()
|
||||
if not receivers then return end
|
||||
|
||||
local fontHeight = draw.GetFontHeight("DarkRPHUD1")
|
||||
local x, y = chat.GetChatBoxPos()
|
||||
y = y - fontHeight - 4
|
||||
|
||||
local receiversCount = #receivers
|
||||
-- No one hears you
|
||||
if receiversCount == 0 then
|
||||
draw.WordBox(2, x, y, DarkRP.getPhrase("hear_noone", currentConfig.text), "DarkRPHUD1", Color(0,0,0,160), Color(255,0,0,255))
|
||||
return
|
||||
-- Everyone hears you
|
||||
elseif receiversCount == player.GetCount() - 1 then
|
||||
draw.WordBox(2, x, y, DarkRP.getPhrase("hear_everyone"), "DarkRPHUD1", Color(0,0,0,160), Color(0,255,0,255))
|
||||
return
|
||||
end
|
||||
|
||||
draw.WordBox(2, x, y - (receiversCount * (fontHeight + 4)), DarkRP.getPhrase("hear_certain_persons", currentConfig.text), "DarkRPHUD1", Color(0,0,0,160), Color(0,255,0,255))
|
||||
for i = 1, receiversCount, 1 do
|
||||
if not IsValid(receivers[i]) then
|
||||
receivers[i] = receivers[#receivers]
|
||||
receivers[#receivers] = nil
|
||||
continue
|
||||
end
|
||||
|
||||
draw.WordBox(2, x, y - (i - 1) * (fontHeight + 4), receivers[i]:Nick(), "DarkRPHUD1", Color(0, 0, 0, 160), color_white)
|
||||
end
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Find out who could hear the player if they were to speak now
|
||||
---------------------------------------------------------------------------]]
|
||||
local function chatGetRecipients()
|
||||
if not currentConfig then return end
|
||||
|
||||
receivers = {}
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
local hidePly = hook.Run("chatHideRecipient", ply)
|
||||
if not IsValid(ply) or ply == LocalPlayer() or ply:GetNoDraw() or hidePly then continue end
|
||||
|
||||
local val = currentConfig.hearFunc(ply, currentChatText)
|
||||
|
||||
-- Return nil to disable the chat recipients temporarily.
|
||||
if val == nil then
|
||||
receivers = nil
|
||||
return
|
||||
elseif val == true then
|
||||
table.insert(receivers, ply)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Called when the player starts typing
|
||||
---------------------------------------------------------------------------]]
|
||||
local function startFind()
|
||||
local shouldDraw = hook.Call("HUDShouldDraw", GAMEMODE, "DarkRP_ChatReceivers")
|
||||
if shouldDraw == false then return end
|
||||
|
||||
currentConfig = receiverConfigs[""]
|
||||
hook.Add("Think", "DarkRP_chatRecipients", chatGetRecipients)
|
||||
hook.Add("HUDPaint", "DarkRP_DrawChatReceivers", drawChatReceivers)
|
||||
end
|
||||
hook.Add("StartChat", "DarkRP_StartFindChatReceivers", startFind)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Called when the player stops typing
|
||||
---------------------------------------------------------------------------]]
|
||||
local function stopFind()
|
||||
hook.Remove("Think", "DarkRP_chatRecipients")
|
||||
hook.Remove("HUDPaint", "DarkRP_DrawChatReceivers")
|
||||
end
|
||||
hook.Add("FinishChat", "DarkRP_StopFindChatReceivers", stopFind)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Find out which chat command the user is typing
|
||||
---------------------------------------------------------------------------]]
|
||||
local function findConfig(text)
|
||||
local split = string.Explode(' ', text)
|
||||
local prefix = string.lower(split[1])
|
||||
|
||||
currentChatText = split
|
||||
|
||||
currentConfig = receiverConfigs[prefix] or receiverConfigs[""]
|
||||
end
|
||||
hook.Add("ChatTextChanged", "DarkRP_FindChatRecipients", findConfig)
|
||||
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Default chat receievers. If you want to add your own ones, don't add them to this file. Add them to a clientside module file instead.
|
||||
---------------------------------------------------------------------------]]
|
||||
-- Load after the custom languages have been loaded
|
||||
local function loadChatReceivers()
|
||||
-- Default talk chat receiver has no prefix
|
||||
DarkRP.addChatReceiver("", DarkRP.getPhrase("talk"), function(ply)
|
||||
if GAMEMODE.Config.alltalk then return nil end
|
||||
|
||||
return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) <
|
||||
GAMEMODE.Config.talkDistance * GAMEMODE.Config.talkDistance
|
||||
end)
|
||||
|
||||
DarkRP.addChatReceiver("/ooc", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end)
|
||||
DarkRP.addChatReceiver("//", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end)
|
||||
DarkRP.addChatReceiver("/a", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end)
|
||||
DarkRP.addChatReceiver("/w", DarkRP.getPhrase("whisper"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.whisperDistance * GAMEMODE.Config.whisperDistance end)
|
||||
DarkRP.addChatReceiver("/y", DarkRP.getPhrase("yell"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.yellDistance * GAMEMODE.Config.yellDistance end)
|
||||
DarkRP.addChatReceiver("/me", DarkRP.getPhrase("perform_your_action"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.meDistance * GAMEMODE.Config.meDistance end)
|
||||
DarkRP.addChatReceiver("/g", DarkRP.getPhrase("talk_to_your_group"), function(ply)
|
||||
for _, func in pairs(GAMEMODE.DarkRPGroupChats) do
|
||||
if func(LocalPlayer()) and func(ply) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end)
|
||||
|
||||
|
||||
DarkRP.addChatReceiver("/pm", "PM", function(ply, text)
|
||||
if not isstring(text[2]) then return false end
|
||||
text[2] = string.lower(tostring(text[2]))
|
||||
|
||||
return string.find(string.lower(ply:Nick()), text[2], 1, true) ~= nil or
|
||||
string.find(string.lower(ply:SteamName()), text[2], 1, true) ~= nil or
|
||||
string.lower(ply:SteamID()) == text[2]
|
||||
end)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Voice chat receivers
|
||||
---------------------------------------------------------------------------]]
|
||||
local voiceDistance = GM.Config.voiceDistance * GM.Config.voiceDistance
|
||||
DarkRP.addChatReceiver("speak", DarkRP.getPhrase("speak"), function(ply)
|
||||
if not LocalPlayer().DRPIsTalking then return nil end
|
||||
if LocalPlayer():GetPos():DistToSqr(ply:GetPos()) > voiceDistance then return false end
|
||||
|
||||
return not GAMEMODE.Config.dynamicvoice or ply:isInRoom()
|
||||
end)
|
||||
end
|
||||
hook.Add("loadCustomDarkRPItems", "loadChatListeners", loadChatReceivers)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Called when the player starts using their voice
|
||||
---------------------------------------------------------------------------]]
|
||||
local function startFindVoice(ply)
|
||||
if ply ~= LocalPlayer() then return end
|
||||
|
||||
local shouldDraw = hook.Call("HUDShouldDraw", GAMEMODE, "DarkRP_ChatReceivers")
|
||||
if shouldDraw == false then return end
|
||||
|
||||
currentConfig = receiverConfigs["speak"]
|
||||
hook.Add("Think", "DarkRP_chatRecipients", chatGetRecipients)
|
||||
hook.Add("HUDPaint", "DarkRP_DrawChatReceivers", drawChatReceivers)
|
||||
end
|
||||
hook.Add("PlayerStartVoice", "DarkRP_VoiceChatReceiverFinder", startFindVoice)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Called when the player stops using their voice
|
||||
---------------------------------------------------------------------------]]
|
||||
local function stopFindVoice(ply)
|
||||
if ply ~= LocalPlayer() then return end
|
||||
|
||||
stopFind()
|
||||
end
|
||||
hook.Add("PlayerEndVoice", "DarkRP_VoiceChatReceiverFinder", stopFindVoice)
|
||||
56
gamemodes/darkrp/gamemode/modules/chat/cl_interface.lua
Normal file
56
gamemodes/darkrp/gamemode/modules/chat/cl_interface.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
DarkRP.addChatReceiver = DarkRP.stub{
|
||||
name = "addChatReceiver",
|
||||
description = "Add a chat command with specific receivers",
|
||||
parameters = {
|
||||
{
|
||||
name = "prefix",
|
||||
description = "The chat command itself (\"/pm\", \"/ooc\", \"/me\" are some examples)",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "text",
|
||||
description = "The text that shows up when it says \"Some people can hear you X\"",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "hearFunc",
|
||||
description = "A function(ply, splitText) that decides whether this player can or cannot hear you.",
|
||||
type = "function",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.removeChatReceiver = DarkRP.stub{
|
||||
name = "removeChatReceiver",
|
||||
description = "Remove a chat command receiver",
|
||||
parameters = {
|
||||
{
|
||||
name = "prefix",
|
||||
description = "The chat command itself (\"/pm\", \"/ooc\", \"/me\" are some examples)",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "chatHideRecipient",
|
||||
description = "Hide a receipent from who can hear/see your text GUI.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who spoke.",
|
||||
type = "Player"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
216
gamemodes/darkrp/gamemode/modules/chat/sh_chatcommands.lua
Normal file
216
gamemodes/darkrp/gamemode/modules/chat/sh_chatcommands.lua
Normal file
@@ -0,0 +1,216 @@
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
DarkRP.chatCommands = DarkRP.chatCommands or {}
|
||||
|
||||
local validChatCommand = {
|
||||
command = isstring,
|
||||
description = isstring,
|
||||
condition = fn.FOr{fn.Curry(fn.Eq, 2)(nil), isfunction},
|
||||
delay = isnumber,
|
||||
tableArgs = fn.FOr{fn.Curry(fn.Eq, 2)(nil), isbool},
|
||||
}
|
||||
|
||||
local checkChatCommand = function(tbl)
|
||||
for k in pairs(validChatCommand) do
|
||||
if not validChatCommand[k](tbl[k]) then
|
||||
return false, k
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function DarkRP.declareChatCommand(tbl)
|
||||
local valid, element = checkChatCommand(tbl)
|
||||
if not valid then
|
||||
DarkRP.error("Incorrect chat command! " .. element .. " is invalid!", 2)
|
||||
end
|
||||
|
||||
tbl.command = string.lower(tbl.command)
|
||||
DarkRP.chatCommands[tbl.command] = DarkRP.chatCommands[tbl.command] or tbl
|
||||
for k, v in pairs(tbl) do
|
||||
DarkRP.chatCommands[tbl.command][k] = v
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.removeChatCommand(command)
|
||||
DarkRP.chatCommands[string.lower(command)] = nil
|
||||
end
|
||||
|
||||
function DarkRP.chatCommandAlias(command, ...)
|
||||
local name
|
||||
for k, v in ipairs{...} do
|
||||
name = string.lower(v)
|
||||
|
||||
DarkRP.chatCommands[name] = {command = name}
|
||||
setmetatable(DarkRP.chatCommands[name], {
|
||||
__index = DarkRP.chatCommands[command]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.getChatCommand(command)
|
||||
return DarkRP.chatCommands[string.lower(command)]
|
||||
end
|
||||
|
||||
function DarkRP.getChatCommands()
|
||||
return DarkRP.chatCommands
|
||||
end
|
||||
|
||||
function DarkRP.getSortedChatCommands()
|
||||
local tbl = fn.Compose{table.ClearKeys, table.Copy, DarkRP.getChatCommands}()
|
||||
table.SortByMember(tbl, "command", true)
|
||||
|
||||
return tbl
|
||||
end
|
||||
|
||||
-- chat commands that have been defined, but not declared
|
||||
DarkRP.getIncompleteChatCommands = fn.Curry(fn.Filter, 3)(fn.Compose{fn.Not, checkChatCommand})(DarkRP.chatCommands)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Chat commands
|
||||
---------------------------------------------------------------------------]]
|
||||
DarkRP.declareChatCommand{
|
||||
command = "pm",
|
||||
description = "Send a private message to someone.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "w",
|
||||
description = "Say something in whisper voice.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "y",
|
||||
description = "Yell something out loud.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "me",
|
||||
description = "Chat roleplay to say you're doing things that you can't show otherwise.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "/",
|
||||
description = "Global server chat.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "a",
|
||||
description = "Global server chat.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "ooc",
|
||||
description = "Global server chat.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "broadcast",
|
||||
description = "Broadcast something as a mayor.",
|
||||
delay = 1.5,
|
||||
condition = plyMeta.isMayor
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "channel",
|
||||
description = "Tune into a radio channel.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "radio",
|
||||
description = "Say something through the radio.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "g",
|
||||
description = "Group chat.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "credits",
|
||||
description = "Send the DarkRP credits to someone.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
|
||||
local function init()
|
||||
if not DarkRP then
|
||||
MsgC(Color(255,0,0), "DarkRP Classic Advert tried to run, but DarkRP wasn't declared!\n")
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.removeChatCommand("advert")
|
||||
DarkRP.declareChatCommand({
|
||||
command = "advert",
|
||||
description = "Displays an advertisement to everyone in chat.",
|
||||
delay = 1.5
|
||||
})
|
||||
|
||||
if SERVER then
|
||||
DarkRP.defineChatCommand("advert",function(ply,args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", "argument", ""))
|
||||
return ""
|
||||
end
|
||||
local DoSay = function(text)
|
||||
if text == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", "argument", ""))
|
||||
return
|
||||
end
|
||||
for k,v in pairs(player.GetAll()) do
|
||||
local col = team.GetColor(ply:Team())
|
||||
DarkRP.talkToPerson(v, col, "[Реклама] " .. ply:Nick(), Color(255, 255, 0, 255), text, ply)
|
||||
end
|
||||
end
|
||||
hook.Call("playerAdverted", nil, ply, args)
|
||||
return args, DoSay
|
||||
end, 1.5)
|
||||
else
|
||||
DarkRP.addChatReceiver("/advert", "advertise", function(ply) return true end)
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
if #player.GetAll() > 0 then
|
||||
init()
|
||||
else
|
||||
hook.Add("PlayerInitialSpawn", "dfca-load", init)
|
||||
end
|
||||
else
|
||||
hook.Add("InitPostEntity", "dfca-load", init)
|
||||
end
|
||||
|
||||
|
||||
if SERVER then
|
||||
|
||||
local function Roll(ply, args)
|
||||
local DoSay = function()
|
||||
if GAMEMODE.Config.alltalk then
|
||||
for _, target in pairs(player.GetAll()) do
|
||||
DarkRP.talkToPerson(target, team.GetColor(ply:Team()), ply:Nick().. " выпало " ..math.random(1,100).." из 100.")
|
||||
end
|
||||
else
|
||||
DarkRP.talkToRange(ply, ply:Nick().. " выпало " ..math.random(1,100).." из 100.", "", 250)
|
||||
end
|
||||
end
|
||||
return args, DoSay
|
||||
end
|
||||
DarkRP.defineChatCommand("roll", Roll, 1.5)
|
||||
|
||||
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "roll",
|
||||
description = "write an roll",
|
||||
delay = 1.5
|
||||
}
|
||||
end
|
||||
121
gamemodes/darkrp/gamemode/modules/chat/sh_interface.lua
Normal file
121
gamemodes/darkrp/gamemode/modules/chat/sh_interface.lua
Normal file
@@ -0,0 +1,121 @@
|
||||
DarkRP.declareChatCommand = DarkRP.stub{
|
||||
name = "declareChatCommand",
|
||||
description = "Declare a chat command (describe it)",
|
||||
parameters = {
|
||||
{
|
||||
name = "table",
|
||||
description = "The description of the chat command. Has to contain a string: command, string: description, number: delay, optional function: condition",
|
||||
type = "table",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.removeChatCommand = DarkRP.stub{
|
||||
name = "removeChatCommand",
|
||||
description = "Remove a chat command",
|
||||
parameters = {
|
||||
{
|
||||
name = "command",
|
||||
description = "The chat command to remove",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.chatCommandAlias = DarkRP.stub{
|
||||
name = "chatCommandAlias",
|
||||
description = "Create an alias for a chat command",
|
||||
parameters = {
|
||||
{
|
||||
name = "command",
|
||||
description = "An already existing chat command.",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "alias",
|
||||
description = "One or more aliases for the chat command.",
|
||||
type = "vararg",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.getChatCommand = DarkRP.stub{
|
||||
name = "getChatCommand",
|
||||
description = "Get the information on a chat command.",
|
||||
parameters = {
|
||||
{
|
||||
name = "command",
|
||||
description = "The chat command",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "chatTable",
|
||||
description = "A table containing the information of the chat command.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.getChatCommands = DarkRP.stub{
|
||||
name = "getChatCommands",
|
||||
description = "Get every chat command.",
|
||||
parameters = {
|
||||
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "commands",
|
||||
description = "A table containing every command. Table indices are the command strings.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.getSortedChatCommands = DarkRP.stub{
|
||||
name = "getSortedChatCommands",
|
||||
description = "Get every chat command, sorted by their name.",
|
||||
parameters = {
|
||||
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "commands",
|
||||
description = "A table containing every command.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.getIncompleteChatCommands = DarkRP.stub{
|
||||
name = "getIncompleteChatCommands",
|
||||
description = "chat commands that have been defined, but not declared. Information about these chat commands is missing.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "commands",
|
||||
description = "A table containing the undeclared chat commands.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
183
gamemodes/darkrp/gamemode/modules/chat/sv_chat.lua
Normal file
183
gamemodes/darkrp/gamemode/modules/chat/sv_chat.lua
Normal file
@@ -0,0 +1,183 @@
|
||||
local function registerCommandDefinition(cmd, callback)
|
||||
local chatcommands = DarkRP.getChatCommands()
|
||||
|
||||
chatcommands[cmd] = chatcommands[cmd] or {}
|
||||
chatcommands[cmd].callback = callback
|
||||
chatcommands[cmd].command = chatcommands[cmd].command or cmd
|
||||
end
|
||||
|
||||
function DarkRP.defineChatCommand(cmd, callback)
|
||||
cmd = string.lower(cmd)
|
||||
local detour = function(ply, arg, ...)
|
||||
local canChatCommand = gamemode.Call("canChatCommand", ply, cmd, arg, ...)
|
||||
if not canChatCommand then
|
||||
return ""
|
||||
end
|
||||
|
||||
local ret = {callback(ply, arg, ...)}
|
||||
local overrideTxt, overrideDoSayFunc = hook.Run("onChatCommand", ply, cmd, arg, ret, ...)
|
||||
|
||||
if overrideTxt then return overrideTxt, overrideDoSayFunc end
|
||||
return unpack(ret)
|
||||
end
|
||||
|
||||
registerCommandDefinition(cmd, detour)
|
||||
end
|
||||
|
||||
function DarkRP.definePrivilegedChatCommand(cmd, priv, callback, extraInfoTbl)
|
||||
cmd = string.lower(cmd)
|
||||
|
||||
local function onCAMIResult(ply, arg, hasAccess, reason)
|
||||
if hasAccess then return callback(ply, arg) end
|
||||
|
||||
local notify = ply:EntIndex() == 0 and print or fp{DarkRP.notify, ply, 1, 4}
|
||||
notify(DarkRP.getPhrase("no_privilege"))
|
||||
end
|
||||
|
||||
local function callbackdetour(ply, arg, ...)
|
||||
local canChatCommand = gamemode.Call("canChatCommand", ply, cmd, arg)
|
||||
if not canChatCommand then
|
||||
return ""
|
||||
end
|
||||
|
||||
CAMI.PlayerHasAccess(ply, priv, fp{onCAMIResult, ply, arg}, nil, extraInfoTbl)
|
||||
|
||||
local overrideTxt, overrideDoSayFunc = hook.Run("onChatCommand", ply, cmd, arg, {""})
|
||||
|
||||
if overrideTxt then return overrideTxt, overrideDoSayFunc end
|
||||
return ""
|
||||
end
|
||||
|
||||
registerCommandDefinition(cmd, callbackdetour)
|
||||
end
|
||||
|
||||
local function RP_PlayerChat(ply, text, teamonly)
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. "): " .. text)
|
||||
local callback = ""
|
||||
local DoSayFunc
|
||||
local groupSay = DarkRP.getChatCommand("g")
|
||||
|
||||
-- Extract the chat command
|
||||
local tblCmd = fn.Compose{
|
||||
DarkRP.getChatCommand,
|
||||
string.lower,
|
||||
fn.Curry(fn.Flip(string.sub), 2)(2), -- extract prefix
|
||||
fn.Curry(fn.GetValue, 2)(1), -- Get the first word
|
||||
fn.Curry(string.Explode, 2)(' ') -- split by spaces
|
||||
}(text)
|
||||
|
||||
if string.sub(text, 1, 1) == GAMEMODE.Config.chatCommandPrefix and tblCmd then
|
||||
local args = string.sub(text, string.len(tblCmd.command) + 3, string.len(text))
|
||||
args = tblCmd.tableArgs and DarkRP.explodeArg(args) or args
|
||||
|
||||
ply.DrpCommandDelays = ply.DrpCommandDelays or {}
|
||||
if tblCmd.delay and ply.DrpCommandDelays[tblCmd.command] and ply.DrpCommandDelays[tblCmd.command] > CurTime() - tblCmd.delay then
|
||||
return ""
|
||||
end
|
||||
|
||||
ply.DrpCommandDelays[tblCmd.command] = CurTime()
|
||||
|
||||
callback, DoSayFunc = tblCmd.callback(ply, args)
|
||||
if callback == "" then
|
||||
return "", "", DoSayFunc
|
||||
end
|
||||
text = string.sub(text, string.len(tblCmd.command) + 3, string.len(text))
|
||||
elseif teamonly and groupSay then
|
||||
callback, DoSayFunc = groupSay.callback(ply, text)
|
||||
return text, "", DoSayFunc
|
||||
end
|
||||
|
||||
if callback ~= "" then
|
||||
callback = callback or "" .. " "
|
||||
end
|
||||
|
||||
return text, callback, DoSayFunc;
|
||||
end
|
||||
|
||||
local function RP_ActualDoSay(ply, text, callback)
|
||||
callback = callback or ""
|
||||
if text == "" then return "" end
|
||||
local col = team.GetColor(ply:Team())
|
||||
local col2 = color_white
|
||||
if not ply:Alive() then
|
||||
col2 = Color(255, 200, 200, 255)
|
||||
col = col2
|
||||
end
|
||||
|
||||
if GAMEMODE.Config.alltalk then
|
||||
local name = ply:Nick()
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
DarkRP.talkToPerson(v, col, callback .. name, col2, text, ply)
|
||||
end
|
||||
else
|
||||
DarkRP.talkToRange(ply, callback .. ply:Nick(), text, GAMEMODE.Config.talkDistance)
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function GM:canChatCommand(ply, cmd, ...)
|
||||
if not ply.DarkRPUnInitialized then return true end
|
||||
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("data_not_loaded_one"))
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("data_not_loaded_two"))
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
g_DarkRPOldHookCall = g_DarkRPOldHookCall or hook.Call
|
||||
|
||||
local GM = GM
|
||||
function hook.Call(name, gm, ply, text, teamonly, ...)
|
||||
if name == "PlayerSay" then
|
||||
local dead = not ply:Alive()
|
||||
|
||||
local text2 = text
|
||||
local callback
|
||||
local DoSayFunc
|
||||
|
||||
text2 = g_DarkRPOldHookCall(name, gm, ply, text, teamonly, dead) or text2
|
||||
|
||||
text2, callback, DoSayFunc = RP_PlayerChat(ply, text2, teamonly)
|
||||
if tostring(text2) == " " then text2, callback = callback, text2 end
|
||||
if not GM.Config.deadtalk and dead then return "" end
|
||||
|
||||
if game.IsDedicated() then
|
||||
ServerLog("\"" .. ply:Nick() .. "<" .. ply:UserID() .. ">" .. "<" .. ply:SteamID() .. ">" .. "<" .. team.GetName(ply:Team()) .. ">\" say \"" .. text .. "\"\n" .. "\n")
|
||||
end
|
||||
|
||||
if DoSayFunc then DoSayFunc(text2) return "" end
|
||||
RP_ActualDoSay(ply, text2, callback)
|
||||
|
||||
hook.Call("PostPlayerSay", nil, ply, text2, teamonly, dead)
|
||||
return ""
|
||||
end
|
||||
|
||||
return g_DarkRPOldHookCall(name, gm, ply, text, teamonly, ...)
|
||||
end
|
||||
|
||||
local function ConCommand(ply, _, args)
|
||||
if not args[1] then return end
|
||||
local cmd = string.lower(args[1])
|
||||
local tbl = DarkRP.getChatCommand(cmd)
|
||||
|
||||
if not tbl then return end
|
||||
|
||||
table.remove(args, 1) -- Remove subcommand
|
||||
local arg = tbl.tableArgs and args or table.concat(args, ' ')
|
||||
local time = CurTime()
|
||||
|
||||
if not tbl then return end
|
||||
|
||||
ply.DrpCommandDelays = ply.DrpCommandDelays or {}
|
||||
|
||||
if IsValid(ply) then -- Server console isn't valid
|
||||
if tbl.delay and ply.DrpCommandDelays[cmd] and ply.DrpCommandDelays[cmd] > time - tbl.delay then
|
||||
return
|
||||
end
|
||||
|
||||
ply.DrpCommandDelays[cmd] = time
|
||||
end
|
||||
|
||||
tbl.callback(ply, arg)
|
||||
end
|
||||
concommand.Add("darkrp", ConCommand)
|
||||
241
gamemodes/darkrp/gamemode/modules/chat/sv_chatcommands.lua
Normal file
241
gamemodes/darkrp/gamemode/modules/chat/sv_chatcommands.lua
Normal file
@@ -0,0 +1,241 @@
|
||||
--[[---------------------------------------------------------
|
||||
Talking
|
||||
---------------------------------------------------------]]
|
||||
local function PM(ply, args)
|
||||
local namepos = string.find(args, " ")
|
||||
if not namepos then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local name = string.sub(args, 1, namepos - 1)
|
||||
local msg = string.sub(args, namepos + 1)
|
||||
|
||||
if msg == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local target = DarkRP.findPlayer(name)
|
||||
if target == ply then return "" end
|
||||
|
||||
if target then
|
||||
local col = team.GetColor(ply:Team())
|
||||
local pname = ply:Nick()
|
||||
local col2 = color_white
|
||||
DarkRP.talkToPerson(target, col, "(PM) " .. pname, col2, msg, ply)
|
||||
DarkRP.talkToPerson(ply, col, "(PM) " .. pname, col2, msg, ply)
|
||||
else
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(name)))
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("pm", PM, 1.5)
|
||||
|
||||
local function Whisper(ply, args)
|
||||
local DoSay = function(text)
|
||||
if text == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
DarkRP.talkToRange(ply, "(" .. DarkRP.getPhrase("whisper") .. ") " .. ply:Nick(), text, GAMEMODE.Config.whisperDistance)
|
||||
end
|
||||
return args, DoSay
|
||||
end
|
||||
DarkRP.defineChatCommand("w", Whisper, 1.5)
|
||||
|
||||
local function Yell(ply, args)
|
||||
local DoSay = function(text)
|
||||
if text == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
DarkRP.talkToRange(ply, "(" .. DarkRP.getPhrase("yell") .. ") " .. ply:Nick(), text, GAMEMODE.Config.yellDistance)
|
||||
end
|
||||
return args, DoSay
|
||||
end
|
||||
DarkRP.defineChatCommand("y", Yell, 1.5)
|
||||
|
||||
local function Me(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local DoSay = function(text)
|
||||
if text == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
if GAMEMODE.Config.alltalk then
|
||||
local col = team.GetColor(ply:Team())
|
||||
local name = ply:Nick()
|
||||
for _, target in ipairs(player.GetAll()) do
|
||||
DarkRP.talkToPerson(target, col, name .. " " .. text)
|
||||
end
|
||||
else
|
||||
DarkRP.talkToRange(ply, ply:Nick() .. " " .. text, "", GAMEMODE.Config.meDistance)
|
||||
end
|
||||
end
|
||||
return args, DoSay
|
||||
end
|
||||
DarkRP.defineChatCommand("me", Me, 1.5)
|
||||
|
||||
local function OOC(ply, args)
|
||||
if not GAMEMODE.Config.ooc then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", DarkRP.getPhrase("ooc"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local DoSay = function(text)
|
||||
if text == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
local col = team.GetColor(ply:Team())
|
||||
local col2 = color_white
|
||||
if not ply:Alive() then
|
||||
col2 = Color(255, 200, 200, 255)
|
||||
col = col2
|
||||
end
|
||||
|
||||
local phrase = DarkRP.getPhrase("ooc")
|
||||
local name = ply:Nick()
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
DarkRP.talkToPerson(v, col, "(" .. phrase .. ") " .. name, col2, text, ply)
|
||||
end
|
||||
end
|
||||
return args, DoSay
|
||||
end
|
||||
DarkRP.defineChatCommand("/", OOC, true, 1.5)
|
||||
DarkRP.defineChatCommand("a", OOC, true, 1.5)
|
||||
DarkRP.defineChatCommand("ooc", OOC, true, 1.5)
|
||||
|
||||
local function MayorBroadcast(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
local Team = ply:Team()
|
||||
if not RPExtraTeams[Team] or not RPExtraTeams[Team].mayor then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("incorrect_job", DarkRP.getPhrase("broadcast")))
|
||||
return ""
|
||||
end
|
||||
local DoSay = function(text)
|
||||
if text == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return
|
||||
end
|
||||
|
||||
local col = team.GetColor(ply:Team())
|
||||
local col2 = Color(170, 0, 0, 255)
|
||||
local phrase = DarkRP.getPhrase("broadcast")
|
||||
local name = ply:Nick()
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
DarkRP.talkToPerson(v, col, phrase .. " " .. name, col2, text, ply)
|
||||
end
|
||||
end
|
||||
return args, DoSay
|
||||
end
|
||||
DarkRP.defineChatCommand("broadcast", MayorBroadcast, 1.5)
|
||||
|
||||
local function SetRadioChannel(ply,args)
|
||||
local channel = DarkRP.toInt(args)
|
||||
if channel == nil or channel < 0 or channel > 100 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "0<" .. DarkRP.getPhrase("channel") .. "<100"))
|
||||
return ""
|
||||
end
|
||||
DarkRP.notify(ply, 2, 4, DarkRP.getPhrase("channel_set_to_x", args))
|
||||
ply.RadioChannel = channel
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("channel", SetRadioChannel)
|
||||
|
||||
local function SayThroughRadio(ply,args)
|
||||
if not ply.RadioChannel then ply.RadioChannel = 1 end
|
||||
local radioChannel = ply.RadioChannel
|
||||
if not args or args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
local DoSay = function(text)
|
||||
if text == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return
|
||||
end
|
||||
local col = Color(180, 180, 180, 255)
|
||||
local phrase = DarkRP.getPhrase("radio_x", radioChannel)
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if v.RadioChannel == radioChannel then
|
||||
DarkRP.talkToPerson(v, col, phrase, col, text, ply)
|
||||
end
|
||||
end
|
||||
end
|
||||
return args, DoSay
|
||||
end
|
||||
DarkRP.defineChatCommand("radio", SayThroughRadio, 1.5)
|
||||
|
||||
local function GroupMsg(ply, args)
|
||||
local DoSay = function(text)
|
||||
if text == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return
|
||||
end
|
||||
|
||||
local col = team.GetColor(ply:Team())
|
||||
|
||||
local groupChats = {}
|
||||
for _, func in pairs(GAMEMODE.DarkRPGroupChats) do
|
||||
-- not the group of the player
|
||||
if not func(ply) then continue end
|
||||
|
||||
table.insert(groupChats, func)
|
||||
end
|
||||
|
||||
if table.IsEmpty(groupChats) then return "" end
|
||||
|
||||
local phrase = DarkRP.getPhrase("group")
|
||||
local name = ply:Nick()
|
||||
local color = color_white
|
||||
for _, target in ipairs(player.GetAll()) do
|
||||
-- The target is in any of the group chats
|
||||
for _, func in ipairs(groupChats) do
|
||||
if not func(target, ply) then continue end
|
||||
|
||||
DarkRP.talkToPerson(target, col, phrase .. " " .. name, color, text, ply)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return args, DoSay
|
||||
end
|
||||
DarkRP.defineChatCommand("g", GroupMsg, 0)
|
||||
|
||||
-- here's the new easter egg. Easier to find, more subtle, doesn't only credit FPtje and unib5
|
||||
-- WARNING: DO NOT EDIT THIS
|
||||
-- You can edit DarkRP but you HAVE to credit the original authors!
|
||||
-- You even have to credit all the previous authors when you rename the gamemode.
|
||||
-- local CreditsWait = true
|
||||
local function GetDarkRPAuthors(ply, args)
|
||||
local target = DarkRP.findPlayer(args) -- Only send to one player. Prevents spamming
|
||||
target = IsValid(target) and target or ply
|
||||
|
||||
if target ~= ply then
|
||||
if ply.CreditsWait then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("wait_with_that")) return "" end
|
||||
ply.CreditsWait = true
|
||||
timer.Simple(60, function() if IsValid(ply) then ply.CreditsWait = nil end end) -- so people don't spam it
|
||||
end
|
||||
|
||||
local rf = RecipientFilter()
|
||||
rf:AddPlayer(target)
|
||||
if ply ~= target then
|
||||
rf:AddPlayer(ply)
|
||||
end
|
||||
|
||||
umsg.Start("DarkRP_Credits", rf)
|
||||
umsg.End()
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("credits", GetDarkRPAuthors, 50)
|
||||
145
gamemodes/darkrp/gamemode/modules/chat/sv_interface.lua
Normal file
145
gamemodes/darkrp/gamemode/modules/chat/sv_interface.lua
Normal file
@@ -0,0 +1,145 @@
|
||||
DarkRP.defineChatCommand = DarkRP.stub{
|
||||
name = "defineChatCommand",
|
||||
description = "Create a chat command that calls the function",
|
||||
parameters = {
|
||||
{
|
||||
name = "chat command",
|
||||
description = "The registered chat command",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "callback",
|
||||
description = "The function that is called when the chat command is executed",
|
||||
type = "function",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.definePrivilegedChatCommand = DarkRP.stub{
|
||||
name = "definePrivilegedChatCommand",
|
||||
description = "Create a chat command that calls the function if the player has the right CAMI privilege. Will automatically notify the user when they don't have access. Note that chat command functions registered with this function can NOT override the chat that will appear after the command has been executed.",
|
||||
parameters = {
|
||||
{
|
||||
name = "chat command",
|
||||
description = "The registered chat command",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "privilege",
|
||||
description = "The name of the CAMI privilege",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "callback",
|
||||
description = "The function that is called when the chat command is executed",
|
||||
type = "function",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "PostPlayerSay",
|
||||
description = "Called after a player has said something.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who spoke.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "text",
|
||||
description = "The thing they said.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "teamonly",
|
||||
description = "Whether they said it to their team only.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "dead",
|
||||
description = "Whether they are dead.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "canChatCommand",
|
||||
description = "Called when a player tries to run any chat command or uses the DarkRP console command. ",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who spoke.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "command",
|
||||
description = "The thing they said.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "arguments",
|
||||
description = "The arguments of the chat command, given as one string.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "canChatCommand",
|
||||
description = "Whether the player is allowed to run the chat command.",
|
||||
type = "boolean"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "onChatCommand",
|
||||
description = "Called after a player has run any chat command or uses the DarkRP console command. Note: the chat command has already been run. Use canChatCommand if you want to stop chat commands from being run.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who spoke.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "command",
|
||||
description = "The thing they said.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "arguments",
|
||||
description = "The arguments of the chat command, given either as one string or a table of strings. That depends on whether the command is declared to use table arguments.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "return",
|
||||
description = "The return value of the chat command function. Should contain a chat text override and/or a say function. See the return values of this hook for a description",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "overrideText",
|
||||
description = "Overrides the text a chat command will put in everyone's chat box. Return nil to not change behaviour.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "overrideSayFunc",
|
||||
description = "Say functions handle what needs to be said to whom. The say function for PMs for example make sure only the sender and receiver see the message. You can override this behaviour by returning a different say function in this hook. Return nil to not change behaviour.",
|
||||
type = "function"
|
||||
},
|
||||
}
|
||||
}
|
||||
70
gamemodes/darkrp/gamemode/modules/chatindicator/cl_init.lua
Normal file
70
gamemodes/darkrp/gamemode/modules/chatindicator/cl_init.lua
Normal file
@@ -0,0 +1,70 @@
|
||||
local function drawIndicator(ply)
|
||||
if not ply:IsTyping() then
|
||||
if ply.indicator then
|
||||
ply.indicator:Remove()
|
||||
ply.indicator = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local chatIndicator = hook.Call("DrawChatIndicator", nil, ply)
|
||||
if chatIndicator == true then return end
|
||||
|
||||
local indicator = ply.indicator
|
||||
if not IsValid(indicator) then
|
||||
indicator = ClientsideModel("models/extras/info_speech.mdl", RENDERGROUP_OPAQUE)
|
||||
if not IsValid(indicator) then return end -- In case the non networked entity limit is hit (still prints a red error message, but doesn't spam client console with lua errors)
|
||||
ply.indicator = indicator
|
||||
end
|
||||
indicator:SetNoDraw(true)
|
||||
indicator:SetModelScale(0.6)
|
||||
|
||||
local ragdoll = ply:GetRagdollEntity()
|
||||
if IsValid(ragdoll) then
|
||||
local maxs = ragdoll:OBBMaxs()
|
||||
indicator:SetPos(ragdoll:GetPos() + Vector(0, 0, maxs.z) + Vector(0, 0, 12))
|
||||
else
|
||||
indicator:SetPos(ply:GetPos() + Vector(0, 0, 72 * ply:GetModelScale()) + Vector(0, 0, 12))
|
||||
end
|
||||
|
||||
local curTime = CurTime()
|
||||
local angle = indicator:GetAngles()
|
||||
angle.y = (angle.y + (360 * (curTime - (indicator.lastDraw or 0)))) % 360
|
||||
indicator:SetAngles(angle)
|
||||
indicator.lastDraw = curTime
|
||||
|
||||
indicator:SetupBones()
|
||||
indicator:DrawModel()
|
||||
end
|
||||
|
||||
hook.Add("PostPlayerDraw", "DarkRP_ChatIndicator", drawIndicator)
|
||||
hook.Add("CreateClientsideRagdoll", "DarkRP_ChatIndicator", function(ent, ragdoll)
|
||||
if not ent:IsPlayer() then return end
|
||||
|
||||
local oldRenderOverride = ragdoll.RenderOverride -- Just in case - best be safe
|
||||
ragdoll.RenderOverride = function(self)
|
||||
if ent:IsValid() then
|
||||
drawIndicator(ent)
|
||||
end
|
||||
|
||||
if oldRenderOverride then
|
||||
oldRenderOverride(self)
|
||||
else
|
||||
self:DrawModel()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- CSEnts aren't GC'd.
|
||||
-- https://github.com/Facepunch/garrysmod-issues/issues/1387
|
||||
gameevent.Listen("player_disconnect")
|
||||
hook.Add("player_disconnect", "DarkRP_ChatIndicator", function(data)
|
||||
local ply = Player(data.userid)
|
||||
|
||||
if not IsValid(ply) then return end -- disconnected while joining
|
||||
|
||||
if ply.indicator then
|
||||
ply.indicator:Remove()
|
||||
ply.indicator = nil
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,18 @@
|
||||
DarkRP.hookStub{
|
||||
name = "DrawChatIndicator",
|
||||
description = "Call when the Chat Indicator is drawn. Return to overwrite.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player the indicator should be drawn for.",
|
||||
type = "Player"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "override",
|
||||
description = "Return true in your hook to disable the default drawing.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
377
gamemodes/darkrp/gamemode/modules/chatsounds.lua
Normal file
377
gamemodes/darkrp/gamemode/modules/chatsounds.lua
Normal file
@@ -0,0 +1,377 @@
|
||||
-- This module will make voice sounds play when certain words are typed in the chat
|
||||
-- You can add/remove sounds as you wish using DarkRP.setChatSound, just follow the format used here
|
||||
-- To disable them completely, set GM.Config.chatsounds to false
|
||||
-- TODO: Add female sounds & detect gender of model, and use combine sounds for CPs
|
||||
|
||||
local sounds = {}
|
||||
sounds["ammo"] = {"vo/npc/male01/ammo03.wav", "vo/npc/male01/ammo04.wav", "vo/npc/male01/ammo05.wav"}
|
||||
|
||||
sounds["behind you"] = {"vo/npc/male01/behindyou01.wav", "vo/npc/male01/behindyou02.wav"}
|
||||
|
||||
sounds["better reload"] = {"vo/npc/male01/youdbetterreload01.wav"}
|
||||
|
||||
sounds["bullshit"] = {"vo/npc/male01/question26.wav"}
|
||||
|
||||
sounds["bull shit"] = sounds["bullshit"]
|
||||
|
||||
sounds["cheese"] = {"vo/npc/male01/question06.wav"}
|
||||
|
||||
sounds["combine"] = {"vo/npc/male01/combine01.wav", "vo/npc/male01/combine02.wav"}
|
||||
|
||||
sounds["coming"] = {"vo/npc/male01/squad_approach04.wav"}
|
||||
|
||||
sounds["cops"] = {"vo/npc/male01/civilprotection01.wav", "vo/npc/male01/civilprotection02.wav", "vo/npc/male01/cps01.wav", "vo/npc/male01/cps02.wav"}
|
||||
|
||||
sounds["cp"] = sounds["cops"]
|
||||
sounds["cps"] = sounds["cops"]
|
||||
|
||||
sounds["cut it"] = {"vo/trainyard/male01/cit_hit01.wav", "vo/trainyard/male01/cit_hit02.wav", "vo/trainyard/male01/cit_hit03.wav", "vo/trainyard/male01/cit_hit04.wav", "vo/trainyard/male01/cit_hit05.wav"}
|
||||
|
||||
sounds["dont tell me"] = {"vo/npc/male01/gordead_ans03.wav"}
|
||||
|
||||
sounds["de ja vu"] = {"vo/npc/male01/question05.wav"}
|
||||
|
||||
sounds["dejavu"] = sounds["de ja vu"]
|
||||
|
||||
sounds["excuse me"] = {"vo/npc/male01/excuseme01.wav", "vo/npc/male01/excuseme02.wav"}
|
||||
|
||||
sounds["fantastic"] = {"vo/npc/male01/fantastic01.wav", "vo/npc/male01/fantastic02.wav"}
|
||||
|
||||
sounds["figures"] = {"vo/npc/male01/answer03.wav"}
|
||||
|
||||
sounds["finally"] = {"vo/npc/male01/finally.wav"}
|
||||
|
||||
sounds["follow"] = {"vo/coast/odessa/male01/stairman_follow01.wav", "vo/npc/male01/squad_away03.wav", "vo/coast/cardock/le_followme.wav"}
|
||||
|
||||
sounds["focus"] = {"vo/npc/male01/answer18.wav", "vo/npc/male01/answer19.wav"}
|
||||
|
||||
sounds["freeman"] = {"vo/npc/male01/freeman.wav", "vo/npc/male01/docfreeman01.wav", "vo/npc/male01/docfreeman02.wav"}
|
||||
|
||||
sounds["get down"] = {"vo/npc/male01/getdown02.wav"}
|
||||
|
||||
sounds["get in"] = {"vo/canals/gunboat_getin.wav"}
|
||||
|
||||
sounds["get out"] = {"vo/npc/male01/gethellout.wav"}
|
||||
|
||||
sounds["good god"] = {"vo/npc/male01/goodgod.wav", "vo/npc/male01/gordead_ans04.wav"}
|
||||
|
||||
sounds["gosh"] = sounds["good god"]
|
||||
|
||||
sounds["got one"] = {"vo/npc/male01/gotone01.wav", "vo/npc/male01/gotone01.wav"}
|
||||
|
||||
sounds["gotta reload"] = {"vo/npc/male01/gottareload01.wav"}
|
||||
|
||||
sounds["gtfo"] = sounds["get out"]
|
||||
|
||||
sounds["hacks"] = {"vo/npc/male01/hacks01.wav", "vo/npc/male01/hacks02.wav", "vo/npc/male01/thehacks01.wav", "vo/npc/male01/thehacks02.wav"}
|
||||
|
||||
sounds["hax"] = sounds["hacks"]
|
||||
sounds["haxx"] = sounds["hacks"]
|
||||
|
||||
sounds["help"] = {"vo/npc/male01/help01.wav"}
|
||||
|
||||
sounds["here they come"] = {"vo/npc/male01/heretheycome01.wav", "vo/npc/male01/incoming02.wav"}
|
||||
|
||||
sounds["hello"] = {"vo/npc/male01/hi01.wav", "vo/npc/male01/hi02.wav"}
|
||||
|
||||
sounds["hey"] = sounds["hello"]
|
||||
sounds["hi"] = sounds["hello"]
|
||||
|
||||
sounds["heads up"] = {"vo/npc/male01/headsup01.wav", "vo/npc/male01/headsup02.wav"}
|
||||
|
||||
sounds["he's dead"] = {"vo/npc/male01/gordead_ques01.wav", "vo/npc/male01/gordead_ques07.wav"}
|
||||
|
||||
sounds["he is dead"] = sounds["he's dead"]
|
||||
|
||||
sounds["how about that"] = {"vo/npc/male01/answer25.wav"}
|
||||
|
||||
sounds["i know"] = {"vo/npc/male01/answer08.wav"}
|
||||
|
||||
sounds["ill stay here"] = {"vo/npc/male01/illstayhere01.wav", "vo/npc/male01/holddownspot01.wav", "vo/npc/male01/holddownspot02.wav", "vo/npc/male01/imstickinghere01.wav", "vo/npc/male01/littlecorner01.wav"}
|
||||
|
||||
sounds["i'll stay here"] = sounds["ill stay here"]
|
||||
sounds["i will stay here"] = sounds["ill stay here"]
|
||||
|
||||
sounds["im busy"] = {"vo/npc/male01/busy02.wav"}
|
||||
|
||||
sounds["i'm busy"] = sounds["im busy"]
|
||||
|
||||
sounds["im with you"] = {"vo/npc/male01/answer13.wav"}
|
||||
|
||||
sounds["i'm with you"] = sounds["im with you"]
|
||||
|
||||
sounds["isnt good"] = {"vo/trainyard/male01/cit_window_use01.wav"}
|
||||
|
||||
sounds["isn't good"] = sounds["isnt good"]
|
||||
sounds["incoming"] = sounds["here they come"]
|
||||
|
||||
sounds["it cant be"] = {"vo/npc/male01/gordead_ques06.wav"}
|
||||
|
||||
sounds["it can't be"] = sounds["it cant be"]
|
||||
|
||||
sounds["it is okay"] = {"vo/npc/male01/answer02.wav"}
|
||||
|
||||
sounds["it's okay"] = sounds["it is okay"]
|
||||
|
||||
sounds["kay"] = {"vo/npc/male01/ok01.wav", "vo/npc/male01/ok02.wav"}
|
||||
|
||||
sounds["kk"] = sounds["kay"]
|
||||
|
||||
sounds["lead the way"] = {"vo/npc/male01/leadtheway01.wav", "vo/npc/male01/leadtheway02.wav"}
|
||||
|
||||
sounds["lead on"] = sounds["lead the way"]
|
||||
|
||||
sounds["lets go"] = {"vo/npc/male01/letsgo01.wav", "vo/npc/male01/letsgo02.wav"}
|
||||
|
||||
sounds["let's go"] = sounds["lets go"]
|
||||
|
||||
sounds["never"] = {"vo/Citadel/eli_nonever.wav"}
|
||||
|
||||
sounds["never can tell"] = {"vo/npc/male01/answer23.wav"}
|
||||
|
||||
sounds["nice"] = {"vo/npc/male01/nice.wav"}
|
||||
|
||||
sounds["no"] = {"vo/Citadel/br_no.wav", "vo/Citadel/eli_notobreen.wav"}
|
||||
|
||||
sounds["not good"] = sounds["isnt good"]
|
||||
|
||||
sounds["not sure"] = {"vo/npc/male01/answer21.wav"}
|
||||
|
||||
sounds["now what"] = {"vo/npc/male01/gordead_ans01.wav", "vo/npc/male01/gordead_ans15.wav"}
|
||||
|
||||
sounds["oh no"] = {"vo/npc/male01/gordead_ans05.wav", "vo/npc/male01/ohno.wav"}
|
||||
|
||||
sounds["oh my god"] = sounds["good god"]
|
||||
sounds["omg"] = sounds["good god"]
|
||||
sounds["omfg"] = sounds["good god"]
|
||||
sounds["ok"] = sounds["kay"]
|
||||
sounds["okay"] = sounds["kay"]
|
||||
|
||||
sounds["oops"] = {"vo/npc/male01/whoops01.wav"}
|
||||
|
||||
sounds["over here"] = {"vo/npc/male01/overhere01.wav", "vo/npc/male01/squad_away02.wav"}
|
||||
|
||||
sounds["over there"] = {"vo/npc/male01/overthere01.wav", "vo/npc/male01/overthere02.wav"}
|
||||
|
||||
sounds["pardon me"] = {"vo/npc/male01/pardonme01.wav", "vo/npc/male01/pardonme02.wav"}
|
||||
|
||||
sounds["please no"] = {"vo/npc/male01/gordead_ans06.wav"}
|
||||
|
||||
sounds["right on"] = {"vo/npc/male01/answer18.wav"}
|
||||
|
||||
sounds["run"] = {"vo/npc/male01/strider_run.wav"}
|
||||
|
||||
sounds["same here"] = {"vo/npc/male01/answer07.wav"}
|
||||
|
||||
sounds["shut up"] = {"vo/npc/male01/answer17.wav"}
|
||||
|
||||
sounds["spread the word"] = {"vo/npc/male01/gordead_ans10.wav"}
|
||||
|
||||
sounds["stop it"] = sounds["cut it"]
|
||||
sounds["stop that"] = sounds["cut it"]
|
||||
|
||||
sounds["stop looking at me"] = {"vo/npc/male01/vquestion01.wav"}
|
||||
|
||||
sounds["sorry"] = {"vo/npc/male01/sorry01.wav", "vo/npc/male01/sorry02.wav", "vo/npc/male01/sorry03.wav"}
|
||||
|
||||
sounds["sry"] = sounds["sorry"]
|
||||
|
||||
sounds["take cover"] = {"vo/npc/male01/takecover02.wav"}
|
||||
|
||||
sounds["take this medkit"] = {"vo/npc/male01/health01.wav", "vo/npc/male01/health02.wav", "vo/npc/male01/health03.wav", "vo/npc/male01/health04.wav"}
|
||||
|
||||
sounds["task at hand"] = {"vo/npc/male01/answer18.wav"}
|
||||
|
||||
sounds["talking to me"] = {"vo/npc/male01/answer30.wav"}
|
||||
|
||||
sounds["thats you"] = {"vo/npc/male01/answer01.wav"}
|
||||
|
||||
sounds["this cant be"] = sounds["it cant be"]
|
||||
sounds["this can't be"] = sounds["it cant be"]
|
||||
|
||||
sounds["this is bad"] = {"vo/npc/male01/gordead_ques10.wav"}
|
||||
|
||||
sounds["too much info"] = {"vo/npc/male01/answer26.wav"}
|
||||
|
||||
sounds["too much information"] = sounds["too much info"]
|
||||
|
||||
sounds["uhoh"] = {"vo/npc/male01/uhoh.wav"}
|
||||
|
||||
sounds["uh oh"] = sounds["uhoh"]
|
||||
|
||||
sounds["wait"] = {"vo/trainyard/man_waitaminute.wav"}
|
||||
|
||||
sounds["wait for me"] = {"vo/npc/male01/squad_reinforce_single04.wav"}
|
||||
|
||||
sounds["wait for us"] = {"vo/npc/male01/squad_reinforce_group04.wav"}
|
||||
|
||||
sounds["wanna bet"] = {"vo/npc/male01/answer27.wav"}
|
||||
|
||||
sounds["watch out"] = {"vo/npc/male01/watchout.wav"}
|
||||
|
||||
sounds["we are done for"] = {"vo/npc/male01/gordead_ans14.wav"}
|
||||
|
||||
sounds["we're done for"] = sounds["we are done for"]
|
||||
|
||||
sounds["what now"] = {"vo/npc/male01/gordead_ques16.wav"}
|
||||
|
||||
sounds["whatever you say"] = {"vo/npc/male01/squad_affirm03.wav"}
|
||||
|
||||
sounds["whats the use"] = {"vo/npc/male01/gordead_ans11.wav"}
|
||||
|
||||
sounds["what's the use"] = sounds["whats the use"]
|
||||
|
||||
sounds["whats the point"] = {"vo/npc/male01/gordead_ans12.wav"}
|
||||
|
||||
sounds["what's the point"] = sounds["whats the point"]
|
||||
sounds["whoops"] = sounds["oops"]
|
||||
|
||||
sounds["why go on"] = {"vo/npc/male01/gordead_ans13.wav"}
|
||||
|
||||
sounds["why telling me"] = {"vo/npc/male01/answer24.wav"}
|
||||
|
||||
sounds["yeah"] = {"vo/npc/male01/yeah02.wav"}
|
||||
|
||||
sounds["yes"] = sounds["yeah"]
|
||||
|
||||
sounds["you and me both"] = {"vo/npc/male01/answer14.wav"}
|
||||
|
||||
sounds["you never know"] = {"vo/npc/male01/answer22.wav"}
|
||||
|
||||
sounds["you sure"] = {"vo/npc/male01/answer37.wav"}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "canChatSound",
|
||||
description = "Whether a chat sound can be played.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who triggered the chat sound.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "chatPhrase",
|
||||
description = "The chat sound phrase that has been detected.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "chatText",
|
||||
description = "The whole chat text the player sent that contains the chat sound phrase.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "canChatSound",
|
||||
description = "False if the chat sound should not be played.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "onChatSound",
|
||||
description = "When a chat sound is played.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who triggered the chat sound.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "chatPhrase",
|
||||
description = "The chat sound phrase that was detected.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "chatText",
|
||||
description = "The whole chat text the player sent that contains the chat sound phrase.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
}
|
||||
}
|
||||
|
||||
local function CheckChat(ply, text)
|
||||
if not GAMEMODE.Config.chatsounds or ply.nextSpeechSound and ply.nextSpeechSound > CurTime() then return end
|
||||
local prefix = string.sub(text, 0, 1)
|
||||
if prefix == "/" or prefix == "!" or prefix == "@" then return end -- should cover most chat commands for various mods/addons
|
||||
local longestMatch = nil
|
||||
local longestMatchLength = 0
|
||||
for k, v in pairs(sounds) do
|
||||
local res1, res2 = string.find(string.lower(text), k)
|
||||
if not res1 then continue end
|
||||
local charBefore = text[res1 - 1]
|
||||
local charAfter = text[res2 + 1]
|
||||
local length = res2 - res1
|
||||
-- Check whether the match is not part of a larger word (e.g. "no" should not match when "know" is said)
|
||||
if charBefore and charBefore ~= "" and charBefore ~= " " then continue end
|
||||
if charAfter and charAfter ~= "" and charAfter ~= " " then continue end
|
||||
|
||||
if length > longestMatchLength then
|
||||
longestMatch = k
|
||||
longestMatchLength = length
|
||||
end
|
||||
end
|
||||
|
||||
if not longestMatch then return end
|
||||
|
||||
local canChatSound = hook.Call("canChatSound", nil, ply, longestMatch, text)
|
||||
if canChatSound == false then return end
|
||||
ply:EmitSound(table.Random(sounds[longestMatch]), 80, 100)
|
||||
ply.nextSpeechSound = CurTime() + GAMEMODE.Config.chatsoundsdelay -- make sure they don't spam HAX HAX HAX, if the server owner so desires
|
||||
hook.Call("onChatSound", nil, ply, longestMatch, text)
|
||||
end
|
||||
hook.Add("PostPlayerSay", "ChatSounds", CheckChat)
|
||||
|
||||
DarkRP.getChatSound = DarkRP.stub{
|
||||
name = "getChatSound",
|
||||
description = "Get a chat sound (play a noise when someone says something) associated with the given phrase.",
|
||||
parameters = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The text that triggers the chat sound.",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "soundPaths",
|
||||
description = "A table of string sound paths associated with the given text.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
function DarkRP.getChatSound(text)
|
||||
return sounds[string.lower(text or "")]
|
||||
end
|
||||
|
||||
DarkRP.setChatSound = DarkRP.stub{
|
||||
name = "setChatSound",
|
||||
description = "Set a chat sound (play a noise when someone says something)",
|
||||
parameters = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The text that should trigger the sound.",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "sounds",
|
||||
description = "A table of string sound paths.",
|
||||
type = "table",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
function DarkRP.setChatSound(text, sndTable)
|
||||
sounds[string.lower(text or "")] = sndTable
|
||||
end
|
||||
56
gamemodes/darkrp/gamemode/modules/cppi/sh_cppi.lua
Normal file
56
gamemodes/darkrp/gamemode/modules/cppi/sh_cppi.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
if CPPI then return end
|
||||
CPPI = {}
|
||||
CPPI.CPPI_DEFER = 100100 --\100\100 = dd
|
||||
CPPI.CPPI_NOTIMPLEMENTED = 7080
|
||||
|
||||
function CPPI:GetName()
|
||||
return "DarkRP"
|
||||
end
|
||||
|
||||
function CPPI:GetVersion()
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
function CPPI:GetInterfaceVersion()
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
function CPPI:GetNameFromUID(uid)
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
local PLAYER = FindMetaTable("Player")
|
||||
function PLAYER:CPPIGetFriends()
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
local ENTITY = FindMetaTable("Entity")
|
||||
function ENTITY:CPPIGetOwner()
|
||||
return NULL, CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
function ENTITY:CPPISetOwner(ply)
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
function ENTITY:CPPISetOwnerUID(UID)
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
function ENTITY:CPPICanTool(ply, tool)
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
function ENTITY:CPPICanPhysgun(ply)
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
function ENTITY:CPPICanPickup(ply)
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
|
||||
function ENTITY:CPPICanPunt(ply)
|
||||
return CPPI.CPPI_NOTIMPLEMENTED
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
local MotdMessage =
|
||||
[[
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
DarkRP Message of the day!
|
||||
---------------------------------------------------------------------------
|
||||
]]
|
||||
|
||||
local endMOTD = "---------------------------------------------------------------------------\n"
|
||||
|
||||
local function drawMOTD(text)
|
||||
MsgC(Color(255, 20, 20, 255), MotdMessage, color_white, text, Color(255, 20, 20, 255), endMOTD)
|
||||
end
|
||||
|
||||
local function receiveMOTD(html, len, headers, code)
|
||||
if not headers or headers.Status and string.sub(headers.Status, 1, 3) ~= "200" then return end
|
||||
drawMOTD(html)
|
||||
end
|
||||
|
||||
local function showMOTD()
|
||||
http.Fetch("https://raw.githubusercontent.com/FPtje/DarkRPMotd/master/motd.txt", receiveMOTD, fn.Id)
|
||||
end
|
||||
timer.Simple(5, showMOTD)
|
||||
|
||||
concommand.Add("DarkRP_motd", showMOTD)
|
||||
44
gamemodes/darkrp/gamemode/modules/deathpov/cl_init.lua
Normal file
44
gamemodes/darkrp/gamemode/modules/deathpov/cl_init.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
local view = {
|
||||
origin = Vector(0, 0, 0),
|
||||
angles = Angle(0, 0, 0),
|
||||
fov = 90,
|
||||
znear = 1
|
||||
}
|
||||
|
||||
local deathpov = GM.Config.deathpov
|
||||
hook.Add("CalcView", "rp_deathPOV", function(ply, origin, angles, fov)
|
||||
-- Entity:Alive() is being slow as hell, we might actually see ourselves from third person for frame or two
|
||||
if not deathpov or ply:Health() > 0 then return end
|
||||
|
||||
local Ragdoll = ply:GetRagdollEntity()
|
||||
if not IsValid(Ragdoll) then return end
|
||||
|
||||
local head = Ragdoll:LookupAttachment("eyes")
|
||||
head = Ragdoll:GetAttachment(head)
|
||||
if not head or not head.Pos then return end
|
||||
|
||||
if not Ragdoll.BonesRattled then
|
||||
Ragdoll.BonesRattled = true
|
||||
|
||||
Ragdoll:InvalidateBoneCache()
|
||||
Ragdoll:SetupBones()
|
||||
|
||||
local matrix
|
||||
|
||||
for bone = 0, (Ragdoll:GetBoneCount() or 1) do
|
||||
if Ragdoll:GetBoneName(bone):lower():find("head") then
|
||||
matrix = Ragdoll:GetBoneMatrix(bone)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if IsValid(matrix) then
|
||||
matrix:SetScale(Vector(0, 0, 0))
|
||||
end
|
||||
end
|
||||
|
||||
view.origin = head.Pos + head.Ang:Up() * 8
|
||||
view.angles = head.Ang
|
||||
|
||||
return view
|
||||
end)
|
||||
248
gamemodes/darkrp/gamemode/modules/dermaskin/cl_dermaskin.lua
Normal file
248
gamemodes/darkrp/gamemode/modules/dermaskin/cl_dermaskin.lua
Normal file
@@ -0,0 +1,248 @@
|
||||
-- Skin for DarkRP gui's
|
||||
SKIN = {}
|
||||
|
||||
SKIN.PrintName = "DarkRP"
|
||||
SKIN.Author = "FPtje Falco"
|
||||
SKIN.DermaVersion = 1
|
||||
SKIN.GwenTexture = Material("darkrp/darkrpderma.png")
|
||||
|
||||
|
||||
SKIN.colTextEntryText = color_white
|
||||
SKIN.colTextEntryTextCursor = color_white
|
||||
|
||||
SKIN.colTextEntryTextPlaceholder = Color(200, 200, 200, 200) -- Unofficial but will probably be named this
|
||||
|
||||
SKIN.tex = {}
|
||||
|
||||
SKIN.tex.Selection = GWEN.CreateTextureBorder(384, 32, 31, 31, 4, 4, 4, 4)
|
||||
|
||||
SKIN.tex.Panels = {}
|
||||
SKIN.tex.Panels.Normal = GWEN.CreateTextureBorder(256, 0, 63, 63, 16, 16, 16, 16)
|
||||
SKIN.tex.Panels.Bright = GWEN.CreateTextureBorder(256 + 64, 0, 63, 63, 16, 16, 16, 16)
|
||||
SKIN.tex.Panels.Dark = GWEN.CreateTextureBorder(256, 64, 63, 63, 16, 16, 16, 16)
|
||||
SKIN.tex.Panels.Highlight = GWEN.CreateTextureBorder(256 + 64, 64, 63, 63, 16, 16, 16, 16)
|
||||
|
||||
SKIN.tex.Button = GWEN.CreateTextureBorder(480, 0, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.Button_Hovered = GWEN.CreateTextureBorder(480, 32, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.Button_Dead = GWEN.CreateTextureBorder(480, 64, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.Button_Down = GWEN.CreateTextureBorder(480, 96, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.Shadow = GWEN.CreateTextureBorder(448, 0, 31, 31, 8, 8, 8, 8)
|
||||
|
||||
SKIN.tex.Tree = GWEN.CreateTextureBorder(256, 128, 127, 127, 16, 16, 16, 16)
|
||||
SKIN.tex.Checkbox_Checked = GWEN.CreateTextureNormal(448, 32, 15, 15)
|
||||
SKIN.tex.Checkbox = GWEN.CreateTextureNormal(464, 32, 15, 15)
|
||||
SKIN.tex.CheckboxD_Checked = GWEN.CreateTextureNormal(448, 48, 15, 15)
|
||||
SKIN.tex.CheckboxD = GWEN.CreateTextureNormal(464, 48, 15, 15)
|
||||
--SKIN.tex.RadioButton_Checked = GWEN.CreateTextureNormal(448, 64, 15, 15)
|
||||
--SKIN.tex.RadioButton = GWEN.CreateTextureNormal(464, 64, 15, 15)
|
||||
--SKIN.tex.RadioButtonD_Checked = GWEN.CreateTextureNormal(448, 80, 15, 15)
|
||||
--SKIN.tex.RadioButtonD = GWEN.CreateTextureNormal(464, 80, 15, 15)
|
||||
SKIN.tex.TreePlus = GWEN.CreateTextureNormal(448, 96, 15, 15)
|
||||
SKIN.tex.TreeMinus = GWEN.CreateTextureNormal(464, 96, 15, 15)
|
||||
--SKIN.tex.Menu_Strip = GWEN.CreateTextureBorder(0, 128, 127, 21, 1, 1, 1, 1)
|
||||
SKIN.tex.TextBox = GWEN.CreateTextureBorder(0, 150, 127, 21, 4, 4, 4, 4)
|
||||
SKIN.tex.TextBox_Focus = GWEN.CreateTextureBorder(0, 172, 127, 21, 4, 4, 4, 4)
|
||||
SKIN.tex.TextBox_Disabled = GWEN.CreateTextureBorder(0, 193, 127, 21, 4, 4, 4, 4)
|
||||
SKIN.tex.MenuBG_Margin = GWEN.CreateTextureBorder(128, 128, 127, 63, 24, 8, 8, 8)
|
||||
SKIN.tex.MenuBG = GWEN.CreateTextureBorder(128, 192, 127, 63, 8, 8, 8, 8)
|
||||
SKIN.tex.MenuBG_Hover = GWEN.CreateTextureBorder(128, 256, 127, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.MenuBG_Spacer = GWEN.CreateTextureNormal(128, 288, 127, 3)
|
||||
SKIN.tex.Tab_Control = GWEN.CreateTextureBorder(0, 256, 127, 127, 8, 8, 8, 8)
|
||||
SKIN.tex.TabB_Active = GWEN.CreateTextureBorder(0, 416, 63, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.TabB_Inactive = GWEN.CreateTextureBorder(0 + 128, 416, 63, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.TabT_Active = GWEN.CreateTextureBorder(0, 384, 63, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.TabT_Inactive = GWEN.CreateTextureBorder(0 + 128, 384, 63, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.TabL_Active = GWEN.CreateTextureBorder(64, 384, 31, 63, 8, 8, 8, 8)
|
||||
SKIN.tex.TabL_Inactive = GWEN.CreateTextureBorder(64 + 128, 384, 31, 63, 8, 8, 8, 8)
|
||||
SKIN.tex.TabR_Active = GWEN.CreateTextureBorder(96, 384, 31, 63, 8, 8, 8, 8)
|
||||
SKIN.tex.TabR_Inactive = GWEN.CreateTextureBorder(96 + 128, 384, 31, 63, 8, 8, 8, 8)
|
||||
SKIN.tex.Tab_Bar = GWEN.CreateTextureBorder(128, 352, 127, 31, 4, 4, 4, 4)
|
||||
|
||||
SKIN.tex.Window = {}
|
||||
|
||||
SKIN.tex.Window.Normal = GWEN.CreateTextureBorder(0, 0, 127, 127, 8, 32, 8, 8)
|
||||
SKIN.tex.Window.Inactive = GWEN.CreateTextureBorder(128, 0, 127, 127, 8, 32, 8, 8)
|
||||
SKIN.tex.Window.Close = GWEN.CreateTextureNormal(0, 224, 24, 24)
|
||||
SKIN.tex.Window.Close_Hover = GWEN.CreateTextureNormal(32, 224, 24, 24)
|
||||
SKIN.tex.Window.Close_Down = GWEN.CreateTextureNormal(64, 224, 24, 24)
|
||||
SKIN.tex.Window.Close_Disabled = GWEN.CreateTextureNormal(96, 224, 24, 24)
|
||||
|
||||
SKIN.tex.Window.Maxi = GWEN.CreateTextureNormal(32 + 96 * 2, 448, 31, 31)
|
||||
SKIN.tex.Window.Maxi_Hover = GWEN.CreateTextureNormal(64 + 96 * 2, 448, 31, 31)
|
||||
SKIN.tex.Window.Maxi_Down = GWEN.CreateTextureNormal(96 + 96 * 2, 448, 31, 31)
|
||||
|
||||
SKIN.tex.Window.Restore = GWEN.CreateTextureNormal(32 + 96 * 2, 448 + 32, 31, 31)
|
||||
SKIN.tex.Window.Restore_Hover = GWEN.CreateTextureNormal(64 + 96 * 2, 448 + 32, 31, 31)
|
||||
SKIN.tex.Window.Restore_Down = GWEN.CreateTextureNormal(96 + 96 * 2, 448 + 32, 31, 31)
|
||||
|
||||
SKIN.tex.Window.Mini = GWEN.CreateTextureNormal(32 + 96, 448, 31, 31)
|
||||
SKIN.tex.Window.Mini_Hover = GWEN.CreateTextureNormal(64 + 96, 448, 31, 31)
|
||||
SKIN.tex.Window.Mini_Down = GWEN.CreateTextureNormal(96 + 96, 448, 31, 31)
|
||||
|
||||
SKIN.tex.Scroller = {}
|
||||
SKIN.tex.Scroller.TrackV = GWEN.CreateTextureBorder(384, 208, 15, 127, 4, 4, 4, 4)
|
||||
SKIN.tex.Scroller.ButtonV_Normal = GWEN.CreateTextureBorder(384 + 16, 208, 15, 127, 4, 4, 4, 4)
|
||||
SKIN.tex.Scroller.ButtonV_Hover = GWEN.CreateTextureBorder(384 + 32, 208, 15, 127, 4, 4, 4, 4)
|
||||
SKIN.tex.Scroller.ButtonV_Down = GWEN.CreateTextureBorder(384 + 48, 208, 15, 127, 4, 4, 4, 4)
|
||||
SKIN.tex.Scroller.ButtonV_Disabled = GWEN.CreateTextureBorder(384 + 64, 208, 15, 127, 4, 4, 4, 4)
|
||||
|
||||
SKIN.tex.Scroller.TrackH = GWEN.CreateTextureBorder(384, 128, 127, 15, 4, 4, 4, 4)
|
||||
SKIN.tex.Scroller.ButtonH_Normal = GWEN.CreateTextureBorder(384, 128 + 16, 127, 15, 4, 4, 4, 4)
|
||||
SKIN.tex.Scroller.ButtonH_Hover = GWEN.CreateTextureBorder(384, 128 + 32, 127, 15, 4, 4, 4, 4)
|
||||
SKIN.tex.Scroller.ButtonH_Down = GWEN.CreateTextureBorder(384, 128 + 48, 127, 15, 4, 4, 4, 4)
|
||||
SKIN.tex.Scroller.ButtonH_Disabled = GWEN.CreateTextureBorder(384, 128 + 64, 127, 15, 4, 4, 4, 4)
|
||||
|
||||
SKIN.tex.Scroller.LeftButton_Normal = GWEN.CreateTextureBorder(464, 208, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.LeftButton_Hover = GWEN.CreateTextureBorder(480, 208, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.LeftButton_Down = GWEN.CreateTextureBorder(464, 272, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.LeftButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272, 15, 15, 2, 2, 2, 2)
|
||||
|
||||
SKIN.tex.Scroller.UpButton_Normal = GWEN.CreateTextureBorder(464, 208 + 16, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.UpButton_Hover = GWEN.CreateTextureBorder(480, 208 + 16, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.UpButton_Down = GWEN.CreateTextureBorder(464, 272 + 16, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.UpButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 16, 15, 15, 2, 2, 2, 2)
|
||||
|
||||
SKIN.tex.Scroller.RightButton_Normal = GWEN.CreateTextureBorder(464, 208 + 32, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.RightButton_Hover = GWEN.CreateTextureBorder(480, 208 + 32, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.RightButton_Down = GWEN.CreateTextureBorder(464, 272 + 32, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.RightButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 32, 15, 15, 2, 2, 2, 2)
|
||||
|
||||
SKIN.tex.Scroller.DownButton_Normal = GWEN.CreateTextureBorder(464, 208 + 48, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.DownButton_Hover = GWEN.CreateTextureBorder(480, 208 + 48, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.DownButton_Down = GWEN.CreateTextureBorder(464, 272 + 48, 15, 15, 2, 2, 2, 2)
|
||||
SKIN.tex.Scroller.DownButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 48, 15, 15, 2, 2, 2, 2)
|
||||
|
||||
SKIN.tex.Menu = {}
|
||||
SKIN.tex.Menu.RightArrow = GWEN.CreateTextureNormal(464, 112, 15, 15)
|
||||
|
||||
SKIN.tex.Input = {}
|
||||
SKIN.tex.Input.ListBox = GWEN.CreateTextureBorder(256, 256, 63, 127, 8, 8, 8, 8)
|
||||
|
||||
SKIN.tex.Input.ComboBox = {}
|
||||
SKIN.tex.Input.ComboBox.Normal = GWEN.CreateTextureBorder(384, 336, 127, 31, 8, 8, 32, 8)
|
||||
SKIN.tex.Input.ComboBox.Hover = GWEN.CreateTextureBorder(384, 336 + 32, 127, 31, 8, 8, 32, 8)
|
||||
SKIN.tex.Input.ComboBox.Down = GWEN.CreateTextureBorder(384, 336 + 64, 127, 31, 8, 8, 32, 8)
|
||||
SKIN.tex.Input.ComboBox.Disabled = GWEN.CreateTextureBorder(384, 336 + 96, 127, 31, 8, 8, 32, 8)
|
||||
|
||||
SKIN.tex.Input.ComboBox.Button = {}
|
||||
SKIN.tex.Input.ComboBox.Button.Normal = GWEN.CreateTextureNormal(496, 272, 15, 15)
|
||||
SKIN.tex.Input.ComboBox.Button.Hover = GWEN.CreateTextureNormal(496, 272 + 16, 15, 15)
|
||||
SKIN.tex.Input.ComboBox.Button.Down = GWEN.CreateTextureNormal(496, 272 + 32, 15, 15)
|
||||
SKIN.tex.Input.ComboBox.Button.Disabled = GWEN.CreateTextureNormal(496, 272 + 48, 15, 15)
|
||||
|
||||
SKIN.tex.Input.UpDown = {}
|
||||
SKIN.tex.Input.UpDown.Up = {}
|
||||
SKIN.tex.Input.UpDown.Up.Normal = GWEN.CreateTextureCentered(384, 112, 7, 7)
|
||||
SKIN.tex.Input.UpDown.Up.Hover = GWEN.CreateTextureCentered(384 + 8, 112, 7, 7)
|
||||
SKIN.tex.Input.UpDown.Up.Down = GWEN.CreateTextureCentered(384 + 16, 112, 7, 7)
|
||||
SKIN.tex.Input.UpDown.Up.Disabled = GWEN.CreateTextureCentered(384 + 24, 112, 7, 7)
|
||||
|
||||
SKIN.tex.Input.UpDown.Down = {}
|
||||
SKIN.tex.Input.UpDown.Down.Normal = GWEN.CreateTextureCentered(384, 120, 7, 7)
|
||||
SKIN.tex.Input.UpDown.Down.Hover = GWEN.CreateTextureCentered(384 + 8, 120, 7, 7)
|
||||
SKIN.tex.Input.UpDown.Down.Down = GWEN.CreateTextureCentered(384 + 16, 120, 7, 7)
|
||||
SKIN.tex.Input.UpDown.Down.Disabled = GWEN.CreateTextureCentered(384 + 24, 120, 7, 7)
|
||||
|
||||
SKIN.tex.Input.Slider = {}
|
||||
SKIN.tex.Input.Slider.H = {}
|
||||
SKIN.tex.Input.Slider.H.Normal = GWEN.CreateTextureNormal(416, 32, 15, 15)
|
||||
SKIN.tex.Input.Slider.H.Hover = GWEN.CreateTextureNormal(416, 32 + 16, 15, 15)
|
||||
SKIN.tex.Input.Slider.H.Down = GWEN.CreateTextureNormal(416, 32 + 32, 15, 15)
|
||||
SKIN.tex.Input.Slider.H.Disabled = GWEN.CreateTextureNormal(416, 32 + 48, 15, 15)
|
||||
|
||||
SKIN.tex.Input.Slider.V = {}
|
||||
SKIN.tex.Input.Slider.V.Normal = GWEN.CreateTextureNormal(416 + 16, 32, 15, 15)
|
||||
SKIN.tex.Input.Slider.V.Hover = GWEN.CreateTextureNormal(416 + 16, 32 + 16, 15, 15)
|
||||
SKIN.tex.Input.Slider.V.Down = GWEN.CreateTextureNormal(416 + 16, 32 + 32, 15, 15)
|
||||
SKIN.tex.Input.Slider.V.Disabled = GWEN.CreateTextureNormal(416 + 16, 32 + 48, 15, 15)
|
||||
|
||||
SKIN.tex.Input.ListBox = {}
|
||||
SKIN.tex.Input.ListBox.Background = GWEN.CreateTextureBorder(256, 256, 63, 127, 8, 8, 8, 8)
|
||||
SKIN.tex.Input.ListBox.Hovered = GWEN.CreateTextureBorder(320, 320, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.Input.ListBox.EvenLine = GWEN.CreateTextureBorder(352, 256, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.Input.ListBox.OddLine = GWEN.CreateTextureBorder(352, 288, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.Input.ListBox.EvenLineSelected = GWEN.CreateTextureBorder(320, 270, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.Input.ListBox.OddLineSelected = GWEN.CreateTextureBorder(320, 288, 31, 31, 8, 8, 8, 8)
|
||||
|
||||
SKIN.tex.ProgressBar = {}
|
||||
SKIN.tex.ProgressBar.Back = GWEN.CreateTextureBorder(384, 0, 31, 31, 8, 8, 8, 8)
|
||||
SKIN.tex.ProgressBar.Front = GWEN.CreateTextureBorder(384 + 32, 0, 31, 31, 8, 8, 8, 8)
|
||||
|
||||
|
||||
SKIN.tex.CategoryList = {}
|
||||
SKIN.tex.CategoryList.Outer = GWEN.CreateTextureBorder(256, 384, 63, 63, 8, 8, 8, 8)
|
||||
SKIN.tex.CategoryList.Inner = GWEN.CreateTextureBorder(256 + 64, 384, 63, 63, 8, 21, 8, 8)
|
||||
SKIN.tex.CategoryList.Header = GWEN.CreateTextureBorder(320, 352, 63, 31, 8, 8, 8, 8)
|
||||
|
||||
SKIN.tex.Tooltip = GWEN.CreateTextureBorder(384, 64, 31, 31, 8, 8, 8, 8)
|
||||
|
||||
SKIN.Colours = {}
|
||||
|
||||
SKIN.Colours.Window = {}
|
||||
SKIN.Colours.Window.TitleActive = GWEN.TextureColor(4 + 8 * 0, 508)
|
||||
SKIN.Colours.Window.TitleInactive = GWEN.TextureColor(4 + 8 * 1, 508)
|
||||
|
||||
SKIN.Colours.Button = {}
|
||||
SKIN.Colours.Button.Normal = GWEN.TextureColor(4 + 8 * 2, 508)
|
||||
SKIN.Colours.Button.Hover = GWEN.TextureColor(4 + 8 * 3, 508)
|
||||
SKIN.Colours.Button.Down = GWEN.TextureColor(4 + 8 * 2, 500)
|
||||
SKIN.Colours.Button.Disabled = GWEN.TextureColor(4 + 8 * 3, 500)
|
||||
|
||||
SKIN.Colours.Tab = {}
|
||||
SKIN.Colours.Tab.Active = {}
|
||||
SKIN.Colours.Tab.Active.Normal = GWEN.TextureColor(4 + 8 * 4, 508)
|
||||
SKIN.Colours.Tab.Active.Hover = GWEN.TextureColor(4 + 8 * 5, 508)
|
||||
SKIN.Colours.Tab.Active.Down = GWEN.TextureColor(4 + 8 * 4, 500)
|
||||
SKIN.Colours.Tab.Active.Disabled = GWEN.TextureColor(4 + 8 * 5, 500)
|
||||
|
||||
SKIN.Colours.Tab.Inactive = {}
|
||||
SKIN.Colours.Tab.Inactive.Normal = GWEN.TextureColor(4 + 8 * 6, 508)
|
||||
SKIN.Colours.Tab.Inactive.Hover = GWEN.TextureColor(4 + 8 * 7, 508)
|
||||
SKIN.Colours.Tab.Inactive.Down = GWEN.TextureColor(4 + 8 * 6, 500)
|
||||
SKIN.Colours.Tab.Inactive.Disabled = GWEN.TextureColor(4 + 8 * 7, 500)
|
||||
|
||||
SKIN.Colours.Label = {}
|
||||
SKIN.Colours.Label.Default = GWEN.TextureColor(4 + 8 * 8, 508)
|
||||
SKIN.Colours.Label.Bright = GWEN.TextureColor(4 + 8 * 9, 508)
|
||||
SKIN.Colours.Label.Dark = GWEN.TextureColor(4 + 8 * 8, 500)
|
||||
SKIN.Colours.Label.Highlight = GWEN.TextureColor(4 + 8 * 9, 500)
|
||||
|
||||
SKIN.Colours.Tree = {}
|
||||
SKIN.Colours.Tree.Lines = GWEN.TextureColor(4 + 8 * 10, 508)
|
||||
---- !!!
|
||||
SKIN.Colours.Tree.Normal = GWEN.TextureColor(4 + 8 * 11, 508)
|
||||
SKIN.Colours.Tree.Hover = GWEN.TextureColor(4 + 8 * 10, 500)
|
||||
SKIN.Colours.Tree.Selected = GWEN.TextureColor(4 + 8 * 11, 500)
|
||||
|
||||
SKIN.Colours.Properties = {}
|
||||
SKIN.Colours.Properties.Line_Normal = GWEN.TextureColor(4 + 8 * 12, 508)
|
||||
SKIN.Colours.Properties.Line_Selected = GWEN.TextureColor(4 + 8 * 13, 508)
|
||||
SKIN.Colours.Properties.Line_Hover = GWEN.TextureColor(4 + 8 * 12, 500)
|
||||
SKIN.Colours.Properties.Title = GWEN.TextureColor(4 + 8 * 13, 500)
|
||||
SKIN.Colours.Properties.Column_Normal = GWEN.TextureColor(4 + 8 * 14, 508)
|
||||
SKIN.Colours.Properties.Column_Selected = GWEN.TextureColor(4 + 8 * 15, 508)
|
||||
SKIN.Colours.Properties.Column_Hover = GWEN.TextureColor(4 + 8 * 14, 500)
|
||||
SKIN.Colours.Properties.Border = GWEN.TextureColor(4 + 8 * 15, 500)
|
||||
SKIN.Colours.Properties.Label_Normal = GWEN.TextureColor(4 + 8 * 16, 508)
|
||||
SKIN.Colours.Properties.Label_Selected = GWEN.TextureColor(4 + 8 * 17, 508)
|
||||
SKIN.Colours.Properties.Label_Hover = GWEN.TextureColor(4 + 8 * 16, 500)
|
||||
|
||||
SKIN.Colours.Category = {}
|
||||
SKIN.Colours.Category.Header = GWEN.TextureColor(4 + 8 * 18, 500)
|
||||
SKIN.Colours.Category.Header_Closed = GWEN.TextureColor(4 + 8 * 19, 500)
|
||||
SKIN.Colours.Category.Line = {}
|
||||
SKIN.Colours.Category.Line.Text = GWEN.TextureColor(4 + 8 * 20, 508)
|
||||
SKIN.Colours.Category.Line.Text_Hover = GWEN.TextureColor(4 + 8 * 21, 508)
|
||||
SKIN.Colours.Category.Line.Text_Selected = GWEN.TextureColor(4 + 8 * 20, 500)
|
||||
SKIN.Colours.Category.Line.Button = GWEN.TextureColor(4 + 8 * 21, 500)
|
||||
SKIN.Colours.Category.Line.Button_Hover = GWEN.TextureColor(4 + 8 * 22, 508)
|
||||
SKIN.Colours.Category.Line.Button_Selected = GWEN.TextureColor(4 + 8 * 23, 508)
|
||||
SKIN.Colours.Category.LineAlt = {}
|
||||
SKIN.Colours.Category.LineAlt.Text = GWEN.TextureColor(4 + 8 * 22, 500)
|
||||
SKIN.Colours.Category.LineAlt.Text_Hover = GWEN.TextureColor(4 + 8 * 23, 500)
|
||||
SKIN.Colours.Category.LineAlt.Text_Selected = GWEN.TextureColor(4 + 8 * 24, 508)
|
||||
SKIN.Colours.Category.LineAlt.Button = GWEN.TextureColor(4 + 8 * 25, 508)
|
||||
SKIN.Colours.Category.LineAlt.Button_Hover = GWEN.TextureColor(4 + 8 * 24, 500)
|
||||
SKIN.Colours.Category.LineAlt.Button_Selected = GWEN.TextureColor(4 + 8 * 25, 500)
|
||||
|
||||
derma.DefineSkin("DarkRP", "The official SKIN for DarkRP", SKIN)
|
||||
@@ -0,0 +1 @@
|
||||
resource.AddFile("materials/darkrp/darkrpderma.png")
|
||||
165
gamemodes/darkrp/gamemode/modules/doorsystem/cl_doors.lua
Normal file
165
gamemodes/darkrp/gamemode/modules/doorsystem/cl_doors.lua
Normal file
@@ -0,0 +1,165 @@
|
||||
local meta = FindMetaTable("Entity")
|
||||
local black = color_black
|
||||
local white = Color(255, 255, 255, 200)
|
||||
local red = Color(128, 30, 30, 255)
|
||||
local changeDoorAccess = false
|
||||
|
||||
local function updatePrivs()
|
||||
CAMI.PlayerHasAccess(LocalPlayer(), "DarkRP_ChangeDoorSettings", function(b, _)
|
||||
changeDoorAccess = b
|
||||
end)
|
||||
end
|
||||
-- Timer due to lack of "on privilege changed" hook
|
||||
hook.Add("InitPostEntity", "Load door privileges", function()
|
||||
updatePrivs()
|
||||
timer.Create("Door changeDoorAccess checker", 1, 0, updatePrivs)
|
||||
end)
|
||||
|
||||
function meta:drawOwnableInfo()
|
||||
local ply = LocalPlayer()
|
||||
if ply:InVehicle() and not ply:GetAllowWeaponsInVehicle() then return end
|
||||
|
||||
-- Look, if you want to change the way door ownership is drawn, don't edit this file, use the hook instead!
|
||||
local doorDrawing = hook.Call("HUDDrawDoorData", nil, self)
|
||||
if doorDrawing == true then return end
|
||||
|
||||
local blocked = self:getKeysNonOwnable()
|
||||
local doorTeams = self:getKeysDoorTeams()
|
||||
local doorGroup = self:getKeysDoorGroup()
|
||||
local playerOwned = self:isKeysOwned() or table.GetFirstValue(self:getKeysCoOwners() or {}) ~= nil
|
||||
local owned = playerOwned or doorGroup or doorTeams
|
||||
|
||||
local doorInfo = {}
|
||||
|
||||
local title = self:getKeysTitle()
|
||||
if title then table.insert(doorInfo, title) end
|
||||
|
||||
if owned then
|
||||
table.insert(doorInfo, DarkRP.getPhrase("keys_owned_by"))
|
||||
end
|
||||
|
||||
if playerOwned then
|
||||
if self:isKeysOwned() then table.insert(doorInfo, self:getDoorOwner():Nick()) end
|
||||
for k in pairs(self:getKeysCoOwners() or {}) do
|
||||
local ent = Player(k)
|
||||
if not IsValid(ent) or not ent:IsPlayer() then continue end
|
||||
table.insert(doorInfo, ent:Nick())
|
||||
end
|
||||
|
||||
local allowedCoOwn = self:getKeysAllowedToOwn()
|
||||
if allowedCoOwn and not fn.Null(allowedCoOwn) then
|
||||
table.insert(doorInfo, DarkRP.getPhrase("keys_other_allowed"))
|
||||
|
||||
for k in pairs(allowedCoOwn) do
|
||||
local ent = Player(k)
|
||||
if not IsValid(ent) or not ent:IsPlayer() then continue end
|
||||
table.insert(doorInfo, ent:Nick())
|
||||
end
|
||||
end
|
||||
elseif doorGroup then
|
||||
table.insert(doorInfo, doorGroup)
|
||||
elseif doorTeams then
|
||||
for k, v in pairs(doorTeams) do
|
||||
if not v or not RPExtraTeams[k] then continue end
|
||||
|
||||
table.insert(doorInfo, RPExtraTeams[k].name)
|
||||
end
|
||||
elseif blocked and changeDoorAccess then
|
||||
table.insert(doorInfo, DarkRP.getPhrase("keys_allow_ownership"))
|
||||
elseif not blocked then
|
||||
table.insert(doorInfo, DarkRP.getPhrase("keys_unowned"))
|
||||
if changeDoorAccess then
|
||||
table.insert(doorInfo, DarkRP.getPhrase("keys_disallow_ownership"))
|
||||
end
|
||||
end
|
||||
|
||||
if self:IsVehicle() then
|
||||
local driver = self:GetDriver()
|
||||
if driver:IsPlayer() then
|
||||
table.insert(doorInfo, DarkRP.getPhrase("driver", driver:Nick()))
|
||||
end
|
||||
end
|
||||
|
||||
local x, y = ScrW() / 2, ScrH() / 2
|
||||
local text = table.concat(doorInfo, "\n")
|
||||
draw.DrawNonParsedText(text, "Roboto20", x , y + 1 , black, 1)
|
||||
draw.DrawNonParsedText(text, "Roboto20", x, y, (blocked or owned) and white or red, 1)
|
||||
end
|
||||
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Door data
|
||||
---------------------------------------------------------------------------]]
|
||||
DarkRP.doorData = DarkRP.doorData or {}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Interface functions
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:getDoorData()
|
||||
local doorData = DarkRP.doorData[self:EntIndex()] or {}
|
||||
|
||||
self.DoorData = doorData -- Backwards compatibility
|
||||
|
||||
return doorData
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Networking
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve all the data for all doors
|
||||
---------------------------------------------------------------------------]]
|
||||
local function retrieveAllDoorData(len)
|
||||
local count = net.ReadUInt(16)
|
||||
|
||||
for i = 1, count do
|
||||
local ix = net.ReadUInt(16)
|
||||
local varCount = net.ReadUInt(8)
|
||||
|
||||
DarkRP.doorData[ix] = DarkRP.doorData[ix] or {}
|
||||
|
||||
for vc = 1, varCount do
|
||||
local name, value = DarkRP.readNetDoorVar()
|
||||
DarkRP.doorData[ix][name] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
net.Receive("DarkRP_AllDoorData", retrieveAllDoorData)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Update changed variables
|
||||
---------------------------------------------------------------------------]]
|
||||
local function updateDoorData()
|
||||
local door = net.ReadUInt(32)
|
||||
|
||||
DarkRP.doorData[door] = DarkRP.doorData[door] or {}
|
||||
|
||||
local var, value = DarkRP.readNetDoorVar()
|
||||
|
||||
DarkRP.doorData[door][var] = value
|
||||
end
|
||||
net.Receive("DarkRP_UpdateDoorData", updateDoorData)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Set a value of a single doorvar to nil
|
||||
---------------------------------------------------------------------------]]
|
||||
local function removeDoorVar()
|
||||
local door = net.ReadUInt(16)
|
||||
local id = net.ReadUInt(8)
|
||||
|
||||
local name = id == 0 and net.ReadString() or DarkRP.getDoorVars()[id].name
|
||||
|
||||
if not DarkRP.doorData[door] then return end
|
||||
DarkRP.doorData[door][name] = nil
|
||||
end
|
||||
net.Receive("DarkRP_RemoveDoorVar", removeDoorVar)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Remove doordata of removed entity
|
||||
---------------------------------------------------------------------------]]
|
||||
local function removeDoorData()
|
||||
local door = net.ReadUInt(32)
|
||||
DarkRP.doorData[door] = nil
|
||||
end
|
||||
net.Receive("DarkRP_RemoveDoorData", removeDoorData)
|
||||
@@ -0,0 +1,48 @@
|
||||
DarkRP.readNetDoorVar = DarkRP.stub{
|
||||
name = "readNetDoorVar",
|
||||
description = "Internal function. You probably shouldn't need this. DarkRP calls this function when reading DoorVar net messages. This function reads the net data for a specific DoorVar.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "name",
|
||||
description = "The name of the DoorVar.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "value",
|
||||
description = "The value of the DoorVar.",
|
||||
type = "any"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.drawOwnableInfo = DarkRP.stub{
|
||||
name = "drawOwnableInfo",
|
||||
description = "Draw the ownability information on a door or vehicle.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "HUDDrawDoorData",
|
||||
description = "Called when DarkRP is about to draw the door ownability information of a door or vehicle. Override this hook to ",
|
||||
parameters = {
|
||||
{
|
||||
name = "ent",
|
||||
description = "The door or vehicle of which the ownability information is about to be drawn.",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "override",
|
||||
description = "Return true in your hook to disable the default drawing and use your own.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
323
gamemodes/darkrp/gamemode/modules/doorsystem/sh_doors.lua
Normal file
323
gamemodes/darkrp/gamemode/modules/doorsystem/sh_doors.lua
Normal file
@@ -0,0 +1,323 @@
|
||||
local meta = FindMetaTable("Entity")
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
local ownableDoors = {
|
||||
["func_door"] = true,
|
||||
["func_door_rotating"] = true,
|
||||
["prop_door_rotating"] = true
|
||||
}
|
||||
local unOwnableDoors = {
|
||||
["func_door"] = true,
|
||||
["func_door_rotating"] = true,
|
||||
["prop_door_rotating"] = true,
|
||||
["func_movelinear"] = true,
|
||||
["prop_dynamic"] = true
|
||||
}
|
||||
function meta:isKeysOwnable()
|
||||
if not IsValid(self) then return false end
|
||||
|
||||
local class = self:GetClass()
|
||||
|
||||
if (ownableDoors[class] or
|
||||
(GAMEMODE.Config.allowvehicleowning and self:IsVehicle() and (not IsValid(self:GetParent()) or not self:GetParent():IsVehicle()))) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function meta:isDoor()
|
||||
local class = self:GetClass()
|
||||
|
||||
if unOwnableDoors[class] then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function meta:isKeysOwned()
|
||||
if IsValid(self:getDoorOwner()) then return true end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function meta:getDoorOwner()
|
||||
local doorData = self:getDoorData()
|
||||
if not doorData then return nil end
|
||||
|
||||
return doorData.owner and Player(doorData.owner) or nil
|
||||
end
|
||||
|
||||
function meta:isMasterOwner(ply)
|
||||
return ply == self:getDoorOwner()
|
||||
end
|
||||
|
||||
function meta:isKeysOwnedBy(ply)
|
||||
if self:isMasterOwner(ply) then return true end
|
||||
|
||||
local coOwners = self:getKeysCoOwners()
|
||||
return coOwners and coOwners[ply:UserID()] or false
|
||||
end
|
||||
|
||||
function meta:isKeysAllowedToOwn(ply)
|
||||
local doorData = self:getDoorData()
|
||||
if not doorData then return false end
|
||||
|
||||
return doorData.allowedToOwn and doorData.allowedToOwn[ply:UserID()] or false
|
||||
end
|
||||
|
||||
function meta:getKeysNonOwnable()
|
||||
local doorData = self:getDoorData()
|
||||
if not doorData then return nil end
|
||||
|
||||
return doorData.nonOwnable
|
||||
end
|
||||
|
||||
function meta:getKeysTitle()
|
||||
local doorData = self:getDoorData()
|
||||
if not doorData then return nil end
|
||||
|
||||
return doorData.title
|
||||
end
|
||||
|
||||
function meta:getKeysDoorGroup()
|
||||
local doorData = self:getDoorData()
|
||||
if not doorData then return nil end
|
||||
|
||||
return doorData.groupOwn
|
||||
end
|
||||
|
||||
function meta:getKeysDoorTeams()
|
||||
local doorData = self:getDoorData()
|
||||
if not doorData or table.IsEmpty(doorData.teamOwn or {}) then return nil end
|
||||
|
||||
return doorData.teamOwn
|
||||
end
|
||||
|
||||
function meta:getKeysAllowedToOwn()
|
||||
local doorData = self:getDoorData()
|
||||
if not doorData then return nil end
|
||||
|
||||
return doorData.allowedToOwn
|
||||
end
|
||||
|
||||
function meta:getKeysCoOwners()
|
||||
local doorData = self:getDoorData()
|
||||
if not doorData then return nil end
|
||||
|
||||
return doorData.extraOwners
|
||||
end
|
||||
|
||||
local function canLockUnlock(ply, ent)
|
||||
local Team = ply:Team()
|
||||
local group = ent:getKeysDoorGroup()
|
||||
local teamOwn = ent:getKeysDoorTeams()
|
||||
|
||||
return ent:isKeysOwnedBy(ply) or
|
||||
(group and table.HasValue(RPExtraTeamDoors[group] or {}, Team)) or
|
||||
(teamOwn and teamOwn[Team])
|
||||
end
|
||||
|
||||
function plyMeta:canKeysLock(ent)
|
||||
local canLock = hook.Run("canKeysLock", self, ent)
|
||||
|
||||
if canLock ~= nil then return canLock end
|
||||
return canLockUnlock(self, ent)
|
||||
end
|
||||
|
||||
function plyMeta:canKeysUnlock(ent)
|
||||
local canUnlock = hook.Run("canKeysUnlock", self, ent)
|
||||
|
||||
if canUnlock ~= nil then return canUnlock end
|
||||
return canLockUnlock(self, ent)
|
||||
end
|
||||
|
||||
local netDoorVars = {}
|
||||
local netDoorVarsByName = {}
|
||||
|
||||
DarkRP.getDoorVars = fp{fn.Id, netDoorVars}
|
||||
DarkRP.getDoorVarsByName = fp{fn.Id, netDoorVarsByName}
|
||||
|
||||
function DarkRP.registerDoorVar(name, writeFn, readFn)
|
||||
netDoorVarsByName[name] = {name = name, write = writeFn, read = readFn}
|
||||
|
||||
netDoorVarsByName[name].id = table.insert(netDoorVars, netDoorVarsByName[name])
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
function DarkRP.writeNetDoorVar(name, value)
|
||||
local var = netDoorVarsByName[name]
|
||||
|
||||
-- Not registered, send inefficiently
|
||||
if not var then
|
||||
net.WriteUInt(0, 8) -- indicate unregistered
|
||||
net.WriteString(name)
|
||||
net.WriteType(value)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
net.WriteUInt(var.id, 8)
|
||||
var.write(value)
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
function DarkRP.readNetDoorVar()
|
||||
local id = net.ReadUInt(8)
|
||||
|
||||
-- unregistered var
|
||||
if id == 0 then
|
||||
return net.ReadString(), net.ReadType(net.ReadUInt(8))
|
||||
end
|
||||
|
||||
if not netDoorVars[id] then
|
||||
DarkRP.error("Unregistered DarkRP Doorvar clientside: " .. id, 2, {"Some addon is registering some DoorVar serverside, but not clientside."})
|
||||
end
|
||||
|
||||
return netDoorVars[id].name, netDoorVars[id].read()
|
||||
end
|
||||
end
|
||||
|
||||
DarkRP.registerDoorVar("groupOwn",
|
||||
function(val)
|
||||
net.WriteUInt(RPExtraTeamDoorIDs[val], 16)
|
||||
end,
|
||||
function()
|
||||
local id = net.ReadUInt(16)
|
||||
for name, id2 in pairs(RPExtraTeamDoorIDs) do
|
||||
if id == id2 then return name end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
-- Net helper function for writing tables with numbers as keys and bools as values
|
||||
local function writeNumBoolTbl(tbl)
|
||||
net.WriteUInt(table.Count(tbl), 10)
|
||||
|
||||
for num, _ in pairs(tbl) do
|
||||
net.WriteUInt(num, 16)
|
||||
end
|
||||
end
|
||||
|
||||
-- Net helper function for reading tables with numbers as keys and bools as values
|
||||
local function readNumBoolTbl(tbl)
|
||||
local res = {}
|
||||
local count = net.ReadUInt(10)
|
||||
|
||||
for i = 1, count do
|
||||
res[net.ReadUInt(16)] = true
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
DarkRP.registerDoorVar("owner", fp{fn.Flip(net.WriteInt), 16}, fp{net.ReadUInt, 16})
|
||||
DarkRP.registerDoorVar("nonOwnable", net.WriteBool, net.ReadBool)
|
||||
DarkRP.registerDoorVar("teamOwn", writeNumBoolTbl, readNumBoolTbl)
|
||||
DarkRP.registerDoorVar("allowedToOwn", writeNumBoolTbl, readNumBoolTbl)
|
||||
DarkRP.registerDoorVar("extraOwners", writeNumBoolTbl, readNumBoolTbl)
|
||||
DarkRP.registerDoorVar("title", net.WriteString, net.ReadString)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Commands
|
||||
---------------------------------------------------------------------------]]
|
||||
DarkRP.declareChatCommand{
|
||||
command = "toggleownable",
|
||||
description = "Toggle ownability status on this door.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "togglegroupownable",
|
||||
description = "Set this door group ownable.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "toggleteamownable",
|
||||
description = "Toggle this door ownable by a given team.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "toggleown",
|
||||
description = "Own or unown the door you're looking at.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "unownalldoors",
|
||||
description = "Sell all of your doors.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.chatCommandAlias("unownalldoors", "sellalldoors")
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "title",
|
||||
description = "Set the title of the door you're looking at.",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "removeowner",
|
||||
description = "Remove an owner from the door you're looking at.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "ro",
|
||||
description = "Remove an owner from the door you're looking at.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "addowner",
|
||||
description = "Invite someone to co-own the door you're looking at.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "ao",
|
||||
description = "Invite someone to co-own the door you're looking at.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "forceunlock",
|
||||
description = "Force the door you're looking at to be unlocked. This is saved.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "forceremoveowner",
|
||||
description = "Forcefully remove an owner from the door you're looking at.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "forceunownall",
|
||||
description = "Force a player to unown all the doors and vehicles they have.",
|
||||
delay = 0.5,
|
||||
tableArgs = true
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "forcelock",
|
||||
description = "Force the door you're looking at to be locked. This is saved.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "forceunown",
|
||||
description = "Forcefully remove any owners from the door you're looking at.",
|
||||
delay = 0.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "forceown",
|
||||
description = "Forcefully make someone own the door you're looking at.",
|
||||
delay = 0.5
|
||||
}
|
||||
377
gamemodes/darkrp/gamemode/modules/doorsystem/sh_interface.lua
Normal file
377
gamemodes/darkrp/gamemode/modules/doorsystem/sh_interface.lua
Normal file
@@ -0,0 +1,377 @@
|
||||
DarkRP.ENTITY.getDoorData = DarkRP.stub{
|
||||
name = "getDoorData",
|
||||
description = "Internal function to get the door/vehicle data.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "doordata",
|
||||
description = "All the DarkRP information on a door or vehicle.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.isKeysOwnable = DarkRP.stub{
|
||||
name = "isKeysOwnable",
|
||||
description = "Whether this door can be bought.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "answer",
|
||||
description = "Whether the door can be bought.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.isDoor = DarkRP.stub{
|
||||
name = "isDoor",
|
||||
description = "Whether this entity is considered a door in DarkRP.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "answer",
|
||||
description = "Whether it's a door.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.isKeysOwned = DarkRP.stub{
|
||||
name = "isKeysOwned",
|
||||
description = "Whether this door is owned by someone.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "answer",
|
||||
description = "Whether it's owned.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.getDoorOwner = DarkRP.stub{
|
||||
name = "getDoorOwner",
|
||||
description = "Get the owner of a door.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "owner",
|
||||
description = "The owner of the door.",
|
||||
type = "Player"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.isMasterOwner = DarkRP.stub{
|
||||
name = "isMasterOwner",
|
||||
description = "Whether the player is the main owner of the door (as opposed to a co-owner).",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player to query.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "answer",
|
||||
description = "Whether this player is the master owner.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.isKeysOwnedBy = DarkRP.stub{
|
||||
name = "isKeysOwnedBy",
|
||||
description = "Whether this door is owned or co-owned by this player",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player to query.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "answer",
|
||||
description = "Whether this door is (co-)owned by the player.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.isKeysAllowedToOwn = DarkRP.stub{
|
||||
name = "isKeysAllowedToOwn",
|
||||
description = "Whether this player is allowed to co-own a door, as decided by the master door owner.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player to query.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "answer",
|
||||
description = "Whether this door is (co-)ownable by the player.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.getKeysNonOwnable = DarkRP.stub{
|
||||
name = "getKeysNonOwnable",
|
||||
description = "Whether ownability of this door/vehicle is disabled.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "title",
|
||||
description = "The ownability status.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.getKeysTitle = DarkRP.stub{
|
||||
name = "getKeysTitle",
|
||||
description = "Get the title of this door or vehicle.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "title",
|
||||
description = "The title of the door or vehicle.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.getKeysDoorGroup = DarkRP.stub{
|
||||
name = "getKeysDoorGroup",
|
||||
description = "The door group of a door if it exists.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "group",
|
||||
description = "The door group.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.getKeysDoorTeams = DarkRP.stub{
|
||||
name = "getKeysDoorTeams",
|
||||
description = "The teams that are allowed to open this door.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "teams",
|
||||
description = "The door teams.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.getKeysAllowedToOwn = DarkRP.stub{
|
||||
name = "getKeysAllowedToOwn",
|
||||
description = "The list of people of which the master door owner has added as allowed to own.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "players",
|
||||
description = "The list of people allowed to own.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.getKeysCoOwners = DarkRP.stub{
|
||||
name = "getKeysCoOwners",
|
||||
description = "The list of people who co-own the door.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "players",
|
||||
description = "The list of people allowed to own. The keys of this table are UserIDs, the values are booleans.",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.PLAYER.canKeysLock = DarkRP.stub{
|
||||
name = "canKeysLock",
|
||||
description = "Whether the player can lock a given door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "door",
|
||||
description = "The door",
|
||||
optional = false,
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to lock the door.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.PLAYER
|
||||
}
|
||||
|
||||
DarkRP.PLAYER.canKeysUnlock = DarkRP.stub{
|
||||
name = "canKeysUnlock",
|
||||
description = "Whether the player can unlock a given door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "door",
|
||||
description = "The door",
|
||||
optional = false,
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to unlock the door.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.PLAYER
|
||||
}
|
||||
|
||||
DarkRP.registerDoorVar = DarkRP.stub{
|
||||
name = "registerDoorVar",
|
||||
description = "Register a door variable by name. You should definitely register door variables. Registering DarkRPVars will make networking much more efficient.",
|
||||
parameters = {
|
||||
{
|
||||
name = "name",
|
||||
description = "The name of the door var.",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "writeFn",
|
||||
description = "The function that writes a value for this door var. Examples: net.WriteString, function(val) net.WriteUInt(val, 8) end.",
|
||||
type = "function",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "readFn",
|
||||
description = "The function that reads and returns a value for this door var. Examples: net.ReadString, function() return net.ReadUInt(8) end.",
|
||||
type = "function",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.getDoorVars = DarkRP.stub{
|
||||
name = "getDoorVars",
|
||||
description = "Internal function, retrieves all the registered door variables.",
|
||||
parameters = {
|
||||
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "doorvars",
|
||||
description = "The door variables, indexed by number",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.getDoorVarsByName = DarkRP.stub{
|
||||
name = "getDoorVarsByName",
|
||||
description = "Internal function, retrieves all the registered door variables, indeded by their names.",
|
||||
parameters = {
|
||||
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "doorvars",
|
||||
description = "The door variables, indexed by name",
|
||||
type = "table"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "canKeysLock",
|
||||
description = "Whether the player can lock a given door. This hook is run when ply:canKeysLock is called.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "door",
|
||||
description = "The door",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to lock the door.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "canKeysUnlock",
|
||||
description = "Whether the player can unlock a given door. This hook is run when ply:canKeysUnlock is called.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "door",
|
||||
description = "The door",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to unlock the door.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
local function ccDoorUnOwn(ply, args)
|
||||
if ply:EntIndex() == 0 then
|
||||
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
|
||||
return
|
||||
end
|
||||
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or not ent:getDoorOwner() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return
|
||||
end
|
||||
|
||||
ent:Fire("unlock", "", 0)
|
||||
ent:keysUnOwn()
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unowned a door with forceunown", Color(30, 30, 30))
|
||||
DarkRP.notify(ply, 0, 4, "Forcefully unowned")
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("forceunown", "DarkRP_SetDoorOwner", ccDoorUnOwn)
|
||||
|
||||
local function unownAll(ply, args)
|
||||
local target = DarkRP.findPlayer(args[1])
|
||||
|
||||
if not IsValid(target) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args))
|
||||
return
|
||||
end
|
||||
target:keysUnOwnAll()
|
||||
|
||||
if ply:EntIndex() == 0 then
|
||||
DarkRP.log("Console force-unowned all doors owned by " .. target:Nick(), Color(30, 30, 30))
|
||||
else
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unowned all doors owned by " .. target:Nick(), Color(30, 30, 30))
|
||||
end
|
||||
|
||||
DarkRP.notify(ply, 0, 4, "All doors of " .. target:Nick() .. " are now unowned")
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("forceunownall", "DarkRP_SetDoorOwner", unownAll)
|
||||
|
||||
local function ccAddOwner(ply, args)
|
||||
if ply:EntIndex() == 0 then
|
||||
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
|
||||
return
|
||||
end
|
||||
|
||||
local trace = ply:GetEyeTrace()
|
||||
|
||||
local ent = trace.Entity
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or ent:getKeysDoorTeams() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return
|
||||
end
|
||||
|
||||
local target = DarkRP.findPlayer(args)
|
||||
|
||||
if not target then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args))
|
||||
return
|
||||
end
|
||||
|
||||
if ent:isKeysOwned() then
|
||||
if not ent:isKeysOwnedBy(target) and not ent:isKeysAllowedToOwn(target) then
|
||||
ent:addKeysAllowedToOwn(target)
|
||||
else
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("rp_addowner_already_owns_door", target))
|
||||
end
|
||||
return
|
||||
end
|
||||
ent:keysOwn(target)
|
||||
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-added a door owner with forceown", Color(30, 30, 30))
|
||||
DarkRP.notify(ply, 0, 4, "Forcefully added " .. target:Nick())
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("forceown", "DarkRP_SetDoorOwner", ccAddOwner)
|
||||
|
||||
local function ccRemoveOwner(ply, args)
|
||||
if ply:EntIndex() == 0 then
|
||||
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
|
||||
return
|
||||
end
|
||||
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or ent:getKeysDoorTeams() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return
|
||||
end
|
||||
|
||||
local target = DarkRP.findPlayer(args)
|
||||
|
||||
if not target then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args))
|
||||
return
|
||||
end
|
||||
|
||||
if ent:isKeysAllowedToOwn(target) then
|
||||
ent:removeKeysAllowedToOwn(target)
|
||||
end
|
||||
|
||||
if ent:isMasterOwner(target) then
|
||||
ent:keysUnOwn()
|
||||
elseif ent:isKeysOwnedBy(target) then
|
||||
ent:removeKeysDoorOwner(target)
|
||||
end
|
||||
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-removed a door owner with forceremoveowner", Color(30, 30, 30))
|
||||
DarkRP.notify(ply, 0, 4, "Forcefully removed " .. target:Nick())
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("forceremoveowner", "DarkRP_SetDoorOwner", ccRemoveOwner)
|
||||
|
||||
local function ccLock(ply, args)
|
||||
if ply:EntIndex() == 0 then
|
||||
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
|
||||
return
|
||||
end
|
||||
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("locked"))
|
||||
|
||||
ent:keysLock()
|
||||
|
||||
if not ent:CreatedByMap() then return end
|
||||
MySQLite.query(string.format([[REPLACE INTO darkrp_door VALUES(%s, %s, %s, 1, %s);]],
|
||||
MySQLite.SQLStr(ent:doorIndex()),
|
||||
MySQLite.SQLStr(string.lower(game.GetMap())),
|
||||
MySQLite.SQLStr(ent:getKeysTitle() or ""),
|
||||
ent:getKeysNonOwnable() and 1 or 0
|
||||
))
|
||||
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-locked a door with forcelock (locked door is saved)", Color(30, 30, 30))
|
||||
DarkRP.notify(ply, 0, 4, "Forcefully locked")
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("forcelock", "DarkRP_ChangeDoorSettings", ccLock)
|
||||
|
||||
local function ccUnLock(ply, args)
|
||||
if ply:EntIndex() == 0 then
|
||||
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
|
||||
return
|
||||
end
|
||||
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("unlocked"))
|
||||
ent:keysUnLock()
|
||||
|
||||
if not ent:CreatedByMap() then return end
|
||||
MySQLite.query(string.format([[REPLACE INTO darkrp_door VALUES(%s, %s, %s, 0, %s);]],
|
||||
MySQLite.SQLStr(ent:doorIndex()),
|
||||
MySQLite.SQLStr(string.lower(game.GetMap())),
|
||||
MySQLite.SQLStr(ent:getKeysTitle() or ""),
|
||||
ent:getKeysNonOwnable() and 1 or 0
|
||||
))
|
||||
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unlocked a door with forcelock (unlocked door is saved)", Color(30, 30, 30))
|
||||
DarkRP.notify(ply, 0, 4, "Forcefully unlocked")
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("forceunlock", "DarkRP_ChangeDoorSettings", ccUnLock)
|
||||
592
gamemodes/darkrp/gamemode/modules/doorsystem/sv_doors.lua
Normal file
592
gamemodes/darkrp/gamemode/modules/doorsystem/sv_doors.lua
Normal file
@@ -0,0 +1,592 @@
|
||||
local meta = FindMetaTable("Entity")
|
||||
local pmeta = FindMetaTable("Player")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Functions
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
function meta:doorIndex()
|
||||
return self:CreatedByMap() and self:MapCreationID() or nil
|
||||
end
|
||||
|
||||
function DarkRP.doorToEntIndex(num)
|
||||
local ent = ents.GetMapCreatedEntity(num)
|
||||
|
||||
return IsValid(ent) and ent:EntIndex() or nil
|
||||
end
|
||||
|
||||
function DarkRP.doorIndexToEnt(num)
|
||||
return ents.GetMapCreatedEntity(num) or NULL
|
||||
end
|
||||
|
||||
function meta:isLocked()
|
||||
local save = self:GetSaveTable()
|
||||
return save and ((self:isDoor() and save.m_bLocked) or (self:IsVehicle() and save.VehicleLocked))
|
||||
end
|
||||
|
||||
function meta:keysLock()
|
||||
self:Fire("lock", "", 0)
|
||||
if isfunction(self.Lock) then self:Lock(true) end -- SCars
|
||||
if IsValid(self.EntOwner) and self.EntOwner ~= self then return self.EntOwner:keysLock() end -- SCars
|
||||
|
||||
hook.Call("onKeysLocked", nil, self)
|
||||
end
|
||||
|
||||
function meta:keysUnLock()
|
||||
self:Fire("unlock", "", 0)
|
||||
if isfunction(self.UnLock) then self:UnLock(true) end -- SCars
|
||||
if IsValid(self.EntOwner) and self.EntOwner ~= self then return self.EntOwner:keysUnLock() end -- SCars
|
||||
|
||||
hook.Call("onKeysUnlocked", nil, self)
|
||||
end
|
||||
|
||||
function meta:keysOwn(ply)
|
||||
if self:isKeysAllowedToOwn(ply) then
|
||||
self:addKeysDoorOwner(ply)
|
||||
return
|
||||
end
|
||||
|
||||
local Owner = self:CPPIGetOwner()
|
||||
|
||||
-- Increase vehicle count
|
||||
if self:IsVehicle() then
|
||||
if IsValid(ply) then
|
||||
ply.Vehicles = ply.Vehicles or 0
|
||||
ply.Vehicles = ply.Vehicles + 1
|
||||
|
||||
self.SID = ply.SID
|
||||
end
|
||||
|
||||
-- Decrease vehicle count of the original owner
|
||||
if IsValid(Owner) and Owner ~= ply then
|
||||
Owner.Vehicles = Owner.Vehicles or 1
|
||||
Owner.Vehicles = Owner.Vehicles - 1
|
||||
end
|
||||
end
|
||||
|
||||
if self:IsVehicle() then
|
||||
self:CPPISetOwner(ply)
|
||||
end
|
||||
|
||||
if not self:isKeysOwned() and not self:isKeysOwnedBy(ply) then
|
||||
local doorData = self:getDoorData()
|
||||
doorData.owner = ply:UserID()
|
||||
DarkRP.updateDoorData(self, "owner")
|
||||
end
|
||||
|
||||
ply.OwnedNumz = ply.OwnedNumz or 0
|
||||
if ply.OwnedNumz == 0 and GAMEMODE.Config.propertytax then
|
||||
timer.Create(ply:SteamID64() .. "propertytax", 270, 0, function() ply.doPropertyTax(ply) end)
|
||||
end
|
||||
|
||||
ply.OwnedNumz = ply.OwnedNumz + 1
|
||||
|
||||
ply.Ownedz[self:EntIndex()] = self
|
||||
end
|
||||
|
||||
function meta:keysUnOwn(ply)
|
||||
if not ply then
|
||||
ply = self:getDoorOwner()
|
||||
|
||||
if not IsValid(ply) then return end
|
||||
end
|
||||
|
||||
if self:isMasterOwner(ply) then
|
||||
local doorData = self:getDoorData()
|
||||
self:removeAllKeysExtraOwners()
|
||||
self:setKeysTitle(nil)
|
||||
doorData.owner = nil
|
||||
DarkRP.updateDoorData(self, "owner")
|
||||
else
|
||||
self:removeKeysDoorOwner(ply)
|
||||
end
|
||||
|
||||
ply.Ownedz[self:EntIndex()] = nil
|
||||
ply.OwnedNumz = math.Clamp((ply.OwnedNumz or 1) - 1, 0, math.huge)
|
||||
end
|
||||
|
||||
function pmeta:keysUnOwnAll()
|
||||
for entIndex, ent in pairs(self.Ownedz or {}) do
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() then self.Ownedz[entIndex] = nil continue end
|
||||
if ent:isMasterOwner(self) then
|
||||
ent:Fire("unlock", "", 0.6)
|
||||
end
|
||||
ent:keysUnOwn(self)
|
||||
end
|
||||
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if ply == self then continue end
|
||||
|
||||
for _, ent in pairs(ply.Ownedz or {}) do
|
||||
if IsValid(ent) and ent:isKeysAllowedToOwn(self) then
|
||||
ent:removeKeysAllowedToOwn(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.OwnedNumz = 0
|
||||
end
|
||||
|
||||
local function taxesUnOwnAll(ply, taxables)
|
||||
for _, ent in ipairs(taxables) do
|
||||
if ent:isMasterOwner(ply) then
|
||||
ent:Fire("unlock", "", 0.6)
|
||||
end
|
||||
|
||||
ent:keysUnOwn(ply)
|
||||
end
|
||||
end
|
||||
|
||||
function pmeta:doPropertyTax()
|
||||
if not GAMEMODE.Config.propertytax then return end
|
||||
if self:isCP() and GAMEMODE.Config.cit_propertytax then return end
|
||||
|
||||
local taxables = {}
|
||||
|
||||
for entIndex, ent in pairs(self.Ownedz or {}) do
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() then self.Ownedz[entIndex] = nil continue end
|
||||
local isAllowed = hook.Call("canTaxEntity", nil, self, ent)
|
||||
if isAllowed == false then continue end
|
||||
|
||||
table.insert(taxables, ent)
|
||||
end
|
||||
|
||||
-- co-owned doors
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if ply == self then continue end
|
||||
|
||||
for _, ent in pairs(ply.Ownedz or {}) do
|
||||
if not IsValid(ent) or not ent:isKeysOwnedBy(self) then continue end
|
||||
|
||||
local isAllowed = hook.Call("canTaxEntity", nil, self, ent)
|
||||
if isAllowed == false then continue end
|
||||
|
||||
table.insert(taxables, ent)
|
||||
end
|
||||
end
|
||||
|
||||
local numowned = #taxables
|
||||
|
||||
if numowned <= 0 then return end
|
||||
|
||||
local price = 10
|
||||
local tax = price * numowned + math.random(-5, 5)
|
||||
|
||||
local shouldTax, taxOverride = hook.Call("canPropertyTax", nil, self, tax)
|
||||
|
||||
if shouldTax == false then return end
|
||||
|
||||
tax = taxOverride or tax
|
||||
if tax == 0 then return end
|
||||
|
||||
local canAfford = self:canAfford(tax)
|
||||
|
||||
if canAfford then
|
||||
self:addMoney(-tax)
|
||||
DarkRP.notify(self, 0, 5, DarkRP.getPhrase("property_tax", DarkRP.formatMoney(tax)))
|
||||
else
|
||||
taxesUnOwnAll(self, taxables)
|
||||
DarkRP.notify(self, 1, 8, DarkRP.getPhrase("property_tax_cant_afford"))
|
||||
end
|
||||
|
||||
hook.Call("onPropertyTax", nil, self, tax, canAfford)
|
||||
end
|
||||
|
||||
function pmeta:initiateTax()
|
||||
local taxtime = GAMEMODE.Config.wallettaxtime
|
||||
local uid = self:SteamID64() -- so we can destroy the timer if the player leaves
|
||||
timer.Create("rp_tax_" .. uid, taxtime or 600, 0, function()
|
||||
if not IsValid(self) then
|
||||
timer.Remove("rp_tax_" .. uid)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if not GAMEMODE.Config.wallettax then
|
||||
return -- Don't remove the hook in case it's turned on afterwards.
|
||||
end
|
||||
|
||||
local money = self:getDarkRPVar("money")
|
||||
local mintax = GAMEMODE.Config.wallettaxmin / 100
|
||||
local maxtax = GAMEMODE.Config.wallettaxmax / 100 -- convert to decimals for percentage calculations
|
||||
local startMoney = GAMEMODE.Config.startingmoney
|
||||
|
||||
|
||||
-- Variate the taxes between twice the starting money ($1000 by default) and 200 - 2 times the starting money (100.000 by default)
|
||||
local tax = (money - (startMoney * 2)) / (startMoney * 198)
|
||||
tax = math.Min(maxtax, mintax + (maxtax - mintax) * tax)
|
||||
|
||||
local taxAmount = tax * money
|
||||
|
||||
local shouldTax, amount = hook.Call("canTax", GAMEMODE, self, taxAmount)
|
||||
|
||||
if shouldTax == false then return end
|
||||
|
||||
taxAmount = amount or taxAmount
|
||||
taxAmount = math.Max(0, taxAmount)
|
||||
|
||||
self:addMoney(-taxAmount)
|
||||
DarkRP.notify(self, 3, 7, DarkRP.getPhrase("taxday", math.Round(taxAmount / money * 100, 3)))
|
||||
|
||||
hook.Call("onPaidTax", DarkRP.hooks, self, tax, money)
|
||||
end)
|
||||
end
|
||||
|
||||
function GM:canTax(ply)
|
||||
-- Don't tax players if they have less than twice the starting amount
|
||||
if ply:getDarkRPVar("money") < (GAMEMODE.Config.startingmoney * 2) then return false end
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Commands
|
||||
---------------------------------------------------------------------------]]
|
||||
local function SetDoorOwnable(ply)
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return
|
||||
end
|
||||
|
||||
if IsValid(ent:getDoorOwner()) then
|
||||
ent:keysUnOwn(ent:getDoorOwner())
|
||||
end
|
||||
ent:setKeysNonOwnable(not ent:getKeysNonOwnable())
|
||||
ent:removeAllKeysExtraOwners()
|
||||
ent:removeAllKeysAllowedToOwn()
|
||||
ent:removeAllKeysDoorTeams()
|
||||
ent:setDoorGroup(nil)
|
||||
ent:setKeysTitle(nil)
|
||||
|
||||
-- Save it for future map loads
|
||||
DarkRP.storeDoorData(ent)
|
||||
DarkRP.storeDoorGroup(ent, nil)
|
||||
DarkRP.storeTeamDoorOwnability(ent)
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("toggleownable", "DarkRP_ChangeDoorSettings", SetDoorOwnable)
|
||||
|
||||
local function SetDoorGroupOwnable(ply, arg)
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return
|
||||
end
|
||||
|
||||
if not RPExtraTeamDoors[arg] and arg ~= "" then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("door_group_doesnt_exist")) return "" end
|
||||
|
||||
ent:keysUnOwn()
|
||||
|
||||
|
||||
ent:removeAllKeysDoorTeams()
|
||||
local group = arg ~= "" and arg or nil
|
||||
ent:setDoorGroup(group)
|
||||
|
||||
-- Save it for future map loads
|
||||
DarkRP.storeDoorGroup(ent, group)
|
||||
DarkRP.storeTeamDoorOwnability(ent)
|
||||
|
||||
|
||||
DarkRP.notify(ply, 0, 8, DarkRP.getPhrase("door_group_set"))
|
||||
return ""
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("togglegroupownable", "DarkRP_ChangeDoorSettings", SetDoorGroupOwnable)
|
||||
|
||||
local function SetDoorTeamOwnable(ply, arg)
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return ""
|
||||
end
|
||||
|
||||
arg = tonumber(arg)
|
||||
if not arg then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("job_doesnt_exist")) return "" end
|
||||
if not RPExtraTeams[arg] and arg ~= nil then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("job_doesnt_exist")) return "" end
|
||||
if IsValid(ent:getDoorOwner()) then
|
||||
ent:keysUnOwn(ent:getDoorOwner())
|
||||
end
|
||||
|
||||
ent:setDoorGroup(nil)
|
||||
DarkRP.storeDoorGroup(ent, nil)
|
||||
|
||||
local doorTeams = ent:getKeysDoorTeams()
|
||||
if not doorTeams or not doorTeams[arg] then
|
||||
ent:addKeysDoorTeam(arg)
|
||||
else
|
||||
ent:removeKeysDoorTeam(arg)
|
||||
end
|
||||
|
||||
DarkRP.notify(ply, 0, 8, DarkRP.getPhrase("door_group_set"))
|
||||
DarkRP.storeTeamDoorOwnability(ent)
|
||||
|
||||
ent:keysUnOwn()
|
||||
return ""
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("toggleteamownable", "DarkRP_ChangeDoorSettings", SetDoorTeamOwnable)
|
||||
|
||||
local function OwnDoor(ply)
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 40000 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local Owner = ent:CPPIGetOwner()
|
||||
|
||||
if ply:isArrested() then
|
||||
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("door_unown_arrested"))
|
||||
return ""
|
||||
end
|
||||
|
||||
if ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or not fn.Null(ent:getKeysDoorTeams() or {}) then
|
||||
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("door_unownable"))
|
||||
return ""
|
||||
end
|
||||
|
||||
if ent:isKeysOwnedBy(ply) then
|
||||
local bAllowed, strReason = hook.Call("playerSell" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent)
|
||||
|
||||
if bAllowed == false then
|
||||
if strReason and strReason ~= "" then
|
||||
DarkRP.notify(ply, 1, 4, strReason)
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if ent:isMasterOwner(ply) then
|
||||
ent:removeAllKeysExtraOwners()
|
||||
ent:removeAllKeysAllowedToOwn()
|
||||
ent:Fire("unlock", "", 0)
|
||||
end
|
||||
|
||||
ent:keysUnOwn(ply)
|
||||
ent:setKeysTitle(nil)
|
||||
local GiveMoneyBack = math.floor((hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent) * 0.666) + 0.5)
|
||||
hook.Call("playerKeysSold", GAMEMODE, ply, ent, GiveMoneyBack)
|
||||
ply:addMoney(GiveMoneyBack)
|
||||
local bSuppress = hook.Call("hideSellDoorMessage", GAMEMODE, ply, ent)
|
||||
if not bSuppress then
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("door_sold", DarkRP.formatMoney(GiveMoneyBack)))
|
||||
end
|
||||
|
||||
else
|
||||
if ent:isKeysOwned() and not ent:isKeysAllowedToOwn(ply) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_already_owned"))
|
||||
return ""
|
||||
end
|
||||
|
||||
local iCost = hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent)
|
||||
if not ply:canAfford(iCost) then
|
||||
DarkRP.notify(ply, 1, 4, ent:IsVehicle() and DarkRP.getPhrase("vehicle_cannot_afford") or DarkRP.getPhrase("door_cannot_afford"))
|
||||
return ""
|
||||
end
|
||||
|
||||
local bAllowed, strReason, bSuppress = hook.Call("playerBuy" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent)
|
||||
if bAllowed == false then
|
||||
if strReason and strReason ~= "" then
|
||||
DarkRP.notify(ply, 1, 4, strReason)
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
local bVehicle = ent:IsVehicle()
|
||||
|
||||
if bVehicle and (ply.Vehicles or 0) >= GAMEMODE.Config.maxvehicles and Owner ~= ply then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", DarkRP.getPhrase("vehicle")))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not bVehicle and (ply.OwnedNumz or 0) >= GAMEMODE.Config.maxdoors then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", DarkRP.getPhrase("door")))
|
||||
return ""
|
||||
end
|
||||
|
||||
ply:addMoney(-iCost)
|
||||
if not bSuppress then
|
||||
DarkRP.notify(ply, 0, 4, bVehicle and DarkRP.getPhrase("vehicle_bought", DarkRP.formatMoney(iCost), "") or DarkRP.getPhrase("door_bought", DarkRP.formatMoney(iCost), ""))
|
||||
end
|
||||
|
||||
ent:keysOwn(ply)
|
||||
hook.Call("playerBought" .. (bVehicle and "Vehicle" or "Door"), GAMEMODE, ply, ent, iCost)
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("toggleown", OwnDoor)
|
||||
|
||||
local function UnOwnAll(ply, cmd, args)
|
||||
local amount = 0
|
||||
local cost = 0
|
||||
|
||||
local unownables = {}
|
||||
for entIndex, ent in pairs(ply.Ownedz or {}) do
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() then ply.Ownedz[entIndex] = nil continue end
|
||||
table.insert(unownables, ent)
|
||||
end
|
||||
|
||||
for _, otherPly in ipairs(player.GetAll()) do
|
||||
if ply == otherPly then continue end
|
||||
|
||||
for _, ent in pairs(otherPly.Ownedz or {}) do
|
||||
if IsValid(ent) and ent:isKeysOwnedBy(ply) then
|
||||
table.insert(unownables, ent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for entIndex, ent in ipairs(unownables) do
|
||||
local bAllowed, _strReason = hook.Call("playerSell" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent)
|
||||
|
||||
if bAllowed == false then continue end
|
||||
|
||||
if ent:isMasterOwner(ply) then
|
||||
ent:Fire("unlock", "", 0)
|
||||
end
|
||||
|
||||
ent:keysUnOwn(ply)
|
||||
amount = amount + 1
|
||||
|
||||
local GiveMoneyBack = math.floor((hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent) * 0.666) + 0.5)
|
||||
hook.Call("playerKeysSold", GAMEMODE, ply, ent, GiveMoneyBack)
|
||||
cost = cost + GiveMoneyBack
|
||||
end
|
||||
|
||||
if amount == 0 then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("no_doors_owned")) return "" end
|
||||
|
||||
ply:addMoney(math.floor(cost))
|
||||
|
||||
DarkRP.notify(ply, 2, 4, DarkRP.getPhrase("sold_x_doors", amount, DarkRP.formatMoney(math.floor(cost))))
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("unownalldoors", UnOwnAll)
|
||||
|
||||
local function SetDoorTitle(ply, args)
|
||||
local trace = ply:GetEyeTrace()
|
||||
|
||||
local ent = trace.Entity
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return ""
|
||||
end
|
||||
|
||||
if ent:isKeysOwnedBy(ply) then
|
||||
ent:setKeysTitle(args)
|
||||
return ""
|
||||
end
|
||||
|
||||
local function onCAMIResult(allowed)
|
||||
if not allowed then
|
||||
DarkRP.notify(ply, 1, 6, DarkRP.getPhrase("no_privilege"))
|
||||
return
|
||||
end
|
||||
|
||||
local hasTeams = not fn.Null(ent:getKeysDoorTeams() or {})
|
||||
if ent:isKeysOwned() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or hasTeams then
|
||||
ent:setKeysTitle(args)
|
||||
end
|
||||
|
||||
if ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or hasTeams then
|
||||
DarkRP.storeDoorData(trace.Entity)
|
||||
end
|
||||
end
|
||||
|
||||
CAMI.PlayerHasAccess(ply, "DarkRP_ChangeDoorSettings", onCAMIResult)
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("title", SetDoorTitle)
|
||||
|
||||
local function RemoveDoorOwner(ply, args)
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local target = DarkRP.findPlayer(args)
|
||||
|
||||
if ent:getKeysNonOwnable() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_rem_owners_unownable"))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not target then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(args)))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not ent:isKeysOwnedBy(ply) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("do_not_own_ent"))
|
||||
return ""
|
||||
end
|
||||
|
||||
|
||||
local canDo = hook.Call("onAllowedToOwnRemoved", nil, ply, ent, target)
|
||||
if canDo == false then return "" end
|
||||
|
||||
if ent:isKeysAllowedToOwn(target) then
|
||||
ent:removeKeysAllowedToOwn(target)
|
||||
end
|
||||
|
||||
if ent:isKeysOwnedBy(target) then
|
||||
ent:removeKeysDoorOwner(target)
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("removeowner", RemoveDoorOwner)
|
||||
DarkRP.defineChatCommand("ro", RemoveDoorOwner)
|
||||
|
||||
local function AddDoorOwner(ply, args)
|
||||
local trace = ply:GetEyeTrace()
|
||||
local ent = trace.Entity
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local target = DarkRP.findPlayer(args)
|
||||
|
||||
if ent:getKeysNonOwnable() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_add_owners_unownable"))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not target then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(args)))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not ent:isKeysOwnedBy(ply) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("do_not_own_ent"))
|
||||
return ""
|
||||
end
|
||||
|
||||
if ent:isKeysOwnedBy(target) or ent:isKeysAllowedToOwn(target) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("rp_addowner_already_owns_door", target:Nick()))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canDo = hook.Call("onAllowedToOwnAdded", nil, ply, ent, target)
|
||||
if canDo == false then return "" end
|
||||
|
||||
ent:addKeysAllowedToOwn(target)
|
||||
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("addowner", AddDoorOwner)
|
||||
DarkRP.defineChatCommand("ao", AddDoorOwner)
|
||||
179
gamemodes/darkrp/gamemode/modules/doorsystem/sv_doorvars.lua
Normal file
179
gamemodes/darkrp/gamemode/modules/doorsystem/sv_doorvars.lua
Normal file
@@ -0,0 +1,179 @@
|
||||
util.AddNetworkString("DarkRP_UpdateDoorData")
|
||||
util.AddNetworkString("DarkRP_RemoveDoorData")
|
||||
util.AddNetworkString("DarkRP_RemoveDoorVar")
|
||||
util.AddNetworkString("DarkRP_AllDoorData")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Interface functions
|
||||
---------------------------------------------------------------------------]]
|
||||
local eMeta = FindMetaTable("Entity")
|
||||
function eMeta:getDoorData()
|
||||
if not self:isKeysOwnable() then return {} end
|
||||
|
||||
self.DoorData = self.DoorData or {}
|
||||
return self.DoorData
|
||||
end
|
||||
|
||||
function eMeta:setKeysNonOwnable(ownable)
|
||||
self:getDoorData().nonOwnable = ownable or nil
|
||||
DarkRP.updateDoorData(self, "nonOwnable")
|
||||
end
|
||||
|
||||
function eMeta:setKeysTitle(title)
|
||||
self:getDoorData().title = title ~= "" and title or nil
|
||||
DarkRP.updateDoorData(self, "title")
|
||||
end
|
||||
|
||||
function eMeta:setDoorGroup(group)
|
||||
self:getDoorData().groupOwn = group
|
||||
DarkRP.updateDoorData(self, "groupOwn")
|
||||
end
|
||||
|
||||
function eMeta:addKeysDoorTeam(t)
|
||||
local doorData = self:getDoorData()
|
||||
doorData.teamOwn = doorData.teamOwn or {}
|
||||
doorData.teamOwn[t] = true
|
||||
|
||||
DarkRP.updateDoorData(self, "teamOwn")
|
||||
end
|
||||
|
||||
function eMeta:removeKeysDoorTeam(t)
|
||||
local doorData = self:getDoorData()
|
||||
doorData.teamOwn = doorData.teamOwn or {}
|
||||
doorData.teamOwn[t] = nil
|
||||
|
||||
if fn.Null(doorData.teamOwn) then
|
||||
doorData.teamOwn = nil
|
||||
end
|
||||
|
||||
DarkRP.updateDoorData(self, "teamOwn")
|
||||
end
|
||||
|
||||
function eMeta:removeAllKeysDoorTeams()
|
||||
local doorData = self:getDoorData()
|
||||
doorData.teamOwn = nil
|
||||
|
||||
DarkRP.updateDoorData(self, "teamOwn")
|
||||
end
|
||||
|
||||
function eMeta:addKeysAllowedToOwn(ply)
|
||||
local doorData = self:getDoorData()
|
||||
doorData.allowedToOwn = doorData.allowedToOwn or {}
|
||||
doorData.allowedToOwn[ply:UserID()] = true
|
||||
|
||||
DarkRP.updateDoorData(self, "allowedToOwn")
|
||||
end
|
||||
|
||||
function eMeta:removeKeysAllowedToOwn(ply)
|
||||
local doorData = self:getDoorData()
|
||||
doorData.allowedToOwn = doorData.allowedToOwn or {}
|
||||
doorData.allowedToOwn[ply:UserID()] = nil
|
||||
|
||||
if fn.Null(doorData.allowedToOwn) then
|
||||
doorData.allowedToOwn = nil
|
||||
end
|
||||
|
||||
DarkRP.updateDoorData(self, "allowedToOwn")
|
||||
end
|
||||
|
||||
function eMeta:removeAllKeysAllowedToOwn()
|
||||
local doorData = self:getDoorData()
|
||||
doorData.allowedToOwn = nil
|
||||
|
||||
DarkRP.updateDoorData(self, "allowedToOwn")
|
||||
end
|
||||
|
||||
function eMeta:addKeysDoorOwner(ply)
|
||||
local doorData = self:getDoorData()
|
||||
doorData.extraOwners = doorData.extraOwners or {}
|
||||
doorData.extraOwners[ply:UserID()] = true
|
||||
|
||||
DarkRP.updateDoorData(self, "extraOwners")
|
||||
|
||||
self:removeKeysAllowedToOwn(ply)
|
||||
end
|
||||
|
||||
function eMeta:removeKeysDoorOwner(ply)
|
||||
local doorData = self:getDoorData()
|
||||
doorData.extraOwners = doorData.extraOwners or {}
|
||||
doorData.extraOwners[ply:UserID()] = nil
|
||||
|
||||
if fn.Null(doorData.extraOwners) then
|
||||
doorData.extraOwners = nil
|
||||
end
|
||||
|
||||
DarkRP.updateDoorData(self, "extraOwners")
|
||||
end
|
||||
|
||||
function eMeta:removeAllKeysExtraOwners()
|
||||
local doorData = self:getDoorData()
|
||||
doorData.extraOwners = nil
|
||||
|
||||
DarkRP.updateDoorData(self, "extraOwners")
|
||||
end
|
||||
|
||||
function eMeta:removeDoorData()
|
||||
net.Start("DarkRP_RemoveDoorData")
|
||||
net.WriteUInt(self:EntIndex(), 32)
|
||||
net.Send(player.GetAll())
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Networking
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
function plyMeta:sendDoorData()
|
||||
if self:EntIndex() == 0 then return end
|
||||
|
||||
local res = {}
|
||||
for _, v in ipairs(ents.GetAll()) do
|
||||
if not v:getDoorData() or table.IsEmpty(v:getDoorData()) then continue end
|
||||
|
||||
res[v:EntIndex()] = v:getDoorData()
|
||||
end
|
||||
|
||||
net.Start("DarkRP_AllDoorData")
|
||||
net.WriteUInt(table.Count(res), 16)
|
||||
|
||||
for ix, vars in pairs(res) do
|
||||
net.WriteUInt(ix, 16)
|
||||
|
||||
net.WriteUInt(table.Count(vars), 8)
|
||||
|
||||
for varName, value in pairs(vars) do
|
||||
DarkRP.writeNetDoorVar(varName, value)
|
||||
end
|
||||
end
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
concommand.Add("_sendAllDoorData", fn.Id) -- Backwards compatibility
|
||||
|
||||
hook.Add("PlayerInitialSpawn", "DarkRP_DoorData", plyMeta.sendDoorData)
|
||||
|
||||
function DarkRP.updateDoorData(door, member)
|
||||
if not IsValid(door) or not door:getDoorData() then error("Calling updateDoorData on a door that has no data!") end
|
||||
|
||||
local value = door:getDoorData()[member]
|
||||
|
||||
if value == nil then
|
||||
local doorvar = DarkRP.getDoorVarsByName()[member]
|
||||
net.Start("DarkRP_RemoveDoorVar")
|
||||
net.WriteUInt(door:EntIndex(), 16)
|
||||
if not doorvar then
|
||||
net.WriteUInt(0, 8)
|
||||
net.WriteString(member)
|
||||
else
|
||||
net.WriteUInt(doorvar.id, 8)
|
||||
end
|
||||
net.Broadcast()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
net.Start("DarkRP_UpdateDoorData")
|
||||
net.WriteUInt(door:EntIndex(), 32)
|
||||
DarkRP.writeNetDoorVar(member, value)
|
||||
net.Broadcast()
|
||||
end
|
||||
851
gamemodes/darkrp/gamemode/modules/doorsystem/sv_interface.lua
Normal file
851
gamemodes/darkrp/gamemode/modules/doorsystem/sv_interface.lua
Normal file
@@ -0,0 +1,851 @@
|
||||
DarkRP.doorToEntIndex = DarkRP.stub{
|
||||
name = "doorToEntIndex",
|
||||
description = "Get an ENT index from a door index.",
|
||||
parameters = {
|
||||
{
|
||||
name = "index",
|
||||
description = "The door index",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "index",
|
||||
description = "The ENT index",
|
||||
type = "number",
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.doorIndexToEnt = DarkRP.stub{
|
||||
name = "doorIndexToEnt",
|
||||
description = "Get the entity of a door index (inverse of ent:doorIndexToEnt()). Note: the door MUST have been created by the map!",
|
||||
parameters = {
|
||||
{
|
||||
name = "index",
|
||||
description = "The door index",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "door",
|
||||
description = "The door entity",
|
||||
type = "Entity",
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.writeNetDoorVar = DarkRP.stub{
|
||||
name = "writeNetDoorVar",
|
||||
description = "Internal function. You probably shouldn't need this. DarkRP calls this function when sending DoorVar net messages. This function writes the net data for a specific DoorVar.",
|
||||
parameters = {
|
||||
{
|
||||
name = "name",
|
||||
description = "The name of the DoorVar.",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "value",
|
||||
description = "The value of the DoorVar.",
|
||||
type = "any",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.doorIndex = DarkRP.stub{
|
||||
name = "doorIndex",
|
||||
description = "Get the door index of a door. Use this to store door information in the database.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "index",
|
||||
description = "The door index.",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.keysLock = DarkRP.stub{
|
||||
name = "keysLock",
|
||||
description = "Lock this door or vehicle.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.keysUnLock = DarkRP.stub{
|
||||
name = "keysUnLock",
|
||||
description = "Unlock this door or vehicle.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.addKeysAllowedToOwn = DarkRP.stub{
|
||||
name = "addKeysAllowedToOwn",
|
||||
description = "Make this player allowed to co-own the door or vehicle.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player to give permission to co-own.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.addKeysDoorOwner = DarkRP.stub{
|
||||
name = "addKeysDoorOwner",
|
||||
description = "Make this player a co-owner of the door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player to add as co-owner.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.removeKeysDoorOwner = DarkRP.stub{
|
||||
name = "removeKeysDoorOwner",
|
||||
description = "Remove this player as co-owner",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player to remove from the co-owners list.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.keysOwn = DarkRP.stub{
|
||||
name = "keysOwn",
|
||||
description = "Make the player the master owner of the door",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player set as master owner.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.keysUnOwn = DarkRP.stub{
|
||||
name = "keysUnOwn",
|
||||
description = "Make this player unown the door/vehicle.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.setKeysNonOwnable = DarkRP.stub{
|
||||
name = "setKeysNonOwnable",
|
||||
description = "Set whether this door or vehicle is ownable or not.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ownable",
|
||||
description = "Whether the door or vehicle is blocked from ownership.",
|
||||
type = "boolean",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.setKeysTitle = DarkRP.stub{
|
||||
name = "setKeysTitle",
|
||||
description = "Set the title of a door or vehicle.",
|
||||
parameters = {
|
||||
{
|
||||
name = "title",
|
||||
description = "The title of the door.",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.setDoorGroup = DarkRP.stub{
|
||||
name = "setDoorGroup",
|
||||
description = "Set the door group of a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "group",
|
||||
description = "The door group.",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.addKeysDoorTeam = DarkRP.stub{
|
||||
name = "addKeysDoorTeam",
|
||||
description = "Allow a team to lock/unlock a door..",
|
||||
parameters = {
|
||||
{
|
||||
name = "team",
|
||||
description = "The team to add to team owners.",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.removeKeysDoorTeam = DarkRP.stub{
|
||||
name = "removeKeysDoorTeam",
|
||||
description = "Disallow a team from locking/unlocking a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "team",
|
||||
description = "The team to remove from team owners.",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.removeAllKeysDoorTeams = DarkRP.stub{
|
||||
name = "removeAllKeysDoorTeams",
|
||||
description = "Disallow all teams from locking/unlocking a door.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.removeAllKeysExtraOwners = DarkRP.stub{
|
||||
name = "removeAllKeysExtraOwners",
|
||||
description = "Remove all co-owners from a door.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.removeAllKeysAllowedToOwn = DarkRP.stub{
|
||||
name = "removeAllKeysAllowedToOwn",
|
||||
description = "Disallow all people from owning the door.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.removeKeysAllowedToOwn = DarkRP.stub{
|
||||
name = "removeKeysAllowedToOwn",
|
||||
description = "Remove a player from being allowed to co-own a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player to be removed.",
|
||||
type = "Player",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.ENTITY.isLocked = DarkRP.stub{
|
||||
name = "isLocked",
|
||||
description = "Whether this door/vehicle is locked.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "answer",
|
||||
description = "Whether it's locked.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.ENTITY
|
||||
}
|
||||
|
||||
DarkRP.PLAYER.keysUnOwnAll = DarkRP.stub{
|
||||
name = "keysUnOwnAll",
|
||||
description = "Unown every door and vehicle owned by this player.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.PLAYER
|
||||
}
|
||||
|
||||
DarkRP.PLAYER.sendDoorData = DarkRP.stub{
|
||||
name = "sendDoorData",
|
||||
description = "Internal function. Sends all door data to a player.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.PLAYER
|
||||
}
|
||||
|
||||
DarkRP.PLAYER.doPropertyTax = DarkRP.stub{
|
||||
name = "doPropertyTax",
|
||||
description = "Tax a player based on the amount of doors and vehicles they have.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.PLAYER
|
||||
}
|
||||
|
||||
DarkRP.PLAYER.initiateTax = DarkRP.stub{
|
||||
name = "initiateTax",
|
||||
description = "Internal function, starts the timer that taxes the player every once in a while.",
|
||||
parameters = {
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP.PLAYER
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "onKeysLocked",
|
||||
description = "Called when a door or vehicle was locked.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ent",
|
||||
description = "The entity that was locked.",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "onKeysUnlocked",
|
||||
description = "Called when a door or vehicle was unlocked.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ent",
|
||||
description = "The entity that was unlocked.",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "playerKeysSold",
|
||||
description = "When a player sold a door or vehicle.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who sold the door or vehicle.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The entity that was sold.",
|
||||
type = "Entity"
|
||||
},
|
||||
{
|
||||
name = "GiveMoneyBack",
|
||||
description = "The amount of money refunded to the player",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "hideSellDoorMessage",
|
||||
description = "Whether to hide the door/vehicle sold notification",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who sold the door or vehicle.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The entity that was sold.",
|
||||
type = "Player"
|
||||
},
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "hide",
|
||||
description = "Whether to hide the notification.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "getDoorCost",
|
||||
description = "Get the cost of a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who has the intention to purchase the door.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The door",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "cost",
|
||||
description = "The price of the door.",
|
||||
type = "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "getVehicleCost",
|
||||
description = "Get the cost of a vehicle.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who has the intention to purchase the vehicle.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The vehicle",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "cost",
|
||||
description = "The price of the vehicle.",
|
||||
type = "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "playerBuyDoor",
|
||||
description = "When a player purchases a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who is to buy the door.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The door.",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to buy the door.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "reason",
|
||||
description = "The reason why a player is not allowed to buy the door, if applicable.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "surpress",
|
||||
description = "Whether to show the reason in a notification to the player, return true here to surpress the message.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "playerSellDoor",
|
||||
description = "When a player is about to sell a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who is to sell the door.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The door.",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to sell the door.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "reason",
|
||||
description = "The reason why a player is not allowed to sell the door, if applicable.",
|
||||
type = "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "onAllowedToOwnAdded",
|
||||
description = "When a player adds a co-owner to a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who adds the co-owner.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The door.",
|
||||
type = "Entity"
|
||||
},
|
||||
{
|
||||
name = "target",
|
||||
description = "The target who will be allowed to own the door.",
|
||||
type = "Player"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to add this player as co-owner.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "onAllowedToOwnRemoved",
|
||||
description = "When a player removes a co-owner to a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who removes the co-owner.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The door.",
|
||||
type = "Entity"
|
||||
},
|
||||
{
|
||||
name = "target",
|
||||
description = "The target who will not be allowed to own the door anymore.",
|
||||
type = "Player"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to remove this player as co-owner.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "playerBuyVehicle",
|
||||
description = "When a player purchases a vehicle.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who is to buy the vehicle.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The vehicle.",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to buy the vehicle.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "reason",
|
||||
description = "The reason why a player is not allowed to buy the vehicle, if applicable.",
|
||||
type = "string"
|
||||
},
|
||||
{
|
||||
name = "surpress",
|
||||
description = "Whether to show the reason in a notification to the player, return true here to surpress the message.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "playerSellVehicle",
|
||||
description = "When a player is about to sell a vehicle.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who is to sell the vehicle.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The vehicle.",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "allowed",
|
||||
description = "Whether the player is allowed to sell the vehicle.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "reason",
|
||||
description = "The reason why a player is not allowed to sell the vehicle, if applicable.",
|
||||
type = "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "playerBoughtDoor",
|
||||
description = "Called when a player has purchased a door.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who has purchased the door.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The purchased door.",
|
||||
type = "Entity"
|
||||
},
|
||||
{
|
||||
name = "cost",
|
||||
description = "The cost of the purchased door.",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "playerBoughtVehicle",
|
||||
description = "Called when a player has purchased a vehicle.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who has purchased the vehicle.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The purchased vehicle.",
|
||||
type = "Entity"
|
||||
},
|
||||
{
|
||||
name = "cost",
|
||||
description = "The cost of the purchased vehicle.",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "canTax",
|
||||
description = "Called before a player pays taxes. This hook differs from onPaidTax in that this hook can prevent the taxing and change the tax amount.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who was taxed.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "tax",
|
||||
description = "The amount of money that is about to be taxed from the player.",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "shouldTax",
|
||||
description = "Whether the player is to be taxed. Return false here to prevent the player from being taxed.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "tax",
|
||||
description = "Overrides the amount of money that is taxed.",
|
||||
type = "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "onPaidTax",
|
||||
description = "Called when a player has paid tax.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player who was taxed.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "tax",
|
||||
description = "The percentage of tax taken from his wallet.",
|
||||
type = "number"
|
||||
},
|
||||
{
|
||||
name = "wallet",
|
||||
description = "The amount of money the player had before the tax was applied.",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "canTaxEntity",
|
||||
description = "Called right before a player's property is taxed. Decides per entity whether it can be taxed.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player whose property will be taxed.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "ent",
|
||||
description = "The door or vehicle that is to be taxed",
|
||||
type = "Entity"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "shouldTax",
|
||||
description = "Return false here to prevent this specific entity from being taxed.",
|
||||
type = "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "canPropertyTax",
|
||||
description = "Called right before a player's property is taxed. This hook differs from onPropertyTax in that onPropertyTax is called AFTER the taxing. With this hook, one can influence the taxing process.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player whose property will be taxed.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "tax",
|
||||
description = "The amount of money that will be taxed (unless overridden by this hook).",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "shouldTax",
|
||||
description = "Return false here to prevent the doors from being taxed.",
|
||||
type = "boolean"
|
||||
},
|
||||
{
|
||||
name = "taxOverride",
|
||||
description = "Override the tax amount.",
|
||||
type = "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "onPropertyTax",
|
||||
description = "Called right AFTER a player's property is taxed. Please use canPropertyTax if you want to influence the taxing process.",
|
||||
parameters = {
|
||||
{
|
||||
name = "ply",
|
||||
description = "The player whose property has been taxed.",
|
||||
type = "Player"
|
||||
},
|
||||
{
|
||||
name = "tax",
|
||||
description = "The amount of money that has been taxed.",
|
||||
type = "number"
|
||||
},
|
||||
{
|
||||
name = "couldAfford",
|
||||
description = "Whether the player was able to afford the tax.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
}
|
||||
}
|
||||
13
gamemodes/darkrp/gamemode/modules/events/sh_events.lua
Normal file
13
gamemodes/darkrp/gamemode/modules/events/sh_events.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
DarkRP.declareChatCommand{
|
||||
command = "enablestorm",
|
||||
description = "Enable meteor storms.",
|
||||
delay = 1.5,
|
||||
condition = hasCommandsPriv
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "disablestorm",
|
||||
description = "Disable meteor storms.",
|
||||
delay = 1.5,
|
||||
condition = hasCommandsPriv
|
||||
}
|
||||
224
gamemodes/darkrp/gamemode/modules/events/sv_events.lua
Normal file
224
gamemodes/darkrp/gamemode/modules/events/sv_events.lua
Normal file
@@ -0,0 +1,224 @@
|
||||
resource.AddFile("sound/earthquake.mp3")
|
||||
util.PrecacheSound("earthquake.mp3")
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Variables
|
||||
---------------------------------------------------------]]
|
||||
local timeLeft = 10
|
||||
local stormOn = false
|
||||
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Meteor storm
|
||||
---------------------------------------------------------]]
|
||||
local function StormStart()
|
||||
local phrase = DarkRP.getPhrase("meteor_approaching")
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if v:Alive() then
|
||||
v:PrintMessage(HUD_PRINTCENTER, phrase)
|
||||
v:PrintMessage(HUD_PRINTTALK, phrase)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function StormEnd()
|
||||
local phrase = DarkRP.getPhrase("meteor_passing")
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if v:Alive() then
|
||||
v:PrintMessage(HUD_PRINTCENTER, phrase)
|
||||
v:PrintMessage(HUD_PRINTTALK, phrase)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ControlStorm()
|
||||
timeLeft = timeLeft - 1
|
||||
|
||||
if timeLeft < 1 then
|
||||
if stormOn then
|
||||
timeLeft = math.random(300, 500)
|
||||
stormOn = false
|
||||
timer.Stop("start")
|
||||
StormEnd()
|
||||
else
|
||||
timeLeft = math.random(60, 90)
|
||||
stormOn = true
|
||||
timer.Start("start")
|
||||
StormStart()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function AttackEnt(ent)
|
||||
local meteor = ents.Create("meteor")
|
||||
meteor.nodupe = true
|
||||
meteor:Spawn()
|
||||
meteor:SetMeteorTarget(ent)
|
||||
end
|
||||
|
||||
local function StartShower()
|
||||
timer.Adjust("start", math.random(0.1, 1), 0, StartShower)
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if math.random(0, 2) == 0 and v:Alive() then
|
||||
AttackEnt(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function StartStorm(ply)
|
||||
timer.Start("stormControl")
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("meteor_enabled"))
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("enablestorm", "DarkRP_AdminCommands", StartStorm)
|
||||
|
||||
local function StopStorm(ply)
|
||||
timer.Stop("stormControl")
|
||||
stormOn = false
|
||||
timer.Stop("start")
|
||||
StormEnd()
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("meteor_disabled"))
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("disablestorm", "DarkRP_AdminCommands", StopStorm)
|
||||
|
||||
timer.Create("start", 1, 0, StartShower)
|
||||
timer.Create("stormControl", 1, 0, ControlStorm)
|
||||
|
||||
timer.Stop("start")
|
||||
timer.Stop("stormControl")
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Earthquake
|
||||
---------------------------------------------------------]]
|
||||
local lastmagnitudes = {} -- The magnitudes of the last tremors
|
||||
local tremor
|
||||
|
||||
local function createTremor()
|
||||
tremor = ents.Create("env_physexplosion")
|
||||
tremor:SetPos(Vector(0, 0, 0))
|
||||
tremor:SetKeyValue("radius", 9999999999)
|
||||
tremor:SetKeyValue("spawnflags", 7)
|
||||
tremor.nodupe = true
|
||||
tremor:Spawn()
|
||||
end
|
||||
|
||||
hook.Add("PostCleanupMap", "DarkRP_events", createTremor)
|
||||
|
||||
hook.Add("InitPostEntity", "DarkRP_SetupTremor", function()
|
||||
createTremor()
|
||||
end)
|
||||
|
||||
local function TremorReport()
|
||||
local mag = table.remove(lastmagnitudes, 1)
|
||||
if mag then
|
||||
if mag < 6.5 then
|
||||
DarkRP.notifyAll(0, 3, DarkRP.getPhrase("earthtremor_report", tostring(mag)))
|
||||
return
|
||||
end
|
||||
DarkRP.notifyAll(0, 3, DarkRP.getPhrase("earthquake_report", tostring(mag)))
|
||||
end
|
||||
end
|
||||
|
||||
local function EarthQuakeTest()
|
||||
if not GAMEMODE.Config.earthquakes then return end
|
||||
|
||||
if GAMEMODE.Config.quakechance and math.random(0, GAMEMODE.Config.quakechance) < 1 then
|
||||
local en = ents.FindByClass("prop_physics")
|
||||
local plys = player.GetAll()
|
||||
|
||||
local force = math.random(10, 1000)
|
||||
tremor:SetKeyValue("magnitude", force / 6)
|
||||
|
||||
for _, v in ipairs(plys) do
|
||||
v:EmitSound("earthquake.mp3", force / 6, 100)
|
||||
end
|
||||
tremor:Fire("explode","",0.5)
|
||||
util.ScreenShake(Vector(0, 0, 0), force, math.random(25, 50), math.random(5, 12), 9999999999)
|
||||
table.insert(lastmagnitudes, math.floor((force / 10) + .5) / 10)
|
||||
timer.Simple(10, function() TremorReport() end)
|
||||
for _, e in ipairs(en) do
|
||||
local rand = math.random(650, 1000)
|
||||
if rand < force and rand % 2 == 0 then
|
||||
e:Fire("enablemotion", "", 0)
|
||||
constraint.RemoveAll(e)
|
||||
end
|
||||
if e:IsOnGround() then
|
||||
e:TakeDamage((force / 100) + 15, game.GetWorld())
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
timer.Create("EarthquakeTest", 1, 0, EarthQuakeTest)
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Flammable
|
||||
---------------------------------------------------------]]
|
||||
-- Class names as index
|
||||
local flammablePropsKV = {
|
||||
drug = true,
|
||||
drug_lab = true,
|
||||
food = true,
|
||||
gunlab = true,
|
||||
letter = true,
|
||||
microwave = true,
|
||||
money_printer = true,
|
||||
spawned_shipment = true,
|
||||
spawned_weapon = true,
|
||||
spawned_money = true
|
||||
}
|
||||
|
||||
local flammableProps = {} -- Numbers as index
|
||||
for k in pairs(flammablePropsKV) do table.insert(flammableProps, k) end
|
||||
|
||||
|
||||
local function IsFlammable(ent)
|
||||
return flammablePropsKV[ent:GetClass()] ~= nil
|
||||
end
|
||||
|
||||
-- FireSpread from SeriousRP
|
||||
local function FireSpread(ent, chanceDiv)
|
||||
if not ent:IsOnFire() then return end
|
||||
|
||||
if ent:isMoneyBag() then
|
||||
ent:Remove()
|
||||
end
|
||||
|
||||
local rand = math.random(0, 300 / chanceDiv)
|
||||
|
||||
if rand > 1 then return end
|
||||
local en = ents.FindInSphere(ent:GetPos(), math.random(20, 90))
|
||||
|
||||
for _, v in ipairs(en) do
|
||||
if not IsFlammable(v) or v == ent then continue end
|
||||
|
||||
if not v.burned then
|
||||
v:Ignite(math.random(5,180), 0)
|
||||
v.burned = true
|
||||
break -- Don't ignite all entities in sphere at once, just one at a time
|
||||
end
|
||||
|
||||
local color = v:GetColor()
|
||||
if (color.r - 51) >= 0 then color.r = color.r - 51 end
|
||||
if (color.g - 51) >= 0 then color.g = color.g - 51 end
|
||||
if (color.b - 51) >= 0 then color.b = color.b - 51 end
|
||||
v:SetColor(color)
|
||||
if (color.r + color.g + color.b) < 103 and math.random(1, 100) < 35 then
|
||||
v:Fire("enablemotion", "", 0)
|
||||
constraint.RemoveAll(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function FlammablePropThink()
|
||||
local class = flammableProps[math.random(#flammableProps)]
|
||||
local entities = ents.FindByClass(class)
|
||||
local ent = entities[math.random(#entities)]
|
||||
|
||||
if class ~= "letter" then return end
|
||||
|
||||
if not ent then return end
|
||||
|
||||
-- The amount of classes and the amount of entities in a class
|
||||
-- affect the chance of fire spreading. This should be minimized.
|
||||
FireSpread(ent, #entities * #flammableProps)
|
||||
end
|
||||
timer.Create("FlammableProps", 0.1, 0, FlammablePropThink)
|
||||
88
gamemodes/darkrp/gamemode/modules/fadmin/cl_fadmin.lua
Normal file
88
gamemodes/darkrp/gamemode/modules/fadmin/cl_fadmin.lua
Normal file
@@ -0,0 +1,88 @@
|
||||
local function IncludeFolder(fol)
|
||||
fol = string.lower(fol)
|
||||
local _, folders = file.Find(fol .. "*", "LUA")
|
||||
|
||||
for _, folder in SortedPairs(folders, true) do
|
||||
if folder ~= "." and folder ~= ".." then
|
||||
for _, File in SortedPairs(file.Find(fol .. folder .. "/sh_*.lua", "LUA"), true) do
|
||||
include(fol .. folder .. "/" .. File)
|
||||
end
|
||||
|
||||
for _, File in SortedPairs(file.Find(fol .. folder .. "/cl_*.lua", "LUA"), true) do
|
||||
include(fol .. folder .. "/" .. File)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
IncludeFolder(GM.FolderName .. "/gamemode/modules/fadmin/fadmin/")
|
||||
IncludeFolder(GM.FolderName .. "/gamemode/modules/fadmin/fadmin/playeractions/")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
FAdmin global settings
|
||||
---------------------------------------------------------------------------]]
|
||||
net.Receive("FAdmin_GlobalSetting", function(len)
|
||||
local setting, value = net.ReadString(), net.ReadType(net.ReadUInt(8))
|
||||
|
||||
FAdmin.GlobalSetting = FAdmin.GlobalSetting or {}
|
||||
FAdmin.GlobalSetting[setting] = value
|
||||
end)
|
||||
|
||||
net.Receive("FAdmin_PlayerSetting", function(len)
|
||||
local uid, setting, value = net.ReadUInt(16), net.ReadString(), net.ReadType(net.ReadUInt(8))
|
||||
|
||||
FAdmin.PlayerSettings = FAdmin.PlayerSettings or {}
|
||||
FAdmin.PlayerSettings[uid] = FAdmin.PlayerSettings[uid] or {}
|
||||
FAdmin.PlayerSettings[uid][setting] = value
|
||||
end)
|
||||
|
||||
timer.Create("FAdmin_CleanPlayerSettings", 300, 0, function()
|
||||
if not FAdmin.PlayerSettings then return end
|
||||
|
||||
-- find highest userID
|
||||
local max = math.huge
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if IsValid(v) and v:UserID() > max then max = v:UserID() end
|
||||
end
|
||||
|
||||
-- Anything lower than the maximal UserID can be culled
|
||||
-- This prevents data from joining players from being removed
|
||||
-- New players always get a strictly higher UserID than any player before them
|
||||
for uid in pairs(FAdmin.PlayerSettings) do
|
||||
if IsValid(Player(uid)) or uid > max then continue end
|
||||
|
||||
FAdmin.PlayerSettings[uid] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
function plyMeta:FAdmin_GetGlobal(setting)
|
||||
local uid = self:UserID()
|
||||
return FAdmin.PlayerSettings and FAdmin.PlayerSettings[uid] and FAdmin.PlayerSettings[uid][setting] or nil
|
||||
end
|
||||
|
||||
net.Receive("FAdmin_GlobalPlayerSettings", function(len)
|
||||
local globalCount = net.ReadUInt(8)
|
||||
|
||||
FAdmin.GlobalSetting = FAdmin.GlobalSetting or {}
|
||||
|
||||
for i = 1, globalCount do
|
||||
FAdmin.GlobalSetting[net.ReadString()] = net.ReadType(net.ReadUInt(8))
|
||||
end
|
||||
|
||||
local plyCount = net.ReadUInt(8)
|
||||
FAdmin.PlayerSettings = FAdmin.PlayerSettings or {}
|
||||
|
||||
for i = 1, plyCount do
|
||||
local uid = net.ReadUInt(16)
|
||||
local count = net.ReadUInt(8)
|
||||
|
||||
FAdmin.PlayerSettings[uid] = FAdmin.PlayerSettings[uid] or {}
|
||||
|
||||
for j = 1, count do
|
||||
FAdmin.PlayerSettings[uid][net.ReadString()] = net.ReadType(net.ReadUInt(8))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
if not FAdmin or not FAdmin.StartHooks then return end
|
||||
FAdmin.StartHooks["DarkRP"] = function()
|
||||
-- DarkRP information:
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Money", function(ply) if LocalPlayer():IsAdmin() then return DarkRP.formatMoney(ply:getDarkRPVar("money")) end end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Steam name", function(ply) return ply:SteamName() end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Wanted", function(ply) if ply:getDarkRPVar("wanted") then return tostring(ply:getDarkRPVar("wantedReason") or "N/A") end end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Community link", function(ply) return FAdmin.SteamToProfile(ply) end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Rank", function(ply)
|
||||
if FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SeeAdmins") then
|
||||
return ply:GetUserGroup()
|
||||
end
|
||||
end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Wanted reason", function(ply)
|
||||
if ply:isWanted() and LocalPlayer():isCP() then
|
||||
return ply:getWantedReason()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Warrant
|
||||
FAdmin.ScoreBoard.Player:AddActionButton("Warrant", "fadmin/icons/message", Color(0, 0, 200, 255),
|
||||
function(ply) return LocalPlayer():isCP() end,
|
||||
function(ply, button)
|
||||
Derma_StringRequest("Warrant reason", "Enter the reason for the warrant", "", function(Reason)
|
||||
RunConsoleCommand("darkrp", "warrant", ply:SteamID(), Reason)
|
||||
end)
|
||||
end)
|
||||
|
||||
--wanted
|
||||
FAdmin.ScoreBoard.Player:AddActionButton(function(ply)
|
||||
return ((ply:getDarkRPVar("wanted") and "Unw") or "W") .. "anted"
|
||||
end,
|
||||
function(ply) return "fadmin/icons/jail", ply:getDarkRPVar("wanted") and "fadmin/icons/disable" end,
|
||||
Color(0, 0, 200, 255),
|
||||
function(ply) return LocalPlayer():isCP() end,
|
||||
function(ply, button)
|
||||
if not ply:getDarkRPVar("wanted") then
|
||||
Derma_StringRequest("wanted reason", "Enter the reason to arrest this player", "", function(Reason)
|
||||
RunConsoleCommand("darkrp", "wanted", ply:SteamID(), Reason)
|
||||
end)
|
||||
else
|
||||
RunConsoleCommand("darkrp", "unwanted", ply:UserID())
|
||||
end
|
||||
end)
|
||||
|
||||
--Teamban
|
||||
local function teamban(ply, button)
|
||||
local menu = DermaMenu()
|
||||
|
||||
local Padding = vgui.Create("DPanel")
|
||||
Padding:SetPaintBackgroundEnabled(false)
|
||||
Padding:SetSize(1,5)
|
||||
menu:AddPanel(Padding)
|
||||
|
||||
local Title = vgui.Create("DLabel")
|
||||
Title:SetText(" Jobs:\n")
|
||||
Title:SetFont("UiBold")
|
||||
Title:SizeToContents()
|
||||
Title:SetTextColor(color_black)
|
||||
menu:AddPanel(Title)
|
||||
|
||||
local command = "teamban"
|
||||
local uid = ply:UserID()
|
||||
for k, v in SortedPairsByMemberValue(RPExtraTeams, "name") do
|
||||
local submenu = menu:AddSubMenu(v.name)
|
||||
submenu:AddOption("2 minutes", function() RunConsoleCommand("darkrp", command, uid, k, 120) end)
|
||||
submenu:AddOption("Half an hour", function() RunConsoleCommand("darkrp", command, uid, k, 1800) end)
|
||||
submenu:AddOption("An hour", function() RunConsoleCommand("darkrp", command, uid, k, 3600) end)
|
||||
submenu:AddOption("Until restart", function() RunConsoleCommand("darkrp", command, uid, k, 0) end)
|
||||
end
|
||||
menu:Open()
|
||||
end
|
||||
FAdmin.ScoreBoard.Player:AddActionButton("Ban from job", "fadmin/icons/changeteam", Color(200, 0, 0, 255),
|
||||
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "DarkRP_AdminCommands", ply) end, teamban)
|
||||
|
||||
local function teamunban(ply, button)
|
||||
local menu = DermaMenu()
|
||||
|
||||
local Padding = vgui.Create("DPanel")
|
||||
Padding:SetPaintBackgroundEnabled(false)
|
||||
Padding:SetSize(1,5)
|
||||
menu:AddPanel(Padding)
|
||||
|
||||
local Title = vgui.Create("DLabel")
|
||||
Title:SetText(" Jobs:\n")
|
||||
Title:SetFont("UiBold")
|
||||
Title:SizeToContents()
|
||||
Title:SetTextColor(color_black)
|
||||
menu:AddPanel(Title)
|
||||
|
||||
local command = "teamunban"
|
||||
local uid = ply:UserID()
|
||||
for k, v in SortedPairsByMemberValue(RPExtraTeams, "name") do
|
||||
menu:AddOption(v.name, function() RunConsoleCommand("darkrp", command, uid, k) end)
|
||||
end
|
||||
menu:Open()
|
||||
end
|
||||
FAdmin.ScoreBoard.Player:AddActionButton("Unban from job", function() return "fadmin/icons/changeteam", "fadmin/icons/disable" end, Color(200, 0, 0, 255),
|
||||
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "DarkRP_AdminCommands", ply) end, teamunban)
|
||||
end
|
||||
@@ -0,0 +1,262 @@
|
||||
local ContinueNewGroup
|
||||
local EditGroups
|
||||
|
||||
local function RetrievePRIVS(len)
|
||||
FAdmin.Access.Groups = net.ReadTable()
|
||||
|
||||
for k, v in pairs(FAdmin.Access.Groups) do
|
||||
if CAMI.GetUsergroup(k) then continue end
|
||||
|
||||
CAMI.RegisterUsergroup({
|
||||
Name = k,
|
||||
Inherits = FAdmin.Access.ADMIN[v.ADMIN]
|
||||
}, "FAdmin")
|
||||
end
|
||||
|
||||
-- Remove any groups that are removed from FAdmin from CAMI.
|
||||
for k in pairs(CAMI.GetUsergroups()) do
|
||||
if FAdmin.Access.Groups[k] then continue end
|
||||
|
||||
CAMI.UnregisterUsergroup(k, "FAdmin")
|
||||
end
|
||||
end
|
||||
net.Receive("FADMIN_SendGroups", RetrievePRIVS)
|
||||
|
||||
local function addPriv(um)
|
||||
local group = um:ReadString()
|
||||
FAdmin.Access.Groups[group] = FAdmin.Access.Groups[group] or {}
|
||||
FAdmin.Access.Groups[group].PRIVS[um:ReadString()] = true
|
||||
end
|
||||
usermessage.Hook("FAdmin_AddPriv", addPriv)
|
||||
|
||||
local function removePriv(um)
|
||||
FAdmin.Access.Groups[um:ReadString()].PRIVS[um:ReadString()] = nil
|
||||
end
|
||||
usermessage.Hook("FAdmin_RemovePriv", removePriv)
|
||||
|
||||
local function addGroupUI(ply, func)
|
||||
Derma_StringRequest("Set name",
|
||||
"What will be the name of the new group?",
|
||||
"",
|
||||
function(text)
|
||||
if text == "" then return end
|
||||
Derma_Query("On what access will this team be based? (the new group will inherit all the privileges from the group)", "Admin access",
|
||||
"user", function() ContinueNewGroup(ply, text, 0, func) end,
|
||||
"admin", function() ContinueNewGroup(ply, text, 1, func) end,
|
||||
"superadmin", function() ContinueNewGroup(ply, text, 2, func) end)
|
||||
end)
|
||||
end
|
||||
|
||||
FAdmin.StartHooks["1SetAccess"] = function() -- 1 in hook name so it will be executed first.
|
||||
FAdmin.Commands.AddCommand("setaccess", nil, "<Player>", "<Group name>", "[new group based on (number)]", "[new group privileges]")
|
||||
|
||||
FAdmin.ScoreBoard.Player:AddActionButton("Set access", "fadmin/icons/access", Color(155, 0, 0, 255),
|
||||
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SetAccess") or LocalPlayer():IsSuperAdmin() end, function(ply)
|
||||
local menu = DermaMenu()
|
||||
|
||||
local Padding = vgui.Create("DPanel")
|
||||
Padding:SetPaintBackgroundEnabled(false)
|
||||
Padding:SetSize(1,5)
|
||||
menu:AddPanel(Padding)
|
||||
|
||||
local Title = vgui.Create("DLabel")
|
||||
Title:SetText(" Set access:\n")
|
||||
Title:SetFont("UiBold")
|
||||
Title:SizeToContents()
|
||||
Title:SetTextColor(color_black)
|
||||
|
||||
menu:AddPanel(Title)
|
||||
|
||||
for k in SortedPairsByMemberValue(FAdmin.Access.Groups, "ADMIN", true) do
|
||||
menu:AddOption(k, function()
|
||||
if not IsValid(ply) then return end
|
||||
RunConsoleCommand("_FAdmin", "setaccess", ply:UserID(), k)
|
||||
end)
|
||||
end
|
||||
|
||||
menu:AddOption("New...", function() addGroupUI(ply) end)
|
||||
menu:Open()
|
||||
end)
|
||||
|
||||
FAdmin.ScoreBoard.Server:AddPlayerAction("Edit groups", "fadmin/icons/access", Color(0, 155, 0, 255), function() return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManageGroups") or FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManagePrivileges") end, EditGroups)
|
||||
|
||||
-- Admin immunity
|
||||
FAdmin.ScoreBoard.Server:AddServerSetting(function()
|
||||
return (FAdmin.GlobalSetting.Immunity and "Disable" or "Enable") .. " Admin immunity"
|
||||
end,
|
||||
function()
|
||||
return "fadmin/icons/access", FAdmin.GlobalSetting.Immunity and "fadmin/icons/disable"
|
||||
end, Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManageGroups") end, function(button)
|
||||
button:SetImage2((not FAdmin.GlobalSetting.Immunity and "fadmin/icons/disable") or "null")
|
||||
button:SetText((not FAdmin.GlobalSetting.Immunity and "Disable" or "Enable") .. " Admin immunity")
|
||||
button:GetParent():InvalidateLayout()
|
||||
|
||||
RunConsoleCommand("_Fadmin", "immunity", (FAdmin.GlobalSetting.Immunity and 0) or 1)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
ContinueNewGroup = function(ply, name, admin_access, func)
|
||||
if IsValid(ply) then
|
||||
RunConsoleCommand("_FAdmin", "setaccess", ply:UserID(), name, admin_access)
|
||||
else
|
||||
RunConsoleCommand("_FAdmin", "AddGroup", name, admin_access)
|
||||
end
|
||||
|
||||
if func then
|
||||
func(name, admin_access)
|
||||
end
|
||||
end
|
||||
|
||||
EditGroups = function()
|
||||
local frame, SelectedGroup, AddGroup, RemGroup, Privileges, SelectedPrivs, AddPriv, RemPriv, lblImmunity, nmbrImmunity
|
||||
|
||||
frame = vgui.Create("DFrame")
|
||||
frame:SetTitle("Create, edit and remove groups")
|
||||
frame:MakePopup()
|
||||
frame:SetVisible(true)
|
||||
frame:SetSize(640, 480)
|
||||
frame:Center()
|
||||
|
||||
SelectedGroup = vgui.Create("DComboBox", frame)
|
||||
SelectedGroup:SetPos(5, 30)
|
||||
SelectedGroup:SetWidth(145)
|
||||
|
||||
for _, v in pairs(FAdmin.Access.Groups) do
|
||||
v.immunity = v.immunity or 0
|
||||
end
|
||||
for k in SortedPairsByMemberValue(FAdmin.Access.Groups, "immunity", true) do
|
||||
SelectedGroup:AddChoice(k)
|
||||
end
|
||||
|
||||
AddGroup = vgui.Create("DButton", frame)
|
||||
AddGroup:SetPos(155, 30)
|
||||
AddGroup:SetSize(60, 22)
|
||||
AddGroup:SetText("Add Group")
|
||||
AddGroup.DoClick = function()
|
||||
addGroupUI(nil, function(name, admin, privs)
|
||||
SelectedGroup:AddChoice(name)
|
||||
SelectedGroup:SetValue(name)
|
||||
RemGroup:SetDisabled(false)
|
||||
|
||||
Privileges:Clear()
|
||||
SelectedPrivs:Clear()
|
||||
nmbrImmunity:SetText(FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity)
|
||||
nmbrImmunity:SetDisabled(false)
|
||||
nmbrImmunity:SetEditable(true)
|
||||
|
||||
for priv, am in SortedPairs(FAdmin.Access.Privileges) do
|
||||
if am <= admin + 1 then
|
||||
SelectedPrivs:AddLine(priv)
|
||||
else
|
||||
Privileges:AddLine(priv)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
RemGroup = vgui.Create("DButton", frame)
|
||||
RemGroup:SetPos(220, 30)
|
||||
RemGroup:SetSize(85, 22)
|
||||
RemGroup:SetText("Remove Group")
|
||||
RemGroup.DoClick = function()
|
||||
RunConsoleCommand("_FAdmin", "RemoveGroup", SelectedGroup:GetValue())
|
||||
|
||||
for k, v in pairs(SelectedGroup.Choices) do
|
||||
if v ~= SelectedGroup:GetValue() then continue end
|
||||
|
||||
SelectedGroup.Choices[k] = nil
|
||||
break
|
||||
end
|
||||
table.ClearKeys(SelectedGroup.Choices)
|
||||
|
||||
SelectedGroup:SetValue("user")
|
||||
SelectedGroup:OnSelect(1, "user")
|
||||
end
|
||||
|
||||
Privileges = vgui.Create("DListView", frame)
|
||||
Privileges:SetPos(5, 55)
|
||||
Privileges:SetSize(300, 420)
|
||||
Privileges:AddColumn("Available privileges")
|
||||
|
||||
SelectedPrivs = vgui.Create("DListView", frame)
|
||||
SelectedPrivs:SetPos(340, 55)
|
||||
SelectedPrivs:SetSize(295, 420)
|
||||
SelectedPrivs:AddColumn("Selected Privileges")
|
||||
|
||||
function SelectedGroup:OnSelect(index, value, data)
|
||||
if not FAdmin.Access.Groups[value] then return end
|
||||
|
||||
RemGroup:SetDisabled(false)
|
||||
if table.HasValue(FAdmin.Access.ADMIN, value) then
|
||||
RemGroup:SetDisabled(true)
|
||||
end
|
||||
|
||||
Privileges:Clear()
|
||||
SelectedPrivs:Clear()
|
||||
|
||||
for priv, _ in SortedPairs(FAdmin.Access.Privileges) do
|
||||
if FAdmin.Access.Groups[value].PRIVS[priv] then
|
||||
SelectedPrivs:AddLine(priv)
|
||||
else
|
||||
Privileges:AddLine(priv)
|
||||
end
|
||||
end
|
||||
|
||||
if nmbrImmunity then
|
||||
nmbrImmunity:SetText(FAdmin.Access.Groups[value].immunity or "")
|
||||
if table.HasValue({"superadmin", "admin", "user", "noaccess"}, string.lower(value)) then
|
||||
nmbrImmunity:SetDisabled(true)
|
||||
nmbrImmunity:SetEditable(false)
|
||||
else
|
||||
nmbrImmunity:SetDisabled(false)
|
||||
nmbrImmunity:SetEditable(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
SelectedGroup:SetValue("user")
|
||||
SelectedGroup:OnSelect(1, "user")
|
||||
|
||||
AddPriv = vgui.Create("DButton", frame)
|
||||
AddPriv:SetPos(310, 55)
|
||||
AddPriv:SetSize(25, 25)
|
||||
AddPriv:SetText(">")
|
||||
AddPriv.DoClick = function()
|
||||
for _, v in ipairs(Privileges:GetSelected()) do
|
||||
local priv = v.Columns[1]:GetValue()
|
||||
RunConsoleCommand("FAdmin", "AddPrivilege", SelectedGroup:GetValue(), priv)
|
||||
SelectedPrivs:AddLine(priv)
|
||||
Privileges:RemoveLine(v.m_iID)
|
||||
end
|
||||
end
|
||||
|
||||
RemPriv = vgui.Create("DButton", frame)
|
||||
RemPriv:SetPos(310, 85)
|
||||
RemPriv:SetSize(25, 25)
|
||||
RemPriv:SetText("<")
|
||||
RemPriv.DoClick = function()
|
||||
for _, v in ipairs(SelectedPrivs:GetSelected()) do
|
||||
local priv = v.Columns[1]:GetValue()
|
||||
if SelectedGroup:GetValue() == LocalPlayer():GetUserGroup() and priv == "ManagePrivileges" then
|
||||
return Derma_Message("You shouldn't be removing ManagePrivileges. It will make you unable to edit the groups. This is preventing you from locking yourself out of the system.", "Clever move.")
|
||||
end
|
||||
RunConsoleCommand("FAdmin", "RemovePrivilege", SelectedGroup:GetValue(), priv)
|
||||
Privileges:AddLine(priv)
|
||||
SelectedPrivs:RemoveLine(v.m_iID)
|
||||
end
|
||||
end
|
||||
|
||||
lblImmunity = vgui.Create("DLabel", frame)
|
||||
lblImmunity:SetPos(340, 30)
|
||||
lblImmunity:SetText("Immunity number (higher is more immune)")
|
||||
lblImmunity:SizeToContents()
|
||||
|
||||
nmbrImmunity = vgui.Create("DTextEntry", frame)
|
||||
nmbrImmunity:SetPos(545, 28)
|
||||
nmbrImmunity:SetWide(90)
|
||||
nmbrImmunity:SetNumeric(true)
|
||||
nmbrImmunity:SetText(FAdmin.Access.Groups.user.immunity)
|
||||
nmbrImmunity:SetDisabled(true)
|
||||
nmbrImmunity:SetEditable(false)
|
||||
nmbrImmunity.OnEnter = function(self) RunConsoleCommand("FAdmin", "SetImmunity", SelectedGroup:GetValue(), self:GetValue()) end
|
||||
end
|
||||
@@ -0,0 +1,273 @@
|
||||
CreateConVar("_FAdmin_immunity", 1, {FCVAR_GAMEDLL, FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE})
|
||||
|
||||
FAdmin.Access = FAdmin.Access or {}
|
||||
FAdmin.Access.ADMIN = {"user", "admin", "superadmin"}
|
||||
FAdmin.Access.ADMIN[0] = "user"
|
||||
|
||||
FAdmin.Access.Groups = FAdmin.Access.Groups or {}
|
||||
FAdmin.Access.Privileges = FAdmin.Access.Privileges or {}
|
||||
|
||||
function FAdmin.Access.AddGroup(name, admin_access --[[0 = not admin, 1 = admin, 2 = superadmin]], privs, immunity, fromCAMI, CAMIsrc)
|
||||
FAdmin.Access.Groups[name] = FAdmin.Access.Groups[name] or {ADMIN = admin_access, PRIVS = privs or {}, immunity = immunity}
|
||||
|
||||
--Make sure things that come from CAMI come with a CAMIsrc
|
||||
assert((fromCAMI and CAMIsrc ~= nil) or ((not fromCAMI) and CAMIsrc == nil))
|
||||
--If the CAMIsrc is a string, save it, otherwise save an empty string
|
||||
if not isstring(CAMIsrc) then
|
||||
CAMIsrc = ""
|
||||
end
|
||||
|
||||
-- Register custom usergroups with CAMI
|
||||
if name ~= "user" and name ~= "admin" and name ~= "superadmin" and not fromCAMI then
|
||||
CAMI.RegisterUsergroup({
|
||||
Name = name,
|
||||
Inherits = FAdmin.Access.ADMIN[admin_access]
|
||||
}, "FAdmin")
|
||||
end
|
||||
|
||||
-- Add newly created privileges on server reload
|
||||
for p, _ in pairs(privs or {}) do
|
||||
FAdmin.Access.Groups[name].PRIVS[p] = true
|
||||
end
|
||||
|
||||
if not SERVER then return end
|
||||
|
||||
MySQLite.queryValue("SELECT COUNT(*) FROM FADMIN_GROUPS WHERE NAME = " .. MySQLite.SQLStr(name) .. ";", function(val)
|
||||
if tonumber(val or 0) > 0 then return end
|
||||
|
||||
MySQLite.query("REPLACE INTO FADMIN_GROUPS VALUES(" .. MySQLite.SQLStr(name) .. ", " .. tonumber(admin_access) .. ");", function()
|
||||
for priv, _ in pairs(privs or {}) do
|
||||
MySQLite.query("REPLACE INTO FADMIN_PRIVILEGES VALUES(" .. MySQLite.SQLStr(name) .. ", " .. MySQLite.SQLStr(priv) .. ");")
|
||||
end
|
||||
if fromCAMI then
|
||||
MySQLite.query("REPLACE INTO FADMIN_GROUPS_SRC VALUES(" .. MySQLite.SQLStr(name) .. ", " .. MySQLite.SQLStr(CAMIsrc) .. ");")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
if immunity then
|
||||
MySQLite.query("REPLACE INTO FAdmin_Immunity VALUES(" .. MySQLite.SQLStr(name) .. ", " .. tonumber(immunity) .. ");")
|
||||
end
|
||||
|
||||
if FAdmin.Access.SendGroups and privs then
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
FAdmin.Access.SendGroups(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function FAdmin.Access.OnUsergroupRegistered(usergroup, source)
|
||||
-- Don't re-add usergroups coming from FAdmin itself
|
||||
if source == "FAdmin" then return end
|
||||
|
||||
local inheritRoot = CAMI.InheritanceRoot(usergroup.Inherits)
|
||||
local admin_access = table.KeyFromValue(FAdmin.Access.ADMIN, inheritRoot) or 1
|
||||
|
||||
-- Add groups registered to CAMI to FAdmin. Assume privileges from either the usergroup it inherits or its inheritance root.
|
||||
-- Immunity is unknown and can be set by the user later. FAdmin immunity only applies to FAdmin anyway.
|
||||
local parent = FAdmin.Access.Groups[usergroup.Inherits] or FAdmin.Access.Groups[inheritRoot] or {}
|
||||
FAdmin.Access.AddGroup(usergroup.Name, admin_access - 1, table.Copy(parent.PRIVS) or {}, parent.immunity or 10, true, source)
|
||||
end
|
||||
|
||||
|
||||
function FAdmin.Access.OnUsergroupUnregistered(usergroup, source)
|
||||
if table.HasValue({"superadmin", "admin", "user", "noaccess"}, usergroup.Name) then return end
|
||||
|
||||
FAdmin.Access.Groups[usergroup.Name] = nil
|
||||
|
||||
if not SERVER then return end
|
||||
|
||||
MySQLite.query("DELETE FROM FADMIN_GROUPS WHERE NAME = " .. MySQLite.SQLStr(usergroup.Name) .. ";")
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
FAdmin.Access.SendGroups(v)
|
||||
end
|
||||
end
|
||||
|
||||
function FAdmin.Access.RemoveGroup(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
if not args[1] then return false end
|
||||
|
||||
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
|
||||
|
||||
if not FAdmin.Access.Groups[args[1]] or table.HasValue({"superadmin", "admin", "user"}, string.lower(args[1])) then return true, args[1] end
|
||||
|
||||
-- Setting a group with a higher rank than one's own
|
||||
if (not plyGroup or FAdmin.Access.Groups[args[1]].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to remove usergroups with a higher rank than your own")
|
||||
return false
|
||||
end
|
||||
|
||||
CAMI.UnregisterUsergroup(args[1], "FAdmin")
|
||||
|
||||
FAdmin.Messages.SendMessage(ply, 4, "Group succesfully removed")
|
||||
end
|
||||
|
||||
local PLAYER = FindMetaTable("Player")
|
||||
|
||||
local oldplyIsAdmin = PLAYER.IsAdmin
|
||||
function PLAYER:IsAdmin(...)
|
||||
local usergroup = self:GetUserGroup()
|
||||
|
||||
if not FAdmin or not FAdmin.Access or not FAdmin.Access.Groups or not FAdmin.Access.Groups[usergroup] then return oldplyIsAdmin(self, ...) or game.SinglePlayer() end
|
||||
|
||||
if (FAdmin.Access.Groups[usergroup] and FAdmin.Access.Groups[usergroup].ADMIN >= 1 --[[1 = admin]]) or (self.IsListenServerHost and self:IsListenServerHost()) then
|
||||
return true
|
||||
end
|
||||
|
||||
if CLIENT and tonumber(self:FAdmin_GetGlobal("FAdmin_admin")) and self:FAdmin_GetGlobal("FAdmin_admin") >= 1 then return true end
|
||||
|
||||
return oldplyIsAdmin(self, ...) or game.SinglePlayer()
|
||||
end
|
||||
|
||||
local oldplyIsSuperAdmin = PLAYER.IsSuperAdmin
|
||||
function PLAYER:IsSuperAdmin(...)
|
||||
local usergroup = self:GetUserGroup()
|
||||
if not FAdmin or not FAdmin.Access or not FAdmin.Access.Groups or not FAdmin.Access.Groups[usergroup] then return oldplyIsSuperAdmin(self, ...) or game.SinglePlayer() end
|
||||
if (FAdmin.Access.Groups[usergroup] and FAdmin.Access.Groups[usergroup].ADMIN >= 2 --[[2 = superadmin]]) or (self.IsListenServerHost and self:IsListenServerHost()) then
|
||||
return true
|
||||
end
|
||||
if CLIENT and tonumber(self:FAdmin_GetGlobal("FAdmin_admin")) and self:FAdmin_GetGlobal("FAdmin_admin") >= 2 then return true end
|
||||
return oldplyIsSuperAdmin(self, ...) or game.SinglePlayer()
|
||||
end
|
||||
|
||||
--Privileges
|
||||
function FAdmin.Access.AddPrivilege(Name, admin_access)
|
||||
FAdmin.Access.Privileges[Name] = admin_access
|
||||
end
|
||||
|
||||
hook.Add("CAMI.OnPrivilegeRegistered", "FAdmin", function(privilege)
|
||||
FAdmin.Access.AddPrivilege(privilege.Name, table.KeyFromValue(FAdmin.Access.ADMIN, CAMI.InheritanceRoot(privilege.MinAccess)) or 3)
|
||||
|
||||
-- Register privilege and add to respective usergroups
|
||||
if SERVER then FAdmin.Access.RegisterCAMIPrivilege(privilege) end
|
||||
end)
|
||||
|
||||
for _, camipriv in pairs(CAMI.GetPrivileges()) do
|
||||
FAdmin.Access.AddPrivilege(camipriv.Name, table.KeyFromValue(FAdmin.Access.ADMIN, CAMI.InheritanceRoot(camipriv.MinAccess)) or 3)
|
||||
-- Register if the database has already loaded
|
||||
if SERVER and FAdmin.Access.RegisterCAMIPrivilege then FAdmin.Access.RegisterCAMIPrivilege(camipriv) end
|
||||
end
|
||||
|
||||
hook.Add("CAMI.OnPrivilegeUnregistered", "FAdmin", function(privilege)
|
||||
FAdmin.Access.Privileges[privilege.Name] = nil
|
||||
end)
|
||||
|
||||
function FAdmin.Access.PlayerIsHost(ply)
|
||||
return ply:EntIndex() == 0 or game.SinglePlayer() or (ply.IsListenServerHost and ply:IsListenServerHost())
|
||||
end
|
||||
|
||||
function FAdmin.Access.PlayerHasPrivilege(ply, priv, target, ignoreImmunity)
|
||||
-- This is the server console
|
||||
if FAdmin.Access.PlayerIsHost(ply) then return true end
|
||||
-- Privilege does not exist
|
||||
if not FAdmin.Access.Privileges[priv] then return ply:IsAdmin() end
|
||||
|
||||
local Usergroup = ply:GetUserGroup()
|
||||
|
||||
local canTarget = hook.Call("FAdmin_CanTarget", nil, ply, priv, target)
|
||||
if canTarget ~= nil then
|
||||
return canTarget
|
||||
end
|
||||
|
||||
if FAdmin.GlobalSetting.Immunity and
|
||||
not ignoreImmunity and
|
||||
not isstring(target) and IsValid(target) and target ~= ply and
|
||||
FAdmin.Access.Groups[Usergroup] and FAdmin.Access.Groups[target:GetUserGroup()] and
|
||||
FAdmin.Access.Groups[Usergroup].immunity and FAdmin.Access.Groups[target:GetUserGroup()].immunity and
|
||||
FAdmin.Access.Groups[target:GetUserGroup()].immunity >= FAdmin.Access.Groups[Usergroup].immunity then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Defer answer when usergroup is unknown
|
||||
if not FAdmin.Access.Groups[Usergroup] then return end
|
||||
|
||||
if FAdmin.Access.Groups[Usergroup].PRIVS[priv] then
|
||||
return true
|
||||
end
|
||||
|
||||
if CLIENT and ply.FADMIN_PRIVS and ply.FADMIN_PRIVS[priv] then return true end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
hook.Add("CAMI.PlayerHasAccess", "FAdmin", function(actor, privilegeName, callback, target, extraInfo)
|
||||
-- FAdmin doesn't know. Defer answer.
|
||||
if not FAdmin.Access.Privileges[privilegeName] then return end
|
||||
|
||||
local res = FAdmin.Access.PlayerHasPrivilege(actor, privilegeName, target, extraInfo and extraInfo.IgnoreImmunity)
|
||||
|
||||
-- Defer again
|
||||
if res == nil then return end
|
||||
|
||||
-- Publish the answer
|
||||
callback(res, "FAdmin")
|
||||
|
||||
-- FAdmin knows the answer. Prevent other hooks from running.
|
||||
return true
|
||||
end)
|
||||
|
||||
hook.Add("CAMI.SteamIDHasAccess", "FAdmin", function(actorSteam, privilegeName, callback, targetSteam, extraInfo)
|
||||
-- The client just doesn't know
|
||||
if CLIENT then return end
|
||||
|
||||
if not targetSteam or extraInfo and extraInfo.IgnoreImmunity then
|
||||
MySQLite.query(string.format(
|
||||
[[SELECT COUNT(*) AS c
|
||||
FROM FAdmin_PlayerGroup l
|
||||
JOIN FADMIN_PRIVILEGES r ON l.groupname = r.NAME
|
||||
WHERE l.steamid = %s AND r.PRIVILEGE = %s]],
|
||||
MySQLite.SQLStr(actorSteam),
|
||||
MySQLite.SQLStr(privilegeName)
|
||||
), function(res) callback(tonumber(res[1].c) > 0) end)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
MySQLite.query(string.format(
|
||||
[[SELECT ll.i AND rr.c AS res
|
||||
FROM (SELECT li.immunity >= ri.immunity AS i
|
||||
FROM FAdmin_PlayerGroup lg
|
||||
JOIN FAdmin_Immunity li ON lg.groupname = li.groupname
|
||||
JOIN FAdmin_PlayerGroup rg
|
||||
JOIN FAdmin_Immunity ri ON rg.groupname = ri.groupname
|
||||
WHERE lg.steamid = %s AND rg.steamid = %s) AS ll
|
||||
JOIN (SELECT COUNT(*) AS c
|
||||
FROM FAdmin_PlayerGroup l
|
||||
JOIN FADMIN_PRIVILEGES r ON l.groupname = r.NAME
|
||||
WHERE l.steamid = %s AND r.PRIVILEGE = %s) AS rr]],
|
||||
MySQLite.SQLStr(actorSteam),
|
||||
MySQLite.SQLStr(targetSteam),
|
||||
MySQLite.SQLStr(actorSteam),
|
||||
MySQLite.SQLStr(privilegeName)
|
||||
), function(res) callback(res and res[1] and tobool(res[1].res) or false) end)
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
FAdmin.StartHooks["AccessFunctions"] = function()
|
||||
FAdmin.Messages.RegisterNotification{
|
||||
name = "setaccess",
|
||||
hasTarget = true,
|
||||
message = {"instigator", " set the usergroup of ", "targets", " to ", "extraInfo.1"},
|
||||
receivers = "everyone",
|
||||
writeExtraInfo = function(i) net.WriteString(i[1]) end,
|
||||
readExtraInfo = function() return {net.ReadString()} end,
|
||||
extraInfoColors = {Color(255, 102, 0)}
|
||||
}
|
||||
|
||||
FAdmin.Access.AddPrivilege("SetAccess", 3) -- AddPrivilege is shared, run on both client and server
|
||||
FAdmin.Access.AddPrivilege("ManagePrivileges", 3)
|
||||
FAdmin.Access.AddPrivilege("ManageGroups", 3)
|
||||
FAdmin.Access.AddPrivilege("SeeAdmins", 1)
|
||||
FAdmin.Commands.AddCommand("RemoveGroup", FAdmin.Access.RemoveGroup)
|
||||
|
||||
FAdmin.Commands.AddCommand("Admins", function(ply)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "SeeAdmins") then return false end
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, v:Nick() .. "\t|\t" .. v:GetUserGroup())
|
||||
end
|
||||
return true
|
||||
end
|
||||
)
|
||||
end
|
||||
@@ -0,0 +1,449 @@
|
||||
--Immunity
|
||||
cvars.AddChangeCallback("_FAdmin_immunity", function(Cvar, Previous, New)
|
||||
FAdmin.SetGlobalSetting("Immunity", (tonumber(New) == 1 and true) or false)
|
||||
FAdmin.SaveSetting("_FAdmin_immunity", tonumber(New))
|
||||
end)
|
||||
|
||||
hook.Add("DatabaseInitialized", "InitializeFAdminGroups", function()
|
||||
MySQLite.query("CREATE TABLE IF NOT EXISTS FADMIN_GROUPS(NAME VARCHAR(40) NOT NULL PRIMARY KEY, ADMIN_ACCESS INTEGER NOT NULL);")
|
||||
MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_PlayerGroup(steamid VARCHAR(40) NOT NULL, groupname VARCHAR(40) NOT NULL, PRIMARY KEY(steamid));")
|
||||
MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_Immunity(groupname VARCHAR(40) NOT NULL, immunity INTEGER NOT NULL, PRIMARY KEY(groupname));")
|
||||
MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_CAMIPrivileges(privname VARCHAR(255) NOT NULL PRIMARY KEY);")
|
||||
MySQLite.query("CREATE TABLE IF NOT EXISTS FADMIN_GROUPS_SRC(NAME VARCHAR(40) NOT NULL PRIMARY KEY REFERENCES FADMIN_GROUPS(NAME) ON DELETE CASCADE, SRC VARCHAR(40));")
|
||||
MySQLite.query([[CREATE TABLE IF NOT EXISTS FADMIN_PRIVILEGES(
|
||||
NAME VARCHAR(40),
|
||||
PRIVILEGE VARCHAR(100),
|
||||
PRIMARY KEY(NAME, PRIVILEGE),
|
||||
FOREIGN KEY(NAME) REFERENCES FADMIN_GROUPS(NAME)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
);]], function()
|
||||
|
||||
-- Remove SetAccess workaround
|
||||
MySQLite.query([[DELETE FROM FADMIN_PRIVILEGES WHERE NAME = "user" AND PRIVILEGE = "SetAccess";]])
|
||||
|
||||
MySQLite.query("SELECT g.NAME, g.ADMIN_ACCESS, p.PRIVILEGE, i.immunity, s.src FROM FADMIN_GROUPS g LEFT OUTER JOIN FADMIN_PRIVILEGES p ON g.NAME = p.NAME LEFT OUTER JOIN FAdmin_Immunity i ON g.NAME = i.groupname LEFT OUTER JOIN FADMIN_GROUPS_SRC s ON g.NAME = s.NAME;", function(data)
|
||||
if not data then return end
|
||||
|
||||
for _, v in pairs(data) do
|
||||
FAdmin.Access.Groups[v.NAME] = FAdmin.Access.Groups[v.NAME] or
|
||||
{ADMIN = tonumber(v.ADMIN_ACCESS), PRIVS = {}}
|
||||
|
||||
if v.PRIVILEGE and v.PRIVILEGE ~= "NULL" then
|
||||
FAdmin.Access.Groups[v.NAME].PRIVS[v.PRIVILEGE] = true
|
||||
end
|
||||
|
||||
if v.immunity and v.immunity ~= "NULL" then
|
||||
FAdmin.Access.Groups[v.NAME].immunity = tonumber(v.immunity)
|
||||
end
|
||||
|
||||
if CAMI.GetUsergroup(v.NAME) then continue end
|
||||
|
||||
CAMI.RegisterUsergroup({
|
||||
Name = v.NAME,
|
||||
Inherits = FAdmin.Access.ADMIN[v.ADMIN_ACCESS] or "user"
|
||||
}, v.SRC)
|
||||
end
|
||||
|
||||
-- Send groups to early joiners and listen server hosts
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
FAdmin.Access.SendGroups(v)
|
||||
end
|
||||
|
||||
-- See if there are any CAMI usergroups that FAdmin doesn't know about yet.
|
||||
-- FAdmin doesn't start listening immediately because the database might not have initialised.
|
||||
-- Besides, other admin mods might add usergroups before FAdmin's Lua files are even run
|
||||
for _, v in pairs(CAMI.GetUsergroups()) do
|
||||
if FAdmin.Access.Groups[v.Name] then continue end
|
||||
|
||||
FAdmin.Access.OnUsergroupRegistered(v,"")
|
||||
end
|
||||
|
||||
-- Start listening for CAMI usergroup registrations.
|
||||
hook.Add("CAMI.OnUsergroupRegistered", "FAdmin", FAdmin.Access.OnUsergroupRegistered)
|
||||
hook.Add("CAMI.OnUsergroupUnregistered", "FAdmin", FAdmin.Access.OnUsergroupUnregistered)
|
||||
|
||||
FAdmin.Access.RegisterCAMIPrivileges()
|
||||
end)
|
||||
|
||||
local function createGroups(privs)
|
||||
FAdmin.Access.AddGroup("superadmin", 2, privs.superadmin, 100)
|
||||
FAdmin.Access.AddGroup("admin", 1, privs.admin, 50)
|
||||
FAdmin.Access.AddGroup("user", 0, privs.user, 10)
|
||||
FAdmin.Access.AddGroup("noaccess", 0, privs.noaccess, 0)
|
||||
end
|
||||
|
||||
MySQLite.query("SELECT DISTINCT PRIVILEGE FROM FADMIN_PRIVILEGES;", function(privTbl)
|
||||
local privs = {}
|
||||
local hasPrivs = {"noaccess", "user", "admin", "superadmin"}
|
||||
|
||||
-- No privileges registered to anyone. Reset everything
|
||||
if not privTbl or table.IsEmpty(privTbl) then
|
||||
for priv, access in pairs(FAdmin.Access.Privileges) do
|
||||
for i = access + 1, #hasPrivs, 1 do
|
||||
privs[hasPrivs[i]] = privs[hasPrivs[i]] or {}
|
||||
privs[hasPrivs[i]][priv] = true
|
||||
end
|
||||
end
|
||||
|
||||
createGroups(privs)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Check for newly created privileges and assign them to the default usergroups
|
||||
-- No privilege can be revoke from every group
|
||||
local privSet = {}
|
||||
for _, priv in ipairs(privTbl) do
|
||||
privSet[priv.PRIVILEGE] = true
|
||||
end
|
||||
|
||||
for priv, access in pairs(FAdmin.Access.Privileges) do
|
||||
if privSet[priv] then continue end
|
||||
|
||||
for i = access + 1, #hasPrivs do
|
||||
MySQLite.query(("REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);"):format(MySQLite.SQLStr(hasPrivs[i]), MySQLite.SQLStr(priv)))
|
||||
end
|
||||
end
|
||||
|
||||
createGroups(privs)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- Assign a privilege to its respective usergroups when they are seen for the first time
|
||||
function FAdmin.Access.RegisterCAMIPrivilege(priv)
|
||||
-- Privileges haven't been loaded yet or has already been seen
|
||||
if not FAdmin.CAMIPrivs or FAdmin.CAMIPrivs[priv.Name] then return end
|
||||
|
||||
FAdmin.CAMIPrivs[priv.Name] = true
|
||||
|
||||
for groupName, groupdata in pairs(FAdmin.Access.Groups) do
|
||||
if FAdmin.Access.Privileges[priv.Name] - 1 > groupdata.ADMIN then continue end
|
||||
groupdata.PRIVS[priv.Name] = true
|
||||
|
||||
MySQLite.query(string.format([[REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);]], MySQLite.SQLStr(groupName), MySQLite.SQLStr(priv.Name)))
|
||||
end
|
||||
|
||||
MySQLite.query(string.format([[REPLACE INTO FAdmin_CAMIPrivileges VALUES(%s);]], MySQLite.SQLStr(priv.Name)))
|
||||
end
|
||||
|
||||
-- Assign privileges to their respective usergroups when they are seen for the first time
|
||||
function FAdmin.Access.RegisterCAMIPrivileges()
|
||||
MySQLite.query([[SELECT privname FROM FAdmin_CAMIPrivileges]], function(data)
|
||||
FAdmin.CAMIPrivs = {}
|
||||
|
||||
for _, row in ipairs(data or {}) do
|
||||
FAdmin.CAMIPrivs[row.privname] = true
|
||||
end
|
||||
|
||||
|
||||
for privName, _ in pairs(CAMI.GetPrivileges()) do
|
||||
if FAdmin.CAMIPrivs[privName] then continue end
|
||||
FAdmin.CAMIPrivs[privName] = true
|
||||
|
||||
for groupName, groupdata in pairs(FAdmin.Access.Groups) do
|
||||
if FAdmin.Access.Privileges[privName] - 1 > groupdata.ADMIN then continue end
|
||||
groupdata.PRIVS[privName] = true
|
||||
|
||||
MySQLite.query(string.format([[REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);]], MySQLite.SQLStr(groupName), MySQLite.SQLStr(privName)))
|
||||
end
|
||||
|
||||
MySQLite.query(string.format([[REPLACE INTO FAdmin_CAMIPrivileges VALUES(%s);]], MySQLite.SQLStr(privName)))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function FAdmin.Access.PlayerSetGroup(ply, group)
|
||||
if not FAdmin.Access.Groups[group] then return end
|
||||
ply = isstring(ply) and FAdmin.FindPlayer(ply) and FAdmin.FindPlayer(ply)[1] or ply
|
||||
|
||||
if not isstring(ply) and IsValid(ply) then
|
||||
ply:SetUserGroup(group)
|
||||
end
|
||||
end
|
||||
|
||||
hook.Remove("PlayerInitialSpawn", "PlayerAuthSpawn") -- Remove Garry's usergroup setter.
|
||||
|
||||
-- Update the database only when an end users indicates that a player's usergroup is to be changed.
|
||||
hook.Add("CAMI.PlayerUsergroupChanged", "FAdmin", function(ply, old, new, source)
|
||||
MySQLite.query("REPLACE INTO FAdmin_PlayerGroup VALUES(" .. MySQLite.SQLStr(ply:SteamID()) .. ", " .. MySQLite.SQLStr(new) .. ");")
|
||||
end)
|
||||
|
||||
hook.Add("CAMI.SteamIDUsergroupChanged", "FAdmin", function(steamId, old, new, source)
|
||||
MySQLite.query("REPLACE INTO FAdmin_PlayerGroup VALUES(" .. MySQLite.SQLStr(steamId) .. ", " .. MySQLite.SQLStr(new) .. ");")
|
||||
end)
|
||||
|
||||
function FAdmin.Access.SetRoot(ply, cmd, args) -- FAdmin setroot player. Sets the player to superadmin
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "SetAccess") then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "No access!")
|
||||
FAdmin.Messages.SendMessage(ply, 5, "Please use RCon to set yourself to superadmin if you are the owner of the server")
|
||||
return false
|
||||
end
|
||||
|
||||
local group = FAdmin.Access.Groups["superadmin"]
|
||||
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
|
||||
|
||||
-- Setting a group with a higher rank than one's own
|
||||
if (not plyGroup or group.immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own")
|
||||
FAdmin.Messages.SendMessage(ply, 5, "Please use RCon to set yourself to superadmin if you are the owner of the server")
|
||||
return false
|
||||
end
|
||||
|
||||
local targets = FAdmin.FindPlayer(args[1])
|
||||
if not targets or #targets == 1 and not IsValid(targets[1]) then
|
||||
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
|
||||
return false
|
||||
end
|
||||
|
||||
for _, target in ipairs(targets) do
|
||||
if not IsValid(target) then continue end
|
||||
|
||||
local target_previous_group = target:GetUserGroup()
|
||||
FAdmin.Access.PlayerSetGroup(target, "superadmin")
|
||||
|
||||
-- An end user changed the usergroup. Register with CAMI
|
||||
CAMI.SignalUserGroupChanged(target, target_previous_group, "superadmin", "FAdmin")
|
||||
|
||||
FAdmin.Messages.SendMessage(ply, 2, "User set to superadmin!")
|
||||
end
|
||||
|
||||
FAdmin.Messages.FireNotification("setaccess", ply, targets, {"superadmin"})
|
||||
return true, targets, "superadmin"
|
||||
end
|
||||
|
||||
-- AddGroup <Groupname> <Adminstatus> <Privileges>
|
||||
local function AddGroup(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
local admin = tonumber(args[2])
|
||||
if not args[1] or not admin then FAdmin.Messages.SendMessage(ply, 5, "Incorrect arguments!") return false end
|
||||
local privs = {}
|
||||
|
||||
for priv, am in SortedPairs(FAdmin.Access.Privileges) do
|
||||
-- The user cannot create groups with privileges they don't have
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, priv) then continue end
|
||||
if am <= admin + 1 then privs[priv] = true end
|
||||
end
|
||||
|
||||
local immunity = FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity
|
||||
|
||||
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
|
||||
|
||||
if (not plyGroup or immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to create usergroups with a higher rank than your own")
|
||||
return false
|
||||
end
|
||||
|
||||
FAdmin.Access.AddGroup(args[1], admin, privs, immunity) -- Add new group
|
||||
FAdmin.Messages.SendMessage(ply, 4, "Group created")
|
||||
FAdmin.Access.SendGroups()
|
||||
|
||||
return true, args[1]
|
||||
end
|
||||
|
||||
local function AddPrivilege(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManagePrivileges") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
|
||||
local group, priv = args[1], args[2]
|
||||
|
||||
if not FAdmin.Access.Groups[group] or not FAdmin.Access.Privileges[priv] then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "Invalid arguments")
|
||||
return false
|
||||
end
|
||||
|
||||
-- The player cannot add privileges that they themselves do not have
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, priv) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign privileges that you don't have yourself")
|
||||
return false
|
||||
end
|
||||
|
||||
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
|
||||
|
||||
-- Setting a group with a higher rank than one's own
|
||||
if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to manage the privileges of a usergroup with a higher rank than your own")
|
||||
return false
|
||||
end
|
||||
|
||||
FAdmin.Access.Groups[group].PRIVS[priv] = true
|
||||
|
||||
MySQLite.query("REPLACE INTO FADMIN_PRIVILEGES VALUES(" .. MySQLite.SQLStr(group) .. ", " .. MySQLite.SQLStr(priv) .. ");")
|
||||
SendUserMessage("FAdmin_AddPriv", player.GetAll(), group, priv)
|
||||
FAdmin.Messages.SendMessage(ply, 4, "Privilege Added!")
|
||||
|
||||
return true, group, priv
|
||||
end
|
||||
|
||||
local function RemovePrivilege(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManagePrivileges") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
|
||||
local group, priv = args[1], args[2]
|
||||
if not FAdmin.Access.Groups[group] or not FAdmin.Access.Privileges[priv] then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "Invalid arguments")
|
||||
return false
|
||||
end
|
||||
|
||||
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
|
||||
|
||||
-- Setting a group with a higher rank than one's own
|
||||
if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to manage the privileges of a usergroup with a higher rank than your own")
|
||||
return false
|
||||
end
|
||||
|
||||
FAdmin.Access.Groups[group].PRIVS[priv] = nil
|
||||
|
||||
MySQLite.query("DELETE FROM FADMIN_PRIVILEGES WHERE NAME = " .. MySQLite.SQLStr(group) .. " AND PRIVILEGE = " .. MySQLite.SQLStr(priv) .. ";")
|
||||
SendUserMessage("FAdmin_RemovePriv", player.GetAll(), group, priv)
|
||||
FAdmin.Messages.SendMessage(ply, 4, "Privilege Removed!")
|
||||
|
||||
return true, group, priv
|
||||
end
|
||||
|
||||
function FAdmin.Access.SendGroups(ply)
|
||||
if not FAdmin.Access.Groups then return end
|
||||
|
||||
net.Start("FADMIN_SendGroups")
|
||||
net.WriteTable(FAdmin.Access.Groups)
|
||||
net.Send(IsValid(ply) and ply or player.GetAll())
|
||||
end
|
||||
|
||||
-- FAdmin SetAccess <player> <groupname> [new_groupadmin, new_groupprivs]
|
||||
function FAdmin.Access.SetAccess(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "SetAccess") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
|
||||
local targets = FAdmin.FindPlayer(args[1])
|
||||
local admin = tonumber(args[3])
|
||||
local group = FAdmin.Access.Groups[args[2]]
|
||||
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
|
||||
|
||||
if not args[2] or not group and not admin then
|
||||
FAdmin.Messages.SendMessage(ply, 1, "Group not found")
|
||||
return false
|
||||
elseif args[2] and not group and admin then
|
||||
local privs = {}
|
||||
for priv, am in SortedPairs(FAdmin.Access.Privileges) do
|
||||
if am <= admin + 1 then privs[priv] = true end
|
||||
end
|
||||
|
||||
local immunity = FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity
|
||||
-- Creating and setting a group with a higher rank than one's own
|
||||
if (not plyGroup or immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own")
|
||||
return false
|
||||
end
|
||||
|
||||
FAdmin.Access.AddGroup(args[2], tonumber(args[3]), privs, immunity) -- Add new group
|
||||
FAdmin.Messages.SendMessage(ply, 4, "Group created")
|
||||
FAdmin.Access.SendGroups()
|
||||
end
|
||||
|
||||
-- Setting a group with a higher rank than one's own
|
||||
if group and (not plyGroup or group.immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own")
|
||||
return false
|
||||
end
|
||||
|
||||
if not targets and (string.find(args[1], "^STEAM_[0-9]:[01]:[0-9]+$") or args[1] == "BOT" or (string.find(args[1], "STEAM_") and #args == 6)) then
|
||||
local target, groupname = args[1], args[2]
|
||||
-- The console splits arguments on colons. Very annoying.
|
||||
if args[1] == "STEAM_0" then
|
||||
target = table.concat(args, "", 1, 5)
|
||||
groupname = args[6]
|
||||
end
|
||||
FAdmin.Access.PlayerSetGroup(target, groupname)
|
||||
|
||||
MySQLite.queryValue(string.format("SELECT groupname FROM FAdmin_PlayerGroup WHERE steamid = %s", MySQLite.SQLStr(target)), function(val)
|
||||
CAMI.SignalSteamIDUserGroupChanged(target, val or "user", groupname, "FAdmin")
|
||||
end)
|
||||
FAdmin.Messages.SendMessage(ply, 4, "User access set!")
|
||||
return true, target, groupname
|
||||
elseif not targets then
|
||||
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
|
||||
return false
|
||||
end
|
||||
|
||||
for _, target in ipairs(targets) do
|
||||
if not IsValid(target) then continue end
|
||||
|
||||
local target_previous_group = target:GetUserGroup()
|
||||
FAdmin.Access.PlayerSetGroup(target, args[2])
|
||||
|
||||
-- An end user changed the usergroup. Register with CAMI
|
||||
CAMI.SignalUserGroupChanged(target, target_previous_group, args[2], "FAdmin")
|
||||
end
|
||||
|
||||
FAdmin.Messages.SendMessage(ply, 4, "User access set!")
|
||||
FAdmin.Messages.FireNotification("setaccess", ply, targets, {args[2]})
|
||||
return true, targets, args[2]
|
||||
end
|
||||
|
||||
--hooks and stuff
|
||||
|
||||
hook.Add("PlayerInitialSpawn", "FAdmin_SetAccess", function(ply)
|
||||
MySQLite.queryValue("SELECT groupname FROM FAdmin_PlayerGroup WHERE steamid = " .. MySQLite.SQLStr(ply:SteamID()) .. ";", function(Group)
|
||||
if not Group then return end
|
||||
ply:SetUserGroup(Group)
|
||||
|
||||
if FAdmin.Access.Groups[Group] then
|
||||
ply:FAdmin_SetGlobal("FAdmin_admin", FAdmin.Access.Groups[Group].ADMIN_ACCESS)
|
||||
end
|
||||
end, function(err) ErrorNoHalt(err) MsgN() end)
|
||||
FAdmin.Access.SendGroups(ply)
|
||||
end)
|
||||
|
||||
local function toggleImmunity(ply, cmd, args)
|
||||
-- ManageGroups privilege because they can handle immunity settings
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
|
||||
if not args[1] then FAdmin.Messages.SendMessage(ply, 5, "Invalid argument!") return false end
|
||||
RunConsoleCommand("_FAdmin_immunity", args[1])
|
||||
local OnOff = (tonumber(args[1]) == 1 and "on") or "off"
|
||||
FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned " .. OnOff .. " admin immunity!", "Admin immunity has been turned " .. OnOff, "Turned admin immunity " .. OnOff)
|
||||
|
||||
return true, OnOff
|
||||
end
|
||||
|
||||
|
||||
local function setImmunity(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
local group, immunity = args[1], tonumber(args[2])
|
||||
|
||||
if not FAdmin.Access.Groups[group] or not immunity then return false end
|
||||
|
||||
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
|
||||
|
||||
-- Setting a group with a higher rank than one's own
|
||||
if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to change the immunity of a group with a higher rank than your")
|
||||
return false
|
||||
end
|
||||
|
||||
if immunity > plyGroup.immunity and not FAdmin.Access.PlayerIsHost(ply) then
|
||||
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to set the immunity to any value higher than your own group's immunity")
|
||||
return false
|
||||
end
|
||||
|
||||
FAdmin.Access.Groups[group].immunity = immunity
|
||||
MySQLite.query("REPLACE INTO FAdmin_Immunity VALUES(" .. MySQLite.SQLStr(group) .. ", " .. tonumber(immunity) .. ");")
|
||||
|
||||
FAdmin.Access.SendGroups(ply)
|
||||
|
||||
return true, group, immunity
|
||||
end
|
||||
|
||||
FAdmin.StartHooks["Access"] = function() --Run all functions that depend on other plugins
|
||||
FAdmin.Commands.AddCommand("setroot", FAdmin.Access.SetRoot)
|
||||
FAdmin.Commands.AddCommand("setaccess", FAdmin.Access.SetAccess)
|
||||
|
||||
FAdmin.Commands.AddCommand("AddGroup", AddGroup)
|
||||
|
||||
FAdmin.Commands.AddCommand("AddPrivilege", AddPrivilege)
|
||||
FAdmin.Commands.AddCommand("RemovePrivilege", RemovePrivilege)
|
||||
|
||||
FAdmin.Commands.AddCommand("immunity", toggleImmunity)
|
||||
FAdmin.Commands.AddCommand("SetImmunity", setImmunity)
|
||||
|
||||
FAdmin.SetGlobalSetting("Immunity", GetConVar("_FAdmin_immunity"):GetBool())
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
net.Receive("FAdmin_ReceiveAdminMessage", function(len)
|
||||
local FromPly = net.ReadEntity()
|
||||
local Text = net.ReadString()
|
||||
local Team = FromPly:IsPlayer() and FromPly:Team() or 1
|
||||
local Nick = FromPly:IsPlayer() and FromPly:Nick() or "Console"
|
||||
local prefix = (FAdmin.Access.PlayerHasPrivilege(FromPly, "AdminChat") or FromPly:IsAdmin()) and "[Admin Chat] " or "[To admins] "
|
||||
|
||||
chat.AddNonParsedText(Color(255, 0, 0, 255), prefix, team.GetColor(Team), Nick .. ": ", color_white, Text)
|
||||
end)
|
||||
|
||||
FAdmin.StartHooks["Chatting"] = function()
|
||||
FAdmin.Commands.AddCommand("adminhelp", nil, "<text>")
|
||||
FAdmin.Commands.AddCommand("//", nil, "<text>")
|
||||
|
||||
FAdmin.Access.AddPrivilege("AdminChat", 2)
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
util.AddNetworkString("FAdmin_ReceiveAdminMessage")
|
||||
local function ToAdmins(ply, cmd, args)
|
||||
if not args[1] then return false end
|
||||
|
||||
local text = table.concat(args, " ")
|
||||
local send = {}
|
||||
|
||||
if IsValid(ply) then table.insert(send, ply) end
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if FAdmin.Access.PlayerHasPrivilege(v, "AdminChat") or v:IsAdmin() then
|
||||
table.insert(send, v)
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("FAdmin_ReceiveAdminMessage")
|
||||
net.WriteEntity(ply)
|
||||
net.WriteString(text)
|
||||
net.Send(send)
|
||||
|
||||
return true, text
|
||||
end
|
||||
|
||||
FAdmin.StartHooks["Chatting"] = function()
|
||||
FAdmin.Commands.AddCommand("adminhelp", ToAdmins)
|
||||
FAdmin.Commands.AddCommand("//", ToAdmins)
|
||||
|
||||
FAdmin.Access.AddPrivilege("AdminChat", 2)
|
||||
end
|
||||
@@ -0,0 +1,87 @@
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "gamemodeList", "GamemodeList")
|
||||
AccessorFunc(PANEL, "mapList", "MapList")
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetMouseInputEnabled(true)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
|
||||
self:SetDeleteOnClose(false)
|
||||
|
||||
self:SetTitle("Change level")
|
||||
self:SetSize(630, ScrH() * 0.8)
|
||||
|
||||
self.gamemodeList = {}
|
||||
self.mapList = {}
|
||||
|
||||
self.catList = vgui.Create("DCategoryList", self)
|
||||
self.catList:Dock(FILL)
|
||||
|
||||
self.topPanel = vgui.Create("DPanel", self)
|
||||
self.topPanel:SetPaintBackground(false)
|
||||
self.topPanel:DockMargin(0, 0, 0, 4)
|
||||
self.topPanel:Dock(TOP)
|
||||
self.gmLabel = vgui.Create("DLabel", self.topPanel)
|
||||
self.gmLabel:SetText("Gamemode:")
|
||||
self.gmLabel:Dock(LEFT)
|
||||
self.gmComboBox = vgui.Create("DComboBox", self.topPanel)
|
||||
self.gmComboBox:Dock(FILL)
|
||||
self.gmComboBox:SetValue("(current)")
|
||||
|
||||
self.bottomPanel = vgui.Create("DPanel", self)
|
||||
self.bottomPanel:SetPaintBackground(false)
|
||||
self.bottomPanel:DockMargin(0, 4, 0, 0)
|
||||
self.bottomPanel:Dock(BOTTOM)
|
||||
self.changeButton = vgui.Create("DButton", self.bottomPanel)
|
||||
self.changeButton:SetText("Change level")
|
||||
self.changeButton:Dock(RIGHT)
|
||||
self.changeButton:SetWidth(100)
|
||||
self.changeButton:SetEnabled(false)
|
||||
self.changeButton.DoClick = function()
|
||||
if not IsValid(self.selectedIconPanel) then return end
|
||||
local _,gmName = self.gmComboBox:GetSelected()
|
||||
local mapName = self.selectedIconPanel:GetText()
|
||||
RunConsoleCommand("_FAdmin", "Changelevel", gmName and gmName or mapName, gmName and mapName)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Refresh()
|
||||
for _, gmInfo in ipairs(self:GetGamemodeList()) do
|
||||
self.gmComboBox:AddChoice(gmInfo.title, gmInfo.name)
|
||||
end
|
||||
self.gmComboBox:SetValue("(current)")
|
||||
|
||||
for catName, maps in pairs(self:GetMapList()) do
|
||||
local cat = self.catList:Add(catName)
|
||||
local iconLayout = vgui.Create("DIconLayout")
|
||||
iconLayout:SetSpaceX(5)
|
||||
iconLayout:SetSpaceY(5)
|
||||
for _, map in ipairs(maps) do
|
||||
local icon = iconLayout:Add("FAdmin_MapIcon")
|
||||
icon:SetText(map)
|
||||
icon:SetDark(true)
|
||||
local mat = Material("maps/thumb/" .. map .. ".png")
|
||||
if mat:IsError() then mat = Material("maps/thumb/noicon.png") end
|
||||
icon:SetMaterial(mat)
|
||||
local onToggled = icon.OnToggled
|
||||
icon.OnToggled = function(iconSelf, selected)
|
||||
onToggled(iconSelf, selected)
|
||||
if IsValid(self.selectedIconPanel) then
|
||||
if selected and self.selectedIconPanel ~= iconSelf then
|
||||
self.selectedIconPanel:Toggle()
|
||||
elseif not selected and self.selectedIconPanel == iconSelf then
|
||||
self.selectedIconPanel = nil
|
||||
self.changeButton:SetEnabled(false)
|
||||
return
|
||||
end
|
||||
end
|
||||
self.selectedIconPanel = iconSelf
|
||||
self.changeButton:SetEnabled(true)
|
||||
end
|
||||
end
|
||||
cat:SetContents(iconLayout)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("FAdmin_Changelevel", PANEL, "DFrame")
|
||||
@@ -0,0 +1,46 @@
|
||||
local mapList = {}
|
||||
local gamemodeList = {}
|
||||
net.Receive("FAdmin_ChangelevelInfo", function(len)
|
||||
mapList = {}
|
||||
local mapLen = net.ReadUInt(16)
|
||||
|
||||
for i = 1, mapLen, 1 do
|
||||
local cat = net.ReadString()
|
||||
mapList[cat] = {}
|
||||
local catLen = net.ReadUInt(16)
|
||||
|
||||
for j = 1, catLen, 1 do
|
||||
mapList[cat][j] = net.ReadString()
|
||||
end
|
||||
end
|
||||
|
||||
gamemodeList = {}
|
||||
local gmLen = net.ReadUInt(16)
|
||||
|
||||
for i = 1, gmLen, 1 do
|
||||
gamemodeList[i] = {
|
||||
name = net.ReadString(),
|
||||
title = net.ReadString()
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
||||
local Changelevel
|
||||
FAdmin.StartHooks["ChangeLevel"] = function()
|
||||
FAdmin.Access.AddPrivilege("changelevel", 2)
|
||||
FAdmin.Commands.AddCommand("changelevel", "[gamemode]", "<map>")
|
||||
|
||||
FAdmin.ScoreBoard.Server:AddServerAction("Changelevel", "icon16/world.png", Color(155, 0, 0, 255), function() return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "changelevel") end,
|
||||
function(ply, button)
|
||||
local refresh = not Changelevel or table.Count(Changelevel:GetMapList()) ~= table.Count(mapList)
|
||||
Changelevel = Changelevel or vgui.Create("FAdmin_Changelevel")
|
||||
if refresh then
|
||||
Changelevel:SetGamemodeList(gamemodeList)
|
||||
Changelevel:SetMapList(mapList)
|
||||
Changelevel:Refresh()
|
||||
end
|
||||
Changelevel:SetVisible(true)
|
||||
Changelevel:Center()
|
||||
Changelevel:MakePopup()
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.BaseClass.Init(self)
|
||||
self:SetPaintBackground(true)
|
||||
self:SetIsToggle(true)
|
||||
self:SetSize(96, 110)
|
||||
end
|
||||
|
||||
function PANEL:Paint(w, h)
|
||||
if self.m_bToggle then
|
||||
surface.SetDrawColor(255, 155, 20, 255)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function PANEL:UpdateColours(skin)
|
||||
return self:SetTextStyleColor(skin.Colours.Button.Normal)
|
||||
end
|
||||
|
||||
function PANEL:OnToggled(selected)
|
||||
self:InvalidateLayout(true)
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed(code)
|
||||
DButton.OnMousePressed(self, code)
|
||||
end
|
||||
|
||||
function PANEL:OnMouseReleased(code)
|
||||
DButton.OnMouseReleased(self, code)
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout()
|
||||
self.m_Image:SetPos(0, 0)
|
||||
local w,h = self:GetSize()
|
||||
h = h - 14
|
||||
self.m_Image:SetSize(w, h)
|
||||
self:SetTextInset(5, w / 2)
|
||||
DLabel.PerformLayout(self)
|
||||
end
|
||||
|
||||
vgui.Register("FAdmin_MapIcon", PANEL, "DImageButton")
|
||||
@@ -0,0 +1,266 @@
|
||||
util.AddNetworkString("FAdmin_ChangelevelInfo")
|
||||
|
||||
local function ChangeLevel(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "changelevel") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
|
||||
local map = args[2] or args[1] -- Changelevel gamemode map OR changelevel map
|
||||
local gameMode = args[2] and args[1]
|
||||
|
||||
if gameMode then
|
||||
RunConsoleCommand("gamemode", gameMode)
|
||||
end
|
||||
|
||||
RunConsoleCommand("changelevel", map)
|
||||
|
||||
return true, map, gameMode
|
||||
end
|
||||
|
||||
local mapNames = {}
|
||||
local mapPatterns = {}
|
||||
|
||||
local ignorePatterns = {
|
||||
"^asi-", "^background", "^c[%d]m", "^devtest", "^ep1_background", "^ep2_background", "^mp_coop_", "^sp_a", "^styleguide"
|
||||
}
|
||||
|
||||
local ignoreMaps = {
|
||||
-- Prefixes
|
||||
["ddd_"] = true,
|
||||
["sdk_"] = true,
|
||||
["test_"] = true,
|
||||
["vst_"] = true,
|
||||
-- Maps
|
||||
["c4a1y"] = true,
|
||||
["cp_docks"] = true,
|
||||
["cp_parkour"] = true,
|
||||
["cp_sequence"] = true,
|
||||
["cp_terrace"] = true,
|
||||
["cp_test"] = true,
|
||||
["credits"] = true,
|
||||
["curling_stadium"] = true,
|
||||
["d2_coast_02"] = true,
|
||||
["d3_c17_02_camera"] = true,
|
||||
["duel_"] = true,
|
||||
["e1912"] = true,
|
||||
["ep1_citadel_00_demo"] = true,
|
||||
["ffa_community"] = true,
|
||||
["free_"] = true,
|
||||
["intro"] = true,
|
||||
["lobby"] = true,
|
||||
["practice_box"] = true,
|
||||
["test"] = true,
|
||||
["tut_training"] = true,
|
||||
["tutorial_standards"] = true,
|
||||
["tutorial_standards_vs"] = true
|
||||
}
|
||||
|
||||
hook.Add("PlayerInitialSpawn", "FAdmin_ChangelevelInfo", function(ply)
|
||||
local mapList = {}
|
||||
local maps = file.Find("maps/*.bsp", "GAME")
|
||||
|
||||
for _, v in ipairs(maps) do
|
||||
local name = string.lower(string.gsub(v, "%.bsp$", ""))
|
||||
if ignoreMaps[name] then continue end
|
||||
|
||||
local prefix = string.match(name, "^(.-_)")
|
||||
if ignoreMaps[prefix] then continue end
|
||||
|
||||
for _, ignore in ipairs(ignorePatterns) do
|
||||
if string.find(name, ignore) then
|
||||
goto mapContinue
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if the map has a simple name or prefix
|
||||
local mapCategory = mapNames[name] or mapNames[prefix]
|
||||
|
||||
-- Check if the map has an embedded prefix, or is TTT/Sandbox
|
||||
if not mapCategory then
|
||||
for pattern, category in pairs(mapPatterns) do
|
||||
if string.find(name, pattern) then
|
||||
mapCategory = category
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Throw all uncategorized maps into Other
|
||||
mapCategory = mapCategory or "Other"
|
||||
-- Don't show CS:GO maps
|
||||
if mapCategory == "Counter-Strike" and not file.Exists("maps/" .. name .. ".bsp", "cstrike") then
|
||||
continue
|
||||
end
|
||||
|
||||
if not mapList[mapCategory] then
|
||||
mapList[mapCategory] = {}
|
||||
end
|
||||
|
||||
table.insert(mapList[mapCategory], name)
|
||||
::mapContinue::
|
||||
end
|
||||
|
||||
local gamemodeList = engine.GetGamemodes()
|
||||
net.Start("FAdmin_ChangelevelInfo")
|
||||
net.WriteUInt(table.Count(mapList), 16) -- 65536 should be enough
|
||||
for cat, mps in pairs(mapList) do
|
||||
net.WriteString(cat)
|
||||
net.WriteUInt(#mps, 16)
|
||||
for _, map in pairs(mps) do
|
||||
net.WriteString(map)
|
||||
end
|
||||
end
|
||||
net.WriteUInt(#gamemodeList, 16)
|
||||
for _, gmInfo in ipairs(gamemodeList) do
|
||||
net.WriteString(gmInfo.name)
|
||||
net.WriteString(gmInfo.title)
|
||||
end
|
||||
net.Send(ply)
|
||||
end)
|
||||
|
||||
FAdmin.StartHooks["ChangeLevel"] = function()
|
||||
FAdmin.Commands.AddCommand("changelevel", ChangeLevel)
|
||||
|
||||
FAdmin.Access.AddPrivilege("changelevel", 2)
|
||||
|
||||
mapNames = {}
|
||||
mapPatterns = {}
|
||||
|
||||
mapNames["aoc_"] = "Age of Chivalry"
|
||||
|
||||
mapNames["ar_"] = "Counter-Strike"
|
||||
mapNames["cs_"] = "Counter-Strike"
|
||||
mapNames["de_"] = "Counter-Strike"
|
||||
mapNames["es_"] = "Counter-Strike"
|
||||
mapNames["fy_"] = "Counter-Strike"
|
||||
mapNames["gd_"] = "Counter-Strike"
|
||||
mapNames["training1"] = "Counter-Strike"
|
||||
|
||||
mapNames["dod_"] = "Day Of Defeat"
|
||||
|
||||
mapNames["de_dam"] = "DIPRIP"
|
||||
mapNames["dm_city"] = "DIPRIP"
|
||||
mapNames["dm_refinery"] = "DIPRIP"
|
||||
mapNames["dm_supermarket"] = "DIPRIP"
|
||||
mapNames["dm_village"] = "DIPRIP"
|
||||
mapNames["ur_city"] = "DIPRIP"
|
||||
mapNames["ur_refinery"] = "DIPRIP"
|
||||
mapNames["ur_supermarket"] = "DIPRIP"
|
||||
mapNames["ur_village"] = "DIPRIP"
|
||||
|
||||
mapNames["dys_"] = "Dystopia"
|
||||
mapNames["pb_dojo"] = "Dystopia"
|
||||
mapNames["pb_rooftop"] = "Dystopia"
|
||||
mapNames["pb_round"] = "Dystopia"
|
||||
mapNames["pb_urbandome"] = "Dystopia"
|
||||
mapNames["sav_dojo6"] = "Dystopia"
|
||||
mapNames["varena"] = "Dystopia"
|
||||
|
||||
mapNames["d1_"] = "Half-Life 2"
|
||||
mapNames["d2_"] = "Half-Life 2"
|
||||
mapNames["d3_"] = "Half-Life 2"
|
||||
|
||||
mapNames["dm_"] = "Half-Life 2: Deathmatch"
|
||||
mapNames["halls3"] = "Half-Life 2: Deathmatch"
|
||||
|
||||
mapNames["ep1_"] = "Half-Life 2: Episode 1"
|
||||
mapNames["ep2_"] = "Half-Life 2: Episode 2"
|
||||
mapNames["ep3_"] = "Half-Life 2: Episode 3"
|
||||
|
||||
mapNames["d2_lostcoast"] = "Half-Life 2: Lost Coast"
|
||||
|
||||
mapPatterns["^c[%d]a"] = "Half-Life"
|
||||
mapPatterns["^t0a"] = "Half-Life"
|
||||
|
||||
mapNames["boot_camp"] = "Half-Life Deathmatch"
|
||||
mapNames["bounce"] = "Half-Life Deathmatch"
|
||||
mapNames["crossfire"] = "Half-Life Deathmatch"
|
||||
mapNames["datacore"] = "Half-Life Deathmatch"
|
||||
mapNames["frenzy"] = "Half-Life Deathmatch"
|
||||
mapNames["lambda_bunker"] = "Half-Life Deathmatch"
|
||||
mapNames["rapidcore"] = "Half-Life Deathmatch"
|
||||
mapNames["snarkpit"] = "Half-Life Deathmatch"
|
||||
mapNames["stalkyard"] = "Half-Life Deathmatch"
|
||||
mapNames["subtransit"] = "Half-Life Deathmatch"
|
||||
mapNames["undertow"] = "Half-Life Deathmatch"
|
||||
|
||||
mapNames["ins_"] = "Insurgency"
|
||||
|
||||
mapNames["l4d_"] = "Left 4 Dead"
|
||||
|
||||
mapNames["clocktower"] = "Nuclear Dawn"
|
||||
mapNames["coast"] = "Nuclear Dawn"
|
||||
mapNames["downtown"] = "Nuclear Dawn"
|
||||
mapNames["gate"] = "Nuclear Dawn"
|
||||
mapNames["hydro"] = "Nuclear Dawn"
|
||||
mapNames["metro"] = "Nuclear Dawn"
|
||||
mapNames["metro_training"] = "Nuclear Dawn"
|
||||
mapNames["oasis"] = "Nuclear Dawn"
|
||||
mapNames["oilfield"] = "Nuclear Dawn"
|
||||
mapNames["silo"] = "Nuclear Dawn"
|
||||
mapNames["sk_metro"] = "Nuclear Dawn"
|
||||
mapNames["training"] = "Nuclear Dawn"
|
||||
|
||||
mapNames["bt_"] = "Pirates, Vikings, & Knights II"
|
||||
mapNames["lts_"] = "Pirates, Vikings, & Knights II"
|
||||
mapNames["te_"] = "Pirates, Vikings, & Knights II"
|
||||
mapNames["tw_"] = "Pirates, Vikings, & Knights II"
|
||||
|
||||
mapNames["escape_"] = "Portal"
|
||||
mapNames["testchmb_"] = "Portal"
|
||||
|
||||
mapNames["achievement_"] = "Team Fortress 2"
|
||||
mapNames["arena_"] = "Team Fortress 2"
|
||||
mapNames["cp_"] = "Team Fortress 2"
|
||||
mapNames["ctf_"] = "Team Fortress 2"
|
||||
mapNames["itemtest"] = "Team Fortress 2"
|
||||
mapNames["koth_"] = "Team Fortress 2"
|
||||
mapNames["mvm_"] = "Team Fortress 2"
|
||||
mapNames["pl_"] = "Team Fortress 2"
|
||||
mapNames["plr_"] = "Team Fortress 2"
|
||||
mapNames["rd_"] = "Team Fortress 2"
|
||||
mapNames["pd_"] = "Team Fortress 2"
|
||||
mapNames["sd_"] = "Team Fortress 2"
|
||||
mapNames["tc_"] = "Team Fortress 2"
|
||||
mapNames["tr_"] = "Team Fortress 2"
|
||||
mapNames["trade_"] = "Team Fortress 2"
|
||||
mapNames["pass_"] = "Team Fortress 2"
|
||||
|
||||
mapNames["zpa_"] = "Zombie Panic! Source"
|
||||
mapNames["zpl_"] = "Zombie Panic! Source"
|
||||
mapNames["zpo_"] = "Zombie Panic! Source"
|
||||
mapNames["zps_"] = "Zombie Panic! Source"
|
||||
|
||||
mapNames["bhop_"] = "Bunny Hop"
|
||||
mapNames["cinema_"] = "Cinema"
|
||||
mapNames["theater_"] = "Cinema"
|
||||
mapNames["xc_"] = "Climb"
|
||||
mapNames["deathrun_"] = "Deathrun"
|
||||
mapNames["dr_"] = "Deathrun"
|
||||
mapNames["fm_"] = "Flood"
|
||||
mapNames["gmt_"] = "GMod Tower"
|
||||
mapNames["gg_"] = "Gun Game"
|
||||
mapNames["scoutzknivez"] = "Gun Game"
|
||||
mapNames["ba_"] = "Jailbreak"
|
||||
mapNames["jail_"] = "Jailbreak"
|
||||
mapNames["jb_"] = "Jailbreak"
|
||||
mapNames["mg_"] = "Minigames"
|
||||
mapNames["pw_"] = "Pirate Ship Wars"
|
||||
mapNames["ph_"] = "Prop Hunt"
|
||||
mapNames["rp_"] = "Roleplay"
|
||||
mapNames["slb_"] = "Sled Build"
|
||||
mapNames["sb_"] = "Spacebuild"
|
||||
mapNames["slender_"] = "Stop it Slender"
|
||||
mapNames["gms_"] = "Stranded"
|
||||
mapNames["surf_"] = "Surf"
|
||||
mapNames["ts_"] = "The Stalker"
|
||||
mapNames["zm_"] = "Zombie Survival"
|
||||
mapNames["zombiesurvival_"] = "Zombie Survival"
|
||||
mapNames["zs_"] = "Zombie Survival"
|
||||
|
||||
for _, gm in ipairs(engine.GetGamemodes()) do
|
||||
if gm.maps ~= "" then
|
||||
for _, pattern in ipairs(string.Split(gm.maps, "|")) do
|
||||
-- When in doubt, just try to match it with string.find later
|
||||
mapPatterns[string.lower(pattern)] = gm.title or "Unnammed Gamemode"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
local function SortedPairsByFunction(Table, Sorted, SortDown)
|
||||
local CopyTable = {}
|
||||
for _, v in pairs(Table) do
|
||||
table.insert(CopyTable, {NAME = tostring(v:Nick()), PLY = v})
|
||||
end
|
||||
table.SortByMember(CopyTable, "NAME", SortDown)
|
||||
|
||||
local SortedTable = {}
|
||||
for _, v in ipairs(CopyTable) do
|
||||
if not IsValid(v.PLY) or not v.PLY[Sorted] then continue end
|
||||
local SortBy = (Sorted ~= "Team" and v.PLY[Sorted](v.PLY)) or (v.PLY:getDarkRPVar("job") or team.GetName(v.PLY[Sorted](v.PLY)))
|
||||
SortedTable[SortBy] = SortedTable[SortBy] or {}
|
||||
table.insert(SortedTable[SortBy], v.PLY)
|
||||
end
|
||||
|
||||
local SecondSort = {}
|
||||
for _, v in SortedPairs(SortedTable, SortDown) do
|
||||
table.insert(SecondSort, v)
|
||||
end
|
||||
|
||||
CopyTable = {}
|
||||
for _, v in ipairs(SecondSort) do
|
||||
for _, b in pairs(v) do
|
||||
table.insert(CopyTable, b)
|
||||
end
|
||||
end
|
||||
|
||||
return ipairs(CopyTable)
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Main.PlayerListView(Sorted, SortDown)
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true)
|
||||
for _, ply in SortedPairsByFunction(player.GetAll(), Sorted, SortDown) do
|
||||
local Row = vgui.Create("FadminPlayerRow")
|
||||
Row:SetPlayer(ply)
|
||||
Row:Dock(TOP)
|
||||
Row:InvalidateLayout()
|
||||
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:AddItem(Row)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,308 @@
|
||||
FAdmin.PlayerIcon = {}
|
||||
FAdmin.PlayerIcon.RightClickOptions = {}
|
||||
|
||||
function FAdmin.PlayerIcon.AddRightClickOption(name, func)
|
||||
FAdmin.PlayerIcon.RightClickOptions[name] = func
|
||||
end
|
||||
|
||||
-- FAdminPanelList
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.Padding = 5
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
local w, h = self:GetSize()
|
||||
|
||||
-- Fix size of w to have the same size as the scoreboard
|
||||
w = math.Clamp(w, ScrW() * 0.9, ScrW() * 0.9)
|
||||
h = math.Min(h, ScrH() * 0.95)
|
||||
|
||||
-- It fucks up when there's only one icon in
|
||||
if #self:GetChildren() == 1 then
|
||||
h = math.Max(0, 120)
|
||||
end
|
||||
|
||||
self:SetSize(w, h)
|
||||
self:PerformLayout()
|
||||
end
|
||||
|
||||
function PANEL:Paint()
|
||||
end
|
||||
|
||||
derma.DefineControl("FAdminPanelList", "DPanellist adapted for FAdmin", PANEL, "DPanelList")
|
||||
|
||||
-- FAdminPlayerCatagoryHeader
|
||||
local PANEL2 = {}
|
||||
|
||||
function PANEL2:PerformLayout()
|
||||
self:SetFont("Trebuchet24")
|
||||
end
|
||||
|
||||
derma.DefineControl("FAdminPlayerCatagoryHeader", "DCatagoryCollapse header adapted for FAdmin", PANEL2, "DCategoryHeader")
|
||||
|
||||
-- FAdminPlayerCatagory
|
||||
local PANEL3 = {}
|
||||
|
||||
function PANEL3:Init()
|
||||
if self.Header then
|
||||
self.Header:Remove() -- the old header is still there don't ask me why
|
||||
end
|
||||
self.Header = vgui.Create("FAdminPlayerCatagoryHeader", self)
|
||||
self.Header:SetSize(20, 25)
|
||||
self:SetPadding(5)
|
||||
self.Header:Dock(TOP)
|
||||
|
||||
self:SetExpanded(true)
|
||||
self:SetMouseInputEnabled(true)
|
||||
|
||||
self:SetAnimTime(0.2)
|
||||
self.animSlide = Derma_Anim("Anim", self, self.AnimSlide)
|
||||
|
||||
self:SetPaintBackgroundEnabled(true)
|
||||
|
||||
end
|
||||
|
||||
function PANEL3:Paint()
|
||||
if self.CatagoryColor then
|
||||
draw.RoundedBox(4, 0, 0, self:GetWide(), self.Header:GetTall(), self.CatagoryColor)
|
||||
end
|
||||
end
|
||||
|
||||
derma.DefineControl("FAdminPlayerCatagory", "DCatagoryCollapse adapted for FAdmin", PANEL3, "DCollapsibleCategory")
|
||||
|
||||
-- FAdmin player row (from the sandbox player row)
|
||||
PANEL = {}
|
||||
|
||||
local PlayerRowSize = CreateClientConVar("FAdmin_PlayerRowSize", 30, true, false)
|
||||
function PANEL:Init()
|
||||
self.Size = PlayerRowSize:GetInt()
|
||||
|
||||
self.lblName = vgui.Create("DLabel", self)
|
||||
self.lblFrags = vgui.Create("DLabel", self)
|
||||
self.lblTeam = vgui.Create("DLabel", self)
|
||||
self.lblDeaths = vgui.Create("DLabel", self)
|
||||
self.lblPing = vgui.Create("DLabel", self)
|
||||
self.lblWanted = vgui.Create("DLabel", self)
|
||||
|
||||
-- If you don't do this it'll block your clicks
|
||||
self.lblName:SetMouseInputEnabled(false)
|
||||
self.lblTeam:SetMouseInputEnabled(false)
|
||||
self.lblFrags:SetMouseInputEnabled(false)
|
||||
self.lblDeaths:SetMouseInputEnabled(false)
|
||||
self.lblPing:SetMouseInputEnabled(false)
|
||||
self.lblWanted:SetMouseInputEnabled(false)
|
||||
|
||||
self.lblName:SetColor(Color(255,255,255,200))
|
||||
self.lblTeam:SetColor(Color(255,255,255,200))
|
||||
self.lblFrags:SetColor(Color(255,255,255,200))
|
||||
self.lblDeaths:SetColor(Color(255,255,255,200))
|
||||
self.lblPing:SetColor(Color(255,255,255,200))
|
||||
self.lblWanted:SetColor(Color(255,255,255,200))
|
||||
|
||||
self.imgAvatar = vgui.Create("AvatarImage", self)
|
||||
|
||||
self:SetCursor("hand")
|
||||
end
|
||||
|
||||
function PANEL:Paint()
|
||||
if not IsValid(self.Player) then return end
|
||||
|
||||
self.Size = PlayerRowSize:GetInt()
|
||||
self.imgAvatar:SetSize(self.Size - 4, self.Size - 4)
|
||||
|
||||
local color = Color(100, 150, 245, 255)
|
||||
|
||||
|
||||
if GAMEMODE.Name == "Sandbox" then
|
||||
color = Color(100, 150, 245, 255)
|
||||
if self.Player:Team() == TEAM_CONNECTING then
|
||||
color = Color(200, 120, 50, 255)
|
||||
elseif self.Player:IsAdmin() then
|
||||
color = Color(30, 200, 50, 255)
|
||||
end
|
||||
|
||||
if self.Player:GetFriendStatus() == "friend" then
|
||||
color = Color(236, 181, 113, 255)
|
||||
end
|
||||
else
|
||||
color = team.GetColor(self.Player:Team())
|
||||
end
|
||||
|
||||
local hooks = hook.GetTable().FAdmin_PlayerRowColour
|
||||
if hooks then
|
||||
for _, v in pairs(hooks) do
|
||||
color = (v and v(self.Player, color)) or color
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
draw.RoundedBox(4, 0, 0, self:GetWide(), self.Size, color)
|
||||
|
||||
surface.SetTexture(0)
|
||||
if self.Player == LocalPlayer() or self.Player:GetFriendStatus() == "friend" then
|
||||
surface.SetDrawColor(255, 255, 255, 50 + math.sin(RealTime() * 2) * 50)
|
||||
end
|
||||
surface.DrawTexturedRect(0, 0, self:GetWide(), self.Size)
|
||||
return true
|
||||
end
|
||||
|
||||
function PANEL:SetPlayer(ply)
|
||||
self.Player = ply
|
||||
|
||||
self.imgAvatar:SetPlayer(ply)
|
||||
|
||||
self:UpdatePlayerData()
|
||||
end
|
||||
|
||||
function PANEL:UpdatePlayerData()
|
||||
if not self.Player then return end
|
||||
if not self.Player:IsValid() then return end
|
||||
|
||||
self.lblName:SetText(DarkRP.deLocalise(self.Player:Nick()))
|
||||
self.lblTeam:SetText((self.Player.DarkRPVars and DarkRP.deLocalise(self.Player:getDarkRPVar("job") or "")) or team.GetName(self.Player:Team()))
|
||||
self.lblTeam:SizeToContents()
|
||||
self.lblFrags:SetText(self.Player:Frags())
|
||||
self.lblDeaths:SetText(self.Player:Deaths())
|
||||
self.lblPing:SetText(self.Player:Ping())
|
||||
self.lblWanted:SetText(self.Player:isWanted() and DarkRP.getPhrase("Wanted_text") or "")
|
||||
end
|
||||
|
||||
function PANEL:ApplySchemeSettings()
|
||||
self.lblName:SetFont("ScoreboardPlayerNameBig")
|
||||
self.lblTeam:SetFont("ScoreboardPlayerNameBig")
|
||||
self.lblFrags:SetFont("ScoreboardPlayerName")
|
||||
self.lblDeaths:SetFont("ScoreboardPlayerName")
|
||||
self.lblPing:SetFont("ScoreboardPlayerName")
|
||||
self.lblWanted:SetFont("ScoreboardPlayerNameBig")
|
||||
|
||||
self.lblName:SetFGColor(color_white)
|
||||
self.lblTeam:SetFGColor(color_white)
|
||||
self.lblFrags:SetFGColor(color_white)
|
||||
self.lblDeaths:SetFGColor(color_white)
|
||||
self.lblPing:SetFGColor(color_white)
|
||||
self.lblWanted:SetFGColor(color_white)
|
||||
end
|
||||
|
||||
function PANEL:DoClick(x, y)
|
||||
if not IsValid(self.Player) then self:Remove() return end
|
||||
FAdmin.ScoreBoard.ChangeView("Player", self.Player)
|
||||
end
|
||||
|
||||
function PANEL:DoRightClick()
|
||||
if table.IsEmpty(FAdmin.PlayerIcon.RightClickOptions) then return end
|
||||
local menu = DermaMenu()
|
||||
|
||||
menu:SetPos(gui.MouseX(), gui.MouseY())
|
||||
|
||||
for Name, func in SortedPairs(FAdmin.PlayerIcon.RightClickOptions) do
|
||||
menu:AddOption(Name, function() if IsValid(self.Player) then func(self.Player, self) end end)
|
||||
end
|
||||
|
||||
menu:Open()
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if not self.PlayerUpdate or self.PlayerUpdate < CurTime() then
|
||||
self.PlayerUpdate = CurTime() + 0.5
|
||||
self:UpdatePlayerData()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout()
|
||||
self.imgAvatar:SetPos(2, 2)
|
||||
self.imgAvatar:SetSize(32, 32)
|
||||
|
||||
self:SetSize(self:GetWide(), self.Size)
|
||||
|
||||
self.lblName:SizeToContents()
|
||||
self.lblName:SetPos(24, 2)
|
||||
self.lblName:MoveRightOf(self.imgAvatar, 8)
|
||||
|
||||
local COLUMN_SIZE = 75
|
||||
|
||||
self.lblPing:SetPos(self:GetWide() - COLUMN_SIZE * 0.4, 0)
|
||||
self.lblDeaths:SetPos(self:GetWide() - COLUMN_SIZE * 1.4, 0)
|
||||
self.lblFrags:SetPos(self:GetWide() - COLUMN_SIZE * 2.4, 0)
|
||||
|
||||
self.lblTeam:SetPos(self:GetWide() / 2 - (0.5 * self.lblTeam:GetWide()))
|
||||
|
||||
self.lblWanted:SizeToContents()
|
||||
self.lblWanted:SetPos(math.floor(self:GetWide() / 4), 2)
|
||||
end
|
||||
vgui.Register("FadminPlayerRow", PANEL, "Button")
|
||||
|
||||
-- FAdminActionButton
|
||||
local PANEL6 = {}
|
||||
|
||||
function PANEL6:Init()
|
||||
self:SetDrawBackground(false)
|
||||
self:SetDrawBorder(false)
|
||||
self:SetStretchToFit(false)
|
||||
self:SetSize(120, 40)
|
||||
|
||||
self.TextLabel = vgui.Create("DLabel", self)
|
||||
self.TextLabel:SetColor(Color(200,200,200,200))
|
||||
self.TextLabel:SetFont("Roboto20")
|
||||
|
||||
self.m_Image2 = vgui.Create("DImage", self)
|
||||
|
||||
self.BorderColor = Color(190,40,0,255)
|
||||
end
|
||||
|
||||
function PANEL6:SetText(text)
|
||||
self.TextLabel:SetText(text)
|
||||
self.TextLabel:SizeToContents()
|
||||
|
||||
self:SetWide(self.TextLabel:GetWide() + 44)
|
||||
end
|
||||
|
||||
function PANEL6:PerformLayout()
|
||||
self.m_Image:SetSize(32,32)
|
||||
self.m_Image:SetPos(4,4)
|
||||
|
||||
self.m_Image2:SetSize(32, 32)
|
||||
self.m_Image2:SetPos(4,4)
|
||||
|
||||
self.TextLabel:SetPos(38, 8)
|
||||
end
|
||||
|
||||
function PANEL6:SetImage2(Mat, bckp)
|
||||
self.m_Image2:SetImage(Mat, bckp)
|
||||
end
|
||||
|
||||
function PANEL6:SetBorderColor(Col)
|
||||
self.BorderColor = Col or Color(190,40,0,255)
|
||||
end
|
||||
|
||||
function PANEL6:Paint()
|
||||
local BorderColor = self.BorderColor
|
||||
if self.Hovered then
|
||||
BorderColor = Color(math.Min(BorderColor.r + 40, 255), math.Min(BorderColor.g + 40, 255), math.Min(BorderColor.b + 40, 255), BorderColor.a)
|
||||
end
|
||||
if self.Depressed then
|
||||
BorderColor = color_transparent
|
||||
end
|
||||
draw.RoundedBox(4, 0, 0, self:GetWide(), self:GetTall(), BorderColor)
|
||||
draw.RoundedBox(4, 2, 2, self:GetWide() - 4, self:GetTall() - 4, Color(40, 40, 40, 255))
|
||||
end
|
||||
|
||||
function PANEL6:OnMousePressed(mouse)
|
||||
if self:GetDisabled() then return end
|
||||
|
||||
self.m_Image:SetSize(24,24)
|
||||
self.m_Image:SetPos(8,8)
|
||||
self.Depressed = true
|
||||
end
|
||||
|
||||
function PANEL6:OnMouseReleased(mouse)
|
||||
if self:GetDisabled() then return end
|
||||
|
||||
self.m_Image:SetSize(32,32)
|
||||
self.m_Image:SetPos(4,4)
|
||||
self.Depressed = false
|
||||
self:DoClick()
|
||||
end
|
||||
|
||||
derma.DefineControl("FAdminActionButton", "Button for doing actions", PANEL6, "DImageButton")
|
||||
@@ -0,0 +1,146 @@
|
||||
local OverrideScoreboard = CreateClientConVar("FAdmin_OverrideScoreboard", 0, true, false) -- Set if it's a scoreboard or not
|
||||
|
||||
function FAdmin.ScoreBoard.ChangeView(newView, ...)
|
||||
if FAdmin.ScoreBoard.CurrentView == newView or not FAdmin.ScoreBoard.Visible then return end
|
||||
|
||||
for _, v in pairs(FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Controls) do
|
||||
v:SetVisible(false)
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.CurrentView = newView
|
||||
FAdmin.ScoreBoard[newView].Show(...)
|
||||
FAdmin.ScoreBoard.ChangeGmodLogo(FAdmin.ScoreBoard[newView].Logo)
|
||||
|
||||
FAdmin.ScoreBoard.Controls.BackButton = FAdmin.ScoreBoard.Controls.BackButton or vgui.Create("DButton")
|
||||
FAdmin.ScoreBoard.Controls.BackButton:SetVisible(true)
|
||||
FAdmin.ScoreBoard.Controls.BackButton:SetPos(FAdmin.ScoreBoard.X, FAdmin.ScoreBoard.Y)
|
||||
FAdmin.ScoreBoard.Controls.BackButton:SetText("")
|
||||
FAdmin.ScoreBoard.Controls.BackButton:SetTooltip("Click me to go back!")
|
||||
FAdmin.ScoreBoard.Controls.BackButton:SetCursor("hand")
|
||||
FAdmin.ScoreBoard.Controls.BackButton:SetSize(100,90)
|
||||
FAdmin.ScoreBoard.Controls.BackButton:SetZPos(999)
|
||||
|
||||
function FAdmin.ScoreBoard.Controls.BackButton:DoClick()
|
||||
FAdmin.ScoreBoard.ChangeView("Main")
|
||||
end
|
||||
FAdmin.ScoreBoard.Controls.BackButton.Paint = function() end
|
||||
end
|
||||
|
||||
--"fadmin/back", gui/gmod_tool
|
||||
local GmodLogo, TempGmodLogo, GmodLogoColor = surface.GetTextureID("gui/gmod_logo"), surface.GetTextureID("gui/gmod_logo"), color_white
|
||||
function FAdmin.ScoreBoard.ChangeGmodLogo(new)
|
||||
if surface.GetTextureID(new) == TempGmodLogo then return end
|
||||
TempGmodLogo = surface.GetTextureID(new)
|
||||
for i = 0, 0.5, 0.01 do
|
||||
timer.Simple(i, function() GmodLogoColor = Color(255,255,255,GmodLogoColor.a-5.1) end)
|
||||
end
|
||||
timer.Simple(0.5, function() GmodLogo = surface.GetTextureID(new) end)
|
||||
for i = 0.5, 1, 0.01 do
|
||||
timer.Simple(i, function()
|
||||
GmodLogoColor = Color(255, 255, 255, GmodLogoColor.a + 5.1)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Background()
|
||||
surface.SetDrawColor(0,0,0,200)
|
||||
surface.DrawRect(FAdmin.ScoreBoard.X, FAdmin.ScoreBoard.Y, FAdmin.ScoreBoard.Width, FAdmin.ScoreBoard.Height)
|
||||
|
||||
surface.SetTexture(GmodLogo)
|
||||
surface.SetDrawColor(255,255,255,GmodLogoColor.a)
|
||||
surface.DrawTexturedRect(FAdmin.ScoreBoard.X - 20, FAdmin.ScoreBoard.Y, 128, 128)
|
||||
end
|
||||
|
||||
|
||||
function FAdmin.ScoreBoard.DrawScoreBoard()
|
||||
if (input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE)) and not FAdmin.ScoreBoard.DontGoBack then
|
||||
FAdmin.ScoreBoard.ChangeView("Main")
|
||||
elseif FAdmin.ScoreBoard.DontGoBack then
|
||||
FAdmin.ScoreBoard.DontGoBack = input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE)
|
||||
end
|
||||
FAdmin.ScoreBoard.Background()
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.ShowScoreBoard()
|
||||
FAdmin.ScoreBoard.Visible = true
|
||||
FAdmin.ScoreBoard.DontGoBack = input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE)
|
||||
|
||||
FAdmin.ScoreBoard.Controls.Hostname = FAdmin.ScoreBoard.Controls.Hostname or vgui.Create("DLabel")
|
||||
FAdmin.ScoreBoard.Controls.Hostname:SetText(DarkRP.deLocalise(GetHostName()))
|
||||
FAdmin.ScoreBoard.Controls.Hostname:SetFont("ScoreboardHeader")
|
||||
FAdmin.ScoreBoard.Controls.Hostname:SetColor(Color(200,200,200,200))
|
||||
FAdmin.ScoreBoard.Controls.Hostname:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 20)
|
||||
FAdmin.ScoreBoard.Controls.Hostname:SizeToContents()
|
||||
FAdmin.ScoreBoard.Controls.Hostname:SetVisible(true)
|
||||
|
||||
FAdmin.ScoreBoard.Controls.Description = FAdmin.ScoreBoard.Controls.Description or vgui.Create("DLabel")
|
||||
FAdmin.ScoreBoard.Controls.Description:SetText(string.format("%s\n%s", GAMEMODE.Name, GAMEMODE.Author))
|
||||
FAdmin.ScoreBoard.Controls.Description:SetFont("ScoreboardSubtitle")
|
||||
FAdmin.ScoreBoard.Controls.Description:SetColor(Color(200,200,200,200))
|
||||
FAdmin.ScoreBoard.Controls.Description:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 50)
|
||||
FAdmin.ScoreBoard.Controls.Description:SizeToContents()
|
||||
if FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Width / 9.5 + FAdmin.ScoreBoard.Controls.Description:GetWide() > FAdmin.ScoreBoard.Width - 150 then
|
||||
FAdmin.ScoreBoard.Controls.Description:SetFont("Trebuchet18")
|
||||
FAdmin.ScoreBoard.Controls.Description:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 50)
|
||||
end
|
||||
FAdmin.ScoreBoard.Controls.Description:SetVisible(true)
|
||||
|
||||
FAdmin.ScoreBoard.Controls.ServerSettingsLabel = FAdmin.ScoreBoard.Controls.ServerSettingsLabel or vgui.Create("DLabel")
|
||||
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetFont("ScoreboardSubtitle")
|
||||
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetText("Server settings")
|
||||
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetColor(Color(200,200,200,200))
|
||||
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SizeToContents()
|
||||
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetPos(FAdmin.ScoreBoard.Width-150, FAdmin.ScoreBoard.Y + 68)
|
||||
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetVisible(true)
|
||||
|
||||
FAdmin.ScoreBoard.Controls.ServerSettings = FAdmin.ScoreBoard.Controls.ServerSettings or vgui.Create("DImageButton")
|
||||
FAdmin.ScoreBoard.Controls.ServerSettings:SetMaterial("vgui/gmod_tool")
|
||||
FAdmin.ScoreBoard.Controls.ServerSettings:SetPos(FAdmin.ScoreBoard.Width-200, FAdmin.ScoreBoard.Y - 20)
|
||||
FAdmin.ScoreBoard.Controls.ServerSettings:SizeToContents()
|
||||
FAdmin.ScoreBoard.Controls.ServerSettings:SetVisible(true)
|
||||
|
||||
function FAdmin.ScoreBoard.Controls.ServerSettings:DoClick()
|
||||
FAdmin.ScoreBoard.ChangeView("Server")
|
||||
end
|
||||
|
||||
if FAdmin.ScoreBoard.Controls.BackButton then FAdmin.ScoreBoard.Controls.BackButton:SetVisible(true) end
|
||||
|
||||
FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Show()
|
||||
|
||||
gui.EnableScreenClicker(true)
|
||||
hook.Add("HUDPaint", "FAdmin_ScoreBoard", FAdmin.ScoreBoard.DrawScoreBoard)
|
||||
hook.Call("FAdmin_ShowFAdminMenu")
|
||||
return true
|
||||
end
|
||||
concommand.Add("+FAdmin_menu", FAdmin.ScoreBoard.ShowScoreBoard)
|
||||
|
||||
hook.Add("ScoreboardShow", "FAdmin_scoreboard", function()
|
||||
if FAdmin.GlobalSetting.FAdmin or OverrideScoreboard:GetBool() then -- Don't show scoreboard when FAdmin is not installed on server
|
||||
return FAdmin.ScoreBoard.ShowScoreBoard()
|
||||
end
|
||||
end)
|
||||
|
||||
function FAdmin.ScoreBoard.HideScoreBoard()
|
||||
if not FAdmin.GlobalSetting.FAdmin then return end
|
||||
FAdmin.ScoreBoard.Visible = false
|
||||
CloseDermaMenus()
|
||||
|
||||
gui.EnableScreenClicker(false)
|
||||
hook.Remove("HUDPaint", "FAdmin_ScoreBoard")
|
||||
|
||||
for _, v in pairs(FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Controls) do
|
||||
v:SetVisible(false)
|
||||
end
|
||||
|
||||
for _, v in pairs(FAdmin.ScoreBoard.Controls) do
|
||||
v:SetVisible(false)
|
||||
end
|
||||
return true
|
||||
end
|
||||
concommand.Add("-FAdmin_menu", FAdmin.ScoreBoard.HideScoreBoard)
|
||||
|
||||
hook.Add("ScoreboardHide", "FAdmin_scoreboard", function()
|
||||
if FAdmin.GlobalSetting.FAdmin or OverrideScoreboard:GetBool() then -- Don't show scoreboard when FAdmin is not installed on server
|
||||
return FAdmin.ScoreBoard.HideScoreBoard()
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,110 @@
|
||||
local Sorted, SortDown = CreateClientConVar("FAdmin_SortPlayerList", "Team", true), CreateClientConVar("FAdmin_SortPlayerListDown", 1, true)
|
||||
local allowedSorts = {
|
||||
["Name"] = true,
|
||||
["Team"] = true,
|
||||
["Frags"] = true,
|
||||
["Deaths"] = true,
|
||||
["Ping"] = true
|
||||
}
|
||||
|
||||
function FAdmin.ScoreBoard.Main.Show()
|
||||
local Sort = {}
|
||||
local ScreenWidth, ScreenHeight = ScrW(), ScrH()
|
||||
|
||||
FAdmin.ScoreBoard.X = ScreenWidth * 0.05
|
||||
FAdmin.ScoreBoard.Y = ScreenHeight * 0.025
|
||||
FAdmin.ScoreBoard.Width = ScreenWidth * 0.9
|
||||
FAdmin.ScoreBoard.Height = ScreenHeight * 0.95
|
||||
|
||||
FAdmin.ScoreBoard.ChangeView("Main")
|
||||
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList = FAdmin.ScoreBoard.Main.Controls.FAdminPanelList or vgui.Create("DPanelList")
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetVisible(true)
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true)
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList.Padding = 3
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:EnableVerticalScrollbar(true)
|
||||
|
||||
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true)
|
||||
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 90 + 30 + 20)
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetSize(FAdmin.ScoreBoard.Width - 40, FAdmin.ScoreBoard.Height - 90 - 30 - 20 - 20)
|
||||
|
||||
Sort.Name = Sort.Name or vgui.Create("DLabel")
|
||||
Sort.Name:SetText("Sort by: Name")
|
||||
Sort.Name:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 90 + 30)
|
||||
Sort.Name.Type = "Name"
|
||||
Sort.Name:SetVisible(true)
|
||||
|
||||
Sort.Team = Sort.Team or vgui.Create("DLabel")
|
||||
Sort.Team:SetText("Team")
|
||||
Sort.Team:SetPos(ScreenWidth * 0.5 - 30, FAdmin.ScoreBoard.Y + 90 + 30)
|
||||
Sort.Team.Type = "Team"
|
||||
Sort.Team:SetVisible(true)
|
||||
|
||||
Sort.Frags = Sort.Frags or vgui.Create("DLabel")
|
||||
Sort.Frags:SetText("Kills")
|
||||
Sort.Frags:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 200, FAdmin.ScoreBoard.Y + 90 + 30)
|
||||
Sort.Frags.Type = "Frags"
|
||||
Sort.Frags:SetVisible(true)
|
||||
|
||||
Sort.Deaths = Sort.Deaths or vgui.Create("DLabel")
|
||||
Sort.Deaths:SetText("Deaths")
|
||||
Sort.Deaths:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 140, FAdmin.ScoreBoard.Y + 90 + 30)
|
||||
Sort.Deaths.Type = "Deaths"
|
||||
Sort.Deaths:SetVisible(true)
|
||||
|
||||
Sort.Ping = Sort.Ping or vgui.Create("DLabel")
|
||||
Sort.Ping:SetText("Ping")
|
||||
Sort.Ping:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 50, FAdmin.ScoreBoard.Y + 90 + 30)
|
||||
Sort.Ping.Type = "Ping"
|
||||
Sort.Ping:SetVisible(true)
|
||||
|
||||
local sortBy = Sorted:GetString()
|
||||
sortBy = allowedSorts[sortBy] and sortBy or "Team"
|
||||
|
||||
FAdmin.ScoreBoard.Main.PlayerListView(sortBy, SortDown:GetBool())
|
||||
|
||||
for _, v in pairs(Sort) do
|
||||
v:SetFont("Trebuchet20")
|
||||
v:SizeToContents()
|
||||
|
||||
local X, Y = v:GetPos()
|
||||
|
||||
v.BtnSort = vgui.Create("DButton")
|
||||
v.BtnSort:SetText("")
|
||||
v.BtnSort.Type = "Down"
|
||||
v.BtnSort.Paint = function(panel, w, h) derma.SkinHook("Paint", "ButtonDown", panel, w, h) end
|
||||
v.BtnSort:SetSkin(GAMEMODE.Config.DarkRPSkin)
|
||||
if Sorted:GetString() == v.Type then
|
||||
v.BtnSort.Depressed = true
|
||||
v.BtnSort.Type = (SortDown:GetBool() and "Down") or "Up"
|
||||
end
|
||||
v.BtnSort:SetSize(16, 16)
|
||||
v.BtnSort:SetPos(X + v:GetWide() + 5, Y + 4)
|
||||
function v.BtnSort.DoClick()
|
||||
for _, b in pairs(Sort) do
|
||||
b.BtnSort.Depressed = b.BtnSort == v.BtnSort
|
||||
end
|
||||
v.BtnSort.Type = (v.BtnSort.Type == "Down" and "Up") or "Down"
|
||||
v.BtnSort.Paint = function(panel, w, h)
|
||||
derma.SkinHook("Paint", "Button" .. v.BtnSort.Type, panel, w, h)
|
||||
end
|
||||
|
||||
RunConsoleCommand("FAdmin_SortPlayerList", v.Type)
|
||||
RunConsoleCommand("FAdmin_SortPlayerListDown", (v.BtnSort.Type == "Down" and "1") or "0")
|
||||
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true)
|
||||
FAdmin.ScoreBoard.Main.PlayerListView(v.Type, v.BtnSort.Type == "Down")
|
||||
end
|
||||
table.insert(FAdmin.ScoreBoard.Main.Controls, v) -- Add them to the table so they get removed when you close the scoreboard
|
||||
table.insert(FAdmin.ScoreBoard.Main.Controls, v.BtnSort)
|
||||
end
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Main.AddPlayerRightClick(Name, func)
|
||||
FAdmin.PlayerIcon.RightClickOptions[Name] = func
|
||||
end
|
||||
|
||||
FAdmin.StartHooks["CopySteamID"] = function()
|
||||
FAdmin.ScoreBoard.Main.AddPlayerRightClick("Copy SteamID", function(ply) SetClipboardText(ply:SteamID()) end)
|
||||
end
|
||||
@@ -0,0 +1,181 @@
|
||||
FAdmin.ScoreBoard.Player.Information = {}
|
||||
FAdmin.ScoreBoard.Player.ActionButtons = {}
|
||||
|
||||
function FAdmin.ScoreBoard.Player.Show(ply)
|
||||
ply = ply or FAdmin.ScoreBoard.Player.Player
|
||||
FAdmin.ScoreBoard.Player.Player = ply
|
||||
|
||||
if not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player) then FAdmin.ScoreBoard.ChangeView("Main") return end
|
||||
|
||||
local ScreenHeight = ScrH()
|
||||
|
||||
FAdmin.ScoreBoard.Player.Controls.AvatarBackground = vgui.Create("AvatarImage")
|
||||
FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100)
|
||||
FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetSize(184, 184)
|
||||
FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetPlayer(ply, 184)
|
||||
FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetVisible(true)
|
||||
|
||||
FAdmin.ScoreBoard.Player.InfoPanels = FAdmin.ScoreBoard.Player.InfoPanels or {}
|
||||
for k, v in pairs(FAdmin.ScoreBoard.Player.InfoPanels) do
|
||||
if IsValid(v) then
|
||||
v:Remove()
|
||||
FAdmin.ScoreBoard.Player.InfoPanels[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if IsValid(FAdmin.ScoreBoard.Player.Controls.InfoPanel1) then
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Remove()
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel1 = vgui.Create("DListLayout")
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100 + 184 + 5 --[[ + Avatar size]])
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetSize(184, ScreenHeight * 0.1 + 2)
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetVisible(true)
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Clear(true)
|
||||
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel2 = FAdmin.ScoreBoard.Player.Controls.InfoPanel2 or vgui.Create("FAdminPanelList")
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetPos(FAdmin.ScoreBoard.X + 25 + 184 --[[+ Avatar]], FAdmin.ScoreBoard.Y + 100)
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetSize(FAdmin.ScoreBoard.Width - 184 - 30 - 10, 184 + 5 + ScreenHeight * 0.1 + 2)
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetVisible(true)
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel2:Clear(true)
|
||||
|
||||
local function AddInfoPanel()
|
||||
local pan = FAdmin.ScoreBoard.Player.Controls.InfoPanel2:Add("DListLayout")
|
||||
pan:SetSize(1, FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall())
|
||||
|
||||
table.insert(FAdmin.ScoreBoard.Player.InfoPanels, pan)
|
||||
return pan
|
||||
end
|
||||
|
||||
local SelectedPanel = AddInfoPanel() -- Make first panel to put the first things in
|
||||
|
||||
for k, v in pairs(FAdmin.ScoreBoard.Player.Information) do
|
||||
SelectedPanel:Dock(LEFT)
|
||||
local Value = v.func(FAdmin.ScoreBoard.Player.Player)
|
||||
--if not Value or Value == "" then return --[[ Value = "N/A" ]] end
|
||||
if Value and Value ~= "" then
|
||||
|
||||
local Text = vgui.Create("DLabel")
|
||||
Text:Dock(LEFT)
|
||||
Text:SetFont("TabLarge")
|
||||
Text:SetText(v.name .. ": " .. Value)
|
||||
Text:SizeToContents()
|
||||
Text:SetColor(Color(200,200,200,200))
|
||||
Text:SetTooltip("Click to copy " .. v.name .. " to clipboard")
|
||||
Text:SetMouseInputEnabled(true)
|
||||
|
||||
function Text:OnMousePressed(mcode)
|
||||
self:SetTooltip(v.name .. " copied to clipboard!")
|
||||
ChangeTooltip(self)
|
||||
SetClipboardText(Value)
|
||||
self:SetTooltip("Click to copy " .. v.name .. " to clipboard")
|
||||
end
|
||||
|
||||
timer.Create("FAdmin_Scoreboard_text_update_" .. v.name, 1, 0, function()
|
||||
if not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player) or not IsValid(Text) then
|
||||
timer.Remove("FAdmin_Scoreboard_text_update_" .. v.name)
|
||||
if FAdmin.ScoreBoard.Visible and (not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player)) then FAdmin.ScoreBoard.ChangeView("Main") end
|
||||
return
|
||||
end
|
||||
Value = v.func(FAdmin.ScoreBoard.Player.Player)
|
||||
if not Value or Value == "" then Value = "N/A" end
|
||||
Text:SetText(v.name .. ": " .. Value)
|
||||
end)
|
||||
|
||||
if (#FAdmin.ScoreBoard.Player.Controls.InfoPanel1:GetChildren() * 17 + 17) <= FAdmin.ScoreBoard.Player.Controls.InfoPanel1:GetTall() and not v.NewPanel then
|
||||
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Add(Text)
|
||||
else
|
||||
if #SelectedPanel:GetChildren() * 17 + 17 >= SelectedPanel:GetTall() or v.NewPanel then
|
||||
SelectedPanel = AddInfoPanel() -- Add new panel if the last one is full
|
||||
end
|
||||
SelectedPanel:Add(Text)
|
||||
if Text:GetWide() > SelectedPanel:GetWide() then
|
||||
SelectedPanel:SetWide(Text:GetWide() + 40)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local CatColor = team.GetColor(ply:Team())
|
||||
if GAMEMODE.Name == "Sandbox" then
|
||||
CatColor = Color(100, 150, 245, 255)
|
||||
if ply:Team() == TEAM_CONNECTING then
|
||||
CatColor = Color(200, 120, 50, 255)
|
||||
elseif ply:IsAdmin() then
|
||||
CatColor = Color(30, 200, 50, 255)
|
||||
end
|
||||
|
||||
if ply:GetFriendStatus() == "friend" then
|
||||
CatColor = Color(236, 181, 113, 255)
|
||||
end
|
||||
end
|
||||
CatColor = hook.Run("FAdmin_PlayerRowColour", ply, CatColor) or CatColor
|
||||
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonCat = FAdmin.ScoreBoard.Player.Controls.ButtonCat or vgui.Create("FAdminPlayerCatagory")
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetLabel(" Player options!")
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonCat.CatagoryColor = CatColor
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetSize(FAdmin.ScoreBoard.Width - 40, 100)
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100 + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall() + 5)
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetVisible(true)
|
||||
|
||||
function FAdmin.ScoreBoard.Player.Controls.ButtonCat:Toggle()
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel = FAdmin.ScoreBoard.Player.Controls.ButtonPanel or vgui.Create("FAdminPanelList", FAdmin.ScoreBoard.Player.Controls.ButtonCat)
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetSpacing(5)
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:EnableHorizontal(true)
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:EnableVerticalScrollbar(true)
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SizeToContents()
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetVisible(true)
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetSize(0, (ScreenHeight - FAdmin.ScoreBoard.Y - 40) - (FAdmin.ScoreBoard.Y + 100 + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall() + 5))
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:Clear()
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:DockMargin(5, 5, 5, 5)
|
||||
|
||||
for _, v in ipairs(FAdmin.ScoreBoard.Player.ActionButtons) do
|
||||
if v.Visible == true or (isfunction(v.Visible) and v.Visible(FAdmin.ScoreBoard.Player.Player) == true) then
|
||||
local ActionButton = vgui.Create("FAdminActionButton")
|
||||
local imageType = TypeID(v.Image)
|
||||
if imageType == TYPE_STRING then
|
||||
ActionButton:SetImage(v.Image or "icon16/exclamation")
|
||||
elseif imageType == TYPE_TABLE then
|
||||
ActionButton:SetImage(v.Image[1])
|
||||
if v.Image[2] then ActionButton:SetImage2(v.Image[2]) end
|
||||
elseif imageType == TYPE_FUNCTION then
|
||||
local img1, img2 = v.Image(ply)
|
||||
ActionButton:SetImage(img1)
|
||||
if img2 then ActionButton:SetImage2(img2) end
|
||||
else
|
||||
ActionButton:SetImage("icon16/exclamation")
|
||||
end
|
||||
local name = v.Name
|
||||
if isfunction(name) then name = name(FAdmin.ScoreBoard.Player.Player) end
|
||||
ActionButton:SetText(DarkRP.deLocalise(name))
|
||||
ActionButton:SetBorderColor(v.color)
|
||||
|
||||
function ActionButton:DoClick()
|
||||
if not IsValid(FAdmin.ScoreBoard.Player.Player) then return end
|
||||
return v.Action(FAdmin.ScoreBoard.Player.Player, self)
|
||||
end
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:AddItem(ActionButton)
|
||||
if v.OnButtonCreated then
|
||||
v.OnButtonCreated(FAdmin.ScoreBoard.Player.Player, ActionButton)
|
||||
end
|
||||
end
|
||||
end
|
||||
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:Dock(TOP)
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Player:AddInformation(name, func, ForceNewPanel) -- ForeNewPanel is to start a new column
|
||||
table.insert(FAdmin.ScoreBoard.Player.Information, {name = name, func = func, NewPanel = ForceNewPanel})
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Player:AddActionButton(Name, Image, color, Visible, Action, OnButtonCreated)
|
||||
table.insert(FAdmin.ScoreBoard.Player.ActionButtons, {Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated})
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Name", function(ply) return ply:Nick() end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Kills", function(ply) return ply:Frags() end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Deaths", function(ply) return ply:Deaths() end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Health", function(ply) return ply:Health() end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("Ping", function(ply) return ply:Ping() end)
|
||||
FAdmin.ScoreBoard.Player:AddInformation("SteamID", function(ply) return ply:SteamID() end, true)
|
||||
@@ -0,0 +1,227 @@
|
||||
FAdmin.ScoreBoard.Server.Information = {} -- Compatibility for autoreload
|
||||
FAdmin.ScoreBoard.Server.ActionButtons = {} -- Refresh server buttons when reloading gamemode
|
||||
|
||||
local function MakeServerOptions()
|
||||
local _, YPos, Width = 20, FAdmin.ScoreBoard.Y + 120 + FAdmin.ScoreBoard.Height / 5 + 20, (FAdmin.ScoreBoard.Width - 40) / 3
|
||||
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat = FAdmin.ScoreBoard.Server.Controls.ServerActionsCat or vgui.Create("FAdminPlayerCatagory")
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetLabel(" Server Actions")
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat.CatagoryColor = Color(155, 0, 0, 255)
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos)
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetPos(FAdmin.ScoreBoard.X + 20, YPos)
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetVisible(true)
|
||||
function FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:Toggle()
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActions = FAdmin.ScoreBoard.Server.Controls.ServerActions or vgui.Create("FAdminPanelList")
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.ServerActions)
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerActions:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos)
|
||||
for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.ServerActions:GetChildren()) do
|
||||
if k == 1 then continue end
|
||||
v:Remove()
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat = FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat or vgui.Create("FAdminPlayerCatagory")
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetLabel(" Player Actions")
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat.CatagoryColor = Color(0, 155, 0, 255)
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos)
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetPos(FAdmin.ScoreBoard.X + 20 + Width, YPos)
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetVisible(true)
|
||||
function FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:Toggle()
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActions = FAdmin.ScoreBoard.Server.Controls.PlayerActions or vgui.Create("FAdminPanelList")
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.PlayerActions)
|
||||
FAdmin.ScoreBoard.Server.Controls.PlayerActions:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos)
|
||||
for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.PlayerActions:GetChildren()) do
|
||||
if k == 1 then continue end
|
||||
v:Remove()
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat = FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat or vgui.Create("FAdminPlayerCatagory")
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetLabel(" Server Settings")
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat.CatagoryColor = Color(0, 0, 155, 255)
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos)
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetPos(FAdmin.ScoreBoard.X + 20 + Width * 2, YPos)
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetVisible(true)
|
||||
function FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:Toggle()
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettings = FAdmin.ScoreBoard.Server.Controls.ServerSettings or vgui.Create("FAdminPanelList")
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.ServerSettings)
|
||||
FAdmin.ScoreBoard.Server.Controls.ServerSettings:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos)
|
||||
for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.ServerSettings:GetChildren()) do
|
||||
if k == 1 then continue end
|
||||
v:Remove()
|
||||
end
|
||||
|
||||
for k, v in ipairs(FAdmin.ScoreBoard.Server.ActionButtons) do
|
||||
local visible = v.Visible == true or (isfunction(v.Visible) and v.Visible(LocalPlayer()) == true)
|
||||
|
||||
local ActionButton = vgui.Create("FAdminActionButton")
|
||||
local imageType = TypeID(v.Image)
|
||||
if imageType == TYPE_STRING then
|
||||
ActionButton:SetImage(v.Image or "icon16/exclamation")
|
||||
elseif imageType == TYPE_TABLE then
|
||||
ActionButton:SetImage(v.Image[1])
|
||||
if v.Image[2] then ActionButton:SetImage2(v.Image[2]) end
|
||||
elseif imageType == TYPE_FUNCTION then
|
||||
local img1, img2 = v.Image()
|
||||
ActionButton:SetImage(img1)
|
||||
if img2 then ActionButton:SetImage2(img2) end
|
||||
else
|
||||
ActionButton:SetImage("icon16/exclamation")
|
||||
end
|
||||
local name = v.Name
|
||||
if isfunction(name) then name = name() end
|
||||
ActionButton:SetText(DarkRP.deLocalise(name))
|
||||
ActionButton:SetBorderColor(visible and v.color or Color(120, 120, 120))
|
||||
ActionButton:SetDisabled(not visible)
|
||||
ActionButton:Dock(TOP)
|
||||
|
||||
function ActionButton:DoClick()
|
||||
return v.Action(self)
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Server.Controls[v.TYPE]:Add(ActionButton)
|
||||
if v.OnButtonCreated then
|
||||
v.OnButtonCreated(ActionButton)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Server:AddServerAction(Name, Image, color, Visible, Action, OnButtonCreated)
|
||||
table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "ServerActions", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated})
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Server:AddPlayerAction(Name, Image, color, Visible, Action, OnButtonCreated)
|
||||
table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "PlayerActions", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated})
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Server:AddServerSetting(Name, Image, color, Visible, Action, OnButtonCreated)
|
||||
table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "ServerSettings", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated})
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Server.Show(ply)
|
||||
FAdmin.ScoreBoard.Server.InfoPanels = FAdmin.ScoreBoard.Server.InfoPanels or {}
|
||||
for k, v in pairs(FAdmin.ScoreBoard.Server.InfoPanels) do
|
||||
if IsValid(v) then
|
||||
v:Remove()
|
||||
FAdmin.ScoreBoard.Server.InfoPanels[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if IsValid(FAdmin.ScoreBoard.Server.Controls.InfoPanel) then
|
||||
FAdmin.ScoreBoard.Server.Controls.InfoPanel:Remove()
|
||||
end
|
||||
FAdmin.ScoreBoard.Server.Controls.InfoPanel = vgui.Create("FAdminPanelList")
|
||||
FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 120)
|
||||
FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetSize(FAdmin.ScoreBoard.Width - 40, FAdmin.ScoreBoard.Height / 5)
|
||||
FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetVisible(true)
|
||||
FAdmin.ScoreBoard.Server.Controls.InfoPanel:Clear(true)
|
||||
|
||||
local function AddInfoPanel()
|
||||
local pan = vgui.Create("FAdminPanelList")
|
||||
pan:SetSize(1, FAdmin.ScoreBoard.Server.Controls.InfoPanel:GetTall())
|
||||
pan:Dock(LEFT)
|
||||
FAdmin.ScoreBoard.Server.Controls.InfoPanel:Add(pan)
|
||||
|
||||
table.insert(FAdmin.ScoreBoard.Server.InfoPanels, pan)
|
||||
return pan
|
||||
end
|
||||
|
||||
local SelectedPanel = AddInfoPanel() -- Make first panel to put the first things in
|
||||
|
||||
for _, v in pairs(FAdmin.ScoreBoard.Server.Information) do
|
||||
local Text = vgui.Create("DLabel")
|
||||
Text:SetFont("TabLarge")
|
||||
Text:SetColor(Color(255,255,255,200))
|
||||
Text:Dock(TOP)
|
||||
Text.Func = v.Func
|
||||
|
||||
local EndText
|
||||
local function RefreshText()
|
||||
local Value = v.func()
|
||||
|
||||
if not Value or Value == "" then
|
||||
Value = "N/A"
|
||||
end
|
||||
|
||||
EndText = v.name .. ": " .. Value
|
||||
local strLen = string.len(EndText)
|
||||
|
||||
if strLen > 40 then
|
||||
local NewValue = string.sub(EndText, 1, 40)
|
||||
|
||||
for i = 40, strLen, 34 do
|
||||
NewValue = NewValue .. "\n " .. string.sub(EndText, i + 1, i + 34)
|
||||
end
|
||||
|
||||
EndText = NewValue
|
||||
else
|
||||
local MaxWidth = 240
|
||||
surface.SetFont("TabLarge")
|
||||
local TextWidth = surface.GetTextSize(v.name .. ": " .. Value)
|
||||
|
||||
if TextWidth <= MaxWidth then
|
||||
local SpacesAmount = (MaxWidth - TextWidth) / 3
|
||||
local Spaces = ""
|
||||
|
||||
for i = 1, SpacesAmount, 1 do
|
||||
Spaces = Spaces .. " "
|
||||
end
|
||||
|
||||
EndText = v.name .. ":" .. Spaces .. Value
|
||||
end
|
||||
end
|
||||
|
||||
Text:SetText(DarkRP.deLocalise(EndText))
|
||||
Text:SizeToContents()
|
||||
Text:SetTooltip("Click to copy " .. v.name .. " to clipboard")
|
||||
Text:SetMouseInputEnabled(true)
|
||||
end
|
||||
|
||||
RefreshText()
|
||||
|
||||
function Text:OnMousePressed(mcode)
|
||||
self:SetTooltip(v.name .. " copied to clipboard!")
|
||||
ChangeTooltip(self)
|
||||
SetClipboardText(v.func() or "")
|
||||
self:SetTooltip("Click to copy " .. v.name .. " to clipboard")
|
||||
end
|
||||
|
||||
timer.Create("FAdmin_Scoreboard_text_update_" .. v.name, 1, 0, function()
|
||||
if not IsValid(Text) then
|
||||
timer.Remove("FAdmin_Scoreboard_text_update_" .. v.name)
|
||||
FAdmin.ScoreBoard.ChangeView("Main")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
RefreshText()
|
||||
end)
|
||||
|
||||
if #SelectedPanel:GetChildren() * 17 + 17 >= SelectedPanel:GetTall() or v.NewPanel then
|
||||
SelectedPanel = AddInfoPanel()
|
||||
end
|
||||
-- Add new panel if the last one is full
|
||||
SelectedPanel:Add(Text)
|
||||
if Text:GetWide() > SelectedPanel:GetWide() then
|
||||
SelectedPanel:SetWide(Text:GetWide() + 40)
|
||||
end
|
||||
end
|
||||
|
||||
MakeServerOptions()
|
||||
end
|
||||
|
||||
function FAdmin.ScoreBoard.Server:AddInformation(name, func, ForceNewPanel) -- ForeNewPanel is to start a new column
|
||||
table.insert(FAdmin.ScoreBoard.Server.Information, {name = name, func = func, NewPanel = ForceNewPanel})
|
||||
end
|
||||
|
||||
FAdmin.ScoreBoard.Server:AddInformation("Hostname", GetHostName)
|
||||
FAdmin.ScoreBoard.Server:AddInformation("Gamemode", function() return GAMEMODE.Name end)
|
||||
FAdmin.ScoreBoard.Server:AddInformation("Author", function() return GAMEMODE.Author end)
|
||||
FAdmin.ScoreBoard.Server:AddInformation("Map", game.GetMap)
|
||||
FAdmin.ScoreBoard.Server:AddInformation("Players", function() return player.GetCount() .. "/" .. game.MaxPlayers() end)
|
||||
FAdmin.ScoreBoard.Server:AddInformation("Ping", function() return LocalPlayer():Ping() end)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
FAdmin.ScoreBoard = FAdmin.ScoreBoard or {}
|
||||
|
||||
local ScreenWidth, ScreenHeight = ScrW(), ScrH()
|
||||
|
||||
FAdmin.ScoreBoard.X = ScreenWidth * 0.05
|
||||
FAdmin.ScoreBoard.Y = ScreenHeight * 0.025
|
||||
FAdmin.ScoreBoard.Width = ScreenWidth * 0.9
|
||||
FAdmin.ScoreBoard.Height = ScreenHeight * 0.95
|
||||
|
||||
FAdmin.ScoreBoard.Controls = FAdmin.ScoreBoard.Controls or {}
|
||||
FAdmin.ScoreBoard.CurrentView = "Main"
|
||||
|
||||
FAdmin.ScoreBoard.Main = FAdmin.ScoreBoard.Main or {}
|
||||
FAdmin.ScoreBoard.Main.Controls = FAdmin.ScoreBoard.Main.Controls or {}
|
||||
FAdmin.ScoreBoard.Main.Logo = "gui/gmod_logo"
|
||||
|
||||
FAdmin.ScoreBoard.Player = FAdmin.ScoreBoard.Player or {}
|
||||
FAdmin.ScoreBoard.Player.Controls = FAdmin.ScoreBoard.Player.Controls or {}
|
||||
FAdmin.ScoreBoard.Player.Player = NULL
|
||||
FAdmin.ScoreBoard.Player.Logo = "fadmin/back"
|
||||
|
||||
FAdmin.ScoreBoard.Server = FAdmin.ScoreBoard.Server or {}
|
||||
FAdmin.ScoreBoard.Server.Controls = FAdmin.ScoreBoard.Server.Controls or {}
|
||||
FAdmin.ScoreBoard.Server.Logo = "fadmin/back"
|
||||
@@ -0,0 +1,53 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
Common times for several punishment actions
|
||||
---------------------------------------------------------------------------]]
|
||||
FAdmin.PlayerActions.commonTimes = {}
|
||||
FAdmin.PlayerActions.commonTimes[0] = "indefinitely"
|
||||
FAdmin.PlayerActions.commonTimes[10] = "10 seconds"
|
||||
FAdmin.PlayerActions.commonTimes[30] = "30 seconds"
|
||||
FAdmin.PlayerActions.commonTimes[60] = "1 minute"
|
||||
FAdmin.PlayerActions.commonTimes[300] = "5 minutes"
|
||||
FAdmin.PlayerActions.commonTimes[600] = "10 minutes"
|
||||
|
||||
function FAdmin.PlayerActions.addTimeSubmenu(menu, submenuText, submenuClick, submenuItemClick)
|
||||
local SubMenu = menu:AddSubMenu(submenuText, submenuClick)
|
||||
|
||||
local Padding = vgui.Create("DPanel")
|
||||
Padding:SetPaintBackgroundEnabled(false)
|
||||
Padding:SetSize(1,5)
|
||||
SubMenu:AddPanel(Padding)
|
||||
|
||||
local SubMenuTitle = vgui.Create("DLabel")
|
||||
SubMenuTitle:SetText(" Time:\n")
|
||||
SubMenuTitle:SetFont("UiBold")
|
||||
SubMenuTitle:SizeToContents()
|
||||
SubMenuTitle:SetTextColor(color_black)
|
||||
|
||||
SubMenu:AddPanel(SubMenuTitle)
|
||||
|
||||
for secs, Time in SortedPairs(FAdmin.PlayerActions.commonTimes) do
|
||||
SubMenu:AddOption(Time, function() submenuItemClick(secs) end)
|
||||
end
|
||||
end
|
||||
|
||||
function FAdmin.PlayerActions.addTimeMenu(ItemClick)
|
||||
local menu = DermaMenu()
|
||||
|
||||
local Padding = vgui.Create("DPanel")
|
||||
Padding:SetPaintBackgroundEnabled(false)
|
||||
Padding:SetSize(1,5)
|
||||
menu:AddPanel(Padding)
|
||||
|
||||
local Title = vgui.Create("DLabel")
|
||||
Title:SetText(" Time:\n")
|
||||
Title:SetFont("UiBold")
|
||||
Title:SizeToContents()
|
||||
Title:SetTextColor(color_black)
|
||||
|
||||
menu:AddPanel(Title)
|
||||
|
||||
for secs, Time in SortedPairs(FAdmin.PlayerActions.commonTimes) do
|
||||
menu:AddOption(Time, function() ItemClick(secs) end)
|
||||
end
|
||||
menu:Open()
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
FAdmin.StartHooks["CleanUp"] = function()
|
||||
FAdmin.Access.AddPrivilege("CleanUp", 2)
|
||||
FAdmin.Commands.AddCommand("ClearDecals", nil)
|
||||
FAdmin.Commands.AddCommand("StopSounds", nil)
|
||||
FAdmin.Commands.AddCommand("CleanUp", nil)
|
||||
|
||||
FAdmin.ScoreBoard.Server:AddServerAction("Clear decals", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button)
|
||||
RunConsoleCommand("_FAdmin", "ClearDecals")
|
||||
end)
|
||||
|
||||
FAdmin.ScoreBoard.Server:AddServerAction("Stop all sounds", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button)
|
||||
RunConsoleCommand("_FAdmin", "StopSounds")
|
||||
end)
|
||||
|
||||
usermessage.Hook("FAdmin_StopSounds", function()
|
||||
RunConsoleCommand("stopsound") -- bypass for ConCommand blocking it
|
||||
end)
|
||||
|
||||
FAdmin.ScoreBoard.Server:AddServerAction("Clean up server", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button)
|
||||
RunConsoleCommand("_FAdmin", "CleanUp")
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
local function ClearDecals(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
v:ConCommand("r_cleardecals")
|
||||
end
|
||||
FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have removed all decals. NOTE: this does NOT make the server ANY less laggy!", "All decals have been removed. NOTE: this does NOT make the server ANY less laggy!", "Removed all decals.")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function StopSounds(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
|
||||
umsg.Start("FAdmin_StopSounds")
|
||||
umsg.End()
|
||||
|
||||
FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have stopped all sounds", "All sounds have been stopped", "Stopped all sounds")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function CleanUp(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
|
||||
game.CleanUpMap()
|
||||
FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have cleaned up the map", "The map has been cleaned up", "Cleaned up the map")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
FAdmin.StartHooks["CleanUp"] = function()
|
||||
FAdmin.Commands.AddCommand("ClearDecals", ClearDecals)
|
||||
FAdmin.Commands.AddCommand("StopSounds", StopSounds)
|
||||
FAdmin.Commands.AddCommand("CleanUp", CleanUp)
|
||||
|
||||
local oldCleanup = concommand.GetTable()["gmod_admin_cleanup"]
|
||||
concommand.Add("gmod_admin_cleanup", function(ply, cmd, args)
|
||||
if args[1] then return oldCleanup(ply, cmd, args) end
|
||||
return CleanUp(ply, cmd, args)
|
||||
end)
|
||||
|
||||
FAdmin.Access.AddPrivilege("CleanUp", 2)
|
||||
end
|
||||
@@ -0,0 +1,109 @@
|
||||
local Options = {}
|
||||
local targets
|
||||
local colorBackground = Color(0, 0, 0, 200)
|
||||
local colorHighlight = Color(255, 125, 0, 200)
|
||||
hook.Add("ChatTextChanged", "FAdmin_Chat_autocomplete", function(text)
|
||||
if not FAdmin.GlobalSetting.FAdmin then return end
|
||||
Options = {}
|
||||
local prefix = GetGlobalString("FAdmin_commandprefix")
|
||||
prefix = prefix ~= '' and prefix or '/'
|
||||
|
||||
if string.sub(text, 1, 1) ~= prefix then targets = nil return end
|
||||
|
||||
local TExplode = string.Explode(" ", string.sub(text, 2))
|
||||
if not TExplode[1] then return end
|
||||
local Command = string.lower(TExplode[1])
|
||||
local Args = table.Copy(TExplode)
|
||||
Args[1] = nil
|
||||
Args = table.ClearKeys(Args)
|
||||
|
||||
|
||||
local optionsCount = 0
|
||||
for k, v in pairs(FAdmin.Commands.List) do
|
||||
if string.find(string.lower(k), Command, 1, true) ~= 1 then continue end
|
||||
|
||||
Options[prefix .. k] = table.Copy(v.ExtraArgs)
|
||||
|
||||
optionsCount = optionsCount + 1
|
||||
end
|
||||
|
||||
local ChatBoxPosX, ChatBoxPosY = chat.GetChatBoxPos()
|
||||
local ChatBoxWidth = chat.GetChatBoxSize() -- Don't need height
|
||||
local DidMakeShorter = false
|
||||
table.sort(Options)
|
||||
local i = 1
|
||||
for k in pairs(Options) do
|
||||
local Pos = ChatBoxPosY + i * 24
|
||||
if Pos + 24 > ScrH() then
|
||||
Options[k] = nil
|
||||
DidMakeShorter = true
|
||||
optionsCount = optionsCount - 1
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- Player arguments
|
||||
local firstVal = table.GetFirstValue(Options)
|
||||
if optionsCount == 1 and firstVal[#Args] and string.match(firstVal[#Args], ".Player.") then
|
||||
local players = {}
|
||||
|
||||
for _, v in ipairs(FAdmin.FindPlayer(Args[#Args]) or {}) do
|
||||
if not IsValid(v) then continue end
|
||||
table.insert(players, v:Nick())
|
||||
end
|
||||
|
||||
targets = table.concat(players, ", ")
|
||||
end
|
||||
|
||||
local xPos = ChatBoxPosX + ChatBoxWidth + 2
|
||||
hook.Add("HUDPaint", "FAdmin_Chat_autocomplete", function()
|
||||
local j = 0
|
||||
for option, args in pairs(Options) do
|
||||
draw.WordBox(4, xPos, ChatBoxPosY + j * 24, option, "UiBold", colorBackground, color_white)
|
||||
|
||||
for k, arg in pairs(args) do
|
||||
draw.WordBox(4, xPos + k * 130, ChatBoxPosY + j * 24, arg, "UiBold", colorBackground, color_white)
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
if targets then
|
||||
draw.WordBox(4, xPos, ChatBoxPosY + j * 24, "Targets: " .. targets, "UiBold", colorHighlight, color_white)
|
||||
end
|
||||
|
||||
if DidMakeShorter then
|
||||
draw.WordBox(4, xPos, ChatBoxPosY + j * 24, "...", "UiBold", colorBackground, color_white)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
hook.Add("FinishChat", "FAdmin_Chat_autocomplete", function() hook.Remove("HUDPaint", "FAdmin_Chat_autocomplete") end)
|
||||
|
||||
local plyIndex = 1
|
||||
|
||||
hook.Add("OnChatTab", "FAdmin_Chat_autocomplete", function(text)
|
||||
if not FAdmin.GlobalSetting.FAdmin then return end
|
||||
|
||||
for command in pairs(Options) do
|
||||
if string.find(text, " ") == nil then
|
||||
return string.sub(command, 1, string.find(command, " "))
|
||||
elseif string.find(text, " ") then
|
||||
plyIndex = plyIndex + 1
|
||||
|
||||
if plyIndex > player.GetCount() then
|
||||
plyIndex = 1
|
||||
end
|
||||
|
||||
return string.sub(command, 1, string.find(command, " ")) .. " " .. string.sub(player.GetAll()[plyIndex]:Nick(), 1, string.find(player.GetAll()[plyIndex]:Nick(), " "))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
FAdmin.StartHooks["Chatcommands"] = function()
|
||||
FAdmin.ScoreBoard.Server:AddServerSetting("Set FAdmin's chat command prefix", "fadmin/icons/message", Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "ServerSetting") end, function()
|
||||
local prefix = GetGlobalString("FAdmin_commandprefix")
|
||||
prefix = prefix ~= '' and prefix or '/'
|
||||
Derma_StringRequest("Set chat command prefix", "Make sure it's only one character!", prefix, fp{RunConsoleCommand, "_Fadmin", "CommandPrefix"})
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
local function AutoComplete(command, args)
|
||||
local autocomplete = {}
|
||||
args = string.Explode(" ", args)
|
||||
table.remove(args, 1) --Remove the first space
|
||||
if args[1] == "" then
|
||||
for k in pairs(FAdmin.Commands.List) do
|
||||
table.insert(autocomplete, command .. " " .. k)
|
||||
end
|
||||
elseif not args[2] or args[3] then
|
||||
for k, v in pairs(FAdmin.Commands.List) do
|
||||
if string.sub(k, 1, string.len(args[1])) == args[1] then
|
||||
local ExtraArgs = table.concat(v.ExtraArgs, " ")
|
||||
table.insert(autocomplete, command .. " " .. k .. " " .. ExtraArgs)
|
||||
end
|
||||
end
|
||||
elseif not args[3] and FAdmin.Commands.List[string.lower(args[1])] and FAdmin.Commands.List[string.lower(args[1])].ExtraArgs[1] == "<Player>" then
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if args[2] == "" or table.HasValue(FAdmin.FindPlayer(args[2]) or {}, v) then
|
||||
table.insert(autocomplete, command .. " " .. args[1] .. " " .. v:Nick())
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(autocomplete)
|
||||
return autocomplete
|
||||
end
|
||||
concommand.Add("FAdmin", function(ply, cmd, args)
|
||||
RunConsoleCommand("_" .. cmd, unpack(args))
|
||||
end, AutoComplete)
|
||||
@@ -0,0 +1,6 @@
|
||||
FAdmin.Commands = {}
|
||||
FAdmin.Commands.List = {}
|
||||
|
||||
function FAdmin.Commands.AddCommand(name, callback, ...)
|
||||
FAdmin.Commands.List[string.lower(name)] = {callback = callback, ExtraArgs = {...}}
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
local convar = CreateConVar("FAdmin_commandprefix", "/", {FCVAR_SERVER_CAN_EXECUTE})
|
||||
|
||||
SetGlobalString("FAdmin_commandprefix", convar:GetString())
|
||||
|
||||
cvars.AddChangeCallback("FAdmin_commandprefix", function()
|
||||
SetGlobalString("FAdmin_commandprefix", convar:GetString())
|
||||
end)
|
||||
|
||||
hook.Add("PlayerSay", "FAdminChatCommands", function(ply, text, Team, dead)
|
||||
local prefix = convar:GetString()
|
||||
|
||||
if string.sub(text, 1, 1) ~= prefix then return end
|
||||
|
||||
local TExplode = string.Explode(" ", string.sub(text, 2))
|
||||
if not TExplode then return end
|
||||
|
||||
for k, v in ipairs(TExplode) do
|
||||
if string.sub(v, -1) == "," and TExplode[k + 1] then
|
||||
TExplode[k] = (TExplode[k] or "") .. (TExplode[k + 1] or "")
|
||||
table.remove(TExplode, k + 1)
|
||||
end
|
||||
end
|
||||
table.ClearKeys(TExplode, false)
|
||||
|
||||
local Command = string.lower(TExplode[1])
|
||||
local Args = table.Copy(TExplode)
|
||||
Args[1] = nil
|
||||
Args = table.ClearKeys(Args)
|
||||
if FAdmin.Commands.List[Command] then
|
||||
local res = {FAdmin.Commands.List[Command].callback(ply, Command, Args)}
|
||||
hook.Call("FAdmin_OnCommandExecuted", nil, ply, Command, Args, res)
|
||||
return ""
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
FAdmin.StartHooks["Chatcommands"] = function()
|
||||
convar = convar or GetConVar("FAdmin_commandprefix")
|
||||
|
||||
FAdmin.Commands.AddCommand("CommandPrefix", function(ply, cmd, args)
|
||||
if not FAdmin.Access.PlayerHasPrivilege(ply, "ServerSetting") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
|
||||
if not args[1] or string.len(args[1]) ~= 1 then return end
|
||||
|
||||
FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " set FAdmin's chat command prefix to " .. args[1], "FAdmin's chat command prefix has been set to " .. args[1], "Chat command prefix set to" .. args[1])
|
||||
|
||||
RunConsoleCommand("FAdmin_commandprefix", args[1])
|
||||
|
||||
FAdmin.SaveSetting("FAdmin_commandprefix", args[1])
|
||||
|
||||
return true
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
local function concommand_executed(ply, cmd, args)
|
||||
if not args[1] then return end
|
||||
local name = string.lower(args[1])
|
||||
|
||||
if not name or not FAdmin.Commands.List[name] then
|
||||
FAdmin.Messages.SendMessage(ply, 1, "Command does not exist!")
|
||||
return
|
||||
end
|
||||
|
||||
local args2 = args
|
||||
table.remove(args2, 1)
|
||||
for k, v in pairs(args2) do
|
||||
if string.sub(v, -1) == "," and args2[k + 1] then
|
||||
args2[k] = args2[k] .. args2[k + 1]
|
||||
table.remove(args2, k + 1)
|
||||
end
|
||||
end
|
||||
table.ClearKeys(args2)
|
||||
local res = {FAdmin.Commands.List[name].callback(ply, name, args2)}
|
||||
hook.Call("FAdmin_OnCommandExecuted", nil, ply, name, args2, res)
|
||||
end
|
||||
|
||||
local function AutoComplete(command, ...)
|
||||
local autocomplete = {}
|
||||
local args = string.Explode(" ", ...)
|
||||
table.remove(args, 1) --Remove the first space
|
||||
if args[1] == "" then
|
||||
for k in pairs(FAdmin.Commands.List) do
|
||||
table.insert(autocomplete, command .. " " .. k)
|
||||
end
|
||||
elseif not args[2] then
|
||||
for k in pairs(FAdmin.Commands.List) do
|
||||
if string.sub(k, 1, string.len(args[1])) == args[1] then
|
||||
table.insert(autocomplete, command .. " " .. k)
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(autocomplete)
|
||||
return autocomplete
|
||||
end
|
||||
concommand.Add("_FAdmin", concommand_executed, AutoComplete)
|
||||
concommand.Add("FAdmin", concommand_executed, AutoComplete)
|
||||
|
||||
-- DO NOT EDIT THIS, NO MATTER HOW MUCH YOU'VE EDITED FADMIN IT DOESN'T GIVE YOU ANY RIGHT TO CHANGE CREDITS AND/OR REMOVE THE AUTHOR
|
||||
FAdmin.Commands.AddCommand("FAdminCredits", function(ply, cmd, args)
|
||||
if ply:SteamID() == "STEAM_0:0:8944068" and args[1] then
|
||||
local targets = FAdmin.FindPlayer(args[1])
|
||||
if not targets or (#targets == 1 and not IsValid(targets[1])) then
|
||||
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
|
||||
return false
|
||||
end
|
||||
for _, target in ipairs(targets) do
|
||||
if IsValid(target) then
|
||||
concommand_executed(target, "FAdmin", {"FAdminCredits"})
|
||||
end
|
||||
end
|
||||
|
||||
FAdmin.Messages.SendMessage(ply, 4, "Credits sent!")
|
||||
return true
|
||||
end
|
||||
FAdmin.Messages.SendMessage(ply, 2, "FAdmin by (FPtje) Falco, STEAM_0:0:8944068")
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if v:SteamID() == "STEAM_0:0:8944068" then
|
||||
FAdmin.Messages.SendMessage(ply, 4, "(FPtje) Falco is in the server at this moment")
|
||||
return true
|
||||
end
|
||||
end
|
||||
FAdmin.Messages.SendMessage(ply, 5, "(FPtje) Falco is NOT in the server at this moment")
|
||||
|
||||
return true
|
||||
end)
|
||||
@@ -0,0 +1,17 @@
|
||||
local logging = CreateConVar("FAdmin_logging", 1, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE})
|
||||
|
||||
if SERVER then return end
|
||||
FAdmin.StartHooks["Logging"] = function()
|
||||
FAdmin.Access.AddPrivilege("Logging", 3)
|
||||
FAdmin.Commands.AddCommand("Logging", nil)
|
||||
|
||||
FAdmin.ScoreBoard.Server:AddServerSetting(function() return (logging:GetBool() and "Disable" or "Enable") .. " Logging" end,
|
||||
function() return "fadmin/icons/message", logging:GetBool() and "fadmin/icons/disable" end,
|
||||
Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "Logging") end, function(button)
|
||||
button:SetImage2((not logging:GetBool() and "fadmin/icons/disable") or "null")
|
||||
button:SetText((not logging:GetBool() and "Disable" or "Enable") .. " Logging")
|
||||
button:GetParent():InvalidateLayout()
|
||||
|
||||
RunConsoleCommand("_Fadmin", "Logging", logging:GetBool() and 0 or 1)
|
||||
end)
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user