mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 21:53:46 +03:00
Upload
This commit is contained in:
397
gamemodes/helix/gamemode/core/meta/sh_character.lua
Normal file
397
gamemodes/helix/gamemode/core/meta/sh_character.lua
Normal file
@@ -0,0 +1,397 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Contains information about a player's current game state.
|
||||
|
||||
Characters are a fundamental object type in Helix. They are distinct from players, where players are the representation of a
|
||||
person's existence in the server that owns a character, and their character is their currently selected persona. All the
|
||||
characters that a player owns will be loaded into memory once they connect to the server. Characters are saved during a regular
|
||||
interval, and during specific events (e.g when the owning player switches away from one character to another).
|
||||
|
||||
They contain all information that is not persistent with the player; names, descriptions, model, currency, etc. For the most
|
||||
part, you'll want to keep all information stored on the character since it will probably be different or change if the
|
||||
player switches to another character. An easy way to do this is to use `ix.char.RegisterVar` to easily create accessor functions
|
||||
for variables that automatically save to the character object.
|
||||
]]
|
||||
-- @classmod Character
|
||||
|
||||
local CHAR = ix.meta.character or {}
|
||||
CHAR.__index = CHAR
|
||||
CHAR.id = CHAR.id or 0
|
||||
CHAR.vars = CHAR.vars or {}
|
||||
|
||||
-- @todo not this
|
||||
if (!ix.db) then
|
||||
ix.util.Include("../libs/sv_database.lua")
|
||||
end
|
||||
|
||||
--- Returns a string representation of this character
|
||||
-- @realm shared
|
||||
-- @treturn string String representation
|
||||
-- @usage print(ix.char.loaded[1])
|
||||
-- > "character[1]"
|
||||
function CHAR:__tostring()
|
||||
return "character["..(self.id or 0).."]"
|
||||
end
|
||||
|
||||
--- Returns true if this character is equal to another character. Internally, this checks character IDs.
|
||||
-- @realm shared
|
||||
-- @char other Character to compare to
|
||||
-- @treturn bool Whether or not this character is equal to the given character
|
||||
-- @usage print(ix.char.loaded[1] == ix.char.loaded[2])
|
||||
-- > false
|
||||
function CHAR:__eq(other)
|
||||
return self:GetID() == other:GetID()
|
||||
end
|
||||
|
||||
--- Returns this character's database ID. This is guaranteed to be unique.
|
||||
-- @realm shared
|
||||
-- @treturn number Unique ID of character
|
||||
function CHAR:GetID()
|
||||
return self.id
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Saves this character's info to the database.
|
||||
-- @realm server
|
||||
-- @func[opt=nil] callback Function to call when the save has completed.
|
||||
-- @usage ix.char.loaded[1]:Save(function()
|
||||
-- print("done!")
|
||||
-- end)
|
||||
-- > done! -- after a moment
|
||||
function CHAR:Save(callback)
|
||||
-- Do not save if the character is for a bot.
|
||||
if (self.isBot) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Let plugins/schema determine if the character should be saved.
|
||||
local shouldSave = hook.Run("CharacterPreSave", self)
|
||||
|
||||
if (shouldSave != false) then
|
||||
-- Run a query to save the character to the database.
|
||||
local query = mysql:Update("ix_characters")
|
||||
-- update all character vars
|
||||
for k, v in pairs(ix.char.vars) do
|
||||
if (v.field and self.vars[k] != nil and !v.bSaveLoadInitialOnly) then
|
||||
if (type(v.default) == "table") then
|
||||
local tblQuery = mysql:Update("ix_characters_data")
|
||||
tblQuery:Update("data", util.TableToJSON(self.vars[k]) or "NULL")
|
||||
tblQuery:Where("id", self:GetID())
|
||||
tblQuery:Where("key", v.field)
|
||||
tblQuery:Execute()
|
||||
else
|
||||
local value = self.vars[k]
|
||||
|
||||
if (v.fieldType == ix.type.bool) then
|
||||
value = value and 1 or 0
|
||||
end
|
||||
query:Update(v.field, tostring(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
query:Where("id", self:GetID())
|
||||
query:Callback(function()
|
||||
if (callback) then
|
||||
callback()
|
||||
end
|
||||
|
||||
hook.Run("CharacterPostSave", self)
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
--- Networks this character's information to make the given player aware of this character's existence. If the receiver is
|
||||
-- not the owner of this character, it will only be sent a limited amount of data (as it does not need anything else).
|
||||
-- This is done automatically by the framework.
|
||||
-- @internal
|
||||
-- @realm server
|
||||
-- @player[opt=nil] receiver Player to send the information to. This will sync to all connected players if set to `nil`.
|
||||
function CHAR:Sync(receiver)
|
||||
-- Broadcast the character information if receiver is not set.
|
||||
if (receiver == nil) then
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
self:Sync(v)
|
||||
end
|
||||
-- Send all character information if the receiver is the character's owner.
|
||||
elseif (receiver == self.player) then
|
||||
local data = {}
|
||||
|
||||
for k, v in pairs(self.vars) do
|
||||
if (ix.char.vars[k] != nil and !ix.char.vars[k].bNoNetworking) then
|
||||
data[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("ixCharacterInfo")
|
||||
ix.compnettable:Write(data)
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteUInt(self.player:EntIndex(), 8)
|
||||
net.Send(self.player)
|
||||
else
|
||||
local data = {}
|
||||
|
||||
for k, v in pairs(ix.char.vars) do
|
||||
if (!v.bNoNetworking and !v.isLocal) then
|
||||
data[k] = self.vars[k]
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("ixCharacterInfo")
|
||||
ix.compnettable:Write(data)
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteUInt(self.player:EntIndex(), 8)
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
|
||||
-- Sets up the "appearance" related inforomation for the character.
|
||||
--- Applies the character's appearance and synchronizes information to the owning player.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @bool[opt] bNoNetworking Whether or not to sync the character info to other players
|
||||
function CHAR:Setup(bNoNetworking)
|
||||
local client = self:GetPlayer()
|
||||
|
||||
if (IsValid(client)) then
|
||||
-- Set the faction, model, and character index for the player.
|
||||
local model = self:GetModel()
|
||||
|
||||
client:SetNetVar("char", self:GetID())
|
||||
client:SetTeam(self:GetFaction())
|
||||
client:SetModel(istable(model) and model[1] or model)
|
||||
|
||||
-- Apply saved body groups.
|
||||
for k, v in pairs(self:GetData("groups", {})) do
|
||||
client:SetBodygroup(k, v)
|
||||
end
|
||||
|
||||
-- Apply a saved skin.
|
||||
client:SetSkin(self:GetData("skin", 0))
|
||||
|
||||
-- Synchronize the character if we should.
|
||||
if (!bNoNetworking) then
|
||||
if (client:IsBot()) then
|
||||
timer.Simple(0.33, function()
|
||||
self:Sync()
|
||||
end)
|
||||
else
|
||||
self:Sync()
|
||||
end
|
||||
|
||||
for _, v in ipairs(self:GetInventory(true)) do
|
||||
if (istable(v)) then
|
||||
v:AddReceiver(client)
|
||||
v:Sync(client)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local id = self:GetID()
|
||||
|
||||
hook.Run("CharacterLoaded", ix.char.loaded[id])
|
||||
|
||||
net.Start("ixCharacterLoaded")
|
||||
net.WriteUInt(id, 32)
|
||||
net.Send(client)
|
||||
|
||||
self.firstTimeLoaded = true
|
||||
end
|
||||
end
|
||||
|
||||
--- Forces a player off their current character, and sends them to the character menu to select a character.
|
||||
-- @realm server
|
||||
function CHAR:Kick()
|
||||
-- Kill the player so they are not standing anywhere.
|
||||
local client = self:GetPlayer()
|
||||
client:KillSilent()
|
||||
|
||||
hook.Run("OnCharacterKicked", self)
|
||||
|
||||
local steamID = client:SteamID64()
|
||||
local id = self:GetID()
|
||||
local isCurrentChar = self and self:GetID() == id
|
||||
|
||||
-- Return the player to the character menu.
|
||||
if (self and self.steamID == steamID) then
|
||||
net.Start("ixCharacterKick")
|
||||
net.WriteBool(isCurrentChar)
|
||||
net.Send(client)
|
||||
|
||||
if (isCurrentChar) then
|
||||
client:SetNetVar("char", nil)
|
||||
client:Spawn()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Forces a player off their current character, and prevents them from using the character for the specified amount of time.
|
||||
-- @realm server
|
||||
-- @number[opt] time Amount of seconds to ban the character for. If left as `nil`, the character will be banned permanently
|
||||
function CHAR:Ban(time)
|
||||
time = tonumber(time)
|
||||
|
||||
hook.Run("OnCharacterBanned", self, time or true)
|
||||
|
||||
if (time) then
|
||||
-- If time is provided, adjust it so it becomes the un-ban time.
|
||||
time = os.time() + math.max(math.ceil(time), 60)
|
||||
end
|
||||
|
||||
-- Mark the character as banned and kick the character back to menu.
|
||||
self:SetData("banned", time or true)
|
||||
self:Kick()
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the player that owns this character.
|
||||
-- @realm shared
|
||||
-- @treturn player Player that owns this character
|
||||
function CHAR:GetPlayer()
|
||||
-- Set the player from entity index.
|
||||
if (isnumber(self.player)) then
|
||||
local client = Entity(self.player)
|
||||
|
||||
if (IsValid(client)) then
|
||||
self.player = client
|
||||
|
||||
return client
|
||||
end
|
||||
-- Return the player from cache.
|
||||
elseif (IsValid(self.player)) then
|
||||
return self.player
|
||||
-- Search for which player owns this character.
|
||||
elseif (self.steamID) then
|
||||
local steamID = self.steamID
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:SteamID64() == steamID) then
|
||||
self.player = v
|
||||
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CHAR:GetFactionVar(variable, default)
|
||||
local faction = ix.faction.Get(self:GetFaction())
|
||||
if (!faction) then return end
|
||||
|
||||
return faction[variable] or default
|
||||
end
|
||||
|
||||
-- Sets up a new character variable.
|
||||
function ix.char.RegisterVar(key, data)
|
||||
-- Store information for the variable.
|
||||
ix.char.vars[key] = data
|
||||
data.index = data.index or table.Count(ix.char.vars)
|
||||
|
||||
local upperName = key:sub(1, 1):upper() .. key:sub(2)
|
||||
|
||||
if (SERVER) then
|
||||
if (data.field) then
|
||||
if (type(data.default) != "table") then
|
||||
ix.db.AddToSchema("ix_characters", data.field, data.fieldType or ix.type.string)
|
||||
end
|
||||
end
|
||||
|
||||
-- Provide functions to change the variable if allowed.
|
||||
if (!data.bNotModifiable) then
|
||||
-- Overwrite the set function if desired.
|
||||
if (data.OnSet) then
|
||||
CHAR["Set"..upperName] = data.OnSet
|
||||
-- Have the set function only set on the server if no networking.
|
||||
elseif (data.bNoNetworking) then
|
||||
CHAR["Set"..upperName] = function(self, value)
|
||||
self.vars[key] = value
|
||||
end
|
||||
-- If the variable is a local one, only send the variable to the local player.
|
||||
elseif (data.isLocal) then
|
||||
CHAR["Set"..upperName] = function(self, value)
|
||||
local oldVar = self.vars[key]
|
||||
self.vars[key] = value
|
||||
|
||||
net.Start("ixCharacterVarChanged")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Send(self.player)
|
||||
|
||||
hook.Run("CharacterVarChanged", self, key, oldVar, value)
|
||||
end
|
||||
-- Otherwise network the variable to everyone.
|
||||
else
|
||||
CHAR["Set"..upperName] = function(self, value)
|
||||
local oldVar = self.vars[key]
|
||||
self.vars[key] = value
|
||||
|
||||
net.Start("ixCharacterVarChanged")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Broadcast()
|
||||
|
||||
hook.Run("CharacterVarChanged", self, key, oldVar, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- The get functions are shared.
|
||||
-- Overwrite the get function if desired.
|
||||
if (data.OnGet) then
|
||||
CHAR["Get"..upperName] = data.OnGet
|
||||
-- Otherwise return the character variable or default if it does not exist.
|
||||
else
|
||||
CHAR["Get"..upperName] = function(self, default)
|
||||
local value = self.vars[key]
|
||||
|
||||
if (value != nil) then
|
||||
return value
|
||||
end
|
||||
|
||||
if (default == nil) then
|
||||
return ix.char.vars[key] and (istable(ix.char.vars[key].default) and table.Copy(ix.char.vars[key].default)
|
||||
or ix.char.vars[key].default)
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
end
|
||||
|
||||
local alias = data.alias
|
||||
|
||||
if (alias) then
|
||||
if (istable(alias)) then
|
||||
for _, v in ipairs(alias) do
|
||||
local aliasName = v:sub(1, 1):upper()..v:sub(2)
|
||||
|
||||
CHAR["Get"..aliasName] = CHAR["Get"..upperName]
|
||||
CHAR["Set"..aliasName] = CHAR["Set"..upperName]
|
||||
end
|
||||
elseif (isstring(alias)) then
|
||||
local aliasName = alias:sub(1, 1):upper()..alias:sub(2)
|
||||
|
||||
CHAR["Get"..aliasName] = CHAR["Get"..upperName]
|
||||
CHAR["Set"..aliasName] = CHAR["Set"..upperName]
|
||||
end
|
||||
end
|
||||
|
||||
-- Add the variable default to the character object.
|
||||
CHAR.vars[key] = data.default
|
||||
end
|
||||
|
||||
-- Allows access to the character metatable using ix.meta.character
|
||||
ix.meta.character = CHAR
|
||||
227
gamemodes/helix/gamemode/core/meta/sh_entity.lua
Normal file
227
gamemodes/helix/gamemode/core/meta/sh_entity.lua
Normal file
@@ -0,0 +1,227 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Physical object in the game world.
|
||||
|
||||
Entities are physical representations of objects in the game world. Helix extends the functionality of entities to interface
|
||||
between Helix's own classes, and to reduce boilerplate code.
|
||||
|
||||
See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Entity) for all other methods that the `Player` class has.
|
||||
]]
|
||||
-- @classmod Entity
|
||||
|
||||
local meta = FindMetaTable("Entity")
|
||||
local CHAIR_CACHE = {}
|
||||
|
||||
-- Add chair models to the cache by checking if its vehicle category is a class.
|
||||
for _, v in pairs(list.Get("Vehicles")) do
|
||||
if (v.Category == "Chairs") then
|
||||
CHAIR_CACHE[v.Model] = true
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns `true` if this entity is a chair.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not this entity is a chair
|
||||
function meta:IsChair()
|
||||
return CHAIR_CACHE[self:GetModel()]
|
||||
end
|
||||
|
||||
--- Returns `true` if this entity is a door. Internally, this checks to see if the entity's class has `door` in its name.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the entity is a door
|
||||
function meta:IsDoor()
|
||||
local class = self:GetClass()
|
||||
|
||||
return (class and class:find("door") != nil)
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Returns `true` if the given entity is a button or door and is locked.
|
||||
-- @realm server
|
||||
-- @treturn bool Whether or not this entity is locked; `false` if this entity cannot be locked at all
|
||||
-- (e.g not a button or door)
|
||||
function meta:IsLocked()
|
||||
if (self:IsVehicle()) then
|
||||
local datatable = self:GetSaveTable()
|
||||
|
||||
if (datatable) then
|
||||
return datatable.VehicleLocked
|
||||
end
|
||||
else
|
||||
local datatable = self:GetSaveTable()
|
||||
|
||||
if (datatable) then
|
||||
return datatable.m_bLocked
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns the neighbouring door entity for double doors.
|
||||
-- @realm shared
|
||||
-- @treturn[1] Entity This door's partner
|
||||
-- @treturn[2] nil If the door does not have a partner
|
||||
function meta:GetDoorPartner()
|
||||
return self.ixPartner
|
||||
end
|
||||
|
||||
--- Returns the entity that is blocking this door from opening.
|
||||
-- @realm server
|
||||
-- @treturn[1] Entity Entity that is blocking this door
|
||||
-- @treturn[2] nil If this entity is not a door, or there is no blocking entity
|
||||
function meta:GetBlocker()
|
||||
local datatable = self:GetSaveTable()
|
||||
|
||||
return datatable.pBlocker
|
||||
end
|
||||
|
||||
--- Blasts a door off its hinges. Internally, this hides the door entity, spawns a physics prop with the same model, and
|
||||
-- applies force to the prop.
|
||||
-- @realm server
|
||||
-- @vector velocity Velocity to apply to the door
|
||||
-- @number lifeTime How long to wait in seconds before the door is put back on its hinges
|
||||
-- @bool bIgnorePartner Whether or not to ignore the door's partner in the case of double doors
|
||||
-- @treturn[1] Entity The physics prop created for the door
|
||||
-- @treturn nil If the entity is not a door
|
||||
function meta:BlastDoor(velocity, lifeTime, bIgnorePartner)
|
||||
if (!self:IsDoor()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (IsValid(self.ixDummy)) then
|
||||
self.ixDummy:Remove()
|
||||
end
|
||||
|
||||
velocity = velocity or VectorRand()*100
|
||||
lifeTime = lifeTime or 120
|
||||
|
||||
local partner = self:GetDoorPartner()
|
||||
|
||||
if (IsValid(partner) and !bIgnorePartner) then
|
||||
partner:BlastDoor(velocity, lifeTime, true)
|
||||
end
|
||||
|
||||
local color = self:GetColor()
|
||||
|
||||
local dummy = ents.Create("prop_physics")
|
||||
dummy:SetModel(self:GetModel())
|
||||
dummy:SetPos(self:GetPos())
|
||||
dummy:SetAngles(self:GetAngles())
|
||||
dummy:Spawn()
|
||||
dummy:SetColor(color)
|
||||
dummy:SetMaterial(self:GetMaterial())
|
||||
dummy:SetSkin(self:GetSkin() or 0)
|
||||
dummy:SetRenderMode(RENDERMODE_TRANSALPHA)
|
||||
dummy:CallOnRemove("restoreDoor", function()
|
||||
if (IsValid(self)) then
|
||||
self:SetNotSolid(false)
|
||||
self:SetNoDraw(false)
|
||||
self:DrawShadow(true)
|
||||
self.ignoreUse = false
|
||||
self.ixIsMuted = false
|
||||
|
||||
for _, v in ipairs(ents.GetAll()) do
|
||||
if (v:GetParent() == self) then
|
||||
v:SetNotSolid(false)
|
||||
v:SetNoDraw(false)
|
||||
|
||||
if (v.OnDoorRestored) then
|
||||
v:OnDoorRestored(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
dummy:SetOwner(self)
|
||||
dummy:SetCollisionGroup(COLLISION_GROUP_WEAPON)
|
||||
|
||||
self:Fire("unlock")
|
||||
self:Fire("open")
|
||||
self:SetNotSolid(true)
|
||||
self:SetNoDraw(true)
|
||||
self:DrawShadow(false)
|
||||
self.ignoreUse = true
|
||||
self.ixDummy = dummy
|
||||
self.ixIsMuted = true
|
||||
self:DeleteOnRemove(dummy)
|
||||
|
||||
for _, v in ipairs(self:GetBodyGroups() or {}) do
|
||||
dummy:SetBodygroup(v.id, self:GetBodygroup(v.id))
|
||||
end
|
||||
|
||||
for _, v in ipairs(ents.GetAll()) do
|
||||
if (v:GetParent() == self) then
|
||||
v:SetNotSolid(true)
|
||||
v:SetNoDraw(true)
|
||||
|
||||
if (v.OnDoorBlasted) then
|
||||
v:OnDoorBlasted(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dummy:GetPhysicsObject():SetVelocity(velocity)
|
||||
|
||||
local uniqueID = "doorRestore"..self:EntIndex()
|
||||
local uniqueID2 = "doorOpener"..self:EntIndex()
|
||||
|
||||
timer.Create(uniqueID2, 1, 0, function()
|
||||
if (IsValid(self) and IsValid(self.ixDummy)) then
|
||||
self:Fire("open")
|
||||
else
|
||||
timer.Remove(uniqueID2)
|
||||
end
|
||||
end)
|
||||
|
||||
timer.Create(uniqueID, lifeTime, 1, function()
|
||||
if (IsValid(self) and IsValid(dummy)) then
|
||||
uniqueID = "dummyFade"..dummy:EntIndex()
|
||||
local alpha = 255
|
||||
|
||||
timer.Create(uniqueID, 0.1, 255, function()
|
||||
if (IsValid(dummy)) then
|
||||
alpha = alpha - 1
|
||||
dummy:SetColor(ColorAlpha(color, alpha))
|
||||
|
||||
if (alpha <= 0) then
|
||||
dummy:Remove()
|
||||
end
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
return dummy
|
||||
end
|
||||
|
||||
else
|
||||
-- Returns the door's slave entity.
|
||||
function meta:GetDoorPartner()
|
||||
local owner = self:GetOwner() or self.ixDoorOwner
|
||||
|
||||
if (IsValid(owner) and owner:IsDoor()) then
|
||||
return owner
|
||||
end
|
||||
|
||||
for _, v in ipairs(ents.FindByClass("prop_door_rotating")) do
|
||||
if (v:GetOwner() == self) then
|
||||
self.ixDoorOwner = v
|
||||
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
953
gamemodes/helix/gamemode/core/meta/sh_inventory.lua
Normal file
953
gamemodes/helix/gamemode/core/meta/sh_inventory.lua
Normal file
@@ -0,0 +1,953 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Holds items within a grid layout.
|
||||
|
||||
Inventories are an object that contains `Item`s in a grid layout. Every `Character` will have exactly one inventory attached to
|
||||
it, which is the only inventory that is allowed to hold bags - any item that has its own inventory (i.e a suitcase). Inventories
|
||||
can be owned by a character, or it can be individually interacted with as a standalone object. For example, the container plugin
|
||||
attaches inventories to props, allowing for items to be stored outside of any character inventories and remain "in the world".
|
||||
|
||||
|
||||
You may be looking for the following common functions:
|
||||
|
||||
`Add` Which adds an item to the inventory.
|
||||
|
||||
`GetItems` Which gets all of the items inside the inventory.
|
||||
|
||||
`GetItemByID` Which gets an item in the inventory by it's item ID.
|
||||
|
||||
`GetItemAt` Which gets an item in the inventory by it's x and y
|
||||
|
||||
`GetID` Which gets the inventory's ID.
|
||||
|
||||
`HasItem` Which checks if the inventory has an item.
|
||||
]]
|
||||
-- @classmod Inventory
|
||||
|
||||
local META = ix.meta.inventory or ix.middleclass("ix_inventory")
|
||||
|
||||
META.slots = META.slots or {}
|
||||
META.w = META.w or 4
|
||||
META.h = META.h or 4
|
||||
META.vars = META.vars or {}
|
||||
META.receivers = META.receivers or {}
|
||||
|
||||
--- Returns a string representation of this inventory
|
||||
-- @realm shared
|
||||
-- @treturn string String representation
|
||||
-- @usage print(ix.item.inventories[1])
|
||||
-- > "inventory[1]"
|
||||
function META:__tostring()
|
||||
return "inventory["..(self.id or 0).."]"
|
||||
end
|
||||
|
||||
--- Initializes the inventory with the provided arguments.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @number id The `Inventory`'s database ID.
|
||||
-- @number width The inventory's width.
|
||||
-- @number height The inventory's height.
|
||||
function META:Initialize(id, width, height)
|
||||
self.id = id
|
||||
self.w = width
|
||||
self.h = height
|
||||
|
||||
self.slots = {}
|
||||
self.vars = {}
|
||||
self.receivers = {}
|
||||
end
|
||||
|
||||
--- Returns this inventory's database ID. This is guaranteed to be unique.
|
||||
-- @realm shared
|
||||
-- @treturn number Unique ID of inventory
|
||||
function META:GetID()
|
||||
return self.id or 0
|
||||
end
|
||||
|
||||
--- Sets the grid size of this inventory.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @number width New width of inventory
|
||||
-- @number height New height of inventory
|
||||
function META:SetSize(width, height)
|
||||
self.w = width
|
||||
self.h = height
|
||||
end
|
||||
|
||||
--- Returns the grid size of this inventory.
|
||||
-- @realm shared
|
||||
-- @treturn number Width of inventory
|
||||
-- @treturn number Height of inventory
|
||||
function META:GetSize()
|
||||
return self.w, self.h
|
||||
end
|
||||
|
||||
-- this is pretty good to debug/develop function to use.
|
||||
function META:Print(printPos)
|
||||
for k, v in pairs(self:GetItems()) do
|
||||
local str = k .. ": " .. v.name
|
||||
|
||||
if (printPos) then
|
||||
str = str .. " (" .. v.gridX .. ", " .. v.gridY .. ")"
|
||||
end
|
||||
|
||||
print(str)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Searches the inventory to find any stacked items.
|
||||
-- A common problem with developing, is that items will sometimes error out, or get corrupt.
|
||||
-- Sometimes, the server knows things you don't while developing live
|
||||
-- This function can be helpful for getting rid of those pesky errors.
|
||||
-- @realm shared
|
||||
function META:FindError()
|
||||
for _, v in pairs(self:GetItems()) do
|
||||
if (v.width == 1 and v.height == 1) then
|
||||
continue
|
||||
end
|
||||
|
||||
print("Finding error: " .. v.name)
|
||||
print("Item Position: " .. v.gridX, v.gridY)
|
||||
|
||||
for x = v.gridX, v.gridX + v.width - 1 do
|
||||
for y = v.gridY, v.gridY + v.height - 1 do
|
||||
local item = self.slots[x][y]
|
||||
|
||||
if (item and item.id != v.id) then
|
||||
print("Error Found: ".. item.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Prints out the id, width, height, slots and each item in each slot of an `Inventory`, used for debugging.
|
||||
-- @realm shared
|
||||
function META:PrintAll()
|
||||
print("------------------------")
|
||||
print("INVID", self:GetID())
|
||||
print("INVSIZE", self:GetSize())
|
||||
|
||||
if (self.slots) then
|
||||
for x = 1, self.w do
|
||||
for y = 1, self.h do
|
||||
local item = self.slots[x] and self.slots[x][y]
|
||||
if (item and item.id) then
|
||||
print(item.name .. "(" .. item.id .. ")", x, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print("INVVARS")
|
||||
PrintTable(self.vars or {})
|
||||
print("------------------------")
|
||||
end
|
||||
|
||||
--- Returns the player that owns this inventory.
|
||||
-- @realm shared
|
||||
-- @treturn[1] Player Owning player
|
||||
-- @treturn[2] nil If no connected player owns this inventory
|
||||
function META:GetOwner()
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:GetCharacter() and v:GetCharacter().id == self.owner) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the player that owns this inventory.
|
||||
-- @realm shared
|
||||
-- @player owner The player to take control over the inventory.
|
||||
-- @bool fullUpdate Whether or not to update the inventory immediately to the new owner.
|
||||
function META:SetOwner(owner, fullUpdate)
|
||||
if (type(owner) == "Player" and owner:GetNetVar("char")) then
|
||||
owner = owner:GetNetVar("char")
|
||||
else
|
||||
owner = tonumber(owner) or 0 // previously if owner variable wasn't a number, function ended, which caused bags to have and owner all the time
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
if (fullUpdate) then
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:GetNetVar("char") == owner) then
|
||||
self:Sync(v, true)
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local query = mysql:Update("ix_inventories")
|
||||
query:Update("character_id", owner)
|
||||
query:Where("inventory_id", self:GetID())
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
self.owner = owner
|
||||
end
|
||||
|
||||
--- Checks whether a player has access to an inventory
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @player client Player to check access for
|
||||
-- @treturn bool Whether or not the player has access to the inventory
|
||||
function META:OnCheckAccess(client)
|
||||
local bAccess = false
|
||||
|
||||
for _, v in ipairs(self:GetReceivers()) do
|
||||
if (v == client) then
|
||||
bAccess = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return bAccess
|
||||
end
|
||||
|
||||
--- Checks whether or not an `Item` can fit into the `Inventory` starting from `x` and `y`.
|
||||
-- Internally used by FindEmptySlot, in most cases you are better off using that.
|
||||
-- This function will search if all of the slots within `x + width` and `y + width` are empty,
|
||||
-- ignoring any space the `Item` itself already occupies.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @number x The beginning x coordinate to search for.
|
||||
-- @number y The beginning y coordiate to search for.
|
||||
-- @number w The `Item`'s width.
|
||||
-- @number h The `Item`'s height.
|
||||
-- @item[opt=nil] item2 An `Item`, if any, to ignore when searching.
|
||||
function META:CanItemFit(x, y, w, h, item2)
|
||||
local canFit = true
|
||||
|
||||
for x2 = 0, w - 1 do
|
||||
for y2 = 0, h - 1 do
|
||||
local item = (self.slots[x + x2] or {})[y + y2]
|
||||
|
||||
if ((x + x2) > self.w or item) then
|
||||
if (item2) then
|
||||
if (item and item.id == item2.id) then
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
canFit = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!canFit) then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return canFit
|
||||
end
|
||||
|
||||
|
||||
--- Returns the amount of slots currently filled in the Inventory.
|
||||
-- @realm shared
|
||||
-- @treturn number The amount of slots currently filled.
|
||||
function META:GetFilledSlotCount()
|
||||
local count = 0
|
||||
|
||||
for x = 1, self.w do
|
||||
for y = 1, self.h do
|
||||
if ((self.slots[x] or {})[y]) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
--- Finds an empty slot of a specified width and height.
|
||||
-- In most cases, to check if an `Item` can actually fit in the `Inventory`,
|
||||
-- as if it can't, it will just return `nil`.
|
||||
--
|
||||
-- FindEmptySlot will loop through all the slots for you, as opposed to `CanItemFit`
|
||||
-- which you specify an `x` and `y` for.
|
||||
-- this will call CanItemFit anyway.
|
||||
-- If you need to check if an item will fit *exactly* at a position, you want CanItemFit instead.
|
||||
-- @realm shared
|
||||
-- @number w The width of the `Item` you are trying to fit.
|
||||
-- @number h The height of the `Item` you are trying to fit.
|
||||
-- @bool onlyMain Whether or not to search any bags connected to this `Inventory`
|
||||
-- @treturn[1] number x The `x` coordinate that the `Item` can fit into.
|
||||
-- @treturn[1] number y The `y` coordinate that the `Item` can fit into.
|
||||
-- @treturn[2] number x The `x` coordinate that the `Item` can fit into.
|
||||
-- @treturn[2] number y The `y` coordinate that the `Item` can fit into.
|
||||
-- @treturn[2] Inventory bagInv If the item was in a bag, it will return the inventory it was in.
|
||||
-- @see CanItemFit
|
||||
function META:FindEmptySlot(w, h, onlyMain)
|
||||
w = w or 1
|
||||
h = h or 1
|
||||
|
||||
if (w > self.w or h > self.h) then
|
||||
return
|
||||
end
|
||||
|
||||
for y = 1, self.h - (h - 1) do
|
||||
for x = 1, self.w - (w - 1) do
|
||||
if (self:CanItemFit(x, y, w, h)) then
|
||||
return x, y
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (onlyMain != true) then
|
||||
local bags = self:GetBags()
|
||||
|
||||
if (#bags > 0) then
|
||||
for _, invID in ipairs(bags) do
|
||||
local bagInv = ix.item.inventories[invID]
|
||||
|
||||
if (bagInv) then
|
||||
local x, y = bagInv:FindEmptySlot(w, h)
|
||||
|
||||
if (x and y) then
|
||||
return x, y, bagInv
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the item that currently exists within `x` and `y` in the `Inventory`.
|
||||
-- Items that have a width or height greater than 0 occupy more than 1 x and y.
|
||||
-- @realm shared
|
||||
-- @number x The `x` coordindate to search in.
|
||||
-- @number y The `y` coordinate to search in.
|
||||
-- @treturn number x The `x` coordinate that the `Item` is located at.
|
||||
-- @treturn number y The `y` coordinate that the `Item` is located at.
|
||||
function META:GetItemAt(x, y)
|
||||
if (self.slots and self.slots[x]) then
|
||||
return self.slots[x][y]
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes an item from the inventory.
|
||||
-- @realm shared
|
||||
-- @number id The item instance ID to remove
|
||||
-- @bool[opt=false] bNoReplication Whether or not the item's removal should not be replicated
|
||||
-- @bool[opt=false] bNoDelete Whether or not the item should not be fully deleted
|
||||
-- @bool[opt=false] bTransferring Whether or not the item is being transferred to another inventory
|
||||
-- @treturn number The X position that the item was removed from
|
||||
-- @treturn number The Y position that the item was removed from
|
||||
function META:Remove(id, bNoReplication, bNoDelete, bTransferring)
|
||||
local x2, y2
|
||||
|
||||
for x = 1, self.w do
|
||||
if (self.slots[x]) then
|
||||
for y = 1, self.h do
|
||||
local item = self.slots[x][y]
|
||||
|
||||
if (item and item.id == id) then
|
||||
self.slots[x][y] = nil
|
||||
|
||||
x2 = x2 or x
|
||||
y2 = y2 or y
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (SERVER and !bNoReplication) then
|
||||
local receivers = self:GetReceivers()
|
||||
|
||||
if (istable(receivers)) then
|
||||
net.Start("ixInventoryRemove")
|
||||
net.WriteUInt(id, 32)
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.Send(receivers)
|
||||
end
|
||||
|
||||
-- we aren't removing the item - we're transferring it to another inventory
|
||||
if (!bTransferring) then
|
||||
hook.Run("InventoryItemRemoved", self, ix.item.instances[id])
|
||||
end
|
||||
|
||||
if (!bNoDelete) then
|
||||
local item = ix.item.instances[id]
|
||||
|
||||
if (item and item.OnRemoved) then
|
||||
item:OnRemoved()
|
||||
end
|
||||
|
||||
local query = mysql:Delete("ix_items")
|
||||
query:Where("item_id", id)
|
||||
query:Execute()
|
||||
|
||||
ix.item.instances[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return x2, y2
|
||||
end
|
||||
|
||||
--- Adds a player as a receiver on this `Inventory`
|
||||
-- Receivers are players who will be networked the items inside the inventory.
|
||||
--
|
||||
-- Calling this will *not* automatically sync it's current contents to the client.
|
||||
-- All future contents will be synced, but not anything that was not synced before this is called.
|
||||
--
|
||||
-- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error.
|
||||
-- @realm shared
|
||||
-- @player client The player to add as a receiver.
|
||||
function META:AddReceiver(client)
|
||||
self.receivers[client] = true
|
||||
end
|
||||
|
||||
--- The opposite of `AddReceiver`.
|
||||
-- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error.
|
||||
-- @realm shared
|
||||
-- @player client The player to remove from the receiver list.
|
||||
function META:RemoveReceiver(client)
|
||||
self.receivers[client] = nil
|
||||
end
|
||||
|
||||
--- Get all of the receivers this `Inventory` has.
|
||||
-- Receivers are players who will be networked the items inside the inventory.
|
||||
--
|
||||
-- This function will automatically sort out invalid players for you.
|
||||
-- @realm shared
|
||||
-- @treturn table result The players who are on the server and allowed to see this table.
|
||||
function META:GetReceivers()
|
||||
local result = {}
|
||||
|
||||
if (self.receivers) then
|
||||
for k, _ in pairs(self.receivers) do
|
||||
if (IsValid(k) and k:IsPlayer()) then
|
||||
result[#result + 1] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Returns a count of a *specific* `Item`s in the `Inventory`
|
||||
-- @realm shared
|
||||
-- @string uniqueID The Unique ID of the item.
|
||||
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
|
||||
-- @treturn number The amount of `Item`s this inventory has.
|
||||
-- @usage local curHighest, winner = 0, false
|
||||
-- for client, character in ix.util.GetCharacters() do
|
||||
-- local itemCount = character:GetInventory():GetItemCount('water', false)
|
||||
-- if itemCount > curHighest then
|
||||
-- curHighest = itemCount
|
||||
-- winner = character
|
||||
-- end
|
||||
-- end
|
||||
-- -- Finds the thirstiest character on the server and returns their Character ID or false if no character has water.
|
||||
function META:GetItemCount(uniqueID, onlyMain)
|
||||
local i = 0
|
||||
|
||||
for _, v in pairs(self:GetItems(onlyMain)) do
|
||||
if (v.uniqueID == uniqueID) then
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
return i
|
||||
end
|
||||
|
||||
--- Returns a table of all `Item`s in the `Inventory` by their Unique ID.
|
||||
-- Not to be confused with `GetItemsByID` or `GetItemByID` which take in an Item Instance's ID instead.
|
||||
-- @realm shared
|
||||
-- @string uniqueID The Unique ID of the item.
|
||||
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
|
||||
-- @treturn number The table of specified `Item`s this inventory has.
|
||||
function META:GetItemsByUniqueID(uniqueID, onlyMain)
|
||||
local items = {}
|
||||
|
||||
for _, v in pairs(self:GetItems(onlyMain)) do
|
||||
if (v.uniqueID == uniqueID) then
|
||||
items[#items + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
--- Returns a table of `Item`s by their base.
|
||||
-- @realm shared
|
||||
-- @string baseID The base to search for.
|
||||
-- @bool bOnlyMain Whether or not to exclude bags that are present from the search.
|
||||
function META:GetItemsByBase(baseID, bOnlyMain)
|
||||
local items = {}
|
||||
|
||||
for _, v in pairs(self:GetItems(bOnlyMain)) do
|
||||
if (v.base == baseID) then
|
||||
items[#items + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
--- Get an item by it's specific Database ID.
|
||||
-- @realm shared
|
||||
-- @number id The ID to search for.
|
||||
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
|
||||
-- @treturn item The item if it exists.
|
||||
function META:GetItemByID(id, onlyMain)
|
||||
for _, v in pairs(self:GetItems(onlyMain)) do
|
||||
if (v.id == id) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Get a table of `Item`s by their specific Database ID.
|
||||
-- It's important to note that while in 99% of cases,
|
||||
-- items will have a unique Database ID, developers or random GMod weirdness could
|
||||
-- cause a second item with the same ID to appear, even though, `ix.item.instances` will only store one of those.
|
||||
-- The inventory only stores a reference to the `ix.item.instance` ID, not the memory reference itself.
|
||||
-- @realm shared
|
||||
-- @number id The ID to search for.
|
||||
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
|
||||
-- @treturn item The item if it exists.
|
||||
function META:GetItemsByID(id, onlyMain)
|
||||
local items = {}
|
||||
|
||||
for _, v in pairs(self:GetItems(onlyMain)) do
|
||||
if (v.id == id) then
|
||||
items[#items + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
-- This function may pretty heavy.
|
||||
|
||||
--- Returns a table of all the items that an `Inventory` has.
|
||||
-- @realm shared
|
||||
-- @bool onlyMain Whether or not to exclude bags from this search.
|
||||
-- @treturn table The items this `Inventory` has.
|
||||
function META:GetItems(onlyMain)
|
||||
local items = {}
|
||||
|
||||
for _, v in pairs(self.slots) do
|
||||
for _, v2 in pairs(v) do
|
||||
if (istable(v2) and !items[v2.id]) then
|
||||
items[v2.id] = v2
|
||||
|
||||
v2.data = v2.data or {}
|
||||
local isBag = v2.data.id
|
||||
if (isBag and isBag != self:GetID() and onlyMain != true) then
|
||||
local bagInv = ix.item.inventories[isBag]
|
||||
|
||||
if (bagInv) then
|
||||
local bagItems = bagInv:GetItems()
|
||||
|
||||
table.Merge(items, bagItems)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
-- This function may pretty heavy.
|
||||
--- Returns a table of all the items that an `Inventory` has.
|
||||
-- @realm shared
|
||||
-- @bool onlyMain Whether or not to exclude bags from this search.
|
||||
-- @treturn table The items this `Inventory` has.
|
||||
function META:GetBags()
|
||||
local invs = {}
|
||||
for _, v in pairs(self.slots) do
|
||||
for _, v2 in pairs(v) do
|
||||
if (istable(v2) and v2.data) then
|
||||
local isBag = (((v2.base == "base_bags") or v2.isBag) and v2.data.id)
|
||||
|
||||
if (!table.HasValue(invs, isBag)) then
|
||||
if (isBag and isBag != self:GetID()) then
|
||||
invs[#invs + 1] = isBag
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return invs
|
||||
end
|
||||
--- Returns the item with the given unique ID (e.g `"handheld_radio"`) if it exists in this inventory.
|
||||
-- This method checks both
|
||||
-- this inventory, and any bags that this inventory has inside of it.
|
||||
-- @realm shared
|
||||
-- @string targetID Unique ID of the item to look for
|
||||
-- @tab[opt] data Item data to check for
|
||||
-- @treturn[1] Item Item that belongs to this inventory with the given criteria
|
||||
-- @treturn[2] bool `false` if the item does not exist
|
||||
-- @see HasItems
|
||||
-- @see HasItemOfBase
|
||||
-- @usage local item = inventory:HasItem("handheld_radio")
|
||||
--
|
||||
-- if (item) then
|
||||
-- -- do something with the item table
|
||||
-- end
|
||||
function META:HasItem(targetID, data)
|
||||
local items = self:GetItems()
|
||||
|
||||
for _, v in pairs(items) do
|
||||
if (v.uniqueID == targetID) then
|
||||
if (data) then
|
||||
local itemData = v.data
|
||||
local bFound = true
|
||||
|
||||
for dataKey, dataVal in pairs(data) do
|
||||
if (itemData[dataKey] != dataVal) then
|
||||
bFound = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!bFound) then
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Checks whether or not the `Inventory` has a table of items.
|
||||
-- This function takes a table with **no** keys and runs in order of first item > last item,
|
||||
--this is due to the usage of the `#` operator in the function.
|
||||
--
|
||||
-- @realm shared
|
||||
-- @tab targetIDs A table of `Item` Unique ID's.
|
||||
-- @treturn[1] bool true Whether or not the `Inventory` has all of the items.
|
||||
-- @treturn[1] table targetIDs Your provided targetIDs table, but it will be empty.
|
||||
-- @treturn[2] bool false
|
||||
-- @treturn[2] table targetIDs Table consisting of the items the `Inventory` did **not** have.
|
||||
-- @usage local itemFilter = {'water', 'water_sparkling'}
|
||||
-- if not Entity(1):GetCharacter():GetInventory():HasItems(itemFilter) then return end
|
||||
-- -- Filters out if this player has both a water, and a sparkling water.
|
||||
function META:HasItems(targetIDs)
|
||||
local items = self:GetItems()
|
||||
local count = #targetIDs -- assuming array
|
||||
targetIDs = table.Copy(targetIDs)
|
||||
|
||||
for _, v in pairs(items) do
|
||||
for k, targetID in ipairs(targetIDs) do
|
||||
if (v.uniqueID == targetID) then
|
||||
table.remove(targetIDs, k)
|
||||
count = count - 1
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return count <= 0, targetIDs
|
||||
end
|
||||
|
||||
--- Whether or not an `Inventory` has an item of a base, optionally with specified data.
|
||||
-- This function has an optional `data` argument, which will take a `table`.
|
||||
-- it will match if the data of the item is correct or not.
|
||||
--
|
||||
-- Items which are a base will automatically have base_ prefixed to their Unique ID, if you are having
|
||||
-- trouble finding your base, that is probably why.
|
||||
-- @realm shared
|
||||
-- @string baseID The Item Base's Unique ID.
|
||||
-- @tab[opt] data The Item's data to compare against.
|
||||
-- @treturn[1] item The first `Item` of `baseID` that is found and there is no `data` argument or `data` was matched.
|
||||
-- @treturn[2] false If no `Item`s of `baseID` is found or the `data` argument, if specified didn't match.
|
||||
-- @usage local bHasWeaponEquipped = Entity(1):GetCharacter():GetInventory():HasItemOfBase('base_weapons', {['equip'] = true})
|
||||
-- if bHasWeaponEquipped then
|
||||
-- Entity(1):Notify('One gun is fun, two guns is Woo-tastic.')
|
||||
-- end
|
||||
-- -- Notifies the player that they should get some more guns.
|
||||
function META:HasItemOfBase(baseID, data)
|
||||
local items = self:GetItems()
|
||||
|
||||
for _, v in pairs(items) do
|
||||
if (v.base == baseID) then
|
||||
if (data) then
|
||||
local itemData = v.data
|
||||
local bFound = true
|
||||
|
||||
for dataKey, dataVal in pairs(data) do
|
||||
if (itemData[dataKey] != dataVal) then
|
||||
bFound = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!bFound) then
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Sends a specific slot to a character.
|
||||
-- This will *not* send all of the slots of the `Item` to the character, items can occupy multiple slots.
|
||||
--
|
||||
-- This will call `OnSendData` on the Item using all of the `Inventory`'s receivers.
|
||||
--
|
||||
-- This function should *not* be used to sync an entire inventory, if you need to do that, use `AddReceiver` and `Sync`.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @number x The Inventory x position to send.
|
||||
-- @number y The Inventory y position to send.
|
||||
-- @item[opt] item The item to send, if any.
|
||||
-- @see AddReceiver
|
||||
-- @see Sync
|
||||
function META:SendSlot(x, y, item)
|
||||
local receivers = self:GetReceivers()
|
||||
local sendData = item and item.data and !table.IsEmpty(item.data) and item.data or {}
|
||||
|
||||
net.Start("ixInventorySet")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteUInt(x, 6)
|
||||
net.WriteUInt(y, 6)
|
||||
net.WriteString(item and item.uniqueID or "")
|
||||
net.WriteUInt(item and item.id or 0, 32)
|
||||
net.WriteUInt(self.owner or 0, 32)
|
||||
net.WriteTable(sendData)
|
||||
net.Send(receivers)
|
||||
|
||||
if (item) then
|
||||
for _, v in pairs(receivers) do
|
||||
item:Call("OnSendData", v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets whether or not an `Inventory` should save.
|
||||
-- This will prevent an `Inventory` from updating in the Database, if the inventory is already saved,
|
||||
-- it will not be deleted when unloaded.
|
||||
-- @realm server
|
||||
-- @bool bNoSave Whether or not the Inventory should save.
|
||||
function META:SetShouldSave(bNoSave)
|
||||
self.noSave = bNoSave
|
||||
end
|
||||
|
||||
--- Gets whether or not an `Inventory` should save.
|
||||
-- Inventories that are marked to not save will not update in the Database, if they inventory is already saved,
|
||||
-- it will not be deleted when unloaded.
|
||||
-- @realm server
|
||||
-- @treturn[1] bool Returns the field `noSave`.
|
||||
-- @treturn[2] bool Returns true if the field `noSave` is not registered to this inventory.
|
||||
function META:GetShouldSave()
|
||||
return self.noSave or true
|
||||
end
|
||||
|
||||
--- Add an item to the inventory.
|
||||
-- @realm server
|
||||
-- @param uniqueID The item unique ID (e.g `"handheld_radio"`) or instance ID (e.g `1024`) to add to the inventory
|
||||
-- @number[opt=1] quantity The quantity of the item to add
|
||||
-- @tab data Item data to add to the item
|
||||
-- @number[opt=nil] x The X position for the item
|
||||
-- @number[opt=nil] y The Y position for the item
|
||||
-- @bool[opt=false] noReplication Whether or not the item's addition should not be replicated
|
||||
-- @treturn[1] bool Whether the add was successful or not
|
||||
-- @treturn[1] string The error, if applicable
|
||||
-- @treturn[2] number The X position that the item was added to
|
||||
-- @treturn[2] number The Y position that the item was added to
|
||||
-- @treturn[2] number The inventory ID that the item was added to
|
||||
function META:Add(uniqueID, quantity, data, x, y, noReplication)
|
||||
quantity = quantity or 1
|
||||
|
||||
if (quantity < 1) then
|
||||
return false, "noOwner"
|
||||
end
|
||||
|
||||
if (!isnumber(uniqueID) and quantity > 1) then
|
||||
for _ = 1, quantity do
|
||||
local bSuccess, error = self:Add(uniqueID, 1, data)
|
||||
|
||||
if (!bSuccess) then
|
||||
return false, error
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local client = self.GetOwner and self:GetOwner() or nil
|
||||
local item = isnumber(uniqueID) and ix.item.instances[uniqueID] or ix.item.list[uniqueID]
|
||||
local targetInv = self
|
||||
local bagInv
|
||||
|
||||
if (!item) then
|
||||
return false, "invalidItem"
|
||||
end
|
||||
|
||||
if (isnumber(uniqueID)) then
|
||||
local oldInvID = item.invID
|
||||
|
||||
if (!x and !y) then
|
||||
x, y, bagInv = self:FindEmptySlot(item.width, item.height)
|
||||
end
|
||||
|
||||
if (bagInv) then
|
||||
targetInv = bagInv
|
||||
end
|
||||
|
||||
-- we need to check for owner since the item instance already exists
|
||||
if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter() and
|
||||
item:GetPlayerID() == client:SteamID64() and item:GetCharacterID() != client:GetCharacter():GetID()) then
|
||||
return false, "itemOwned"
|
||||
end
|
||||
|
||||
if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv, x, y) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
if (x and y) then
|
||||
targetInv.slots[x] = targetInv.slots[x] or {}
|
||||
targetInv.slots[x][y] = true
|
||||
|
||||
item.gridX = x
|
||||
item.gridY = y
|
||||
item.invID = targetInv:GetID()
|
||||
|
||||
for x2 = 0, item.width - 1 do
|
||||
local index = x + x2
|
||||
|
||||
for y2 = 0, item.height - 1 do
|
||||
targetInv.slots[index] = targetInv.slots[index] or {}
|
||||
targetInv.slots[index][y + y2] = item
|
||||
end
|
||||
end
|
||||
|
||||
if (!noReplication) then
|
||||
targetInv:SendSlot(x, y, item)
|
||||
end
|
||||
|
||||
if (!self.noSave) then
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("inventory_id", targetInv:GetID())
|
||||
query:Update("x", x)
|
||||
query:Update("y", y)
|
||||
query:Where("item_id", item.id)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
hook.Run("InventoryItemAdded", ix.item.inventories[oldInvID], targetInv, item)
|
||||
|
||||
return x, y, targetInv:GetID()
|
||||
else
|
||||
return false, "noFit"
|
||||
end
|
||||
else
|
||||
if (!x and !y) then
|
||||
x, y, bagInv = self:FindEmptySlot(item.width, item.height)
|
||||
end
|
||||
|
||||
if (bagInv) then
|
||||
targetInv = bagInv
|
||||
end
|
||||
|
||||
if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv, x, y) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
if (x and y) then
|
||||
for x2 = 0, item.width - 1 do
|
||||
local index = x + x2
|
||||
|
||||
for y2 = 0, item.height - 1 do
|
||||
targetInv.slots[index] = targetInv.slots[index] or {}
|
||||
targetInv.slots[index][y + y2] = true
|
||||
end
|
||||
end
|
||||
|
||||
local characterID
|
||||
local playerID
|
||||
|
||||
if (self.owner) then
|
||||
local character = ix.char.loaded[self.owner]
|
||||
|
||||
if (character) then
|
||||
characterID = character.id
|
||||
playerID = character.steamID
|
||||
end
|
||||
end
|
||||
|
||||
ix.item.Instance(targetInv:GetID(), uniqueID, data, x, y, function(newItem)
|
||||
newItem.gridX = x
|
||||
newItem.gridY = y
|
||||
|
||||
for x2 = 0, newItem.width - 1 do
|
||||
local index = x + x2
|
||||
|
||||
for y2 = 0, newItem.height - 1 do
|
||||
targetInv.slots[index] = targetInv.slots[index] or {}
|
||||
targetInv.slots[index][y + y2] = newItem
|
||||
end
|
||||
end
|
||||
|
||||
if (!noReplication) then
|
||||
targetInv:SendSlot(x, y, newItem)
|
||||
end
|
||||
|
||||
hook.Run("InventoryItemAdded", nil, targetInv, newItem)
|
||||
end, characterID, playerID)
|
||||
|
||||
return x, y, targetInv:GetID()
|
||||
else
|
||||
return false, "noFit"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Syncs the `Inventory` to the receiver.
|
||||
-- This will call Item.OnSendData on every item in the `Inventory`.
|
||||
-- @realm server
|
||||
-- @player receiver The player to
|
||||
function META:Sync(receiver)
|
||||
local slots = {}
|
||||
|
||||
for x, items in pairs(self.slots) do
|
||||
for y, item in pairs(items) do
|
||||
if (istable(item) and item.gridX == x and item.gridY == y) then
|
||||
slots[#slots + 1] = {x, y, item.uniqueID, item.id, item.data}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("ixInventorySync")
|
||||
net.WriteTable(slots)
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteUInt(self.w, 6)
|
||||
net.WriteUInt(self.h, 6)
|
||||
net.WriteType(self.owner)
|
||||
net.WriteTable(self.vars or {})
|
||||
net.Send(receiver)
|
||||
|
||||
for _, v in pairs(self:GetItems()) do
|
||||
v:Call("OnSendData", receiver)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.meta.inventory = META
|
||||
709
gamemodes/helix/gamemode/core/meta/sh_item.lua
Normal file
709
gamemodes/helix/gamemode/core/meta/sh_item.lua
Normal file
@@ -0,0 +1,709 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Interactable entities that can be held in inventories.
|
||||
|
||||
Items are objects that are contained inside of an `Inventory`, or as standalone entities if they are dropped in the world. They
|
||||
usually have functionality that provides more gameplay aspects to the schema. For example, the zipties in the HL2 RP schema
|
||||
allow a player to tie up and search a player.
|
||||
|
||||
For an item to have an actual presence, they need to be instanced (usually with `ix.item.Instance`). Items describe the
|
||||
properties, while instances are a clone of these properties that can have their own unique data (e.g an ID card will have the
|
||||
same name but different numerical IDs). You can think of items as the class, while instances are objects of the `Item` class.
|
||||
|
||||
## Creating item classes (`ItemStructure`)
|
||||
Item classes are defined in their own file inside of your schema or plugin's `items/` folder. In these item class files you
|
||||
specify how instances of the item behave. This includes default values for basic things like the item's name and description,
|
||||
to more advanced things by overriding extra methods from an item base. See `ItemStructure` for information on how to define
|
||||
a basic item class.
|
||||
|
||||
Item classes in this folder are automatically loaded by Helix when the server starts up.
|
||||
|
||||
## Item bases
|
||||
If many items share the same functionality (i.e a can of soda and a bottle of water can both be consumed), then you might want
|
||||
to consider using an item base to reduce the amount of duplication for these items. Item bases are defined the same way as
|
||||
regular item classes, but they are placed in the `items/base/` folder in your schema or plugin. For example, a `consumables`
|
||||
base would be in `items/base/sh_consumables.lua`.
|
||||
|
||||
Any items that you want to use this base must be placed in a subfolder that has the name of the base you want that item to use.
|
||||
For example, for a bottled water item to use the consumable base, it must be placed in `items/consumables/sh_bottled_water.lua`.
|
||||
This also means that you cannot place items into subfolders as you wish, since the framework will try to use an item base that
|
||||
doesn't exist.
|
||||
|
||||
The default item bases that come with Helix are:
|
||||
|
||||
- `ammo` - provides ammo to any items with the `weapons` base
|
||||
- `bags` - holds an inventory that other items can be stored inside of
|
||||
- `outfit` - changes the appearance of the player that wears it
|
||||
- `pacoutfit` - changes the appearance of the player that wears it using PAC3
|
||||
- `weapons` - makes any SWEP into an item that can be equipped
|
||||
|
||||
These item bases usually come with extra values and methods that you can define/override in order to change their functionality.
|
||||
You should take a look at the source code for these bases to see their capabilities.
|
||||
|
||||
## Item functions (`ItemFunctionStructure`)
|
||||
Requiring players to interact with items in order for them to do something is quite common. As such, there is already a built-in
|
||||
mechanism to allow players to right-click items and show a list of available options. Item functions are defined in your item
|
||||
class file in the `ITEM.functions` table. See `ItemFunctionStructure` on how to define them.
|
||||
|
||||
Helix comes with `drop`, `take`, and `combine` item functions by default that allows items to be dropped from a player's
|
||||
inventory, picked up from the world, and combining items together. These can be overridden by defining an item function
|
||||
in your item class file with the same name. See the `bags` base for example usage of the `combine` item function.
|
||||
|
||||
## Item icons (`ItemIconStructure`)
|
||||
Icons for items sometimes don't line up quite right, in which case you can modify an item's `iconCam` value and line up the
|
||||
rendered model as needed. See `ItemIconStructure` for more details.
|
||||
]]
|
||||
-- @classmod Item
|
||||
|
||||
--[[--
|
||||
All item functions live inside of an item's `functions` table. An item function entry includes a few methods and fields you can
|
||||
use to customize the functionality and appearance of the item function. An example item function is below:
|
||||
|
||||
-- this item function's unique ID is "MyFunction"
|
||||
ITEM.functions.MyFunction = {
|
||||
name = "myFunctionPhrase", -- uses the "myFunctionPhrase" language phrase when displaying in the UI
|
||||
tip = "myFunctionDescription", -- uses the "myFunctionDescription" language phrase when displaying in the UI
|
||||
icon = "icon16/add.png", -- path to the icon material
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
local entity = item.entity -- only set if this is function is being ran while the item is in the world
|
||||
|
||||
if (IsValid(client)) then
|
||||
client:ChatPrint("This is a test.")
|
||||
|
||||
if (IsValid(entity)) then
|
||||
client:ChatPrint(entity:GetName())
|
||||
end
|
||||
end
|
||||
|
||||
-- do not remove this item from the owning player's inventory
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
-- only allow admins to run this item function
|
||||
local client = item.player
|
||||
return IsValid(client) and client:IsAdmin()
|
||||
end
|
||||
}
|
||||
]]
|
||||
-- @table ItemFunctionStructure
|
||||
-- @realm shared
|
||||
-- @field[type=string,opt] name Language phrase to use when displaying this item function's name in the UI. If not specified,
|
||||
-- then it will use the unique ID of the item function
|
||||
-- @field[type=string,opt] tip Language phrase to use when displaying this item function's detailed description in the UI
|
||||
-- @field[type=string,opt] icon Path to the material to use when displaying this item function's icon
|
||||
-- @field[type=function] OnRun Function to call when the item function is ran. This function is **ONLY** ran on the server.
|
||||
--
|
||||
-- The only argument passed into this function is the instance of the item being called. The instance will have its `player`
|
||||
-- field set if the item function is being ran by a player (which it should be most of the time). It will also have its `entity`
|
||||
-- field set if the item function is being ran while the item is in the world, and not in a player's inventory.
|
||||
--
|
||||
-- The item will be removed after the item function is ran. If you want to prevent this behaviour, then you can return `false`
|
||||
-- in this function. See the example above.
|
||||
-- @field[type=function] OnCanRun Function to call when checking whether or not this item function can be ran. This function is
|
||||
-- ran **BOTH** on the client and server.
|
||||
--
|
||||
-- The arguments are the same as `OnCanRun`, and the `player` and `entity` fields will be set on the item instance accordingly.
|
||||
-- Returning `true` will allow the item function to be ran. Returning `false` will prevent it from running and additionally
|
||||
-- hide it from the UI. See the example above.
|
||||
-- @field[type=function,opt] OnClick This function is called when the player clicks on this item function's entry in the UI.
|
||||
-- This function is ran **ONLY** on the client, and is only ran if `OnCanRun` succeeds.
|
||||
--
|
||||
-- The same arguments from `OnCanRun` and `OnRun` apply to this function.
|
||||
|
||||
--[[--
|
||||
Changing the way an item's icon is rendered is done by modifying the location and angle of the model, as well as the FOV of the
|
||||
camera. You can tweak the values in code, or use the `ix_dev_icon` console command to visually position the model and camera. An
|
||||
example entry for an item's icon is below:
|
||||
|
||||
ITEM.iconCam = {
|
||||
pos = Vector(0, 0, 60),
|
||||
ang = Angle(90, 0, 0),
|
||||
fov = 45
|
||||
}
|
||||
|
||||
Note that this will probably not work for your item's specific model, since every model has a different size, origin, etc. All
|
||||
item icons need to be tweaked individually.
|
||||
]]
|
||||
-- @table ItemIconStructure
|
||||
-- @realm client
|
||||
-- @field[type=vector] pos Location of the model relative to the camera. +X is forward, +Z is up
|
||||
-- @field[type=angle] ang Angle of the model
|
||||
-- @field[type=number] fov FOV of the camera
|
||||
|
||||
--[[--
|
||||
When creating an item class, the file will have a global table `ITEM` set that you use to define the item's values/methods. An
|
||||
example item class is below:
|
||||
|
||||
`items/sh_brick.lua`
|
||||
ITEM.name = "Brick"
|
||||
ITEM.description = "A brick. Pretty self-explanatory. You can eat it but you'll probably lose some teeth."
|
||||
ITEM.model = Model("models/props_debris/concrete_cynderblock001.mdl")
|
||||
ITEM.width = 1
|
||||
ITEM.height = 1
|
||||
ITEM.price = 25
|
||||
|
||||
Note that the below list only includes the default fields available for *all* items, and not special ones defined in custom
|
||||
item bases.
|
||||
]]
|
||||
-- @table ItemStructure
|
||||
-- @realm shared
|
||||
-- @field[type=string] name Display name of the item
|
||||
-- @field[type=string] description Detailed description of the item
|
||||
-- @field[type=string] model Model to use for the item's icon and when it's dropped in the world
|
||||
-- @field[type=number,opt=1] width Width of the item in grid cells
|
||||
-- @field[type=number,opt=1] height Height of the item in grid cells
|
||||
-- @field[type=number,opt=0] price How much money it costs to purchase this item in the business menu
|
||||
-- @field[type=string,opt] category Name of the category this item belongs to - mainly used for the business menu
|
||||
-- @field[type=boolean,opt=false] noBusiness Whether or not to disallow purchasing this item in the business menu
|
||||
-- @field[type=table,opt] factions List of factions allowed to purchase this item in the business menu
|
||||
-- @field[type=table,opt] classes List of character classes allowed to purchase this item in the business menu. Classes are
|
||||
-- checked after factions, so the character must also be in an allowed faction
|
||||
-- @field[type=string,opt] flag List of flags (as a string - e.g `"a"` or `"abc"`) allowed to purchase this item in the
|
||||
-- business menu. Flags are checked last, so the character must also be in an allowed faction and class
|
||||
-- @field[type=ItemIconStructure,opt] iconCam How to render this item's icon
|
||||
-- @field[type=table,opt] functions List of all item functions that this item has. See `ItemFunctionStructure` on how to define
|
||||
-- new item functions
|
||||
|
||||
local ITEM = ix.meta.item or {}
|
||||
ITEM.__index = ITEM
|
||||
ITEM.name = "Undefined"
|
||||
ITEM.description = ITEM.description or "An item that is undefined."
|
||||
ITEM.id = ITEM.id or 0
|
||||
ITEM.uniqueID = "undefined"
|
||||
|
||||
--- Returns a string representation of this item.
|
||||
-- @realm shared
|
||||
-- @treturn string String representation
|
||||
-- @usage print(ix.item.instances[1])
|
||||
-- > "item[1]"
|
||||
function ITEM:__tostring()
|
||||
return "item["..self.uniqueID.."]["..self.id.."]"
|
||||
end
|
||||
|
||||
--- Returns true if this item is equal to another item. Internally, this checks item IDs.
|
||||
-- @realm shared
|
||||
-- @item other Item to compare to
|
||||
-- @treturn bool Whether or not this item is equal to the given item
|
||||
-- @usage print(ix.item.instances[1] == ix.item.instances[2])
|
||||
-- > false
|
||||
function ITEM:__eq(other)
|
||||
return self:GetID() == other:GetID()
|
||||
end
|
||||
|
||||
--- Returns this item's database ID. This is guaranteed to be unique.
|
||||
-- @realm shared
|
||||
-- @treturn number Unique ID of item
|
||||
function ITEM:GetID()
|
||||
return self.id
|
||||
end
|
||||
|
||||
--- Returns the name of the item.
|
||||
-- @realm shared
|
||||
-- @treturn string The name of the item
|
||||
function ITEM:GetName()
|
||||
return (CLIENT and L(self.name) or self.name)
|
||||
end
|
||||
|
||||
--- Returns the description of the item.
|
||||
-- @realm shared
|
||||
-- @treturn string The description of the item
|
||||
function ITEM:GetDescription()
|
||||
if (!self.description) then return "ERROR" end
|
||||
|
||||
return L(self.description or "noDesc")
|
||||
end
|
||||
|
||||
--- Returns the model of the item.
|
||||
-- @realm shared
|
||||
-- @treturn string The model of the item
|
||||
function ITEM:GetModel()
|
||||
return self.model
|
||||
end
|
||||
|
||||
--- Returns the skin of the item.
|
||||
-- @realm shared
|
||||
-- @treturn number The skin of the item
|
||||
function ITEM:GetSkin()
|
||||
return self.skin or 0
|
||||
end
|
||||
|
||||
--- Returns the bodygroup of the item model.
|
||||
-- @realm shared
|
||||
-- @return The body groups to set. Each single-digit number in the string represents a separate bodygroup.
|
||||
|
||||
function ITEM:GetModelBodygroups()
|
||||
return self.modelBodygroups or ""
|
||||
end
|
||||
|
||||
function ITEM:GetMaterial()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the ID of the owning character, if one exists.
|
||||
-- @realm shared
|
||||
-- @treturn number The owning character's ID
|
||||
function ITEM:GetCharacterID()
|
||||
return self.characterID
|
||||
end
|
||||
|
||||
--- Returns the SteamID64 of the owning player, if one exists.
|
||||
-- @realm shared
|
||||
-- @treturn number The owning player's SteamID64
|
||||
function ITEM:GetPlayerID()
|
||||
return self.playerID
|
||||
end
|
||||
|
||||
--- A utility function which prints the item's details.
|
||||
-- @realm shared
|
||||
-- @bool[opt=false] detail Whether additional detail should be printed or not(Owner, X position, Y position)
|
||||
function ITEM:Print(detail)
|
||||
if (detail == true) then
|
||||
print(Format("%s[%s]: >> [%s](%s,%s)", self.uniqueID, self.id, self.owner, self.gridX, self.gridY))
|
||||
else
|
||||
print(Format("%s[%s]", self.uniqueID, self.id))
|
||||
end
|
||||
end
|
||||
|
||||
--- A utility function printing the item's stored data.
|
||||
-- @realm shared
|
||||
function ITEM:PrintData()
|
||||
self:Print(true)
|
||||
print("ITEM DATA:")
|
||||
for k, v in pairs(self.data) do
|
||||
print(Format("[%s] = %s", k, v))
|
||||
end
|
||||
end
|
||||
|
||||
--- Calls one of the item's methods.
|
||||
-- @realm shared
|
||||
-- @string method The method to be called
|
||||
-- @player client The client to pass when calling the method, if applicable
|
||||
-- @entity entity The eneity to pass when calling the method, if applicable
|
||||
-- @param ... Arguments to pass to the method
|
||||
-- @return The values returned by the method
|
||||
function ITEM:Call(method, client, entity, ...)
|
||||
local oldPlayer, oldEntity = self.player, self.entity
|
||||
|
||||
self.player = client or self.player
|
||||
self.entity = entity or self.entity
|
||||
|
||||
if (isfunction(self[method])) then
|
||||
local results = {self[method](self, ...)}
|
||||
|
||||
self.player = nil
|
||||
self.entity = nil
|
||||
|
||||
return unpack(results)
|
||||
end
|
||||
|
||||
self.player = oldPlayer
|
||||
self.entity = oldEntity
|
||||
end
|
||||
|
||||
--- Returns the player that owns this item.
|
||||
-- @realm shared
|
||||
-- @treturn player Player owning this item
|
||||
function ITEM:GetOwner()
|
||||
local inventory = ix.item.inventories[self.invID]
|
||||
|
||||
if (inventory) then
|
||||
return inventory.GetOwner and inventory:GetOwner()
|
||||
end
|
||||
|
||||
local id = self:GetID()
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
local character = v:GetCharacter()
|
||||
|
||||
if (character and character:GetInventory():GetItemByID(id)) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets a key within the item's data.
|
||||
-- @realm shared
|
||||
-- @string key The key to store the value within
|
||||
-- @param[opt=nil] value The value to store within the key
|
||||
-- @tab[opt=nil] receivers The players to replicate the data on
|
||||
-- @bool[opt=false] noSave Whether to disable saving the data on the database or not
|
||||
-- @bool[opt=false] noCheckEntity Whether to disable setting the data on the entity, if applicable
|
||||
function ITEM:SetData(key, value, receivers, noSave, noCheckEntity)
|
||||
self.data = self.data or {}
|
||||
self.data[key] = value
|
||||
|
||||
if (SERVER) then
|
||||
if (!noCheckEntity) then
|
||||
local ent = self:GetEntity()
|
||||
|
||||
if (IsValid(ent)) then
|
||||
local data = ent:GetNetVar("data", {})
|
||||
data[key] = value
|
||||
|
||||
ent:SetNetVar("data", data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (receivers != false and (receivers or self:GetOwner())) then
|
||||
net.Start("ixInventoryData")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Send(receivers or self:GetOwner())
|
||||
end
|
||||
|
||||
if (!noSave and ix.db) then
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("data", util.TableToJSON(self.data))
|
||||
query:Where("item_id", self:GetID())
|
||||
query:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the value stored on a key within the item's data.
|
||||
-- @realm shared
|
||||
-- @string key The key in which the value is stored
|
||||
-- @param[opt=nil] default The value to return in case there is no value stored in the key
|
||||
-- @return The value stored within the key
|
||||
function ITEM:GetData(key, default)
|
||||
self.data = self.data or {}
|
||||
|
||||
if (self.data) then
|
||||
if (key == true) then
|
||||
return self.data
|
||||
end
|
||||
|
||||
local value = self.data[key]
|
||||
|
||||
if (value != nil) then
|
||||
return value
|
||||
elseif (IsValid(self.entity)) then
|
||||
local data = self.entity:GetNetVar("data", {})
|
||||
value = data[key]
|
||||
|
||||
if (value != nil) then
|
||||
return value
|
||||
end
|
||||
end
|
||||
else
|
||||
self.data = {}
|
||||
end
|
||||
|
||||
if (default != nil) then
|
||||
return default
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
--- Changes the function called on specific events for the item.
|
||||
-- @realm shared
|
||||
-- @string name The name of the hook
|
||||
-- @func func The function to call once the event occurs
|
||||
function ITEM:Hook(name, func)
|
||||
if (name) then
|
||||
self.hooks[name] = func
|
||||
end
|
||||
end
|
||||
|
||||
--- Changes the function called after hooks for specific events for the item.
|
||||
-- @realm shared
|
||||
-- @string name The name of the hook
|
||||
-- @func func The function to call after the original hook was called
|
||||
function ITEM:PostHook(name, func)
|
||||
if (name) then
|
||||
self.postHooks[name] = func
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes the item.
|
||||
-- @realm shared
|
||||
-- @bool bNoReplication Whether or not the item's removal should not be replicated.
|
||||
-- @bool bNoDelete Whether or not the item should not be fully deleted
|
||||
-- @treturn bool Whether the item was successfully deleted or not
|
||||
function ITEM:Remove(bNoReplication, bNoDelete)
|
||||
local inv = ix.item.inventories[self.invID]
|
||||
|
||||
if (self.invID > 0 and inv) then
|
||||
local failed = false
|
||||
|
||||
for x = self.gridX, self.gridX + (self.width - 1) do
|
||||
if (inv.slots[x]) then
|
||||
for y = self.gridY, self.gridY + (self.height - 1) do
|
||||
local item = inv.slots[x][y]
|
||||
|
||||
if (item and item.id == self.id) then
|
||||
inv.slots[x][y] = nil
|
||||
else
|
||||
failed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (failed) then
|
||||
local items = inv:GetItems()
|
||||
|
||||
inv.slots = {}
|
||||
for _, v in pairs(items) do
|
||||
if (v.invID == inv:GetID()) then
|
||||
for x = self.gridX, self.gridX + (self.width - 1) do
|
||||
for y = self.gridY, self.gridY + (self.height - 1) do
|
||||
inv.slots[x][y] = v.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (IsValid(inv.owner) and inv.owner:IsPlayer()) then
|
||||
inv:Sync(inv.owner, true)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
else
|
||||
-- @todo definition probably isn't needed
|
||||
inv = ix.item.inventories[self.invID]
|
||||
|
||||
if (inv) then
|
||||
ix.item.inventories[self.invID][self.id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if (SERVER and !bNoReplication) then
|
||||
local entity = self:GetEntity()
|
||||
|
||||
if (IsValid(entity)) then
|
||||
entity:Remove()
|
||||
end
|
||||
|
||||
local receivers = inv.GetReceivers and inv:GetReceivers()
|
||||
|
||||
if (self.invID != 0 and istable(receivers)) then
|
||||
net.Start("ixInventoryRemove")
|
||||
net.WriteUInt(self.id, 32)
|
||||
net.WriteUInt(self.invID, 32)
|
||||
net.Send(receivers)
|
||||
end
|
||||
|
||||
if (!bNoDelete) then
|
||||
local item = ix.item.instances[self.id]
|
||||
|
||||
if (item and item.OnRemoved) then
|
||||
item:OnRemoved()
|
||||
end
|
||||
|
||||
local query = mysql:Delete("ix_items")
|
||||
query:Where("item_id", self.id)
|
||||
query:Execute()
|
||||
|
||||
ix.item.instances[self.id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Returns the item's entity.
|
||||
-- @realm server
|
||||
-- @treturn entity The entity of the item
|
||||
function ITEM:GetEntity()
|
||||
local id = self:GetID()
|
||||
|
||||
for _, v in ipairs(ents.FindByClass("ix_item")) do
|
||||
if (v.ixItemID == id) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Spawn an item entity based off the item table.
|
||||
-- @realm server
|
||||
-- @param[type=vector] position The position in which the item's entity will be spawned
|
||||
-- @param[type=angle] angles The angles at which the item's entity will spawn
|
||||
-- @treturn entity The spawned entity
|
||||
function ITEM:Spawn(position, angles)
|
||||
-- Check if the item has been created before.
|
||||
if (ix.item.instances[self.id]) then
|
||||
local client
|
||||
|
||||
-- Spawn the actual item entity.
|
||||
local entity = ents.Create("ix_item")
|
||||
entity:Spawn()
|
||||
entity:SetAngles(angles or Angle(0, 0, 0))
|
||||
entity:SetItem(self.id)
|
||||
|
||||
-- If the first argument is a player, then we will find a position to drop
|
||||
-- the item based off their aim.
|
||||
if (type(position) == "Player") then
|
||||
client = position
|
||||
position = position:GetItemDropPos(entity)
|
||||
end
|
||||
|
||||
entity:SetPos(position)
|
||||
|
||||
if (IsValid(client)) then
|
||||
entity.ixSteamID = client:SteamID()
|
||||
entity.ixCharID = client:GetCharacter():GetID()
|
||||
entity:SetNetVar("owner", entity.ixCharID)
|
||||
end
|
||||
|
||||
hook.Run("OnItemSpawned", entity)
|
||||
return entity
|
||||
end
|
||||
end
|
||||
|
||||
--- Transfers an item to a specific inventory.
|
||||
-- @realm server
|
||||
-- @number invID The inventory to transfer the item to
|
||||
-- @number x The X position to which the item should be transferred on the new inventory
|
||||
-- @number y The Y position to which the item should be transferred on the new inventory
|
||||
-- @player client The player to which the item is being transferred
|
||||
-- @bool noReplication Whether there should be no replication of the transferral
|
||||
-- @bool isLogical Whether or not an entity should spawn if the item is transferred to the world
|
||||
-- @treturn[1] bool Whether the transfer was successful or not
|
||||
-- @treturn[1] string The error, if applicable
|
||||
function ITEM:Transfer(invID, x, y, client, noReplication, isLogical)
|
||||
invID = invID or 0
|
||||
|
||||
if (self.invID == invID) then
|
||||
return false, "same inv"
|
||||
end
|
||||
|
||||
local inventory = ix.item.inventories[invID]
|
||||
local curInv = ix.item.inventories[self.invID or 0]
|
||||
|
||||
if (curInv and !IsValid(client)) then
|
||||
client = curInv.GetOwner and curInv:GetOwner() or nil
|
||||
end
|
||||
|
||||
-- check if this item doesn't belong to another one of this player's characters
|
||||
local itemPlayerID = self:GetPlayerID()
|
||||
local itemCharacterID = self:GetCharacterID()
|
||||
|
||||
if (!self.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then
|
||||
local playerID = client:SteamID64()
|
||||
local characterID = client:GetCharacter():GetID()
|
||||
|
||||
if (itemPlayerID and itemCharacterID) then
|
||||
if (itemPlayerID == playerID and itemCharacterID != characterID) then
|
||||
return false, "itemOwned"
|
||||
end
|
||||
else
|
||||
self.characterID = characterID
|
||||
self.playerID = playerID
|
||||
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("character_id", characterID)
|
||||
query:Update("player_id", playerID)
|
||||
query:Where("item_id", self:GetID())
|
||||
query:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
if (hook.Run("CanTransferItem", self, curInv, inventory, x, y) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
local authorized = false
|
||||
|
||||
if (inventory and inventory.OnAuthorizeTransfer and inventory:OnAuthorizeTransfer(client, curInv, self)) then
|
||||
authorized = true
|
||||
end
|
||||
|
||||
if (!authorized and self.CanTransfer and self:CanTransfer(curInv, inventory) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
if (curInv) then
|
||||
if (invID and invID > 0 and inventory) then
|
||||
local targetInv = inventory
|
||||
local bagInv
|
||||
|
||||
if (!x and !y) then
|
||||
x, y, bagInv = inventory:FindEmptySlot(self.width, self.height)
|
||||
end
|
||||
|
||||
if (bagInv) then
|
||||
targetInv = bagInv
|
||||
end
|
||||
|
||||
if (!x or !y) then
|
||||
return false, "noFit"
|
||||
end
|
||||
|
||||
local prevID = self.invID
|
||||
local status, result = targetInv:Add(self.id, nil, nil, x, y, noReplication)
|
||||
|
||||
if (status) then
|
||||
if (self.invID > 0 and prevID != 0) then
|
||||
-- we are transferring this item from one inventory to another
|
||||
curInv:Remove(self.id, false, true, true)
|
||||
|
||||
if (self.OnTransferred) then
|
||||
self:OnTransferred(curInv, inventory)
|
||||
end
|
||||
|
||||
hook.Run("OnItemTransferred", self, curInv, inventory)
|
||||
return true
|
||||
elseif (self.invID > 0 and prevID == 0) then
|
||||
-- we are transferring this item from the world to an inventory
|
||||
ix.item.inventories[0][self.id] = nil
|
||||
|
||||
if (self.OnTransferred) then
|
||||
self:OnTransferred(curInv, inventory)
|
||||
end
|
||||
|
||||
hook.Run("OnItemTransferred", self, curInv, inventory)
|
||||
return true
|
||||
end
|
||||
else
|
||||
return false, result
|
||||
end
|
||||
elseif (IsValid(client)) then
|
||||
-- we are transferring this item from an inventory to the world
|
||||
self.invID = 0
|
||||
curInv:Remove(self.id, false, true)
|
||||
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("inventory_id", 0)
|
||||
query:Where("item_id", self.id)
|
||||
query:Execute()
|
||||
|
||||
inventory = ix.item.inventories[0]
|
||||
inventory[self:GetID()] = self
|
||||
|
||||
if (self.OnTransferred) then
|
||||
self:OnTransferred(curInv, inventory)
|
||||
end
|
||||
|
||||
hook.Run("OnItemTransferred", self, curInv, inventory)
|
||||
|
||||
if (!isLogical) then
|
||||
return self:Spawn(client)
|
||||
end
|
||||
|
||||
return true
|
||||
else
|
||||
return false, "noOwner"
|
||||
end
|
||||
else
|
||||
return false, "invalidInventory"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.meta.item = ITEM
|
||||
690
gamemodes/helix/gamemode/core/meta/sh_player.lua
Normal file
690
gamemodes/helix/gamemode/core/meta/sh_player.lua
Normal file
@@ -0,0 +1,690 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Physical representation of connected player.
|
||||
|
||||
`Player`s are a type of `Entity`. They are a physical representation of a `Character` - and can possess at most one `Character`
|
||||
object at a time that you can interface with.
|
||||
|
||||
See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Player) for all other methods that the `Player` class has.
|
||||
]]
|
||||
-- @classmod Player
|
||||
|
||||
local meta = FindMetaTable("Player")
|
||||
|
||||
if (SERVER) then
|
||||
--- Returns the amount of time the player has played on the server.
|
||||
-- @realm shared
|
||||
-- @treturn number Number of seconds the player has played on the server
|
||||
function meta:GetPlayTime()
|
||||
return self.ixPlayTime + (RealTime() - (self.ixJoinTime or RealTime()))
|
||||
end
|
||||
else
|
||||
ix.playTime = ix.playTime or 0
|
||||
|
||||
function meta:GetPlayTime()
|
||||
return ix.playTime + (RealTime() - ix.joinTime or 0)
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns `true` if the player has their weapon raised.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player has their weapon raised
|
||||
function meta:IsWepRaised()
|
||||
return self:GetNetVar("raised", false)
|
||||
end
|
||||
|
||||
--- Returns `true` if the player is restricted - that is to say that they are considered "bound" and cannot interact with
|
||||
-- objects normally (e.g hold weapons, use items, etc). An example of this would be a player in handcuffs.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player is restricted
|
||||
function meta:IsRestricted()
|
||||
return self:GetNetVar("restricted", false)
|
||||
end
|
||||
|
||||
--- Returns `true` if the player is able to shoot their weapon.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player can shoot their weapon
|
||||
function meta:CanShootWeapon()
|
||||
return self:GetNetVar("canShoot", true)
|
||||
end
|
||||
|
||||
local vectorLength2D = FindMetaTable("Vector").Length2D
|
||||
|
||||
--- Returns `true` if the player is running. Running in this case means that their current speed is greater than their
|
||||
-- regularly set walk speed.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player is running
|
||||
function meta:IsRunning()
|
||||
return vectorLength2D(self:GetVelocity()) > (self:GetWalkSpeed() + 10)
|
||||
end
|
||||
|
||||
--- Returns `true` if the player currently has a female model. This checks if the model has `female`, `alyx` or `mossman` in its
|
||||
-- name, or if the player's model class is `citizen_female`.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player has a female model
|
||||
function meta:IsFemale()
|
||||
local model = self:GetModel():lower()
|
||||
|
||||
return (model:find("female") or model:find("alyx") or model:find("mossman")) != nil or
|
||||
ix.anim.GetModelClass(model) == "citizen_female"
|
||||
end
|
||||
|
||||
--- Whether or not this player is stuck and cannot move.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not this player is stuck
|
||||
function meta:IsStuck()
|
||||
return util.TraceEntity({
|
||||
start = self:GetPos(),
|
||||
endpos = self:GetPos(),
|
||||
filter = self
|
||||
}, self).StartSolid
|
||||
end
|
||||
|
||||
--- Returns a good position in front of the player for an entity to be placed. This is usually used for item entities.
|
||||
-- @realm shared
|
||||
-- @entity entity Entity to get a position for
|
||||
-- @treturn vector Best guess for a good drop position in front of the player
|
||||
-- @usage local position = client:GetItemDropPos(entity)
|
||||
-- entity:SetPos(position)
|
||||
function meta:GetItemDropPos(entity)
|
||||
local data = {}
|
||||
local trace
|
||||
|
||||
data.start = self:GetShootPos()
|
||||
data.endpos = self:GetShootPos() + self:GetAimVector() * 86
|
||||
data.filter = self
|
||||
|
||||
if (IsValid(entity)) then
|
||||
-- use a hull trace if there's a valid entity to avoid collisions
|
||||
local mins, maxs = entity:GetRotatedAABB(entity:OBBMins(), entity:OBBMaxs())
|
||||
|
||||
data.mins = mins
|
||||
data.maxs = maxs
|
||||
data.filter = {entity, self}
|
||||
trace = util.TraceHull(data)
|
||||
else
|
||||
-- trace along the normal for a few units so we can attempt to avoid a collision
|
||||
trace = util.TraceLine(data)
|
||||
|
||||
data.start = trace.HitPos
|
||||
data.endpos = data.start + trace.HitNormal * 48
|
||||
trace = util.TraceLine(data)
|
||||
end
|
||||
|
||||
return trace.HitPos
|
||||
end
|
||||
|
||||
--- Performs a time-delay action that requires this player to look at an entity. If this player looks away from the entity
|
||||
-- before the action timer completes, the action is cancelled. This is usually used in conjunction with `SetAction` to display
|
||||
-- progress to the player.
|
||||
-- @realm shared
|
||||
-- @entity entity that this player must look at
|
||||
-- @func callback Function to call when the timer completes
|
||||
-- @number time How much time in seconds this player must look at the entity for
|
||||
-- @func[opt=nil] onCancel Function to call when the timer has been cancelled
|
||||
-- @number[opt=96] distance Maximum distance a player can move away from the entity before the action is cancelled
|
||||
-- @see SetAction
|
||||
-- @usage client:SetAction("Searching...", 4) -- for displaying the progress bar
|
||||
-- client:DoStaredAction(entity, function()
|
||||
-- print("hello!")
|
||||
-- end)
|
||||
-- -- prints "hello!" after looking at the entity for 4 seconds
|
||||
function meta:DoStaredAction(entity, callback, time, onCancel, distance)
|
||||
local uniqueID = "ixStare"..self:SteamID64()
|
||||
local data = {}
|
||||
data.filter = self
|
||||
|
||||
timer.Create(uniqueID, 0.1, time / 0.1, function()
|
||||
if (IsValid(self) and IsValid(entity)) then
|
||||
data.start = self:GetShootPos()
|
||||
data.endpos = data.start + self:GetAimVector()*(distance or 96)
|
||||
|
||||
if (util.TraceLine(data).Entity != entity) then
|
||||
timer.Remove(uniqueID)
|
||||
|
||||
if (onCancel) then
|
||||
onCancel()
|
||||
end
|
||||
elseif (callback and timer.RepsLeft(uniqueID) == 0) then
|
||||
callback()
|
||||
end
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
|
||||
if (onCancel) then
|
||||
onCancel()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Resets all bodygroups this player's model has to their defaults (`0`).
|
||||
-- @realm shared
|
||||
function meta:ResetBodygroups()
|
||||
for i = 0, (self:GetNumBodyGroups() - 1) do
|
||||
self:SetBodygroup(i, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function meta:GetFactionVar(variable, default)
|
||||
if (!self:GetCharacter()) then return end
|
||||
|
||||
local faction = ix.faction.Get(self:Team())
|
||||
if (!faction) then return end
|
||||
|
||||
return faction[variable] or default
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixActionBar")
|
||||
util.AddNetworkString("ixActionBarReset")
|
||||
util.AddNetworkString("ixStringRequest")
|
||||
util.AddNetworkString("ixConfirmationRequest")
|
||||
|
||||
--- Sets whether or not this player's current weapon is raised.
|
||||
-- @realm server
|
||||
-- @bool bState Whether or not the raise the weapon
|
||||
-- @entity[opt=GetActiveWeapon()] weapon Weapon to raise or lower. You should pass this argument if you already have a
|
||||
-- reference to this player's current weapon to avoid an expensive lookup for this player's current weapon.
|
||||
function meta:SetWepRaised(bState, weapon)
|
||||
weapon = weapon or self:GetActiveWeapon()
|
||||
|
||||
if (IsValid(weapon)) then
|
||||
local bCanShoot = !bState and weapon.FireWhenLowered or bState
|
||||
self:SetNetVar("raised", bState)
|
||||
|
||||
if (bCanShoot) then
|
||||
-- delay shooting while the raise animation is playing
|
||||
timer.Create("ixWeaponRaise" .. self:SteamID64(), 1, 1, function()
|
||||
if (IsValid(self)) then
|
||||
self:SetNetVar("canShoot", true)
|
||||
end
|
||||
end)
|
||||
else
|
||||
timer.Remove("ixWeaponRaise" .. self:SteamID64())
|
||||
self:SetNetVar("canShoot", false)
|
||||
end
|
||||
else
|
||||
timer.Remove("ixWeaponRaise" .. self:SteamID64())
|
||||
self:SetNetVar("raised", false)
|
||||
self:SetNetVar("canShoot", false)
|
||||
end
|
||||
end
|
||||
|
||||
--- Inverts this player's weapon raised state. You should use `SetWepRaised` instead of this if you already have a reference
|
||||
-- to this player's current weapon.
|
||||
-- @realm server
|
||||
function meta:ToggleWepRaised()
|
||||
local weapon = self:GetActiveWeapon()
|
||||
|
||||
if (!IsValid(weapon) or
|
||||
weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()] or
|
||||
weapon.IsAlwaysLowered or weapon.NeverRaised) then
|
||||
return
|
||||
end
|
||||
|
||||
self:SetWepRaised(!self:IsWepRaised(), weapon)
|
||||
|
||||
if (self:IsWepRaised() and weapon.OnRaised) then
|
||||
weapon:OnRaised()
|
||||
elseif (!self:IsWepRaised() and weapon.OnLowered) then
|
||||
weapon:OnLowered()
|
||||
end
|
||||
end
|
||||
|
||||
--- Performs a delayed action that requires this player to hold use on an entity. This is displayed to this player as a
|
||||
-- closing ring over their crosshair.
|
||||
-- @realm server
|
||||
-- @number time How much time in seconds this player has to hold use for
|
||||
-- @entity entity Entity that this player must be looking at
|
||||
-- @func callback Function to run when the timer completes. It will be ran right away if `time` is `0`. Returning `false` in
|
||||
-- the callback will not mark this interaction as dirty if you're managing the interaction state manually.
|
||||
function meta:PerformInteraction(time, entity, callback)
|
||||
if (!IsValid(entity) or entity.ixInteractionDirty) then
|
||||
return
|
||||
end
|
||||
|
||||
if (time > 0) then
|
||||
self.ixInteractionTarget = entity
|
||||
self.ixInteractionCharacter = self:GetCharacter():GetID()
|
||||
|
||||
timer.Create("ixCharacterInteraction" .. self:SteamID(), time, 1, function()
|
||||
if (IsValid(self) and IsValid(entity) and IsValid(self.ixInteractionTarget) and
|
||||
self.ixInteractionCharacter == self:GetCharacter():GetID()) then
|
||||
local data = {}
|
||||
data.start = self:GetShootPos()
|
||||
data.endpos = data.start + self:GetAimVector() * 96
|
||||
data.filter = self
|
||||
local traceEntity = util.TraceLine(data).Entity
|
||||
|
||||
if (IsValid(traceEntity) and traceEntity == self.ixInteractionTarget and !traceEntity.ixInteractionDirty) then
|
||||
if (callback(self) != false) then
|
||||
traceEntity.ixInteractionDirty = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
if (callback(self) != false) then
|
||||
entity.ixInteractionDirty = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Displays a progress bar for this player that takes the given amount of time to complete.
|
||||
-- @realm server
|
||||
-- @string text Text to display above the progress bar
|
||||
-- @number[opt=5] time How much time in seconds to wait before the timer completes
|
||||
-- @func callback Function to run once the timer completes
|
||||
-- @number[opt=CurTime()] startTime Game time in seconds that the timer started. If you are using `time`, then you shouldn't
|
||||
-- use this argument
|
||||
-- @number[opt=startTime + time] finishTime Game time in seconds that the timer should complete at. If you are using `time`,
|
||||
-- then you shouldn't use this argument
|
||||
function meta:SetAction(text, time, callback, startTime, finishTime)
|
||||
if (time and time <= 0) then
|
||||
if (callback) then
|
||||
callback(self)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Default the time to five seconds.
|
||||
time = time or 5
|
||||
startTime = startTime or CurTime()
|
||||
finishTime = finishTime or (startTime + time)
|
||||
|
||||
if (text == false) then
|
||||
timer.Remove("ixAct"..self:SteamID64())
|
||||
|
||||
net.Start("ixActionBarReset")
|
||||
net.Send(self)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if (!text) then
|
||||
net.Start("ixActionBarReset")
|
||||
net.Send(self)
|
||||
else
|
||||
net.Start("ixActionBar")
|
||||
net.WriteFloat(startTime)
|
||||
net.WriteFloat(finishTime)
|
||||
net.WriteString(text)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
-- If we have provided a callback, run it delayed.
|
||||
if (callback) then
|
||||
-- Create a timer that runs once with a delay.
|
||||
timer.Create("ixAct"..self:SteamID64(), time, 1, function()
|
||||
-- Call the callback if the player is still valid.
|
||||
if (IsValid(self)) then
|
||||
callback(self)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- Opens up a text box on this player's screen for input and returns the result. Remember to sanitize the user's input if
|
||||
-- it's needed!
|
||||
-- @realm server
|
||||
-- @string title Title to display on the panel
|
||||
-- @string subTitle Subtitle to display on the panel
|
||||
-- @func callback Function to run when this player enters their input. Callback is ran with the user's input string.
|
||||
-- @string[opt=nil] default Default value to put in the text box.
|
||||
-- @usage client:RequestString("Hello", "Please enter your name", function(text)
|
||||
-- client:ChatPrint("Hello, " .. text)
|
||||
-- end)
|
||||
-- -- prints "Hello, <text>" in the player's chat
|
||||
function meta:RequestString(title, subTitle, callback, default)
|
||||
local time = math.floor(os.time())
|
||||
|
||||
self.ixStrReqs = self.ixStrReqs or {}
|
||||
self.ixStrReqs[time] = callback
|
||||
|
||||
net.Start("ixStringRequest")
|
||||
net.WriteUInt(time, 32)
|
||||
net.WriteString(title)
|
||||
net.WriteString(subTitle)
|
||||
net.WriteString(default)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
--- Opens up a confirmation box on this player's screen for input and returns the result.
|
||||
-- @realm server
|
||||
-- @string title Title to display on the panel
|
||||
-- @string subTitle Subtitle to display on the panel
|
||||
-- @func callback Function to run when this player enters their input.
|
||||
-- @usage client:RequestString("Confirm", "Are you sure you want to do this?", function(confirmation)
|
||||
-- client:ChatPrint(confirmation and "Confirmed." or "Cancelled.")
|
||||
-- end)
|
||||
-- -- prints whether the player confirmed or cancelled.
|
||||
function meta:RequestConfirmation(title, subTitle, callback)
|
||||
local time = math.floor(os.time())
|
||||
|
||||
self.ixConfReqs = self.ixConfReqs or {}
|
||||
self.ixConfReqs[time] = callback
|
||||
|
||||
net.Start("ixConfirmationRequest")
|
||||
net.WriteUInt(time, 32)
|
||||
net.WriteString(title)
|
||||
net.WriteString(subTitle)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
--- Sets this player's restricted status.
|
||||
-- @realm server
|
||||
-- @bool bState Whether or not to restrict this player
|
||||
-- @bool bNoMessage Whether or not to suppress the restriction notification
|
||||
function meta:SetRestricted(bState, bNoMessage)
|
||||
if (bState) then
|
||||
self:SetNetVar("restricted", true)
|
||||
|
||||
if (bNoMessage) then
|
||||
self:SetLocalVar("restrictNoMsg", true)
|
||||
end
|
||||
|
||||
self.ixRestrictWeps = self.ixRestrictWeps or {}
|
||||
|
||||
for _, v in ipairs(self:GetWeapons()) do
|
||||
self.ixRestrictWeps[#self.ixRestrictWeps + 1] = v:GetClass()
|
||||
v:Remove()
|
||||
end
|
||||
|
||||
hook.Run("OnPlayerRestricted", self)
|
||||
else
|
||||
self:SetNetVar("restricted")
|
||||
|
||||
if (self:GetLocalVar("restrictNoMsg")) then
|
||||
self:SetLocalVar("restrictNoMsg")
|
||||
end
|
||||
|
||||
if (self.ixRestrictWeps) then
|
||||
for _, v in ipairs(self.ixRestrictWeps) do
|
||||
self:Give(v)
|
||||
end
|
||||
|
||||
self.ixRestrictWeps = nil
|
||||
end
|
||||
|
||||
hook.Run("OnPlayerUnRestricted", self)
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates a ragdoll entity of this player that will be synced with clients. This does **not** affect the player like
|
||||
-- `SetRagdolled` does.
|
||||
-- @realm server
|
||||
-- @bool[opt=false] bDontSetPlayer Whether or not to avoid setting the ragdoll's owning player
|
||||
-- @treturn entity Created ragdoll entity
|
||||
function meta:CreateServerRagdoll(bDontSetPlayer)
|
||||
local entity = ents.Create("prop_ragdoll")
|
||||
entity:SetPos(self:GetPos())
|
||||
entity:SetAngles(self:EyeAngles())
|
||||
entity:SetModel(self:GetModel())
|
||||
entity:SetSkin(self:GetSkin())
|
||||
|
||||
for i = 0, (self:GetNumBodyGroups() - 1) do
|
||||
entity:SetBodygroup(i, self:GetBodygroup(i))
|
||||
end
|
||||
|
||||
entity:Spawn()
|
||||
|
||||
if (!bDontSetPlayer) then
|
||||
entity:SetNetVar("player", self)
|
||||
end
|
||||
|
||||
entity:SetCollisionGroup(COLLISION_GROUP_WEAPON)
|
||||
entity:Activate()
|
||||
|
||||
local velocity = self:GetVelocity()
|
||||
|
||||
for i = 0, entity:GetPhysicsObjectCount() - 1 do
|
||||
local physObj = entity:GetPhysicsObjectNum(i)
|
||||
|
||||
if (IsValid(physObj)) then
|
||||
physObj:SetVelocity(velocity)
|
||||
|
||||
local index = entity:TranslatePhysBoneToBone(i)
|
||||
|
||||
if (index) then
|
||||
local position, angles = self:GetBonePosition(index)
|
||||
|
||||
physObj:SetPos(position)
|
||||
physObj:SetAngles(angles)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return entity
|
||||
end
|
||||
|
||||
--- Sets this player's ragdoll status.
|
||||
-- @realm server
|
||||
-- @bool bState Whether or not to ragdoll this player
|
||||
-- @number[opt=0] time How long this player should stay ragdolled for. Set to `0` if they should stay ragdolled until they
|
||||
-- get back up manually
|
||||
-- @number[opt=5] getUpGrace How much time in seconds to wait before the player is able to get back up manually. Set to
|
||||
-- the same number as `time` to disable getting up manually entirely
|
||||
function meta:SetRagdolled(bState, time, getUpGrace)
|
||||
if (!self:Alive()) then
|
||||
return
|
||||
end
|
||||
|
||||
getUpGrace = getUpGrace or time or 5
|
||||
|
||||
if (bState) then
|
||||
if (IsValid(self.ixRagdoll)) then
|
||||
self.ixRagdoll:Remove()
|
||||
end
|
||||
|
||||
local entity = self:CreateServerRagdoll()
|
||||
|
||||
entity:CallOnRemove("fixer", function()
|
||||
if (IsValid(self)) then
|
||||
self:SetLocalVar("blur", nil)
|
||||
self:SetLocalVar("ragdoll", nil)
|
||||
|
||||
if (!entity.ixNoReset) then
|
||||
self:SetPos(entity:GetPos())
|
||||
end
|
||||
|
||||
self:SetNoDraw(false)
|
||||
self:SetNotSolid(false)
|
||||
self:SetMoveType(MOVETYPE_WALK)
|
||||
self:SetLocalVelocity(IsValid(entity) and entity.ixLastVelocity or vector_origin)
|
||||
end
|
||||
|
||||
if (IsValid(self) and !entity.ixIgnoreDelete) then
|
||||
if (entity.ixWeapons) then
|
||||
local active
|
||||
for _, v in ipairs(entity.ixWeapons) do
|
||||
if (v.active) then
|
||||
active = v
|
||||
elseif (v.class) then
|
||||
local weapon = self:Give(v.class, true)
|
||||
if (IsValid(weapon)) then
|
||||
if (v.item) then
|
||||
weapon.ixItem = v.item
|
||||
end
|
||||
|
||||
self:SetAmmo(v.ammo, weapon:GetPrimaryAmmoType())
|
||||
weapon:SetClip1(v.clip)
|
||||
end
|
||||
elseif (v.item and v.invID == v.item.invID) then
|
||||
v.item:Equip(self, true, true)
|
||||
self:SetAmmo(v.ammo, self.carryWeapons[v.item.weaponCategory]:GetPrimaryAmmoType())
|
||||
end
|
||||
end
|
||||
|
||||
if (active) then
|
||||
if (active.class) then
|
||||
local weapon = self:Give(active.class, true)
|
||||
if (IsValid(weapon)) then
|
||||
if (active.item) then
|
||||
weapon.ixItem = active.item
|
||||
end
|
||||
|
||||
self:SetAmmo(active.ammo, weapon:GetPrimaryAmmoType())
|
||||
weapon:SetClip1(active.clip)
|
||||
end
|
||||
elseif (active.item and active.invID == active.item.invID) then
|
||||
active.item:Equip(self, true, true)
|
||||
self:SetAmmo(active.ammo, self.carryWeapons[active.item.weaponCategory]:GetPrimaryAmmoType())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
if (entity.ixActiveWeapon) then
|
||||
local weapon = entity.ixActiveWeapon
|
||||
timer.Simple(1, function()
|
||||
if (self:HasWeapon(weapon)) then
|
||||
self:SetActiveWeapon(self:GetWeapon(weapon))
|
||||
else
|
||||
local weapons = self:GetWeapons()
|
||||
if (#weapons > 0) then
|
||||
self:SetActiveWeapon(weapons[1])
|
||||
end
|
||||
end
|
||||
end)
|
||||
timer.Simple(0.5, function()
|
||||
if (self:HasWeapon("ix_hands")) then
|
||||
self:SetActiveWeapon(self:GetWeapon("ix_hands"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
--]]
|
||||
|
||||
if (self:IsStuck()) then
|
||||
entity:DropToFloor()
|
||||
self:SetPos(entity:GetPos() + Vector(0, 0, 16))
|
||||
|
||||
local positions = ix.util.FindEmptySpace(self, {entity, self})
|
||||
|
||||
for _, v in ipairs(positions) do
|
||||
self:SetPos(v)
|
||||
|
||||
if (!self:IsStuck()) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
self:SetLocalVar("blur", 25)
|
||||
self.ixRagdoll = entity
|
||||
|
||||
entity.ixWeapons = {}
|
||||
entity.ixPlayer = self
|
||||
|
||||
if (getUpGrace) then
|
||||
entity.ixGrace = CurTime() + getUpGrace
|
||||
end
|
||||
|
||||
if (time and time > 0) then
|
||||
entity.ixStart = CurTime()
|
||||
entity.ixFinish = entity.ixStart + time
|
||||
|
||||
self:SetAction("@wakingUp", nil, nil, entity.ixStart, entity.ixFinish)
|
||||
end
|
||||
|
||||
if (IsValid(self:GetActiveWeapon())) then
|
||||
entity.ixActiveWeapon = self:GetActiveWeapon():GetClass()
|
||||
end
|
||||
|
||||
for _, v in ipairs(self:GetWeapons()) do
|
||||
if (v.ixItem and v.ixItem.Equip and v.ixItem.Unequip) then
|
||||
entity.ixWeapons[#entity.ixWeapons + 1] = {
|
||||
item = v.ixItem,
|
||||
invID = v.ixItem.invID,
|
||||
ammo = self:GetAmmoCount(v:GetPrimaryAmmoType()),
|
||||
active = v:GetClass() == entity.ixActiveWeapon
|
||||
}
|
||||
v.ixItem:Unequip(self, false)
|
||||
else
|
||||
local clip = v:Clip1()
|
||||
local reserve = self:GetAmmoCount(v:GetPrimaryAmmoType())
|
||||
entity.ixWeapons[#entity.ixWeapons + 1] = {
|
||||
class = v:GetClass(),
|
||||
item = v.ixItem,
|
||||
clip = clip,
|
||||
ammo = reserve,
|
||||
active = v:GetClass() == entity.ixActiveWeapon
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
self:GodDisable()
|
||||
self:StripWeapons()
|
||||
self:SetMoveType(MOVETYPE_OBSERVER)
|
||||
self:SetNoDraw(true)
|
||||
self:SetNotSolid(true)
|
||||
|
||||
local uniqueID = "ixUnRagdoll" .. self:SteamID()
|
||||
|
||||
if (time) then
|
||||
timer.Create(uniqueID, 0.33, 0, function()
|
||||
if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then
|
||||
local velocity = entity:GetVelocity()
|
||||
entity.ixLastVelocity = velocity
|
||||
|
||||
self:SetPos(entity:GetPos())
|
||||
|
||||
if (velocity:Length2D() >= 8) then
|
||||
if (!entity.ixPausing) then
|
||||
self:SetAction()
|
||||
entity.ixPausing = true
|
||||
end
|
||||
|
||||
return
|
||||
elseif (entity.ixPausing) then
|
||||
self:SetAction("@wakingUp", time)
|
||||
entity.ixPausing = false
|
||||
end
|
||||
|
||||
time = time - 0.33
|
||||
|
||||
if (time <= 0) then
|
||||
entity:Remove()
|
||||
end
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
end)
|
||||
else
|
||||
timer.Create(uniqueID, 0.33, 0, function()
|
||||
if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then
|
||||
self:SetPos(entity:GetPos())
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
self:SetLocalVar("ragdoll", entity:EntIndex())
|
||||
hook.Run("OnCharacterFallover", self, entity, true)
|
||||
elseif (IsValid(self.ixRagdoll)) then
|
||||
-- Anti-Exploit measure. Just silently fail if near a door.
|
||||
for _, entity in ipairs(ents.FindInSphere(self:GetPos(), 50)) do
|
||||
if (entity:GetClass() == "func_door" or entity:GetClass() == "func_door_rotating" or entity:GetClass() == "prop_door_rotating") then return end
|
||||
end
|
||||
|
||||
self.ixRagdoll:Remove()
|
||||
|
||||
hook.Run("OnCharacterFallover", self, nil, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
140
gamemodes/helix/gamemode/core/meta/sh_tool.lua
Normal file
140
gamemodes/helix/gamemode/core/meta/sh_tool.lua
Normal file
@@ -0,0 +1,140 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local TOOL = ix.meta.tool or {}
|
||||
|
||||
-- code replicated from gamemodes/sandbox/entities/weapons/gmod_tool/stool.lua
|
||||
function TOOL:Create()
|
||||
local object = {}
|
||||
|
||||
setmetatable(object, self)
|
||||
self.__index = self
|
||||
|
||||
object.Mode = nil
|
||||
object.SWEP = nil
|
||||
object.Owner = nil
|
||||
object.ClientConVar = {}
|
||||
object.ServerConVar = {}
|
||||
object.Objects = {}
|
||||
object.Stage = 0
|
||||
object.Message = "start"
|
||||
object.LastMessage = 0
|
||||
object.AllowedCVar = 0
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
function TOOL:CreateConVars()
|
||||
local mode = self:GetMode()
|
||||
|
||||
if (CLIENT) then
|
||||
for cvar, default in pairs(self.ClientConVar) do
|
||||
CreateClientConVar(mode .. "_" .. cvar, default, true, true)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Note: I changed this from replicated because replicated convars don't work when they're created via Lua.
|
||||
if (SERVER) then
|
||||
self.AllowedCVar = CreateConVar("toolmode_allow_" .. mode, 1, FCVAR_NOTIFY)
|
||||
end
|
||||
end
|
||||
|
||||
function TOOL:GetServerInfo(property)
|
||||
local mode = self:GetMode()
|
||||
return GetConVarString(mode .. "_" .. property)
|
||||
end
|
||||
|
||||
function TOOL:BuildConVarList()
|
||||
local mode = self:GetMode()
|
||||
local convars = {}
|
||||
|
||||
for k, v in pairs(self.ClientConVar) do
|
||||
convars[mode .. "_" .. k] = v
|
||||
end
|
||||
|
||||
return convars
|
||||
end
|
||||
|
||||
function TOOL:GetClientInfo(property)
|
||||
return self:GetOwner():GetInfo(self:GetMode() .. "_" .. property)
|
||||
end
|
||||
|
||||
function TOOL:GetClientNumber(property, default)
|
||||
return self:GetOwner():GetInfoNum(self:GetMode() .. "_" .. property, tonumber(default) or 0)
|
||||
end
|
||||
|
||||
function TOOL:Allowed()
|
||||
if (CLIENT) then
|
||||
return true
|
||||
end
|
||||
|
||||
return self.AllowedCVar:GetBool()
|
||||
end
|
||||
|
||||
-- Now for all the TOOL redirects
|
||||
function TOOL:Init()
|
||||
end
|
||||
|
||||
function TOOL:GetMode()
|
||||
return self.Mode
|
||||
end
|
||||
|
||||
function TOOL:GetSWEP()
|
||||
return self.SWEP
|
||||
end
|
||||
|
||||
function TOOL:GetOwner()
|
||||
return self:GetSWEP().Owner or self.Owner
|
||||
end
|
||||
|
||||
function TOOL:GetWeapon()
|
||||
return self:GetSWEP().Weapon or self.Weapon
|
||||
end
|
||||
|
||||
function TOOL:LeftClick()
|
||||
return false
|
||||
end
|
||||
|
||||
function TOOL:RightClick()
|
||||
return false
|
||||
end
|
||||
|
||||
function TOOL:Reload()
|
||||
self:ClearObjects()
|
||||
end
|
||||
|
||||
function TOOL:Deploy()
|
||||
self:ReleaseGhostEntity()
|
||||
return
|
||||
end
|
||||
|
||||
function TOOL:Holster()
|
||||
self:ReleaseGhostEntity()
|
||||
return
|
||||
end
|
||||
|
||||
function TOOL:Think()
|
||||
self:ReleaseGhostEntity()
|
||||
end
|
||||
|
||||
-- Checks the objects before any action is taken
|
||||
-- This is to make sure that the entities haven't been removed
|
||||
function TOOL:CheckObjects()
|
||||
for _, v in pairs(self.Objects) do
|
||||
if (!v.Ent:IsWorld() and !v.Ent:IsValid()) then
|
||||
self:ClearObjects()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.meta.tool = TOOL
|
||||
Reference in New Issue
Block a user