Initial commit

This commit is contained in:
2026-03-15 14:54:49 +03:00
commit 64f8029c06
4027 changed files with 254888 additions and 0 deletions

View 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)

View File

@@ -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

View 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

View 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,
}

View 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,
}

View 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

View 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,
}

View 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

View 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()

View 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
}

View 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}

View 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()

View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,4 @@
Do not add, edit or remove anything from this folder.
Use the DarkRPMod addon instead
https://github.com/FPtje/DarkRPModification

View 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)

View 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
}

View 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)

View 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"
}
}
}

View 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)

View File

@@ -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
}

View 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

View 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)

View 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()

View File

@@ -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)

View 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 = {
}
}

View 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)

View 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

View 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."
),
}

View 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
}

View 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)

View 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

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View 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")

View 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)

View 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)

View 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)

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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)

View 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)

View 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)

View 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)

View 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 = {
}
}

View 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

View 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
}

View 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)

View 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)

View 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"
},
}
}

View 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)

View File

@@ -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"
}
}
}

View 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

View 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

View File

@@ -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)

View 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)

View 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)

View File

@@ -0,0 +1 @@
resource.AddFile("materials/darkrp/darkrpderma.png")

View 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)

View File

@@ -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"
}
}
}

View 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
}

View 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"
}
}
}

View File

@@ -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)

View 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)

View 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

View 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 = {
}
}

View 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
}

View 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)

View 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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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