diff --git a/lua/starfall/libs_sv/acffunctions.lua b/lua/starfall/libs_sv/acffunctions.lua index 0bf382999..535bb995f 100644 --- a/lua/starfall/libs_sv/acffunctions.lua +++ b/lua/starfall/libs_sv/acffunctions.lua @@ -18,1323 +18,2315 @@ -- #fuel ---===============================================================================================-- --- Local Variables and Helper Functions ---===============================================================================================-- +local checkluatype = SF.CheckLuaType +local checkpermission = SF.Permissions.check +local registerprivilege = SF.Permissions.registerPrivilege -local RestrictInfoConVar = GetConVar("sbox_acf_restrictinfo") -local AllLinkSources = ACF.GetAllLinkSources -local LinkSource = ACF.GetLinkSource -local RoundTypes = ACF.RoundTypes -local CheckType = SF.CheckType -local CheckLuaType = SF.CheckLuaType -local match = string.match -local floor = math.floor -local Round = math.Round +registerprivilege("acf.createMobility", "Create acf engine", "Allows the user to create ACF engines and gearboxes", { usergroups = { default = 3 } }) +registerprivilege("acf.createFuelTank", "Create acf fuel tank", "Allows the user to create ACF fuel tanks", { usergroups = { default = 3 } }) +registerprivilege("acf.createGun", "Create acf gun", "Allows the user to create ACF guns", { usergroups = { default = 3 } }) +registerprivilege("acf.createAmmo", "Create acf ammo", "Allows the user to create ACF ammoboxes", { usergroups = { default = 3 } } ) +registerprivilege("entities.acf", "ACF", "Allows the user to control ACF components", { entities = {} }) -local function ValidPhysics(Entity) - if not IsValid(Entity) then return false end - if Entity:IsWorld() then return false end - if Entity:GetMoveType() ~= MOVETYPE_VPHYSICS then return false end +local plyCount = SF.LimitObject("acf_components", "acf_components", -1, "The number of ACF components allowed to spawn via Starfall") +local plyBurst = SF.BurstObject("acf_components", "acf_components", 4, 4, "Rate ACF components can be spawned per second.", "Number of ACF components that can be spawned in a short time.") - return IsValid(Entity:GetPhysicsObject()) -end - -local function IsACFEntity(Entity) - if not ValidPhysics(Entity) then return false end - - local Match = match(Entity:GetClass(), "^acf_") +-- [ Helper Functions ] -- - return Match and true or false +local function isEngine ( ent ) + if not validPhysics( ent ) then return false end + if ( ent:GetClass() == "acf_engine" ) then return true else return false end end -local function IsOwner(Player, Entity) - if not CPPI then return true end - - return Entity:CPPIGetOwner() == Player +local function isGearbox ( ent ) + if not validPhysics( ent ) then return false end + if ( ent:GetClass() == "acf_gearbox" ) then return true else return false end end -local function RestrictInfo(Player, Entity) - if RestrictInfoConVar:GetInt() == 0 then return false end - - return not IsOwner(Player, Entity) +local function isGun ( ent ) + if not validPhysics( ent ) then return false end + if ( ent:GetClass() == "acf_gun" ) then return true else return false end end -local function GetReloadTime(Entity) - return Entity.OnReload and Entity.MagReload or Entity.ReloadTime or 0 +local function isAmmo ( ent ) + if not validPhysics( ent ) then return false end + if ( ent:GetClass() == "acf_ammo" ) then return true else return false end end -local function GetMaxPower(Entity) - if not Entity.PeakTorque then return 0 end - - local MaxPower +local function isFuel ( ent ) + if not validPhysics(ent) then return false end + if ( ent:GetClass() == "acf_fueltank" ) then return true else return false end +end - if Entity.IsElectric then - if not Entity.LimitRPM then return 0 end +local function reloadTime( ent ) + if ent.CurrentShot and ent.CurrentShot > 0 then return ent.ReloadTime end + return ent.MagReload +end - MaxPower = floor(Entity.PeakTorque * Entity.LimitRPM / 38195.2) --(4*9548.8) - else - if not Entity.PeakMaxRPM then return 0 end +local propProtectionInstalled = FindMetaTable("Entity").CPPIGetOwner and true - MaxPower = floor(Entity.PeakTorque * Entity.PeakMaxRPM / 9548.8) - end +---------------------------------------- +-- ACF Library +-- @name acf +-- @class library +-- @libtbl acf_library +SF.RegisterLibrary("acf") - return MaxPower -end +-- Local to each starfall +return function(instance) -- Called for library declarations -local function GetLinkedWheels(Target) - local Current, Class, Sources - local Queued = { [Target] = true } - local Checked = {} - local Linked = {} - while next(Queued) do - Current = next(Queued) - Class = Current:GetClass() - Sources = AllLinkSources(Class) +local checktype = instance.CheckType +local acf_library = instance.Libraries.acf +local owrap, ounwrap = instance.WrapObject, instance.UnwrapObject +local ents_methods, ent_meta, ewrap, eunwrap = instance.Types.Entity.Methods, instance.Types.Entity, instance.Types.Entity.Wrap, instance.Types.Entity.Unwrap +local ang_meta, awrap, aunwrap = instance.Types.Angle, instance.Types.Angle.Wrap, instance.Types.Angle.Unwrap +local vec_meta, vwrap, vunwrap = instance.Types.Vector, instance.Types.Vector.Wrap, instance.Types.Vector.Unwrap - Queued[Current] = nil - Checked[Current] = true - for Name, Action in pairs(Sources) do - for Entity in pairs(Action(Current)) do - if not (Checked[Entity] or Queued[Entity]) then - if Name == "Wheels" then - Checked[Entity] = true - Linked[Entity] = true - else - Queued[Entity] = true - end - end - end - end +local function restrictInfo ( ent ) + if not propProtectionInstalled then return false end + if GetConVar("sbox_acf_restrictinfo"):GetInt() ~= 0 then + if ent:CPPIGetOwner() ~= instance.player then return true else return false end end + return false +end - return Linked +local function propOnDestroy(ent, instance) + local ply = instance.player + plyCount:free(ply, 1) + instance.data.props.props[ent] = nil end -SF.AddHook("postload", function() - local vec_metatable = SF.Vectors.Metatable - local ents_metatable = SF.Entities.Metatable - local ents_methods = SF.Entities.Methods - local wrap, unwrap = SF.Entities.Wrap, SF.Entities.Unwrap +local function register(ent, instance) + ent:CallOnRemove("starfall_prop_delete", propOnDestroy, instance) + plyCount:free(instance.player, -1) + instance.data.props.props[ent] = true +end - --===============================================================================================-- - -- General Functions - --===============================================================================================-- - -- Returns true if functions returning sensitive info are restricted to owned props - function ents_methods:acfInfoRestricted() - return RestrictInfoConVar:GetInt() ~= 0 - end +--- Returns true if functions returning sensitive info are restricted to owned props +-- @server +-- @return True if restriced, False if not +function acf_library.infoRestricted() + return GetConVar("sbox_acf_restrictinfo"):GetInt() ~= 0 +end - -- Returns the full name of an ACF entity - function ents_methods:acfName() - local This = unwrap(self) +--- Returns current ACF drag divisor +-- @server +-- @return The current drag divisor +function acf_library.dragDivisor() + return ACF.DragDiv +end - if not IsACFEntity(This) then return "" end - if RestrictInfo(self, This) then return "" end +--- Returns the effective armor given an armor value and hit angle +-- @server +-- @return The effective armor +function acf_library.effectiveArmor(armor, angle) + checkluatype(armor, TYPE_NUMBER) + checkluatype(angle, TYPE_NUMBER) + + return math.Round(armor / math.abs(math.cos(math.rad(math.min(angle, 89.999)))), 1) +end - return This.Name or "" +-- Dont create a cache on init because maby a new entity get registered later on? +local id_name_cache = {} +local function idFromName(list, name) + id_name_cache[list] = id_name_cache[list] or {} + + if id_name_cache[list][name] then return id_name_cache[list][name] end + + for id, data in pairs(list) do + if data.name == name then + id_name_cache[list][name] = id + + return id + end end +end - -- Returns the short name of an ACF entity - function ents_methods:acfNameShort() - local This = unwrap(self) - - if not IsACFEntity(This) then return "" end - if RestrictInfo(SF.instance.player, This) then return "" end - - return This.ShortName or "" +--- Creates a engine or gearbox given the id or name +-- @param pos Position of created engine or gearbox +-- @param ang Angle of created engine or gearbox +-- @param id id or name of the engine or gearbox to create +-- @param frozen True to spawn frozen +-- @param gear_ratio A table containing the gear ratios, only applied if the mobility is a gearbox. -1 is final drive +-- @server +-- @return The created engine or gearbox +function acf_library.createMobility(pos, ang, id, frozen, gear_ratio) + checkpermission(instance, nil, "acf.createMobility") + + local ply = instance.player + + if not hook.Run("CanTool", ply, {Hit = true, Entity = game.GetWorld()}, "acfmenu") then SF.Throw("No permission to spawn ACF components", 2) end + + checktype(pos, vec_meta) + checktype(ang, ang_meta) + checkluatype(id, TYPE_STRING) + frozen = frozen and true or false + gear_ratio = type(gear_ratio) == "table" and gear_ratio or {} + + local pos = vunwrap(pos) + local ang = aunwrap(ang) + + local list_entries = ACF.Weapons.Mobility + + -- Not a valid id, try name + if not list_entries[id] then + id = idFromName(list_entries, id) + + -- Name is also invalid, error + if not id or not list_entries[id] then + SF.Throw("Invalid id or name", 2) + end end + + local type_id = list_entries[id] + local dupe_class = duplicator.FindEntityClass(type_id.ent) + + if not dupe_class then SF.Throw("Didn't find entity duplicator records", 2) end + + plyBurst:use(ply, 1) + plyCount:checkuse(ply, 1) + + local args_table = { + SF.clampPos(pos), + ang, + id + } + + if type_id.ent == "acf_gearbox" then + for i = 1, 9 do + args_table[3 + i] = type(gear_ratio[i]) == "number" and gear_ratio[i] or (i < type_id.gears and i / 10 or -0.1) + end + + args_table[13] = type(gear_ratio[-1]) == "number" and gear_ratio[-1] or 0.5 + end + + local ent = dupe_class.Func(ply, unpack(args_table)) + ent:Activate() + + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:EnableMotion(not frozen) + end + + if instance.data.props.undo then + undo.Create("ACF Mobility") + undo.SetPlayer(ply) + undo.AddEntity(ent) + undo.Finish("ACF Mobility (" .. tostring(id) .. ")") + end + + ply:AddCleanup("props", ent) + register(ent, instance) + + return owrap(ent) +end - -- Returns the type of ACF entity - function ents_methods:acfType() - local This = unwrap(self) - - if not IsACFEntity(This) then return "" end - if RestrictInfo(SF.instance.player, This) then return "" end - - return This.EntType or "" +--- Returns the specs of the engine or gearbox +-- @param id id or name of the engine or gearbox +-- @server +-- @return The specs table +function acf_library.getMobilitySpecs(id) + checkluatype(id, TYPE_STRING) + + local list_entries = ACF.Weapons.Mobility + + -- Not a valid id, try name + if not list_entries[id] then + id = idFromName(list_entries, id) + + -- Name is also invalid, error + if not id or not list_entries[id] then + SF.Throw("Invalid id or name", 2) + end end + + local specs = table.Copy(list_entries[id]) + specs.BaseClass = nil + + return specs +end - -- Returns true if the entity is an ACF engine - function ents_methods:acfIsEngine() - local This = unwrap(self) - - if not ValidPhysics(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end +--- Returns a list of all mobility components +-- @server +-- @return The mobility component list +function acf_library.getAllMobility() + local list = {} + + for id, _ in pairs(ACF.Weapons.Mobility) do + table.insert(list, id) + end + + return list +end - return This:GetClass() == "acf_engine" +--- Returns a list of all engines +-- @server +-- @return The engine list +function acf_library.getAllEngines() + local list = {} + + for id, d in pairs(ACF.Weapons.Mobility) do + if d.ent == "acf_engine" then + table.insert(list, id) + end end + + return list +end - -- Returns true if the entity is an ACF gearbox - function ents_methods:acfIsGearbox() - local This = unwrap(self) - - if not ValidPhysics(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end - - return This:GetClass() == "acf_gearbox" +--- Returns a list of all gearboxes +-- @server +-- @return The gearbox list +function acf_library.getAllGearboxes() + local list = {} + + for id, d in pairs(ACF.Weapons.Mobility) do + if d.ent == "acf_gearbox" then + table.insert(list, id) + end end + + return list +end - -- Returns true if the entity is an ACF gun - function ents_methods:acfIsGun() - local This = unwrap(self) - - if not ValidPhysics(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end - - return This:GetClass() == "acf_gun" - end +--- Creates a fuel tank given the id +-- @param pos Position of created fuel tank +-- @param ang Angle of created fuel tank +-- @param id id of the fuel tank to create +-- @param frozen True to spawn frozen +-- @param fueltype The type of fuel to use (Diesel, Electric, Petrol) +-- @server +-- @return The created fuel tank +function acf_library.createFuelTank(pos, ang, id, fueltype, frozen) + checkpermission(instance, nil, "acf.createFuelTank") + + local ply = instance.player + + if not hook.Run("CanTool", ply, {Hit = true, Entity = game.GetWorld()}, "acfmenu") then SF.Throw("No permission to spawn ACF components", 2) end + + checktype(pos, vec_meta) + checktype(ang, ang_meta) + checkluatype(id, TYPE_STRING) + frozen = frozen and true or false + fueltype = fueltype or "Diesel" + checkluatype(fueltype, TYPE_STRING) + + local pos = vunwrap(pos) + local ang = aunwrap(ang) + + if fueltype ~= "Diesel" and fueltype ~= "Electric" and fueltype ~= "Petrol" then SF.Throw("Invalid fuel type") end + + local list_entries = ACF.Weapons.FuelTanks + if not list_entries[id] then SF.Throw("Invalid id", 2) end + + local type_id = list_entries[id] + local dupe_class = duplicator.FindEntityClass(type_id.ent) + + if not dupe_class then SF.Throw("Didn't find entity duplicator records", 2) end + + plyBurst:use(ply, 1) + plyCount:checkuse(ply, 1) + + local ent = dupe_class.Func(ply, SF.clampPos(pos), ang, "Basic_FuelTank", id, fueltype) + ent:Activate() + + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:EnableMotion(not frozen) + end + + if instance.data.props.undo then + undo.Create("ACF Fuel Tank") + undo.SetPlayer(ply) + undo.AddEntity(ent) + undo.Finish("ACF Fuel Tank (" .. tostring(id) .. ")") + end + + ply:AddCleanup("props", ent) + register(ent, instance) + + return owrap(ent) +end - -- Returns true if the entity is an ACF ammo crate - function ents_methods:acfIsAmmo() - local This = unwrap(self) +--- Returns the specs of the fuel tank +-- @param id id of the engine or gearbox +-- @server +-- @return The specs table +function acf_library.getFuelTankSpecs(id) + checkluatype(id, TYPE_STRING) + + local list_entries = ACF.Weapons.FuelTanks + if not list_entries[id] then SF.Throw("Invalid id", 2) end + + local specs = table.Copy(list_entries[id]) + specs.BaseClass = nil + + return specs +end - if not ValidPhysics(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end +--- Returns a list of all fuel tanks +-- @server +-- @return The fuel tank list +function acf_library.getAllFuelTanks() + local list = {} + + for id, _ in pairs(ACF.Weapons.FuelTanks) do + table.insert(list, id) + end + + return list +end - return This:GetClass() == "acf_ammo" +--- Creates a fun given the id or name +-- @param pos Position of created gun +-- @param ang Angle of created gun +-- @param id id or name of the gun to create +-- @param frozen True to spawn frozen +-- @server +-- @return The created gun +function acf_library.createGun(pos, ang, id, frozen) + checkpermission(instance, nil, "acf.createGun") + + local ply = instance.player + + if not hook.Run("CanTool", ply, {Hit = true, Entity = game.GetWorld()}, "acfmenu") then SF.Throw("No permission to spawn ACF components", 2) end + + checktype(pos, vec_meta) + checktype(ang, ang_meta) + checkluatype(id, TYPE_STRING) + frozen = frozen and true or false + + local pos = vunwrap(pos) + local ang = aunwrap(ang) + + local list_entries = ACF.Weapons.Guns + + -- Not a valid id, try name + if not list_entries[id] then + id = idFromName(list_entries, id) + + -- Name is also invalid, error + if not id or not list_entries[id] then + SF.Throw("Invalid id or name", 2) + end end + + local type_id = list_entries[id] + local dupe_class = duplicator.FindEntityClass(type_id.ent) + + if not dupe_class then SF.Throw("Didn't find entity duplicator records", 2) end + + plyBurst:use(ply, 1) + plyCount:checkuse(ply, 1) + + local ent = dupe_class.Func(ply, SF.clampPos(pos), ang, id) + ent:Activate() + + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:EnableMotion(not frozen) + end + + if instance.data.props.undo then + undo.Create("ACF Gun") + undo.SetPlayer(ply) + undo.AddEntity(ent) + undo.Finish("ACF Gun (" .. tostring(id) .. ")") + end + + ply:AddCleanup("props", ent) + register(ent, instance) + + return owrap(ent) +end - -- Returns true if the entity is an ACF fuel tank - function ents_methods:acfIsFuel() - local This = unwrap(self) - - if not ValidPhysics(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end - - return This:GetClass() == "acf_fueltank" +--- Returns the specs of gun +-- @param id id or name of the gun +-- @server +-- @return The specs table +function acf_library.getGunSpecs(id) + checkluatype(id, TYPE_STRING) + + local list_entries = ACF.Weapons.Guns + + -- Not a valid id, try name + if not list_entries[id] then + id = idFromName(list_entries, id) + + -- Name is also invalid, error + if not id or not list_entries[id] then + SF.Throw("Invalid id or name", 2) + end end + + local specs = table.Copy(list_entries[id]) + specs.BaseClass = nil + + return specs +end - -- Returns the capacity of an acf ammo crate or fuel tank - function ents_methods:acfCapacity() - local This = unwrap(self) +--- Returns a list of all guns +-- @server +-- @return The guns list +function acf_library.getAllGuns() + local list = {} + + for id, _ in pairs(ACF.Weapons.Guns) do + table.insert(list, id) + end + + return list +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +-- Set ammo properties +local ammo_properties = {} + +for id, data in pairs(ACF.RoundTypes) do + ammo_properties[id] = { + name = data.name, + desc = data.desc, + model = data.model, + gun_blacklist = ACF.AmmoBlacklist[id], + create_data = {} + } +end - return This.Capacity or 0 +-- No other way to get this so hardcoded here it is ;( +local ammo_property_data = { + propellantLength = { + type = "number", + default = 0.01, + data = 3, + convert = function(value) return value end + }, + + projectileLength = { + type = "number", + default = 15, + data = 4, + convert = function(value) return value end + }, + + heFillerVolume = { + type = "number", + default = 0, + data = 5, + convert = function(value) return value end + }, + + tracer = { + type = "boolean", + default = false, + data = 10, + convert = function(value) return value and 0.5 or 0 end + } +} + +ammo_properties.AP.create_data = { + propellantLength = ammo_property_data.propellantLength, + projectileLength = ammo_property_data.projectileLength, + tracer = ammo_property_data.tracer +} + +ammo_properties.APHE.create_data = { + propellantLength = ammo_property_data.propellantLength, + projectileLength = ammo_property_data.projectileLength, + heFillerVolume = ammo_property_data.heFillerVolume, + tracer = ammo_property_data.tracer +} + +ammo_properties.FL.create_data = { + propellantLength = ammo_property_data.propellantLength, + projectileLength = ammo_property_data.projectileLength, + flechettes = { + type = "number", + default = 6, + data = 5, + convert = function(value) return value end + }, + flechettesSpread = { + type = "number", + default = 10, + data = 6, + convert = function(value) return value end + }, + tracer = ammo_property_data.tracer +} + +ammo_properties.HE.create_data = { + propellantLength = ammo_property_data.propellantLength, + projectileLength = ammo_property_data.projectileLength, + heFillerVolume = ammo_property_data.heFillerVolume, + tracer = ammo_property_data.tracer +} + +ammo_properties.HEAT.create_data = { + propellantLength = ammo_property_data.propellantLength, + projectileLength = ammo_property_data.projectileLength, + heFillerVolume = ammo_property_data.heFillerVolume, + crushConeAngle = { + type = "number", + default = 0, + data = 6, + convert = function(value) return value end + }, + tracer = ammo_property_data.tracer +} + +ammo_properties.HP.create_data = { + propellantLength = ammo_property_data.propellantLength, + projectileLength = ammo_property_data.projectileLength, + heFillerVolume = ammo_property_data.heFillerVolume, + hollowPointCavityVolume = { + type = "number", + default = 0, + data = 5, + convert = function(value) return value end + }, + tracer = ammo_property_data.tracer +} + +ammo_properties.SM.create_data = { + propellantLength = ammo_property_data.propellantLength, + projectileLength = ammo_property_data.projectileLength, + smokeFillerVolume = ammo_property_data.heFillerVolume, + wpFillerVolume = { + type = "number", + default = 0, + data = 6, + convert = function(value) return value end + }, + fuseTime = { + type = "number", + default = 0, + data = 7, + convert = function(value) return value end + }, + tracer = ammo_property_data.tracer +} + +ammo_properties.Refill.create_data = {} + +--- Creates a ammo box given the id +-- If ammo_data isn't provided default values will be used (same as in the ACF menu) +-- Possible values for ammo_data corresponding to ammo_id: +-- @param pos Position of created ammo box +-- @param ang Angle of created ammo box +-- @param id id of the ammo box to create +-- @param gun_id id of the gun +-- @param ammo_id id of the ammo +-- @param frozen True to spawn frozen +-- @param ammo_data the ammo data +-- @server +-- @return The created ammo box +-- +-- AP: +-- \- propellantLength (number) +-- \- projectileLength (number) +-- \- tracer (bool) +-- +-- APHE: +-- \- propellantLength (number) +-- \- projectileLength (number) +-- \- heFillerVolume (number) +-- \- tracer (bool) +-- +-- FL: +-- \- propellantLength (number) +-- \- projectileLength (number) +-- \- flechettes (number) +-- \- flechettesSpread (number) +-- \- tracer (bool) +-- +-- HE: +-- \- propellantLength (number) +-- \- projectileLength (number) +-- \- heFillerVolume (number) +-- \- tracer (bool) +-- +-- HEAT: +-- \- propellantLength (number) +-- \- projectileLength (number) +-- \- heFillerVolume (number) +-- \- crushConeAngle (number) +-- \- tracer (bool) +-- +-- HP: +-- \- propellantLength (number) +-- \- projectileLength (number) +-- \- heFillerVolume (number) +-- \- hollowPointCavityVolume (number) +-- \- tracer (bool) +-- +-- SM: +-- \- propellantLength (number) +-- \- projectileLength (number) +-- \- smokeFillerVolume (number) +-- \- wpFillerVolume (number) +-- \- fuseTime (number) +-- \- tracer (bool) +-- +-- Refil: +-- +function acf_library.createAmmo(pos, ang, id, gun_id, ammo_id, frozen, ammo_data) + checkpermission(instance, nil, "acf.createAmmo") + + local ply = instance.player + + if not hook.Run("CanTool", ply, {Hit = true, Entity = game.GetWorld()}, "acfmenu") then SF.Throw("No permission to spawn ACF components", 2) end + + checktype(pos, vec_meta) + checktype(ang, ang_meta) + checkluatype(id, TYPE_STRING) + checkluatype(ammo_id, TYPE_STRING) + checkluatype(gun_id, TYPE_STRING) + frozen = frozen and true or false + ammo_data = type(ammo_data) == "table" and ammo_data or {} + + local pos = vunwrap(pos) + local ang = aunwrap(ang) + + local list_entries = ACF.Weapons.Ammo + local type_id = list_entries[id] + if not type_id then SF.Throw("Invalid id", 2) end + + local ammo = ammo_properties[ammo_id] + if not ammo then SF.Throw("Invalid ammo id", 2) end + + local gun_list_entries = ACF.Weapons.Guns + if not gun_list_entries[gun_id] then + gun_id = idFromName(gun_list_entries, gun_id) + + if not gun_id or not gun_list_entries[gun_id] then + SF.Throw("Invalid gun id or name", 2) + end end - - -- Returns the path of an ACF entity's sound - function ents_methods:acfSoundPath() - local This = unwrap(self) - - if not IsACFEntity(This) then return "" end - if RestrictInfo(SF.instance.player, This) then return "" end - - return This.SoundPath or "" + + local dupe_class = duplicator.FindEntityClass(type_id.ent) + if not dupe_class then SF.Throw("Didn't find entity duplicator records", 2) end + + plyBurst:use(ply, 1) + plyCount:checkuse(ply, 1) + + local args_table = { + SF.clampPos(pos), + ang, + id, + gun_id, + ammo_id, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } + + for k, v in pairs(ammo.create_data) do + local value = ammo_data[k] + + if value then + if type(value) == v.type then + args_table[3 + v.data] = v.convert(value) + else + args_table[3 + v.data] = v.convert(v.default) + end + else + args_table[3 + v.data] = v.convert(v.default) + end end + + local ent = dupe_class.Func(ply, unpack(args_table)) + ent:Activate() + + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:EnableMotion(not frozen) + end + + if instance.data.props.undo then + undo.Create("ACF Ammo") + undo.SetPlayer(ply) + undo.AddEntity(ent) + undo.Finish("ACF Ammo (" .. tostring(id) .. ")") + end + + ply:AddCleanup("props", ent) + register(ent, instance) + + return owrap(ent) +end - -- Returns true if the acf engine, fuel tank, or ammo crate is active - function ents_methods:acfGetActive() - local This = unwrap(self) +--- Returns the specs of the ammo +-- @param id id of the ammo +-- @server +-- @return The specs table +function acf_library.getAmmoSpecs(id) + checkluatype(id, TYPE_STRING) + + local data = ammo_properties[id] + if not data then SF.Throw("Invalid id", 2) end + + local properties = {} + for name, d in pairs(data.create_data) do + properties[name] = { + type = d.type, + default = d.default, + convert = d.convert + } + end + + return { + name = data.name, + desc = data.desc, + model = data.model, + properties = table.Copy(data.create_data)--properties + } +end - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end +--- Returns a list of all ammo types +-- @server +-- @return The ammo list +function acf_library.getAllAmmo() + local list = {} + + for id, _ in pairs(ammo_properties) do + table.insert(list, id) + end + + return list +end - return (This.Active or This.Load) and true or false - end +--- Returns a list of all ammo boxes +-- @server +-- @return The ammo box list +function acf_library.getAllAmmoBoxes() + local list = {} + + for id, _ in pairs(ACF.Weapons.Ammo) do + table.insert(list, id) + end + + return list +end - -- Turns an ACF engine, ammo crate, or fuel tank on or off - function ents_methods:acfSetActive(On) - CheckLuaType(On, TYPE_BOOL) +---------------------------------------- +-- Entity Methods - local This = unwrap(self) +-- [General Functions ] -- - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end +-- Moved to acf lib +-- Returns true if functions returning sensitive info are restricted to owned props +--[[function ents_methods:acfInfoRestricted () + return GetConVar( "sbox_acf_restrictinfo" ):GetInt() ~= 0 +end]] - This:TriggerInput("Active", On and 1 or 0) - end +--- Returns true if this entity contains sensitive info and is not accessable to us +-- @server +function ents_methods:acfIsInfoRestricted () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Returns the current health of an entity - function ents_methods:acfPropHealth() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not ValidPhysics(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not ACF_Check(This) then return 0 end - if not This.ACF.Health then return 0 end + return restrictInfo( this ) +end - return Round(This.ACF.Health, 2) - end +--- Returns the short name of an ACF entity +-- @server +function ents_methods:acfNameShort () + checktype( self, ents_metatable ) + local this = unwrap( self ) + + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + + if isEngine( this ) then return this.Id or "" end + if isGearbox( this ) then return this.Id or "" end + if isGun( this ) then return this.Id or "" end + if isAmmo( this ) then return this.RoundId or "" end + if isFuel( this ) then return this.FuelType .. " " .. this.SizeId end + + return "" +end - -- Returns the current armor of an entity - function ents_methods:acfPropArmor() - local This = unwrap(self) +--- Returns the capacity of an acf ammo crate or fuel tank +-- @server +function ents_methods:acfCapacity () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not ValidPhysics(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not ACF_Check(This) then return 0 end - if not This.ACF.Armour then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(This.ACF.Armour, 2) - end + if not ( isAmmo( this ) or isFuel( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + return this.Capacity or 1 +end - -- Returns the max health of an entity - function ents_methods:acfPropHealthMax() - local This = unwrap(self) +--- Returns true if the acf engine, fuel tank, or ammo crate is active +-- @server +function ents_methods:acfGetActive () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not ValidPhysics(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not ACF_Check(This) then return 0 end - if not This.ACF.MaxHealth then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(This.ACF.MaxHealth, 2) + if not ( isEngine( this ) or isAmmo( this ) or isFuel( this ) ) then return false end + if restrictInfo( this ) then return false end + if not isAmmo( this ) then + if this.Active then return true end + else + if this.Load then return true end end + return false +end - -- Returns the max armor of an entity - function ents_methods:acfPropArmorMax() - local This = unwrap(self) +--- Turns an ACF engine, ammo crate, or fuel tank on or off +-- @server +function ents_methods:acfSetActive ( on ) + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not ValidPhysics(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not ACF_Check(This) then return 0 end - if not This.ACF.MaxArmour then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - return Round(This.ACF.MaxArmour, 2) - end + if not ( isEngine( this ) or isAmmo( this ) or isFuel( this ) ) then return end + this:TriggerInput( "Active", on and 1 or 0 ) +end - -- Returns the ductility of an entity - function ents_methods:acfPropDuctility() - local This = unwrap(self) +--- Returns true if hitpos is on a clipped part of prop +-- @server +function ents_methods:acfHitClip( hitpos ) + checktype( self, ents_metatable ) + checktype( hitpos, vec_meta ) + local this = unwrap( self ) + local hitpos = vunwrap( hitpos ) - if not ValidPhysics(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not ACF_Check(This) then return 0 end - if not This.ACF.Ductility then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) -- E2 has owner check so i guess having a check if the player has permission is sufficient enough? - return This.ACF.Ductility * 100 + if ACF_CheckClips( nil, nil, this, hitpos ) then return true else return false end +end + +local linkTables = +{ -- link resources within each ent type. should point to an ent: true if adding link.Ent, false to add link itself + acf_engine = { GearLink = true, FuelLink = false }, + acf_gearbox = { WheelLink = true, Master = false }, + acf_fueltank = { Master = false }, + acf_gun = { AmmoLink = false }, + acf_ammo = { Master = false } +} + +local function getLinks ( ent, enttype ) + local ret = {} + -- find the link resources available for this ent type + for entry, mode in pairs( linkTables[ enttype ] ) do + if not ent[ entry ] then error( "Couldn't find link resource " .. entry .. " for entity " .. tostring( ent ) ) return end + + -- find all the links inside the resources + for _, link in pairs( ent[ entry ] ) do + ret[ #ret + 1 ] = mode and wrap( link.Ent ) or link + end end - --returns true if hitpos is on a clipped part of prop - function ents_methods:acfHitClip(HitPos) - CheckType(HitPos, vec_metatable) + return ret +end - local This = unwrap(self) +local function searchForGearboxLinks ( ent ) + local boxes = ents.FindByClass( "acf_gearbox" ) - if not ValidPhysics(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end + local ret = {} - return ACF_CheckClips(This, HitPos) + for _, box in pairs( boxes ) do + if IsValid( box ) then + for _, link in pairs( box.WheelLink ) do + if link.Ent == ent then + ret[ #ret + 1 ] = wrap( box ) + break + end + end + end end - -- Returns the ACF links associated with the entity - function ents_methods:acfLinks() - local This = unwrap(self) + return ret +end - if not IsACFEntity(This) then return {} end - if RestrictInfo(SF.instance.player, This) then return {} end +--- Returns the ACF links associated with the entity +-- @server +function ents_methods:acfLinks () + checktype( self, ents_metatable ) + local this = unwrap( self ) - local Sources = AllLinkSources(This:GetClass()) - local Result = {} - local Count = 0 + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - for _, Function in pairs(Sources) do - for Entity in pairs(Function(This)) do - Count = Count + 1 - Result[Count] = wrap(Entity) - end - end + local enttype = this:GetClass() - return Result + if not linkTables[ enttype ] then + return searchForGearboxLinks( this ) end - --perform ACF links - function ents_methods:acfLinkTo(Target, Notify) - CheckType(Target, ents_metatable) - CheckLuaType(Notify, TYPE_BOOL) - - local This = unwrap(self) - local TargetEnt = unwrap(Target) + return getLinks( this, enttype ) +end - if not validPhysics(This) then return false end - if not validPhysics(TargetEnt) then return false end - if not (IsOwner(SF.instance.player, This) and IsOwner(SF.instance.player, TargetEnt)) then - if Notify then - ACF_SendNotify(SF.instance.player, 0, "Must be called on entities you own.") - end +--- Returns the full name of an ACF entity +-- @server +function ents_methods:acfName () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return false - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not This.Link then - if Notify then - ACF_SendNotify(SF.instance.player, 0, "This entity is not linkable.") - end + if isAmmo( this ) then return ( this.RoundId .. " " .. this.RoundType) end + if isFuel( this ) then return this.FuelType .. " " .. this.SizeId end - return false - end + local acftype = "" + if isEngine( this ) then acftype = "Mobility" end + if isGearbox( this ) then acftype = "Mobility" end + if isGun( this ) then acftype = "Guns" end + if ( acftype == "" ) then return "" end + local List = list.Get( "ACFEnts" ) + return List[ acftype ][ this.Id ][ "name" ] or "" +end - local Sucess, Message = This:Link(TargetEnt) +--- Returns the type of ACF entity +-- @server +function ents_methods:acfType () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if Notify then - ACF_SendNotify(SF.instance.player, Sucess, Message) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Sucess + if isEngine( this ) or isGearbox( this ) then + local List = list.Get( "ACFEnts" ) + return List[ "Mobility" ][ this.Id ][ "category" ] or "" + end + if isGun( this ) then + local Classes = list.Get( "ACFClasses" ) + return Classes[ "GunClass" ][ this.Class ][ "name" ] or "" end + if isAmmo( this ) then return this.RoundType or "" end + if isFuel( this ) then return this.FuelType or "" end + return "" +end - --perform ACF unlinks - function ents_methods:acfUnlinkFrom(Target, Notify) - CheckType(Target, ents_metatable) - CheckLuaType(Notify, TYPE_BOOL) +--- Perform ACF links +-- @server +function ents_methods:acfLinkTo ( target, notify ) + checktype( self, ents_metatable ) + checktype( target, ents_metatable ) - local This = unwrap(self) - local TargetEnt = unwrap(Target) + local this = unwrap( self ) + local tar = unwrap( target ) - if not validPhysics(This) then return false end - if not validPhysics(TargetEnt) then return false end - if not (IsOwner(SF.instance.player, This) and IsOwner(SF.instance.player, TargetEnt)) then - if Notify then - ACF_SendNotify(SF.instance.player, 0, "Must be called on entities you own.") - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + if not ( tar and tar:IsValid() ) then SF.Throw( "Invalid Link Entity", 2 ) end - return false - end + checkpermission( instance, this, "entities.acf" ) + checkpermission( instance, tar, "entities.acf" ) - if not This.Unlink then - if Notify then - ACF_SendNotify(SF.instance.player, 0, "This entity is not linkable.") - end + if not ( isGun( this ) or isEngine( this ) or isGearbox( this ) ) then + SF.Throw( "Target must be a gun, engine, or gearbox", 2 ) + end - return false - end + local success, msg = this:Link( tar ) + if notify then + ACF_SendNotify( self.player, success, msg ) + end + return success, msg +end - local Sucess, Message = This:Unlink(TargetEnt) +--- Perform ACF unlinks +-- @server +function ents_methods:acfUnlinkFrom ( target, notify ) + checktype( self, ents_metatable ) + checktype( target, ents_metatable ) - if Notify then - ACF_SendNotify(SF.instance.player, Sucess, Message) - end + local this = unwrap( self ) + local tar = unwrap( target ) - return Sucess + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + if not ( tar and tar:IsValid() ) then SF.Throw( "Invalid Link Entity", 2 ) end + + checkpermission( instance, this, "entities.acf" ) + checkpermission( instance, tar, "entities.acf" ) + + if not ( isGun( this ) or isEngine( this ) or isGearbox( this ) ) then + SF.Throw( "Target must be a gun, engine, or gearbox", 2 ) end - --===============================================================================================-- - -- Mobility Functions - --===============================================================================================-- + local success, msg = this:Unlink( tar ) + if notify then + ACF_SendNotify( self.player, success, msg ) + end + return success, msg +end - -- Returns true if an ACF entity is electric - function ents_methods:acfIsElectric() - local This = unwrap(self) +--- returns any wheels linked to this engine/gearbox or child gearboxes +-- @server +function ents_methods:acfGetLinkedWheels () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + if not ( isEngine(this) or isGearbox(this) ) then SF.Throw( "Target must be a engine, or gearbox", 2 ) end - return This.IsElectric or false + local wheels = {} + for k, ent in pairs( ACF_GetLinkedWheels( this ) ) do + table.insert( wheels, wrap( ent ) ) end - -- Returns the torque in N/m of an ACF engine - function ents_methods:acfMaxTorque() - local This = unwrap(self) + return wheels +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +-- [ Engine Functions ] -- - return This.PeakTorque or 0 - end +--- Returns true if the entity is an ACF engine +-- @server +function ents_methods:acfIsEngine () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Returns the power in kW of an ACF engine - function ents_methods:acfMaxPower() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + return isEngine( this ) +end - return GetMaxPower(This) - end +--- Returns true if an ACF engine is electric +-- @server +function ents_methods:acfIsElectric () + checktype( self, ents_metatable ) + local this = unwrap( self ) - function ents_methods:acfMaxTorqueWithFuel() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.PeakTorque then return 0 end + return this.iselec == true +end - return This.PeakTorque * ACF.TorqueBoost - end +--- Returns the torque in N/m of an ACF engine +-- @server +function ents_methods:acfMaxTorque () + checktype( self, ents_metatable ) + local this = unwrap( self ) - function ents_methods:acfMaxPowerWithFuel() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + if not isEngine( this ) then return 0 end + return this.PeakTorque or 0 +end - return GetMaxPower(This) * ACF.TorqueBoost - end +--- Returns the torque in N/m of an ACF engine with fuel +-- @server +function ents_methods:acfMaxTorqueWithFuel () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Returns the idle rpm of an ACF engine - function ents_methods:acfIdleRPM() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + if not isEngine( this ) then return 0 end + return (this.PeakTorque or 0) * (ACF.TorqueBoost or 0) +end - return This.IdleRPM or 0 +local function getMaxPower( ent ) + local peakpower + + if ent.iselec then + peakpower = math.floor( ent.PeakTorque * ent.LimitRPM / ( 4 * 9548.8 ) ) + else + peakpower = math.floor( ent.PeakTorque * ent.PeakMaxRPM / 9548.8 ) end + + return peakpower or 0 +end - -- Returns the powerband min of an ACF engine - function ents_methods:acfPowerbandMin() - local This = unwrap(self) +--- Returns the power in kW of an ACF engine +-- @server +function ents_methods:acfMaxPower () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.PeakMinRPM then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if This.IsElectric and This.IdleRPM then - return math.max(This.IdleRPM, This.PeakMinRPM) - end + return isEngine( this ) and getMaxPower( this ) or 0 +end - return This.PeakMinRPM - end +--- Returns the power in kW of an ACF engine with fuel +-- @server +function ents_methods:acfMaxPowerWithFuel () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Returns the powerband max of an ACF engine - function ents_methods:acfPowerbandMax() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + return (isEngine( this ) and getMaxPower( this ) or 0) * (ACF.TorqueBoost or 0) +end - if This.IsElectric and This.LimitRPM then - return floor(This.LimitRPM * 0.5) - end +--- Returns the idle rpm of an ACF engine +-- @server +function ents_methods:acfIdleRPM () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return This.PeakMaxRPM or 0 - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the redline rpm of an ACF engine - function ents_methods:acfRedline() - local This = unwrap(self) + if not isEngine( this ) then return 0 end + return this.IdleRPM or 0 +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +--- Returns the powerband min and max of an ACF Engine +-- @server +function ents_methods:acfPowerband () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return This.LimitRPM or 0 - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the current rpm of an ACF engine - function ents_methods:acfRPM() - local This = unwrap(self) + if not isEngine( this ) then return 0, 0 end + return this.PeakMinRPM or 0, this.PeakMaxRPM or 0 +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.FlyRPM then return 0 end +--- Returns the powerband min of an ACF engine +-- @server +function ents_methods:acfPowerbandMin () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return floor(This.FlyRPM) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the current torque of an ACF engine - function ents_methods:acfTorque() - local This = unwrap(self) + if not isEngine( this ) then return 0 end + return this.PeakMinRPM or 0 +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.Torque then return 0 end +--- Returns the powerband max of an ACF engine +-- @server +function ents_methods:acfPowerbandMax () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return floor(This.Torque) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the inertia of an ACF engine's flywheel - function ents_methods:acfFlyInertia() - local This = unwrap(self) + if not isEngine( this ) then return 0 end + return this.PeakMaxRPM or 0 +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +--- Returns the redline rpm of an ACF engine +-- @server +function ents_methods:acfRedline () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return This.Inertia or 0 - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the mass of an ACF engine's flywheel - function ents_methods:acfFlyMass() - local This = unwrap(self) + if not isEngine( this ) then return 0 end + return this.LimitRPM or 0 +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.Inertia then return 0 end +--- Returns the current rpm of an ACF engine +-- @server +function ents_methods:acfRPM () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return (This.Inertia / 3.1416) * (This.Inertia / 3.1416) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - --- Returns the current power of an ACF engine - function ents_methods:acfPower() - local This = unwrap(self) + if not isEngine( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return math.floor( this.FlyRPM ) or 0 +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.Torque then return 0 end - if not This.FlyRPM then return 0 end +--- Returns the current torque of an ACF engine +-- @server +function ents_methods:acfTorque () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return floor(This.Torque * This.FlyRPM / 9548.8) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns true if the RPM of an ACF engine is inside the powerband - function ents_methods:acfInPowerband() - local This = unwrap(self) + if not isEngine( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return math.floor( this.Torque or 0 ) +end - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end - if not This.FlyRPM then return false end +--- Returns the inertia of an ACF engine's flywheel +-- @server +function ents_methods:acfFlyInertia () + checktype( self, ents_metatable ) + local this = unwrap( self ) - local PowerbandMin - local PowerbandMax + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if This.IsElectric then - PowerbandMin = This.IdleRPM - PowerbandMax = floor((This.LimitRPM or 0) * 0.5) - else - PowerbandMin = This.PeakMinRPM - PowerbandMax = This.PeakMaxRPM - end + if not isEngine( this ) then return nil end + if restrictInfo( this ) then return 0 end + return this.Inertia or 0 +end - if not PowerbandMin then return false end - if not PowerbandMax then return false end - if This.FlyRPM < PowerbandMin then return false end - if This.FlyRPM > PowerbandMax then return false end +--- Returns the mass of an ACF engine's flywheel +-- @server +function ents_methods:acfFlyMass () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return true - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - function ents_methods:acfGetThrottle() - local This = unwrap(self) + if not isEngine( this ) then return nil end + if restrictInfo( this ) then return 0 end + return this.Inertia / ( 3.1416 )^2 or 0 +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.Throttle then return 0 end +--- Returns the current power of an ACF engine +-- @server +function ents_methods:acfPower () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return This.Throttle * 100 - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Sets the throttle value for an ACF engine - function ents_methods:acfSetThrottle(Throttle) - CheckLuaType(Throttle, TYPE_NUMBER) + if not isEngine( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return math.floor( ( this.Torque or 0 ) * ( this.FlyRPM or 0 ) / 9548.8 ) +end - local This = unwrap(self) +--- Returns true if the RPM of an ACF engine is inside the powerband +-- @server +function ents_methods:acfInPowerband () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - This:TriggerInput("Throttle", Throttle) - end + if not isEngine( this ) then return false end + if restrictInfo( this ) then return false end + if ( this.FlyRPM < this.PeakMinRPM ) then return false end + if ( this.FlyRPM > this.PeakMaxRPM ) then return false end - -- Returns the current gear for an ACF gearbox - function ents_methods:acfGear() - local This = unwrap(self) + return true +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +--- Returns the throttle value +-- @server +function ents_methods:acfGetThrottle () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return This.Gear or 0 - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the number of gears for an ACF gearbox - function ents_methods:acfNumGears() - local This = unwrap(self) + if not isEngine( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return ( this.Throttle or 0 ) * 100 +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +--- Sets the throttle value for an ACF engine +-- @server +function ents_methods:acfSetThrottle ( throttle ) + checktype( self, ents_metatable ) + checkluatype( throttle, TYPE_NUMBER ) + local this = unwrap( self ) - return This.Gears or 0 - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - -- Returns the final ratio for an ACF gearbox - function ents_methods:acfFinalRatio() - local This = unwrap(self) + if not isEngine( this ) then return end + this:TriggerInput( "Throttle", throttle ) +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.GearTable then return 0 end - return This.GearTable.Final or 0 - end +-- [ Gearbox Functions ] -- - -- Returns the total ratio (current gear * final) for an ACF gearbox - function ents_methods:acfTotalRatio() - local This = unwrap(self) +--- Returns true if the entity is an ACF gearbox +-- @server +function ents_methods:acfIsGearbox () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.GearRatio or 0 - end + return isGearbox( this ) +end - -- Returns the max torque for an ACF gearbox - function ents_methods:acfTorqueRating() - local This = unwrap(self) +--- Returns the current gear for an ACF gearbox +-- @server +function ents_methods:acfGear () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.MaxTorque or 0 - end + if not isGearbox( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return this.Gear or 0 +end - -- Returns whether an ACF gearbox is dual clutch - function ents_methods:acfIsDual() - local This = unwrap(self) +--- Returns the number of gears for an ACF gearbox +-- @server +function ents_methods:acfNumGears () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.Dual or false - end + if not isGearbox( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return this.Gears or 0 +end - -- Returns the time in ms an ACF gearbox takes to change gears - function ents_methods:acfShiftTime() - local This = unwrap(self) +--- Returns the final ratio for an ACF gearbox +-- @server +function ents_methods:acfFinalRatio () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.SwitchTime then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.SwitchTime * 1000 - end + if not isGearbox( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return this.GearTable[ "Final" ] or 0 +end - -- Returns true if an ACF gearbox is in gear - function ents_methods:acfInGear() - local This = unwrap(self) +--- Returns the total ratio (current gear * final) for an ACF gearbox +-- @server +function ents_methods:acfTotalRatio () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.InGear or false - end + if not isGearbox( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return this.GearRatio or 0 +end - -- Returns the ratio for a specified gear of an ACF gearbox - function ents_methods:acfGearRatio(Gear) - CheckLuaType(Gear, TYPE_NUMBER) +--- Returns the max torque for an ACF gearbox +-- @server +function ents_methods:acfTorqueRating () + checktype( self, ents_metatable ) + local this = unwrap( self ) - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.GearTable then return 0 end - if not This.Gears then return 0 end + if not isGearbox( this ) then return 0 end + return this.MaxTorque or 0 +end - local GearNum = math.Clamp(floor(Gear), 1, This.Gears) +--- Returns whether an ACF gearbox is dual clutch +-- @server +function ents_methods:acfIsDual () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return This.GearTable[GearNum] or 0 - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the current torque output for an ACF gearbox - function ents_methods:acfTorqueOut() - local This = unwrap(self) + if not isGearbox( this ) then return false end + if restrictInfo( this ) then return false end + + return this.Dual +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +--- Returns the time in ms an ACF gearbox takes to change gears +-- @server +function ents_methods:acfShiftTime () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return math.min(This.TotalReqTq or 0, This.MaxTorque or 0) / (This.GearRatio or 1) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Sets the gear ratio of a CVT, set to 0 to use built-in algorithm - function ents_methods:acfCVTRatio(Ratio) - CheckLuaType(Ratio, TYPE_NUMBER) + if not isGearbox( this ) then return 0 end + return ( this.SwitchTime or 0 ) * 1000 +end - local This = unwrap(self) +--- Returns true if an ACF gearbox is in gear +-- @server +function ents_methods:acfInGear () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end - if not This.CVT then return end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - This:TriggerInput("CVT Ratio", math.Clamp(Ratio, 0, 1)) - end + if not isGearbox( this ) then return false end + if restrictInfo( this ) then return false end + + return this.InGear +end - -- Sets the current gear for an ACF gearbox - function ents_methods:acfShift(Gear) - CheckLuaType(Gear, TYPE_NUMBER) +--- Returns the ratio for a specified gear of an ACF gearbox +-- @server +function ents_methods:acfGearRatio ( gear ) + checktype( self, ents_metatable ) + checkluatype( gear, TYPE_NUMBER ) + local this = unwrap( self ) - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end + if not isGearbox( this ) then return 0 end + if restrictInfo( this ) then return 0 end + local g = math.Clamp( math.floor( gear ), 1, this.Gears ) + return this.GearTable[ g ] or 0 +end - This:TriggerInput("Gear", Gear) - end +--- Returns the current torque output for an ACF gearbox +-- @server +function ents_methods:acfTorqueOut () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Cause an ACF gearbox to shift up - function ents_methods:acfShiftUp() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end + if not isGearbox( this ) then return 0 end + return math.min( this.TotalReqTq or 0, this.MaxTorque or 0 ) / ( this.GearRatio or 1 ) +end - This:TriggerInput("Gear Up", 1) - end +--- Sets the gear ratio of a CVT, set to 0 to use built-in algorithm +-- @server +function ents_methods:acfCVTRatio ( ratio ) + checktype( self, ents_metatable ) + checkluatype( ratio, TYPE_NUMBER ) + local this = unwrap( self ) - -- Cause an ACF gearbox to shift down - function ents_methods:acfShiftDown() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + if not this.CVT then return end + this.CVTRatio = math.Clamp( ratio, 0, 1 ) +end - This:TriggerInput("Gear Down", 1) - end +--- Sets the current gear for an ACF gearbox +-- @server +function ents_methods:acfShift ( gear ) + checktype( self, ents_metatable ) + checkluatype( gear, TYPE_NUMBER ) + local this = unwrap( self ) - -- Sets the brakes for an ACF gearbox - function ents_methods:acfBrake(Brake) - CheckLuaType(Brake, TYPE_NUMBER) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - local This = unwrap(self) + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + this:TriggerInput( "Gear", gear ) +end - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end +--- Cause an ACF gearbox to shift up +-- @server +function ents_methods:acfShiftUp () + checktype( self, ents_metatable ) + local this = unwrap( self ) - This:TriggerInput("Brake", Brake) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - -- Sets the left brakes for an ACF gearbox - function ents_methods:acfBrakeLeft(Brake) - CheckLuaType(Brake, TYPE_NUMBER) + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + this:TriggerInput( "Gear Up", 1 ) --doesn't need to be toggled off +end - local This = unwrap(self) +--- Cause an ACF gearbox to shift down +-- @server +function ents_methods:acfShiftDown () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end - if not This.Dual then return end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - This:TriggerInput("Left Brake", Brake) - end + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + this:TriggerInput( "Gear Down", 1 ) --doesn't need to be toggled off +end - -- Sets the right brakes for an ACF gearbox - function ents_methods:acfBrakeRight (Brake) - CheckLuaType(Brake, TYPE_NUMBER) +--- Sets the brakes for an ACF gearbox +-- @server +function ents_methods:acfBrake ( brake ) + checktype( self, ents_metatable ) + checkluatype( brake, TYPE_NUMBER ) + local this = unwrap( self ) - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end - if not This.Dual then return end + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + this:TriggerInput( "Brake", brake ) +end - This:TriggerInput("Right Brake", Brake) - end +--- Sets the left brakes for an ACF gearbox +-- @server +function ents_methods:acfBrakeLeft ( brake ) + checktype( self, ents_metatable ) + checkluatype( brake, TYPE_NUMBER ) + local this = unwrap( self ) - -- Sets the clutch for an ACF gearbox - function ents_methods:acfClutch(Clutch) - CheckLuaType(Clutch, TYPE_NUMBER) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - local This = unwrap(self) + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + if not this.Dual then return end + this:TriggerInput( "Left Brake", brake ) +end - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end +--- Sets the right brakes for an ACF gearbox +-- @server +function ents_methods:acfBrakeRight ( brake ) + checktype( self, ents_metatable ) + checkluatype( brake, TYPE_NUMBER ) + local this = unwrap( self ) - This:TriggerInput("Clutch", Clutch) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - -- Sets the left clutch for an ACF gearbox - function ents_methods:acfClutchLeft(Clutch) - CheckLuaType(Clutch, TYPE_NUMBER) + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + if not this.Dual then return end + this:TriggerInput("Right Brake", brake ) +end - local This = unwrap(self) +--- Sets the clutch for an ACF gearbox +-- @server +function ents_methods:acfClutch ( clutch ) + checktype( self, ents_metatable ) + checkluatype( clutch, TYPE_NUMBER ) + local this = unwrap( self ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end - if not This.Dual then return end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - This:TriggerInput("Left Clutch", Clutch) - end + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + this:TriggerInput( "Clutch", clutch ) +end - -- Sets the right clutch for an ACF gearbox - function ents_methods:acfClutchRight(Clutch) - CheckLuaType(Clutch, TYPE_NUMBER) +--- Sets the left clutch for an ACF gearbox +-- @server +function ents_methods:acfClutchLeft( clutch ) + checktype( self, ents_metatable ) + checkluatype( clutch, TYPE_NUMBER ) + local this = unwrap( self ) - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end - if not This.Dual then return end + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + if not this.Dual then return end + this:TriggerInput( "Left Clutch", clutch ) +end - This:TriggerInput("Right Clutch", Clutch) - end +--- Sets the right clutch for an ACF gearbox +-- @server +function ents_methods:acfClutchRight ( clutch ) + checktype( self, ents_metatable ) + checkluatype( clutch, TYPE_NUMBER ) + local this = unwrap( self ) - -- Sets the steer ratio for an ACF gearbox - function ents_methods:acfSteerRate(Rate) - CheckLuaType(Rate, TYPE_NUMBER) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - local This = unwrap(self) + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + if not this.Dual then return end + this:TriggerInput( "Right Clutch", clutch ) +end - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end - if not This.DoubleDiff then return end +--- Sets the steer ratio for an ACF gearbox +-- @server +function ents_methods:acfSteerRate ( rate ) + checktype( self, ents_metatable ) + checkluatype( rate, TYPE_NUMBER ) + local this = unwrap( self ) - This:TriggerInput("Steer Rate", Rate) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - -- Applies gear hold for an automatic ACF gearbox - function ents_methods:acfHoldGear(Hold) - CheckLuaType(Hold, TYPE_BOOL) + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + if not this.DoubleDiff then return end + this:TriggerInput( "Steer Rate", rate ) +end - local This = unwrap(self) +--- Applies gear hold for an automatic ACF gearbox +-- @server +function ents_methods:acfHoldGear( hold ) + checktype( self, ents_metatable ) + checkluatype( hold, TYPE_NUMBER ) + local this = unwrap( self ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end - if not This.Auto then return end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - This:TriggerInput("Hold Gear", Hold) - end + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + if not this.Auto then return end + this:TriggerInput( "Hold Gear", hold ) +end - -- Sets the shift point scaling for an automatic ACF gearbox - function ents_methods:acfShiftPointScale(Scale) - CheckLuaType(Scale, TYPE_NUMBER) +--- Sets the shift point scaling for an automatic ACF gearbox +-- @server +function ents_methods:acfShiftPointScale( scale ) + checktype( self, ents_metatable ) + checkluatype( scale, TYPE_NUMBER ) + local this = unwrap( self ) - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end - if not This.Auto then return end + if not isGearbox( this ) then return end + if restrictInfo( this ) then return end + if not this.Auto then return end + this:TriggerInput( "Shift Speed Scale", scale ) +end - This:TriggerInput("Shift Speed Scale", Scale) - end - -- Returns true if the current engine requires fuel to run - function ents_methods:acfFuelRequired() - local This = unwrap(self) +-- [ Gun Functions ] -- - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end +--- Returns true if the entity is an ACF gun +-- @server +function ents_methods:acfIsGun () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return This.RequiresFuel or false - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Sets the ACF fuel tank refuel duty status, which supplies fuel to other fuel tanks - function ents_methods:acfRefuelDuty (On) - CheckLuaType(On, TYPE_BOOL) + if isGun( this ) and not restrictInfo( this ) then return true else return false end +end - local This = unwrap(self) +--- Returns true if the ACF gun is ready to fire +-- @server +function ents_methods:acfReady () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - This:TriggerInput("Refuel Duty", On) - end + if not isGun( this ) then return false end + if restrictInfo( this ) then return false end + if ( this.Ready ) then return true end + return false +end - -- Returns the remaining liters or kilowatt hours of fuel in an ACF fuel tank or engine - function ents_methods:acfFuel () - local This = unwrap(self) +--- Returns the magazine size for an ACF gun +-- @server +function ents_methods:acfMagSize () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end - if This.Fuel then return Round(This.Fuel, 2) end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - local Fuel = 0 - local Source = LinkSource(This:GetClass(), "FuelTanks") + if not isGun( this ) then return 0 end + return this.MagSize or 1 +end - if not Source then return 0 end +--- Returns the spread for an ACF gun or flechette ammo +-- @server +function ents_methods:acfSpread () + checktype( self, ents_metatable ) + local this = unwrap( self ) - for Tank in pairs(Source(This)) do - Fuel = Fuel + Tank.Fuel - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(Fuel, 2) + if not isGun( this ) or isAmmo( this ) then return 0 end + local Spread = this.GetInaccuracy and this:GetInaccuracy() or this.Inaccuracy or 0 + if this.BulletData[ "Type" ] == "FL" then + if restrictInfo( this ) then return Spread end + return Spread + ( this.BulletData[ "FlechetteSpread" ] or 0 ) end + return Spread +end - -- Returns the amount of fuel in an ACF fuel tank or linked to engine as a percentage of capacity - function ents_methods:acfFuelLevel() - local This = unwrap(self) +--- Returns true if an ACF gun is reloading +-- @server +function ents_methods:acfIsReloading () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end - if This.Capacity then return Round((This.Fuel or 0) / This.Capacity, 2) end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - local Fuel = 0 - local Capacity = 0 - local Source = LinkSource(This:GetClass(), "FuelTanks") + if not isGun( this ) then return false end + if restrictInfo( this ) then return false end + if (this.Reloading) then return true end + return false +end - if not Source then return 0 end +--- Returns the rate of fire of an acf gun +-- @server +function ents_methods:acfFireRate () + checktype( self, ents_metatable ) + local this = unwrap( self ) - for Tank in pairs(Source(This)) do - Fuel = Fuel + Tank.Fuel - Capacity = Capacity + Tank.Capacity - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(Fuel / Capacity, 2) - end + if not isGun( this ) then return 0 end + return math.Round( this.RateOfFire or 0, 3 ) +end - -- Returns the current fuel consumption in liters per minute or kilowatts of an engine - function ents_methods:acfFuelUse() - local This = unwrap(self) +--- Returns the number of rounds left in a magazine for an ACF gun +-- @server +function ents_methods:acfMagRounds () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.GetConsumption then return 0 end - if not This.Throttle then return 0 end - if not This.FlyRPM then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This:GetConsumption(This.Throttle, This.FlyRPM) * 60 + if not isGun( this ) then return 0 end + if restrictInfo( this ) then return 0 end + if this.MagSize > 1 then + return ( this.MagSize - this.CurrentShot ) or 1 end + if this.Ready then return 1 end + return 0 +end - -- Returns the peak fuel consumption in liters per minute or kilowatts of an engine at powerband max, for the current fuel type the engine is using - function ents_methods:acfPeakFuelUse() - local This = unwrap(self) +--- Sets the firing state of an ACF weapon +-- @server +function ents_methods:acfFire ( fire ) + checktype( self, ents_metatable ) + checkluatype( fire, TYPE_NUMBER ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.GetConsumption then return 0 end - if not This.LimitRPM then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - return This:GetConsumption(1, This.LimitRPM) * 60 - end + if not isGun( this ) then return end + + this:TriggerInput( "Fire", fire ) +end - -- returns any wheels linked to this mobility setup - function ents_methods:acfGetLinkedWheels() - local This = unwrap(self) +--- Causes an ACF weapon to unload +-- @server +function ents_methods:acfUnload () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return {} end - if RestrictInfo(SF.instance.player, This) then return {} end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - local Wheels = {} - local Count = 0 + if not isGun( this ) then return end + + this:UnloadAmmo() +end - for Wheel in pairs(GetLinkedWheels(This)) do - Count = Count + 1 - Wheels[Count] = wrap(Wheel) - end +--- Causes an ACF weapon to reload +-- @server +function ents_methods:acfReload () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return Wheels - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - --===============================================================================================-- - -- Weaponry Functions - --===============================================================================================-- + if not isGun( this ) then return end + + this.Reloading = true +end - -- Returns true if the ACF gun is ready to fire - function ents_methods:acfReady() - local This = unwrap(self) +--- Returns the number of rounds in active ammo crates linked to an ACF weapon +-- @server +function ents_methods:acfAmmoCount () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end - if not This.State then return false end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.State == "Loaded" + if not isGun( this ) then return 0 end + if restrictInfo( this ) then return 0 end + local Ammo = 0 + for Key, AmmoEnt in pairs( this.AmmoLink ) do + if AmmoEnt and AmmoEnt:IsValid() and AmmoEnt[ "Load" ] then + Ammo = Ammo + ( AmmoEnt.Ammo or 0 ) + end end + return Ammo +end - -- Returns the state of the ACF entity - function ents_methods:acfState() - local This = unwrap(self) +--- Returns the number of rounds in all ammo crates linked to an ACF weapon +-- @server +function ents_methods:acfTotalAmmoCount () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return "" end - if RestrictInfo(SF.instance.player, This) then return "" end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.State or "" + if not isGun( this ) then return 0 end + if restrictInfo( this ) then return 0 end + local Ammo = 0 + for Key, AmmoEnt in pairs( this.AmmoLink ) do + if AmmoEnt and AmmoEnt:IsValid() then + Ammo = Ammo + ( AmmoEnt.Ammo or 0 ) + end end + return Ammo +end - -- Returns time to next shot of an ACF weapon - function ents_methods:acfReloadTime() - local This = unwrap(self) +--- Returns time to next shot of an ACF weapon +-- @server +function ents_methods:acfReloadTime () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if This.State and This.State == "Loaded" then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return GetReloadTime(This) - end - - -- Returns number between 0 and 1 which represents reloading progress of an ACF weapon. Useful for progress bars - function ents_methods:acfReloadProgress() - local This = unwrap(self) + if restrictInfo( this ) or not isGun( this ) or this.Ready then return 0 end + return reloadTime( this ) +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.NextFire then return 0 end +--- Returns number between 0 and 1 which represents reloading progress of an ACF weapon. Useful for progress bars +-- @server +function ents_methods:acfReloadProgress () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return math.Clamp(1 - (This.NextFire - CurTime()) / GetReloadTime(This), 0, 1) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns time it takes for an ACF weapon to reload magazine - function ents_methods:acfMagReloadTime() - local This = unwrap(self) + if restrictInfo( this ) or not isGun( this ) or this.Ready then return 1 end + return math.Clamp( 1 - (this.NextFire - CurTime()) / reloadTime( this ), 0, 1 ) +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +--- Returns time it takes for an ACF weapon to reload magazine +-- @server +function ents_methods:acfMagReloadTime () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return This.MagReload or 0 - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the magazine size for an ACF gun - function ents_methods:acfMagSize() - local This = unwrap(self) + if restrictInfo( instance.player , this ) or not isGun( this ) or not this.MagReload then return 0 end + return this.MagReload +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +-- [ Ammo Functions ] -- - return This.MagSize or 0 - end +--- Returns true if the entity is an ACF ammo crate +-- @server +function ents_methods:acfIsAmmo () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Returns the spread for an ACF gun or flechette ammo - function ents_methods:acfSpread() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + return isAmmo( this ) and not restrictInfo( this ) +end - local Spread = (This.GetSpread and This:GetSpread()) or This.Spread or 0 +--- Returns the rounds left in an acf ammo crate +-- @server +function ents_methods:acfRounds () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if This.BulletData and This.BulletData.Type == "FL" then - return Spread + (This.BulletData.FlechetteSpread or 0) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Spread - end + if not isAmmo( this ) then return 0 end + if restrictInfo( this ) then return 0 end + return this.Ammo or 0 +end - -- Returns true if an ACF gun is reloading - function ents_methods:acfIsReloading() - local This = unwrap(self) +--- Returns the type of weapon the ammo in an ACF ammo crate loads into +-- @server +function ents_methods:acfRoundType () --cartridge? + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return false end - if RestrictInfo(SF.instance.player, This) then return false end - if not This.State then return false end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.State == "Reloading" - end + if not isAmmo( this ) then return "" end + if restrictInfo( this ) then return "" end + --return this.RoundId or "" + return this.RoundType or "" -- E2 uses this one now +end - -- Returns the rate of fire of an acf gun - function ents_methods:acfFireRate() - local This = unwrap(self) +--- Returns the type of ammo in a crate or gun +-- @server +function ents_methods:acfAmmoType () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.ReloadTime then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(60 / This.ReloadTime, 2) - end + if not isAmmo( this ) or isGun( this ) then return "" end + if restrictInfo( this ) then return "" end + return this.BulletData[ "Type" ] or "" +end - -- Returns the number of rounds left in a magazine for an ACF gun - function ents_methods:acfMagRounds() - local This = unwrap(self) +--- Returns the caliber of an ammo or gun +-- @server +function ents_methods:acfCaliber () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.CurrentShot or 0 - end + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + return ( this.Caliber or 0 ) * 10 +end - -- Sets the firing state of an ACF weapon - function ents_methods:acfFire(Fire) - CheckLuaType(Fire, TYPE_BOOL) +--- Returns the muzzle velocity of the ammo in a crate or gun +-- @server +function ents_methods:acfMuzzleVel () + checktype( self, ents_metatable ) + local this = unwrap( self ) - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + return math.Round( ( this.BulletData[ "MuzzleVel" ] or 0 ) * ACF.VelScale, 3 ) +end - This:TriggerInput("Fire", Fire) - end +--- Returns the mass of the projectile in a crate or gun +-- @server +function ents_methods:acfProjectileMass () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Causes an ACF weapon to unload - function ents_methods:acfUnload() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + return math.Round( this.BulletData[ "ProjMass" ] or 0, 3 ) +end - This:TriggerInput("Unload", true) - end +--- Returns the number of projectiles in a flechette round +-- @server +function ents_methods:acfFLSpikes () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Causes an ACF weapon to reload - function ents_methods:acfReload() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return end - if not IsOwner(SF.instance.player, This) then return end + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + if not this.BulletData[ "Type" ] == "FL" then return 0 end + return this.BulletData[ "Flechettes" ] or 0 +end - This:TriggerInput("Reload", true) - end +--- Returns the mass of a single spike in a FL round in a crate or gun +-- @server +function ents_methods:acfFLSpikeMass () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Returns the rounds left in an acf ammo crate - function ents_methods:acfRounds() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + if not this.BulletData[ "Type" ] == "FL" then return 0 end + return math.Round( this.BulletData[ "FlechetteMass" ] or 0, 3) +end - return This.Ammo or 0 - end +--- Returns the radius of the spikes in a flechette round in mm +-- @server +function ents_methods:acfFLSpikeRadius () + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Returns the type of weapon the ammo in an ACF ammo crate loads into - function ents_methods:acfRoundType() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return "" end - if RestrictInfo(SF.instance.player, This) then return "" end + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + if not this.BulletData[ "Type" ] == "FL" then return 0 end + return math.Round( ( this.BulletData[ "FlechetteRadius" ] or 0 ) * 10, 3) +end - return This.RoundId or "" - end +--- Returns the penetration of an AP, APHE, or HEAT round +-- @server +function ents_methods:acfPenetration () + checktype( self, ents_metatable ) + local this = unwrap( self ) + + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + local Type = this.BulletData[ "Type" ] or "" + local Energy + + --[[if Type == "AP" or Type == "APHE" then + Energy = ACF_Kinetic( this.BulletData[ "MuzzleVel" ] * 39.37, this.BulletData[ "ProjMass" ] - ( this.BulletData[ "FillerMass" ] or 0 ), this.BulletData[ "LimitVel" ] ) + return math.Round( ( Energy.Penetration / this.BulletData[ "PenAera" ] ) * ACF.KEtoRHA, 3 ) + elseif Type == "HEAT" then + Energy = ACF_Kinetic( this.BulletData[ "SlugMV" ] * 39.37, this.BulletData[ "SlugMass" ], 9999999 ) + return math.Round( ( Energy.Penetration / this.BulletData[ "SlugPenAera" ] ) * ACF.KEtoRHA, 3 ) + elseif Type == "FL" then + Energy = ACF_Kinetic( this.BulletData[ "MuzzleVel" ] * 39.37 , this.BulletData[ "FlechetteMass" ], this.BulletData[ "LimitVel" ] ) + return math.Round( ( Energy.Penetration / this.BulletData[ "FlechettePenArea" ] ) * ACF.KEtoRHA, 3 ) + end]] + + if Type == "AP" or Type == "APHE" then + Energy = ACF_Kinetic(this.BulletData["MuzzleVel"]*39.37, this.BulletData["ProjMass"] - (this.BulletData["FillerMass"] or 0), this.BulletData["LimitVel"] ) + return math.Round((Energy.Penetration/this.BulletData["PenAera"])*ACF.KEtoRHA,3) + elseif Type == "HEAT" then + local Crushed, HEATFillerMass, BoomFillerMass = ACF.RoundTypes["HEAT"].CrushCalc(this.BulletData.MuzzleVel, this.BulletData.FillerMass) + if Crushed == 1 then return 0 end -- no HEAT jet to fire off, it was all converted to HE + Energy = ACF_Kinetic(ACF.RoundTypes["HEAT"].CalcSlugMV( this.BulletData, HEATFillerMass )*39.37, this.BulletData["SlugMass"], 9999999 ) + return math.Round((Energy.Penetration/this.BulletData["SlugPenAera"])*ACF.KEtoRHA,3) + elseif Type == "FL" then + Energy = ACF_Kinetic(this.BulletData["MuzzleVel"]*39.37 , this.BulletData["FlechetteMass"], this.BulletData["LimitVel"] ) + return math.Round((Energy.Penetration/this.BulletData["FlechettePenArea"])*ACF.KEtoRHA, 3) + end + + return 0 +end - -- Returns the type of ammo in a crate or gun - function ents_methods:acfAmmoType() - local This = unwrap(self) +--- Returns the blast radius of an HE, APHE, or HEAT round +-- @server +function ents_methods:acfBlastRadius () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return "" end - if RestrictInfo(SF.instance.player, This) then return "" end - if not This.BulletData then return "" end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.BulletData.Type or "" + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + local Type = this.BulletData[ "Type" ] or "" + if Type == "HE" or Type == "APHE" then + return math.Round( this.BulletData[ "FillerMass" ]^0.33 * 8, 3 ) + elseif Type == "HEAT" then + return math.Round( ( this.BulletData[ "FillerMass" ] / 3)^0.33 * 8, 3 ) end + return 0 +end - -- [ Ammo Functions ] -- +--- Returns the drag coef of the ammo in a crate or gun +-- @server +function ents_methods:acfDragCoef() + checktype( self, ents_metatable ) + local this = unwrap( self ) - -- Returns the caliber of an ammo or gun - function ents_methods:acfCaliber() - local This = unwrap(self) + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.Caliber then return 0 end + if not ( isAmmo( this ) or isGun( this ) ) then return 0 end + if restrictInfo( this ) then return 0 end + return ( this.BulletData[ "DragCoef" ] or 0 ) / ACF.DragDiv +end - return This.Caliber * 10 - end +-- [ Armor Functions ] -- - -- Returns the muzzle velocity of the ammo in a crate or gun - function ents_methods:acfMuzzleVel() - local This = unwrap(self) +--- Returns the current health of an entity +-- @server +function ents_methods:acfPropHealth () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.BulletData then return 0 end - if not This.BulletData.MuzzleVel then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(This.BulletData.MuzzleVel * ACF.Scale, 2) - end + if not validPhysics( this ) then return 0 end + if restrictInfo( this ) then return 0 end + if not ACF_Check( this ) then return 0 end + return math.Round( this.ACF.Health or 0, 3 ) +end - -- Returns the mass of the projectile in a crate or gun - function ents_methods:acfProjectileMass() -local This = unwrap(self) +--- Returns the current armor of an entity +-- @server +function ents_methods:acfPropArmor () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.BulletData then return 0 end - if not This.BulletData.ProjMass then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(This.BulletData.ProjMass, 2) - end + if not validPhysics( this ) then return 0 end + if restrictInfo( this ) then return 0 end + if not ACF_Check( this ) then return 0 end + return math.Round( this.ACF.Armour or 0, 3 ) +end - -- Returns the drag coef of the ammo in a crate or gun - function ents_methods:acfDragCoef() - local This = unwrap(self) +--- Returns the max health of an entity +-- @server +function ents_methods:acfPropHealthMax () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.BulletData then return 0 end - if not This.BulletData.DragCoef then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(This.BulletData.DragCoef / ACF.DragDiv, 2) - end + if not validPhysics( this ) then return 0 end + if restrictInfo( this ) then return 0 end + if not ACF_Check( this ) then return 0 end + return math.Round( this.ACF.MaxHealth or 0, 3 ) +end - -- Returns the number of projectiles in a flechette round - function ents_methods:acfFLSpikes() - local This = unwrap(self) +--- Returns the max armor of an entity +-- @server +function ents_methods:acfPropArmorMax () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.BulletData then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return This.BulletData.Flechettes or 0 - end + if not validPhysics( this ) then return 0 end + if restrictInfo( this ) then return 0 end + if not ACF_Check( this ) then return 0 end + return math.Round( this.ACF.MaxArmour or 0, 3 ) +end - -- Returns the mass of a single spike in a FL round in a crate or gun - function ents_methods:acfFLSpikeMass() - local This = unwrap(self) +--- Returns the ductility of an entity +-- @server +function ents_methods:acfPropDuctility () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.BulletData then return 0 end - if not This.BulletData.FlechetteMass then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - return Round(This.BulletData.FlechetteMass, 2) - end + if not validPhysics( this ) then return 0 end + if restrictInfo( this ) then return 0 end + if not ACF_Check( this ) then return 0 end + return ( this.ACF.Ductility or 0 ) * 100 +end - -- Returns the radius of the spikes in a flechette round in mm - function ents_methods:acfFLSpikeRadius() - local This = unwrap(self) +-- [ Fuel Functions ] -- - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.BulletData then return 0 end - if not This.BulletData.FlechetteRadius then return 0 end +--- Returns true if the entity is an ACF fuel tank +-- @server +function ents_methods:acfIsFuel () + checktype( self, ents_metatable ) + local this = unwrap( self ) - return Round(This.BulletData.FlechetteRadius * 10, 2) - end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - -- Returns the penetration of an ACF round - function ents_methods:acfPenetration() - local This = unwrap(self) + return isFuel( this ) and not restrictInfo( this ) +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.BulletData then return 0 end - if not This.BulletData.Type then return 0 end +--- Returns true if the current engine requires fuel to run +-- @server +function ents_methods:acfFuelRequired () + checktype( self, ents_metatable ) + local this = unwrap( self ) - local BulletData = This.BulletData - local RoundData = RoundTypes[BulletData.Type] + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not RoundData then return 0 end + if not isEngine( this ) then return false end + if restrictInfo( this ) then return false end + return ( this.RequiresFuel and true ) or false +end - local DisplayData = RoundData.getDisplayData(BulletData) +--- Sets the ACF fuel tank refuel duty status, which supplies fuel to other fuel tanks +-- @server +function ents_methods:acfRefuelDuty ( on ) + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not DisplayData.MaxPen then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + checkpermission( instance, this, "entities.acf" ) - return Round(DisplayData.MaxPen, 2) - end + if not isFuel( this ) then return end + + this:TriggerInput( "Refuel Duty", on and true or false ) +end - -- Returns the blast radius of an ACF round - function ents_methods:acfBlastRadius() - local This = unwrap(self) +--- Returns the remaining liters or kilowatt hours of fuel in an ACF fuel tank or engine +-- @server +function ents_methods:acfFuel () + checktype( self, ents_metatable ) + local this = unwrap( self ) - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end - if not This.BulletData then return 0 end - if not This.BulletData.Type then return 0 end + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - local BulletData = This.BulletData - local RoundData = RoundTypes[BulletData.Type] + if restrictInfo( this ) then return 0 end + if isFuel( this ) then + return math.Round( this.Fuel, 3 ) + elseif isEngine( this ) then + if not #(this.FuelLink) then return 0 end --if no tanks, return 0 - if not RoundData then return 0 end + local liters = 0 + for _, tank in pairs( this.FuelLink ) do + if validPhysics( tank ) and tank.Active then + liters = liters + tank.Fuel + end + end - local DisplayData = RoundData.getDisplayData(BulletData) + return math.Round( liters, 3 ) + end + return 0 +end - if not DisplayData.BlastRadius then return 0 end +--- Returns the amount of fuel in an ACF fuel tank or linked to engine as a percentage of capacity +-- @server +function ents_methods:acfFuelLevel () + checktype( self, ents_metatable ) + local this = unwrap( self ) + + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + + if isFuel( this ) then + if restrictInfo( this ) then return 0 end + return math.Round( this.Fuel / this.Capacity, 3 ) + elseif isEngine( this ) then + if restrictInfo( this ) then return 0 end + if not #( this.FuelLink ) then return 0 end --if no tanks, return 0 + + local liters = 0 + local capacity = 0 + for _, tank in pairs( this.FuelLink ) do + if validPhysics( tank ) and tank.Active then + capacity = capacity + tank.Capacity + liters = liters + tank.Fuel + end + end + if not capacity > 0 then return 0 end - return Round(DisplayData.BlastRadius, 2) + return math.Round( liters / capacity, 3 ) end + return 0 +end - --Returns the number of rounds in active ammo crates linked to an ACF weapon - function ents_methods:acfAmmoCount() - local This = unwrap(self) - - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +--- Returns the current fuel consumption in liters per minute or kilowatts of an engine +-- @server +function ents_methods:acfFuelUse () + checktype( self, ents_metatable ) + local this = unwrap( self ) - local Count = 0 - local Source = LinkSource(This:GetClass(), "Crates") + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not Source then return 0 end + if not isEngine( this ) then return 0 end + if restrictInfo( this ) then return 0 end + if not #( this.FuelLink ) then return 0 end --if no tanks, return 0 - for Crate in pairs(Source(This)) do - if Crate.Load then - Count = Count + Crate.Ammo - end + local tank + for _, fueltank in pairs( this.FuelLink ) do + if validPhysics( fueltank ) and fueltank.Fuel > 0 and fueltank.Active then + tank = fueltank + break end - - return Count end + if not tank then return 0 end - --Returns the number of rounds in all ammo crates linked to an ACF weapon - function ents_methods:acfTotalAmmoCount () - local This = unwrap(self) + local Consumption + if this.FuelType == "Electric" then + Consumption = 60 * ( this.Torque * this.FlyRPM / 9548.8 ) * this.FuelUse + else + local Load = 0.3 + this.Throttle * 0.7 + Consumption = 60 * Load * this.FuelUse * ( this.FlyRPM / this.PeakKwRPM ) / ACF.FuelDensity[ tank.FuelType ] + end + return math.Round( Consumption, 3 ) +end - if not IsACFEntity(This) then return 0 end - if RestrictInfo(SF.instance.player, This) then return 0 end +--- Returns the peak fuel consumption in liters per minute or kilowatts of an engine at powerband max, for the current fuel type the engine is using +-- @server +function ents_methods:acfPeakFuelUse () + checktype( self, ents_metatable ) + local this = unwrap( self ) - local Count = 0 - local Source = LinkSource(This:GetClass(), "Crates") + if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - if not Source then return 0 end + if not isEngine( this ) then return 0 end + if restrictInfo( this ) then return 0 end + if not #( this.FuelLink ) then return 0 end --if no tanks, return 0 - for Crate in pairs(Source(This)) do - Count = Count + Crate.Ammo - end + local fuel = "Petrol" + local tank + for _, fueltank in pairs( this.FuelLink ) do + if fueltank.Fuel > 0 and fueltank.Active then tank = fueltank break end + end + if tank then fuel = tank.Fuel end - return Count + local Consumption + if this.FuelType == "Electric" then + Consumption = 60 * ( this.PeakTorque * this.LimitRPM / ( 4 * 9548.8 ) ) * this.FuelUse + else + local Load = 0.3 + this.Throttle * 0.7 + Consumption = 60 * this.FuelUse / ACF.FuelDensity[ fuel ] end -end) \ No newline at end of file + return math.Round( Consumption, 3 ) +end + +end